From 43e214b5d00bde4ce7402f5c221f5d73613ce1fc Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Wed, 5 Feb 2020 14:51:46 +0100 Subject: [PATCH] Work on photo resolver --- api/gqlgen.yml | 6 +- api/graphql/generated.go | 103 +++++++++++++----- api/graphql/models/generated.go | 23 ---- api/graphql/models/photo.go | 41 +++++++ api/graphql/resolver.go | 55 ---------- api/graphql/resolvers/photo.go | 71 ++++++++++++ api/graphql/resolvers/root.go | 34 ++++++ .../scanner.go} | 2 +- .../{resolver_user.go => resolvers/user.go} | 18 ++- api/server/server.go | 9 +- 10 files changed, 247 insertions(+), 115 deletions(-) create mode 100644 api/graphql/models/photo.go delete mode 100644 api/graphql/resolver.go create mode 100644 api/graphql/resolvers/photo.go create mode 100644 api/graphql/resolvers/root.go rename api/graphql/{resolver_scanner.go => resolvers/scanner.go} (97%) rename api/graphql/{resolver_user.go => resolvers/user.go} (82%) diff --git a/api/gqlgen.yml b/api/gqlgen.yml index 391379ed..dfbf32d2 100644 --- a/api/gqlgen.yml +++ b/api/gqlgen.yml @@ -14,7 +14,7 @@ model: package: models resolver: - filename: graphql/resolver.go + filename: graphql/resolvers/root.go type: Resolver autobind: [] @@ -22,3 +22,7 @@ autobind: [] models: User: model: github.com/viktorstrate/photoview/api/graphql/models.User + Photo: + model: github.com/viktorstrate/photoview/api/graphql/models.Photo + PhotoURL: + model: github.com/viktorstrate/photoview/api/graphql/models.PhotoURL diff --git a/api/graphql/generated.go b/api/graphql/generated.go index 216fae0e..10d274bb 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -38,6 +38,7 @@ type Config struct { type ResolverRoot interface { Mutation() MutationResolver + Photo() PhotoResolver Query() QueryResolver } @@ -129,6 +130,12 @@ type MutationResolver interface { ScanAll(ctx context.Context) (*models.ScannerResult, error) ScanUser(ctx context.Context, userID string) (*models.ScannerResult, error) } +type PhotoResolver interface { + Original(ctx context.Context, obj *models.Photo) (*models.PhotoURL, error) + Thumbnail(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) +} type QueryResolver interface { Users(ctx context.Context) ([]*models.User, error) MyUser(ctx context.Context) (*models.User, error) @@ -1388,13 +1395,13 @@ func (ec *executionContext) _Photo_id(ctx context.Context, field graphql.Collect Object: "Photo", 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) @@ -1440,10 +1447,10 @@ func (ec *executionContext) _Photo_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) _Photo_path(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) { @@ -1474,10 +1481,10 @@ func (ec *executionContext) _Photo_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) _Photo_original(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) { @@ -1493,13 +1500,13 @@ func (ec *executionContext) _Photo_original(ctx context.Context, field graphql.C Object: "Photo", 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.Original, nil + return ec.resolvers.Photo().Original(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -1527,13 +1534,13 @@ func (ec *executionContext) _Photo_thumbnail(ctx context.Context, field graphql. Object: "Photo", 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.Thumbnail, nil + return ec.resolvers.Photo().Thumbnail(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -1561,13 +1568,13 @@ func (ec *executionContext) _Photo_album(ctx context.Context, field graphql.Coll Object: "Photo", 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.Album, nil + return ec.resolvers.Photo().Album(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -1598,13 +1605,13 @@ func (ec *executionContext) _Photo_exif(ctx context.Context, field graphql.Colle Object: "Photo", 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.Exif, nil + return ec.resolvers.Photo().Exif(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -2006,13 +2013,13 @@ func (ec *executionContext) _PhotoURL_url(ctx context.Context, field graphql.Col Object: "PhotoURL", 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.URL, nil + return obj.URL(), nil }) if err != nil { ec.Error(ctx, err) @@ -2021,10 +2028,10 @@ func (ec *executionContext) _PhotoURL_url(ctx context.Context, field graphql.Col 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) _PhotoURL_width(ctx context.Context, field graphql.CollectedField, obj *models.PhotoURL) (ret graphql.Marshaler) { @@ -2055,10 +2062,10 @@ func (ec *executionContext) _PhotoURL_width(ctx context.Context, field graphql.C if resTmp == nil { return graphql.Null } - res := resTmp.(*int) + res := resTmp.(int) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalOInt2ᚖint(ctx, field.Selections, res) + return ec.marshalOInt2int(ctx, field.Selections, res) } func (ec *executionContext) _PhotoURL_height(ctx context.Context, field graphql.CollectedField, obj *models.PhotoURL) (ret graphql.Marshaler) { @@ -2089,10 +2096,10 @@ func (ec *executionContext) _PhotoURL_height(ctx context.Context, field graphql. if resTmp == nil { return graphql.Null } - res := resTmp.(*int) + res := resTmp.(int) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalOInt2ᚖint(ctx, field.Selections, res) + return ec.marshalOInt2int(ctx, field.Selections, res) } func (ec *executionContext) _Query_users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -4016,23 +4023,59 @@ func (ec *executionContext) _Photo(ctx context.Context, sel ast.SelectionSet, ob case "id": out.Values[i] = ec._Photo_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "title": out.Values[i] = ec._Photo_title(ctx, field, obj) case "path": out.Values[i] = ec._Photo_path(ctx, field, obj) case "original": - out.Values[i] = ec._Photo_original(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._Photo_original(ctx, field, obj) + return res + }) case "thumbnail": - out.Values[i] = ec._Photo_thumbnail(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._Photo_thumbnail(ctx, field, obj) + return res + }) case "album": - out.Values[i] = ec._Photo_album(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._Photo_album(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "exif": - out.Values[i] = ec._Photo_exif(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._Photo_exif(ctx, field, obj) + return res + }) default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go index 8a4db555..87f0b5a7 100644 --- a/api/graphql/models/generated.go +++ b/api/graphql/models/generated.go @@ -22,20 +22,6 @@ type AuthorizeResult struct { Token *string `json:"token"` } -type Photo struct { - ID string `json:"id"` - Title *string `json:"title"` - // Local filepath for the photo - Path *string `json:"path"` - // URL to display the photo in full resolution - Original *PhotoURL `json:"original"` - // URL to display the photo in a smaller resolution - Thumbnail *PhotoURL `json:"thumbnail"` - // The album that holds the photo - Album *Album `json:"album"` - Exif *PhotoExif `json:"exif"` -} - // EXIF metadata from the camera type PhotoExif struct { Photo *Photo `json:"photo"` @@ -60,15 +46,6 @@ type PhotoExif struct { Flash *string `json:"flash"` } -type PhotoURL struct { - // URL for previewing the image - URL *string `json:"url"` - // Width of the image in pixels - Width *int `json:"width"` - // Height of the image in pixels - Height *int `json:"height"` -} - type ScannerResult struct { Finished bool `json:"finished"` Success bool `json:"success"` diff --git a/api/graphql/models/photo.go b/api/graphql/models/photo.go new file mode 100644 index 00000000..8a9c0216 --- /dev/null +++ b/api/graphql/models/photo.go @@ -0,0 +1,41 @@ +package models + +import ( + "database/sql" + "strconv" +) + +type Photo struct { + PhotoID int + Title string + Path string + OriginalUrl int + ThumbnailUrl int + AlbumId int + ExifId *int +} + +type PhotoURL struct { + UrlId int + Token string + Width int + Height int +} + +func (p *Photo) ID() string { + return strconv.Itoa(p.PhotoID) +} + +func NewPhotoFromRow(row *sql.Row) (*Photo, error) { + photo := Photo{} + + if err := row.Scan(&photo.PhotoID, &photo.Title, &photo.Path, &photo.OriginalUrl, &photo.ThumbnailUrl, &photo.AlbumId, &photo.ExifId); err != nil { + return nil, err + } + + return &photo, nil +} + +func (p *PhotoURL) URL() string { + return "URL:" + p.Token +} diff --git a/api/graphql/resolver.go b/api/graphql/resolver.go deleted file mode 100644 index 8dd49952..00000000 --- a/api/graphql/resolver.go +++ /dev/null @@ -1,55 +0,0 @@ -package api - -import ( - "context" - "database/sql" - - "github.com/viktorstrate/photoview/api/graphql/models" -) - -//go:generate go run github.com/99designs/gqlgen - -type Resolver struct { - Database *sql.DB -} - -func (r *Resolver) Mutation() MutationResolver { - return &mutationResolver{r} -} - -func (r *Resolver) Query() QueryResolver { - return &queryResolver{r} -} - -type mutationResolver struct{ *Resolver } - -type queryResolver struct{ *Resolver } - -func (r *queryResolver) Users(ctx context.Context) ([]*models.User, error) { - - rows, err := r.Database.Query("SELECT * FROM users") - if err != nil { - return nil, err - } - defer rows.Close() - - users, err := models.NewUsersFromRows(rows) - if err != nil { - return nil, err - } - - return users, nil -} - -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") -} -func (r *queryResolver) MyPhotos(ctx context.Context) ([]*models.Photo, error) { - panic("Not implemented") -} -func (r *queryResolver) Photo(ctx context.Context, id string) (*models.Photo, error) { - panic("Not implemented") -} diff --git a/api/graphql/resolvers/photo.go b/api/graphql/resolvers/photo.go new file mode 100644 index 00000000..20387eab --- /dev/null +++ b/api/graphql/resolvers/photo.go @@ -0,0 +1,71 @@ +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) MyPhotos(ctx context.Context) ([]*models.Photo, error) { + panic("Not implemented") +} + +func (r *queryResolver) Photo(ctx context.Context, id string) (*models.Photo, error) { + user := auth.UserFromContext(ctx) + if user == nil { + return nil, auth.ErrUnauthorized + } + + row := r.Database.QueryRow(` + SELECT photo.* FROM photo + LEFT JOIN album ON photo.album_id = album.album_id + WHERE photo.photo_id = ? AND album.owner_id = ? + `, id, user.UserID) + + photo, err := models.NewPhotoFromRow(row) + if err != nil { + return nil, err + } + + return photo, nil +} + +type photoResolver struct { + *Resolver +} + +func (r *Resolver) Photo() api.PhotoResolver { + return &photoResolver{r} +} + +func (r *photoResolver) Original(ctx context.Context, obj *models.Photo) (*models.PhotoURL, error) { + 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 { + return nil, err + } + + return &photoUrl, nil +} + +func (r *photoResolver) Thumbnail(ctx context.Context, obj *models.Photo) (*models.PhotoURL, error) { + 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 { + return nil, err + } + + return &photoUrl, nil +} + +func (r *photoResolver) Album(ctx context.Context, obj *models.Photo) (*models.Album, error) { + panic("not implemented") +} + +func (r *photoResolver) Exif(ctx context.Context, obj *models.Photo) (*models.PhotoExif, error) { + panic("not implemented") +} diff --git a/api/graphql/resolvers/root.go b/api/graphql/resolvers/root.go new file mode 100644 index 00000000..5c0084d9 --- /dev/null +++ b/api/graphql/resolvers/root.go @@ -0,0 +1,34 @@ +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 + +type Resolver struct { + Database *sql.DB +} + +func (r *Resolver) Mutation() api.MutationResolver { + return &mutationResolver{r} +} + +func (r *Resolver) Query() api.QueryResolver { + return &queryResolver{r} +} + +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") +} diff --git a/api/graphql/resolver_scanner.go b/api/graphql/resolvers/scanner.go similarity index 97% rename from api/graphql/resolver_scanner.go rename to api/graphql/resolvers/scanner.go index 39a3057c..030c8213 100644 --- a/api/graphql/resolver_scanner.go +++ b/api/graphql/resolvers/scanner.go @@ -1,4 +1,4 @@ -package api +package resolvers import ( "context" diff --git a/api/graphql/resolver_user.go b/api/graphql/resolvers/user.go similarity index 82% rename from api/graphql/resolver_user.go rename to api/graphql/resolvers/user.go index f54a0614..14dc257f 100644 --- a/api/graphql/resolver_user.go +++ b/api/graphql/resolvers/user.go @@ -1,4 +1,4 @@ -package api +package resolvers import ( "context" @@ -13,6 +13,22 @@ import ( // type userResolver struct{ *Resolver } +func (r *queryResolver) Users(ctx context.Context) ([]*models.User, error) { + + rows, err := r.Database.Query("SELECT * FROM users") + if err != nil { + return nil, err + } + defer rows.Close() + + users, err := models.NewUsersFromRows(rows) + if err != nil { + return nil, err + } + + return users, nil +} + func (r *queryResolver) MyUser(ctx context.Context) (*models.User, error) { user := auth.UserFromContext(ctx) diff --git a/api/server/server.go b/api/server/server.go index 9d9d3728..62544a4c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -6,14 +6,15 @@ import ( "os" "github.com/go-chi/chi" - "github.com/joho/godotenv" "github.com/go-chi/cors" + "github.com/joho/godotenv" "github.com/viktorstrate/photoview/api/database" "github.com/viktorstrate/photoview/api/graphql/auth" "github.com/99designs/gqlgen/handler" photoview_graphql "github.com/viktorstrate/photoview/api/graphql" + "github.com/viktorstrate/photoview/api/graphql/resolvers" ) const defaultPort = "4001" @@ -45,10 +46,10 @@ func main() { AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"}, AllowCredentials: true, - Debug: true, + Debug: false, }).Handler) - graphqlResolver := photoview_graphql.Resolver{Database: db} + graphqlResolver := resolvers.Resolver{Database: db} graphqlDirective := photoview_graphql.DirectiveRoot{} graphqlDirective.IsAdmin = photoview_graphql.IsAdmin(db) @@ -60,6 +61,6 @@ func main() { router.Handle("/", handler.Playground("GraphQL playground", "/graphql")) router.Handle("/graphql", handler.GraphQL(photoview_graphql.NewExecutableSchema(graphqlConfig))) - log.Printf("connect to http://localhost:%s/ for GraphQL playground", port) + log.Printf("🚀 Graphql playground ready at http://localhost:%s/", port) log.Fatal(http.ListenAndServe(":"+port, router)) }