From c5fa6ab098df3df1960a0c5d0f584b232a02bede Mon Sep 17 00:00:00 2001 From: Yasuyuki Takeo Date: Thu, 11 Jul 2024 05:54:23 +0900 Subject: [PATCH] Schema change --- backend/Makefile | 2 +- ...04123000_create_users_and_cards_tables.sql | 32 +- backend/go.mod | 1 + backend/go.sum | 23 + backend/graph/generated.go | 9 +- backend/graph/model/models_gen.go | 1 - backend/graph/schema.graphqls | 3 - backend/graph/schema.resolvers.go | 1 - backend/graph/schema.resolvers_test.go | 442 ++++++++++++++++++ backend/main.go | 4 +- backend/pkg/config/config.go | 6 +- backend/pkg/repository/postgres.go | 4 +- backend/pkg/repository/postgres_test.go | 6 +- backend/testutils/database.go | 69 +++ 14 files changed, 565 insertions(+), 38 deletions(-) create mode 100644 backend/graph/schema.resolvers_test.go create mode 100644 backend/testutils/database.go diff --git a/backend/Makefile b/backend/Makefile index 7bf5b1b..6eaa04f 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -40,7 +40,7 @@ fmt: ## Format code test: ## Run tests printf "${GREEN}Run all tests\n\n${WHITE}"; \ go clean -testcache; \ - go test -v -race -run=./... -bench=./... ./...; \ + go test -race -run=./... -bench=./... ./...; \ printf "${GREEN}Done\n"; \ .PHONY: init diff --git a/backend/db/migrations/20240704123000_create_users_and_cards_tables.sql b/backend/db/migrations/20240704123000_create_users_and_cards_tables.sql index 71e9610..31d046d 100644 --- a/backend/db/migrations/20240704123000_create_users_and_cards_tables.sql +++ b/backend/db/migrations/20240704123000_create_users_and_cards_tables.sql @@ -3,23 +3,23 @@ CREATE TABLE users ( - id TEXT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE cardgroups ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE cards ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, front TEXT NOT NULL, back TEXT NOT NULL, review_date TIMESTAMP NOT NULL, @@ -35,8 +35,8 @@ CREATE TABLE cards CREATE TABLE cardgroup_users ( - cardgroup_id INTEGER NOT NULL, - user_id TEXT NOT NULL, + cardgroup_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, PRIMARY KEY (cardgroup_id, user_id), FOREIGN KEY (cardgroup_id) REFERENCES cardgroups (id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE @@ -44,14 +44,14 @@ CREATE TABLE cardgroup_users CREATE TABLE roles ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE ); CREATE TABLE user_roles ( - user_id TEXT NOT NULL, - role_id INTEGER NOT NULL, + user_id BIGINT NOT NULL, + role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id), FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, FOREIGN KEY (role_id) REFERENCES roles (id) ON DELETE CASCADE @@ -60,9 +60,9 @@ CREATE TABLE user_roles -- +goose Down -- SQL section 'Down' is executed when this migration is rolled back. -DROP TABLE IF EXISTS users_roles; +DROP TABLE IF EXISTS user_roles; DROP TABLE IF EXISTS roles; -DROP TABLE IF EXISTS card_groups_users; +DROP TABLE IF EXISTS cardgroup_users; DROP TABLE IF EXISTS cards; -DROP TABLE IF EXISTS card_groups; -DROP TABLE IF EXISTS users; \ No newline at end of file +DROP TABLE IF EXISTS cardgroups; +DROP TABLE IF EXISTS users; diff --git a/backend/go.mod b/backend/go.mod index dbbc8f6..d757eaf 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -6,6 +6,7 @@ require ( github.com/99designs/gqlgen v0.17.49 github.com/caarlos0/env/v11 v11.1.0 github.com/gorilla/websocket v1.5.0 + github.com/jinzhu/gorm v1.9.16 github.com/labstack/echo/v4 v4.12.0 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.31.0 diff --git a/backend/go.sum b/backend/go.sum index 12e19cc..7a10f40 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -10,12 +10,14 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= @@ -36,6 +38,8 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= @@ -46,6 +50,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -55,10 +61,14 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -81,8 +91,11 @@ github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -97,6 +110,8 @@ github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+k github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -106,6 +121,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= @@ -185,7 +202,9 @@ go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= @@ -193,9 +212,12 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= @@ -207,6 +229,7 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/backend/graph/generated.go b/backend/graph/generated.go index 84c6c78..aab053d 100644 --- a/backend/graph/generated.go +++ b/backend/graph/generated.go @@ -5801,20 +5801,13 @@ func (ec *executionContext) unmarshalInputNewUser(ctx context.Context, obj inter asMap[k] = v } - fieldsInOrder := [...]string{"id", "name"} + fieldsInOrder := [...]string{"name"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { - case "id": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) - data, err := ec.unmarshalNID2string(ctx, v) - if err != nil { - return it, err - } - it.ID = data case "name": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) data, err := ec.unmarshalNString2string(ctx, v) diff --git a/backend/graph/model/models_gen.go b/backend/graph/model/models_gen.go index d0f7b73..413cb5e 100644 --- a/backend/graph/model/models_gen.go +++ b/backend/graph/model/models_gen.go @@ -43,7 +43,6 @@ type NewRole struct { } type NewUser struct { - ID string `json:"id"` Name string `json:"name"` } diff --git a/backend/graph/schema.graphqls b/backend/graph/schema.graphqls index fa05331..8fc6ab7 100644 --- a/backend/graph/schema.graphqls +++ b/backend/graph/schema.graphqls @@ -1,5 +1,3 @@ -# schema.graphql - type Card { id: ID! front: String! @@ -49,7 +47,6 @@ input NewCardGroup { } input NewUser { - id: ID! name: String! } diff --git a/backend/graph/schema.resolvers.go b/backend/graph/schema.resolvers.go index f2e9bad..5aa695d 100644 --- a/backend/graph/schema.resolvers.go +++ b/backend/graph/schema.resolvers.go @@ -63,7 +63,6 @@ func (r *mutationResolver) DeleteCard(ctx context.Context, id string) (bool, err func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) { db := r.Resolver.DB user := &model.User{ - ID: input.ID, Name: input.Name, } diff --git a/backend/graph/schema.resolvers_test.go b/backend/graph/schema.resolvers_test.go new file mode 100644 index 0000000..c464a5c --- /dev/null +++ b/backend/graph/schema.resolvers_test.go @@ -0,0 +1,442 @@ +package graph + +import ( + "backend/pkg/config" + "backend/testutils" + "bytes" + "context" + "encoding/json" + "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/playground" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "github.com/stretchr/testify/assert" + "gorm.io/gorm" + "log" + "net/http" + "net/http/httptest" + "testing" +) + +var e *echo.Echo +var migrationFilePath = "../db/migrations" + +func TestMain(m *testing.M) { + + // Setup context + ctx := context.Background() + + // Set up the test database + pg, cleanup, err := testutils.SetupTestDB(ctx, config.Cfg.PGUser, config.Cfg.PGPassword, config.Cfg.PGDBName) + if err != nil { + log.Fatalf("Failed to setup test database: %v", err) + } + defer cleanup() + + // Run migrations + if err := pg.RunGooseMigrations(migrationFilePath); err != nil { + log.Fatalf("failed to run migrations: %v", err) + } + + // Setup Echo server + e = setupEchoServer(pg.DB) + + // Run the tests + m.Run() +} + +func setupEchoServer(db *gorm.DB) *echo.Echo { + resolver := &Resolver{DB: db} + srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolver})) + + e := echo.New() + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + e.GET("/", func(c echo.Context) error { + playground.Handler("GraphQL playground", "/query").ServeHTTP(c.Response(), c.Request()) + return nil + }) + + e.POST("/query", func(c echo.Context) error { + srv.ServeHTTP(c.Response(), c.Request()) + return nil + }) + + return e +} + +func TestGraphQLResolvers(t *testing.T) { + + t.Run("CreateCard", func(t *testing.T) { + input := map[string]interface{}{ + "input": map[string]interface{}{ + "front": "front", + "back": "back", + "review_date": "2024-07-10", + "interval_days": 1, + "cardgroup_id": "1", + }, + } + query := `mutation ($input: NewCard!) { + createCard(input: $input) { + id + front + back + review_date + interval_days + cardgroup_id + } + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"createCard":{"front":"front","back":"back","review_date":"2024-07-10","interval_days":1,"cardgroup_id":"1"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("UpdateCard", func(t *testing.T) { + input := map[string]interface{}{ + "id": "1", + "input": map[string]interface{}{ + "front": "updated front", + "back": "updated back", + "review_date": "2024-08-10", + "interval_days": 2, + "cardgroup_id": "1", + }, + } + query := `mutation ($id: ID!, $input: NewCard!) { + updateCard(id: $id, input: $input) { + id + front + back + review_date + interval_days + cardgroup_id + } + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"updateCard":{"front":"updated front","back":"updated back","review_date":"2024-08-10","interval_days":2,"cardgroup_id":"1"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("DeleteCard", func(t *testing.T) { + query := `mutation ($id: ID!) { deleteCard(id: $id) }` + input := map[string]interface{}{ + "id": "1", + } + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"deleteCard":true}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("CreateUser", func(t *testing.T) { + input := map[string]interface{}{ + "input": map[string]interface{}{ + "name": "John Doe", + }, + } + query := `mutation ($input: NewUser!) { + createUser(input: $input) { + id + name + } + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"createUser":{"name":"John Doe"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("UpdateUser", func(t *testing.T) { + input := map[string]interface{}{ + "id": "1", + "name": "Jane Doe", + } + query := `mutation ($id: ID!, $name: String!) { + updateUser(id: $id, name: $name) { + id + name + } + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"updateUser":{"name":"Jane Doe"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("DeleteUser", func(t *testing.T) { + query := `mutation ($id: ID!) { deleteUser(id: $id) }` + input := map[string]interface{}{ + "id": "1", + } + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"deleteUser":true}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("CreateCardGroup", func(t *testing.T) { + input := map[string]interface{}{ + "input": map[string]interface{}{ + "name": "group1", + }, + } + query := `mutation ($input: NewCardGroup!) { + createCardGroup(input: $input) { + id + name + } + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"createCardGroup":{"name":"group1"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("UpdateCardGroup", func(t *testing.T) { + input := map[string]interface{}{ + "id": "1", + "name": "group2", + } + query := `mutation ($id: ID!, $name: String!) { + updateCardGroup(id: $id, name: $name) { + id + name + } + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"updateCardGroup":{"name":"group2"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("DeleteCardGroup", func(t *testing.T) { + query := `mutation ($id: ID!) { deleteCardGroup(id: $id) }` + input := map[string]interface{}{ + "id": "1", + } + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"deleteCardGroup":true}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("CreateRole", func(t *testing.T) { + input := map[string]interface{}{ + "input": map[string]interface{}{ + "name": "admin", + }, + } + query := `mutation ($input: NewRole!) { + createRole(input: $input) { + id + name + } + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"createRole":{"name":"admin"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("UpdateRole", func(t *testing.T) { + input := map[string]interface{}{ + "id": "1", + "name": "user", + } + query := `mutation ($id: ID!, $name: String!) { + updateRole(id: $id, name: $name) { + id + name + } + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"updateRole":{"name":"user"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("DeleteRole", func(t *testing.T) { + query := `mutation ($id: ID!) { deleteRole(id: $id) }` + input := map[string]interface{}{ + "id": "1", + } + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"deleteRole":true}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("AddUserToCardGroup", func(t *testing.T) { + input := map[string]interface{}{ + "userId": "1", + "cardGroupId": "1", + } + query := `mutation ($userId: ID!, $cardGroupId: ID!) { + addUserToCardGroup(userId: $userId, cardGroupId: $cardGroupId) + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"addUserToCardGroup":true}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("RemoveUserFromCardGroup", func(t *testing.T) { + input := map[string]interface{}{ + "userId": "1", + "cardGroupId": "1", + } + query := `mutation ($userId: ID!, $cardGroupId: ID!) { + removeUserFromCardGroup(userId: $userId, cardGroupId: $cardGroupId) + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"removeUserFromCardGroup":true}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("AssignRoleToUser", func(t *testing.T) { + input := map[string]interface{}{ + "userId": "1", + "roleId": "1", + } + query := `mutation ($userId: ID!, $roleId: ID!) { + assignRoleToUser(userId: $userId, roleId: $roleId) + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"assignRoleToUser":true}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("RemoveRoleFromUser", func(t *testing.T) { + input := map[string]interface{}{ + "userId": "1", + "roleId": "1", + } + query := `mutation ($userId: ID!, $roleId: ID!) { + removeRoleFromUser(userId: $userId, roleId: $roleId) + }` + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"removeRoleFromUser":true}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("Cards", func(t *testing.T) { + query := `query { + cards { + id + front + back + review_date + interval_days + cardgroup_id + } + }` + expected := `{"data":{"cards":[{"id":"1","front":"front","back":"back","review_date":"2024-07-10","interval_days":1,"cardgroup_id":"1"}]}}` + testGraphQLQuery(t, e, query, nil, expected) + }) + + t.Run("Card", func(t *testing.T) { + query := `query ($id: ID!) { + card(id: $id) { + id + front + back + review_date + interval_days + cardgroup_id + } + }` + input := map[string]interface{}{ + "id": "1", + } + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"card":{"id":"1","front":"front","back":"back","review_date":"2024-07-10","interval_days":1,"cardgroup_id":"1"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("Users", func(t *testing.T) { + query := `query { + users { + id + name + } + }` + expected := `{"data":{"users":[{"id":"1","name":"John Doe"}]}}` + testGraphQLQuery(t, e, query, nil, expected) + }) + + t.Run("User", func(t *testing.T) { + query := `query ($id: ID!) { + user(id: $id) { + id + name + } + }` + input := map[string]interface{}{ + "id": "1", + } + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"user":{"id":"1","name":"John Doe"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("CardGroups", func(t *testing.T) { + query := `query { + cardGroups { + id + name + } + }` + expected := `{"data":{"cardGroups":[{"id":"1","name":"group1"}]}}` + testGraphQLQuery(t, e, query, nil, expected) + }) + + t.Run("CardGroup", func(t *testing.T) { + query := `query ($id: ID!) { + cardGroup(id: $id) { + id + name + } + }` + input := map[string]interface{}{ + "id": "1", + } + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"cardGroup":{"id":"1","name":"group1"}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) + + t.Run("Roles", func(t *testing.T) { + query := `query { + roles { + id + name + users { + id + name + } + } + }` + expected := `{"data":{"roles":[{"id":"1","name":"admin","users":[{"id":"1","name":"John Doe"}]}]}}` + testGraphQLQuery(t, e, query, nil, expected) + }) + + t.Run("Role", func(t *testing.T) { + query := `query ($id: ID!) { + role(id: $id) { + id + name + users { + id + name + } + } + }` + input := map[string]interface{}{ + "id": "1", + } + jsonInput, _ := json.Marshal(input) + expected := `{"data":{"role":{"id":"1","name":"admin","users":[{"id":"1","name":"John Doe"}]}}}` + testGraphQLQuery(t, e, query, jsonInput, expected) + }) +} + +func testGraphQLQuery(t *testing.T, e *echo.Echo, query string, variables []byte, expected string) { + req := httptest.NewRequest(http.MethodPost, "/query", bytes.NewBuffer(variables)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + + e.ServeHTTP(rec, req) + + assert.JSONEq(t, expected, rec.Body.String()) +} diff --git a/backend/main.go b/backend/main.go index 0353eb8..842880e 100644 --- a/backend/main.go +++ b/backend/main.go @@ -16,6 +16,8 @@ import ( "net/http" ) +var migrationFilePath = "./db/migrations" + func main() { e := echo.New() @@ -77,7 +79,7 @@ func initializeDatabase() *gorm.DB { } // Run migrations - if err := pg.RunGooseMigrations(); err != nil { + if err := pg.RunGooseMigrations(migrationFilePath); err != nil { log.Fatalf("failed to run migrations: %v", err) } diff --git a/backend/pkg/config/config.go b/backend/pkg/config/config.go index a04323c..7990717 100644 --- a/backend/pkg/config/config.go +++ b/backend/pkg/config/config.go @@ -14,9 +14,9 @@ type Config struct { // PostgreSQL configuration PGHost string `env:"PG_HOST" envDefault:"localhost"` - PGUser string `env:"PG_USER"` - PGPassword string `env:"PG_PASSWORD"` - PGDBName string `env:"PG_DBNAME"` + PGUser string `env:"PG_USER" envDefault:"testuser"` + PGPassword string `env:"PG_PASSWORD" envDefault:"testpassword"` + PGDBName string `env:"PG_DBNAME" envDefault:"testdb"` PGPort string `env:"PG_PORT" envDefault:"5432"` PGSSLMode string `env:"PG_SSLMODE" envDefault:"disable"` } diff --git a/backend/pkg/repository/postgres.go b/backend/pkg/repository/postgres.go index 8d01a19..b15e570 100644 --- a/backend/pkg/repository/postgres.go +++ b/backend/pkg/repository/postgres.go @@ -44,9 +44,9 @@ func (pg *Postgres) Open() error { return nil } -func (pg *Postgres) RunGooseMigrations() error { +func (pg *Postgres) RunGooseMigrations(path string) error { dsn := pg.DSN() - cmd := exec.Command("goose", "-dir", "../../db/migrations", "postgres", dsn, "up") + cmd := exec.Command("goose", "-dir", path, "postgres", dsn, "up") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { diff --git a/backend/pkg/repository/postgres_test.go b/backend/pkg/repository/postgres_test.go index 423f28c..55d6e7a 100644 --- a/backend/pkg/repository/postgres_test.go +++ b/backend/pkg/repository/postgres_test.go @@ -12,6 +12,8 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) +var migrationFilePath = "../../db/migrations" + // setupTestDB sets up a Postgres test container and returns the connection and a cleanup function. func setupTestDB(ctx context.Context) (*Postgres, func(), error) { req := testcontainers.ContainerRequest{ @@ -89,7 +91,7 @@ func TestPostgres_Open(t *testing.T) { t.Fatalf("failed to ping database: %s", err) } - if err = pg.RunGooseMigrations(); err != nil { + if err = pg.RunGooseMigrations(migrationFilePath); err != nil { t.Fatalf("goose migration failed: %s", err) } @@ -107,7 +109,7 @@ func TestPostgres_RunGooseMigrations(t *testing.T) { } defer cleanup() - if err = pg.RunGooseMigrations(); err != nil { + if err = pg.RunGooseMigrations(migrationFilePath); err != nil { t.Fatalf("goose migration failed: %s", err) } diff --git a/backend/testutils/database.go b/backend/testutils/database.go new file mode 100644 index 0000000..6c271c2 --- /dev/null +++ b/backend/testutils/database.go @@ -0,0 +1,69 @@ +package testutils + +import ( + "context" + "fmt" + "log" + "time" + + "backend/pkg/repository" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +// SetupTestDB sets up a Postgres test container and returns the connection and a cleanup function. +func SetupTestDB(ctx context.Context, user, password, dbName string) (*repository.Postgres, func(), error) { + req := testcontainers.ContainerRequest{ + Image: "postgres:latest", + ExposedPorts: []string{"5432/tcp"}, + Env: map[string]string{ + "POSTGRES_USER": user, + "POSTGRES_PASSWORD": password, + "POSTGRES_DB": dbName, + }, + WaitingFor: wait.ForListeningPort("5432/tcp").WithStartupTimeout(5 * time.Minute), + } + + pgContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to start postgres container: %w", err) + } + + host, err := pgContainer.Host(ctx) + if err != nil { + pgContainer.Terminate(ctx) + return nil, nil, fmt.Errorf("failed to get container host: %w", err) + } + + port, err := pgContainer.MappedPort(ctx, "5432") + if err != nil { + pgContainer.Terminate(ctx) + return nil, nil, fmt.Errorf("failed to get container port: %w", err) + } + + config := repository.DBConfig{ + Host: host, + User: user, + Password: password, + DBName: dbName, + Port: port.Port(), + SSLMode: "disable", + } + + pg := repository.NewPostgres(config) + if err = pg.Open(); err != nil { + pgContainer.Terminate(ctx) + return nil, nil, fmt.Errorf("failed to open database: %w", err) + } + + cleanup := func() { + if err := pgContainer.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate postgres container: %s", err) + } + } + + return pg, cleanup, nil +}