diff --git a/api/example.env b/api/example.env index 97820231..3df9deb5 100644 --- a/api/example.env +++ b/api/example.env @@ -2,4 +2,5 @@ MYSQL_URL=user:password@/dbname -API_PORT=4001 \ No newline at end of file +API_PORT=4001 +API_ENDPOINT=http://localhost:4001/ \ No newline at end of file diff --git a/api/graphql/generated.go b/api/graphql/generated.go index eb0dc623..2db8b9c1 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -53,7 +53,7 @@ type ComplexityRoot struct { Owner func(childComplexity int) int ParentAlbum func(childComplexity int) int Path func(childComplexity int) int - Photos func(childComplexity int) int + Photos func(childComplexity int, filter *models.Filter) int SubAlbums func(childComplexity int) int Title func(childComplexity int) int } @@ -105,12 +105,12 @@ type ComplexityRoot struct { Query struct { Album func(childComplexity int, id *string) int - MyAlbums func(childComplexity int) int - MyPhotos func(childComplexity int) int + MyAlbums func(childComplexity int, filter *models.Filter) int + MyPhotos func(childComplexity int, filter *models.Filter) int MyUser func(childComplexity int) int Photo func(childComplexity int, id string) int SiteInfo func(childComplexity int) int - Users func(childComplexity int) int + Users func(childComplexity int, filter *models.Filter) int } ScannerResult struct { @@ -133,7 +133,7 @@ type ComplexityRoot struct { } type AlbumResolver interface { - Photos(ctx context.Context, obj *models.Album) ([]*models.Photo, error) + Photos(ctx context.Context, obj *models.Album, filter *models.Filter) ([]*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) @@ -154,11 +154,11 @@ type PhotoResolver interface { } type QueryResolver interface { SiteInfo(ctx context.Context) (*models.SiteInfo, error) - Users(ctx context.Context) ([]*models.User, error) + Users(ctx context.Context, filter *models.Filter) ([]*models.User, error) MyUser(ctx context.Context) (*models.User, error) - MyAlbums(ctx context.Context) ([]*models.Album, error) + MyAlbums(ctx context.Context, filter *models.Filter) ([]*models.Album, error) Album(ctx context.Context, id *string) (*models.Album, error) - MyPhotos(ctx context.Context) ([]*models.Photo, error) + MyPhotos(ctx context.Context, filter *models.Filter) ([]*models.Photo, error) Photo(ctx context.Context, id string) (*models.Photo, error) } @@ -210,7 +210,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in break } - return e.complexity.Album.Photos(childComplexity), true + args, err := ec.field_Album_photos_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Album.Photos(childComplexity, args["filter"].(*models.Filter)), true case "Album.subAlbums": if e.complexity.Album.SubAlbums == nil { @@ -473,14 +478,24 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in break } - return e.complexity.Query.MyAlbums(childComplexity), true + args, err := ec.field_Query_myAlbums_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.MyAlbums(childComplexity, args["filter"].(*models.Filter)), true case "Query.myPhotos": if e.complexity.Query.MyPhotos == nil { break } - return e.complexity.Query.MyPhotos(childComplexity), true + args, err := ec.field_Query_myPhotos_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.MyPhotos(childComplexity, args["filter"].(*models.Filter)), true case "Query.myUser": if e.complexity.Query.MyUser == nil { @@ -513,7 +528,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in break } - return e.complexity.Query.Users(childComplexity), true + args, err := ec.field_Query_users_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.Users(childComplexity, args["filter"].(*models.Filter)), true case "ScannerResult.finished": if e.complexity.ScannerResult.Finished == nil { @@ -644,21 +664,33 @@ var parsedSchema = gqlparser.MustLoadSchema( scalar Time +enum OrderDirection { + ASC + DESC +} + +input Filter { + order_by: String + order_direction: OrderDirection + limit: Int + offset: Int +} + type Query { siteInfo: SiteInfo! "List of registered users, must be admin to call" - users: [User!]! @isAdmin + users(filter: Filter): [User!]! @isAdmin "Information about the currently logged in user" myUser: User! "List of albums owned by the logged in user" - myAlbums: [Album!]! + myAlbums(filter: Filter): [Album!]! "Get album by id, user must own the album or be admin" album(id: ID): Album! "List of photos owned by the logged in user" - myPhotos: [Photo!]! + myPhotos(filter: Filter): [Photo!]! "Get photo by id, user must own the photo or be admin" photo(id: ID!): Photo! } @@ -717,7 +749,7 @@ type User { type Album { id: ID! title: String! - photos: [Photo!]! + photos(filter: Filter): [Photo!]! subAlbums: [Album!]! parentAlbum: Album owner: User! @@ -783,6 +815,20 @@ type PhotoEXIF { // region ***************************** args.gotpl ***************************** +func (ec *executionContext) field_Album_photos_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *models.Filter + if tmp, ok := rawArgs["filter"]; ok { + arg0, err = ec.unmarshalOFilter2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFilter(ctx, tmp) + if err != nil { + return nil, err + } + } + args["filter"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_authorizeUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -907,6 +953,34 @@ func (ec *executionContext) field_Query_album_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query_myAlbums_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *models.Filter + if tmp, ok := rawArgs["filter"]; ok { + arg0, err = ec.unmarshalOFilter2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFilter(ctx, tmp) + if err != nil { + return nil, err + } + } + args["filter"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query_myPhotos_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *models.Filter + if tmp, ok := rawArgs["filter"]; ok { + arg0, err = ec.unmarshalOFilter2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFilter(ctx, tmp) + if err != nil { + return nil, err + } + } + args["filter"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query_photo_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -921,6 +995,20 @@ func (ec *executionContext) field_Query_photo_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query_users_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *models.Filter + if tmp, ok := rawArgs["filter"]; ok { + arg0, err = ec.unmarshalOFilter2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFilter(ctx, tmp) + if err != nil { + return nil, err + } + } + args["filter"] = arg0 + return args, nil +} + func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1047,10 +1135,17 @@ func (ec *executionContext) _Album_photos(ctx context.Context, field graphql.Col IsMethod: true, } ctx = graphql.WithResolverContext(ctx, rctx) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Album_photos_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + rctx.Args = args 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 ec.resolvers.Album().Photos(rctx, obj) + return ec.resolvers.Album().Photos(rctx, obj, args["filter"].(*models.Filter)) }) if err != nil { ec.Error(ctx, err) @@ -2362,11 +2457,18 @@ func (ec *executionContext) _Query_users(ctx context.Context, field graphql.Coll IsMethod: true, } ctx = graphql.WithResolverContext(ctx, rctx) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_users_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + rctx.Args = args ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) 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.Query().Users(rctx) + return ec.resolvers.Query().Users(rctx, args["filter"].(*models.Filter)) } directive1 := func(ctx context.Context) (interface{}, error) { if ec.directives.IsAdmin == nil { @@ -2456,10 +2558,17 @@ func (ec *executionContext) _Query_myAlbums(ctx context.Context, field graphql.C IsMethod: true, } ctx = graphql.WithResolverContext(ctx, rctx) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_myAlbums_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + rctx.Args = args 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 ec.resolvers.Query().MyAlbums(rctx) + return ec.resolvers.Query().MyAlbums(rctx, args["filter"].(*models.Filter)) }) if err != nil { ec.Error(ctx, err) @@ -2537,10 +2646,17 @@ func (ec *executionContext) _Query_myPhotos(ctx context.Context, field graphql.C IsMethod: true, } ctx = graphql.WithResolverContext(ctx, rctx) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_myPhotos_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + rctx.Args = args 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 ec.resolvers.Query().MyPhotos(rctx) + return ec.resolvers.Query().MyPhotos(rctx, args["filter"].(*models.Filter)) }) if err != nil { ec.Error(ctx, err) @@ -4175,6 +4291,42 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputFilter(ctx context.Context, obj interface{}) (models.Filter, error) { + var it models.Filter + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "order_by": + var err error + it.OrderBy, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "order_direction": + var err error + it.OrderDirection, err = ec.unmarshalOOrderDirection2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐOrderDirection(ctx, v) + if err != nil { + return it, err + } + case "limit": + var err error + it.Limit, err = ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + case "offset": + var err error + it.Offset, err = ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + // endregion **************************** input.gotpl ***************************** // region ************************** interface.gotpl *************************** @@ -5554,6 +5706,18 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return ec.marshalOBoolean2bool(ctx, sel, *v) } +func (ec *executionContext) unmarshalOFilter2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFilter(ctx context.Context, v interface{}) (models.Filter, error) { + return ec.unmarshalInputFilter(ctx, v) +} + +func (ec *executionContext) unmarshalOFilter2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFilter(ctx context.Context, v interface{}) (*models.Filter, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalOFilter2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFilter(ctx, v) + return &res, err +} + func (ec *executionContext) unmarshalOFloat2float64(ctx context.Context, v interface{}) (float64, error) { return graphql.UnmarshalFloat(v) } @@ -5623,6 +5787,30 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele return ec.marshalOInt2int(ctx, sel, *v) } +func (ec *executionContext) unmarshalOOrderDirection2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐOrderDirection(ctx context.Context, v interface{}) (models.OrderDirection, error) { + var res models.OrderDirection + return res, res.UnmarshalGQL(v) +} + +func (ec *executionContext) marshalOOrderDirection2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐOrderDirection(ctx context.Context, sel ast.SelectionSet, v models.OrderDirection) graphql.Marshaler { + return v +} + +func (ec *executionContext) unmarshalOOrderDirection2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐOrderDirection(ctx context.Context, v interface{}) (*models.OrderDirection, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalOOrderDirection2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐOrderDirection(ctx, v) + return &res, err +} + +func (ec *executionContext) marshalOOrderDirection2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐOrderDirection(ctx context.Context, sel ast.SelectionSet, v *models.OrderDirection) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return v +} + func (ec *executionContext) marshalOPhoto2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx context.Context, sel ast.SelectionSet, v models.Photo) graphql.Marshaler { return ec._Photo(ctx, sel, &v) } diff --git a/api/graphql/models/album.go b/api/graphql/models/album.go index 293625b2..1adb43f7 100644 --- a/api/graphql/models/album.go +++ b/api/graphql/models/album.go @@ -26,3 +26,17 @@ func NewAlbumFromRow(row *sql.Row) (*Album, error) { return &album, nil } + +func NewAlbumsFromRows(rows *sql.Rows) ([]*Album, error) { + albums := make([]*Album, 0) + + for rows.Next() { + var album Album + if err := rows.Scan(&album.AlbumID, &album.Title, &album.ParentAlbum, &album.OwnerID, &album.Path); err != nil { + return nil, err + } + albums = append(albums, &album) + } + + return albums, nil +} diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go index 273f340b..9dc942be 100644 --- a/api/graphql/models/generated.go +++ b/api/graphql/models/generated.go @@ -3,6 +3,9 @@ package models import ( + "fmt" + "io" + "strconv" "time" ) @@ -12,6 +15,13 @@ type AuthorizeResult struct { Token *string `json:"token"` } +type Filter struct { + OrderBy *string `json:"order_by"` + OrderDirection *OrderDirection `json:"order_direction"` + Limit *int `json:"limit"` + Offset *int `json:"offset"` +} + // EXIF metadata from the camera type PhotoExif struct { Photo *Photo `json:"photo"` @@ -47,3 +57,44 @@ type ScannerResult struct { type SiteInfo struct { InitialSetup bool `json:"initialSetup"` } + +type OrderDirection string + +const ( + OrderDirectionAsc OrderDirection = "ASC" + OrderDirectionDesc OrderDirection = "DESC" +) + +var AllOrderDirection = []OrderDirection{ + OrderDirectionAsc, + OrderDirectionDesc, +} + +func (e OrderDirection) IsValid() bool { + switch e { + case OrderDirectionAsc, OrderDirectionDesc: + return true + } + return false +} + +func (e OrderDirection) String() string { + return string(e) +} + +func (e *OrderDirection) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = OrderDirection(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid OrderDirection", str) + } + return nil +} + +func (e OrderDirection) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/api/graphql/models/photo.go b/api/graphql/models/photo.go index 1ae97682..3285452c 100644 --- a/api/graphql/models/photo.go +++ b/api/graphql/models/photo.go @@ -2,7 +2,9 @@ package models import ( "database/sql" - "fmt" + "net/url" + "os" + "path" "strconv" ) @@ -61,7 +63,12 @@ func NewPhotosFromRows(rows *sql.Rows) ([]*Photo, error) { } func (p *PhotoURL) URL() string { - return fmt.Sprintf("/photo/%s", p.PhotoName) + imageUrl, err := url.Parse(os.Getenv("API_ENDPOINT")) + if err != nil { + return path.Join("/photo", p.PhotoName) + } + imageUrl.Path = path.Join(imageUrl.Path, "photo", p.PhotoName) + return imageUrl.String() } func NewPhotoURLFromRow(row *sql.Row) (*PhotoURL, error) { diff --git a/api/graphql/models/utils.go b/api/graphql/models/utils.go new file mode 100644 index 00000000..b6c02e4e --- /dev/null +++ b/api/graphql/models/utils.go @@ -0,0 +1,47 @@ +package models + +import ( + "fmt" + "log" + "regexp" + "strings" +) + +func (filter *Filter) FormatSQL() (string, error) { + if filter == nil { + return "", nil + } + + result := "" + + if filter.Limit != nil { + log.Println("Limit") + offset := 0 + if filter.Offset != nil && *filter.Offset >= 0 { + offset = *filter.Offset + } + + result += fmt.Sprintf(" LIMIT %d OFFSET %d", *filter.Limit, offset) + } + + if filter.OrderBy != nil { + log.Println("Order by") + order_by := filter.OrderBy + match, err := regexp.MatchString("^(\\w+(,\\s*))*\\w+$", strings.TrimSpace(*filter.OrderBy)) + if err != nil { + return "", err + } + + if match { + direction := "ASC" + if filter.OrderDirection != nil && filter.OrderDirection.IsValid() { + direction = filter.OrderDirection.String() + } + + result += fmt.Sprintf(" ORDER BY %s %s", *order_by, direction) + } + } + + log.Printf("Filter: " + result) + return result, nil +} diff --git a/api/graphql/resolvers/album.go b/api/graphql/resolvers/album.go index a39ca97b..8f4d7dd2 100644 --- a/api/graphql/resolvers/album.go +++ b/api/graphql/resolvers/album.go @@ -8,9 +8,30 @@ import ( "github.com/viktorstrate/photoview/api/graphql/models" ) -func (r *queryResolver) MyAlbums(ctx context.Context) ([]*models.Album, error) { - panic("Not implemented") +func (r *queryResolver) MyAlbums(ctx context.Context, filter *models.Filter) ([]*models.Album, error) { + user := auth.UserFromContext(ctx) + if user == nil { + return nil, auth.ErrUnauthorized + } + + filterSQL, err := filter.FormatSQL() + if err != nil { + return nil, err + } + + rows, err := r.Database.Query("SELECT * FROM album WHERE owner_id = ? AND parent_album IS NULL"+filterSQL, user.UserID) + if err != nil { + return nil, err + } + + albums, err := models.NewAlbumsFromRows(rows) + if err != nil { + return nil, err + } + + return albums, nil } + func (r *queryResolver) Album(ctx context.Context, id *string) (*models.Album, error) { user := auth.UserFromContext(ctx) if user == nil { @@ -32,8 +53,14 @@ func (r *Resolver) Album() api.AlbumResolver { 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) +func (r *albumResolver) Photos(ctx context.Context, obj *models.Album, filter *models.Filter) ([]*models.Photo, error) { + + filterSQL, err := filter.FormatSQL() + if err != nil { + return nil, err + } + + photoRows, err := r.Database.Query("SELECT photo.* FROM album, photo WHERE album.album_id = ? AND photo.album_id = album.album_id"+filterSQL, obj.AlbumID) if err != nil { return nil, err } @@ -48,7 +75,17 @@ func (r *albumResolver) Photos(ctx context.Context, obj *models.Album) ([]*model } func (r *albumResolver) SubAlbums(ctx context.Context, obj *models.Album) ([]*models.Album, error) { - panic("not implemented") + rows, err := r.Database.Query("SELECT * FROM album WHERE parent_album = ?", obj.AlbumID) + if err != nil { + return nil, err + } + + albums, err := models.NewAlbumsFromRows(rows) + if err != nil { + return nil, err + } + + return albums, nil } func (r *albumResolver) ParentAlbum(ctx context.Context, obj *models.Album) (*models.Album, error) { diff --git a/api/graphql/resolvers/photo.go b/api/graphql/resolvers/photo.go index 56d403dd..b37c904d 100644 --- a/api/graphql/resolvers/photo.go +++ b/api/graphql/resolvers/photo.go @@ -9,13 +9,18 @@ import ( "github.com/viktorstrate/photoview/api/graphql/models" ) -func (r *queryResolver) MyPhotos(ctx context.Context) ([]*models.Photo, error) { +func (r *queryResolver) MyPhotos(ctx context.Context, filter *models.Filter) ([]*models.Photo, error) { user := auth.UserFromContext(ctx) if user == nil { return nil, errors.New("unauthorized") } - rows, err := r.Database.Query("SELECT photo.* FROM photo, album WHERE photo.album_id = album.album_id AND album.owner_id = ?", user.UserID) + filterSQL, err := filter.FormatSQL() + if err != nil { + return nil, err + } + + rows, err := r.Database.Query("SELECT photo.* FROM photo, album WHERE photo.album_id = album.album_id AND album.owner_id = ?"+filterSQL, user.UserID) if err != nil { return nil, err } diff --git a/api/graphql/resolvers/user.go b/api/graphql/resolvers/user.go index d4643d23..0f6afc46 100644 --- a/api/graphql/resolvers/user.go +++ b/api/graphql/resolvers/user.go @@ -14,9 +14,14 @@ import ( // type userResolver struct{ *Resolver } -func (r *queryResolver) Users(ctx context.Context) ([]*models.User, error) { +func (r *queryResolver) Users(ctx context.Context, filter *models.Filter) ([]*models.User, error) { - rows, err := r.Database.Query("SELECT * FROM users") + filterSQL, err := filter.FormatSQL() + if err != nil { + return nil, err + } + + rows, err := r.Database.Query("SELECT * FROM users" + filterSQL) if err != nil { return nil, err } diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index 6815373a..84f44d32 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -2,21 +2,33 @@ directive @isAdmin on FIELD_DEFINITION scalar Time +enum OrderDirection { + ASC + DESC +} + +input Filter { + order_by: String + order_direction: OrderDirection + limit: Int + offset: Int +} + type Query { siteInfo: SiteInfo! "List of registered users, must be admin to call" - users: [User!]! @isAdmin + users(filter: Filter): [User!]! @isAdmin "Information about the currently logged in user" myUser: User! "List of albums owned by the logged in user" - myAlbums: [Album!]! + myAlbums(filter: Filter): [Album!]! "Get album by id, user must own the album or be admin" album(id: ID): Album! "List of photos owned by the logged in user" - myPhotos: [Photo!]! + myPhotos(filter: Filter): [Photo!]! "Get photo by id, user must own the photo or be admin" photo(id: ID!): Photo! } @@ -75,7 +87,7 @@ type User { type Album { id: ID! title: String! - photos: [Photo!]! + photos(filter: Filter): [Photo!]! subAlbums: [Album!]! parentAlbum: Album owner: User! diff --git a/api/scanner/scanner.go b/api/scanner/scanner.go index 651a6fe0..62cce841 100644 --- a/api/scanner/scanner.go +++ b/api/scanner/scanner.go @@ -120,8 +120,6 @@ func scan(database *sql.DB, user *models.User) { return } - log.Printf("Content type: %s\n", *content_type) - if err := ProcessImage(tx, photoPath, albumId, *content_type); err != nil { log.Printf("ERROR: processing image %s: %s", photoPath, err) tx.Rollback() diff --git a/ui/src/Pages/PhotosPage/PhotosPage.js b/ui/src/Pages/PhotosPage/PhotosPage.js index 66748c39..8ef3c70a 100644 --- a/ui/src/Pages/PhotosPage/PhotosPage.js +++ b/ui/src/Pages/PhotosPage/PhotosPage.js @@ -9,10 +9,12 @@ import { SidebarConsumer } from '../../components/sidebar/Sidebar' const photoQuery = gql` query allPhotosPage { - myAlbums(orderBy: title_asc, filter: { photos_not: null }) { + myAlbums(filter: { order_by: "title", order_direction: ASC }) { title id - photos(orderBy: title_desc, first: 12) { + photos( + filter: { order_by: "photo.title", order_direction: DESC, limit: 12 } + ) { id title thumbnail {