restructure user <-> albums relations in database

This commit is contained in:
viktorstrate
2020-12-20 17:10:00 +01:00
parent ae8e764539
commit 732ccd05ab
9 changed files with 193 additions and 308 deletions

View File

@@ -60,6 +60,7 @@ func MigrateDatabase(db *gorm.DB) error {
&models.MediaEXIF{},
&models.VideoMetadata{},
&models.ShareToken{},
&models.UserMediaData{},
)
return nil

View File

@@ -9,6 +9,7 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -64,16 +65,19 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
@@ -103,6 +107,7 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -115,20 +115,19 @@ type ComplexityRoot struct {
Mutation struct {
AuthorizeUser func(childComplexity int, username string, password string) int
CreateUser func(childComplexity int, username string, rootPath string, password *string, admin bool) int
CreateUser func(childComplexity int, username string, password *string, admin bool) int
DeleteShareToken func(childComplexity int, token string) int
DeleteUser func(childComplexity int, id int) int
FavoriteMedia func(childComplexity int, mediaID int, favorite bool) int
InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int
ProtectShareToken func(childComplexity int, token string, password *string) int
RegisterUser func(childComplexity int, username string, password string, rootPath string) int
ScanAll func(childComplexity int) int
ScanUser func(childComplexity int, userID int) int
SetPeriodicScanInterval func(childComplexity int, interval int) int
SetScannerConcurrentWorkers func(childComplexity int, workers int) int
ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int
ShareMedia func(childComplexity int, mediaID int, expire *time.Time, password *string) int
UpdateUser func(childComplexity int, id int, username *string, rootPath *string, password *string, admin *bool) int
UpdateUser func(childComplexity int, id int, username *string, password *string, admin *bool) int
}
Notification struct {
@@ -193,8 +192,8 @@ type ComplexityRoot struct {
User struct {
Admin func(childComplexity int) int
Albums func(childComplexity int) int
ID func(childComplexity int) int
RootPath func(childComplexity int) int
Username func(childComplexity int) int
}
@@ -225,12 +224,13 @@ type MediaResolver interface {
HighRes(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
VideoWeb(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
Favorite(ctx context.Context, obj *models.Media) (bool, error)
Shares(ctx context.Context, obj *models.Media) ([]*models.ShareToken, error)
Downloads(ctx context.Context, obj *models.Media) ([]*models.MediaDownload, error)
}
type MutationResolver interface {
AuthorizeUser(ctx context.Context, username string, password string) (*models.AuthorizeResult, error)
RegisterUser(ctx context.Context, username string, password string, rootPath string) (*models.AuthorizeResult, error)
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 int) (*models.ScannerResult, error)
@@ -239,8 +239,8 @@ type MutationResolver interface {
DeleteShareToken(ctx context.Context, token string) (*models.ShareToken, error)
ProtectShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error)
FavoriteMedia(ctx context.Context, mediaID int, favorite bool) (*models.Media, error)
UpdateUser(ctx context.Context, id int, username *string, rootPath *string, password *string, admin *bool) (*models.User, error)
CreateUser(ctx context.Context, username string, rootPath string, password *string, admin bool) (*models.User, error)
UpdateUser(ctx context.Context, id int, username *string, password *string, admin *bool) (*models.User, error)
CreateUser(ctx context.Context, username string, password *string, admin bool) (*models.User, error)
DeleteUser(ctx context.Context, id int) (*models.User, error)
SetPeriodicScanInterval(ctx context.Context, interval int) (int, error)
SetScannerConcurrentWorkers(ctx context.Context, workers int) (int, error)
@@ -622,7 +622,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return 0, false
}
return e.complexity.Mutation.CreateUser(childComplexity, args["username"].(string), args["rootPath"].(string), args["password"].(*string), args["admin"].(bool)), true
return e.complexity.Mutation.CreateUser(childComplexity, args["username"].(string), args["password"].(*string), args["admin"].(bool)), true
case "Mutation.deleteShareToken":
if e.complexity.Mutation.DeleteShareToken == nil {
@@ -684,18 +684,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.ProtectShareToken(childComplexity, args["token"].(string), args["password"].(*string)), true
case "Mutation.registerUser":
if e.complexity.Mutation.RegisterUser == nil {
break
}
args, err := ec.field_Mutation_registerUser_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.RegisterUser(childComplexity, args["username"].(string), args["password"].(string), args["rootPath"].(string)), true
case "Mutation.scanAll":
if e.complexity.Mutation.ScanAll == nil {
break
@@ -773,7 +761,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return 0, false
}
return e.complexity.Mutation.UpdateUser(childComplexity, args["id"].(int), args["username"].(*string), args["rootPath"].(*string), args["password"].(*string), args["admin"].(*bool)), true
return e.complexity.Mutation.UpdateUser(childComplexity, args["id"].(int), args["username"].(*string), args["password"].(*string), args["admin"].(*bool)), true
case "Notification.content":
if e.complexity.Notification.Content == nil {
@@ -1100,6 +1088,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.User.Admin(childComplexity), true
case "User.albums":
if e.complexity.User.Albums == nil {
break
}
return e.complexity.User.Albums(childComplexity), true
case "User.id":
if e.complexity.User.ID == nil {
break
@@ -1107,13 +1102,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.User.ID(childComplexity), true
case "User.rootPath":
if e.complexity.User.RootPath == nil {
break
}
return e.complexity.User.RootPath(childComplexity), true
case "User.username":
if e.complexity.User.Username == nil {
break
@@ -1332,13 +1320,6 @@ type Query {
type Mutation {
authorizeUser(username: String!, password: String!): AuthorizeResult!
"Registers a new user, must be admin to call"
registerUser(
username: String!
password: String!
rootPath: String!
): AuthorizeResult!
"Registers the initial user, can only be called if initialSetup from SiteInfo is true"
initialSetupWizard(
username: String!
@@ -1366,13 +1347,11 @@ type Mutation {
updateUser(
id: ID!
username: String
rootPath: String
password: String
admin: Boolean
): User @isAdmin
createUser(
username: String!
rootPath: String!
password: String
admin: Boolean!
): User @isAdmin
@@ -1454,8 +1433,9 @@ type User {
id: ID!
username: String!
#albums: [Album]
"Local filepath for the user's photos"
rootPath: String! @isAdmin
# rootPath: String! @isAdmin
"Top level albums owned by this user"
albums: [Album!]! @isAdmin
admin: Boolean!
#shareTokens: [ShareToken]
}
@@ -1653,33 +1633,24 @@ func (ec *executionContext) field_Mutation_createUser_args(ctx context.Context,
}
}
args["username"] = arg0
var arg1 string
if tmp, ok := rawArgs["rootPath"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("rootPath"))
arg1, err = ec.unmarshalNString2string(ctx, tmp)
if err != nil {
return nil, err
}
}
args["rootPath"] = arg1
var arg2 *string
var arg1 *string
if tmp, ok := rawArgs["password"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password"))
arg2, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
if err != nil {
return nil, err
}
}
args["password"] = arg2
var arg3 bool
args["password"] = arg1
var arg2 bool
if tmp, ok := rawArgs["admin"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("admin"))
arg3, err = ec.unmarshalNBoolean2bool(ctx, tmp)
arg2, err = ec.unmarshalNBoolean2bool(ctx, tmp)
if err != nil {
return nil, err
}
}
args["admin"] = arg3
args["admin"] = arg2
return args, nil
}
@@ -1794,39 +1765,6 @@ func (ec *executionContext) field_Mutation_protectShareToken_args(ctx context.Co
return args, nil
}
func (ec *executionContext) field_Mutation_registerUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 string
if tmp, ok := rawArgs["username"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("username"))
arg0, err = ec.unmarshalNString2string(ctx, tmp)
if err != nil {
return nil, err
}
}
args["username"] = arg0
var arg1 string
if tmp, ok := rawArgs["password"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password"))
arg1, err = ec.unmarshalNString2string(ctx, tmp)
if err != nil {
return nil, err
}
}
args["password"] = arg1
var arg2 string
if tmp, ok := rawArgs["rootPath"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("rootPath"))
arg2, err = ec.unmarshalNString2string(ctx, tmp)
if err != nil {
return nil, err
}
}
args["rootPath"] = arg2
return args, nil
}
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{}{}
@@ -1960,32 +1898,23 @@ func (ec *executionContext) field_Mutation_updateUser_args(ctx context.Context,
}
args["username"] = arg1
var arg2 *string
if tmp, ok := rawArgs["rootPath"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("rootPath"))
if tmp, ok := rawArgs["password"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password"))
arg2, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
if err != nil {
return nil, err
}
}
args["rootPath"] = arg2
var arg3 *string
if tmp, ok := rawArgs["password"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password"))
arg3, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
if err != nil {
return nil, err
}
}
args["password"] = arg3
var arg4 *bool
args["password"] = arg2
var arg3 *bool
if tmp, ok := rawArgs["admin"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("admin"))
arg4, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp)
arg3, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp)
if err != nil {
return nil, err
}
}
args["admin"] = arg4
args["admin"] = arg3
return args, nil
}
@@ -3011,14 +2940,14 @@ func (ec *executionContext) _Media_favorite(ctx context.Context, field graphql.C
Object: "Media",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Favorite, nil
return ec.resolvers.Media().Favorite(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
@@ -3782,48 +3711,6 @@ func (ec *executionContext) _Mutation_authorizeUser(ctx context.Context, field g
return ec.marshalNAuthorizeResult2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAuthorizeResult(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_registerUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_registerUser_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().RegisterUser(rctx, args["username"].(string), args["password"].(string), args["rootPath"].(string))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*models.AuthorizeResult)
fc.Result = res
return ec.marshalNAuthorizeResult2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAuthorizeResult(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_initialSetupWizard(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -4181,7 +4068,7 @@ func (ec *executionContext) _Mutation_updateUser(ctx context.Context, field grap
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.Mutation().UpdateUser(rctx, args["id"].(int), args["username"].(*string), args["rootPath"].(*string), args["password"].(*string), args["admin"].(*bool))
return ec.resolvers.Mutation().UpdateUser(rctx, args["id"].(int), args["username"].(*string), args["password"].(*string), args["admin"].(*bool))
}
directive1 := func(ctx context.Context) (interface{}, error) {
if ec.directives.IsAdmin == nil {
@@ -4240,7 +4127,7 @@ func (ec *executionContext) _Mutation_createUser(ctx context.Context, field grap
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.Mutation().CreateUser(rctx, args["username"].(string), args["rootPath"].(string), args["password"].(*string), args["admin"].(bool))
return ec.resolvers.Mutation().CreateUser(rctx, args["username"].(string), args["password"].(*string), args["admin"].(bool))
}
directive1 := func(ctx context.Context) (interface{}, error) {
if ec.directives.IsAdmin == nil {
@@ -6031,7 +5918,7 @@ func (ec *executionContext) _User_username(ctx context.Context, field graphql.Co
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _User_rootPath(ctx context.Context, field graphql.CollectedField, obj *models.User) (ret graphql.Marshaler) {
func (ec *executionContext) _User_albums(ctx context.Context, field graphql.CollectedField, obj *models.User) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
@@ -6050,7 +5937,7 @@ func (ec *executionContext) _User_rootPath(ctx context.Context, field graphql.Co
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 obj.RootPath, nil
return obj.Albums, nil
}
directive1 := func(ctx context.Context) (interface{}, error) {
if ec.directives.IsAdmin == nil {
@@ -6066,10 +5953,10 @@ func (ec *executionContext) _User_rootPath(ctx context.Context, field graphql.Co
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(string); ok {
if data, ok := tmp.([]models.Album); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be string`, tmp)
return nil, fmt.Errorf(`unexpected type %T from directive, should be []github.com/photoview/photoview/api/graphql/models.Album`, tmp)
})
if err != nil {
ec.Error(ctx, err)
@@ -6081,9 +5968,9 @@ func (ec *executionContext) _User_rootPath(ctx context.Context, field graphql.Co
}
return graphql.Null
}
res := resTmp.(string)
res := resTmp.([]models.Album)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
return ec.marshalNAlbum2ᚕgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbumᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _User_admin(ctx context.Context, field graphql.CollectedField, obj *models.User) (ret graphql.Marshaler) {
@@ -7809,10 +7696,19 @@ func (ec *executionContext) _Media(ctx context.Context, sel ast.SelectionSet, ob
case "videoMetadata":
out.Values[i] = ec._Media_videoMetadata(ctx, field, obj)
case "favorite":
out.Values[i] = ec._Media_favorite(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
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._Media_favorite(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "type":
out.Values[i] = ec._Media_type(ctx, field, obj)
if out.Values[i] == graphql.Null {
@@ -8003,11 +7899,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
invalids++
}
case "registerUser":
out.Values[i] = ec._Mutation_registerUser(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "initialSetupWizard":
out.Values[i] = ec._Mutation_initialSetupWizard(ctx, field)
case "scanAll":
@@ -8530,8 +8421,8 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null {
invalids++
}
case "rootPath":
out.Values[i] = ec._User_rootPath(ctx, field, obj)
case "albums":
out.Values[i] = ec._User_albums(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
@@ -8857,6 +8748,43 @@ func (ec *executionContext) marshalNAlbum2githubᚗcomᚋphotoviewᚋphotoview
return ec._Album(ctx, sel, &v)
}
func (ec *executionContext) marshalNAlbum2ᚕgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbumᚄ(ctx context.Context, sel ast.SelectionSet, v []models.Album) 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
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
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.marshalNAlbum2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
return ret
}
func (ec *executionContext) marshalNAlbum2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbumᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.Album) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup

View File

@@ -13,17 +13,17 @@ import (
type Media struct {
Model
Title string `gorm:"not null"`
Path string `gorm:"not null"`
PathHash string `gorm:"not null"`
AlbumID int `gorm:"not null"`
Album Album
ExifID *int
Exif *MediaEXIF
MediaURL []MediaURL
DateShot time.Time `gorm:"not null"`
DateImported time.Time `gorm:"not null"`
Favorite bool `gorm:"not null, default:false"`
Title string `gorm:"not null"`
Path string `gorm:"not null"`
PathHash string `gorm:"not null"`
AlbumID int `gorm:"not null"`
Album Album
ExifID *int
Exif *MediaEXIF
MediaURL []MediaURL
DateShot time.Time `gorm:"not null"`
DateImported time.Time `gorm:"not null"`
// Favorite bool `gorm:"not null, default:false"`
Type MediaType `gorm:"not null"`
VideoMetadataID *int
VideoMetadata *VideoMetadata

View File

@@ -16,13 +16,17 @@ type User struct {
Model
Username string `gorm:"unique,size:128"`
Password *string `gorm:"size:256`
RootPath string `gorm:"size:512`
Admin bool `gorm:"default:false"`
// RootPath string `gorm:"size:512`
Albums []Album `gorm:"many2many:user_albums"`
Admin bool `gorm:"default:false"`
}
// func (u *User) ID() int {
// return u.UserID
// }
type UserMediaData struct {
ModelTimestamps
UserID int `gorm:"primaryKey;autoIncrement:false"`
MediaID int `gorm:"primaryKey;autoIncrement:false"`
Favorite bool `gorm:"not null;default:false"`
}
type AccessToken struct {
Model
@@ -34,35 +38,7 @@ type AccessToken struct {
var ErrorInvalidUserCredentials = errors.New("invalid credentials")
// func NewUserFromRow(row *sql.Row) (*User, error) {
// user := User{}
// if err := row.Scan(&user.UserID, &user.Username, &user.Password, &user.RootPath, &user.Admin); err != nil {
// return nil, errors.Wrap(err, "failed to scan user from database")
// }
// return &user, nil
// }
// func NewUsersFromRows(rows *sql.Rows) ([]*User, error) {
// users := make([]*User, 0)
// for rows.Next() {
// var user User
// if err := rows.Scan(&user.UserID, &user.Username, &user.Password, &user.RootPath, &user.Admin); err != nil {
// return nil, errors.Wrap(err, "failed to scan users from database")
// }
// users = append(users, &user)
// }
// rows.Close()
// return users, nil
// }
func AuthorizeUser(db *gorm.DB, username string, password string) (*User, error) {
// row := database.QueryRow("SELECT * FROM user WHERE username = ?", username)
var user User
result := db.Where("username = ?", username).First(&user)
@@ -100,15 +76,15 @@ func ValidRootPath(rootPath string) bool {
return true
}
func RegisterUser(db *gorm.DB, username string, password *string, rootPath string, admin bool) (*User, error) {
if !ValidRootPath(rootPath) {
return nil, ErrorInvalidRootPath
}
func RegisterUser(db *gorm.DB, username string, password *string, admin bool) (*User, error) {
// if !ValidRootPath(rootPath) {
// return nil, ErrorInvalidRootPath
// }
user := User{
Username: username,
RootPath: rootPath,
Admin: admin,
// RootPath: rootPath,
Admin: admin,
}
if password != nil {
@@ -163,27 +139,12 @@ func (user *User) GenerateAccessToken(db *gorm.DB) (*AccessToken, error) {
func VerifyTokenAndGetUser(db *gorm.DB, token string) (*User, error) {
// row := database.QueryRow("SELECT (user_id) FROM access_token WHERE expire > ? AND value = ?", now, token)
var accessToken AccessToken
result := db.Where("expire > ? AND value = ?", time.Now(), token).First(&accessToken)
if result.Error != nil {
return nil, result.Error
}
// var userId string
// if err := row.Scan(&userId); err != nil {
// log.Println(err.Error())
// return nil, err
// }
// row = db.QueryRow("SELECT * FROM user WHERE user_id = ?", userId)
// user, err := NewUserFromRow(row)
// if err != nil {
// return nil, err
// }
var user User
result = db.First(&user, accessToken.UserID)
if result.Error != nil {

View File

@@ -9,6 +9,7 @@ import (
"github.com/photoview/photoview/api/scanner"
"github.com/pkg/errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func (r *queryResolver) MyMedia(ctx context.Context, filter *models.Filter) ([]*models.Media, error) {
@@ -182,20 +183,45 @@ func (r *mediaResolver) VideoWeb(ctx context.Context, media *models.Media) (*mod
return &url, nil
}
func (r *mediaResolver) Favorite(ctx context.Context, media *models.Media) (bool, error) {
user := auth.UserFromContext(ctx)
if user == nil {
return false, auth.ErrUnauthorized
}
userMediaData := models.UserMediaData{
UserID: user.ID,
MediaID: media.ID,
Favorite: false,
}
if err := r.Database.FirstOrInit(&userMediaData).Error; err != nil {
return false, errors.Wrapf(err, "get user media data from database (user: %d, media: %d)", user.ID, media.ID)
}
return userMediaData.Favorite, nil
}
func (r *mutationResolver) FavoriteMedia(ctx context.Context, mediaID int, favorite bool) (*models.Media, error) {
user := auth.UserFromContext(ctx)
var media models.Media
if err := r.Database.Joins("Album").Where("Album.owner_id = ?", user.ID).First(&media, mediaID).Error; err != nil {
return nil, err
if user == nil {
return nil, auth.ErrUnauthorized
}
media.Favorite = favorite
userMediaData := models.UserMediaData{
UserID: user.ID,
MediaID: mediaID,
Favorite: favorite,
}
if err := r.Database.Save(&media).Error; err != nil {
return nil, errors.Wrap(err, "failed to update media favorite on database")
if err := r.Database.Clauses(clause.OnConflict{UpdateAll: true}).Create(&userMediaData).Error; err != nil {
return nil, errors.Wrapf(err, "update user favorite media in database")
}
var media models.Media
if err := r.Database.First(&media, mediaID).Error; err != nil {
return nil, errors.Wrap(err, "get media from database after favorite update")
}
return &media, nil

View File

@@ -61,38 +61,6 @@ func (r *mutationResolver) AuthorizeUser(ctx context.Context, username string, p
Token: &token.Value,
}, nil
}
func (r *mutationResolver) RegisterUser(ctx context.Context, username string, password string, rootPath string) (*models.AuthorizeResult, error) {
var token *models.AccessToken
transactionError := r.Database.Transaction(func(tx *gorm.DB) error {
user, err := models.RegisterUser(tx, username, &password, rootPath, false)
if err != nil {
return err
}
token, err = user.GenerateAccessToken(tx)
if err != nil {
tx.Rollback()
return err
}
return nil
})
if transactionError != nil {
return &models.AuthorizeResult{
Success: false,
Status: transactionError.Error(),
}, transactionError
}
return &models.AuthorizeResult{
Success: true,
Status: "ok",
Token: &token.Value,
}, nil
}
func (r *mutationResolver) InitialSetupWizard(ctx context.Context, username string, password string, rootPath string) (*models.AuthorizeResult, error) {
siteInfo, err := models.GetSiteInfo(r.Database)
@@ -111,7 +79,7 @@ func (r *mutationResolver) InitialSetupWizard(ctx context.Context, username stri
return err
}
user, err := models.RegisterUser(tx, username, &password, rootPath, true)
user, err := models.RegisterUser(tx, username, &password, true)
if err != nil {
return err
}
@@ -139,9 +107,9 @@ func (r *mutationResolver) InitialSetupWizard(ctx context.Context, username stri
}
// Admin queries
func (r *mutationResolver) UpdateUser(ctx context.Context, id int, username *string, rootPath *string, password *string, admin *bool) (*models.User, error) {
func (r *mutationResolver) UpdateUser(ctx context.Context, id int, username *string, password *string, admin *bool) (*models.User, error) {
if username == nil && rootPath == nil && password == nil && admin == nil {
if username == nil && password == nil && admin == nil {
return nil, errors.New("no updates requested")
}
@@ -154,10 +122,6 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, id int, username *str
user.Username = *username
}
if rootPath != nil {
user.RootPath = *rootPath
}
if password != nil {
hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(*password), 12)
if err != nil {
@@ -179,13 +143,13 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, id int, username *str
return &user, nil
}
func (r *mutationResolver) CreateUser(ctx context.Context, username string, rootPath string, password *string, admin bool) (*models.User, error) {
func (r *mutationResolver) CreateUser(ctx context.Context, username string, password *string, admin bool) (*models.User, error) {
var user *models.User
transactionError := r.Database.Transaction(func(tx *gorm.DB) error {
var err error
user, err = models.RegisterUser(tx, username, password, rootPath, admin)
user, err = models.RegisterUser(tx, username, password, admin)
if err != nil {
return err
}

View File

@@ -58,13 +58,6 @@ type Query {
type Mutation {
authorizeUser(username: String!, password: String!): AuthorizeResult!
"Registers a new user, must be admin to call"
registerUser(
username: String!
password: String!
rootPath: String!
): AuthorizeResult!
"Registers the initial user, can only be called if initialSetup from SiteInfo is true"
initialSetupWizard(
username: String!
@@ -92,13 +85,11 @@ type Mutation {
updateUser(
id: ID!
username: String
rootPath: String
password: String
admin: Boolean
): User @isAdmin
createUser(
username: String!
rootPath: String!
password: String
admin: Boolean!
): User @isAdmin
@@ -180,8 +171,9 @@ type User {
id: ID!
username: String!
#albums: [Album]
"Local filepath for the user's photos"
rootPath: String! @isAdmin
# rootPath: String! @isAdmin
"Top level albums owned by this user"
albums: [Album!]! @isAdmin
admin: Boolean!
#shareTokens: [ShareToken]
}

View File

@@ -17,29 +17,37 @@ import (
func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScannerCache) ([]*models.Album, []error) {
// Check if user directory exists on the file system
if _, err := os.Stat(user.RootPath); err != nil {
if os.IsNotExist(err) {
return nil, []error{errors.Errorf("Photo directory for user '%s' does not exist '%s'\n", user.Username, user.RootPath)}
} else {
return nil, []error{errors.Errorf("Could not read photo directory for user '%s': %s\n", user.Username, user.RootPath)}
}
var userRootAlbums []*models.Album
if err := db.Model(&user).Association("Albums").Find(&userRootAlbums); err != nil {
return nil, []error{errors.Wrapf(err, "get albums of user (%s)", user.Username)}
}
scanErrors := make([]error, 0)
type scanInfo struct {
path string
parentID *int
}
scanQueue := list.New()
scanQueue.PushBack(scanInfo{
path: user.RootPath,
parentID: nil,
})
for _, album := range userRootAlbums {
// Check if user album directory exists on the file system
if _, err := os.Stat(album.Path); err != nil {
if os.IsNotExist(err) {
scanErrors = append(scanErrors, errors.Errorf("Album directory for user '%s' does not exist '%s'\n", user.Username, album.Path))
} else {
scanErrors = append(scanErrors, errors.Errorf("Could not read album directory for user '%s': %s\n", user.Username, album.Path))
}
} else {
scanQueue.PushBack(scanInfo{
path: album.Path,
parentID: nil,
})
}
}
userAlbums := make([]*models.Album, 0)
albumErrors := make([]error, 0)
// newPhotos := make([]*models.Photo, 0)
for scanQueue.Front() != nil {
albumInfo := scanQueue.Front().Value.(scanInfo)
@@ -51,7 +59,7 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScanner
// Read path
dirContent, err := ioutil.ReadDir(albumPath)
if err != nil {
albumErrors = append(albumErrors, errors.Wrapf(err, "read directory (%s)", albumPath))
scanErrors = append(scanErrors, errors.Wrapf(err, "read directory (%s)", albumPath))
continue
}
@@ -81,7 +89,7 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScanner
})
if transErr != nil {
albumErrors = append(albumErrors, errors.Wrap(transErr, "begin database transaction"))
scanErrors = append(scanErrors, errors.Wrap(transErr, "begin database transaction"))
continue
}
@@ -104,9 +112,9 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScanner
}
deleteErrors := deleteOldUserAlbums(db, userAlbums, user)
albumErrors = append(albumErrors, deleteErrors...)
scanErrors = append(scanErrors, deleteErrors...)
return userAlbums, albumErrors
return userAlbums, scanErrors
}
func directoryContainsPhotos(rootPath string, cache *AlbumScannerCache) bool {