add a new field to the Profession graphql type (Qualifications)

This commit is contained in:
Dawid Wysokiński 2021-03-21 18:19:22 +01:00
parent 827a353fba
commit e936b9b0ed
15 changed files with 199 additions and 30 deletions

2
go.mod
View File

@ -19,7 +19,7 @@ require (
github.com/sirupsen/logrus v1.8.0
github.com/vektah/gqlparser/v2 v2.1.0
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/sys v0.0.0-20210313110737-8e9fff1a3a18 // indirect
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

4
go.sum
View File

@ -223,8 +223,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210313110737-8e9fff1a3a18 h1:jxr7/dEo+rR29uEBoLSWJ1tRHCFAMwFbGUU9nRqzpds=
golang.org/x/sys v0.0.0-20210313110737-8e9fff1a3a18/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

View File

@ -2,24 +2,31 @@ package dataloader
import (
"context"
"github.com/zdam-egzamin-zawodowy/backend/internal/profession"
"time"
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
"github.com/zdam-egzamin-zawodowy/backend/internal/qualification"
)
const (
wait = 2 * time.Millisecond
)
type Config struct {
ProfessionRepo profession.Repository
QualificationRepo qualification.Repository
}
type DataLoader struct {
QualificationByID *QualificationLoader
QualificationByID *QualificationLoader
QualificationsByProfessionID *QualificationSliceByProfessionIDLoader
}
func New(cfg Config) *DataLoader {
return &DataLoader{
QualificationByID: NewQualificationLoader(QualificationLoaderConfig{
Wait: 2 * time.Millisecond,
Wait: wait,
Fetch: func(ids []int) ([]*models.Qualification, []error) {
qualificationsNotInOrder, _, err := cfg.QualificationRepo.Fetch(context.Background(), &qualification.FetchConfig{
Filter: &models.QualificationFilter{
@ -41,5 +48,22 @@ func New(cfg Config) *DataLoader {
return qualifications, nil
},
}),
QualificationsByProfessionID: NewQualificationSliceByProfessionIDLoader(QualificationSliceByProfessionIDLoaderConfig{
Wait: wait,
Fetch: func(ids []int) ([][]*models.Qualification, []error) {
m, err := cfg.ProfessionRepo.GetAssociatedQualifications(context.Background(), ids...)
if err != nil {
return nil, []error{err}
}
qualifications := make([][]*models.Qualification, len(ids))
for i, id := range ids {
qualifications[i] = m[id]
}
return qualifications, nil
},
}),
}
}

View File

@ -194,5 +194,8 @@ func getComplexityRoot() generated.ComplexityRoot {
complexityRoot.Mutation.UpdateUser = func(childComplexity int, id int, input models.UserInput) int {
return 200 + childComplexity
}
complexityRoot.Profession.Qualifications = func(childComplexity int) int {
return 50 + childComplexity
}
return complexityRoot
}

View File

@ -38,6 +38,7 @@ type Config struct {
type ResolverRoot interface {
Mutation() MutationResolver
Profession() ProfessionResolver
Query() QueryResolver
Question() QuestionResolver
}
@ -66,11 +67,12 @@ type ComplexityRoot struct {
}
Profession struct {
CreatedAt func(childComplexity int) int
Description func(childComplexity int) int
ID func(childComplexity int) int
Name func(childComplexity int) int
Slug func(childComplexity int) int
CreatedAt func(childComplexity int) int
Description func(childComplexity int) int
ID func(childComplexity int) int
Name func(childComplexity int) int
Qualifications func(childComplexity int) int
Slug func(childComplexity int) int
}
ProfessionList struct {
@ -166,6 +168,9 @@ type MutationResolver interface {
DeleteUsers(ctx context.Context, ids []int) ([]*models.User, error)
SignIn(ctx context.Context, email string, password string, staySignedIn *bool) (*UserWithToken, error)
}
type ProfessionResolver interface {
Qualifications(ctx context.Context, obj *models.Profession) ([]*models.Qualification, error)
}
type QueryResolver interface {
Professions(ctx context.Context, filter *models.ProfessionFilter, limit *int, offset *int, sort []string) (*ProfessionList, error)
Profession(ctx context.Context, id *int, slug *string) (*models.Profession, error)
@ -392,6 +397,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Profession.Name(childComplexity), true
case "Profession.qualifications":
if e.complexity.Profession.Qualifications == nil {
break
}
return e.complexity.Profession.Qualifications(childComplexity), true
case "Profession.slug":
if e.complexity.Profession.Slug == nil {
break
@ -860,6 +872,7 @@ directive @hasRole(role: Role!) on FIELD_DEFINITION
name: String!
description: String
createdAt: Time!
qualifications: [Qualification!]! @goField(forceResolver: true)
}
type ProfessionList {
@ -3011,6 +3024,41 @@ func (ec *executionContext) _Profession_createdAt(ctx context.Context, field gra
return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res)
}
func (ec *executionContext) _Profession_qualifications(ctx context.Context, field graphql.CollectedField, obj *models.Profession) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Profession",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Profession().Qualifications(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]*models.Qualification)
fc.Result = res
return ec.marshalNQualification2ᚕᚖgithubᚗcomᚋzdamᚑegzaminᚑzawodowyᚋbackendᚋinternalᚋmodelsᚐQualificationᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _ProfessionList_total(ctx context.Context, field graphql.CollectedField, obj *ProfessionList) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -7085,25 +7133,39 @@ func (ec *executionContext) _Profession(ctx context.Context, sel ast.SelectionSe
case "id":
out.Values[i] = ec._Profession_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "slug":
out.Values[i] = ec._Profession_slug(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "name":
out.Values[i] = ec._Profession_name(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "description":
out.Values[i] = ec._Profession_description(ctx, field, obj)
case "createdAt":
out.Values[i] = ec._Profession_createdAt(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "qualifications":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Profession_qualifications(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@ -7967,6 +8029,43 @@ func (ec *executionContext) marshalNProfessionList2ᚖgithubᚗcomᚋzdamᚑegza
return ec._ProfessionList(ctx, sel, v)
}
func (ec *executionContext) marshalNQualification2ᚕᚖgithubᚗcomᚋzdamᚑegzaminᚑzawodowyᚋbackendᚋinternalᚋmodelsᚐQualificationᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.Qualification) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNQualification2ᚖgithubᚗcomᚋzdamᚑegzaminᚑzawodowyᚋbackendᚋinternalᚋmodelsᚐQualification(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
return ret
}
func (ec *executionContext) marshalNQualification2ᚖgithubᚗcomᚋzdamᚑegzaminᚑzawodowyᚋbackendᚋinternalᚋmodelsᚐQualification(ctx context.Context, sel ast.SelectionSet, v *models.Qualification) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {

View File

@ -5,6 +5,7 @@ package resolvers
import (
"context"
"github.com/zdam-egzamin-zawodowy/backend/internal/gin/middleware"
"github.com/zdam-egzamin-zawodowy/backend/internal/graphql/generated"
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
@ -57,3 +58,15 @@ func (r *queryResolver) Profession(ctx context.Context, id *int, slug *string) (
return nil, nil
}
func (r *professionResolver) Qualifications(
ctx context.Context,
obj *models.Profession,
) ([]*models.Qualification, error) {
if obj != nil {
if dataloader, err := middleware.DataLoaderFromContext(ctx); err == nil && dataloader != nil {
return dataloader.QualificationsByProfessionID.Load(obj.ID)
}
}
return []*models.Qualification{}, nil
}

View File

@ -23,8 +23,10 @@ type Resolver struct {
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type professionResolver struct{ *Resolver }
type questionResolver struct{ *Resolver }
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
func (r *Resolver) Question() generated.QuestionResolver { return &questionResolver{r} }
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
func (r *Resolver) Profession() generated.ProfessionResolver { return &professionResolver{r} }
func (r *Resolver) Question() generated.QuestionResolver { return &questionResolver{r} }

View File

@ -4,6 +4,7 @@ type Profession {
name: String!
description: String
createdAt: Time!
qualifications: [Qualification!]! @goField(forceResolver: true)
}
type ProfessionList {

View File

@ -19,4 +19,5 @@ type Repository interface {
UpdateMany(ctx context.Context, f *models.ProfessionFilter, input *models.ProfessionInput) ([]*models.Profession, error)
Delete(ctx context.Context, f *models.ProfessionFilter) ([]*models.Profession, error)
Fetch(ctx context.Context, cfg *FetchConfig) ([]*models.Profession, int, error)
GetAssociatedQualifications(ctx context.Context, ids ...int) (map[int][]*models.Qualification, error)
}

View File

@ -1,8 +1,9 @@
package repository
const (
messageNameIsAlreadyTaken = "Istnieje już zawód o podanej nazwie."
messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania zawodu, prosimy spróbować później."
messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania zawodu, prosimy spróbować później."
messageFailedToFetchModel = "Wystąpił błąd podczas pobierania zawodów, prosimy spróbować później."
messageNameIsAlreadyTaken = "Istnieje już zawód o podanej nazwie."
messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania zawodu."
messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania zawodu."
messageFailedToFetchModel = "Wystąpił błąd podczas pobierania zawodów."
messageFailedToFetchAssociatedQualifications = "Wystąpił błąd poczas pobierania powiązanych kwalifikacji."
)

View File

@ -3,6 +3,7 @@ package repository
import (
"context"
"fmt"
sqlutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/sql"
"strings"
errorutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/error"
@ -101,3 +102,26 @@ func (repo *pgRepository) Fetch(ctx context.Context, cfg *profession.FetchConfig
}
return items, total, nil
}
func (repo *pgRepository) GetAssociatedQualifications(
ctx context.Context,
ids ...int,
) (map[int][]*models.Qualification, error) {
m := make(map[int][]*models.Qualification)
for _, id := range ids {
m[id] = []*models.Qualification{}
}
qualificationToProfession := []*models.QualificationToProfession{}
if err := repo.
Model(&qualificationToProfession).
Context(ctx).
Where(sqlutils.BuildConditionArray("profession_id"), pg.Array(ids)).
Relation("Qualification").
Select(); err != nil {
return nil, errorutils.Wrap(err, messageFailedToFetchAssociatedQualifications)
}
for _, record := range qualificationToProfession {
m[record.ProfessionID] = append(m[record.ProfessionID], record.Qualification)
}
return m, nil
}

View File

@ -3,7 +3,7 @@ package repository
const (
messageNameIsAlreadyTaken = "Istnieje już kwalifikacja o podanej nazwie."
messageCodeIsAlreadyTaken = "Istnieje już kwalifikacja o podanym oznaczeniu."
messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania kwalifikacji, prosimy spróbować później."
messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania kwalifikacji, prosimy spróbować później."
messageFailedToFetchModel = "Wystąpił błąd podczas pobierania kwalifikacji, prosimy spróbować później."
messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania kwalifikacji."
messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania kwalifikacji."
messageFailedToFetchModel = "Wystąpił błąd podczas pobierania kwalifikacji."
)

View File

@ -2,8 +2,8 @@ package repository
const (
messageSimilarRecordExists = "Istnieje już podobne pytanie."
messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania pytania, prosimy spróbować później."
messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania pytania, prosimy spróbować później."
messageFailedToFetchModel = "Wystąpił błąd podczas pobierania pytań, prosimy spróbować później."
messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania pytania."
messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania pytania."
messageFailedToFetchModel = "Wystąpił błąd podczas pobierania pytań."
messageItemNotFound = "Nie znaleziono pytania."
)

View File

@ -2,7 +2,7 @@ package repository
const (
messageEmailIsAlreadyTaken = "Istnieje już użytkownik o podanym adresie e-mail."
messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania użytkownika, prosimy spróbować później."
messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania użytkownika, prosimy spróbować później."
messageFailedToFetchModel = "Wystąpił błąd podczas pobierania użytkowników, prosimy spróbować później."
messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania użytkownika."
messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania użytkownika."
messageFailedToFetchModel = "Wystąpił błąd podczas pobierania użytkowników."
)

View File

@ -123,6 +123,7 @@ func main() {
graphql.Use(
middleware.GinContextToContext(),
middleware.DataLoaderToContext(dataloader.Config{
ProfessionRepo: professionRepository,
QualificationRepo: qualificationRepository,
}),
middleware.Authenticate(authUsecase),