From 7fab8287a26182990a05a5262861f1b5db26f68d Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Thu, 25 Feb 2021 18:48:59 +0100 Subject: [PATCH] Add API pagination for faces --- api/gqlgen.yml | 3 + api/graphql/generated.go | 170 +++++++++++++++++--------- api/graphql/resolvers/faces.go | 68 ++++++----- api/graphql/schema.graphql | 3 +- ui/src/Pages/PeoplePage/PeoplePage.js | 5 +- 5 files changed, 158 insertions(+), 91 deletions(-) diff --git a/api/gqlgen.yml b/api/gqlgen.yml index 0ca6f79b..449ee444 100644 --- a/api/gqlgen.yml +++ b/api/gqlgen.yml @@ -46,6 +46,9 @@ models: model: github.com/photoview/photoview/api/graphql/models.ShareToken FaceGroup: model: github.com/photoview/photoview/api/graphql/models.FaceGroup + fields: + imageFaces: + resolver: true ImageFace: model: github.com/photoview/photoview/api/graphql/models.ImageFace fields: diff --git a/api/graphql/generated.go b/api/graphql/generated.go index 00734b52..8c325750 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -39,6 +39,7 @@ type Config struct { type ResolverRoot interface { Album() AlbumResolver + FaceGroup() FaceGroupResolver ImageFace() ImageFaceResolver Media() MediaResolver Mutation() MutationResolver @@ -73,9 +74,10 @@ type ComplexityRoot struct { } FaceGroup struct { - ID func(childComplexity int) int - ImageFaces func(childComplexity int) int - Label func(childComplexity int) int + ID func(childComplexity int) int + ImageFaceCount func(childComplexity int) int + ImageFaces func(childComplexity int, paginate *models.Pagination) int + Label func(childComplexity int) int } FaceRectangle struct { @@ -261,6 +263,10 @@ type AlbumResolver interface { Path(ctx context.Context, obj *models.Album) ([]*models.Album, error) Shares(ctx context.Context, obj *models.Album) ([]*models.ShareToken, error) } +type FaceGroupResolver interface { + ImageFaces(ctx context.Context, obj *models.FaceGroup, paginate *models.Pagination) ([]*models.ImageFace, error) + ImageFaceCount(ctx context.Context, obj *models.FaceGroup) (int, error) +} type ImageFaceResolver interface { FaceGroup(ctx context.Context, obj *models.ImageFace) (*models.FaceGroup, error) } @@ -451,12 +457,24 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.FaceGroup.ID(childComplexity), true + case "FaceGroup.imageFaceCount": + if e.complexity.FaceGroup.ImageFaceCount == nil { + break + } + + return e.complexity.FaceGroup.ImageFaceCount(childComplexity), true + case "FaceGroup.imageFaces": if e.complexity.FaceGroup.ImageFaces == nil { break } - return e.complexity.FaceGroup.ImageFaces(childComplexity), true + args, err := ec.field_FaceGroup_imageFaces_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.FaceGroup.ImageFaces(childComplexity, args["paginate"].(*models.Pagination)), true case "FaceGroup.label": if e.complexity.FaceGroup.Label == nil { @@ -1878,7 +1896,8 @@ type TimelineGroup { type FaceGroup { id: ID! label: String - imageFaces: [ImageFace!]! + imageFaces(paginate: Pagination): [ImageFace!]! + imageFaceCount: Int! } type ImageFace { @@ -1959,6 +1978,21 @@ func (ec *executionContext) field_Album_subAlbums_args(ctx context.Context, rawA return args, nil } +func (ec *executionContext) field_FaceGroup_imageFaces_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *models.Pagination + if tmp, ok := rawArgs["paginate"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("paginate")) + arg0, err = ec.unmarshalOPagination2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPagination(ctx, tmp) + if err != nil { + return nil, err + } + } + args["paginate"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_authorizeUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -3267,14 +3301,21 @@ func (ec *executionContext) _FaceGroup_imageFaces(ctx context.Context, field gra Object: "FaceGroup", Field: field, Args: nil, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, } ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_FaceGroup_imageFaces_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.ImageFaces, nil + return ec.resolvers.FaceGroup().ImageFaces(rctx, obj, args["paginate"].(*models.Pagination)) }) if err != nil { ec.Error(ctx, err) @@ -3286,9 +3327,44 @@ func (ec *executionContext) _FaceGroup_imageFaces(ctx context.Context, field gra } return graphql.Null } - res := resTmp.([]models.ImageFace) + res := resTmp.([]*models.ImageFace) fc.Result = res - return ec.marshalNImageFace2ᚕgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐImageFaceᚄ(ctx, field.Selections, res) + return ec.marshalNImageFace2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐImageFaceᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _FaceGroup_imageFaceCount(ctx context.Context, field graphql.CollectedField, obj *models.FaceGroup) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "FaceGroup", + 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.FaceGroup().ImageFaceCount(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.(int) + fc.Result = res + return ec.marshalNInt2int(ctx, field.Selections, res) } func (ec *executionContext) _FaceRectangle_minX(ctx context.Context, field graphql.CollectedField, obj *models.FaceRectangle) (ret graphql.Marshaler) { @@ -9233,15 +9309,38 @@ func (ec *executionContext) _FaceGroup(ctx context.Context, sel ast.SelectionSet case "id": out.Values[i] = ec._FaceGroup_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "label": out.Values[i] = ec._FaceGroup_label(ctx, field, obj) case "imageFaces": - out.Values[i] = ec._FaceGroup_imageFaces(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } + 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._FaceGroup_imageFaces(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "imageFaceCount": + 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._FaceGroup_imageFaceCount(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -10817,47 +10916,6 @@ func (ec *executionContext) marshalNID2ᚕintᚄ(ctx context.Context, sel ast.Se return ret } -func (ec *executionContext) marshalNImageFace2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐImageFace(ctx context.Context, sel ast.SelectionSet, v models.ImageFace) graphql.Marshaler { - return ec._ImageFace(ctx, sel, &v) -} - -func (ec *executionContext) marshalNImageFace2ᚕgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐImageFaceᚄ(ctx context.Context, sel ast.SelectionSet, v []models.ImageFace) 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.marshalNImageFace2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐImageFace(ctx, sel, v[i]) - } - if isLen1 { - f(i) - } else { - go f(i) - } - - } - wg.Wait() - return ret -} - func (ec *executionContext) marshalNImageFace2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐImageFaceᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.ImageFace) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup diff --git a/api/graphql/resolvers/faces.go b/api/graphql/resolvers/faces.go index 4e0b28e4..6f863b6e 100644 --- a/api/graphql/resolvers/faces.go +++ b/api/graphql/resolvers/faces.go @@ -15,10 +15,18 @@ type imageFaceResolver struct { *Resolver } +type faceGroupResolver struct { + *Resolver +} + func (r *Resolver) ImageFace() api.ImageFaceResolver { return imageFaceResolver{r} } +func (r *Resolver) FaceGroup() api.FaceGroupResolver { + return faceGroupResolver{r} +} + func (r imageFaceResolver) FaceGroup(ctx context.Context, obj *models.ImageFace) (*models.FaceGroup, error) { if obj.FaceGroup != nil { return obj.FaceGroup, nil @@ -34,6 +42,27 @@ func (r imageFaceResolver) FaceGroup(ctx context.Context, obj *models.ImageFace) return &faceGroup, nil } +func (r faceGroupResolver) ImageFaces(ctx context.Context, obj *models.FaceGroup, paginate *models.Pagination) ([]*models.ImageFace, error) { + query := r.Database.Joins("Media").Where("face_group_id = ?", obj.ID) + query = models.FormatSQL(query, nil, paginate) + + var imageFaces []*models.ImageFace + if err := query.Find(&imageFaces).Error; err != nil { + return nil, err + } + + return imageFaces, nil +} + +func (r faceGroupResolver) ImageFaceCount(ctx context.Context, obj *models.FaceGroup) (int, error) { + var count int64 + if err := r.Database.Model(&models.ImageFace{}).Where("face_group_id = ?", obj.ID).Count(&count).Error; err != nil { + return -1, err + } + + return int(count), nil +} + func (r *queryResolver) MyFaceGroups(ctx context.Context, paginate *models.Pagination) ([]*models.FaceGroup, error) { user := auth.UserFromContext(ctx) if user == nil { @@ -49,47 +78,22 @@ func (r *queryResolver) MyFaceGroups(ctx context.Context, paginate *models.Pagin userAlbumIDs[i] = album.ID } - imageFaceQuery := r.Database. - Joins("Media"). - Where("Media.album_id IN (?)", userAlbumIDs) - - var imageFaces []*models.ImageFace - if err := imageFaceQuery.Find(&imageFaces).Error; err != nil { - return nil, err - } - - faceGroupMap := make(map[int][]models.ImageFace) - for _, face := range imageFaces { - _, found := faceGroupMap[face.FaceGroupID] - - if found { - faceGroupMap[face.FaceGroupID] = append(faceGroupMap[face.FaceGroupID], *face) - } else { - faceGroupMap[face.FaceGroupID] = make([]models.ImageFace, 1) - faceGroupMap[face.FaceGroupID][0] = *face - } - } - - faceGroupIDs := make([]int, len(faceGroupMap)) - i := 0 - for groupID := range faceGroupMap { - faceGroupIDs[i] = groupID - i++ - } - faceGroupQuery := r.Database. Joins("LEFT JOIN image_faces ON image_faces.id = face_groups.id"). - Where("face_groups.id IN (?)", faceGroupIDs). + // Where("face_groups.id IN (?)", faceGroupIDs). + Where("image_faces.media_id IN (?)", r.Database.Select("media_id").Table("media").Where("media.album_id IN (?)", userAlbumIDs)). Order("CASE WHEN label IS NULL THEN 1 ELSE 0 END") + faceGroupQuery = models.FormatSQL(faceGroupQuery, nil, paginate) + var faceGroups []*models.FaceGroup if err := faceGroupQuery.Find(&faceGroups).Error; err != nil { return nil, err } - for _, faceGroup := range faceGroups { - faceGroup.ImageFaces = faceGroupMap[faceGroup.ID] - } + // for _, faceGroup := range faceGroups { + // faceGroup.ImageFaces = faceGroupMap[faceGroup.ID] + // } return faceGroups, nil } diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index 15de4c0a..7a3d3816 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -333,7 +333,8 @@ type TimelineGroup { type FaceGroup { id: ID! label: String - imageFaces: [ImageFace!]! + imageFaces(paginate: Pagination): [ImageFace!]! + imageFaceCount: Int! } type ImageFace { diff --git a/ui/src/Pages/PeoplePage/PeoplePage.js b/ui/src/Pages/PeoplePage/PeoplePage.js index f4476d8a..ef2ffbe8 100644 --- a/ui/src/Pages/PeoplePage/PeoplePage.js +++ b/ui/src/Pages/PeoplePage/PeoplePage.js @@ -13,7 +13,8 @@ export const MY_FACES_QUERY = gql` myFaceGroups { id label - imageFaces { + imageFaceCount + imageFaces(paginate: { limit: 1 }) { id rectangle { minX @@ -125,7 +126,7 @@ const FaceDetails = ({ group }) => { labeled={!!group.label} onClick={() => setEditLabel(true)} > - {group.imageFaces.length} + {group.imageFaceCount} {group.label ?? 'Unlabeled'}