From 4ee8040364aa70b99758306e79098604541d1500 Mon Sep 17 00:00:00 2001 From: Peter - Ubuntu dual boot Date: Sat, 18 Sep 2021 11:58:21 +0100 Subject: [PATCH] Working permissions (logic reused from other components), album and photo sidebars now fully functional. Will require some testing, and need to adjust for people pages. --- api/graphql/generated.go | 141 ++++++++++++++---- api/graphql/models/album.go | 2 +- api/graphql/resolvers/album.go | 54 +++++-- api/graphql/schema.graphql | 6 +- ui/src/components/sidebar/AlbumCovers.tsx | 51 ++++--- ui/src/components/sidebar/AlbumSidebar.tsx | 5 - ui/src/components/sidebar/MediaSidebar.tsx | 4 - ui/src/components/sidebar/Sharing.tsx | 87 ++++++----- .../sidebar/__generated__/resetAlbumCover.ts | 24 +++ .../sidebar/__generated__/setAlbumCover.ts | 24 +++ 10 files changed, 287 insertions(+), 111 deletions(-) create mode 100644 ui/src/components/sidebar/__generated__/resetAlbumCover.ts create mode 100644 ui/src/components/sidebar/__generated__/setAlbumCover.ts diff --git a/api/graphql/generated.go b/api/graphql/generated.go index 92bdaa6d..36d4d2a9 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -154,9 +154,10 @@ type ComplexityRoot struct { MoveImageFaces func(childComplexity int, imageFaceIDs []int, destinationFaceGroupID int) int ProtectShareToken func(childComplexity int, token string, password *string) int RecognizeUnlabeledFaces func(childComplexity int) int + ResetAlbumCover func(childComplexity int, albumID int) int ScanAll func(childComplexity int) int ScanUser func(childComplexity int, userID int) int - SetAlbumCoverID func(childComplexity int, albumID int, coverID *int) int + SetAlbumCover func(childComplexity int, coverID 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 @@ -314,7 +315,8 @@ type MutationResolver interface { SetPeriodicScanInterval(ctx context.Context, interval int) (int, error) SetScannerConcurrentWorkers(ctx context.Context, workers int) (int, error) ChangeUserPreferences(ctx context.Context, language *string) (*models.UserPreferences, error) - SetAlbumCoverID(ctx context.Context, albumID int, coverID *int) (*models.Album, error) + ResetAlbumCover(ctx context.Context, albumID int) (*models.Album, error) + SetAlbumCover(ctx context.Context, coverID int) (*models.Album, error) SetFaceGroupLabel(ctx context.Context, faceGroupID int, label *string) (*models.FaceGroup, error) CombineFaceGroups(ctx context.Context, destinationFaceGroupID int, sourceFaceGroupID int) (*models.FaceGroup, error) MoveImageFaces(ctx context.Context, imageFaceIDs []int, destinationFaceGroupID int) (*models.FaceGroup, error) @@ -929,6 +931,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.RecognizeUnlabeledFaces(childComplexity), true + case "Mutation.resetAlbumCover": + if e.complexity.Mutation.ResetAlbumCover == nil { + break + } + + args, err := ec.field_Mutation_resetAlbumCover_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.ResetAlbumCover(childComplexity, args["albumID"].(int)), true + case "Mutation.scanAll": if e.complexity.Mutation.ScanAll == nil { break @@ -948,17 +962,17 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.ScanUser(childComplexity, args["userId"].(int)), true - case "Mutation.setAlbumCoverID": - if e.complexity.Mutation.SetAlbumCoverID == nil { + case "Mutation.setAlbumCover": + if e.complexity.Mutation.SetAlbumCover == nil { break } - args, err := ec.field_Mutation_setAlbumCoverID_args(context.TODO(), rawArgs) + args, err := ec.field_Mutation_setAlbumCover_args(context.TODO(), rawArgs) if err != nil { return 0, false } - return e.complexity.Mutation.SetAlbumCoverID(childComplexity, args["albumID"].(int), args["coverID"].(*int)), true + return e.complexity.Mutation.SetAlbumCover(childComplexity, args["coverID"].(int)), true case "Mutation.setFaceGroupLabel": if e.complexity.Mutation.SetFaceGroupLabel == nil { @@ -1788,8 +1802,10 @@ type Mutation { changeUserPreferences(language: String): UserPreferences! @isAuthorized - "Assign a cover image to an album, set coverID to -1 to remove the current one" - setAlbumCoverID(albumID: ID!, coverID: Int): Album! @isAuthorized + "Reset the assigned cover photo for an album" + resetAlbumCover(albumID: ID!): Album! @isAuthorized + "Assign a cover photo to an album" + setAlbumCover(coverID: ID!): Album! @isAuthorized "Assign a label to a face group, set label to null to remove the current one" setFaceGroupLabel(faceGroupID: ID!, label: String): FaceGroup! @isAuthorized @@ -2376,6 +2392,21 @@ func (ec *executionContext) field_Mutation_protectShareToken_args(ctx context.Co return args, nil } +func (ec *executionContext) field_Mutation_resetAlbumCover_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["albumID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("albumID")) + arg0, err = ec.unmarshalNID2int(ctx, tmp) + if err != nil { + return nil, err + } + } + args["albumID"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_scanUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2391,27 +2422,18 @@ func (ec *executionContext) field_Mutation_scanUser_args(ctx context.Context, ra return args, nil } -func (ec *executionContext) field_Mutation_setAlbumCoverID_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { +func (ec *executionContext) field_Mutation_setAlbumCover_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["albumID"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("albumID")) + if tmp, ok := rawArgs["coverID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("coverID")) arg0, err = ec.unmarshalNID2int(ctx, tmp) if err != nil { return nil, err } } - args["albumID"] = arg0 - var arg1 *int - if tmp, ok := rawArgs["coverID"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("coverID")) - arg1, err = ec.unmarshalOInt2ᚖint(ctx, tmp) - if err != nil { - return nil, err - } - } - args["coverID"] = arg1 + args["coverID"] = arg0 return args, nil } @@ -5949,7 +5971,7 @@ func (ec *executionContext) _Mutation_changeUserPreferences(ctx context.Context, return ec.marshalNUserPreferences2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUserPreferences(ctx, field.Selections, res) } -func (ec *executionContext) _Mutation_setAlbumCoverID(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Mutation_resetAlbumCover(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -5966,7 +5988,7 @@ func (ec *executionContext) _Mutation_setAlbumCoverID(ctx context.Context, field ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Mutation_setAlbumCoverID_args(ctx, rawArgs) + args, err := ec.field_Mutation_resetAlbumCover_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -5975,7 +5997,69 @@ func (ec *executionContext) _Mutation_setAlbumCoverID(ctx context.Context, field resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().SetAlbumCoverID(rctx, args["albumID"].(int), args["coverID"].(*int)) + return ec.resolvers.Mutation().ResetAlbumCover(rctx, args["albumID"].(int)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.IsAuthorized == nil { + return nil, errors.New("directive isAuthorized is not implemented") + } + return ec.directives.IsAuthorized(ctx, nil, directive0) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*models.Album); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/photoview/photoview/api/graphql/models.Album`, tmp) + }) + 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.Album) + fc.Result = res + return ec.marshalNAlbum2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation_setAlbumCover(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_setAlbumCover_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) { + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().SetAlbumCover(rctx, args["coverID"].(int)) } directive1 := func(ctx context.Context) (interface{}, error) { if ec.directives.IsAuthorized == nil { @@ -10855,8 +10939,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } - case "setAlbumCoverID": - out.Values[i] = ec._Mutation_setAlbumCoverID(ctx, field) + case "resetAlbumCover": + out.Values[i] = ec._Mutation_resetAlbumCover(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } + case "setAlbumCover": + out.Values[i] = ec._Mutation_setAlbumCover(ctx, field) if out.Values[i] == graphql.Null { invalids++ } diff --git a/api/graphql/models/album.go b/api/graphql/models/album.go index 15887bda..93b81875 100644 --- a/api/graphql/models/album.go +++ b/api/graphql/models/album.go @@ -17,7 +17,7 @@ type Album struct { Owners []User `gorm:"many2many:user_albums;constraint:OnDelete:CASCADE;"` Path string `gorm:"not null"` PathHash string `gorm:"unique"` - CoverID int `gorm:"index;default:-1;"` + CoverID int `gorm:"index;"` } func (a *Album) FilePath() string { diff --git a/api/graphql/resolvers/album.go b/api/graphql/resolvers/album.go index 8a067cbc..c97e5019 100644 --- a/api/graphql/resolvers/album.go +++ b/api/graphql/resolvers/album.go @@ -152,7 +152,7 @@ func (r *albumResolver) Thumbnail(ctx context.Context, obj *models.Album) (*mode fmt.Print(obj.CoverID) - if obj.CoverID == -1 { + if obj.CoverID == 0 { if err := r.Database.Raw(` WITH recursive sub_albums AS ( SELECT * FROM albums AS root WHERE id = ? @@ -265,7 +265,8 @@ func (r *albumResolver) Path(ctx context.Context, obj *models.Album) ([]*models. return album_path, nil } -func (r *mutationResolver) SetAlbumCoverID(ctx context.Context, albumID int, coverID *int) (*models.Album, error) { +// Takes album_id, resets album.cover_id to 0 (null) +func (r *mutationResolver) ResetAlbumCover(ctx context.Context, albumID int) (*models.Album, error) { user := auth.UserFromContext(ctx) if user == nil { return nil, errors.New("unauthorized") @@ -276,9 +277,41 @@ func (r *mutationResolver) SetAlbumCoverID(ctx context.Context, albumID int, cov return nil, err } - // - // var album models.Album - // + ownsAlbum, err := user.OwnsAlbum(r.Database, &album) + if err != nil { + return nil, err + } + + if !ownsAlbum { + return nil, errors.New("forbidden") + } + + if err := r.Database.Model(&album).Update("cover_id", 0).Error; err != nil { + return nil, err + } + + return &album, nil +} + +// Takes media.id, finds parent album, sets album.cover_id to media.id (must be a more efficient way of doing this, but it works) +func (r *mutationResolver) SetAlbumCover(ctx context.Context, coverID int) (*models.Album, error) { + user := auth.UserFromContext(ctx) + if user == nil { + return nil, errors.New("unauthorized") + } + + var media models.Media + + if err := r.Database.Find(&media, coverID).Error; err != nil { + return nil, err + } + + var album models.Album + + if err := r.Database.Find(&album, &media.AlbumID).Error; err != nil { + return nil, err + } + ownsAlbum, err := user.OwnsAlbum(r.Database, &album) if err != nil { return nil, err @@ -292,16 +325,5 @@ func (r *mutationResolver) SetAlbumCoverID(ctx context.Context, albumID int, cov return nil, err } - // var faceGroup models.FaceGroup - // if err := db.Where("id = ?", faceGroupID).Find(&faceGroup).Error; err != nil { - // return nil, err - // } - // - // return &faceGroup, nil - // - // if err := r.Database.Model(faceGroup).Update("label", label).Error; err != nil { - // return nil, err - // } - return &album, nil } diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index 998b313d..8a6d372f 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -134,8 +134,10 @@ type Mutation { changeUserPreferences(language: String): UserPreferences! @isAuthorized - "Assign a cover image to an album, set coverID to -1 to remove the current one" - setAlbumCoverID(albumID: ID!, coverID: Int): Album! @isAuthorized + "Reset the assigned cover photo for an album" + resetAlbumCover(albumID: ID!): Album! @isAuthorized + "Assign a cover photo to an album" + setAlbumCover(coverID: ID!): Album! @isAuthorized "Assign a label to a face group, set label to null to remove the current one" setFaceGroupLabel(faceGroupID: ID!, label: String): FaceGroup! @isAuthorized diff --git a/ui/src/components/sidebar/AlbumCovers.tsx b/ui/src/components/sidebar/AlbumCovers.tsx index 06ecc8e5..51c99494 100644 --- a/ui/src/components/sidebar/AlbumCovers.tsx +++ b/ui/src/components/sidebar/AlbumCovers.tsx @@ -5,33 +5,41 @@ import { useTranslation } from 'react-i18next' import { SidebarSection, SidebarSectionTitle } from './SidebarComponents' import { - setAlbumCoverID, - setAlbumCoverIDVariables, -} from './__generated__/setAlbumCoverID' + setAlbumCover, + setAlbumCoverVariables, +} from './__generated__/setAlbumCover' +import { + resetAlbumCover, + resetAlbumCoverVariables, +} from './__generated__/resetAlbumCover' -const SET_ALBUM_COVER_ID_MUTATION = gql` - mutation setAlbumCoverID($albumID: ID!, $coverID: Int!) { - setAlbumCoverID(albumID: $albumID, coverID: $coverID) { +const RESET_ALBUM_COVER_MUTATION = gql` + mutation resetAlbumCover($albumID: ID!) { + resetAlbumCover(albumID: $albumID) { id + } + } +` +const SET_ALBUM_COVER_MUTATION = gql` + mutation setAlbumCover($coverID: ID!) { + setAlbumCover(coverID: $coverID) { coverID } } ` type SidebarPhotoCoverProps = { - id: string cover_id: string } -export const SidebarPhotoCover = ({ id, cover_id }: SidebarPhotoCoverProps) => { +export const SidebarPhotoCover = ({ cover_id }: SidebarPhotoCoverProps) => { const { t } = useTranslation() - const [setAlbumCoverID, { loading }] = useMutation< - setAlbumCoverID, - setAlbumCoverIDVariables - >(SET_ALBUM_COVER_ID_MUTATION, { + const [setAlbumCover, { loading }] = useMutation< + setAlbumCover, + setAlbumCoverVariables + >(SET_ALBUM_COVER_MUTATION, { variables: { - albumID: id, coverID: cover_id, }, }) @@ -39,7 +47,7 @@ export const SidebarPhotoCover = ({ id, cover_id }: SidebarPhotoCoverProps) => { return ( - {t('sidebar.album.cover_photo', 'Cover Photo')} + {t('sidebar.album.cover_photo', 'Album cover')}
@@ -50,9 +58,8 @@ export const SidebarPhotoCover = ({ id, cover_id }: SidebarPhotoCoverProps) => { className="text-green-500 font-bold uppercase text-xs" disabled={loading} onClick={() => { - setAlbumCoverID({ + setAlbumCover({ variables: { - albumID: id, coverID: cover_id, }, }) @@ -78,13 +85,12 @@ type SidebarAlbumCoverProps = { export const SidebarAlbumCover = ({ id }: SidebarAlbumCoverProps) => { const { t } = useTranslation() - const [setAlbumCoverID, { loading }] = useMutation< - setAlbumCoverID, - setAlbumCoverIDVariables - >(SET_ALBUM_COVER_ID_MUTATION, { + const [resetAlbumCover, { loading }] = useMutation< + resetAlbumCover, + resetAlbumCoverVariables + >(RESET_ALBUM_COVER_MUTATION, { variables: { albumID: id, - coverID: '-1', }, }) @@ -102,10 +108,9 @@ export const SidebarAlbumCover = ({ id }: SidebarAlbumCoverProps) => { className="text-red-500 font-bold uppercase text-xs" disabled={loading} onClick={() => { - setAlbumCoverID({ + resetAlbumCover({ variables: { albumID: id, - coverID: '-1', }, }) }} diff --git a/ui/src/components/sidebar/AlbumSidebar.tsx b/ui/src/components/sidebar/AlbumSidebar.tsx index 09a91832..47ea5c50 100644 --- a/ui/src/components/sidebar/AlbumSidebar.tsx +++ b/ui/src/components/sidebar/AlbumSidebar.tsx @@ -1,7 +1,6 @@ import React from 'react' import { useQuery, gql } from '@apollo/client' import { SidebarAlbumShare } from './Sharing' -import { SidebarAlbumCover } from './AlbumCovers' import { useTranslation } from 'react-i18next' import SidebarHeader from './SidebarHeader' import { @@ -47,10 +46,6 @@ const AlbumSidebar = ({ albumId }: AlbumSidebarProps) => { {/*

{data.album.title}

*/} -
- {/*

{data.album.title}

*/} - -
) } diff --git a/ui/src/components/sidebar/MediaSidebar.tsx b/ui/src/components/sidebar/MediaSidebar.tsx index 4c8e04da..051f0979 100644 --- a/ui/src/components/sidebar/MediaSidebar.tsx +++ b/ui/src/components/sidebar/MediaSidebar.tsx @@ -26,7 +26,6 @@ import { import { sidebarDownloadQuery_media_downloads } from './__generated__/sidebarDownloadQuery' import SidebarHeader from './SidebarHeader' -import { SidebarPhotoCover } from './AlbumCovers' const SIDEBAR_MEDIA_QUERY = gql` query sidebarPhoto($id: ID!) { @@ -366,9 +365,6 @@ const SidebarContent = ({ media, hidePreview }: SidebarContentProps) => { -
- -
) } diff --git a/ui/src/components/sidebar/Sharing.tsx b/ui/src/components/sidebar/Sharing.tsx index c75d9143..5caa6401 100644 --- a/ui/src/components/sidebar/Sharing.tsx +++ b/ui/src/components/sidebar/Sharing.tsx @@ -34,6 +34,8 @@ import { import { authToken } from '../../helpers/authentication' import { SidebarSection, SidebarSectionTitle } from './SidebarComponents' +import { SidebarPhotoCover, SidebarAlbumCover } from './AlbumCovers' + import { ReactComponent as LinkIcon } from './icons/shareLinkIcon.svg' import { ReactComponent as CopyIcon } from './icons/shareCopyIcon.svg' import { ReactComponent as DeleteIcon } from './icons/shareDeleteIcon.svg' @@ -312,10 +314,9 @@ export const SidebarPhotoShare = ({ id }: SidebarSharePhotoProps) => { const [ loadShares, { loading: queryLoading, error: sharesError, data: sharesData }, - ] = - useLazyQuery( - SHARE_PHOTO_QUERY - ) + ] = useLazyQuery( + SHARE_PHOTO_QUERY + ) const [sharePhoto, { loading: mutationLoading }] = useMutation< sidebarPhotoAddShare, @@ -432,36 +433,54 @@ const SidebarShare = ({ ) } - return ( - - - {t('sidebar.sharing.title', 'Sharing options')} - -
-
- {optionsRows} - - - - - -
- -
+ let coverPhotoSection + if (isPhoto) { + coverPhotoSection = ( +
+
- + ) + } else { + coverPhotoSection = ( +
+ +
+ ) + } + + return ( +
+ + + {t('sidebar.sharing.title', 'Sharing options')} + +
+ + {optionsRows} + + + + + +
+ +
+
+
+ {coverPhotoSection} +
) } diff --git a/ui/src/components/sidebar/__generated__/resetAlbumCover.ts b/ui/src/components/sidebar/__generated__/resetAlbumCover.ts new file mode 100644 index 00000000..c9410349 --- /dev/null +++ b/ui/src/components/sidebar/__generated__/resetAlbumCover.ts @@ -0,0 +1,24 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL mutation operation: resetAlbumCover +// ==================================================== + +export interface resetAlbumCover_resetAlbumCover { + __typename: 'Album' + id: string +} + +export interface resetAlbumCover { + /** + * Reset the assigned cover image for an album + */ + resetAlbumCover: resetAlbumCover_resetAlbumCover +} + +export interface resetAlbumCoverVariables { + albumID: string +} diff --git a/ui/src/components/sidebar/__generated__/setAlbumCover.ts b/ui/src/components/sidebar/__generated__/setAlbumCover.ts new file mode 100644 index 00000000..6df1e641 --- /dev/null +++ b/ui/src/components/sidebar/__generated__/setAlbumCover.ts @@ -0,0 +1,24 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL mutation operation: setAlbumCover +// ==================================================== + +export interface setAlbumCover_setAlbumCover { + __typename: 'Album' + coverID: number +} + +export interface setAlbumCover { + /** + * Assign a cover image to an album + */ + setAlbumCover: setAlbumCover_setAlbumCover +} + +export interface setAlbumCoverVariables { + coverID: string +}