1
Fork 0

Start on photo favorite

This commit is contained in:
viktorstrate 2020-06-17 18:00:58 +02:00
parent 66b54a97f4
commit b289b25e34
8 changed files with 210 additions and 30 deletions

View File

@ -0,0 +1,2 @@
-- Add favorite attribute to photos
ALTER TABLE photo DROP favorite

View File

@ -0,0 +1,2 @@
-- Add favorite attribute to photos
ALTER TABLE photo ADD favorite BOOL DEFAULT false

View File

@ -75,6 +75,7 @@ type ComplexityRoot struct {
CreateUser func(childComplexity int, username string, rootPath string, password *string, admin bool) int
DeleteShareToken func(childComplexity int, token string) int
DeleteUser func(childComplexity int, id int) int
FavoritePhoto func(childComplexity int, photoID int, favorite bool) int
InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int
ProtectShareToken func(childComplexity int, token string, password *string) int
RegisterUser func(childComplexity int, username string, password string, rootPath string) int
@ -100,6 +101,7 @@ type ComplexityRoot struct {
Album func(childComplexity int) int
Downloads func(childComplexity int) int
Exif func(childComplexity int) int
Favorite func(childComplexity int) int
HighRes func(childComplexity int) int
ID func(childComplexity int) int
Path func(childComplexity int) int
@ -208,6 +210,7 @@ type MutationResolver interface {
SharePhoto(ctx context.Context, photoID int, expire *time.Time, password *string) (*models.ShareToken, error)
DeleteShareToken(ctx context.Context, token string) (*models.ShareToken, error)
ProtectShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error)
FavoritePhoto(ctx context.Context, photoID int, favorite bool) (*models.Photo, error)
UpdateUser(ctx context.Context, id int, username *string, rootPath *string, password *string, admin *bool) (*models.User, error)
CreateUser(ctx context.Context, username string, rootPath string, password *string, admin bool) (*models.User, error)
DeleteUser(ctx context.Context, id int) (*models.User, error)
@ -217,6 +220,7 @@ type PhotoResolver interface {
HighRes(ctx context.Context, obj *models.Photo) (*models.PhotoURL, error)
Album(ctx context.Context, obj *models.Photo) (*models.Album, error)
Exif(ctx context.Context, obj *models.Photo) (*models.PhotoEXIF, error)
Shares(ctx context.Context, obj *models.Photo) ([]*models.ShareToken, error)
Downloads(ctx context.Context, obj *models.Photo) ([]*models.PhotoDownload, error)
}
@ -407,6 +411,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.DeleteUser(childComplexity, args["id"].(int)), true
case "Mutation.favoritePhoto":
if e.complexity.Mutation.FavoritePhoto == nil {
break
}
args, err := ec.field_Mutation_favoritePhoto_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.FavoritePhoto(childComplexity, args["photoId"].(int), args["favorite"].(bool)), true
case "Mutation.initialSetupWizard":
if e.complexity.Mutation.InitialSetupWizard == nil {
break
@ -575,6 +591,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Photo.Exif(childComplexity), true
case "Photo.favorite":
if e.complexity.Photo.Favorite == nil {
break
}
return e.complexity.Photo.Favorite(childComplexity), true
case "Photo.highRes":
if e.complexity.Photo.HighRes == nil {
break
@ -1158,6 +1181,9 @@ type Mutation {
"Set a password for a token, if null is passed for the password argument, the password will be cleared"
protectShareToken(token: String!, password: String): ShareToken
"Mark or unmark a photo as being a favorite"
favoritePhoto(photoId: Int!, favorite: Boolean!): Photo
updateUser(
id: Int!
username: String
@ -1290,6 +1316,7 @@ type Photo {
"The album that holds the photo"
album: Album!
exif: PhotoEXIF
favorite: Boolean!
shares: [ShareToken!]!
downloads: [PhotoDownload!]!
@ -1449,6 +1476,28 @@ func (ec *executionContext) field_Mutation_deleteUser_args(ctx context.Context,
return args, nil
}
func (ec *executionContext) field_Mutation_favoritePhoto_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["photoId"]; ok {
arg0, err = ec.unmarshalNInt2int(ctx, tmp)
if err != nil {
return nil, err
}
}
args["photoId"] = arg0
var arg1 bool
if tmp, ok := rawArgs["favorite"]; ok {
arg1, err = ec.unmarshalNBoolean2bool(ctx, tmp)
if err != nil {
return nil, err
}
}
args["favorite"] = arg1
return args, nil
}
func (ec *executionContext) field_Mutation_initialSetupWizard_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -2672,6 +2721,44 @@ func (ec *executionContext) _Mutation_protectShareToken(ctx context.Context, fie
return ec.marshalOShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_favoritePhoto(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,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_favoritePhoto_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().FavoritePhoto(rctx, args["photoId"].(int), args["favorite"].(bool))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*models.Photo)
fc.Result = res
return ec.marshalOPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_updateUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -3347,6 +3434,40 @@ func (ec *executionContext) _Photo_exif(ctx context.Context, field graphql.Colle
return ec.marshalOPhotoEXIF2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoEXIF(ctx, field.Selections, res)
}
func (ec *executionContext) _Photo_favorite(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Photo",
Field: field,
Args: nil,
IsMethod: false,
}
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 obj.Favorite, nil
})
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.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Photo_shares(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -6512,6 +6633,8 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
out.Values[i] = ec._Mutation_deleteShareToken(ctx, field)
case "protectShareToken":
out.Values[i] = ec._Mutation_protectShareToken(ctx, field)
case "favoritePhoto":
out.Values[i] = ec._Mutation_favoritePhoto(ctx, field)
case "updateUser":
out.Values[i] = ec._Mutation_updateUser(ctx, field)
case "createUser":
@ -6664,6 +6787,11 @@ func (ec *executionContext) _Photo(ctx context.Context, sel ast.SelectionSet, ob
res = ec._Photo_exif(ctx, field, obj)
return res
})
case "favorite":
out.Values[i] = ec._Photo_favorite(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
case "shares":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {

View File

@ -8,11 +8,12 @@ import (
)
type Photo struct {
PhotoID int
Title string
Path string
AlbumId int
ExifId *int
PhotoID int
Title string
Path string
AlbumId int
ExifId *int
Favorite bool
}
func (p *Photo) ID() int {
@ -40,7 +41,7 @@ type PhotoURL struct {
func NewPhotoFromRow(row *sql.Row) (*Photo, error) {
photo := Photo{}
if err := row.Scan(&photo.PhotoID, &photo.Title, &photo.Path, &photo.AlbumId, &photo.ExifId); err != nil {
if err := row.Scan(&photo.PhotoID, &photo.Title, &photo.Path, &photo.AlbumId, &photo.ExifId, &photo.Favorite); err != nil {
return nil, err
}
@ -52,7 +53,7 @@ func NewPhotosFromRows(rows *sql.Rows) ([]*Photo, error) {
for rows.Next() {
var photo Photo
if err := rows.Scan(&photo.PhotoID, &photo.Title, &photo.Path, &photo.AlbumId, &photo.ExifId); err != nil {
if err := rows.Scan(&photo.PhotoID, &photo.Title, &photo.Path, &photo.AlbumId, &photo.ExifId, &photo.Favorite); err != nil {
return nil, err
}
photos = append(photos, &photo)

View File

@ -171,3 +171,19 @@ func (r *photoResolver) Exif(ctx context.Context, obj *models.Photo) (*models.Ph
return exif, nil
}
func (r *mutationResolver) FavoritePhoto(ctx context.Context, photoID int, favourite bool) (*models.Photo, error) {
user := auth.UserFromContext(ctx)
row := r.Database.QueryRow("SELECT photo.* FROM photo JOIN album ON photo.album_id = album.album_id WHERE photo.photo_id = ? AND album.owner_id = ?", photoID, user.UserID)
photo, err := models.NewPhotoFromRow(row)
if err != nil {
return nil, err
}
// TODO: Update photo row
return photo, nil
}

View File

@ -75,6 +75,9 @@ type Mutation {
"Set a password for a token, if null is passed for the password argument, the password will be cleared"
protectShareToken(token: String!, password: String): ShareToken
"Mark or unmark a photo as being a favorite"
favoritePhoto(photoId: Int!, favorite: Boolean!): Photo
updateUser(
id: Int!
username: String
@ -207,6 +210,7 @@ type Photo {
"The album that holds the photo"
album: Album!
exif: PhotoEXIF
favorite: Boolean!
shares: [ShareToken!]!
downloads: [PhotoDownload!]!

View File

@ -23,6 +23,7 @@ const photoQuery = gql`
highRes {
url
}
favorite
}
}
}

View File

@ -1,10 +1,21 @@
import React, { useState } from 'react'
import gql from 'graphql-tag'
import { useMutation } from 'react-apollo'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import LazyLoad from 'react-lazyload'
import { Icon } from 'semantic-ui-react'
import ProtectedImage from './ProtectedImage'
const markFavoriteMutation = gql`
mutation markPhotoFavorite($photoId: Int!, $favorite: Boolean!) {
favoritePhoto(photoId: $photoId, favorite: $favorite) {
id
favorite
}
}
`
const PhotoContainer = styled.div`
flex-grow: 1;
flex-basis: 0;
@ -107,29 +118,44 @@ export const Photo = ({
index,
active,
setPresenting,
}) => (
<PhotoContainer
key={photo.id}
style={{
cursor: onSelectImage ? 'pointer' : null,
minWidth: `min(${minWidth}px, 100% - 8px)`,
}}
onClick={() => {
onSelectImage && onSelectImage(index)
}}
>
<LazyPhoto src={photo.thumbnail && photo.thumbnail.url} />
<PhotoOverlay active={active}>
<HoverIcon
name="expand"
onClick={() => {
setPresenting(true)
}}
/>
<HoverIcon name="heart outline" />
</PhotoOverlay>
</PhotoContainer>
)
}) => {
const [markFavorite] = useMutation(markFavoriteMutation)
return (
<PhotoContainer
key={photo.id}
style={{
cursor: onSelectImage ? 'pointer' : null,
minWidth: `min(${minWidth}px, 100% - 8px)`,
}}
onClick={() => {
onSelectImage && onSelectImage(index)
}}
>
<LazyPhoto src={photo.thumbnail && photo.thumbnail.url} />
<PhotoOverlay active={active}>
<HoverIcon
name="expand"
onClick={() => {
setPresenting(true)
}}
/>
<HoverIcon
name={photo.favorite ? 'heart' : 'heart outline'}
onClick={event => {
event.stopPropagation()
markFavorite({
variables: {
photoId: photo.id,
favorite: !photo.favorite,
},
})
}}
/>
</PhotoOverlay>
</PhotoContainer>
)
}
Photo.propTypes = {
photo: PropTypes.object.isRequired,