From 1b2420901b3712c4e90e25b6085527af6c1573aa Mon Sep 17 00:00:00 2001 From: Yasuyuki Takeo Date: Wed, 31 Jul 2024 20:44:11 +0900 Subject: [PATCH] Implement dataloader base --- backend/go.mod | 3 +- backend/go.sum | 5 +- backend/gqlgen.yml | 21 + backend/graph/dataloader.go | 184 +++++ backend/graph/generated.go | 931 ++++++++----------------- backend/graph/resolver.go | 1 + backend/graph/schema.graphqls | 8 +- backend/graph/schema.resolvers.go | 153 +++- backend/graph/schema.resolvers_test.go | 604 +++++++--------- backend/graph/services/card.go | 11 +- backend/graph/services/cardgroup.go | 8 + backend/graph/services/role.go | 8 + backend/graph/services/service.go | 4 + backend/graph/services/user.go | 8 + backend/web/server/server.go | 7 +- 15 files changed, 939 insertions(+), 1017 deletions(-) create mode 100644 backend/graph/dataloader.go diff --git a/backend/go.mod b/backend/go.mod index d4334bb..9bbc23d 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -7,6 +7,7 @@ require ( github.com/caarlos0/env/v11 v11.1.0 github.com/go-playground/validator/v10 v10.22.0 github.com/gorilla/websocket v1.5.0 + github.com/graph-gophers/dataloader/v7 v7.1.0 github.com/labstack/echo/v4 v4.12.0 github.com/labstack/gommon v0.4.2 github.com/pkg/errors v0.9.1 @@ -14,7 +15,7 @@ require ( github.com/testcontainers/testcontainers-go v0.31.0 github.com/vektah/gqlparser/v2 v2.5.16 golang.org/x/net v0.26.0 - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 + golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f gorm.io/driver/postgres v1.5.9 gorm.io/gorm v1.25.10 ) diff --git a/backend/go.sum b/backend/go.sum index 4940c92..92598c6 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -79,6 +79,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/dataloader/v7 v7.1.0 h1:Wn8HGF/q7MNXcvfaBnLEPEFJttVHR8zuEqP1obys/oc= +github.com/graph-gophers/dataloader/v7 v7.1.0/go.mod h1:1bKE0Dm6OUcTB/OAuYVOZctgIz7Q3d0XrYtlIzTgg6Q= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -247,8 +249,9 @@ golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= diff --git a/backend/gqlgen.yml b/backend/gqlgen.yml index 5a2480f..e43a0f4 100644 --- a/backend/gqlgen.yml +++ b/backend/gqlgen.yml @@ -85,6 +85,27 @@ models: Time: model: - github.com/99designs/gqlgen/graphql.Time + Card: + fields: + cardGroup: + resolver: true + CardGroup: + fields: + cards: + resolver: true + users: + resolver: true + User: + fields: + cardGroups: + resolver: true + roles: + resolver: true + Role: + fields: + users: + resolver: true + directives: validation: skip_runtime: true \ No newline at end of file diff --git a/backend/graph/dataloader.go b/backend/graph/dataloader.go new file mode 100644 index 0000000..0dda8ed --- /dev/null +++ b/backend/graph/dataloader.go @@ -0,0 +1,184 @@ +package graph + +import ( + "backend/graph/model" + "backend/graph/services" + "context" + "errors" + "strconv" + + "github.com/graph-gophers/dataloader/v7" +) + +type Loaders struct { + CardLoader dataloader.Interface[string, *model.Card] + UserLoader dataloader.Interface[string, *model.User] + RoleLoader dataloader.Interface[string, *model.Role] + CardGroupLoader dataloader.Interface[string, *model.CardGroup] +} + +func NewLoaders(srv services.Services) *Loaders { + cardBatcher := &cardBatcher{Srv: srv} + userBatcher := &userBatcher{Srv: srv} + roleBatcher := &roleBatcher{Srv: srv} + cardGroupBatcher := &cardGroupBatcher{Srv: srv} + + return &Loaders{ + CardLoader: dataloader.NewBatchedLoader[string, *model.Card](cardBatcher.BatchGetCards), + UserLoader: dataloader.NewBatchedLoader[string, *model.User](userBatcher.BatchGetUsers), + RoleLoader: dataloader.NewBatchedLoader[string, *model.Role](roleBatcher.BatchGetRoles), + CardGroupLoader: dataloader.NewBatchedLoader[string, *model.CardGroup](cardGroupBatcher.BatchGetCardGroups), + } +} + +type cardBatcher struct { + Srv services.Services +} + +func (c *cardBatcher) BatchGetCards(ctx context.Context, keys []string) []*dataloader.Result[*model.Card] { + ids := make([]int64, len(keys)) + for i, key := range keys { + id, err := strconv.ParseInt(key, 10, 64) + if err != nil { + return []*dataloader.Result[*model.Card]{{ + Error: err, + }} + } + ids[i] = id + } + + cards, err := c.Srv.GetCardsByIDs(ctx, ids) + if err != nil { + return make([]*dataloader.Result[*model.Card], len(keys)) + } + + cardMap := make(map[int64]*model.Card) + for _, card := range cards { + cardMap[card.ID] = card + } + + results := make([]*dataloader.Result[*model.Card], len(keys)) + for i, key := range keys { + id, _ := strconv.ParseInt(key, 10, 64) + if card, ok := cardMap[id]; ok { + results[i] = &dataloader.Result[*model.Card]{Data: card} + } else { + results[i] = &dataloader.Result[*model.Card]{Error: errors.New("card not found")} + } + } + return results +} + +type userBatcher struct { + Srv services.Services +} + +func (u *userBatcher) BatchGetUsers(ctx context.Context, keys []string) []*dataloader.Result[*model.User] { + ids := make([]int64, len(keys)) + for i, key := range keys { + id, err := strconv.ParseInt(key, 10, 64) + if err != nil { + return []*dataloader.Result[*model.User]{{ + Error: err, + }} + } + ids[i] = id + } + + users, err := u.Srv.GetUsersByIDs(ctx, ids) + if err != nil { + return make([]*dataloader.Result[*model.User], len(keys)) + } + + userMap := make(map[int64]*model.User) + for _, user := range users { + userMap[user.ID] = user + } + + results := make([]*dataloader.Result[*model.User], len(keys)) + for i, key := range keys { + id, _ := strconv.ParseInt(key, 10, 64) + if user, ok := userMap[id]; ok { + results[i] = &dataloader.Result[*model.User]{Data: user} + } else { + results[i] = &dataloader.Result[*model.User]{Error: errors.New("user not found")} + } + } + return results +} + +type roleBatcher struct { + Srv services.Services +} + +func (r *roleBatcher) BatchGetRoles(ctx context.Context, keys []string) []*dataloader.Result[*model.Role] { + ids := make([]int64, len(keys)) + for i, key := range keys { + id, err := strconv.ParseInt(key, 10, 64) + if err != nil { + return []*dataloader.Result[*model.Role]{{ + Error: err, + }} + } + ids[i] = id + } + + roles, err := r.Srv.GetRolesByIDs(ctx, ids) + if err != nil { + return make([]*dataloader.Result[*model.Role], len(keys)) + } + + roleMap := make(map[int64]*model.Role) + for _, role := range roles { + roleMap[role.ID] = role + } + + results := make([]*dataloader.Result[*model.Role], len(keys)) + for i, key := range keys { + id, _ := strconv.ParseInt(key, 10, 64) + if role, ok := roleMap[id]; ok { + results[i] = &dataloader.Result[*model.Role]{Data: role} + } else { + results[i] = &dataloader.Result[*model.Role]{Error: errors.New("role not found")} + } + } + return results +} + +type cardGroupBatcher struct { + Srv services.Services +} + +func (c *cardGroupBatcher) BatchGetCardGroups(ctx context.Context, keys []string) []*dataloader.Result[*model.CardGroup] { + ids := make([]int64, len(keys)) + for i, key := range keys { + id, err := strconv.ParseInt(key, 10, 64) + if err != nil { + return []*dataloader.Result[*model.CardGroup]{{ + Error: err, + }} + } + ids[i] = id + } + + cardGroups, err := c.Srv.GetCardGroupsByIDs(ctx, ids) + if err != nil { + return make([]*dataloader.Result[*model.CardGroup], len(keys)) + } + + cardGroupMap := make(map[int64]*model.CardGroup) + for _, cardGroup := range cardGroups { + cardGroupMap[cardGroup.ID] = cardGroup + } + + results := make([]*dataloader.Result[*model.CardGroup], len(keys)) + for i, key := range keys { + id, _ := strconv.ParseInt(key, 10, 64) + if cardGroup, ok := cardGroupMap[id]; ok { + results[i] = &dataloader.Result[*model.CardGroup]{Data: cardGroup} + } else { + results[i] = &dataloader.Result[*model.CardGroup]{Error: errors.New("card group not found")} + } + } + return results +} diff --git a/backend/graph/generated.go b/backend/graph/generated.go index 4932a17..9f9b096 100644 --- a/backend/graph/generated.go +++ b/backend/graph/generated.go @@ -40,8 +40,12 @@ type Config struct { } type ResolverRoot interface { + Card() CardResolver + CardGroup() CardGroupResolver Mutation() MutationResolver Query() QueryResolver + Role() RoleResolver + User() UserResolver } type DirectiveRoot struct { @@ -121,15 +125,11 @@ type ComplexityRoot struct { Query struct { Card func(childComplexity int, id int64) int CardGroup func(childComplexity int, id int64) int - CardGroups func(childComplexity int, first *int, after *int64, last *int, before *int64) int CardGroupsByUser func(childComplexity int, userID int64, first *int, after *int64, last *int, before *int64) int - Cards func(childComplexity int, first *int, after *int64, last *int, before *int64) int CardsByCardGroup func(childComplexity int, cardGroupID int64, first *int, after *int64, last *int, before *int64) int Role func(childComplexity int, id int64) int - Roles func(childComplexity int, first *int, after *int64, last *int, before *int64) int User func(childComplexity int, id int64) int UserRole func(childComplexity int, userID int64) int - Users func(childComplexity int, first *int, after *int64, last *int, before *int64) int UsersByRole func(childComplexity int, roleID int64, first *int, after *int64, last *int, before *int64) int } @@ -175,6 +175,13 @@ type ComplexityRoot struct { } } +type CardResolver interface { + CardGroup(ctx context.Context, obj *model.Card) (*model.CardGroup, error) +} +type CardGroupResolver interface { + Cards(ctx context.Context, obj *model.CardGroup, first *int, after *int64, last *int, before *int64) (*model.CardConnection, error) + Users(ctx context.Context, obj *model.CardGroup, first *int, after *int64, last *int, before *int64) (*model.UserConnection, error) +} type MutationResolver interface { CreateCard(ctx context.Context, input model.NewCard) (*model.Card, error) UpdateCard(ctx context.Context, id int64, input model.NewCard) (*model.Card, error) @@ -194,19 +201,22 @@ type MutationResolver interface { RemoveRoleFromUser(ctx context.Context, userID int64, roleID int64) (*model.User, error) } type QueryResolver interface { - Cards(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.CardConnection, error) Card(ctx context.Context, id int64) (*model.Card, error) - CardGroups(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.CardGroupConnection, error) CardGroup(ctx context.Context, id int64) (*model.CardGroup, error) - Roles(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.RoleConnection, error) Role(ctx context.Context, id int64) (*model.Role, error) - Users(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.UserConnection, error) User(ctx context.Context, id int64) (*model.User, error) CardsByCardGroup(ctx context.Context, cardGroupID int64, first *int, after *int64, last *int, before *int64) (*model.CardConnection, error) UserRole(ctx context.Context, userID int64) (*model.Role, error) CardGroupsByUser(ctx context.Context, userID int64, first *int, after *int64, last *int, before *int64) (*model.CardGroupConnection, error) UsersByRole(ctx context.Context, roleID int64, first *int, after *int64, last *int, before *int64) (*model.UserConnection, error) } +type RoleResolver interface { + Users(ctx context.Context, obj *model.Role, first *int, after *int64, last *int, before *int64) (*model.UserConnection, error) +} +type UserResolver interface { + CardGroups(ctx context.Context, obj *model.User, first *int, after *int64, last *int, before *int64) (*model.CardGroupConnection, error) + Roles(ctx context.Context, obj *model.User, first *int, after *int64, last *int, before *int64) (*model.RoleConnection, error) +} type executableSchema struct { schema *ast.Schema @@ -663,18 +673,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.CardGroup(childComplexity, args["id"].(int64)), true - case "Query.cardGroups": - if e.complexity.Query.CardGroups == nil { - break - } - - args, err := ec.field_Query_cardGroups_args(context.TODO(), rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.Query.CardGroups(childComplexity, args["first"].(*int), args["after"].(*int64), args["last"].(*int), args["before"].(*int64)), true - case "Query.cardGroupsByUser": if e.complexity.Query.CardGroupsByUser == nil { break @@ -687,18 +685,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.CardGroupsByUser(childComplexity, args["userID"].(int64), args["first"].(*int), args["after"].(*int64), args["last"].(*int), args["before"].(*int64)), true - case "Query.cards": - if e.complexity.Query.Cards == nil { - break - } - - args, err := ec.field_Query_cards_args(context.TODO(), rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.Query.Cards(childComplexity, args["first"].(*int), args["after"].(*int64), args["last"].(*int), args["before"].(*int64)), true - case "Query.cardsByCardGroup": if e.complexity.Query.CardsByCardGroup == nil { break @@ -723,18 +709,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Role(childComplexity, args["id"].(int64)), true - case "Query.roles": - if e.complexity.Query.Roles == nil { - break - } - - args, err := ec.field_Query_roles_args(context.TODO(), rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.Query.Roles(childComplexity, args["first"].(*int), args["after"].(*int64), args["last"].(*int), args["before"].(*int64)), true - case "Query.user": if e.complexity.Query.User == nil { break @@ -759,18 +733,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.UserRole(childComplexity, args["userID"].(int64)), true - case "Query.users": - if e.complexity.Query.Users == nil { - break - } - - args, err := ec.field_Query_users_args(context.TODO(), rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.Query.Users(childComplexity, args["first"].(*int), args["after"].(*int64), args["last"].(*int), args["before"].(*int64)), true - case "Query.usersByRole": if e.complexity.Query.UsersByRole == nil { break @@ -1564,48 +1526,6 @@ func (ec *executionContext) field_Query_cardGroupsByUser_args(ctx context.Contex return args, nil } -func (ec *executionContext) field_Query_cardGroups_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 *int - if tmp, ok := rawArgs["first"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) - arg0, err = ec.unmarshalOInt2ᚖint(ctx, tmp) - if err != nil { - return nil, err - } - } - args["first"] = arg0 - var arg1 *int64 - if tmp, ok := rawArgs["after"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) - arg1, err = ec.unmarshalOID2ᚖint64(ctx, tmp) - if err != nil { - return nil, err - } - } - args["after"] = arg1 - var arg2 *int - if tmp, ok := rawArgs["last"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) - arg2, err = ec.unmarshalOInt2ᚖint(ctx, tmp) - if err != nil { - return nil, err - } - } - args["last"] = arg2 - var arg3 *int64 - if tmp, ok := rawArgs["before"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) - arg3, err = ec.unmarshalOID2ᚖint64(ctx, tmp) - if err != nil { - return nil, err - } - } - args["before"] = arg3 - return args, nil -} - func (ec *executionContext) field_Query_card_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1672,48 +1592,6 @@ func (ec *executionContext) field_Query_cardsByCardGroup_args(ctx context.Contex return args, nil } -func (ec *executionContext) field_Query_cards_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 *int - if tmp, ok := rawArgs["first"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) - arg0, err = ec.unmarshalOInt2ᚖint(ctx, tmp) - if err != nil { - return nil, err - } - } - args["first"] = arg0 - var arg1 *int64 - if tmp, ok := rawArgs["after"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) - arg1, err = ec.unmarshalOID2ᚖint64(ctx, tmp) - if err != nil { - return nil, err - } - } - args["after"] = arg1 - var arg2 *int - if tmp, ok := rawArgs["last"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) - arg2, err = ec.unmarshalOInt2ᚖint(ctx, tmp) - if err != nil { - return nil, err - } - } - args["last"] = arg2 - var arg3 *int64 - if tmp, ok := rawArgs["before"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) - arg3, err = ec.unmarshalOID2ᚖint64(ctx, tmp) - if err != nil { - return nil, err - } - } - args["before"] = arg3 - return args, nil -} - func (ec *executionContext) field_Query_role_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1729,48 +1607,6 @@ func (ec *executionContext) field_Query_role_args(ctx context.Context, rawArgs m return args, nil } -func (ec *executionContext) field_Query_roles_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 *int - if tmp, ok := rawArgs["first"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) - arg0, err = ec.unmarshalOInt2ᚖint(ctx, tmp) - if err != nil { - return nil, err - } - } - args["first"] = arg0 - var arg1 *int64 - if tmp, ok := rawArgs["after"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) - arg1, err = ec.unmarshalOID2ᚖint64(ctx, tmp) - if err != nil { - return nil, err - } - } - args["after"] = arg1 - var arg2 *int - if tmp, ok := rawArgs["last"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) - arg2, err = ec.unmarshalOInt2ᚖint(ctx, tmp) - if err != nil { - return nil, err - } - } - args["last"] = arg2 - var arg3 *int64 - if tmp, ok := rawArgs["before"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) - arg3, err = ec.unmarshalOID2ᚖint64(ctx, tmp) - if err != nil { - return nil, err - } - } - args["before"] = arg3 - return args, nil -} - func (ec *executionContext) field_Query_userRole_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1852,48 +1688,6 @@ func (ec *executionContext) field_Query_usersByRole_args(ctx context.Context, ra return args, nil } -func (ec *executionContext) field_Query_users_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 *int - if tmp, ok := rawArgs["first"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) - arg0, err = ec.unmarshalOInt2ᚖint(ctx, tmp) - if err != nil { - return nil, err - } - } - args["first"] = arg0 - var arg1 *int64 - if tmp, ok := rawArgs["after"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) - arg1, err = ec.unmarshalOID2ᚖint64(ctx, tmp) - if err != nil { - return nil, err - } - } - args["after"] = arg1 - var arg2 *int - if tmp, ok := rawArgs["last"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) - arg2, err = ec.unmarshalOInt2ᚖint(ctx, tmp) - if err != nil { - return nil, err - } - } - args["last"] = arg2 - var arg3 *int64 - if tmp, ok := rawArgs["before"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) - arg3, err = ec.unmarshalOID2ᚖint64(ctx, tmp) - if err != nil { - return nil, err - } - } - args["before"] = arg3 - return args, nil -} - func (ec *executionContext) field_Role_users_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2380,7 +2174,7 @@ func (ec *executionContext) _Card_cardGroup(ctx context.Context, field graphql.C }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.CardGroup, nil + return ec.resolvers.Card().CardGroup(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -2401,8 +2195,8 @@ func (ec *executionContext) fieldContext_Card_cardGroup(_ context.Context, field fc = &graphql.FieldContext{ Object: "Card", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "id": @@ -2924,7 +2718,7 @@ func (ec *executionContext) _CardGroup_cards(ctx context.Context, field graphql. }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Cards, nil + return ec.resolvers.CardGroup().Cards(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*int64), fc.Args["last"].(*int), fc.Args["before"].(*int64)) }) if err != nil { ec.Error(ctx, err) @@ -2945,8 +2739,8 @@ func (ec *executionContext) fieldContext_CardGroup_cards(ctx context.Context, fi fc = &graphql.FieldContext{ Object: "CardGroup", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "edges": @@ -2989,7 +2783,7 @@ func (ec *executionContext) _CardGroup_users(ctx context.Context, field graphql. }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Users, nil + return ec.resolvers.CardGroup().Users(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*int64), fc.Args["last"].(*int), fc.Args["before"].(*int64)) }) if err != nil { ec.Error(ctx, err) @@ -3010,8 +2804,8 @@ func (ec *executionContext) fieldContext_CardGroup_users(ctx context.Context, fi fc = &graphql.FieldContext{ Object: "CardGroup", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "edges": @@ -4516,68 +4310,6 @@ func (ec *executionContext) fieldContext_PageInfo_startCursor(_ context.Context, return fc, nil } -func (ec *executionContext) _Query_cards(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query_cards(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 ec.resolvers.Query().Cards(rctx, fc.Args["first"].(*int), fc.Args["after"].(*int64), fc.Args["last"].(*int), fc.Args["before"].(*int64)) - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*model.CardConnection) - fc.Result = res - return ec.marshalOCardConnection2ᚖbackendᚋgraphᚋmodelᚐCardConnection(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_Query_cards(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Query", - Field: field, - IsMethod: true, - IsResolver: true, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "edges": - return ec.fieldContext_CardConnection_edges(ctx, field) - case "nodes": - return ec.fieldContext_CardConnection_nodes(ctx, field) - case "pageInfo": - return ec.fieldContext_CardConnection_pageInfo(ctx, field) - case "totalCount": - return ec.fieldContext_CardConnection_totalCount(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type CardConnection", field.Name) - }, - } - defer func() { - if r := recover(); r != nil { - err = ec.Recover(ctx, r) - ec.Error(ctx, err) - } - }() - ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_cards_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } - return fc, nil -} - func (ec *executionContext) _Query_card(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_card(ctx, field) if err != nil { @@ -4648,68 +4380,6 @@ func (ec *executionContext) fieldContext_Query_card(ctx context.Context, field g return fc, nil } -func (ec *executionContext) _Query_cardGroups(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query_cardGroups(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 ec.resolvers.Query().CardGroups(rctx, fc.Args["first"].(*int), fc.Args["after"].(*int64), fc.Args["last"].(*int), fc.Args["before"].(*int64)) - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*model.CardGroupConnection) - fc.Result = res - return ec.marshalOCardGroupConnection2ᚖbackendᚋgraphᚋmodelᚐCardGroupConnection(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_Query_cardGroups(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Query", - Field: field, - IsMethod: true, - IsResolver: true, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "edges": - return ec.fieldContext_CardGroupConnection_edges(ctx, field) - case "nodes": - return ec.fieldContext_CardGroupConnection_nodes(ctx, field) - case "pageInfo": - return ec.fieldContext_CardGroupConnection_pageInfo(ctx, field) - case "totalCount": - return ec.fieldContext_CardGroupConnection_totalCount(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type CardGroupConnection", field.Name) - }, - } - defer func() { - if r := recover(); r != nil { - err = ec.Recover(ctx, r) - ec.Error(ctx, err) - } - }() - ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_cardGroups_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } - return fc, nil -} - func (ec *executionContext) _Query_cardGroup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_cardGroup(ctx, field) if err != nil { @@ -4757,135 +4427,9 @@ func (ec *executionContext) fieldContext_Query_cardGroup(ctx context.Context, fi case "cards": return ec.fieldContext_CardGroup_cards(ctx, field) case "users": - return ec.fieldContext_CardGroup_users(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type CardGroup", field.Name) - }, - } - defer func() { - if r := recover(); r != nil { - err = ec.Recover(ctx, r) - ec.Error(ctx, err) - } - }() - ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_cardGroup_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } - return fc, nil -} - -func (ec *executionContext) _Query_roles(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query_roles(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 ec.resolvers.Query().Roles(rctx, fc.Args["first"].(*int), fc.Args["after"].(*int64), fc.Args["last"].(*int), fc.Args["before"].(*int64)) - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*model.RoleConnection) - fc.Result = res - return ec.marshalORoleConnection2ᚖbackendᚋgraphᚋmodelᚐRoleConnection(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_Query_roles(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Query", - Field: field, - IsMethod: true, - IsResolver: true, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "edges": - return ec.fieldContext_RoleConnection_edges(ctx, field) - case "nodes": - return ec.fieldContext_RoleConnection_nodes(ctx, field) - case "pageInfo": - return ec.fieldContext_RoleConnection_pageInfo(ctx, field) - case "totalCount": - return ec.fieldContext_RoleConnection_totalCount(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type RoleConnection", field.Name) - }, - } - defer func() { - if r := recover(); r != nil { - err = ec.Recover(ctx, r) - ec.Error(ctx, err) - } - }() - ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_roles_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } - return fc, nil -} - -func (ec *executionContext) _Query_role(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query_role(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 ec.resolvers.Query().Role(rctx, fc.Args["id"].(int64)) - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*model.Role) - fc.Result = res - return ec.marshalORole2ᚖbackendᚋgraphᚋmodelᚐRole(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_Query_role(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Query", - Field: field, - IsMethod: true, - IsResolver: true, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "id": - return ec.fieldContext_Role_id(ctx, field) - case "name": - return ec.fieldContext_Role_name(ctx, field) - case "created": - return ec.fieldContext_Role_created(ctx, field) - case "updated": - return ec.fieldContext_Role_updated(ctx, field) - case "users": - return ec.fieldContext_Role_users(ctx, field) + return ec.fieldContext_CardGroup_users(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type Role", field.Name) + return nil, fmt.Errorf("no field named %q was found under type CardGroup", field.Name) }, } defer func() { @@ -4895,15 +4439,15 @@ func (ec *executionContext) fieldContext_Query_role(ctx context.Context, field g } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_role_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Query_cardGroup_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } return fc, nil } -func (ec *executionContext) _Query_users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query_users(ctx, field) +func (ec *executionContext) _Query_role(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_role(ctx, field) if err != nil { return graphql.Null } @@ -4916,7 +4460,7 @@ func (ec *executionContext) _Query_users(ctx context.Context, field graphql.Coll }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Users(rctx, fc.Args["first"].(*int), fc.Args["after"].(*int64), fc.Args["last"].(*int), fc.Args["before"].(*int64)) + return ec.resolvers.Query().Role(rctx, fc.Args["id"].(int64)) }) if err != nil { ec.Error(ctx, err) @@ -4925,12 +4469,12 @@ func (ec *executionContext) _Query_users(ctx context.Context, field graphql.Coll if resTmp == nil { return graphql.Null } - res := resTmp.(*model.UserConnection) + res := resTmp.(*model.Role) fc.Result = res - return ec.marshalOUserConnection2ᚖbackendᚋgraphᚋmodelᚐUserConnection(ctx, field.Selections, res) + return ec.marshalORole2ᚖbackendᚋgraphᚋmodelᚐRole(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Query_users(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Query_role(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Query", Field: field, @@ -4938,16 +4482,18 @@ func (ec *executionContext) fieldContext_Query_users(ctx context.Context, field IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "edges": - return ec.fieldContext_UserConnection_edges(ctx, field) - case "nodes": - return ec.fieldContext_UserConnection_nodes(ctx, field) - case "pageInfo": - return ec.fieldContext_UserConnection_pageInfo(ctx, field) - case "totalCount": - return ec.fieldContext_UserConnection_totalCount(ctx, field) + case "id": + return ec.fieldContext_Role_id(ctx, field) + case "name": + return ec.fieldContext_Role_name(ctx, field) + case "created": + return ec.fieldContext_Role_created(ctx, field) + case "updated": + return ec.fieldContext_Role_updated(ctx, field) + case "users": + return ec.fieldContext_Role_users(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type UserConnection", field.Name) + return nil, fmt.Errorf("no field named %q was found under type Role", field.Name) }, } defer func() { @@ -4957,7 +4503,7 @@ func (ec *executionContext) fieldContext_Query_users(ctx context.Context, field } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_users_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Query_role_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } @@ -5599,7 +5145,7 @@ func (ec *executionContext) _Role_users(ctx context.Context, field graphql.Colle }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Users, nil + return ec.resolvers.Role().Users(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*int64), fc.Args["last"].(*int), fc.Args["before"].(*int64)) }) if err != nil { ec.Error(ctx, err) @@ -5620,8 +5166,8 @@ func (ec *executionContext) fieldContext_Role_users(ctx context.Context, field g fc = &graphql.FieldContext{ Object: "Role", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "edges": @@ -6138,7 +5684,7 @@ func (ec *executionContext) _User_cardGroups(ctx context.Context, field graphql. }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.CardGroups, nil + return ec.resolvers.User().CardGroups(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*int64), fc.Args["last"].(*int), fc.Args["before"].(*int64)) }) if err != nil { ec.Error(ctx, err) @@ -6159,8 +5705,8 @@ func (ec *executionContext) fieldContext_User_cardGroups(ctx context.Context, fi fc = &graphql.FieldContext{ Object: "User", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "edges": @@ -6203,7 +5749,7 @@ func (ec *executionContext) _User_roles(ctx context.Context, field graphql.Colle }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Roles, nil + return ec.resolvers.User().Roles(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*int64), fc.Args["last"].(*int), fc.Args["before"].(*int64)) }) if err != nil { ec.Error(ctx, err) @@ -6224,8 +5770,8 @@ func (ec *executionContext) fieldContext_User_roles(ctx context.Context, field g fc = &graphql.FieldContext{ Object: "User", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "edges": @@ -8605,43 +8151,74 @@ func (ec *executionContext) _Card(ctx context.Context, sel ast.SelectionSet, obj case "id": out.Values[i] = ec._Card_id(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "front": out.Values[i] = ec._Card_front(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "back": out.Values[i] = ec._Card_back(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "review_date": out.Values[i] = ec._Card_review_date(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "interval_days": out.Values[i] = ec._Card_interval_days(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "created": out.Values[i] = ec._Card_created(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "updated": out.Values[i] = ec._Card_updated(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "cardGroup": - out.Values[i] = ec._Card_cardGroup(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Card_cardGroup(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -8771,33 +8348,95 @@ func (ec *executionContext) _CardGroup(ctx context.Context, sel ast.SelectionSet case "id": out.Values[i] = ec._CardGroup_id(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "name": out.Values[i] = ec._CardGroup_name(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "created": out.Values[i] = ec._CardGroup_created(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "updated": out.Values[i] = ec._CardGroup_updated(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "cards": - out.Values[i] = ec._CardGroup_cards(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._CardGroup_cards(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "users": - out.Values[i] = ec._CardGroup_users(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._CardGroup_users(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -9086,25 +8725,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("Query") - case "cards": - field := field - - innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._Query_cards(ctx, field) - return res - } - - rrm := func(ctx context.Context) graphql.Marshaler { - return ec.OperationContext.RootResolverMiddleware(ctx, - func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) - } - - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "card": field := field @@ -9123,25 +8743,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) - case "cardGroups": - field := field - - innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._Query_cardGroups(ctx, field) - return res - } - - rrm := func(ctx context.Context) graphql.Marshaler { - return ec.OperationContext.RootResolverMiddleware(ctx, - func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) - } - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "cardGroup": field := field @@ -9161,25 +8762,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) - case "roles": - field := field - - innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._Query_roles(ctx, field) - return res - } - - rrm := func(ctx context.Context) graphql.Marshaler { - return ec.OperationContext.RootResolverMiddleware(ctx, - func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) - } - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "role": field := field @@ -9199,25 +8781,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) - case "users": - field := field - - innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._Query_users(ctx, field) - return res - } - - rrm := func(ctx context.Context) graphql.Marshaler { - return ec.OperationContext.RootResolverMiddleware(ctx, - func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) - } - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "user": field := field @@ -9359,28 +8922,59 @@ func (ec *executionContext) _Role(ctx context.Context, sel ast.SelectionSet, obj case "id": out.Values[i] = ec._Role_id(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "name": out.Values[i] = ec._Role_name(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "created": out.Values[i] = ec._Role_created(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "updated": out.Values[i] = ec._Role_updated(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "users": - out.Values[i] = ec._Role_users(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Role_users(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -9510,33 +9104,95 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj case "id": out.Values[i] = ec._User_id(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "name": out.Values[i] = ec._User_name(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "created": out.Values[i] = ec._User_created(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "updated": out.Values[i] = ec._User_updated(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "cardGroups": - out.Values[i] = ec._User_cardGroups(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._User_cardGroups(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "roles": - out.Values[i] = ec._User_roles(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._User_roles(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -10003,6 +9659,10 @@ func (ec *executionContext) marshalNCard2ᚖbackendᚋgraphᚋmodelᚐCard(ctx c return ec._Card(ctx, sel, v) } +func (ec *executionContext) marshalNCardConnection2backendᚋgraphᚋmodelᚐCardConnection(ctx context.Context, sel ast.SelectionSet, v model.CardConnection) graphql.Marshaler { + return ec._CardConnection(ctx, sel, &v) +} + func (ec *executionContext) marshalNCardConnection2ᚖbackendᚋgraphᚋmodelᚐCardConnection(ctx context.Context, sel ast.SelectionSet, v *model.CardConnection) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -10013,6 +9673,10 @@ func (ec *executionContext) marshalNCardConnection2ᚖbackendᚋgraphᚋmodelᚐ return ec._CardConnection(ctx, sel, v) } +func (ec *executionContext) marshalNCardGroup2backendᚋgraphᚋmodelᚐCardGroup(ctx context.Context, sel ast.SelectionSet, v model.CardGroup) graphql.Marshaler { + return ec._CardGroup(ctx, sel, &v) +} + func (ec *executionContext) marshalNCardGroup2ᚖbackendᚋgraphᚋmodelᚐCardGroup(ctx context.Context, sel ast.SelectionSet, v *model.CardGroup) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -10023,6 +9687,10 @@ func (ec *executionContext) marshalNCardGroup2ᚖbackendᚋgraphᚋmodelᚐCardG return ec._CardGroup(ctx, sel, v) } +func (ec *executionContext) marshalNCardGroupConnection2backendᚋgraphᚋmodelᚐCardGroupConnection(ctx context.Context, sel ast.SelectionSet, v model.CardGroupConnection) graphql.Marshaler { + return ec._CardGroupConnection(ctx, sel, &v) +} + func (ec *executionContext) marshalNCardGroupConnection2ᚖbackendᚋgraphᚋmodelᚐCardGroupConnection(ctx context.Context, sel ast.SelectionSet, v *model.CardGroupConnection) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -10135,6 +9803,10 @@ func (ec *executionContext) marshalNRole2ᚖbackendᚋgraphᚋmodelᚐRole(ctx c return ec._Role(ctx, sel, v) } +func (ec *executionContext) marshalNRoleConnection2backendᚋgraphᚋmodelᚐRoleConnection(ctx context.Context, sel ast.SelectionSet, v model.RoleConnection) graphql.Marshaler { + return ec._RoleConnection(ctx, sel, &v) +} + func (ec *executionContext) marshalNRoleConnection2ᚖbackendᚋgraphᚋmodelᚐRoleConnection(ctx context.Context, sel ast.SelectionSet, v *model.RoleConnection) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -10185,6 +9857,10 @@ func (ec *executionContext) marshalNUser2ᚖbackendᚋgraphᚋmodelᚐUser(ctx c return ec._User(ctx, sel, v) } +func (ec *executionContext) marshalNUserConnection2backendᚋgraphᚋmodelᚐUserConnection(ctx context.Context, sel ast.SelectionSet, v model.UserConnection) graphql.Marshaler { + return ec._UserConnection(ctx, sel, &v) +} + func (ec *executionContext) marshalNUserConnection2ᚖbackendᚋgraphᚋmodelᚐUserConnection(ctx context.Context, sel ast.SelectionSet, v *model.UserConnection) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -10798,13 +10474,6 @@ func (ec *executionContext) marshalORole2ᚖbackendᚋgraphᚋmodelᚐRole(ctx c return ec._Role(ctx, sel, v) } -func (ec *executionContext) marshalORoleConnection2ᚖbackendᚋgraphᚋmodelᚐRoleConnection(ctx context.Context, sel ast.SelectionSet, v *model.RoleConnection) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._RoleConnection(ctx, sel, v) -} - func (ec *executionContext) marshalORoleEdge2ᚕᚖbackendᚋgraphᚋmodelᚐRoleEdge(ctx context.Context, sel ast.SelectionSet, v []*model.RoleEdge) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/backend/graph/resolver.go b/backend/graph/resolver.go index e99dc84..e127749 100644 --- a/backend/graph/resolver.go +++ b/backend/graph/resolver.go @@ -12,4 +12,5 @@ type Resolver struct { DB *gorm.DB Srv services.Services VW validator.ValidateWrapper + *Loaders } diff --git a/backend/graph/schema.graphqls b/backend/graph/schema.graphqls index c1afbbd..9ec02d2 100644 --- a/backend/graph/schema.graphqls +++ b/backend/graph/schema.graphqls @@ -132,13 +132,13 @@ input NewRole { } type Query { - cards(first: Int, after: ID, last: Int, before: ID): CardConnection +# cards(first: Int, after: ID, last: Int, before: ID): CardConnection +# users(first: Int, after: ID, last: Int, before: ID): UserConnection +# cardGroups(first: Int, after: ID, last: Int, before: ID): CardGroupConnection +# roles(first: Int, after: ID, last: Int, before: ID): RoleConnection card(id: ID!): Card - cardGroups(first: Int, after: ID, last: Int, before: ID): CardGroupConnection cardGroup(id: ID!): CardGroup - roles(first: Int, after: ID, last: Int, before: ID): RoleConnection role(id: ID!): Role - users(first: Int, after: ID, last: Int, before: ID): UserConnection user(id: ID!): User cardsByCardGroup(cardGroupID: ID!, first: Int, after: ID, last: Int, before: ID): CardConnection userRole(userID: ID!): Role diff --git a/backend/graph/schema.resolvers.go b/backend/graph/schema.resolvers.go index b0281f2..abc679c 100644 --- a/backend/graph/schema.resolvers.go +++ b/backend/graph/schema.resolvers.go @@ -1,12 +1,66 @@ package graph +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.49 + import ( "backend/graph/model" "backend/pkg/logger" "context" "fmt" + "strconv" ) +// CardGroup is the resolver for the cardGroup field in Card. +func (r *cardResolver) CardGroup(ctx context.Context, obj *model.Card) (*model.CardGroup, error) { + thunk := r.Loaders.CardGroupLoader.Load(ctx, strconv.FormatInt(obj.CardGroup.ID, 10)) + cardGroup, err := thunk() + if err != nil { + logger.Logger.ErrorContext(ctx, "CardGroup", fmt.Errorf("fetch by dataloader: %+v", err)) + return nil, fmt.Errorf("fetch by dataloader: %+v", err) + } + return cardGroup, nil +} + +// Cards is the resolver for the cards field in CardGroup. +func (r *cardGroupResolver) Cards(ctx context.Context, obj *model.CardGroup, first *int, after *int64, last *int, before *int64) (*model.CardConnection, error) { + var cardIDs []string + for _, edge := range obj.Cards.Edges { + cardIDs = append(cardIDs, strconv.FormatInt(edge.Node.ID, 10)) + } + thunks := r.Loaders.CardLoader.LoadMany(ctx, cardIDs) + cards, err := thunks() + if err != nil { + logger.Logger.ErrorContext(ctx, "Cards", fmt.Errorf("fetch by dataloader: %+v", err)) + return nil, fmt.Errorf("fetch by dataloader: %+v", err) + } + + for i, card := range cards { + cards[i] = card + } + return &model.CardConnection{Nodes: cards}, nil +} + +// Users is the resolver for the users field in CardGroup. +func (r *cardGroupResolver) Users(ctx context.Context, obj *model.CardGroup, first *int, after *int64, last *int, before *int64) (*model.UserConnection, error) { + var userIDs []string + for _, edge := range obj.Users.Edges { + userIDs = append(userIDs, strconv.FormatInt(edge.Node.ID, 10)) + } + thunks := r.Loaders.UserLoader.LoadMany(ctx, userIDs) + users, err := thunks() + if err != nil { + logger.Logger.ErrorContext(ctx, "Users", fmt.Errorf("fetch by dataloader: %+v", err)) + return nil, fmt.Errorf("fetch by dataloader: %+v", err) + } + + for i, user := range users { + users[i] = user + } + return &model.UserConnection{Nodes: users}, nil +} + // CreateCard is the resolver for the createCard field. func (r *mutationResolver) CreateCard(ctx context.Context, input model.NewCard) (*model.Card, error) { if err := r.VW.Validator().Struct(input); err != nil { @@ -135,11 +189,6 @@ func (r *mutationResolver) RemoveRoleFromUser(ctx context.Context, userID int64, return r.Srv.RemoveRoleFromUser(ctx, userID, roleID) } -// Cards is the resolver for the cards field. -func (r *queryResolver) Cards(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.CardConnection, error) { - return r.Srv.PaginatedCards(ctx, first, after, last, before) -} - // Card is the resolver for the card field. func (r *queryResolver) Card(ctx context.Context, id int64) (*model.Card, error) { if id <= 0 { @@ -149,11 +198,6 @@ func (r *queryResolver) Card(ctx context.Context, id int64) (*model.Card, error) return r.Srv.GetCardByID(ctx, id) } -// CardGroups is the resolver for the cardGroups field. -func (r *queryResolver) CardGroups(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.CardGroupConnection, error) { - return r.Srv.PaginatedCardGroups(ctx, first, after, last, before) -} - // CardGroup is the resolver for the cardGroup field. func (r *queryResolver) CardGroup(ctx context.Context, id int64) (*model.CardGroup, error) { if id <= 0 { @@ -163,11 +207,6 @@ func (r *queryResolver) CardGroup(ctx context.Context, id int64) (*model.CardGro return r.Srv.GetCardGroupByID(ctx, id) } -// Roles is the resolver for the roles field. -func (r *queryResolver) Roles(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.RoleConnection, error) { - return r.Srv.PaginatedRoles(ctx, first, after, last, before) -} - // Role is the resolver for the role field. func (r *queryResolver) Role(ctx context.Context, id int64) (*model.Role, error) { if id <= 0 { @@ -177,11 +216,6 @@ func (r *queryResolver) Role(ctx context.Context, id int64) (*model.Role, error) return r.Srv.GetRoleByID(ctx, id) } -// Users is the resolver for the users field. -func (r *queryResolver) Users(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.UserConnection, error) { - return r.Srv.PaginatedUsers(ctx, first, after, last, before) -} - // User is the resolver for the user field. func (r *queryResolver) User(ctx context.Context, id int64) (*model.User, error) { if id <= 0 { @@ -203,7 +237,7 @@ func (r *queryResolver) CardsByCardGroup(ctx context.Context, cardGroupID int64, // UserRole is the resolver for the userRole field. func (r *queryResolver) UserRole(ctx context.Context, userID int64) (*model.Role, error) { if userID <= 0 { - logger.Logger.ErrorContext(ctx, "Validation error", fmt.Errorf("invalid userID: %d", userID)) + logger.Logger.ErrorContext(ctx, "User", fmt.Errorf("invalid userID: %d", userID)) return nil, fmt.Errorf("invalid userID: %d", userID) } return r.Srv.GetRoleByUserID(ctx, userID) @@ -212,7 +246,7 @@ func (r *queryResolver) UserRole(ctx context.Context, userID int64) (*model.Role // CardGroupsByUser is the resolver for the cardGroupsByUser field. func (r *queryResolver) CardGroupsByUser(ctx context.Context, userID int64, first *int, after *int64, last *int, before *int64) (*model.CardGroupConnection, error) { if userID <= 0 { - logger.Logger.ErrorContext(ctx, "Validation error", fmt.Errorf("invalid userID: %d", userID)) + logger.Logger.ErrorContext(ctx, "User", fmt.Errorf("invalid userID: %d", userID)) return nil, fmt.Errorf("invalid userID: %d", userID) } return r.Srv.PaginatedCardGroupsByUser(ctx, userID, first, after, last, before) @@ -221,17 +255,90 @@ func (r *queryResolver) CardGroupsByUser(ctx context.Context, userID int64, firs // UsersByRole is the resolver for the usersByRole field. func (r *queryResolver) UsersByRole(ctx context.Context, roleID int64, first *int, after *int64, last *int, before *int64) (*model.UserConnection, error) { if roleID <= 0 { - logger.Logger.ErrorContext(ctx, "Validation error", fmt.Errorf("invalid roleID: %d", roleID)) + logger.Logger.ErrorContext(ctx, "Role", fmt.Errorf("invalid roleID: %d", roleID)) return nil, fmt.Errorf("invalid roleID: %d", roleID) } return r.Srv.PaginatedUsersByRole(ctx, roleID, first, after, last, before) } +// Users is the resolver for the users field in Role. +func (r *roleResolver) Users(ctx context.Context, obj *model.Role, first *int, after *int64, last *int, before *int64) (*model.UserConnection, error) { + var userIDs []string + for _, edge := range obj.Users.Edges { + userIDs = append(userIDs, strconv.FormatInt(edge.Node.ID, 10)) + } + thunks := r.Loaders.UserLoader.LoadMany(ctx, userIDs) + users, err := thunks() + if err != nil { + logger.Logger.ErrorContext(ctx, "Users", fmt.Errorf("fetch by dataloader: %+v", err)) + return nil, fmt.Errorf("fetch by dataloader: %+v", err) + } + + for i, user := range users { + users[i] = user + } + return &model.UserConnection{Nodes: users}, nil +} + +// CardGroups is the resolver for the cardGroups field in User. +func (r *userResolver) CardGroups(ctx context.Context, obj *model.User, first *int, after *int64, last *int, before *int64) (*model.CardGroupConnection, error) { + var cardGroupIDs []string + for _, edge := range obj.CardGroups.Edges { + cardGroupIDs = append(cardGroupIDs, strconv.FormatInt(edge.Node.ID, 10)) + } + thunks := r.Loaders.CardGroupLoader.LoadMany(ctx, cardGroupIDs) + cardGroups, err := thunks() + if err != nil { + logger.Logger.ErrorContext(ctx, "CardGroups", fmt.Errorf("fetch by dataloader: %+v", err)) + return nil, fmt.Errorf("fetch by dataloader: %+v", err) + } + + for i, cardGroup := range cardGroups { + cardGroups[i] = cardGroup + } + return &model.CardGroupConnection{Nodes: cardGroups}, nil +} + +// Roles is the resolver for the roles field in User. +func (r *userResolver) Roles(ctx context.Context, obj *model.User, first *int, after *int64, last *int, before *int64) (*model.RoleConnection, error) { + var roleIDs []string + for _, edge := range obj.Roles.Edges { + roleIDs = append(roleIDs, strconv.FormatInt(edge.Node.ID, 10)) + } + thunks := r.Loaders.RoleLoader.LoadMany(ctx, roleIDs) + roles, err := thunks() + if err != nil { + logger.Logger.ErrorContext(ctx, "Roles", fmt.Errorf("fetch by dataloader: %+v", err)) + return nil, fmt.Errorf("fetch by dataloader: %+v", err) + } + + for i, role := range roles { + roles[i] = role + } + return &model.RoleConnection{Nodes: roles}, nil +} + +// Card returns CardResolver implementation. +func (r *Resolver) Card() CardResolver { return &cardResolver{r} } + +// CardGroup returns CardGroupResolver implementation. +func (r *Resolver) CardGroup() CardGroupResolver { return &cardGroupResolver{r} } + // Mutation returns MutationResolver implementation. func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} } // Query returns QueryResolver implementation. func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } +// Role returns RoleResolver implementation. +func (r *Resolver) Role() RoleResolver { return &roleResolver{r} } + +// User returns UserResolver implementation. +func (r *Resolver) User() UserResolver { return &userResolver{r} } + +type cardResolver struct{ *Resolver } +type cardGroupResolver struct{ *Resolver } type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } +type roleResolver struct{ *Resolver } +type userResolver struct{ *Resolver } diff --git a/backend/graph/schema.resolvers_test.go b/backend/graph/schema.resolvers_test.go index 3d8e3c8..7d190f2 100644 --- a/backend/graph/schema.resolvers_test.go +++ b/backend/graph/schema.resolvers_test.go @@ -36,57 +36,42 @@ var db *gorm.DB 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(migrationFilePath) - // Run migrations if err := pg.RunGooseMigrationsUp(migrationFilePath); err != nil { log.Fatalf("failed to run migrations: %+v", err) } - // Setup Echo server db = pg.GetDB() e = NewRouter(db) - // Run the tests m.Run() } func NewRouter(db *gorm.DB) *echo.Echo { - e := echo.New() e.Use(middleware.Logger()) e.Use(middleware.Recover()) - - // Store the database connection in Echo's context e.Use(middlewares.DatabaseCtxMiddleware(db)) - - // Set Transaction Middleware e.Use(middlewares.TransactionMiddleware()) - // Create services service := services.New(db) - - // Validator validateWrapper := validator.NewValidateWrapper() - - // Create a new resolver with the database connection resolver := &Resolver{ - DB: db, - Srv: service, - VW: validateWrapper, + DB: db, + Srv: service, + VW: validateWrapper, + Loaders: NewLoaders(service), } srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolver})) - // Setup WebSocket for subscriptions srv.AddTransport(&transport.Websocket{ Upgrader: websocket.Upgrader{ ReadBufferSize: 1024, @@ -124,23 +109,19 @@ func testGraphQLQuery(t *testing.T, e *echo.Echo, jsonInput []byte, expected str var actualResponse map[string]interface{} var expectedResponse map[string]interface{} - // Parse the actual response if err := json.Unmarshal(rec.Body.Bytes(), &actualResponse); err != nil { t.Fatalf("Failed to unmarshal actual response: %v", err) } - // Parse the expected response if err := json.Unmarshal([]byte(expected), &expectedResponse); err != nil { t.Fatalf("Failed to unmarshal expected response: %v", err) } - // Remove the fields to ignore from both responses for _, field := range ignoreFields { removeField(actualResponse, field) removeField(expectedResponse, field) } - // Compare the modified responses assert.Equal(t, expectedResponse, actualResponse) } @@ -170,7 +151,6 @@ func TestMutationResolver(t *testing.T) { testutils.RunServersTest(t, db, func(t *testing.T) { t.Run("CreateCard", func(t *testing.T) { - // Step 1: Create a Cardgroup now := time.Now() cardgroup := repository.Cardgroup{ Name: "TestMutationResolver_CreateCard Cardgroup", @@ -179,13 +159,12 @@ func TestMutationResolver(t *testing.T) { } db.Create(&cardgroup) - // Step 2: Create a new card with the CardgroupID - intervalDays := 1 + interval_days := 1 input := model.NewCard{ Front: "CreateCard Front of card", Back: "CreateCard Back of card", ReviewDate: now, - IntervalDays: &intervalDays, // Properly initialized + IntervalDays: &interval_days, CardgroupID: cardgroup.ID, } jsonInput, _ := json.Marshal(map[string]interface{}{ @@ -222,39 +201,36 @@ func TestMutationResolver(t *testing.T) { }) t.Run("CreateCard_Error", func(t *testing.T) { - // Prepare GraphQL query with invalid ID jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($id: ID!) { - card(id: $id) { - id - front - back - review_date - interval_days - created - updated - } - }`, + card(id: $id) { + id + front + back + review_date + interval_days + created + updated + } + }`, "variables": map[string]interface{}{ - "id": -1, // Invalid ID + "id": -1, }, }) expected := `{ - "data": { - "card": null - }, - "errors": [{ - "message": "invalid id: -1", - "path": ["card"] - }] - }` + "data": { + "card": null + }, + "errors": [{ + "message": "invalid id: -1", + "path": ["card"] + }] + }` testGraphQLQuery(t, e, jsonInput, expected) }) t.Run("UpdateCard", func(t *testing.T) { - - // Step 1: Create a Cardgroup now := time.Now() cardgroup := repository.Cardgroup{ Name: "TestMutationResolver_UpdateCard Cardgroup", @@ -263,7 +239,6 @@ func TestMutationResolver(t *testing.T) { } db.Create(&cardgroup) - // Create a card to update card := repository.Card{ Front: "UpdateCard Old Front", Back: "UpdateCard Old Back", @@ -282,30 +257,30 @@ func TestMutationResolver(t *testing.T) { } jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `mutation ($id: ID!, $input: NewCard!) { - updateCard(id: $id, input: $input) { - id - front - back - review_date - interval_days - created - updated - } - }`, + updateCard(id: $id, input: $input) { + id + front + back + review_date + interval_days + created + updated + } + }`, "variables": map[string]interface{}{ "id": card.ID, "input": input, }, }) expected := `{ - "data": { - "updateCard": { - "id": ` + fmt.Sprintf("%d", card.ID) + `, - "front": "UpdateCard New Front", - "back": "UpdateCard New Back" - } - } - }` + "data": { + "updateCard": { + "id": ` + fmt.Sprintf("%d", card.ID) + `, + "front": "UpdateCard New Front", + "back": "UpdateCard New Back" + } + } + }` testGraphQLQuery(t, e, jsonInput, expected, "data.updateCard.created", "data.updateCard.updated", "data.updateCard.review_date", "data.updateCard.interval_days") }) @@ -328,7 +303,7 @@ func TestMutationResolver(t *testing.T) { } }`, "variables": map[string]interface{}{ - "id": -1, // Invalid ID + "id": -1, "input": input, }, }) @@ -337,17 +312,14 @@ func TestMutationResolver(t *testing.T) { "updateCard": null }, "errors": [{ - "message": "record not found", + "message": "invalid id: -1", "path": ["updateCard"] }] }` - testGraphQLQuery(t, e, jsonInput, expected) }) t.Run("DeleteCard", func(t *testing.T) { - - // Step 1: Create a Cardgroup now := time.Now() cardgroup := repository.Cardgroup{ Name: "TestMutationResolver_DeleteCard Cardgroup", @@ -356,10 +328,9 @@ func TestMutationResolver(t *testing.T) { } db.Create(&cardgroup) - // Step 2: Create a card to delete card := repository.Card{ - Front: "DeleteCard Front to delete", - Back: "DeleteCard Back to delete", + Front: "DeleteCard Front", + Back: "DeleteCard Back", ReviewDate: time.Now(), IntervalDays: 1, CardGroupID: cardgroup.ID, @@ -370,43 +341,40 @@ func TestMutationResolver(t *testing.T) { jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `mutation ($id: ID!) { - deleteCard(id: $id) - }`, + deleteCard(id: $id) + }`, "variables": map[string]interface{}{ "id": card.ID, }, }) expected := `{ - "data": { - "deleteCard": true - } - }` + "data": { + "deleteCard": true + } + }` testGraphQLQuery(t, e, jsonInput, expected) }) t.Run("DeleteCard_Error", func(t *testing.T) { - // Attempt to delete a card with an invalid ID jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `mutation ($id: ID!) { deleteCard(id: $id) }`, "variables": map[string]interface{}{ - "id": -1, // Invalid ID + "id": -1, }, }) expected := `{ "data": { - "deleteCard": null + "deleteCard": false }, "errors": [{ - "message": "record not found", + "message": "invalid id: -1", "path": ["deleteCard"] }] }` - testGraphQLQuery(t, e, jsonInput, expected) }) - }) } @@ -415,32 +383,29 @@ func TestQueryResolver(t *testing.T) { t.Parallel() testutils.RunServersTest(t, db, func(t *testing.T) { - t.Run("Cards", func(t *testing.T) { - // Step 1: Create a Cardgroup - now := time.Now() - cardgroup := repository.Cardgroup{ - Name: "TestQueryResolver_Cards Cardgroup", + t.Run("CardsByCardGroup", func(t *testing.T) { + now := time.Now().UTC() + cardGroup := repository.Cardgroup{ + Name: "Test CardGroup", Created: now, Updated: now, } - db.Create(&cardgroup) + db.Create(&cardGroup) - // Step 2: Create a Card associated with the created Cardgroup card := repository.Card{ - Front: "Cards Front", - Back: "Cards Back", + Front: "Card Front", + Back: "Card Back", ReviewDate: now, IntervalDays: 1, - CardGroupID: cardgroup.ID, + CardGroupID: cardGroup.ID, Created: now, Updated: now, } db.Create(&card) - // Prepare GraphQL query jsonInput, _ := json.Marshal(map[string]interface{}{ - "query": `{ - cards(first: 10) { + "query": `query ($cardGroupID: ID!) { + cardsByCardGroup(cardGroupID: $cardGroupID) { edges { node { id @@ -454,46 +419,30 @@ func TestQueryResolver(t *testing.T) { } } }`, + "variables": map[string]interface{}{ + "cardGroupID": cardGroup.ID, + }, }) - // Execute GraphQL query - req := httptest.NewRequest(http.MethodPost, "/query", bytes.NewBuffer(jsonInput)) - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - rec := httptest.NewRecorder() - - e.ServeHTTP(rec, req) - - // Check HTTP status code - assert.Equal(t, http.StatusOK, rec.Code, "Expected HTTP status 200, got %v", rec.Code) - - // Parse response body - var response map[string]interface{} - if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil { - t.Fatalf("Failed to unmarshal response: %v", err) - } - - // Check for errors in the response - if errors, ok := response["errors"]; ok { - t.Fatalf("GraphQL errors: %v", errors) - } - - // Check number of cards in the response - edges, ok := response["data"].(map[string]interface{})["cards"].(map[string]interface{})["edges"].([]interface{}) - if !ok { - t.Fatalf("Failed to parse cards from response") - } - assert.Len(t, edges, 1, "Expected number of cards to be 1") + expected := fmt.Sprintf(`{ + "data": { + "cardsByCardGroup": { + "edges": [{ + "node": { + "id": %d, + "front": "Card Front", + "back": "Card Back", + "interval_days": 1 + } + }] + } + } + }`, card.ID) - // Ensure the card details match what was created - cardDetails := edges[0].(map[string]interface{})["node"].(map[string]interface{}) - assert.Equal(t, "Cards Front", cardDetails["front"]) - assert.Equal(t, "Cards Back", cardDetails["back"]) - assert.Equal(t, float64(1), cardDetails["interval_days"]) + testGraphQLQuery(t, e, jsonInput, expected, "data.cardsByCardGroup.edges.node.created", "data.cardsByCardGroup.edges.node.updated", "data.cardsByCardGroup.edges.node.review_date") }) t.Run("Card", func(t *testing.T) { - - // Step 1: Create a Cardgroup now := time.Now() cardgroup := repository.Cardgroup{ Name: "TestQueryResolver_Card Cardgroup", @@ -502,7 +451,6 @@ func TestQueryResolver(t *testing.T) { } db.Create(&cardgroup) - // Step 2: Create a Card associated with the created Cardgroup card := repository.Card{ Front: "Card Front", Back: "Card Back", @@ -514,76 +462,75 @@ func TestQueryResolver(t *testing.T) { } db.Create(&card) - // Prepare GraphQL query jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($id: ID!) { - card(id: $id) { - id - front - back - review_date - interval_days - created - updated - } - }`, + card(id: $id) { + id + front + back + review_date + interval_days + created + updated + } + }`, "variables": map[string]interface{}{ "id": card.ID, }, }) expected := fmt.Sprintf(`{ - "data": { - "card": { - "id": %d, - "front": "Card Front", - "back": "Card Back" - } - } - }`, card.ID) + "data": { + "card": { + "id": %d, + "front": "Card Front", + "back": "Card Back", + "review_date": "%s", + "interval_days": 1, + "created": "%s", + "updated": "%s" + } + } + }`, card.ID, card.ReviewDate.Format(time.RFC3339Nano), card.Created.Format(time.RFC3339Nano), card.Updated.Format(time.RFC3339Nano)) - testGraphQLQuery(t, e, jsonInput, expected, "data.card.created", "data.card.updated", "data.card.review_date", "data.card.interval_days") + testGraphQLQuery(t, e, jsonInput, expected, "data.card.id", "data.card.created", "data.card.updated", "data.card.review_date") }) t.Run("Card_Error", func(t *testing.T) { - // Prepare GraphQL query with invalid ID jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($id: ID!) { - card(id: $id) { - id - front - back - review_date - interval_days - created - updated - } - }`, + card(id: $id) { + id + front + back + review_date + interval_days + created + updated + } + }`, "variables": map[string]interface{}{ - "id": -1, // Invalid ID + "id": -1, }, }) expected := `{ - "data": { - "card": null - }, - "errors": [{ - "message": "invalid id: -1", - "path": ["card"] - }] - }` + "data": { + "card": null + }, + "errors": [{ + "message": "invalid id: -1", + "path": ["card"] + }] + }` testGraphQLQuery(t, e, jsonInput, expected) }) - // Test for roles t.Run("Roles", func(t *testing.T) { - // Step 1: Create a Role role := repository.Role{ Name: "Test Role", } db.Create(&role) - // Prepare GraphQL query jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `{ roles { @@ -593,99 +540,88 @@ func TestQueryResolver(t *testing.T) { }`, }) - // Execute GraphQL query req := httptest.NewRequest(http.MethodPost, "/query", bytes.NewBuffer(jsonInput)) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) - // Check HTTP status code assert.Equal(t, http.StatusOK, rec.Code) - // Parse response body var response map[string]interface{} if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil { t.Fatalf("Failed to unmarshal response: %v", err) } - // Check number of roles in the response roles, ok := response["data"].(map[string]interface{})["roles"].([]interface{}) if !ok { t.Fatalf("Failed to parse roles from response") } assert.Len(t, roles, 1, "Expected number of roles to be 1") - // Ensure the role details match what was created roleDetails := roles[0].(map[string]interface{}) assert.Equal(t, "Test Role", roleDetails["name"]) }) t.Run("Roles_Error", func(t *testing.T) { - // Prepare GraphQL query with invalid field jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `{ - roles { - invalid_field - } - }`, + roles { + invalid_field + } + }`, }) expected := `{ - "data": null, - "errors": [{ - "message": "Cannot query field \"invalid_field\" on type \"Role\".", - "extensions": { - "code": "GRAPHQL_VALIDATION_FAILED" - }, - "locations": [{ - "line": 3, - "column": 17 - }] - }] - }` + "data": null, + "errors": [{ + "message": "Cannot query field \"invalid_field\" on type \"Role\".", + "extensions": { + "code": "GRAPHQL_VALIDATION_FAILED" + }, + "locations": [{ + "line": 3, + "column": 17 + }] + }] + }` testGraphQLQuery(t, e, jsonInput, expected) }) - // Test for role by ID t.Run("Role", func(t *testing.T) { - // Step 1: Clean up roles to avoid duplicate key error if err := db.Where("name = ?", "Test Role").Delete(&repository.Role{}).Error; err != nil { t.Fatalf("Failed to delete existing Test Role: %v", err) } - // Step 2: Create a Role role := repository.Role{ Name: "Test Role", } db.Create(&role) - // Prepare GraphQL query jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($id: ID!) { - role(id: $id) { - id - name - } - }`, + role(id: $id) { + id + name + } + }`, "variables": map[string]interface{}{ "id": role.ID, }, }) expected := fmt.Sprintf(`{ - "data": { - "role": { - "id": %d, - "name": "Test Role" - } - } - }`, role.ID) + "data": { + "role": { + "id": %d, + "name": "Test Role" + } + } + }`, role.ID) testGraphQLQuery(t, e, jsonInput, expected) }) t.Run("Role_Error", func(t *testing.T) { - // Prepare GraphQL query with invalid ID jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($id: ID!) { role(id: $id) { @@ -694,7 +630,7 @@ func TestQueryResolver(t *testing.T) { } }`, "variables": map[string]interface{}{ - "id": -1, // Invalid ID + "id": -1, }, }) expected := `{ @@ -710,105 +646,91 @@ func TestQueryResolver(t *testing.T) { testGraphQLQuery(t, e, jsonInput, expected) }) - // Test for users t.Run("Users", func(t *testing.T) { - - // Step 1: Create a User user := repository.User{ Name: "Test User", } db.Create(&user) - // Prepare GraphQL query jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `{ - users { - id - name - } - }`, + users { + id + name + } + }`, }) - // Execute GraphQL query req := httptest.NewRequest(http.MethodPost, "/query", bytes.NewBuffer(jsonInput)) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) - // Check HTTP status code assert.Equal(t, http.StatusOK, rec.Code) - // Parse response body var response map[string]interface{} if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil { t.Fatalf("Failed to unmarshal response: %v", err) } - // Check number of users in the response users, ok := response["data"].(map[string]interface{})["users"].([]interface{}) if !ok { t.Fatalf("Failed to parse users from response") } assert.Len(t, users, 1, "Expected number of users to be 1") - // Ensure the user details match what was created userDetails := users[0].(map[string]interface{}) assert.Equal(t, "Test User", userDetails["name"]) }) t.Run("Users_Error", func(t *testing.T) { - // Prepare GraphQL query with invalid field jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `{ - users { - invalid_field - } - }`, + users { + invalid_field + } + }`, }) expected := `{ - "errors": [{ - "message": "Cannot query field \"invalid_field\" on type \"User\".", - "locations": [{"line": 3, "column": 5}], - "extensions": { - "code": "GRAPHQL_VALIDATION_FAILED" - } - }], - "data": null - }` + "errors": [{ + "message": "Cannot query field \"invalid_field\" on type \"User\".", + "locations": [{"line": 3, "column": 5}], + "extensions": { + "code": "GRAPHQL_VALIDATION_FAILED" + } + }], + "data": null + }` testGraphQLQuery(t, e, jsonInput, expected) }) - // Test for user by ID t.Run("User", func(t *testing.T) { - - // Step 1: Create a User user := repository.User{ Name: "Test User", } db.Create(&user) - // Prepare GraphQL query jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($id: ID!) { - user(id: $id) { - id - name - } - }`, + user(id: $id) { + id + name + } + }`, "variables": map[string]interface{}{ "id": user.ID, }, }) expected := fmt.Sprintf(`{ - "data": { - "user": { - "id": %d, - "name": "Test User" - } - } - }`, user.ID) + "data": { + "user": { + "id": %d, + "name": "Test User" + } + } + }`, user.ID) testGraphQLQuery(t, e, jsonInput, expected) }) @@ -816,32 +738,29 @@ func TestQueryResolver(t *testing.T) { t.Run("User_Error", func(t *testing.T) { jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($id: ID!) { - user(id: $id) { - id - name - } - }`, + user(id: $id) { + id + name + } + }`, "variables": map[string]interface{}{ - "id": -1, // Invalid ID + "id": -1, }, }) expected := `{ - "data": { - "user": null - }, - "errors": [{ - "message": "invalid id: -1", - "path": ["user"] - }] - }` + "data": { + "user": null + }, + "errors": [{ + "message": "invalid id: -1", + "path": ["user"] + }] + }` testGraphQLQuery(t, e, jsonInput, expected) }) - // Test for cardsByCardGroup t.Run("CardsByCardGroup", func(t *testing.T) { - - // Step 1: Create a Cardgroup now := time.Now() cardgroup := repository.Cardgroup{ Name: "Test CardGroup", @@ -850,7 +769,6 @@ func TestQueryResolver(t *testing.T) { } db.Create(&cardgroup) - // Step 2: Create a Card associated with the created Cardgroup card := repository.Card{ Front: "Card Front", Back: "Card Back", @@ -862,34 +780,32 @@ func TestQueryResolver(t *testing.T) { } db.Create(&card) - // Prepare GraphQL query jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($cardGroupId: ID!) { - cardsByCardGroup(cardGroupId: $cardGroupId) { - id - front - back - } - }`, + cardsByCardGroup(cardGroupId: $cardGroupId) { + id + front + back + } + }`, "variables": map[string]interface{}{ "cardGroupId": cardgroup.ID, }, }) expected := fmt.Sprintf(`{ - "data": { - "cardsByCardGroup": [{ - "id": %d, - "front": "Card Front", - "back": "Card Back" - }] - } - }`, card.ID) + "data": { + "cardsByCardGroup": [{ + "id": %d, + "front": "Card Front", + "back": "Card Back" + }] + } + }`, card.ID) testGraphQLQuery(t, e, jsonInput, expected) }) t.Run("CardsByCardGroup_Error", func(t *testing.T) { - // Prepare GraphQL query with invalid cardGroupId jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($cardGroupId: ID!) { cardsByCardGroup(cardGroupId: $cardGroupId) { @@ -899,7 +815,7 @@ func TestQueryResolver(t *testing.T) { } }`, "variables": map[string]interface{}{ - "cardGroupId": -1, // Invalid ID + "cardGroupId": -1, }, }) expected := `{ @@ -913,9 +829,7 @@ func TestQueryResolver(t *testing.T) { testGraphQLQuery(t, e, jsonInput, expected) }) - // Test for userRole t.Run("UserRole", func(t *testing.T) { - // Step 1: Check if the role exists and create if not var role repository.Role if err := db.Where("name = ?", "Test Role").First(&role).Error; err != nil { role = repository.Role{ @@ -924,39 +838,36 @@ func TestQueryResolver(t *testing.T) { db.Create(&role) } - // Step 2: Create a User and assign the role user := repository.User{ Name: "Test User", } db.Create(&user) db.Model(&user).Association("Roles").Append(&role) - // Prepare GraphQL query jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($userId: ID!) { - userRole(userId: $userId) { - id - name - } - }`, + userRole(userId: $userId) { + id + name + } + }`, "variables": map[string]interface{}{ "userId": user.ID, }, }) expected := fmt.Sprintf(`{ - "data": { - "userRole": { - "id": %d, - "name": "Test Role" + "data": { + "userRole": { + "id": %d, + "name": "Test Role" + } } - } - }`, role.ID) + }`, role.ID) testGraphQLQuery(t, e, jsonInput, expected) }) t.Run("UserRole_Error", func(t *testing.T) { - // Prepare GraphQL query with invalid userId jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($userId: ID!) { userRole(userId: $userId) { @@ -965,7 +876,7 @@ func TestQueryResolver(t *testing.T) { } }`, "variables": map[string]interface{}{ - "userId": -1, // Invalid ID + "userId": -1, }, }) expected := `{ @@ -981,16 +892,12 @@ func TestQueryResolver(t *testing.T) { testGraphQLQuery(t, e, jsonInput, expected) }) - // Test for cardGroupsByUser t.Run("CardGroupsByUser", func(t *testing.T) { - - // Step 1: Create a User user := repository.User{ Name: "Test User", } db.Create(&user) - // Step 2: Create a Cardgroup and assign the user now := time.Now() cardgroup := repository.Cardgroup{ Name: "Test CardGroup", @@ -1000,32 +907,30 @@ func TestQueryResolver(t *testing.T) { db.Create(&cardgroup) db.Model(&cardgroup).Association("Users").Append(&user) - // Prepare GraphQL query jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($userId: ID!) { - cardGroupsByUser(userId: $userId) { - id - name - } - }`, + cardGroupsByUser(userId: $userId) { + id + name + } + }`, "variables": map[string]interface{}{ "userId": user.ID, }, }) expected := fmt.Sprintf(`{ - "data": { - "cardGroupsByUser": [{ - "id": %d, - "name": "Test CardGroup" - }] - } - }`, cardgroup.ID) + "data": { + "cardGroupsByUser": [{ + "id": %d, + "name": "Test CardGroup" + }] + } + }`, cardgroup.ID) testGraphQLQuery(t, e, jsonInput, expected) }) t.Run("CardGroupsByUser_Error", func(t *testing.T) { - // Prepare GraphQL query with invalid userId jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($userId: ID!) { cardGroupsByUser(userId: $userId) { @@ -1034,7 +939,7 @@ func TestQueryResolver(t *testing.T) { } }`, "variables": map[string]interface{}{ - "userId": -1, // Invalid ID + "userId": -1, }, }) expected := `{ @@ -1048,31 +953,25 @@ func TestQueryResolver(t *testing.T) { testGraphQLQuery(t, e, jsonInput, expected) }) - // Test for usersByRole t.Run("UsersByRole", func(t *testing.T) { - // Step 1: Ensure the role does not exist before creating it var existingRole repository.Role roleName := "Test Role" if err := db.Where("name = ?", roleName).First(&existingRole).Error; err == nil { - // Role already exists, delete it first db.Delete(&existingRole) } - // Step 2: Create a new Role role := repository.Role{ Name: roleName, } db.Create(&role) - // Step 3: Create a User and assign the role user := repository.User{ Name: "Test User", } db.Create(&user) db.Model(&user).Association("Roles").Append(&role) - // Prepare GraphQL query jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($roleId: ID!) { usersByRole(roleId: $roleId) { @@ -1097,25 +996,24 @@ func TestQueryResolver(t *testing.T) { }) t.Run("UsersByRole_Error", func(t *testing.T) { - // Prepare GraphQL query with invalid roleId jsonInput, _ := json.Marshal(map[string]interface{}{ "query": `query ($roleId: ID!) { - usersByRole(roleId: $roleId) { - id - name - } - }`, + usersByRole(roleId: $roleId) { + id + name + } + }`, "variables": map[string]interface{}{ - "roleId": -1, // Invalid ID + "roleId": -1, }, }) expected := `{ - "data": null, - "errors": [{ - "message": "invalid roleID: -1", - "path": ["usersByRole"] - }] - }` + "data": null, + "errors": [{ + "message": "invalid roleID: -1", + "path": ["usersByRole"] + }] + }` testGraphQLQuery(t, e, jsonInput, expected) }) diff --git a/backend/graph/services/card.go b/backend/graph/services/card.go index 7d13415..01d9718 100644 --- a/backend/graph/services/card.go +++ b/backend/graph/services/card.go @@ -6,9 +6,10 @@ import ( "context" "errors" "fmt" - "gorm.io/gorm" "strings" "time" + + "gorm.io/gorm" ) type cardService struct { @@ -228,3 +229,11 @@ func (s *cardService) PaginatedCardsByCardGroup(ctx context.Context, cardGroupID TotalCount: len(cards), }, nil } + +func (s *cardService) GetCardsByIDs(ctx context.Context, ids []int64) ([]*model.Card, error) { + var cards []*model.Card + if err := s.db.Where("id IN ?", ids).Find(&cards).Error; err != nil { + return nil, err + } + return cards, nil +} diff --git a/backend/graph/services/cardgroup.go b/backend/graph/services/cardgroup.go index b83c565..c2a9e04 100644 --- a/backend/graph/services/cardgroup.go +++ b/backend/graph/services/cardgroup.go @@ -230,3 +230,11 @@ func (s *cardGroupService) PaginatedCardGroupsByUser(ctx context.Context, userID TotalCount: len(cardGroups), }, nil } + +func (s *cardGroupService) GetCardGroupsByIDs(ctx context.Context, ids []int64) ([]*model.CardGroup, error) { + var cardGroups []*model.CardGroup + if err := s.db.Where("id IN ?", ids).Find(&cardGroups).Error; err != nil { + return nil, err + } + return cardGroups, nil +} diff --git a/backend/graph/services/role.go b/backend/graph/services/role.go index 3f6aab5..7429eb5 100644 --- a/backend/graph/services/role.go +++ b/backend/graph/services/role.go @@ -235,3 +235,11 @@ func (s *roleService) PaginatedRolesByUser(ctx context.Context, userID int64, fi TotalCount: len(roles), }, nil } + +func (s *roleService) GetRolesByIDs(ctx context.Context, ids []int64) ([]*model.Role, error) { + var roles []*model.Role + if err := s.db.Where("id IN ?", ids).Find(&roles).Error; err != nil { + return nil, err + } + return roles, nil +} diff --git a/backend/graph/services/service.go b/backend/graph/services/service.go index 2e16794..ad3a0ac 100644 --- a/backend/graph/services/service.go +++ b/backend/graph/services/service.go @@ -23,6 +23,7 @@ type CardService interface { CardsByCardGroup(ctx context.Context, cardGroupID int64) ([]*model.Card, error) PaginatedCards(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.CardConnection, error) PaginatedCardsByCardGroup(ctx context.Context, cardGroupID int64, first *int, after *int64, last *int, before *int64) (*model.CardConnection, error) + GetCardsByIDs(ctx context.Context, ids []int64) ([]*model.Card, error) } type CardGroupService interface { @@ -36,6 +37,7 @@ type CardGroupService interface { GetCardGroupsByUser(ctx context.Context, userID int64) ([]*model.CardGroup, error) PaginatedCardGroups(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.CardGroupConnection, error) PaginatedCardGroupsByUser(ctx context.Context, userID int64, first *int, after *int64, last *int, before *int64) (*model.CardGroupConnection, error) + GetCardGroupsByIDs(ctx context.Context, ids []int64) ([]*model.CardGroup, error) } type UserService interface { @@ -47,6 +49,7 @@ type UserService interface { DeleteUser(ctx context.Context, id int64) (*bool, error) PaginatedUsers(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.UserConnection, error) PaginatedUsersByRole(ctx context.Context, roleID int64, first *int, after *int64, last *int, before *int64) (*model.UserConnection, error) + GetUsersByIDs(ctx context.Context, ids []int64) ([]*model.User, error) } type RoleService interface { @@ -59,6 +62,7 @@ type RoleService interface { RemoveRoleFromUser(ctx context.Context, userID int64, roleID int64) (*model.User, error) Roles(ctx context.Context) ([]*model.Role, error) PaginatedRoles(ctx context.Context, first *int, after *int64, last *int, before *int64) (*model.RoleConnection, error) + GetRolesByIDs(ctx context.Context, ids []int64) ([]*model.Role, error) } type services struct { diff --git a/backend/graph/services/user.go b/backend/graph/services/user.go index 8feb2d7..e311579 100644 --- a/backend/graph/services/user.go +++ b/backend/graph/services/user.go @@ -199,3 +199,11 @@ func (s *userService) PaginatedUsersByRole(ctx context.Context, roleID int64, fi TotalCount: len(users), }, nil } + +func (s *userService) GetUsersByIDs(ctx context.Context, ids []int64) ([]*model.User, error) { + var users []*model.User + if err := s.db.Where("id IN ?", ids).Find(&users).Error; err != nil { + return nil, err + } + return users, nil +} diff --git a/backend/web/server/server.go b/backend/web/server/server.go index 762d11a..5734b10 100644 --- a/backend/web/server/server.go +++ b/backend/web/server/server.go @@ -40,9 +40,10 @@ func NewRouter(db *gorm.DB) *echo.Echo { // Create a new resolver resolver := &graph.Resolver{ - DB: db, - Srv: service, - VW: validateWrapper, + DB: db, + Srv: service, + VW: validateWrapper, + Loaders: graph.NewLoaders(service), } srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: resolver}))