diff --git a/api/gqlgen.yml b/api/gqlgen.yml index dfbf32d2..9575ccb7 100644 --- a/api/gqlgen.yml +++ b/api/gqlgen.yml @@ -26,3 +26,5 @@ models: model: github.com/viktorstrate/photoview/api/graphql/models.Photo PhotoURL: model: github.com/viktorstrate/photoview/api/graphql/models.PhotoURL + Album: + model: github.com/viktorstrate/photoview/api/graphql/models.Album diff --git a/api/graphql/generated.go b/api/graphql/generated.go index 10d274bb..f29a1334 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -37,6 +37,7 @@ type Config struct { } type ResolverRoot interface { + Album() AlbumResolver Mutation() MutationResolver Photo() PhotoResolver Query() QueryResolver @@ -124,6 +125,12 @@ type ComplexityRoot struct { } } +type AlbumResolver interface { + Photos(ctx context.Context, obj *models.Album) ([]*models.Photo, error) + SubAlbums(ctx context.Context, obj *models.Album) ([]*models.Album, error) + ParentAlbum(ctx context.Context, obj *models.Album) (*models.Album, error) + Owner(ctx context.Context, obj *models.Album) (*models.User, error) +} type MutationResolver interface { AuthorizeUser(ctx context.Context, username string, password string) (*models.AuthorizeResult, error) RegisterUser(ctx context.Context, username string, password string, rootPath string) (*models.AuthorizeResult, error) @@ -874,13 +881,13 @@ func (ec *executionContext) _Album_id(ctx context.Context, field graphql.Collect Object: "Album", Field: field, Args: nil, - IsMethod: false, + IsMethod: true, } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.ID, nil + return obj.ID(), nil }) if err != nil { ec.Error(ctx, err) @@ -926,10 +933,10 @@ func (ec *executionContext) _Album_title(ctx context.Context, field graphql.Coll if resTmp == nil { return graphql.Null } - res := resTmp.(*string) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalOString2áš–string(ctx, field.Selections, res) + return ec.marshalOString2string(ctx, field.Selections, res) } func (ec *executionContext) _Album_photos(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) { @@ -945,13 +952,13 @@ func (ec *executionContext) _Album_photos(ctx context.Context, field graphql.Col Object: "Album", Field: field, Args: nil, - IsMethod: false, + IsMethod: true, } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Photos, nil + return ec.resolvers.Album().Photos(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -979,13 +986,13 @@ func (ec *executionContext) _Album_subAlbums(ctx context.Context, field graphql. Object: "Album", Field: field, Args: nil, - IsMethod: false, + IsMethod: true, } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.SubAlbums, nil + return ec.resolvers.Album().SubAlbums(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -1013,13 +1020,13 @@ func (ec *executionContext) _Album_parentAlbum(ctx context.Context, field graphq Object: "Album", Field: field, Args: nil, - IsMethod: false, + IsMethod: true, } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.ParentAlbum, nil + return ec.resolvers.Album().ParentAlbum(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -1047,13 +1054,13 @@ func (ec *executionContext) _Album_owner(ctx context.Context, field graphql.Coll Object: "Album", Field: field, Args: nil, - IsMethod: false, + IsMethod: true, } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Owner, nil + return ec.resolvers.Album().Owner(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -1099,10 +1106,10 @@ func (ec *executionContext) _Album_path(ctx context.Context, field graphql.Colle if resTmp == nil { return graphql.Null } - res := resTmp.(*string) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalOString2áš–string(ctx, field.Selections, res) + return ec.marshalOString2string(ctx, field.Selections, res) } func (ec *executionContext) _AuthorizeResult_success(ctx context.Context, field graphql.CollectedField, obj *models.AuthorizeResult) (ret graphql.Marshaler) { @@ -3901,21 +3908,57 @@ func (ec *executionContext) _Album(ctx context.Context, sel ast.SelectionSet, ob case "id": out.Values[i] = ec._Album_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "title": out.Values[i] = ec._Album_title(ctx, field, obj) case "photos": - out.Values[i] = ec._Album_photos(ctx, field, obj) + 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._Album_photos(ctx, field, obj) + return res + }) case "subAlbums": - out.Values[i] = ec._Album_subAlbums(ctx, field, obj) + 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._Album_subAlbums(ctx, field, obj) + return res + }) case "parentAlbum": - out.Values[i] = ec._Album_parentAlbum(ctx, field, obj) + 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._Album_parentAlbum(ctx, field, obj) + return res + }) case "owner": - out.Values[i] = ec._Album_owner(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._Album_owner(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "path": out.Values[i] = ec._Album_path(ctx, field, obj) default: diff --git a/api/graphql/models/album.go b/api/graphql/models/album.go new file mode 100644 index 00000000..293625b2 --- /dev/null +++ b/api/graphql/models/album.go @@ -0,0 +1,28 @@ +package models + +import ( + "database/sql" + "strconv" +) + +type Album struct { + AlbumID int + Title string + ParentAlbum *int + OwnerID int + Path string +} + +func (a *Album) ID() string { + return strconv.Itoa(a.AlbumID) +} + +func NewAlbumFromRow(row *sql.Row) (*Album, error) { + album := Album{} + + if err := row.Scan(&album.AlbumID, &album.Title, &album.ParentAlbum, &album.OwnerID, &album.Path); err != nil { + return nil, err + } + + return &album, nil +} diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go index 87f0b5a7..925ad413 100644 --- a/api/graphql/models/generated.go +++ b/api/graphql/models/generated.go @@ -6,16 +6,6 @@ import ( "time" ) -type Album struct { - ID string `json:"id"` - Title *string `json:"title"` - Photos []*Photo `json:"photos"` - SubAlbums []*Album `json:"subAlbums"` - ParentAlbum *Album `json:"parentAlbum"` - Owner *User `json:"owner"` - Path *string `json:"path"` -} - type AuthorizeResult struct { Success bool `json:"success"` Status string `json:"status"` diff --git a/api/graphql/models/photo.go b/api/graphql/models/photo.go index 8a9c0216..34a8f7d1 100644 --- a/api/graphql/models/photo.go +++ b/api/graphql/models/photo.go @@ -16,7 +16,7 @@ type Photo struct { } type PhotoURL struct { - UrlId int + UrlID int Token string Width int Height int @@ -36,6 +36,20 @@ func NewPhotoFromRow(row *sql.Row) (*Photo, error) { return &photo, nil } +func NewPhotosFromRows(rows *sql.Rows) ([]*Photo, error) { + photos := make([]*Photo, 0) + + for rows.Next() { + var photo Photo + if err := rows.Scan(&photo.PhotoID, &photo.Title, &photo.Path, &photo.OriginalUrl, &photo.ThumbnailUrl, &photo.AlbumId, &photo.ExifId); err != nil { + return nil, err + } + photos = append(photos, &photo) + } + + return photos, nil +} + func (p *PhotoURL) URL() string { return "URL:" + p.Token } diff --git a/api/graphql/resolvers/album.go b/api/graphql/resolvers/album.go new file mode 100644 index 00000000..a39ca97b --- /dev/null +++ b/api/graphql/resolvers/album.go @@ -0,0 +1,60 @@ +package resolvers + +import ( + "context" + + api "github.com/viktorstrate/photoview/api/graphql" + "github.com/viktorstrate/photoview/api/graphql/auth" + "github.com/viktorstrate/photoview/api/graphql/models" +) + +func (r *queryResolver) MyAlbums(ctx context.Context) ([]*models.Album, error) { + panic("Not implemented") +} +func (r *queryResolver) Album(ctx context.Context, id *string) (*models.Album, error) { + user := auth.UserFromContext(ctx) + if user == nil { + return nil, auth.ErrUnauthorized + } + + row := r.Database.QueryRow("SELECT * FROM album WHERE album_id = ? AND owner_id = ?", id, user.UserID) + album, err := models.NewAlbumFromRow(row) + if err != nil { + return nil, err + } + + return album, nil +} + +func (r *Resolver) Album() api.AlbumResolver { + return &albumResolver{r} +} + +type albumResolver struct{ *Resolver } + +func (r *albumResolver) Photos(ctx context.Context, obj *models.Album) ([]*models.Photo, error) { + photoRows, err := r.Database.Query("SELECT photo.* FROM album, photo WHERE album.album_id = ? AND photo.album_id = album.album_id", obj.AlbumID) + if err != nil { + return nil, err + } + defer photoRows.Close() + + photos, err := models.NewPhotosFromRows(photoRows) + if err != nil { + return nil, err + } + + return photos, nil +} + +func (r *albumResolver) SubAlbums(ctx context.Context, obj *models.Album) ([]*models.Album, error) { + panic("not implemented") +} + +func (r *albumResolver) ParentAlbum(ctx context.Context, obj *models.Album) (*models.Album, error) { + panic("not implemented") +} + +func (r *albumResolver) Owner(ctx context.Context, obj *models.Album) (*models.User, error) { + panic("not implemented") +} diff --git a/api/graphql/resolvers/photo.go b/api/graphql/resolvers/photo.go index 20387eab..6a657404 100644 --- a/api/graphql/resolvers/photo.go +++ b/api/graphql/resolvers/photo.go @@ -44,7 +44,7 @@ func (r *photoResolver) Original(ctx context.Context, obj *models.Photo) (*model row := r.Database.QueryRow("SELECT photo_url.* FROM photo, photo_url WHERE photo.photo_id = ? AND photo.original_url = photo_url.url_id", obj.PhotoID) var photoUrl models.PhotoURL - if err := row.Scan(&photoUrl.UrlId, &photoUrl.Token, &photoUrl.Width, &photoUrl.Height); err != nil { + if err := row.Scan(&photoUrl.UrlID, &photoUrl.Token, &photoUrl.Width, &photoUrl.Height); err != nil { return nil, err } @@ -55,7 +55,7 @@ func (r *photoResolver) Thumbnail(ctx context.Context, obj *models.Photo) (*mode row := r.Database.QueryRow("SELECT photo_url.* FROM photo, photo_url WHERE photo.photo_id = ? AND photo.thumbnail_url = photo_url.url_id", obj.PhotoID) var photoUrl models.PhotoURL - if err := row.Scan(&photoUrl.UrlId, &photoUrl.Token, &photoUrl.Width, &photoUrl.Height); err != nil { + if err := row.Scan(&photoUrl.UrlID, &photoUrl.Token, &photoUrl.Width, &photoUrl.Height); err != nil { return nil, err } diff --git a/api/graphql/resolvers/root.go b/api/graphql/resolvers/root.go index 5c0084d9..5cab073d 100644 --- a/api/graphql/resolvers/root.go +++ b/api/graphql/resolvers/root.go @@ -1,11 +1,9 @@ package resolvers import ( - "context" "database/sql" api "github.com/viktorstrate/photoview/api/graphql" - "github.com/viktorstrate/photoview/api/graphql/models" ) //go:generate go run github.com/99designs/gqlgen @@ -25,10 +23,3 @@ func (r *Resolver) Query() api.QueryResolver { type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } - -func (r *queryResolver) MyAlbums(ctx context.Context) ([]*models.Album, error) { - panic("Not implemented") -} -func (r *queryResolver) Album(ctx context.Context, id *string) (*models.Album, error) { - panic("Not implemented") -}