From 0ddf44d6b6dbd126ebff2fa5fda49908e7ac4d9c Mon Sep 17 00:00:00 2001 From: Yasuyuki Takeo Date: Wed, 10 Jul 2024 04:50:12 +0900 Subject: [PATCH] Implement initial schema.resolvers.go --- README.md | 12 +- backend/.env.example | 6 + backend/Makefile | 8 +- ...04123000_create_users_and_cards_tables.sql | 4 +- backend/go.mod | 3 +- backend/go.sum | 2 + backend/graph/generated.go | 218 ++++--------- backend/graph/model/models_gen.go | 6 +- backend/graph/resolver.go | 9 +- backend/graph/schema.graphqls | 10 +- backend/graph/schema.resolvers.go | 286 ++++++++++++++++-- backend/main.go | 69 ++++- backend/pkg/config/config.go | 32 ++ backend/pkg/repository/postgres.go | 2 +- backend/pkg/repository/postgres_test.go | 4 +- 15 files changed, 456 insertions(+), 215 deletions(-) create mode 100644 backend/pkg/config/config.go diff --git a/README.md b/README.md index 8f89bef..2c49378 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,23 @@ # Framingo Armond -Tinder like Flashcard App +Tinder like Flashcard App. # Environment +This repository is structured as a mono repository. + +| Directroy | Discription | +|:--|:--| +|frontend | frontend implementation | +|backend| backend GraphQL API server | + ## frontend - Typescript - React - Vite ## backend +- Go - Echo -- + # Run Locally ``` docker compose up --build diff --git a/backend/.env.example b/backend/.env.example index e69de29..2027504 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -0,0 +1,6 @@ +PG_HOST=localhost +PG_USER=your_db_user +PG_PASSWORD=your_db_password +PG_DBNAME=your_db_name +PG_PORT=5432 +PG_SSLMODE=disable \ No newline at end of file diff --git a/backend/Makefile b/backend/Makefile index 505e6b9..7bf5b1b 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -16,10 +16,14 @@ ENVS = \ update: ## Update go.mod go mod tidy -.PHONY: gen -gen: ## Generate GraphQL implementations +.PHONY: clear_gen +clear_gen: ## Clear GraphQL implementations printf "${GREEN} Clean up files\n\n"; \ cd ./graph; rm -fR generated.go resolver.go schema.resolvers.go model; cd ${ORG_PATH} ; \ + printf "${GREEN}Done\n"; \ + +.PHONY: gen +gen: ## Generate GraphQL implementations printf "${GREEN} Update gqlgen\n\n"; \ go get github.com/99designs/gqlgen@latest; cd ${ORG_PATH} ; \ printf "${GREEN} Generating GraphQL related sources\n\n"; \ 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 3ece683..71e9610 100644 --- a/backend/db/migrations/20240704123000_create_users_and_cards_tables.sql +++ b/backend/db/migrations/20240704123000_create_users_and_cards_tables.sql @@ -33,7 +33,7 @@ CREATE TABLE cards ON DELETE CASCADE ); -CREATE TABLE cardgroups_users +CREATE TABLE cardgroup_users ( cardgroup_id INTEGER NOT NULL, user_id TEXT NOT NULL, @@ -48,7 +48,7 @@ CREATE TABLE roles name TEXT NOT NULL UNIQUE ); -CREATE TABLE users_roles +CREATE TABLE user_roles ( user_id TEXT NOT NULL, role_id INTEGER NOT NULL, diff --git a/backend/go.mod b/backend/go.mod index 81f282f..dbbc8f6 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,6 +4,8 @@ go 1.22.5 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/labstack/echo/v4 v4.12.0 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.31.0 @@ -36,7 +38,6 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect diff --git a/backend/go.sum b/backend/go.sum index 10268ea..12e19cc 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -20,6 +20,8 @@ github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsVi 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= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/caarlos0/env/v11 v11.1.0 h1:a5qZqieE9ZfzdvbbdhTalRrHT5vu/4V1/ad1Ka6frhI= +github.com/caarlos0/env/v11 v11.1.0/go.mod h1:LwgkYk1kDvfGpHthrWWLof3Ny7PezzFwS4QrsJdHTMo= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= diff --git a/backend/graph/generated.go b/backend/graph/generated.go index b6c6e1b..84c6c78 100644 --- a/backend/graph/generated.go +++ b/backend/graph/generated.go @@ -50,6 +50,7 @@ type ComplexityRoot struct { Card struct { Back func(childComplexity int) int CardGroup func(childComplexity int) int + CardgroupID func(childComplexity int) int Created func(childComplexity int) int Front func(childComplexity int) int ID func(childComplexity int) int @@ -67,11 +68,6 @@ type ComplexityRoot struct { Users func(childComplexity int) int } - CardGroupUser struct { - CardgroupID func(childComplexity int) int - UserID func(childComplexity int) int - } - Mutation struct { AddUserToCardGroup func(childComplexity int, userID string, cardGroupID string) int AssignRoleToUser func(childComplexity int, userID string, roleID string) int @@ -180,6 +176,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Card.CardGroup(childComplexity), true + case "Card.cardgroup_id": + if e.complexity.Card.CardgroupID == nil { + break + } + + return e.complexity.Card.CardgroupID(childComplexity), true + case "Card.created": if e.complexity.Card.Created == nil { break @@ -264,20 +267,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.CardGroup.Users(childComplexity), true - case "CardGroupUser.cardgroup_id": - if e.complexity.CardGroupUser.CardgroupID == nil { - break - } - - return e.complexity.CardGroupUser.CardgroupID(childComplexity), true - - case "CardGroupUser.user_id": - if e.complexity.CardGroupUser.UserID == nil { - break - } - - return e.complexity.CardGroupUser.UserID(childComplexity), true - case "Mutation.addUserToCardGroup": if e.complexity.Mutation.AddUserToCardGroup == nil { break @@ -1470,6 +1459,50 @@ func (ec *executionContext) fieldContext_Card_updated(_ context.Context, field g return fc, nil } +func (ec *executionContext) _Card_cardgroup_id(ctx context.Context, field graphql.CollectedField, obj *model.Card) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Card_cardgroup_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CardgroupID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Card_cardgroup_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Card", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Card_cardGroup(ctx context.Context, field graphql.CollectedField, obj *model.Card) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Card_cardGroup(ctx, field) if err != nil { @@ -1757,6 +1790,8 @@ func (ec *executionContext) fieldContext_CardGroup_cards(_ context.Context, fiel return ec.fieldContext_Card_created(ctx, field) case "updated": return ec.fieldContext_Card_updated(ctx, field) + case "cardgroup_id": + return ec.fieldContext_Card_cardgroup_id(ctx, field) case "cardGroup": return ec.fieldContext_Card_cardGroup(ctx, field) } @@ -1824,94 +1859,6 @@ func (ec *executionContext) fieldContext_CardGroup_users(_ context.Context, fiel return fc, nil } -func (ec *executionContext) _CardGroupUser_cardgroup_id(ctx context.Context, field graphql.CollectedField, obj *model.CardGroupUser) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_CardGroupUser_cardgroup_id(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.CardgroupID, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNID2string(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_CardGroupUser_cardgroup_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "CardGroupUser", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type ID does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _CardGroupUser_user_id(ctx context.Context, field graphql.CollectedField, obj *model.CardGroupUser) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_CardGroupUser_user_id(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.UserID, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNID2string(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_CardGroupUser_user_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "CardGroupUser", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type ID does not have child fields") - }, - } - return fc, nil -} - func (ec *executionContext) _Mutation_createCard(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Mutation_createCard(ctx, field) if err != nil { @@ -1965,6 +1912,8 @@ func (ec *executionContext) fieldContext_Mutation_createCard(ctx context.Context return ec.fieldContext_Card_created(ctx, field) case "updated": return ec.fieldContext_Card_updated(ctx, field) + case "cardgroup_id": + return ec.fieldContext_Card_cardgroup_id(ctx, field) case "cardGroup": return ec.fieldContext_Card_cardGroup(ctx, field) } @@ -2038,6 +1987,8 @@ func (ec *executionContext) fieldContext_Mutation_updateCard(ctx context.Context return ec.fieldContext_Card_created(ctx, field) case "updated": return ec.fieldContext_Card_updated(ctx, field) + case "cardgroup_id": + return ec.fieldContext_Card_cardgroup_id(ctx, field) case "cardGroup": return ec.fieldContext_Card_cardGroup(ctx, field) } @@ -2953,6 +2904,8 @@ func (ec *executionContext) fieldContext_Query_cards(_ context.Context, field gr return ec.fieldContext_Card_created(ctx, field) case "updated": return ec.fieldContext_Card_updated(ctx, field) + case "cardgroup_id": + return ec.fieldContext_Card_cardgroup_id(ctx, field) case "cardGroup": return ec.fieldContext_Card_cardGroup(ctx, field) } @@ -3012,6 +2965,8 @@ func (ec *executionContext) fieldContext_Query_card(ctx context.Context, field g return ec.fieldContext_Card_created(ctx, field) case "updated": return ec.fieldContext_Card_updated(ctx, field) + case "cardgroup_id": + return ec.fieldContext_Card_cardgroup_id(ctx, field) case "cardGroup": return ec.fieldContext_Card_cardGroup(ctx, field) } @@ -5927,6 +5882,11 @@ func (ec *executionContext) _Card(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { out.Invalids++ } + case "cardgroup_id": + out.Values[i] = ec._Card_cardgroup_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "cardGroup": out.Values[i] = ec._Card_cardGroup(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -6019,50 +5979,6 @@ func (ec *executionContext) _CardGroup(ctx context.Context, sel ast.SelectionSet return out } -var cardGroupUserImplementors = []string{"CardGroupUser"} - -func (ec *executionContext) _CardGroupUser(ctx context.Context, sel ast.SelectionSet, obj *model.CardGroupUser) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, cardGroupUserImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("CardGroupUser") - case "cardgroup_id": - out.Values[i] = ec._CardGroupUser_cardgroup_id(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "user_id": - out.Values[i] = ec._CardGroupUser_user_id(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.deferred, int32(len(deferred))) - - for label, dfs := range deferred { - ec.processDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - var mutationImplementors = []string{"Mutation"} func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { diff --git a/backend/graph/model/models_gen.go b/backend/graph/model/models_gen.go index ac11039..d0f7b73 100644 --- a/backend/graph/model/models_gen.go +++ b/backend/graph/model/models_gen.go @@ -10,6 +10,7 @@ type Card struct { IntervalDays int `json:"interval_days"` Created string `json:"created"` Updated string `json:"updated"` + CardgroupID string `json:"cardgroup_id"` CardGroup *CardGroup `json:"cardGroup"` } @@ -22,11 +23,6 @@ type CardGroup struct { Users []*User `json:"users"` } -type CardGroupUser struct { - CardgroupID string `json:"cardgroup_id"` - UserID string `json:"user_id"` -} - type Mutation struct { } diff --git a/backend/graph/resolver.go b/backend/graph/resolver.go index a25c09c..dc3dc8d 100644 --- a/backend/graph/resolver.go +++ b/backend/graph/resolver.go @@ -1,7 +1,8 @@ package graph -// This file will not be regenerated automatically. -// -// It serves as dependency injection for your app, add any dependencies you require here. +import "gorm.io/gorm" -type Resolver struct{} +// Resolver struct updated to include a DB field. +type Resolver struct { + DB *gorm.DB +} diff --git a/backend/graph/schema.graphqls b/backend/graph/schema.graphqls index 2b17831..fa05331 100644 --- a/backend/graph/schema.graphqls +++ b/backend/graph/schema.graphqls @@ -1,6 +1,4 @@ -# GraphQL schema example -# -# https://gqlgen.com/getting-started/ +# schema.graphql type Card { id: ID! @@ -10,6 +8,7 @@ type Card { interval_days: Int! created: String! updated: String! + cardgroup_id: ID! cardGroup: CardGroup! } @@ -37,11 +36,6 @@ type User { roles: [Role!]! } -type CardGroupUser { - cardgroup_id: ID! - user_id: ID! -} - input NewCard { front: String! back: String! diff --git a/backend/graph/schema.resolvers.go b/backend/graph/schema.resolvers.go index 1adf537..f2e9bad 100644 --- a/backend/graph/schema.resolvers.go +++ b/backend/graph/schema.resolvers.go @@ -7,127 +7,363 @@ package graph import ( "backend/graph/model" "context" - "fmt" ) // CreateCard is the resolver for the createCard field. func (r *mutationResolver) CreateCard(ctx context.Context, input model.NewCard) (*model.Card, error) { - panic(fmt.Errorf("not implemented: CreateCard - createCard")) + db := r.Resolver.DB + card := &model.Card{ + Front: input.Front, + Back: input.Back, + ReviewDate: input.ReviewDate, + IntervalDays: *input.IntervalDays, + CardgroupID: input.CardgroupID, + } + + if err := db.Create(card).Error; err != nil { + return nil, err + } + + return card, nil } // UpdateCard is the resolver for the updateCard field. func (r *mutationResolver) UpdateCard(ctx context.Context, id string, input model.NewCard) (*model.Card, error) { - panic(fmt.Errorf("not implemented: UpdateCard - updateCard")) + db := r.Resolver.DB + var card model.Card + + if err := db.First(&card, id).Error; err != nil { + return nil, err + } + + card.Front = input.Front + card.Back = input.Back + card.ReviewDate = input.ReviewDate + card.IntervalDays = *input.IntervalDays + card.CardgroupID = input.CardgroupID + + if err := db.Save(&card).Error; err != nil { + return nil, err + } + + return &card, nil } // DeleteCard is the resolver for the deleteCard field. func (r *mutationResolver) DeleteCard(ctx context.Context, id string) (bool, error) { - panic(fmt.Errorf("not implemented: DeleteCard - deleteCard")) + db := r.Resolver.DB + if err := db.Delete(&model.Card{}, id).Error; err != nil { + return false, err + } + + return true, nil } // CreateUser is the resolver for the createUser field. func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) { - panic(fmt.Errorf("not implemented: CreateUser - createUser")) + db := r.Resolver.DB + user := &model.User{ + ID: input.ID, + Name: input.Name, + } + + if err := db.Create(user).Error; err != nil { + return nil, err + } + + return user, nil } // UpdateUser is the resolver for the updateUser field. func (r *mutationResolver) UpdateUser(ctx context.Context, id string, name string) (*model.User, error) { - panic(fmt.Errorf("not implemented: UpdateUser - updateUser")) + db := r.Resolver.DB + var user model.User + + if err := db.First(&user, id).Error; err != nil { + return nil, err + } + + user.Name = name + + if err := db.Save(&user).Error; err != nil { + return nil, err + } + + return &user, nil } // DeleteUser is the resolver for the deleteUser field. func (r *mutationResolver) DeleteUser(ctx context.Context, id string) (bool, error) { - panic(fmt.Errorf("not implemented: DeleteUser - deleteUser")) + db := r.Resolver.DB + if err := db.Delete(&model.User{}, id).Error; err != nil { + return false, err + } + + return true, nil } // CreateCardGroup is the resolver for the createCardGroup field. func (r *mutationResolver) CreateCardGroup(ctx context.Context, input model.NewCardGroup) (*model.CardGroup, error) { - panic(fmt.Errorf("not implemented: CreateCardGroup - createCardGroup")) + db := r.Resolver.DB + cardGroup := &model.CardGroup{ + Name: input.Name, + } + + if err := db.Create(cardGroup).Error; err != nil { + return nil, err + } + + return cardGroup, nil } // UpdateCardGroup is the resolver for the updateCardGroup field. func (r *mutationResolver) UpdateCardGroup(ctx context.Context, id string, name string) (*model.CardGroup, error) { - panic(fmt.Errorf("not implemented: UpdateCardGroup - updateCardGroup")) + db := r.Resolver.DB + var cardGroup model.CardGroup + + if err := db.First(&cardGroup, id).Error; err != nil { + return nil, err + } + + cardGroup.Name = name + + if err := db.Save(&cardGroup).Error; err != nil { + return nil, err + } + + return &cardGroup, nil } // DeleteCardGroup is the resolver for the deleteCardGroup field. func (r *mutationResolver) DeleteCardGroup(ctx context.Context, id string) (bool, error) { - panic(fmt.Errorf("not implemented: DeleteCardGroup - deleteCardGroup")) + db := r.Resolver.DB + if err := db.Delete(&model.CardGroup{}, id).Error; err != nil { + return false, err + } + + return true, nil } // CreateRole is the resolver for the createRole field. func (r *mutationResolver) CreateRole(ctx context.Context, input model.NewRole) (*model.Role, error) { - panic(fmt.Errorf("not implemented: CreateRole - createRole")) + db := r.Resolver.DB + role := &model.Role{ + Name: input.Name, + } + + if err := db.Create(role).Error; err != nil { + return nil, err + } + + return role, nil } // UpdateRole is the resolver for the updateRole field. func (r *mutationResolver) UpdateRole(ctx context.Context, id string, name string) (*model.Role, error) { - panic(fmt.Errorf("not implemented: UpdateRole - updateRole")) + db := r.Resolver.DB + var role model.Role + + if err := db.First(&role, id).Error; err != nil { + return nil, err + } + + role.Name = name + + if err := db.Save(&role).Error; err != nil { + return nil, err + } + + return &role, nil } // DeleteRole is the resolver for the deleteRole field. func (r *mutationResolver) DeleteRole(ctx context.Context, id string) (bool, error) { - panic(fmt.Errorf("not implemented: DeleteRole - deleteRole")) + db := r.Resolver.DB + if err := db.Delete(&model.Role{}, id).Error; err != nil { + return false, err + } + + return true, nil } // AddUserToCardGroup is the resolver for the addUserToCardGroup field. func (r *mutationResolver) AddUserToCardGroup(ctx context.Context, userID string, cardGroupID string) (bool, error) { - panic(fmt.Errorf("not implemented: AddUserToCardGroup - addUserToCardGroup")) + db := r.Resolver.DB + var cardGroup model.CardGroup + var user model.User + + if err := db.First(&cardGroup, cardGroupID).Error; err != nil { + return false, err + } + + if err := db.First(&user, userID).Error; err != nil { + return false, err + } + + if err := db.Model(&cardGroup).Association("Users").Append(&user); err != nil { + return false, err + } + + return true, nil } // RemoveUserFromCardGroup is the resolver for the removeUserFromCardGroup field. func (r *mutationResolver) RemoveUserFromCardGroup(ctx context.Context, userID string, cardGroupID string) (bool, error) { - panic(fmt.Errorf("not implemented: RemoveUserFromCardGroup - removeUserFromCardGroup")) + db := r.Resolver.DB + var cardGroup model.CardGroup + var user model.User + + if err := db.First(&cardGroup, cardGroupID).Error; err != nil { + return false, err + } + + if err := db.First(&user, userID).Error; err != nil { + return false, err + } + + if err := db.Model(&cardGroup).Association("Users").Delete(&user); err != nil { + return false, err + } + + return true, nil } // AssignRoleToUser is the resolver for the assignRoleToUser field. func (r *mutationResolver) AssignRoleToUser(ctx context.Context, userID string, roleID string) (bool, error) { - panic(fmt.Errorf("not implemented: AssignRoleToUser - assignRoleToUser")) + db := r.Resolver.DB + var role model.Role + var user model.User + + if err := db.First(&role, roleID).Error; err != nil { + return false, err + } + + if err := db.First(&user, userID).Error; err != nil { + return false, err + } + + if err := db.Model(&user).Association("Roles").Append(&role); err != nil { + return false, err + } + + return true, nil } // RemoveRoleFromUser is the resolver for the removeRoleFromUser field. func (r *mutationResolver) RemoveRoleFromUser(ctx context.Context, userID string, roleID string) (bool, error) { - panic(fmt.Errorf("not implemented: RemoveRoleFromUser - removeRoleFromUser")) + db := r.Resolver.DB + var role model.Role + var user model.User + + if err := db.First(&role, roleID).Error; err != nil { + return false, err + } + + if err := db.First(&user, userID).Error; err != nil { + return false, err + } + + if err := db.Model(&user).Association("Roles").Delete(&role); err != nil { + return false, err + } + + return true, nil } // Cards is the resolver for the cards field. func (r *queryResolver) Cards(ctx context.Context) ([]*model.Card, error) { - panic(fmt.Errorf("not implemented: Cards - cards")) + db := r.Resolver.DB + var cards []*model.Card + + if err := db.Find(&cards).Error; err != nil { + return nil, err + } + + return cards, nil } // Card is the resolver for the card field. func (r *queryResolver) Card(ctx context.Context, id string) (*model.Card, error) { - panic(fmt.Errorf("not implemented: Card - card")) + db := r.Resolver.DB + var card model.Card + + if err := db.First(&card, id).Error; err != nil { + return nil, err + } + + return &card, nil } // Users is the resolver for the users field. func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) { - panic(fmt.Errorf("not implemented: Users - users")) + db := r.Resolver.DB + var users []*model.User + + if err := db.Find(&users).Error; err != nil { + return nil, err + } + + return users, nil } // User is the resolver for the user field. func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) { - panic(fmt.Errorf("not implemented: User - user")) + db := r.Resolver.DB + var user model.User + + if err := db.First(&user, id).Error; err != nil { + return nil, err + } + + return &user, nil } // CardGroups is the resolver for the cardGroups field. func (r *queryResolver) CardGroups(ctx context.Context) ([]*model.CardGroup, error) { - panic(fmt.Errorf("not implemented: CardGroups - cardGroups")) + db := r.Resolver.DB + var cardGroups []*model.CardGroup + + if err := db.Find(&cardGroups).Error; err != nil { + return nil, err + } + + return cardGroups, nil } // CardGroup is the resolver for the cardGroup field. func (r *queryResolver) CardGroup(ctx context.Context, id string) (*model.CardGroup, error) { - panic(fmt.Errorf("not implemented: CardGroup - cardGroup")) + db := r.Resolver.DB + var cardGroup model.CardGroup + + if err := db.First(&cardGroup, id).Error; err != nil { + return nil, err + } + + return &cardGroup, nil } // Roles is the resolver for the roles field. func (r *queryResolver) Roles(ctx context.Context) ([]*model.Role, error) { - panic(fmt.Errorf("not implemented: Roles - roles")) + db := r.Resolver.DB + var roles []*model.Role + + if err := db.Find(&roles).Error; err != nil { + return nil, err + } + + return roles, nil } // Role is the resolver for the role field. func (r *queryResolver) Role(ctx context.Context, id string) (*model.Role, error) { - panic(fmt.Errorf("not implemented: Role - role")) + db := r.Resolver.DB + var role model.Role + + if err := db.First(&role, id).Error; err != nil { + return nil, err + } + + return &role, nil } // Mutation returns MutationResolver implementation. diff --git a/backend/main.go b/backend/main.go index 0819721..0353eb8 100644 --- a/backend/main.go +++ b/backend/main.go @@ -2,28 +2,46 @@ package main import ( "backend/graph" - "log" - "os" - + "backend/pkg/config" + "backend/pkg/repository" "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/transport" "github.com/99designs/gqlgen/graphql/playground" + "github.com/gorilla/websocket" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "golang.org/x/xerrors" + "gorm.io/gorm" + "log" + "net/http" ) -const defaultPort = "8080" - func main() { - port := os.Getenv("PORT") - if port == "" { - port = defaultPort - } e := echo.New() e.Use(middleware.Logger()) e.Use(middleware.Recover()) - srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}})) + // Initialize the database + db := initializeDatabase() + + // Create a new resolver with the database connection + resolver := &graph.Resolver{ + DB: db, + } + + srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: resolver})) + + // Setup WebSocket for subscriptions + srv.AddTransport(&transport.Websocket{ + Upgrader: websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, + }, + }) e.GET("/", func(c echo.Context) error { playground.Handler("GraphQL playground", "/query").ServeHTTP(c.Response(), c.Request()) @@ -35,6 +53,33 @@ func main() { return nil }) - log.Printf("connect to http://localhost:%s/ for GraphQL playground", port) - log.Fatal(e.Start(":" + port)) + xerrors.Errorf("connect to http://localhost:%d/ for GraphQL playground", config.Cfg.GqlPort) +} + +// initializeDatabase encapsulates the database configuration and initialization logic +func initializeDatabase() *gorm.DB { + // Database configuration + dbConfig := repository.DBConfig{ + Host: config.Cfg.PGHost, + User: config.Cfg.PGUser, + Password: config.Cfg.PGPassword, + DBName: config.Cfg.PGDBName, + Port: config.Cfg.PGPort, + SSLMode: config.Cfg.PGSSLMode, + } + + // Initialize the Postgres instance + pg := repository.NewPostgres(dbConfig) + + // Open the database connection + if err := pg.Open(); err != nil { + log.Fatalf("failed to connect to the database: %v", err) + } + + // Run migrations + if err := pg.RunGooseMigrations(); err != nil { + log.Fatalf("failed to run migrations: %v", err) + } + + return pg.DB } diff --git a/backend/pkg/config/config.go b/backend/pkg/config/config.go new file mode 100644 index 0000000..a04323c --- /dev/null +++ b/backend/pkg/config/config.go @@ -0,0 +1,32 @@ +package config + +import ( + "github.com/caarlos0/env/v11" + "golang.org/x/xerrors" +) + +// Config structure holds all the configuration values +type Config struct { + // System Settings + Port int `env:"PORT" envDefault:"1323"` + GqlPort int `env:"GQL_PORT" envDefault:"8080"` + GoEnv string `env:"GO_ENV" envDefault:"dev"` + + // PostgreSQL configuration + PGHost string `env:"PG_HOST" envDefault:"localhost"` + PGUser string `env:"PG_USER"` + PGPassword string `env:"PG_PASSWORD"` + PGDBName string `env:"PG_DBNAME"` + PGPort string `env:"PG_PORT" envDefault:"5432"` + PGSSLMode string `env:"PG_SSLMODE" envDefault:"disable"` +} + +// Cfg is the package-level variable that holds the parsed configuration +var Cfg Config + +// init function initializes the package-level variable Cfg by parsing environment variables +func init() { + if err := env.Parse(&Cfg); err != nil { + xerrors.Errorf("Failed to parse environment variables: %+v", err) + } +} diff --git a/backend/pkg/repository/postgres.go b/backend/pkg/repository/postgres.go index a42abba..8d01a19 100644 --- a/backend/pkg/repository/postgres.go +++ b/backend/pkg/repository/postgres.go @@ -44,7 +44,7 @@ func (pg *Postgres) Open() error { return nil } -func (pg *Postgres) runGooseMigrations() error { +func (pg *Postgres) RunGooseMigrations() error { dsn := pg.DSN() cmd := exec.Command("goose", "-dir", "../../db/migrations", "postgres", dsn, "up") cmd.Stdout = os.Stdout diff --git a/backend/pkg/repository/postgres_test.go b/backend/pkg/repository/postgres_test.go index eb0e828..701343a 100644 --- a/backend/pkg/repository/postgres_test.go +++ b/backend/pkg/repository/postgres_test.go @@ -87,7 +87,7 @@ func TestPostgres_Open(t *testing.T) { t.Fatalf("failed to ping database: %s", err) } - err = pg.runGooseMigrations() + err = pg.RunGooseMigrations() if err != nil { t.Fatalf("goose migration failed: %s", err) } @@ -104,7 +104,7 @@ func TestPostgres_runGooseMigrations(t *testing.T) { } defer cleanup() - err = pg.runGooseMigrations() + err = pg.RunGooseMigrations() if err != nil { t.Fatalf("goose migration failed: %s", err) }