diff --git a/api/database/migrations/0001_user.up.sql b/api/database/migrations/0001_user.up.sql index 62d00608..93ef1b50 100644 --- a/api/database/migrations/0001_user.up.sql +++ b/api/database/migrations/0001_user.up.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS user ( user_id int NOT NULL AUTO_INCREMENT, - username varchar(255) NOT NULL UNIQUE, - password varchar(255) NOT NULL, + username varchar(256) NOT NULL UNIQUE, + password varchar(256) NOT NULL, root_path varchar(512), admin boolean NOT NULL DEFAULT 0, diff --git a/api/database/migrations/0002_photo.up.sql b/api/database/migrations/0002_photo.up.sql index 73a2b251..f3893298 100644 --- a/api/database/migrations/0002_photo.up.sql +++ b/api/database/migrations/0002_photo.up.sql @@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS album ( CREATE TABLE IF NOT EXISTS photo ( photo_id int NOT NULL AUTO_INCREMENT, title varchar(256) NOT NULL, - path varchar(512) NOT NULL UNIQUE, + path varchar(1024) NOT NULL UNIQUE, album_id int NOT NULL, exif_id int, @@ -41,7 +41,7 @@ CREATE TABLE IF NOT EXISTS photo ( CREATE TABLE IF NOT EXISTS photo_url ( url_id int NOT NULL AUTO_INCREMENT, photo_id int NOT NULL, - photo_name varchar(256) NOT NULL, + photo_name varchar(512) NOT NULL, width int NOT NULL, height int NOT NULL, purpose varchar(64) NOT NULL, diff --git a/api/database/migrations/004_shares.down.sql b/api/database/migrations/004_shares.down.sql new file mode 100644 index 00000000..15dc8df5 --- /dev/null +++ b/api/database/migrations/004_shares.down.sql @@ -0,0 +1,2 @@ + +DROP TABLE IF EXISTS share_token; diff --git a/api/database/migrations/004_shares.up.sql b/api/database/migrations/004_shares.up.sql new file mode 100644 index 00000000..248f46f1 --- /dev/null +++ b/api/database/migrations/004_shares.up.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS share_token ( + token_id int AUTO_INCREMENT, + value char(24) NOT NULL UNIQUE, + owner_id int NOT NULL, + expire timestamp, + password varchar(256) NOT NULL, + album_id int, + photo_id int, + + PRIMARY KEY (token_id), + CHECK (album_id IS NOT NULL OR photo_id IS NOT NULL) +); diff --git a/api/gqlgen.yml b/api/gqlgen.yml index 9575ccb7..67267481 100644 --- a/api/gqlgen.yml +++ b/api/gqlgen.yml @@ -28,3 +28,5 @@ models: model: github.com/viktorstrate/photoview/api/graphql/models.PhotoURL Album: model: github.com/viktorstrate/photoview/api/graphql/models.Album + ShareToken: + model: github.com/viktorstrate/photoview/api/graphql/models.ShareToken diff --git a/api/graphql/generated.go b/api/graphql/generated.go index c8f01e19..a6b27384 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -41,6 +41,7 @@ type ResolverRoot interface { Mutation() MutationResolver Photo() PhotoResolver Query() QueryResolver + ShareToken() ShareTokenResolver } type DirectiveRoot struct { @@ -70,7 +71,9 @@ type ComplexityRoot struct { InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int RegisterUser func(childComplexity int, username string, password string, rootPath string) int ScanAll func(childComplexity int) int - ScanUser func(childComplexity int, userID string) int + ScanUser func(childComplexity int, userID int) int + ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int + SharePhoto func(childComplexity int, photoID int, expire *time.Time, password *string) int } Photo struct { @@ -105,13 +108,15 @@ type ComplexityRoot struct { } Query struct { - Album func(childComplexity int, id *string) 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, filter *models.Filter) int + Album func(childComplexity int, id int) int + AlbumShares func(childComplexity int, id int, password *string) 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 int) int + PhotoShares func(childComplexity int, id int, password *string) int + SiteInfo func(childComplexity int) int + Users func(childComplexity int, filter *models.Filter) int } ScannerResult struct { @@ -121,6 +126,15 @@ type ComplexityRoot struct { Success func(childComplexity int) int } + ShareToken struct { + Album func(childComplexity int) int + Expire func(childComplexity int) int + ID func(childComplexity int) int + Owner func(childComplexity int) int + Photo func(childComplexity int) int + Token func(childComplexity int) int + } + SiteInfo struct { InitialSetup func(childComplexity int) int } @@ -146,7 +160,9 @@ type MutationResolver interface { RegisterUser(ctx context.Context, username string, password string, rootPath string) (*models.AuthorizeResult, error) InitialSetupWizard(ctx context.Context, username string, password string, rootPath string) (*models.AuthorizeResult, error) ScanAll(ctx context.Context) (*models.ScannerResult, error) - ScanUser(ctx context.Context, userID string) (*models.ScannerResult, error) + ScanUser(ctx context.Context, userID int) (*models.ScannerResult, error) + ShareAlbum(ctx context.Context, albumID int, expire *time.Time, password *string) (*models.ShareToken, error) + SharePhoto(ctx context.Context, photoID int, expire *time.Time, password *string) (*models.ShareToken, error) } type PhotoResolver interface { Original(ctx context.Context, obj *models.Photo) (*models.PhotoURL, error) @@ -160,9 +176,17 @@ type QueryResolver interface { Users(ctx context.Context, filter *models.Filter) ([]*models.User, error) MyUser(ctx context.Context) (*models.User, error) MyAlbums(ctx context.Context, filter *models.Filter) ([]*models.Album, error) - Album(ctx context.Context, id *string) (*models.Album, error) + Album(ctx context.Context, id int) (*models.Album, error) MyPhotos(ctx context.Context, filter *models.Filter) ([]*models.Photo, error) - Photo(ctx context.Context, id string) (*models.Photo, error) + Photo(ctx context.Context, id int) (*models.Photo, error) + AlbumShares(ctx context.Context, id int, password *string) ([]*models.ShareToken, error) + PhotoShares(ctx context.Context, id int, password *string) ([]*models.ShareToken, error) +} +type ShareTokenResolver interface { + Owner(ctx context.Context, obj *models.ShareToken) (*models.User, error) + + Album(ctx context.Context, obj *models.ShareToken) (*models.Album, error) + Photo(ctx context.Context, obj *models.ShareToken) (*models.Photo, error) } type executableSchema struct { @@ -320,7 +344,31 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.ScanUser(childComplexity, args["userId"].(string)), true + return e.complexity.Mutation.ScanUser(childComplexity, args["userId"].(int)), true + + case "Mutation.shareAlbum": + if e.complexity.Mutation.ShareAlbum == nil { + break + } + + args, err := ec.field_Mutation_shareAlbum_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.ShareAlbum(childComplexity, args["albumId"].(int), args["expire"].(*time.Time), args["password"].(*string)), true + + case "Mutation.sharePhoto": + if e.complexity.Mutation.SharePhoto == nil { + break + } + + args, err := ec.field_Mutation_sharePhoto_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.SharePhoto(childComplexity, args["photoId"].(int), args["expire"].(*time.Time), args["password"].(*string)), true case "Photo.album": if e.complexity.Photo.Album == nil { @@ -486,7 +534,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.Album(childComplexity, args["id"].(*string)), true + return e.complexity.Query.Album(childComplexity, args["id"].(int)), true + + case "Query.albumShares": + if e.complexity.Query.AlbumShares == nil { + break + } + + args, err := ec.field_Query_albumShares_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.AlbumShares(childComplexity, args["id"].(int), args["password"].(*string)), true case "Query.myAlbums": if e.complexity.Query.MyAlbums == nil { @@ -529,7 +589,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.Photo(childComplexity, args["id"].(string)), true + return e.complexity.Query.Photo(childComplexity, args["id"].(int)), true + + case "Query.photoShares": + if e.complexity.Query.PhotoShares == nil { + break + } + + args, err := ec.field_Query_photoShares_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.PhotoShares(childComplexity, args["id"].(int), args["password"].(*string)), true case "Query.siteInfo": if e.complexity.Query.SiteInfo == nil { @@ -578,6 +650,48 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ScannerResult.Success(childComplexity), true + case "ShareToken.album": + if e.complexity.ShareToken.Album == nil { + break + } + + return e.complexity.ShareToken.Album(childComplexity), true + + case "ShareToken.expire": + if e.complexity.ShareToken.Expire == nil { + break + } + + return e.complexity.ShareToken.Expire(childComplexity), true + + case "ShareToken.id": + if e.complexity.ShareToken.ID == nil { + break + } + + return e.complexity.ShareToken.ID(childComplexity), true + + case "ShareToken.owner": + if e.complexity.ShareToken.Owner == nil { + break + } + + return e.complexity.ShareToken.Owner(childComplexity), true + + case "ShareToken.photo": + if e.complexity.ShareToken.Photo == nil { + break + } + + return e.complexity.ShareToken.Photo(childComplexity), true + + case "ShareToken.token": + if e.complexity.ShareToken.Token == nil { + break + } + + return e.complexity.ShareToken.Token(childComplexity), true + case "SiteInfo.initialSetup": if e.complexity.SiteInfo.InitialSetup == nil { break @@ -702,12 +816,15 @@ type Query { "List of albums owned by the logged in user" myAlbums(filter: Filter): [Album!]! "Get album by id, user must own the album or be admin" - album(id: ID): Album! + album(id: Int!): Album! "List of photos owned by the logged in user" myPhotos(filter: Filter): [Photo!]! "Get photo by id, user must own the photo or be admin" - photo(id: ID!): Photo! + photo(id: Int!): Photo! + + albumShares(id: Int!, password: String): [ShareToken!]! + photoShares(id: Int!, password: String): [ShareToken!]! } type Mutation { @@ -730,7 +847,12 @@ type Mutation { "Scan all users for new photos" scanAll: ScannerResult! "Scan a single user for new photos" - scanUser(userId: ID!): ScannerResult! + scanUser(userId: Int!): ScannerResult! + + "Generate share token for album" + shareAlbum(albumId: Int!, expire: Time, password: String): ShareToken + "Generate share token for photo" + sharePhoto(photoId: Int!, expire: Time, password: String): ShareToken } type AuthorizeResult { @@ -746,13 +868,28 @@ type ScannerResult { message: String } +"A token used to publicly access an album or photo" +type ShareToken { + id: Int! + token: String! + "The user who created the token" + owner: User! + "Optional expire date" + expire: Time + + "The album this token shares" + album: Album + "The photo this token shares" + photo: Photo +} + "General public information about the site" type SiteInfo { initialSetup: Boolean! } type User { - id: ID! + id: Int! username: String! #albums: [Album] "Local filepath for the user's photos" @@ -762,13 +899,19 @@ type User { } type Album { - id: ID! + id: Int! title: String! + "The photos inside this album" photos(filter: Filter): [Photo!]! + "The albums contained in this album" subAlbums(filter: Filter): [Album!]! + "The album witch contains this album" parentAlbum: Album + "The user who owns this album" owner: User! + "The path on the filesystem of the server, where this album is located" path: String! + "An image in this album used for previewing this album" thumbnail: Photo # shares: [ShareToken] @@ -784,7 +927,7 @@ type PhotoURL { } type Photo { - id: ID! + id: Int! title: String! "Local filepath for the photo" path: String! @@ -944,9 +1087,9 @@ func (ec *executionContext) field_Mutation_registerUser_args(ctx context.Context 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{}{} - var arg0 string + var arg0 int if tmp, ok := rawArgs["userId"]; ok { - arg0, err = ec.unmarshalNID2string(ctx, tmp) + arg0, err = ec.unmarshalNInt2int(ctx, tmp) if err != nil { return nil, err } @@ -955,6 +1098,66 @@ func (ec *executionContext) field_Mutation_scanUser_args(ctx context.Context, ra return args, nil } +func (ec *executionContext) field_Mutation_shareAlbum_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 { + arg0, err = ec.unmarshalNInt2int(ctx, tmp) + if err != nil { + return nil, err + } + } + args["albumId"] = arg0 + var arg1 *time.Time + if tmp, ok := rawArgs["expire"]; ok { + arg1, err = ec.unmarshalOTime2ᚖtimeᚐTime(ctx, tmp) + if err != nil { + return nil, err + } + } + args["expire"] = arg1 + var arg2 *string + if tmp, ok := rawArgs["password"]; ok { + arg2, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["password"] = arg2 + return args, nil +} + +func (ec *executionContext) field_Mutation_sharePhoto_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 *time.Time + if tmp, ok := rawArgs["expire"]; ok { + arg1, err = ec.unmarshalOTime2ᚖtimeᚐTime(ctx, tmp) + if err != nil { + return nil, err + } + } + args["expire"] = arg1 + var arg2 *string + if tmp, ok := rawArgs["password"]; ok { + arg2, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["password"] = arg2 + return args, nil +} + func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -969,12 +1172,34 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query_albumShares_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["id"]; ok { + arg0, err = ec.unmarshalNInt2int(ctx, tmp) + if err != nil { + return nil, err + } + } + args["id"] = arg0 + var arg1 *string + if tmp, ok := rawArgs["password"]; ok { + arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["password"] = arg1 + return args, nil +} + func (ec *executionContext) field_Query_album_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} - var arg0 *string + var arg0 int if tmp, ok := rawArgs["id"]; ok { - arg0, err = ec.unmarshalOID2ᚖstring(ctx, tmp) + arg0, err = ec.unmarshalNInt2int(ctx, tmp) if err != nil { return nil, err } @@ -1011,12 +1236,34 @@ func (ec *executionContext) field_Query_myPhotos_args(ctx context.Context, rawAr return args, nil } +func (ec *executionContext) field_Query_photoShares_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["id"]; ok { + arg0, err = ec.unmarshalNInt2int(ctx, tmp) + if err != nil { + return nil, err + } + } + args["id"] = arg0 + var arg1 *string + if tmp, ok := rawArgs["password"]; ok { + arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["password"] = arg1 + 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{}{} - var arg0 string + var arg0 int if tmp, ok := rawArgs["id"]; ok { - arg0, err = ec.unmarshalNID2string(ctx, tmp) + arg0, err = ec.unmarshalNInt2int(ctx, tmp) if err != nil { return nil, err } @@ -1106,10 +1353,10 @@ func (ec *executionContext) _Album_id(ctx context.Context, field graphql.Collect } return graphql.Null } - res := resTmp.(string) + res := resTmp.(int) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalNInt2int(ctx, field.Selections, res) } func (ec *executionContext) _Album_title(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) { @@ -1679,7 +1926,7 @@ func (ec *executionContext) _Mutation_scanUser(ctx context.Context, field graphq 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.Mutation().ScanUser(rctx, args["userId"].(string)) + return ec.resolvers.Mutation().ScanUser(rctx, args["userId"].(int)) }) if err != nil { ec.Error(ctx, err) @@ -1697,6 +1944,88 @@ func (ec *executionContext) _Mutation_scanUser(ctx context.Context, field graphq return ec.marshalNScannerResult2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐScannerResult(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_shareAlbum(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + ctx = graphql.WithResolverContext(ctx, rctx) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_shareAlbum_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.Mutation().ShareAlbum(rctx, args["albumId"].(int), args["expire"].(*time.Time), args["password"].(*string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*models.ShareToken) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalOShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation_sharePhoto(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + ctx = graphql.WithResolverContext(ctx, rctx) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_sharePhoto_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.Mutation().SharePhoto(rctx, args["photoId"].(int), args["expire"].(*time.Time), args["password"].(*string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*models.ShareToken) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalOShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, field.Selections, res) +} + func (ec *executionContext) _Photo_id(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { @@ -1728,10 +2057,10 @@ func (ec *executionContext) _Photo_id(ctx context.Context, field graphql.Collect } return graphql.Null } - res := resTmp.(string) + res := resTmp.(int) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalNInt2int(ctx, field.Selections, res) } func (ec *executionContext) _Photo_title(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) { @@ -2683,7 +3012,7 @@ func (ec *executionContext) _Query_album(ctx context.Context, field graphql.Coll 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().Album(rctx, args["id"].(*string)) + return ec.resolvers.Query().Album(rctx, args["id"].(int)) }) if err != nil { ec.Error(ctx, err) @@ -2771,7 +3100,7 @@ func (ec *executionContext) _Query_photo(ctx context.Context, field graphql.Coll 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().Photo(rctx, args["id"].(string)) + return ec.resolvers.Query().Photo(rctx, args["id"].(int)) }) if err != nil { ec.Error(ctx, err) @@ -2789,6 +3118,94 @@ func (ec *executionContext) _Query_photo(ctx context.Context, field graphql.Coll return ec.marshalNPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx, field.Selections, res) } +func (ec *executionContext) _Query_albumShares(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + ctx = graphql.WithResolverContext(ctx, rctx) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_albumShares_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().AlbumShares(rctx, args["id"].(int), args["password"].(*string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*models.ShareToken) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNShareToken2ᚕᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_photoShares(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + ctx = graphql.WithResolverContext(ctx, rctx) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_photoShares_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().PhotoShares(rctx, args["id"].(int), args["password"].(*string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*models.ShareToken) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNShareToken2ᚕᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { @@ -3006,6 +3423,219 @@ func (ec *executionContext) _ScannerResult_message(ctx context.Context, field gr return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } +func (ec *executionContext) _ShareToken_id(ctx context.Context, field graphql.CollectedField, obj *models.ShareToken) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "ShareToken", + Field: field, + Args: nil, + 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 + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNInt2int(ctx, field.Selections, res) +} + +func (ec *executionContext) _ShareToken_token(ctx context.Context, field graphql.CollectedField, obj *models.ShareToken) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "ShareToken", + Field: field, + Args: nil, + 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.Token(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _ShareToken_owner(ctx context.Context, field graphql.CollectedField, obj *models.ShareToken) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "ShareToken", + Field: field, + Args: nil, + 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 ec.resolvers.ShareToken().Owner(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*models.User) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNUser2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res) +} + +func (ec *executionContext) _ShareToken_expire(ctx context.Context, field graphql.CollectedField, obj *models.ShareToken) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "ShareToken", + Field: field, + Args: nil, + IsMethod: false, + } + 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.Expire, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*time.Time) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) _ShareToken_album(ctx context.Context, field graphql.CollectedField, obj *models.ShareToken) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "ShareToken", + Field: field, + Args: nil, + 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 ec.resolvers.ShareToken().Album(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*models.Album) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalOAlbum2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res) +} + +func (ec *executionContext) _ShareToken_photo(ctx context.Context, field graphql.CollectedField, obj *models.ShareToken) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "ShareToken", + Field: field, + Args: nil, + 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 ec.resolvers.ShareToken().Photo(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*models.Photo) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalOPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx, field.Selections, res) +} + func (ec *executionContext) _SiteInfo_initialSetup(ctx context.Context, field graphql.CollectedField, obj *models.SiteInfo) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { @@ -3074,10 +3704,10 @@ func (ec *executionContext) _User_id(ctx context.Context, field graphql.Collecte } return graphql.Null } - res := resTmp.(string) + res := resTmp.(int) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalNInt2int(ctx, field.Selections, res) } func (ec *executionContext) _User_username(ctx context.Context, field graphql.CollectedField, obj *models.User) (ret graphql.Marshaler) { @@ -4578,6 +5208,10 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "shareAlbum": + out.Values[i] = ec._Mutation_shareAlbum(ctx, field) + case "sharePhoto": + out.Values[i] = ec._Mutation_sharePhoto(ctx, field) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -4887,6 +5521,34 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "albumShares": + 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._Query_albumShares(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "photoShares": + 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._Query_photoShares(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "__type": out.Values[i] = ec._Query___type(ctx, field) case "__schema": @@ -4938,6 +5600,76 @@ func (ec *executionContext) _ScannerResult(ctx context.Context, sel ast.Selectio return out } +var shareTokenImplementors = []string{"ShareToken"} + +func (ec *executionContext) _ShareToken(ctx context.Context, sel ast.SelectionSet, obj *models.ShareToken) graphql.Marshaler { + fields := graphql.CollectFields(ec.RequestContext, sel, shareTokenImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ShareToken") + case "id": + out.Values[i] = ec._ShareToken_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "token": + out.Values[i] = ec._ShareToken_token(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "owner": + 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._ShareToken_owner(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "expire": + out.Values[i] = ec._ShareToken_expire(ctx, field, obj) + case "album": + 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._ShareToken_album(ctx, field, obj) + return res + }) + case "photo": + 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._ShareToken_photo(ctx, field, obj) + return res + }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var siteInfoImplementors = []string{"SiteInfo"} func (ec *executionContext) _SiteInfo(ctx context.Context, sel ast.SelectionSet, obj *models.SiteInfo) graphql.Marshaler { @@ -5331,20 +6063,6 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } -func (ec *executionContext) unmarshalNID2string(ctx context.Context, v interface{}) (string, error) { - return graphql.UnmarshalID(v) -} - -func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { - res := graphql.MarshalID(v) - if res == graphql.Null { - if !ec.HasError(graphql.GetResolverContext(ctx)) { - ec.Errorf(ctx, "must not be null") - } - } - return res -} - func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) { return graphql.UnmarshalInt(v) } @@ -5438,6 +6156,57 @@ func (ec *executionContext) marshalNScannerResult2ᚖgithubᚗcomᚋviktorstrate return ec._ScannerResult(ctx, sel, v) } +func (ec *executionContext) marshalNShareToken2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx context.Context, sel ast.SelectionSet, v models.ShareToken) graphql.Marshaler { + return ec._ShareToken(ctx, sel, &v) +} + +func (ec *executionContext) marshalNShareToken2ᚕᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.ShareToken) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + rctx := &graphql.ResolverContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithResolverContext(ctx, rctx) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + +func (ec *executionContext) marshalNShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx context.Context, sel ast.SelectionSet, v *models.ShareToken) graphql.Marshaler { + if v == nil { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._ShareToken(ctx, sel, v) +} + func (ec *executionContext) marshalNSiteInfo2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐSiteInfo(ctx context.Context, sel ast.SelectionSet, v models.SiteInfo) graphql.Marshaler { return ec._SiteInfo(ctx, sel, &v) } @@ -5823,29 +6592,6 @@ func (ec *executionContext) marshalOFloat2ᚖfloat64(ctx context.Context, sel as return ec.marshalOFloat2float64(ctx, sel, *v) } -func (ec *executionContext) unmarshalOID2string(ctx context.Context, v interface{}) (string, error) { - return graphql.UnmarshalID(v) -} - -func (ec *executionContext) marshalOID2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { - return graphql.MarshalID(v) -} - -func (ec *executionContext) unmarshalOID2ᚖstring(ctx context.Context, v interface{}) (*string, error) { - if v == nil { - return nil, nil - } - res, err := ec.unmarshalOID2string(ctx, v) - return &res, err -} - -func (ec *executionContext) marshalOID2ᚖstring(ctx context.Context, sel ast.SelectionSet, v *string) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec.marshalOID2string(ctx, sel, *v) -} - func (ec *executionContext) unmarshalOInt2int(ctx context.Context, v interface{}) (int, error) { return graphql.UnmarshalInt(v) } @@ -5915,6 +6661,17 @@ func (ec *executionContext) marshalOPhotoEXIF2ᚖgithubᚗcomᚋviktorstrateᚋp return ec._PhotoEXIF(ctx, sel, v) } +func (ec *executionContext) marshalOShareToken2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx context.Context, sel ast.SelectionSet, v models.ShareToken) graphql.Marshaler { + return ec._ShareToken(ctx, sel, &v) +} + +func (ec *executionContext) marshalOShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx context.Context, sel ast.SelectionSet, v *models.ShareToken) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._ShareToken(ctx, sel, v) +} + func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) { return graphql.UnmarshalString(v) } diff --git a/api/graphql/models/album.go b/api/graphql/models/album.go index 1adb43f7..2f5bdd62 100644 --- a/api/graphql/models/album.go +++ b/api/graphql/models/album.go @@ -2,7 +2,6 @@ package models import ( "database/sql" - "strconv" ) type Album struct { @@ -13,8 +12,8 @@ type Album struct { Path string } -func (a *Album) ID() string { - return strconv.Itoa(a.AlbumID) +func (a *Album) ID() int { + return a.AlbumID } func NewAlbumFromRow(row *sql.Row) (*Album, error) { diff --git a/api/graphql/models/photo.go b/api/graphql/models/photo.go index 3285452c..e8c1bde8 100644 --- a/api/graphql/models/photo.go +++ b/api/graphql/models/photo.go @@ -5,7 +5,6 @@ import ( "net/url" "os" "path" - "strconv" ) type Photo struct { @@ -16,6 +15,10 @@ type Photo struct { ExifId *int } +func (p *Photo) ID() int { + return p.PhotoID +} + type PhotoPurpose string const ( @@ -34,10 +37,6 @@ type PhotoURL struct { ContentType string } -func (p *Photo) ID() string { - return strconv.Itoa(p.PhotoID) -} - func NewPhotoFromRow(row *sql.Row) (*Photo, error) { photo := Photo{} diff --git a/api/graphql/models/share_token.go b/api/graphql/models/share_token.go new file mode 100644 index 00000000..07862f0d --- /dev/null +++ b/api/graphql/models/share_token.go @@ -0,0 +1,21 @@ +package models + +import "time" + +type ShareToken struct { + TokenID int + Value string + OwnerID int + Expire *time.Time + Password *string + AlbumID *int + PhotoID *int +} + +func (share *ShareToken) Token() string { + return share.Value +} + +func (share *ShareToken) ID() int { + return share.TokenID +} diff --git a/api/graphql/models/user.go b/api/graphql/models/user.go index 3ff58aea..68ca6781 100644 --- a/api/graphql/models/user.go +++ b/api/graphql/models/user.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "log" - "strconv" "time" "golang.org/x/crypto/bcrypt" @@ -20,8 +19,8 @@ type User struct { Admin bool } -func (u *User) ID() string { - return strconv.Itoa(u.UserID) +func (u *User) ID() int { + return u.UserID } type AccessToken struct { diff --git a/api/graphql/resolvers/album.go b/api/graphql/resolvers/album.go index 6049835f..eba95396 100644 --- a/api/graphql/resolvers/album.go +++ b/api/graphql/resolvers/album.go @@ -33,7 +33,7 @@ func (r *queryResolver) MyAlbums(ctx context.Context, filter *models.Filter) ([] return albums, nil } -func (r *queryResolver) Album(ctx context.Context, id *string) (*models.Album, error) { +func (r *queryResolver) Album(ctx context.Context, id int) (*models.Album, error) { user := auth.UserFromContext(ctx) if user == nil { return nil, auth.ErrUnauthorized diff --git a/api/graphql/resolvers/photo.go b/api/graphql/resolvers/photo.go index b37c904d..4e0ea90d 100644 --- a/api/graphql/resolvers/photo.go +++ b/api/graphql/resolvers/photo.go @@ -28,7 +28,7 @@ func (r *queryResolver) MyPhotos(ctx context.Context, filter *models.Filter) ([] return models.NewPhotosFromRows(rows) } -func (r *queryResolver) Photo(ctx context.Context, id string) (*models.Photo, error) { +func (r *queryResolver) Photo(ctx context.Context, id int) (*models.Photo, error) { user := auth.UserFromContext(ctx) if user == nil { return nil, auth.ErrUnauthorized diff --git a/api/graphql/resolvers/scanner.go b/api/graphql/resolvers/scanner.go index 030c8213..cd2fb901 100644 --- a/api/graphql/resolvers/scanner.go +++ b/api/graphql/resolvers/scanner.go @@ -11,7 +11,7 @@ import ( func (r *mutationResolver) ScanAll(ctx context.Context) (*models.ScannerResult, error) { panic("Not implemented") } -func (r *mutationResolver) ScanUser(ctx context.Context, userID string) (*models.ScannerResult, error) { +func (r *mutationResolver) ScanUser(ctx context.Context, userID int) (*models.ScannerResult, error) { if err := scanner.ScanUser(r.Database, userID); err != nil { errorMessage := fmt.Sprintf("Error scanning user: %s", err.Error()) return &models.ScannerResult{ diff --git a/api/graphql/resolvers/share_token.go b/api/graphql/resolvers/share_token.go new file mode 100644 index 00000000..8d385a4a --- /dev/null +++ b/api/graphql/resolvers/share_token.go @@ -0,0 +1,120 @@ +package resolvers + +import ( + "context" + "database/sql" + "log" + "time" + + api "github.com/viktorstrate/photoview/api/graphql" + "github.com/viktorstrate/photoview/api/graphql/auth" + "github.com/viktorstrate/photoview/api/graphql/models" + "github.com/viktorstrate/photoview/api/utils" + "golang.org/x/crypto/bcrypt" +) + +type shareTokenResolver struct { + *Resolver +} + +func (r *Resolver) ShareToken() api.ShareTokenResolver { + return &shareTokenResolver{r} +} + +func (r *shareTokenResolver) Owner(ctx context.Context, obj *models.ShareToken) (*models.User, error) { + row := r.Database.QueryRow("SELECT * FROM user WHERE user.user_id = ?", obj.OwnerID) + return models.NewUserFromRow(row) +} + +func (r *shareTokenResolver) Album(ctx context.Context, obj *models.ShareToken) (*models.Album, error) { + row := r.Database.QueryRow("SELECT * FROM album WHERE album.album_id = ?", obj.AlbumID) + album, err := models.NewAlbumFromRow(row) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } else { + return nil, err + } + } + + return album, nil +} + +func (r *shareTokenResolver) Photo(ctx context.Context, obj *models.ShareToken) (*models.Photo, error) { + row := r.Database.QueryRow("SELECT * FROM photo WHERE photo.photo_id = ?", obj.PhotoID) + photo, err := models.NewPhotoFromRow(row) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } else { + return nil, err + } + } + + return photo, nil +} + +func (r *queryResolver) AlbumShares(ctx context.Context, id int, password *string) ([]*models.ShareToken, error) { + log.Println("Query AlbumShares: not implemented") + + tokens := make([]*models.ShareToken, 0) + return tokens, nil +} + +func (r *queryResolver) PhotoShares(ctx context.Context, id int, password *string) ([]*models.ShareToken, error) { + log.Println("Query PhotoShares: not implemented") + + tokens := make([]*models.ShareToken, 0) + return tokens, nil +} + +func (r *mutationResolver) ShareAlbum(ctx context.Context, albumID int, expire *time.Time, password *string) (*models.ShareToken, error) { + user := auth.UserFromContext(ctx) + if user == nil { + return nil, auth.ErrUnauthorized + } + + rows, err := r.Database.Query("SELECT owner_id FROM album WHERE album.album_id = ? AND album.owner_id = ?", albumID, user.UserID) + if err != nil { + return nil, err + } + if rows.Next() == false { + return nil, auth.ErrUnauthorized + } + rows.Close() + + var hashed_password *string = nil + if password != nil { + hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(*password), 12) + if err != nil { + return nil, err + } + hashed_str := string(hashedPassBytes) + hashed_password = &hashed_str + } + + token := utils.GenerateToken() + res, err := r.Database.Exec("INSERT INTO share_token (value, owner_id, expire, password, album_id) VALUES (?, ?, ?, ?, ?)", token, user.UserID, expire, hashed_password, albumID) + if err != nil { + return nil, err + } + + token_id, err := res.LastInsertId() + if err != nil { + return nil, err + } + + return &models.ShareToken{ + TokenID: int(token_id), + Value: token, + OwnerID: user.UserID, + Expire: expire, + Password: password, + AlbumID: &albumID, + PhotoID: nil, + }, nil +} + +func (r *mutationResolver) SharePhoto(ctx context.Context, photoID int, expire *time.Time, password *string) (*models.ShareToken, error) { + panic("not implemented") +} diff --git a/api/graphql/schema-reference.graphql b/api/graphql/schema-reference.graphql index 8095bb2a..00996d7c 100644 --- a/api/graphql/schema-reference.graphql +++ b/api/graphql/schema-reference.graphql @@ -74,9 +74,9 @@ type Photo { type ShareToken { token: ID! - owner: User + owner: User! # Optional expire date - expire: Time + expire: Time! # Optional password # password: String diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index 43495819..123c1194 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -25,12 +25,15 @@ type Query { "List of albums owned by the logged in user" myAlbums(filter: Filter): [Album!]! "Get album by id, user must own the album or be admin" - album(id: ID): Album! + album(id: Int!): Album! "List of photos owned by the logged in user" myPhotos(filter: Filter): [Photo!]! "Get photo by id, user must own the photo or be admin" - photo(id: ID!): Photo! + photo(id: Int!): Photo! + + albumShares(id: Int!, password: String): [ShareToken!]! + photoShares(id: Int!, password: String): [ShareToken!]! } type Mutation { @@ -53,7 +56,12 @@ type Mutation { "Scan all users for new photos" scanAll: ScannerResult! "Scan a single user for new photos" - scanUser(userId: ID!): ScannerResult! + scanUser(userId: Int!): ScannerResult! + + "Generate share token for album" + shareAlbum(albumId: Int!, expire: Time, password: String): ShareToken + "Generate share token for photo" + sharePhoto(photoId: Int!, expire: Time, password: String): ShareToken } type AuthorizeResult { @@ -69,13 +77,28 @@ type ScannerResult { message: String } +"A token used to publicly access an album or photo" +type ShareToken { + id: Int! + token: String! + "The user who created the token" + owner: User! + "Optional expire date" + expire: Time + + "The album this token shares" + album: Album + "The photo this token shares" + photo: Photo +} + "General public information about the site" type SiteInfo { initialSetup: Boolean! } type User { - id: ID! + id: Int! username: String! #albums: [Album] "Local filepath for the user's photos" @@ -85,13 +108,19 @@ type User { } type Album { - id: ID! + id: Int! title: String! + "The photos inside this album" photos(filter: Filter): [Photo!]! + "The albums contained in this album" subAlbums(filter: Filter): [Album!]! + "The album witch contains this album" parentAlbum: Album + "The user who owns this album" owner: User! + "The path on the filesystem of the server, where this album is located" path: String! + "An image in this album used for previewing this album" thumbnail: Photo # shares: [ShareToken] @@ -107,7 +136,7 @@ type PhotoURL { } type Photo { - id: ID! + id: Int! title: String! "Local filepath for the photo" path: String! diff --git a/api/scanner/process_image.go b/api/scanner/process_image.go index 601a7c97..fbfc59ac 100644 --- a/api/scanner/process_image.go +++ b/api/scanner/process_image.go @@ -6,7 +6,6 @@ import ( "image" "image/jpeg" "log" - "math/rand" "os" "path" "strconv" @@ -14,6 +13,7 @@ import ( "github.com/nfnt/resize" "github.com/viktorstrate/photoview/api/graphql/models" + "github.com/viktorstrate/photoview/api/utils" // Image decoders _ "golang.org/x/image/bmp" @@ -68,7 +68,7 @@ func ProcessImage(tx *sql.Tx, photoPath string, albumId int, content_type string photoBaseName := photoName[0 : len(photoName)-len(path.Ext(photoName))] photoBaseExt := path.Ext(photoName) - original_image_name := fmt.Sprintf("%s_%s", photoBaseName, generateToken()) + original_image_name := fmt.Sprintf("%s_%s", photoBaseName, utils.GenerateToken()) original_image_name = strings.ReplaceAll(original_image_name, " ", "_") + photoBaseExt _, err = tx.Exec("INSERT INTO photo_url (photo_id, photo_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)", photo_id, original_image_name, image.Bounds().Max.X, image.Bounds().Max.Y, models.PhotoOriginal, content_type) @@ -96,7 +96,7 @@ func ProcessImage(tx *sql.Tx, photoPath string, albumId int, content_type string } // Save thumbnail as jpg - thumbnail_name := fmt.Sprintf("thumbnail_%s_%s", photoName, generateToken()) + thumbnail_name := fmt.Sprintf("thumbnail_%s_%s", photoName, utils.GenerateToken()) thumbnail_name = strings.ReplaceAll(thumbnail_name, ".", "_") thumbnail_name = strings.ReplaceAll(thumbnail_name, " ", "_") thumbnail_name = thumbnail_name + ".jpg" @@ -118,14 +118,3 @@ func ProcessImage(tx *sql.Tx, photoPath string, albumId int, content_type string return nil } - -func generateToken() string { - const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - const length = 8 - - b := make([]byte, length) - for i := range b { - b[i] = charset[rand.Intn(len(charset))] - } - return string(b) -} diff --git a/api/scanner/scanner.go b/api/scanner/scanner.go index 62cce841..5f9e1b38 100644 --- a/api/scanner/scanner.go +++ b/api/scanner/scanner.go @@ -29,7 +29,7 @@ func (cache *scanner_cache) get_photo_type(path string) *string { return &photo_type } -func ScanUser(database *sql.DB, userId string) error { +func ScanUser(database *sql.DB, userId int) error { row := database.QueryRow("SELECT * FROM user WHERE user_id = ?", userId) user, err := models.NewUserFromRow(row) diff --git a/api/utils/utils.go b/api/utils/utils.go new file mode 100644 index 00000000..710fd372 --- /dev/null +++ b/api/utils/utils.go @@ -0,0 +1,14 @@ +package utils + +import "math/rand" + +func GenerateToken() string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + const length = 8 + + b := make([]byte, length) + for i := range b { + b[i] = charset[rand.Intn(len(charset))] + } + return string(b) +} diff --git a/ui/src/Pages/AlbumPage/AlbumPage.js b/ui/src/Pages/AlbumPage/AlbumPage.js index 3f928fa0..76ae3c78 100644 --- a/ui/src/Pages/AlbumPage/AlbumPage.js +++ b/ui/src/Pages/AlbumPage/AlbumPage.js @@ -10,7 +10,7 @@ import AlbumTitle from '../../components/AlbumTitle' import AlbumGallery from '../../components/albumGallery/AlbumGallery' const albumQuery = gql` - query albumQuery($id: ID!) { + query albumQuery($id: Int!) { album(id: $id) { id title diff --git a/ui/src/Pages/SettingsPage/UserRow.js b/ui/src/Pages/SettingsPage/UserRow.js index 9e6a0b90..b74408a7 100644 --- a/ui/src/Pages/SettingsPage/UserRow.js +++ b/ui/src/Pages/SettingsPage/UserRow.js @@ -14,7 +14,7 @@ import gql from 'graphql-tag' const updateUserMutation = gql` mutation updateUser( - $id: ID! + $id: Int! $username: String $rootPath: String $admin: Boolean @@ -34,7 +34,7 @@ const updateUserMutation = gql` ` const deleteUserMutation = gql` - mutation deleteUser($id: ID!) { + mutation deleteUser($id: Int!) { deleteUser(id: $id) { id username diff --git a/ui/src/Pages/SharePage/SharePage.js b/ui/src/Pages/SharePage/SharePage.js index 0671ae76..0eeedd78 100644 --- a/ui/src/Pages/SharePage/SharePage.js +++ b/ui/src/Pages/SharePage/SharePage.js @@ -7,7 +7,7 @@ import { Query } from 'react-apollo' import gql from 'graphql-tag' const tokenQuery = gql` - query SharePageToken($token: ID!) { + query SharePageToken($token: Int!) { shareToken(token: $token) { token album { diff --git a/ui/src/components/sidebar/AlbumSidebar.js b/ui/src/components/sidebar/AlbumSidebar.js index 98372acf..c76ecabf 100644 --- a/ui/src/components/sidebar/AlbumSidebar.js +++ b/ui/src/components/sidebar/AlbumSidebar.js @@ -5,7 +5,7 @@ import gql from 'graphql-tag' import SidebarShare from './Sharing' const albumQuery = gql` - query getAlbumSidebar($id: ID!) { + query getAlbumSidebar($id: Int!) { album(id: $id) { id title diff --git a/ui/src/components/sidebar/PhotoSidebar.js b/ui/src/components/sidebar/PhotoSidebar.js index 6ecb9d11..253316c0 100644 --- a/ui/src/components/sidebar/PhotoSidebar.js +++ b/ui/src/components/sidebar/PhotoSidebar.js @@ -9,7 +9,7 @@ import SidebarShare from './Sharing' import SidebarDownload from './SidebarDownload' const photoQuery = gql` - query sidebarPhoto($id: ID!) { + query sidebarPhoto($id: Int!) { photo(id: $id) { id title diff --git a/ui/src/components/sidebar/Sharing.js b/ui/src/components/sidebar/Sharing.js index c49d33e9..b400704a 100644 --- a/ui/src/components/sidebar/Sharing.js +++ b/ui/src/components/sidebar/Sharing.js @@ -6,7 +6,7 @@ import { Table, Button, Dropdown } from 'semantic-ui-react' import copy from 'copy-to-clipboard' const sharePhotoQuery = gql` - query sidbarGetPhotoShares($id: ID!) { + query sidbarGetPhotoShares($id: Int!) { photoShares(id: $id) { token } @@ -14,7 +14,7 @@ const sharePhotoQuery = gql` ` const shareAlbumQuery = gql` - query sidbarGetAlbumShares($id: ID!) { + query sidbarGetAlbumShares($id: Int!) { albumShares(id: $id) { token } @@ -23,7 +23,7 @@ const shareAlbumQuery = gql` const addPhotoShareMutation = gql` mutation sidebarPhotoAddShare( - $id: ID! + $id: Int! $password: String $expire: _Neo4jDateInput ) { @@ -35,7 +35,7 @@ const addPhotoShareMutation = gql` const addAlbumShareMutation = gql` mutation sidebarAlbumAddShare( - $id: ID! + $id: Int! $password: String $expire: _Neo4jDateInput ) { diff --git a/ui/src/components/sidebar/SidebarDownload.js b/ui/src/components/sidebar/SidebarDownload.js index d4844553..cd9bccbb 100644 --- a/ui/src/components/sidebar/SidebarDownload.js +++ b/ui/src/components/sidebar/SidebarDownload.js @@ -6,7 +6,7 @@ import gql from 'graphql-tag' import download from 'downloadjs' const downloadQuery = gql` - query sidebarDownloadQuery($photoId: ID!) { + query sidebarDownloadQuery($photoId: Int!) { photo(id: $photoId) { id downloads {