Add schemas for face related mutations

This commit is contained in:
viktorstrate
2021-02-17 13:50:32 +01:00
parent ce7a75ddc4
commit 6b48ac9a16
5 changed files with 405 additions and 7 deletions

View File

@@ -136,14 +136,18 @@ type ComplexityRoot struct {
Mutation struct {
AuthorizeUser func(childComplexity int, username string, password string) int
CombineFaceGroups func(childComplexity int, destinationFaceGroupID int, sourceFaceGroupID int) int
CreateUser func(childComplexity int, username string, password *string, admin bool) int
DeleteShareToken func(childComplexity int, token string) int
DeleteUser func(childComplexity int, id int) int
FavoriteMedia func(childComplexity int, mediaID int, favorite bool) int
InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int
MoveImageFace func(childComplexity int, imageFaceID int, newFaceGroupID int) int
ProtectShareToken func(childComplexity int, token string, password *string) int
RecognizeUnlabeledFaces func(childComplexity int) int
ScanAll func(childComplexity int) int
ScanUser func(childComplexity int, userID int) int
SetFaceGroupLabel func(childComplexity int, faceGroupID int, label *string) int
SetPeriodicScanInterval func(childComplexity int, interval int) int
SetScannerConcurrentWorkers func(childComplexity int, workers int) int
ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int
@@ -284,6 +288,10 @@ type MutationResolver interface {
UserRemoveRootAlbum(ctx context.Context, userID int, albumID int) (*models.Album, error)
SetPeriodicScanInterval(ctx context.Context, interval int) (int, error)
SetScannerConcurrentWorkers(ctx context.Context, workers int) (int, error)
SetFaceGroupLabel(ctx context.Context, faceGroupID int, label *string) (*models.FaceGroup, error)
CombineFaceGroups(ctx context.Context, destinationFaceGroupID int, sourceFaceGroupID int) (*models.FaceGroup, error)
MoveImageFace(ctx context.Context, imageFaceID int, newFaceGroupID int) (*models.ImageFace, error)
RecognizeUnlabeledFaces(ctx context.Context) ([]*models.ImageFace, error)
}
type QueryResolver interface {
SiteInfo(ctx context.Context) (*models.SiteInfo, error)
@@ -735,6 +743,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.AuthorizeUser(childComplexity, args["username"].(string), args["password"].(string)), true
case "Mutation.combineFaceGroups":
if e.complexity.Mutation.CombineFaceGroups == nil {
break
}
args, err := ec.field_Mutation_combineFaceGroups_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.CombineFaceGroups(childComplexity, args["destinationFaceGroupID"].(int), args["sourceFaceGroupID"].(int)), true
case "Mutation.createUser":
if e.complexity.Mutation.CreateUser == nil {
break
@@ -795,6 +815,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.InitialSetupWizard(childComplexity, args["username"].(string), args["password"].(string), args["rootPath"].(string)), true
case "Mutation.moveImageFace":
if e.complexity.Mutation.MoveImageFace == nil {
break
}
args, err := ec.field_Mutation_moveImageFace_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.MoveImageFace(childComplexity, args["imageFaceID"].(int), args["newFaceGroupID"].(int)), true
case "Mutation.protectShareToken":
if e.complexity.Mutation.ProtectShareToken == nil {
break
@@ -807,6 +839,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.ProtectShareToken(childComplexity, args["token"].(string), args["password"].(*string)), true
case "Mutation.recognizeUnlabeledFaces":
if e.complexity.Mutation.RecognizeUnlabeledFaces == nil {
break
}
return e.complexity.Mutation.RecognizeUnlabeledFaces(childComplexity), true
case "Mutation.scanAll":
if e.complexity.Mutation.ScanAll == nil {
break
@@ -826,6 +865,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.ScanUser(childComplexity, args["userId"].(int)), true
case "Mutation.setFaceGroupLabel":
if e.complexity.Mutation.SetFaceGroupLabel == nil {
break
}
args, err := ec.field_Mutation_setFaceGroupLabel_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.SetFaceGroupLabel(childComplexity, args["faceGroupID"].(int), args["label"].(*string)), true
case "Mutation.setPeriodicScanInterval":
if e.complexity.Mutation.SetPeriodicScanInterval == nil {
break
@@ -1583,6 +1634,15 @@ type Mutation {
"Set max number of concurrent scanner jobs running at once"
setScannerConcurrentWorkers(workers: Int!): Int!
"Assign a label to a face group, set label to null to remove the current one"
setFaceGroupLabel(faceGroupID: ID!, label: String): FaceGroup!
"Merge two face groups into a single one, all ImageFaces from source will be moved to destination"
combineFaceGroups(destinationFaceGroupID: ID!, sourceFaceGroupID: ID!): FaceGroup!
"Move a single ImageFace to another face group"
moveImageFace(imageFaceID: ID!, newFaceGroupID: ID!): ImageFace!
"Check all unlabeled faces to see if they match a labeled FaceGroup, and move them if they match"
recognizeUnlabeledFaces: [ImageFace!]!
}
type Subscription {
@@ -1894,6 +1954,30 @@ func (ec *executionContext) field_Mutation_authorizeUser_args(ctx context.Contex
return args, nil
}
func (ec *executionContext) field_Mutation_combineFaceGroups_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["destinationFaceGroupID"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("destinationFaceGroupID"))
arg0, err = ec.unmarshalNID2int(ctx, tmp)
if err != nil {
return nil, err
}
}
args["destinationFaceGroupID"] = arg0
var arg1 int
if tmp, ok := rawArgs["sourceFaceGroupID"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sourceFaceGroupID"))
arg1, err = ec.unmarshalNID2int(ctx, tmp)
if err != nil {
return nil, err
}
}
args["sourceFaceGroupID"] = arg1
return args, nil
}
func (ec *executionContext) field_Mutation_createUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -2014,6 +2098,30 @@ func (ec *executionContext) field_Mutation_initialSetupWizard_args(ctx context.C
return args, nil
}
func (ec *executionContext) field_Mutation_moveImageFace_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["imageFaceID"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("imageFaceID"))
arg0, err = ec.unmarshalNID2int(ctx, tmp)
if err != nil {
return nil, err
}
}
args["imageFaceID"] = arg0
var arg1 int
if tmp, ok := rawArgs["newFaceGroupID"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("newFaceGroupID"))
arg1, err = ec.unmarshalNID2int(ctx, tmp)
if err != nil {
return nil, err
}
}
args["newFaceGroupID"] = arg1
return args, nil
}
func (ec *executionContext) field_Mutation_protectShareToken_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -2053,6 +2161,30 @@ func (ec *executionContext) field_Mutation_scanUser_args(ctx context.Context, ra
return args, nil
}
func (ec *executionContext) field_Mutation_setFaceGroupLabel_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["faceGroupID"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("faceGroupID"))
arg0, err = ec.unmarshalNID2int(ctx, tmp)
if err != nil {
return nil, err
}
}
args["faceGroupID"] = arg0
var arg1 *string
if tmp, ok := rawArgs["label"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("label"))
arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
if err != nil {
return nil, err
}
}
args["label"] = arg1
return args, nil
}
func (ec *executionContext) field_Mutation_setPeriodicScanInterval_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -5184,6 +5316,167 @@ func (ec *executionContext) _Mutation_setScannerConcurrentWorkers(ctx context.Co
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_setFaceGroupLabel(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_setFaceGroupLabel_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 ec.resolvers.Mutation().SetFaceGroupLabel(rctx, args["faceGroupID"].(int), args["label"].(*string))
})
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.FaceGroup)
fc.Result = res
return ec.marshalNFaceGroup2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFaceGroup(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_combineFaceGroups(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_combineFaceGroups_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 ec.resolvers.Mutation().CombineFaceGroups(rctx, args["destinationFaceGroupID"].(int), args["sourceFaceGroupID"].(int))
})
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.FaceGroup)
fc.Result = res
return ec.marshalNFaceGroup2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFaceGroup(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_moveImageFace(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_moveImageFace_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 ec.resolvers.Mutation().MoveImageFace(rctx, args["imageFaceID"].(int), args["newFaceGroupID"].(int))
})
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.ImageFace)
fc.Result = res
return ec.marshalNImageFace2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐImageFace(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_recognizeUnlabeledFaces(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
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.Mutation().RecognizeUnlabeledFaces(rctx)
})
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.ImageFace)
fc.Result = res
return ec.marshalNImageFace2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐImageFaceᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _Notification_key(ctx context.Context, field graphql.CollectedField, obj *models.Notification) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -9252,6 +9545,26 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
invalids++
}
case "setFaceGroupLabel":
out.Values[i] = ec._Mutation_setFaceGroupLabel(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "combineFaceGroups":
out.Values[i] = ec._Mutation_combineFaceGroups(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "moveImageFace":
out.Values[i] = ec._Mutation_moveImageFace(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "recognizeUnlabeledFaces":
out.Values[i] = ec._Mutation_recognizeUnlabeledFaces(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -10253,6 +10566,10 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se
return res
}
func (ec *executionContext) marshalNFaceGroup2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFaceGroup(ctx context.Context, sel ast.SelectionSet, v models.FaceGroup) graphql.Marshaler {
return ec._FaceGroup(ctx, sel, &v)
}
func (ec *executionContext) marshalNFaceGroup2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFaceGroupᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.FaceGroup) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup

View File

@@ -52,7 +52,8 @@ func (r *queryResolver) MyFaceGroups(ctx context.Context, paginate *models.Pagin
}
faceGroupQuery := r.Database.
Where("id IN (?)", faceGroupIDs).
Joins("LEFT JOIN image_faces ON image_faces.id = face_groups.id").
Where("face_groups.id IN (?)", faceGroupIDs).
Order("CASE WHEN label IS NULL THEN 1 ELSE 0 END")
var faceGroups []*models.FaceGroup
@@ -66,3 +67,55 @@ func (r *queryResolver) MyFaceGroups(ctx context.Context, paginate *models.Pagin
return faceGroups, nil
}
func (r *mutationResolver) SetFaceGroupLabel(ctx context.Context, faceGroupID int, label *string) (*models.FaceGroup, error) {
user := auth.UserFromContext(ctx)
if user == nil {
return nil, errors.New("unauthorized")
}
if err := user.FillAlbums(r.Database); err != nil {
return nil, err
}
userAlbumIDs := make([]int, len(user.Albums))
for i, album := range user.Albums {
userAlbumIDs[i] = album.ID
}
// Verify that user owns at leat one of the images in the face group
imageFaceQuery := r.Database.
Select("image_faces.id").
Table("image_faces").
Joins("LEFT JOIN media ON media.id = image_faces.media_id").
Where("media.album_id IN (?)", userAlbumIDs)
faceGroupQuery := r.Database.
Model(&models.FaceGroup{}).
Joins("JOIN image_faces ON face_groups.id = image_faces.face_group_id").
Where("face_groups.id = ?", faceGroupID).
Where("image_faces.id IN (?)", imageFaceQuery)
var faceGroup models.FaceGroup
if err := faceGroupQuery.Find(&faceGroup).Error; err != nil {
return nil, err
}
if err := r.Database.Model(&faceGroup).Update("label", label).Error; err != nil {
return nil, err
}
return &faceGroup, nil
}
func (r *mutationResolver) CombineFaceGroups(ctx context.Context, destinationFaceGroupID int, sourceFaceGroupID int) (*models.FaceGroup, error) {
panic("not implemented")
}
func (r *mutationResolver) MoveImageFace(ctx context.Context, imageFaceID int, newFaceGroupID int) (*models.ImageFace, error) {
panic("not implemented")
}
func (r *mutationResolver) RecognizeUnlabeledFaces(ctx context.Context) ([]*models.ImageFace, error) {
panic("not implemented")
}

View File

@@ -115,6 +115,15 @@ type Mutation {
"Set max number of concurrent scanner jobs running at once"
setScannerConcurrentWorkers(workers: Int!): Int!
"Assign a label to a face group, set label to null to remove the current one"
setFaceGroupLabel(faceGroupID: ID!, label: String): FaceGroup!
"Merge two face groups into a single one, all ImageFaces from source will be moved to destination"
combineFaceGroups(destinationFaceGroupID: ID!, sourceFaceGroupID: ID!): FaceGroup!
"Move a single ImageFace to another face group"
moveImageFace(imageFaceID: ID!, newFaceGroupID: ID!): ImageFace!
"Check all unlabeled faces to see if they match a labeled FaceGroup, and move them if they match"
recognizeUnlabeledFaces: [ImageFace!]!
}
type Subscription {

View File

@@ -104,21 +104,21 @@ export const SideMenu = () => {
return (
<SideMenuContainer>
<SideButton to="/photos" exact>
<Icon name="image outline" />
<Icon name="image" />
<SideButtonLabel>Photos</SideButtonLabel>
</SideButton>
<SideButton to="/albums" exact>
<Icon name="images outline" />
<Icon name="images" />
<SideButtonLabel>Albums</SideButtonLabel>
</SideButton>
{mapboxEnabled ? (
<SideButton to="/places" exact>
<Icon name="map outline" />
<Icon name="map" />
<SideButtonLabel>Places</SideButtonLabel>
</SideButton>
) : null}
<SideButton to="/people" exact>
<Icon name="user outline" />
<Icon name="user" />
<SideButtonLabel>People</SideButtonLabel>
</SideButton>
{isAdmin ? (

View File

@@ -44,7 +44,7 @@ const CircleImageWrapper = styled.div`
overflow: hidden;
`
const FaceImage = styled(ProtectedImage)`
const FaceImagePortrait = styled(ProtectedImage)`
position: absolute;
width: 100%;
top: 50%;
@@ -52,7 +52,21 @@ const FaceImage = styled(ProtectedImage)`
${({ origin, scale }) =>
`translate(${(0.5 - origin.x) * 100}%, ${
(0.5 - origin.y) * 100
}%) scale(${scale * 0.8})`};
}%) scale(${Math.max(scale * 0.8, 1)})`};
transform-origin: ${({ origin }) => `${origin.x * 100}% ${origin.y * 100}%`};
object-fit: cover;
`
const FaceImageLandscape = styled(ProtectedImage)`
position: absolute;
height: 100%;
left: 50%;
transform: translateX(-50%)
${({ origin, scale }) =>
`translate(${(0.5 - origin.x) * 100}%, ${
(0.5 - origin.y) * 100
}%) scale(${Math.max(scale * 0.8, 1)})`};
transform-origin: ${({ origin }) => `${origin.x * 100}% ${origin.y * 100}%`};
object-fit: cover;
@@ -76,6 +90,11 @@ const FaceGroup = ({ group }) => {
y: (rect.minY + rect.maxY) / 2,
}
const FaceImage =
previewFace.media.thumbnail.width > previewFace.media.thumbnail.height
? FaceImageLandscape
: FaceImagePortrait
return (
<Link to={`/people/${group.id}`}>
<CircleImageWrapper>