Merge pull request #157 from photoview/v2/user-multi-paths
Multiple and shared user paths
This commit is contained in:
commit
8700f98fd2
|
@ -60,6 +60,7 @@ func MigrateDatabase(db *gorm.DB) error {
|
|||
&models.MediaEXIF{},
|
||||
&models.VideoMetadata{},
|
||||
&models.ShareToken{},
|
||||
&models.UserMediaData{},
|
||||
)
|
||||
|
||||
return nil
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -24,6 +24,9 @@ models:
|
|||
model: github.com/99designs/gqlgen/graphql.IntID
|
||||
User:
|
||||
model: github.com/photoview/photoview/api/graphql/models.User
|
||||
fields:
|
||||
albums:
|
||||
resolver: true
|
||||
Media:
|
||||
model: github.com/photoview/photoview/api/graphql/models.Media
|
||||
MediaURL:
|
||||
|
|
|
@ -44,6 +44,7 @@ type ResolverRoot interface {
|
|||
Query() QueryResolver
|
||||
ShareToken() ShareTokenResolver
|
||||
Subscription() SubscriptionResolver
|
||||
User() UserResolver
|
||||
}
|
||||
|
||||
type DirectiveRoot struct {
|
||||
|
@ -115,20 +116,21 @@ 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
|
||||
UserAddRootPath func(childComplexity int, id int, rootPath string) int
|
||||
UserRemoveRootAlbum func(childComplexity int, userID int, albumID int) int
|
||||
}
|
||||
|
||||
Notification struct {
|
||||
|
@ -193,8 +195,9 @@ 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
|
||||
RootAlbums func(childComplexity int) int
|
||||
Username func(childComplexity int) int
|
||||
}
|
||||
|
||||
|
@ -216,6 +219,8 @@ type AlbumResolver interface {
|
|||
Media(ctx context.Context, obj *models.Album, filter *models.Filter, onlyFavorites *bool) ([]*models.Media, error)
|
||||
SubAlbums(ctx context.Context, obj *models.Album, filter *models.Filter) ([]*models.Album, error)
|
||||
|
||||
Owner(ctx context.Context, obj *models.Album) (*models.User, error)
|
||||
|
||||
Thumbnail(ctx context.Context, obj *models.Album) (*models.Media, error)
|
||||
Path(ctx context.Context, obj *models.Album) ([]*models.Album, error)
|
||||
Shares(ctx context.Context, obj *models.Album) ([]*models.ShareToken, error)
|
||||
|
@ -225,12 +230,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,9 +245,11 @@ 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)
|
||||
UserAddRootPath(ctx context.Context, id int, rootPath string) (*models.Album, error)
|
||||
UserRemoveRootAlbum(ctx context.Context, userID int, albumID int) (*models.Album, error)
|
||||
SetPeriodicScanInterval(ctx context.Context, interval int) (int, error)
|
||||
SetScannerConcurrentWorkers(ctx context.Context, workers int) (int, error)
|
||||
}
|
||||
|
@ -266,6 +274,10 @@ type ShareTokenResolver interface {
|
|||
type SubscriptionResolver interface {
|
||||
Notification(ctx context.Context) (<-chan *models.Notification, error)
|
||||
}
|
||||
type UserResolver interface {
|
||||
Albums(ctx context.Context, obj *models.User) ([]*models.Album, error)
|
||||
RootAlbums(ctx context.Context, obj *models.User) ([]*models.Album, error)
|
||||
}
|
||||
|
||||
type executableSchema struct {
|
||||
resolvers ResolverRoot
|
||||
|
@ -622,7 +634,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 +696,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 +773,31 @@ 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 "Mutation.userAddRootPath":
|
||||
if e.complexity.Mutation.UserAddRootPath == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_userAddRootPath_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.UserAddRootPath(childComplexity, args["id"].(int), args["rootPath"].(string)), true
|
||||
|
||||
case "Mutation.userRemoveRootAlbum":
|
||||
if e.complexity.Mutation.UserRemoveRootAlbum == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_userRemoveRootAlbum_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.UserRemoveRootAlbum(childComplexity, args["userId"].(int), args["albumId"].(int)), true
|
||||
|
||||
case "Notification.content":
|
||||
if e.complexity.Notification.Content == nil {
|
||||
|
@ -1100,6 +1124,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,12 +1138,12 @@ 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 {
|
||||
case "User.rootAlbums":
|
||||
if e.complexity.User.RootAlbums == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.User.RootPath(childComplexity), true
|
||||
return e.complexity.User.RootAlbums(childComplexity), true
|
||||
|
||||
case "User.username":
|
||||
if e.complexity.User.Username == nil {
|
||||
|
@ -1332,13 +1363,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,18 +1390,20 @@ 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
|
||||
deleteUser(id: ID!): User @isAdmin
|
||||
|
||||
"Add a root path from where to look for media for the given user"
|
||||
userAddRootPath(id: ID!, rootPath: String!): Album @isAdmin
|
||||
userRemoveRootAlbum(userId: ID!, albumId: ID!): Album @isAdmin
|
||||
|
||||
"""
|
||||
Set how often, in seconds, the server should automatically scan for new media,
|
||||
a value of 0 will disable periodic scans
|
||||
|
@ -1454,8 +1480,11 @@ type User {
|
|||
id: ID!
|
||||
username: String!
|
||||
#albums: [Album]
|
||||
"Local filepath for the user's photos"
|
||||
rootPath: String! @isAdmin
|
||||
# rootPath: String! @isAdmin
|
||||
"All albums owned by this user"
|
||||
albums: [Album!]! @isAdmin
|
||||
"Top level albums owned by this user"
|
||||
rootAlbums: [Album!]! @isAdmin
|
||||
admin: Boolean!
|
||||
#shareTokens: [ShareToken]
|
||||
}
|
||||
|
@ -1653,33 +1682,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 +1814,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 +1947,71 @@ 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
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_userAddRootPath_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 {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
|
||||
arg0, err = ec.unmarshalNID2int(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["id"] = 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
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_userRemoveRootAlbum_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["userId"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userId"))
|
||||
arg0, err = ec.unmarshalNID2int(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["userId"] = arg0
|
||||
var arg1 int
|
||||
if tmp, ok := rawArgs["albumId"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("albumId"))
|
||||
arg1, err = ec.unmarshalNID2int(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["albumId"] = arg1
|
||||
return args, nil
|
||||
}
|
||||
|
||||
|
@ -2437,14 +2463,14 @@ func (ec *executionContext) _Album_owner(ctx context.Context, field graphql.Coll
|
|||
Object: "Album",
|
||||
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.Owner, nil
|
||||
return ec.resolvers.Album().Owner(rctx, obj)
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
|
@ -2456,9 +2482,9 @@ func (ec *executionContext) _Album_owner(ctx context.Context, field graphql.Coll
|
|||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(models.User)
|
||||
res := resTmp.(*models.User)
|
||||
fc.Result = res
|
||||
return ec.marshalNUser2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
|
||||
return ec.marshalNUser2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Album_filePath(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) {
|
||||
|
@ -3011,14 +3037,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 +3808,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 +4165,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 +4224,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 {
|
||||
|
@ -4332,6 +4316,124 @@ func (ec *executionContext) _Mutation_deleteUser(ctx context.Context, field grap
|
|||
return ec.marshalOUser2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_userAddRootPath(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_userAddRootPath_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) {
|
||||
directive0 := func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Mutation().UserAddRootPath(rctx, args["id"].(int), args["rootPath"].(string))
|
||||
}
|
||||
directive1 := func(ctx context.Context) (interface{}, error) {
|
||||
if ec.directives.IsAdmin == nil {
|
||||
return nil, errors.New("directive isAdmin is not implemented")
|
||||
}
|
||||
return ec.directives.IsAdmin(ctx, nil, directive0)
|
||||
}
|
||||
|
||||
tmp, err := directive1(rctx)
|
||||
if err != nil {
|
||||
return nil, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
if tmp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if data, ok := tmp.(*models.Album); ok {
|
||||
return data, nil
|
||||
}
|
||||
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)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*models.Album)
|
||||
fc.Result = res
|
||||
return ec.marshalOAlbum2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_userRemoveRootAlbum(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_userRemoveRootAlbum_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) {
|
||||
directive0 := func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Mutation().UserRemoveRootAlbum(rctx, args["userId"].(int), args["albumId"].(int))
|
||||
}
|
||||
directive1 := func(ctx context.Context) (interface{}, error) {
|
||||
if ec.directives.IsAdmin == nil {
|
||||
return nil, errors.New("directive isAdmin is not implemented")
|
||||
}
|
||||
return ec.directives.IsAdmin(ctx, nil, directive0)
|
||||
}
|
||||
|
||||
tmp, err := directive1(rctx)
|
||||
if err != nil {
|
||||
return nil, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
if tmp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if data, ok := tmp.(*models.Album); ok {
|
||||
return data, nil
|
||||
}
|
||||
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)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*models.Album)
|
||||
fc.Result = res
|
||||
return ec.marshalOAlbum2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_setPeriodicScanInterval(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
@ -6031,7 +6133,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))
|
||||
|
@ -6042,15 +6144,15 @@ func (ec *executionContext) _User_rootPath(ctx context.Context, field graphql.Co
|
|||
Object: "User",
|
||||
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) {
|
||||
directive0 := func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.RootPath, nil
|
||||
return ec.resolvers.User().Albums(rctx, obj)
|
||||
}
|
||||
directive1 := func(ctx context.Context) (interface{}, error) {
|
||||
if ec.directives.IsAdmin == nil {
|
||||
|
@ -6066,10 +6168,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 +6183,64 @@ 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_rootAlbums(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))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "User",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
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.User().RootAlbums(rctx, obj)
|
||||
}
|
||||
directive1 := func(ctx context.Context) (interface{}, error) {
|
||||
if ec.directives.IsAdmin == nil {
|
||||
return nil, errors.New("directive isAdmin is not implemented")
|
||||
}
|
||||
return ec.directives.IsAdmin(ctx, obj, directive0)
|
||||
}
|
||||
|
||||
tmp, err := directive1(rctx)
|
||||
if err != nil {
|
||||
return nil, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
if tmp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if data, ok := tmp.([]*models.Album); ok {
|
||||
return data, nil
|
||||
}
|
||||
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)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.([]*models.Album)
|
||||
fc.Result = 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) {
|
||||
|
@ -7647,10 +7804,19 @@ func (ec *executionContext) _Album(ctx context.Context, sel ast.SelectionSet, ob
|
|||
case "parentAlbum":
|
||||
out.Values[i] = ec._Album_parentAlbum(ctx, field, obj)
|
||||
case "owner":
|
||||
out.Values[i] = ec._Album_owner(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
field := field
|
||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
}
|
||||
}()
|
||||
res = ec._Album_owner(ctx, field, obj)
|
||||
if res == graphql.Null {
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
return res
|
||||
})
|
||||
case "filePath":
|
||||
out.Values[i] = ec._Album_filePath(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
|
@ -7809,10 +7975,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 {
|
||||
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 +8178,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":
|
||||
|
@ -8036,6 +8206,10 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
|||
out.Values[i] = ec._Mutation_createUser(ctx, field)
|
||||
case "deleteUser":
|
||||
out.Values[i] = ec._Mutation_deleteUser(ctx, field)
|
||||
case "userAddRootPath":
|
||||
out.Values[i] = ec._Mutation_userAddRootPath(ctx, field)
|
||||
case "userRemoveRootAlbum":
|
||||
out.Values[i] = ec._Mutation_userRemoveRootAlbum(ctx, field)
|
||||
case "setPeriodicScanInterval":
|
||||
out.Values[i] = ec._Mutation_setPeriodicScanInterval(ctx, field)
|
||||
if out.Values[i] == graphql.Null {
|
||||
|
@ -8523,22 +8697,45 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
|
|||
case "id":
|
||||
out.Values[i] = ec._User_id(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
case "username":
|
||||
out.Values[i] = ec._User_username(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
case "rootPath":
|
||||
out.Values[i] = ec._User_rootPath(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
case "albums":
|
||||
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._User_albums(ctx, field, obj)
|
||||
if res == graphql.Null {
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
return res
|
||||
})
|
||||
case "rootAlbums":
|
||||
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._User_rootAlbums(ctx, field, obj)
|
||||
if res == graphql.Null {
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
return res
|
||||
})
|
||||
case "admin":
|
||||
out.Values[i] = ec._User_admin(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
|
|
|
@ -11,9 +11,10 @@ type Album struct {
|
|||
Model
|
||||
Title string `gorm:"not null"`
|
||||
ParentAlbumID *int
|
||||
ParentAlbum *Album
|
||||
OwnerID int `gorm:"not null"`
|
||||
Owner User
|
||||
ParentAlbum *Album `gorm:"constraint:OnDelete:SET NULL;"`
|
||||
// OwnerID int `gorm:"not null"`
|
||||
// Owner User
|
||||
Owners []User `gorm:"many2many:user_albums"`
|
||||
Path string `gorm:"not null"`
|
||||
PathHash string `gorm:"unique"`
|
||||
}
|
||||
|
@ -27,3 +28,17 @@ func (a *Album) BeforeSave(tx *gorm.DB) (err error) {
|
|||
a.PathHash = hex.EncodeToString(hash[:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Album) GetChildren(db *gorm.DB) (children []*Album, err error) {
|
||||
err = db.Raw(`
|
||||
WITH recursive sub_albums AS (
|
||||
SELECT * FROM albums AS root WHERE id = ?
|
||||
UNION ALL
|
||||
SELECT child.* FROM albums AS child JOIN sub_albums ON child.parent_album_id = sub_albums.id
|
||||
)
|
||||
|
||||
SELECT * FROM sub_albums
|
||||
`, a.ID).Find(&children).Error
|
||||
|
||||
return children, err
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ package models
|
|||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
|
@ -14,5 +12,4 @@ type Model struct {
|
|||
type ModelTimestamps struct {
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
}
|
||||
|
|
|
@ -17,16 +17,16 @@ type Media struct {
|
|||
Path string `gorm:"not null"`
|
||||
PathHash string `gorm:"not null"`
|
||||
AlbumID int `gorm:"not null"`
|
||||
Album Album
|
||||
Album Album `gorm:"constraint:OnDelete:CASCADE;"`
|
||||
ExifID *int
|
||||
Exif *MediaEXIF
|
||||
MediaURL []MediaURL
|
||||
Exif *MediaEXIF `gorm:"constraint:OnDelete:SET NULL;"`
|
||||
MediaURL []MediaURL `gorm:"constraint:OnDelete:CASCADE;"`
|
||||
DateShot time.Time `gorm:"not null"`
|
||||
DateImported time.Time `gorm:"not null"`
|
||||
Favorite bool `gorm:"not null, default:false"`
|
||||
// Favorite bool `gorm:"not null, default:false"`
|
||||
Type MediaType `gorm:"not null"`
|
||||
VideoMetadataID *int
|
||||
VideoMetadata *VideoMetadata
|
||||
VideoMetadata *VideoMetadata `gorm:"constraint:OnDelete:SET NULL;"`
|
||||
SideCarPath *string
|
||||
SideCarHash *string
|
||||
|
||||
|
@ -52,6 +52,18 @@ func (m *Media) BeforeSave(tx *gorm.DB) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Media) BeforeDelete(tx *gorm.DB) error {
|
||||
if err := tx.Model(m).Association("Exif").Clear(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(m).Association("MediaURL").Clear(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type MediaPurpose string
|
||||
|
||||
const (
|
||||
|
@ -65,7 +77,7 @@ const (
|
|||
type MediaURL struct {
|
||||
Model
|
||||
MediaID int `gorm:"not null"`
|
||||
Media Media
|
||||
Media Media `gorm:"constraint:OnDelete:CASCADE;"`
|
||||
MediaName string `gorm:"not null"`
|
||||
Width int `gorm:"not null"`
|
||||
Height int `gorm:"not null"`
|
||||
|
|
|
@ -8,13 +8,13 @@ type ShareToken struct {
|
|||
Model
|
||||
Value string `gorm:"not null"`
|
||||
OwnerID int `gorm:"not null"`
|
||||
Owner User
|
||||
Owner User `gorm:"constraint:OnDelete:CASCADE;"`
|
||||
Expire *time.Time
|
||||
Password *string
|
||||
AlbumID *int
|
||||
Album *Album
|
||||
Album *Album `gorm:"constraint:OnDelete:CASCADE;"`
|
||||
MediaID *int
|
||||
Media *Media
|
||||
Media *Media `gorm:"constraint:OnDelete:CASCADE;"`
|
||||
}
|
||||
|
||||
func (share *ShareToken) Token() string {
|
||||
|
|
|
@ -16,13 +16,17 @@ type User struct {
|
|||
Model
|
||||
Username string `gorm:"unique,size:128"`
|
||||
Password *string `gorm:"size:256`
|
||||
RootPath string `gorm:"size:512`
|
||||
// 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,14 +76,14 @@ 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,
|
||||
// RootPath: rootPath,
|
||||
Admin: admin,
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -192,3 +153,30 @@ func VerifyTokenAndGetUser(db *gorm.DB, token string) (*User, error) {
|
|||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// FillAlbums fill user.Albums with albums from database
|
||||
func (user *User) FillAlbums(db *gorm.DB) error {
|
||||
// Albums already present
|
||||
if len(user.Albums) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := db.Model(&user).Association("Albums").Find(&user.Albums); err != nil {
|
||||
return errors.Wrap(err, "fill user albums")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (user *User) OwnsAlbum(db *gorm.DB, album *Album) (bool, error) {
|
||||
|
||||
// user.QueryUserAlbums(db, db.Where("id = ?", album.ID))
|
||||
|
||||
// TODO: Implement this
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (user *User) OwnsMedia(db *gorm.DB, media *Media) (bool, error) {
|
||||
// TODO: implement this
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ package resolvers
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
api "github.com/photoview/photoview/api/graphql"
|
||||
"github.com/photoview/photoview/api/graphql/auth"
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (r *queryResolver) MyAlbums(ctx context.Context, filter *models.Filter, onlyRoot *bool, showEmpty *bool, onlyWithFavorites *bool) ([]*models.Album, error) {
|
||||
|
@ -14,10 +16,19 @@ func (r *queryResolver) MyAlbums(ctx context.Context, filter *models.Filter, onl
|
|||
return nil, auth.ErrUnauthorized
|
||||
}
|
||||
|
||||
query := r.Database.Where("owner_id = ?", user.ID)
|
||||
if err := user.FillAlbums(r.Database); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userAlbumIDs := make([]int, len(user.Albums))
|
||||
for i, album := range user.Albums {
|
||||
userAlbumIDs[i] = album.ID
|
||||
}
|
||||
|
||||
query := r.Database.Model(models.Album{}).Where("id IN (?)", userAlbumIDs)
|
||||
|
||||
if onlyRoot != nil && *onlyRoot == true {
|
||||
query = query.Where("parent_album_id = (?)", r.Database.Model(&models.Album{}).Select("id").Where("parent_album_id IS NULL AND owner_id = ?", user.ID))
|
||||
query = query.Where("parent_album_id IS NULL")
|
||||
}
|
||||
|
||||
if showEmpty == nil || *showEmpty == false {
|
||||
|
@ -33,7 +44,7 @@ func (r *queryResolver) MyAlbums(ctx context.Context, filter *models.Filter, onl
|
|||
query = filter.FormatSQL(query)
|
||||
|
||||
var albums []*models.Album
|
||||
if err := query.Find(&albums).Error; err != nil {
|
||||
if err := query.Scan(&albums).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -47,10 +58,22 @@ func (r *queryResolver) Album(ctx context.Context, id int) (*models.Album, error
|
|||
}
|
||||
|
||||
var album models.Album
|
||||
if err := r.Database.Where("owner_id = ?", user.ID).First(&album, id).Error; err != nil {
|
||||
if err := r.Database.First(&album, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("album not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ownsAlbum, err := user.OwnsAlbum(r.Database, &album)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ownsAlbum {
|
||||
return nil, errors.New("forbidden")
|
||||
}
|
||||
|
||||
return &album, nil
|
||||
}
|
||||
|
||||
|
@ -154,8 +177,24 @@ func (r *albumResolver) Path(ctx context.Context, obj *models.Album) ([]*models.
|
|||
UNION
|
||||
SELECT parent.* FROM path_albums child JOIN albums parent ON parent.id = child.parent_album_id
|
||||
)
|
||||
SELECT * FROM path_albums WHERE id != ? AND owner_id = ?
|
||||
`, obj.ID, obj.ID, user.ID).Scan(&album_path).Error
|
||||
SELECT * FROM path_albums WHERE id != ?
|
||||
`, obj.ID, obj.ID).Scan(&album_path).Error
|
||||
|
||||
// Make sure to only return albums this user owns
|
||||
for i := len(album_path) - 1; i >= 0; i-- {
|
||||
album := album_path[i]
|
||||
|
||||
owns, err := user.OwnsAlbum(r.Database, album)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !owns {
|
||||
album_path = album_path[i+1:]
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -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) {
|
||||
|
@ -17,11 +18,20 @@ func (r *queryResolver) MyMedia(ctx context.Context, filter *models.Filter) ([]*
|
|||
return nil, errors.New("unauthorized")
|
||||
}
|
||||
|
||||
if err := user.FillAlbums(r.Database); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userAlbumIDs := make([]int, len(user.Albums))
|
||||
for i, album := range user.Albums {
|
||||
userAlbumIDs[i] = album.ID
|
||||
}
|
||||
|
||||
var media []*models.Media
|
||||
|
||||
query := r.Database.
|
||||
Joins("Album").
|
||||
Where("albums.owner_id = ?", user.ID).
|
||||
Where("albums.id IN (?)", userAlbumIDs).
|
||||
Where("media.id IN (?)", r.Database.Model(&models.MediaURL{}).Select("id").Where("media_url.media_id = media.id"))
|
||||
|
||||
query = filter.FormatSQL(query)
|
||||
|
@ -44,7 +54,7 @@ func (r *queryResolver) Media(ctx context.Context, id int) (*models.Media, error
|
|||
err := r.Database.
|
||||
Joins("Album").
|
||||
Where("media.id = ?", id).
|
||||
Where("Album.owner_id = ?", user.ID).
|
||||
Where("EXISTS (SELECT * FROM user_albums WHERE user_albums.album_id = Album.id AND user_albums.user_id = ?)", user.ID).
|
||||
Where("media.id IN (?)", r.Database.Model(&models.MediaURL{}).Select("media_id").Where("media_urls.media_id = media.id")).
|
||||
First(&media).Error
|
||||
|
||||
|
@ -182,20 +192,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
|
||||
|
|
|
@ -28,7 +28,6 @@ func (r *mutationResolver) ScanAll(ctx context.Context) (*models.ScannerResult,
|
|||
func (r *mutationResolver) ScanUser(ctx context.Context, userID int) (*models.ScannerResult, error) {
|
||||
|
||||
var user models.User
|
||||
|
||||
if err := r.Database.First(&user, userID).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "get user from database")
|
||||
}
|
||||
|
|
|
@ -100,7 +100,12 @@ func (r *mutationResolver) ShareAlbum(ctx context.Context, albumID int, expire *
|
|||
}
|
||||
|
||||
var count int64
|
||||
if err := r.Database.Model(&models.Album{}).Where("owner_id = ?", user.ID).Count(&count).Error; err != nil {
|
||||
err := r.Database.
|
||||
Model(&models.Album{}).
|
||||
Where("EXISTS (SELECT * FROM user_albums WHERE user_albums.album_id = albums.id AND user_albums.user_id = ?)", user.ID).
|
||||
Count(&count).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to validate album owner with database")
|
||||
}
|
||||
|
||||
|
@ -142,7 +147,12 @@ func (r *mutationResolver) ShareMedia(ctx context.Context, mediaID int, expire *
|
|||
|
||||
var media models.Media
|
||||
|
||||
if err := r.Database.Joins("Album").Where("Album.owner_id = ?", user.ID).First(&media, mediaID).Error; err != nil {
|
||||
err := r.Database.Joins("Album").
|
||||
Where("EXISTS (SELECT * FROM user_albums WHERE user_albums.album_id = Album.id AND user_albums.user_id = ?)", user.ID).
|
||||
First(&media, mediaID).
|
||||
Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, auth.ErrUnauthorized
|
||||
} else {
|
||||
|
@ -150,17 +160,6 @@ func (r *mutationResolver) ShareMedia(ctx context.Context, mediaID int, expire *
|
|||
}
|
||||
}
|
||||
|
||||
var count int64
|
||||
|
||||
err := r.Database.Raw("SELECT owner_id FROM albums, media WHERE media.id = ? AND media.album_id = albums.id AND albums.owner_id = ?", mediaID, user.ID).Count(&count).Error
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error validating owner of media with database")
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return nil, auth.ErrUnauthorized
|
||||
}
|
||||
|
||||
hashedPassword, err := hashSharePassword(password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -2,14 +2,29 @@ package resolvers
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
api "github.com/photoview/photoview/api/graphql"
|
||||
"github.com/photoview/photoview/api/graphql/auth"
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"github.com/photoview/photoview/api/scanner"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type userResolver struct {
|
||||
*Resolver
|
||||
}
|
||||
|
||||
func (r *Resolver) User() api.UserResolver {
|
||||
return &userResolver{r}
|
||||
}
|
||||
|
||||
func (r *queryResolver) User(ctx context.Context, filter *models.Filter) ([]*models.User, error) {
|
||||
|
||||
var users []*models.User
|
||||
|
@ -21,6 +36,30 @@ func (r *queryResolver) User(ctx context.Context, filter *models.Filter) ([]*mod
|
|||
return users, nil
|
||||
}
|
||||
|
||||
func (r *userResolver) Albums(ctx context.Context, user *models.User) ([]*models.Album, error) {
|
||||
user.FillAlbums(r.Database)
|
||||
|
||||
pointerAlbums := make([]*models.Album, len(user.Albums))
|
||||
for i, album := range user.Albums {
|
||||
pointerAlbums[i] = &album
|
||||
}
|
||||
|
||||
return pointerAlbums, nil
|
||||
}
|
||||
|
||||
func (r *userResolver) RootAlbums(ctx context.Context, user *models.User) (albums []*models.Album, err error) {
|
||||
|
||||
err = r.Database.Model(&user).
|
||||
Where("albums.parent_album_id NOT IN (?)",
|
||||
r.Database.Table("user_albums").
|
||||
Select("albums.id").
|
||||
Joins("JOIN albums ON albums.id = user_albums.album_id AND user_albums.user_id = ?", user.ID),
|
||||
).Or("albums.parent_album_id IS NULL").
|
||||
Association("Albums").Find(&albums)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *queryResolver) MyUser(ctx context.Context) (*models.User, error) {
|
||||
|
||||
user := auth.UserFromContext(ctx)
|
||||
|
@ -61,38 +100,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)
|
||||
|
@ -104,6 +111,8 @@ func (r *mutationResolver) InitialSetupWizard(ctx context.Context, username stri
|
|||
return nil, errors.New("not initial setup")
|
||||
}
|
||||
|
||||
rootPath = path.Clean(rootPath)
|
||||
|
||||
var token *models.AccessToken
|
||||
|
||||
transactionError := r.Database.Transaction(func(tx *gorm.DB) error {
|
||||
|
@ -111,7 +120,12 @@ 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
|
||||
}
|
||||
|
||||
_, err = scanner.NewRootAlbum(tx, rootPath, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -139,9 +153,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 +168,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 +189,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
|
||||
}
|
||||
|
@ -214,3 +224,124 @@ func (r *mutationResolver) DeleteUser(ctx context.Context, id int) (*models.User
|
|||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UserAddRootPath(ctx context.Context, id int, rootPath string) (*models.Album, error) {
|
||||
|
||||
rootPath = path.Clean(rootPath)
|
||||
|
||||
var user models.User
|
||||
if err := r.Database.First(&user, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !models.ValidRootPath(rootPath) {
|
||||
return nil, errors.New("invalid root path")
|
||||
}
|
||||
|
||||
upperPaths := make([]string, 1)
|
||||
upperPath := rootPath
|
||||
upperPaths[0] = upperPath
|
||||
for {
|
||||
|
||||
substrIndex := strings.LastIndex(upperPath, "/")
|
||||
if substrIndex == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
if substrIndex == 0 {
|
||||
upperPaths = append(upperPaths, "/")
|
||||
break
|
||||
}
|
||||
|
||||
upperPath = upperPath[0:substrIndex]
|
||||
upperPaths = append(upperPaths, upperPath)
|
||||
}
|
||||
|
||||
var upperAlbums []models.Album
|
||||
if err := r.Database.Model(&user).Association("Albums").Find(&upperAlbums, "albums.path IN (?)", upperPaths); err != nil {
|
||||
// if err := r.Database.Model(models.Album{}).Where("path IN (?)", upperPaths).Find(&upperAlbums).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(upperAlbums) > 0 {
|
||||
return nil, errors.New(fmt.Sprintf("user already owns a path containing this path: %s", upperAlbums[0].Path))
|
||||
}
|
||||
|
||||
newAlbum, err := scanner.NewRootAlbum(r.Database, rootPath, &user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newAlbum, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UserRemoveRootAlbum(ctx context.Context, userID int, albumID int) (*models.Album, error) {
|
||||
|
||||
var album models.Album
|
||||
if err := r.Database.First(&album, albumID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletedAlbumIDs []int = nil
|
||||
|
||||
err := r.Database.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Raw("DELETE FROM user_albums WHERE user_id = ? AND album_id = ?", userID, albumID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
children, err := album.GetChildren(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
childAlbumIDs := make([]int, len(children))
|
||||
for i, child := range children {
|
||||
childAlbumIDs[i] = child.ID
|
||||
}
|
||||
|
||||
result := tx.Exec("DELETE FROM user_albums WHERE user_id = ? and album_id IN (?)", userID, childAlbumIDs)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New("No relation deleted")
|
||||
}
|
||||
|
||||
// Cleanup if no user owns the album anymore
|
||||
var count int
|
||||
if err := tx.Raw("SELECT COUNT(user_id) FROM user_albums WHERE album_id = ?", albumID).Scan(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
deletedAlbumIDs = append(childAlbumIDs, albumID)
|
||||
childAlbumIDs = nil
|
||||
|
||||
// Delete albums from database
|
||||
if err := tx.Delete(&models.Album{}, "id IN (?)", deletedAlbumIDs).Error; err != nil {
|
||||
deletedAlbumIDs = nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if deletedAlbumIDs != nil {
|
||||
// Delete albums from cache
|
||||
for _, id := range deletedAlbumIDs {
|
||||
cacheAlbumPath := path.Join(scanner.PhotoCache(), strconv.Itoa(id))
|
||||
|
||||
if err := os.RemoveAll(cacheAlbumPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &album, nil
|
||||
}
|
||||
|
|
|
@ -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,18 +85,20 @@ 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
|
||||
deleteUser(id: ID!): User @isAdmin
|
||||
|
||||
"Add a root path from where to look for media for the given user"
|
||||
userAddRootPath(id: ID!, rootPath: String!): Album @isAdmin
|
||||
userRemoveRootAlbum(userId: ID!, albumId: ID!): Album @isAdmin
|
||||
|
||||
"""
|
||||
Set how often, in seconds, the server should automatically scan for new media,
|
||||
a value of 0 will disable periodic scans
|
||||
|
@ -180,8 +175,11 @@ type User {
|
|||
id: ID!
|
||||
username: String!
|
||||
#albums: [Album]
|
||||
"Local filepath for the user's photos"
|
||||
rootPath: String! @isAdmin
|
||||
# rootPath: String! @isAdmin
|
||||
"All albums owned by this user"
|
||||
albums: [Album!]! @isAdmin
|
||||
"Top level albums owned by this user"
|
||||
rootAlbums: [Album!]! @isAdmin
|
||||
admin: Boolean!
|
||||
#shareTokens: [ShareToken]
|
||||
}
|
||||
|
|
|
@ -19,7 +19,12 @@ func authenticateMedia(media *models.Media, db *gorm.DB, r *http.Request) (succe
|
|||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if album.OwnerID != user.ID {
|
||||
ownsAlbum, err := user.OwnsAlbum(db, &album)
|
||||
if err != nil {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if !ownsAlbum {
|
||||
return false, "invalid credentials", http.StatusForbidden, nil
|
||||
}
|
||||
} else {
|
||||
|
@ -55,20 +60,21 @@ func authenticateMedia(media *models.Media, db *gorm.DB, r *http.Request) (succe
|
|||
if shareToken.AlbumID != nil && media.AlbumID != *shareToken.AlbumID {
|
||||
// Check child albums
|
||||
|
||||
result := db.Raw(`
|
||||
var count int
|
||||
err := db.Raw(`
|
||||
WITH recursive child_albums AS (
|
||||
SELECT * FROM album WHERE parent_album = ?
|
||||
SELECT * FROM albums WHERE parent_album_id = ?
|
||||
UNION ALL
|
||||
SELECT child.* FROM album child JOIN child_albums parent ON parent.album_id = child.parent_album
|
||||
SELECT child.* FROM albums child JOIN child_albums parent ON parent.id = child.parent_album_id
|
||||
)
|
||||
SELECT * FROM child_albums WHERE album_id = ?
|
||||
`, *shareToken.AlbumID, media.AlbumID)
|
||||
SELECT COUNT(id) FROM child_albums WHERE id = ?
|
||||
`, *shareToken.AlbumID, media.AlbumID).Find(&count).Error
|
||||
|
||||
if err := result.Error; err != nil {
|
||||
if err != nil {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
if count == 0 {
|
||||
return false, "unauthorized", http.StatusForbidden, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -59,28 +58,32 @@ func deleteOldUserAlbums(db *gorm.DB, scannedAlbums []*models.Album, user *model
|
|||
return nil
|
||||
}
|
||||
|
||||
albumPaths := make([]interface{}, len(scannedAlbums))
|
||||
scannedAlbumIDs := make([]interface{}, len(scannedAlbums))
|
||||
for i, album := range scannedAlbums {
|
||||
albumPaths[i] = album.Path
|
||||
scannedAlbumIDs[i] = album.ID
|
||||
}
|
||||
|
||||
// Delete old albums
|
||||
album_args := make([]interface{}, 0)
|
||||
album_args = append(album_args, user.ID)
|
||||
album_args = append(album_args, albumPaths...)
|
||||
|
||||
var albums []models.Album
|
||||
|
||||
albums_questions := strings.Repeat("MD5(?),", len(albumPaths))[:len(albumPaths)*7-1]
|
||||
if err := db.Where("owner_id = ? AND path_hash NOT IN ("+albums_questions+")", album_args...).Find(&albums).Error; err != nil {
|
||||
userAlbumIDs := make([]int, len(user.Albums))
|
||||
for i, album := range user.Albums {
|
||||
userAlbumIDs[i] = album.ID
|
||||
}
|
||||
|
||||
query := db.
|
||||
Where("id IN (?)", userAlbumIDs).
|
||||
Where("id NOT IN (?)", scannedAlbumIDs)
|
||||
|
||||
if err := query.Find(&albums).Error; err != nil {
|
||||
return []error{errors.Wrap(err, "get albums to be deleted from database")}
|
||||
}
|
||||
|
||||
deleteErrors := make([]error, 0)
|
||||
|
||||
albumIDs := make([]int, 0)
|
||||
for _, album := range albums {
|
||||
albumIDs = append(albumIDs, album.ID)
|
||||
deleteAlbumIDs := make([]int, len(albums))
|
||||
for i, album := range albums {
|
||||
deleteAlbumIDs[i] = album.ID
|
||||
cachePath := path.Join(PhotoCache(), strconv.Itoa(int(album.ID)))
|
||||
err := os.RemoveAll(cachePath)
|
||||
if err != nil {
|
||||
|
@ -88,7 +91,7 @@ func deleteOldUserAlbums(db *gorm.DB, scannedAlbums []*models.Album, user *model
|
|||
}
|
||||
}
|
||||
|
||||
if err := db.Where("id IN ?", albumIDs).Delete(models.Album{}).Error; err != nil {
|
||||
if err := db.Where("id IN ?", deleteAlbumIDs).Delete(models.Album{}).Error; err != nil {
|
||||
ScannerError("Could not delete old albums from database:\n%s\n", err)
|
||||
deleteErrors = append(deleteErrors, errors.Wrap(err, "delete old albums from database"))
|
||||
}
|
||||
|
|
|
@ -13,6 +13,40 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func NewRootAlbum(db *gorm.DB, rootPath string, owner *models.User) (*models.Album, error) {
|
||||
|
||||
owners := []models.User{
|
||||
*owner,
|
||||
}
|
||||
|
||||
var matchedAlbums []models.Album
|
||||
if err := db.Where("path_hash = MD5(?)", rootPath).Find(&matchedAlbums).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(matchedAlbums) > 0 {
|
||||
album := matchedAlbums[0]
|
||||
|
||||
if err := db.Model(&owner).Association("Albums").Append(&album); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to add owner to already existing album")
|
||||
}
|
||||
|
||||
return &album, nil
|
||||
} else {
|
||||
album := models.Album{
|
||||
Title: path.Base(rootPath),
|
||||
Path: rootPath,
|
||||
Owners: owners,
|
||||
}
|
||||
|
||||
if err := db.Create(&album).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &album, nil
|
||||
}
|
||||
}
|
||||
|
||||
func scanAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.DB) {
|
||||
|
||||
album_notify_key := utils.GenerateToken()
|
||||
|
|
|
@ -17,71 +17,112 @@ 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)}
|
||||
if err := user.FillAlbums(db); err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
userAlbumIDs := make([]int, len(user.Albums))
|
||||
for i, album := range user.Albums {
|
||||
userAlbumIDs[i] = album.ID
|
||||
}
|
||||
|
||||
var userRootAlbums []*models.Album
|
||||
if err := db.Where("id IN (?)", userAlbumIDs).Where("parent_album_id IS NULL").Find(&userRootAlbums).Error; err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
scanErrors := make([]error, 0)
|
||||
|
||||
type scanInfo struct {
|
||||
path string
|
||||
parentID *int
|
||||
parent *models.Album
|
||||
}
|
||||
|
||||
scanQueue := list.New()
|
||||
|
||||
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: user.RootPath,
|
||||
parentID: nil,
|
||||
path: album.Path,
|
||||
parent: nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
userAlbums := make([]*models.Album, 0)
|
||||
albumErrors := make([]error, 0)
|
||||
// newPhotos := make([]*models.Photo, 0)
|
||||
|
||||
for scanQueue.Front() != nil {
|
||||
albumInfo := scanQueue.Front().Value.(scanInfo)
|
||||
scanQueue.Remove(scanQueue.Front())
|
||||
|
||||
albumPath := albumInfo.path
|
||||
albumParentID := albumInfo.parentID
|
||||
albumParent := albumInfo.parent
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Will become new album or album from db
|
||||
var album models.Album
|
||||
var album *models.Album
|
||||
|
||||
transErr := db.Transaction(func(tx *gorm.DB) error {
|
||||
log.Printf("Scanning directory: %s", albumPath)
|
||||
|
||||
// Make album if not exists
|
||||
// check if album already exists
|
||||
var albumResult []models.Album
|
||||
result := tx.Where("path_hash = md5(?)", albumPath).Find(&albumResult)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// album does not exist, create new
|
||||
if len(albumResult) == 0 {
|
||||
albumTitle := path.Base(albumPath)
|
||||
|
||||
err = tx.FirstOrCreate(&album, models.Album{
|
||||
var albumParentID *int
|
||||
parentOwners := make([]models.User, 0)
|
||||
if albumParent != nil {
|
||||
albumParentID = &albumParent.ID
|
||||
|
||||
if err := db.Model(&albumParent).Association("Owners").Find(&parentOwners); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
album = &models.Album{
|
||||
Title: albumTitle,
|
||||
ParentAlbumID: albumParentID,
|
||||
OwnerID: user.ID,
|
||||
Path: albumPath,
|
||||
}).Error
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err := tx.Create(&album).Error; err != nil {
|
||||
return errors.Wrap(err, "insert album into database")
|
||||
}
|
||||
|
||||
userAlbums = append(userAlbums, &album)
|
||||
if err := tx.Model(&album).Association("Owners").Append(parentOwners); err != nil {
|
||||
return errors.Wrap(err, "add owners to album")
|
||||
}
|
||||
} else {
|
||||
album = &albumResult[0]
|
||||
}
|
||||
|
||||
userAlbums = append(userAlbums, album)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if transErr != nil {
|
||||
albumErrors = append(albumErrors, errors.Wrap(transErr, "begin database transaction"))
|
||||
scanErrors = append(scanErrors, errors.Wrap(transErr, "begin database transaction"))
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -97,16 +138,16 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScanner
|
|||
if item.IsDir() && directoryContainsPhotos(subalbumPath, album_cache) {
|
||||
scanQueue.PushBack(scanInfo{
|
||||
path: subalbumPath,
|
||||
parentID: &album.ID,
|
||||
parent: album,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -4,7 +4,7 @@ import styled from 'styled-components'
|
|||
import Layout from '../../Layout'
|
||||
|
||||
import ScannerSection from './ScannerSection'
|
||||
import UsersTable from './UsersTable'
|
||||
import UsersTable from './Users/UsersTable'
|
||||
|
||||
export const SectionTitle = styled.h2`
|
||||
margin-top: ${({ nospace }) => (nospace ? '0' : '1.4em')} !important;
|
||||
|
|
|
@ -1,298 +0,0 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import React, { useState } from 'react'
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Form,
|
||||
Icon,
|
||||
Input,
|
||||
Modal,
|
||||
Table,
|
||||
} from 'semantic-ui-react'
|
||||
|
||||
const updateUserMutation = gql`
|
||||
mutation updateUser(
|
||||
$id: ID!
|
||||
$username: String
|
||||
$rootPath: String
|
||||
$admin: Boolean
|
||||
) {
|
||||
updateUser(
|
||||
id: $id
|
||||
username: $username
|
||||
rootPath: $rootPath
|
||||
admin: $admin
|
||||
) {
|
||||
id
|
||||
username
|
||||
rootPath
|
||||
admin
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const deleteUserMutation = gql`
|
||||
mutation deleteUser($id: ID!) {
|
||||
deleteUser(id: $id) {
|
||||
id
|
||||
username
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const changeUserPasswordMutation = gql`
|
||||
mutation changeUserPassword($userId: ID!, $password: String!) {
|
||||
updateUser(id: $userId, password: $password) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const scanUserMutation = gql`
|
||||
mutation scanUser($userId: ID!) {
|
||||
scanUser(userId: $userId) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const ChangePasswordModal = ({ onClose, user, ...props }) => {
|
||||
const [passwordInput, setPasswordInput] = useState('')
|
||||
|
||||
const [changePassword] = useMutation(changeUserPasswordMutation, {
|
||||
onCompleted: () => {
|
||||
onClose && onClose()
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<Modal {...props}>
|
||||
<Modal.Header>Change password</Modal.Header>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Change password for <b>{user.username}</b>
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Field>
|
||||
<label>New password</label>
|
||||
<Input
|
||||
placeholder="password"
|
||||
onChange={e => setPasswordInput(e.target.value)}
|
||||
type="password"
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={() => onClose && onClose()}>Cancel</Button>
|
||||
<Button
|
||||
positive
|
||||
onClick={() => {
|
||||
changePassword({
|
||||
variables: {
|
||||
userId: user.id,
|
||||
password: passwordInput,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
Change password
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
ChangePasswordModal.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
user: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
const UserRow = ({ user, refetchUsers }) => {
|
||||
const [state, setState] = useState({
|
||||
...user,
|
||||
editing: false,
|
||||
})
|
||||
|
||||
const [showConfirmDelete, setConfirmDelete] = useState(false)
|
||||
const [showChangePassword, setChangePassword] = useState(false)
|
||||
|
||||
function updateInput(event, key) {
|
||||
setState({
|
||||
...state,
|
||||
[key]: event.target.value,
|
||||
})
|
||||
}
|
||||
|
||||
const [updateUser, { loading: updateUserLoading }] = useMutation(
|
||||
updateUserMutation,
|
||||
{
|
||||
onCompleted: data => {
|
||||
setState({
|
||||
...data.updateUser,
|
||||
editing: false,
|
||||
})
|
||||
refetchUsers()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const [deleteUser] = useMutation(deleteUserMutation, {
|
||||
onCompleted: () => {
|
||||
refetchUsers()
|
||||
},
|
||||
})
|
||||
|
||||
const [scanUser, { called: scanUserCalled }] = useMutation(scanUserMutation, {
|
||||
onCompleted: () => {
|
||||
refetchUsers()
|
||||
},
|
||||
})
|
||||
|
||||
if (state.editing) {
|
||||
return (
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
style={{ width: '100%' }}
|
||||
placeholder={user.username}
|
||||
value={state.username}
|
||||
onChange={e => updateInput(e, 'username')}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
style={{ width: '100%' }}
|
||||
placeholder={user.rootPath}
|
||||
value={state.rootPath}
|
||||
onChange={e => updateInput(e, 'rootPath')}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Checkbox
|
||||
toggle
|
||||
checked={state.admin}
|
||||
onChange={(_, data) => {
|
||||
setState({
|
||||
...state,
|
||||
admin: data.checked,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button.Group>
|
||||
<Button
|
||||
negative
|
||||
onClick={() =>
|
||||
setState({
|
||||
...state.oldState,
|
||||
})
|
||||
}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
loading={updateUserLoading}
|
||||
disabled={updateUserLoading}
|
||||
positive
|
||||
onClick={() =>
|
||||
updateUser({
|
||||
variables: {
|
||||
id: user.id,
|
||||
username: state.username,
|
||||
rootPath: state.rootPath,
|
||||
admin: state.admin,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<Table.Cell>{user.username}</Table.Cell>
|
||||
<Table.Cell>{user.rootPath}</Table.Cell>
|
||||
<Table.Cell>
|
||||
{user.admin ? <Icon name="checkmark" size="large" /> : null}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button.Group>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setState({ ...state, editing: true, oldState: state })
|
||||
}}
|
||||
>
|
||||
<Icon name="edit" />
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
disabled={scanUserCalled}
|
||||
onClick={() => scanUser({ variables: { userId: user.id } })}
|
||||
>
|
||||
<Icon name="sync" />
|
||||
Scan
|
||||
</Button>
|
||||
<Button onClick={() => setChangePassword(true)}>
|
||||
<Icon name="key" />
|
||||
Change password
|
||||
</Button>
|
||||
<ChangePasswordModal
|
||||
user={user}
|
||||
open={showChangePassword}
|
||||
onClose={() => setChangePassword(false)}
|
||||
/>
|
||||
<Button
|
||||
negative
|
||||
onClick={() => {
|
||||
setConfirmDelete(true)
|
||||
}}
|
||||
>
|
||||
<Icon name="delete" />
|
||||
Delete
|
||||
</Button>
|
||||
<Modal open={showConfirmDelete}>
|
||||
<Modal.Header>Delete user</Modal.Header>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
{`Are you sure, you want to delete `}
|
||||
<b>{user.username}</b>?
|
||||
</p>
|
||||
<p>{`This action cannot be undone`}</p>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={() => setConfirmDelete(false)}>Cancel</Button>
|
||||
<Button
|
||||
negative
|
||||
onClick={() => {
|
||||
setConfirmDelete(false)
|
||||
deleteUser({
|
||||
variables: {
|
||||
id: user.id,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
Delete {user.username}
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
</Button.Group>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
}
|
||||
|
||||
UserRow.propTypes = {
|
||||
user: PropTypes.object.isRequired,
|
||||
refetchUsers: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default UserRow
|
|
@ -0,0 +1,81 @@
|
|||
import React from 'react'
|
||||
import { Button, Checkbox, Input, Table } from 'semantic-ui-react'
|
||||
import { EditRootPaths } from './EditUserRowRootPaths'
|
||||
import { UserRowProps } from './UserRow'
|
||||
|
||||
const EditUserRow = ({
|
||||
user,
|
||||
state,
|
||||
setState,
|
||||
updateUser,
|
||||
updateUserLoading,
|
||||
}) => {
|
||||
function updateInput(event, key) {
|
||||
setState(state => ({
|
||||
...state,
|
||||
[key]: event.target.value,
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Input
|
||||
style={{ width: '100%' }}
|
||||
placeholder={user.username}
|
||||
value={state.username}
|
||||
onChange={e => updateInput(e, 'username')}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<EditRootPaths user={user} />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Checkbox
|
||||
toggle
|
||||
checked={state.admin}
|
||||
onChange={(_, data) => {
|
||||
setState(state => ({
|
||||
...state,
|
||||
admin: data.checked,
|
||||
}))
|
||||
}}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button.Group>
|
||||
<Button
|
||||
negative
|
||||
onClick={() =>
|
||||
setState(state => ({
|
||||
...state.oldState,
|
||||
}))
|
||||
}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
loading={updateUserLoading}
|
||||
disabled={updateUserLoading}
|
||||
positive
|
||||
onClick={() =>
|
||||
updateUser({
|
||||
variables: {
|
||||
id: user.id,
|
||||
username: state.username,
|
||||
admin: state.admin,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
}
|
||||
|
||||
EditUserRow.propTypes = UserRowProps
|
||||
|
||||
export default EditUserRow
|
|
@ -0,0 +1,135 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import React, { useState } from 'react'
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import { Button, Icon, Input } from 'semantic-ui-react'
|
||||
import styled from 'styled-components'
|
||||
import { USERS_QUERY } from './UsersTable'
|
||||
|
||||
const userAddRootPathMutation = gql`
|
||||
mutation userAddRootPath($id: ID!, $rootPath: String!) {
|
||||
userAddRootPath(id: $id, rootPath: $rootPath) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const userRemoveAlbumPathMutation = gql`
|
||||
mutation userRemoveAlbumPathMutation($userId: ID!, $albumId: ID!) {
|
||||
userRemoveRootAlbum(userId: $userId, albumId: $albumId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const RootPathListItem = styled.li`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const EditRootPath = ({ album, user }) => {
|
||||
const [removeAlbumPath, { loading }] = useMutation(
|
||||
userRemoveAlbumPathMutation,
|
||||
{
|
||||
refetchQueries: [
|
||||
{
|
||||
query: USERS_QUERY,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<RootPathListItem>
|
||||
<span>{album.filePath}</span>
|
||||
<Button
|
||||
negative
|
||||
disabled={loading}
|
||||
onClick={() =>
|
||||
removeAlbumPath({
|
||||
variables: {
|
||||
userId: user.id,
|
||||
albumId: album.id,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
<Icon name="remove" />
|
||||
Remove
|
||||
</Button>
|
||||
</RootPathListItem>
|
||||
)
|
||||
}
|
||||
|
||||
EditRootPath.propTypes = {
|
||||
album: PropTypes.object.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
const NewRootPathInput = styled(Input)`
|
||||
width: 100%;
|
||||
margin-top: 24px;
|
||||
`
|
||||
|
||||
const EditNewRootPath = ({ userID }) => {
|
||||
const [value, setValue] = useState('')
|
||||
const [addRootPath, { loading }] = useMutation(userAddRootPathMutation, {
|
||||
refetchQueries: [
|
||||
{
|
||||
query: USERS_QUERY,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return (
|
||||
<li>
|
||||
<NewRootPathInput
|
||||
style={{ width: '100%' }}
|
||||
value={value}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
disabled={loading}
|
||||
action={{
|
||||
positive: true,
|
||||
icon: 'add',
|
||||
content: 'Add',
|
||||
onClick: () => {
|
||||
setValue('')
|
||||
addRootPath({
|
||||
variables: {
|
||||
id: userID,
|
||||
rootPath: value,
|
||||
},
|
||||
})
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
EditNewRootPath.propTypes = {
|
||||
userID: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
const RootPathList = styled.ul`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
`
|
||||
|
||||
export const EditRootPaths = ({ user }) => {
|
||||
const editRows = user.rootAlbums.map(album => (
|
||||
<EditRootPath key={album.id} album={album} user={user} />
|
||||
))
|
||||
|
||||
return (
|
||||
<RootPathList>
|
||||
{editRows}
|
||||
<EditNewRootPath userID={user.id} />
|
||||
</RootPathList>
|
||||
)
|
||||
}
|
||||
|
||||
EditRootPaths.propTypes = {
|
||||
user: PropTypes.object.isRequired,
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import React, { useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import { Button, Form, Input, Modal } from 'semantic-ui-react'
|
||||
|
||||
const changeUserPasswordMutation = gql`
|
||||
mutation changeUserPassword($userId: ID!, $password: String!) {
|
||||
updateUser(id: $userId, password: $password) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const ChangePasswordModal = ({ onClose, user, ...props }) => {
|
||||
const [passwordInput, setPasswordInput] = useState('')
|
||||
|
||||
const [changePassword] = useMutation(changeUserPasswordMutation, {
|
||||
onCompleted: () => {
|
||||
onClose && onClose()
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<Modal {...props}>
|
||||
<Modal.Header>Change password</Modal.Header>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Change password for <b>{user.username}</b>
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Field>
|
||||
<label>New password</label>
|
||||
<Input
|
||||
placeholder="password"
|
||||
onChange={e => setPasswordInput(e.target.value)}
|
||||
type="password"
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={() => onClose && onClose()}>Cancel</Button>
|
||||
<Button
|
||||
positive
|
||||
onClick={() => {
|
||||
changePassword({
|
||||
variables: {
|
||||
userId: user.id,
|
||||
password: passwordInput,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
Change password
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
ChangePasswordModal.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
user: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default ChangePasswordModal
|
|
@ -0,0 +1,111 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import React, { useState } from 'react'
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import EditUserRow from './EditUserRow'
|
||||
import ViewUserRow from './ViewUserRow'
|
||||
|
||||
const updateUserMutation = gql`
|
||||
mutation updateUser($id: ID!, $username: String, $admin: Boolean) {
|
||||
updateUser(id: $id, username: $username, admin: $admin) {
|
||||
id
|
||||
username
|
||||
admin
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const deleteUserMutation = gql`
|
||||
mutation deleteUser($id: ID!) {
|
||||
deleteUser(id: $id) {
|
||||
id
|
||||
username
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const scanUserMutation = gql`
|
||||
mutation scanUser($userId: ID!) {
|
||||
scanUser(userId: $userId) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const UserRow = ({ user, refetchUsers }) => {
|
||||
const [state, setState] = useState({
|
||||
...user,
|
||||
editing: false,
|
||||
newRootPath: '',
|
||||
})
|
||||
|
||||
const [showConfirmDelete, setConfirmDelete] = useState(false)
|
||||
const [showChangePassword, setChangePassword] = useState(false)
|
||||
|
||||
const [updateUser, { loading: updateUserLoading }] = useMutation(
|
||||
updateUserMutation,
|
||||
{
|
||||
onCompleted: data => {
|
||||
setState({
|
||||
...data.updateUser,
|
||||
editing: false,
|
||||
})
|
||||
refetchUsers()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const [deleteUser] = useMutation(deleteUserMutation, {
|
||||
onCompleted: () => {
|
||||
refetchUsers()
|
||||
},
|
||||
})
|
||||
|
||||
const [scanUser, { called: scanUserCalled }] = useMutation(scanUserMutation, {
|
||||
onCompleted: () => {
|
||||
refetchUsers()
|
||||
},
|
||||
})
|
||||
|
||||
const props = {
|
||||
user,
|
||||
state,
|
||||
setState,
|
||||
scanUser,
|
||||
updateUser,
|
||||
updateUserLoading,
|
||||
deleteUser,
|
||||
setChangePassword,
|
||||
setConfirmDelete,
|
||||
scanUserCalled,
|
||||
showChangePassword,
|
||||
showConfirmDelete,
|
||||
}
|
||||
|
||||
if (state.editing) {
|
||||
return <EditUserRow {...props} />
|
||||
}
|
||||
|
||||
return <ViewUserRow {...props} />
|
||||
}
|
||||
|
||||
UserRow.propTypes = {
|
||||
user: PropTypes.object.isRequired,
|
||||
refetchUsers: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export const UserRowProps = {
|
||||
user: PropTypes.object.isRequired,
|
||||
state: PropTypes.object.isRequired,
|
||||
setState: PropTypes.func.isRequired,
|
||||
scanUser: PropTypes.func.isRequired,
|
||||
updateUser: PropTypes.func.isRequired,
|
||||
updateUserLoading: PropTypes.bool.isRequired,
|
||||
deleteUser: PropTypes.func.isRequired,
|
||||
setChangePassword: PropTypes.func.isRequired,
|
||||
setConfirmDelete: PropTypes.func.isRequired,
|
||||
scanUserCalled: PropTypes.func.isRequired,
|
||||
showChangePassword: PropTypes.func.isRequired,
|
||||
showConfirmDelete: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default UserRow
|
|
@ -4,15 +4,19 @@ import { Table, Loader, Button, Icon } from 'semantic-ui-react'
|
|||
import { useQuery, gql } from '@apollo/client'
|
||||
import UserRow from './UserRow'
|
||||
import AddUserRow from './AddUserRow'
|
||||
import { SectionTitle } from './SettingsPage'
|
||||
import { SectionTitle } from '../SettingsPage'
|
||||
|
||||
const USERS_QUERY = gql`
|
||||
export const USERS_QUERY = gql`
|
||||
query settingsUsersQuery {
|
||||
user {
|
||||
id
|
||||
username
|
||||
rootPath
|
||||
# rootPath
|
||||
admin
|
||||
rootAlbums {
|
||||
id
|
||||
filePath
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -0,0 +1,109 @@
|
|||
import React from 'react'
|
||||
import { Button, Icon, Table, Modal } from 'semantic-ui-react'
|
||||
import styled from 'styled-components'
|
||||
import ChangePasswordModal from './UserChangePassword'
|
||||
import { UserRowProps } from './UserRow'
|
||||
|
||||
const PathList = styled.ul`
|
||||
margin: 0;
|
||||
padding: 0 0 0 12px;
|
||||
list-style: none;
|
||||
`
|
||||
|
||||
const ViewUserRow = ({
|
||||
user,
|
||||
// state,
|
||||
setState,
|
||||
scanUser,
|
||||
deleteUser,
|
||||
setChangePassword,
|
||||
setConfirmDelete,
|
||||
scanUserCalled,
|
||||
showChangePassword,
|
||||
showConfirmDelete,
|
||||
}) => {
|
||||
const paths = (
|
||||
<PathList>
|
||||
{user.rootAlbums.map(album => (
|
||||
<li key={album.id}>{album.filePath}</li>
|
||||
))}
|
||||
</PathList>
|
||||
)
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<Table.Cell>{user.username}</Table.Cell>
|
||||
<Table.Cell>{paths}</Table.Cell>
|
||||
<Table.Cell>
|
||||
{user.admin ? <Icon name="checkmark" size="large" /> : null}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button.Group>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setState(state => ({ ...state, editing: true, oldState: state }))
|
||||
}}
|
||||
>
|
||||
<Icon name="edit" />
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
disabled={scanUserCalled}
|
||||
onClick={() => scanUser({ variables: { userId: user.id } })}
|
||||
>
|
||||
<Icon name="sync" />
|
||||
Scan
|
||||
</Button>
|
||||
<Button onClick={() => setChangePassword(true)}>
|
||||
<Icon name="key" />
|
||||
Change password
|
||||
</Button>
|
||||
<ChangePasswordModal
|
||||
user={user}
|
||||
open={showChangePassword}
|
||||
onClose={() => setChangePassword(false)}
|
||||
/>
|
||||
<Button
|
||||
negative
|
||||
onClick={() => {
|
||||
setConfirmDelete(true)
|
||||
}}
|
||||
>
|
||||
<Icon name="delete" />
|
||||
Delete
|
||||
</Button>
|
||||
<Modal open={showConfirmDelete}>
|
||||
<Modal.Header>Delete user</Modal.Header>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
{`Are you sure, you want to delete `}
|
||||
<b>{user.username}</b>?
|
||||
</p>
|
||||
<p>{`This action cannot be undone`}</p>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={() => setConfirmDelete(false)}>Cancel</Button>
|
||||
<Button
|
||||
negative
|
||||
onClick={() => {
|
||||
setConfirmDelete(false)
|
||||
deleteUser({
|
||||
variables: {
|
||||
id: user.id,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
Delete {user.username}
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
</Button.Group>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
}
|
||||
|
||||
ViewUserRow.propTypes = UserRowProps
|
||||
|
||||
export default ViewUserRow
|
|
@ -61,6 +61,8 @@ export const SHARE_TOKEN_QUERY = gql`
|
|||
}
|
||||
highRes {
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
videoWeb {
|
||||
url
|
||||
|
|
|
@ -32,7 +32,7 @@ const AlbumSidebar = ({ albumId }) => {
|
|||
}
|
||||
|
||||
AlbumSidebar.propTypes = {
|
||||
albumId: PropTypes.number.isRequired,
|
||||
albumId: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default AlbumSidebar
|
||||
|
|
Loading…
Reference in New Issue