mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 21:59:12 +00:00
@@ -174,10 +174,10 @@ type ComplexityRoot struct {
|
||||
}
|
||||
|
||||
Query struct {
|
||||
Album func(childComplexity int, id int) int
|
||||
Album func(childComplexity int, id int, tokenCredentials *models.ShareTokenCredentials) int
|
||||
FaceGroup func(childComplexity int, id int) int
|
||||
MapboxToken func(childComplexity int) int
|
||||
Media func(childComplexity int, id int) int
|
||||
Media func(childComplexity int, id int, tokenCredentials *models.ShareTokenCredentials) int
|
||||
MediaList func(childComplexity int, ids []int) int
|
||||
MyAlbums func(childComplexity int, order *models.Ordering, paginate *models.Pagination, onlyRoot *bool, showEmpty *bool, onlyWithFavorites *bool) int
|
||||
MyFaceGroups func(childComplexity int, paginate *models.Pagination) int
|
||||
@@ -186,8 +186,8 @@ type ComplexityRoot struct {
|
||||
MyTimeline func(childComplexity int, paginate *models.Pagination, onlyFavorites *bool) int
|
||||
MyUser func(childComplexity int) int
|
||||
Search func(childComplexity int, query string, limitMedia *int, limitAlbums *int) int
|
||||
ShareToken func(childComplexity int, token string, password *string) int
|
||||
ShareTokenValidatePassword func(childComplexity int, token string, password *string) int
|
||||
ShareToken func(childComplexity int, credentials models.ShareTokenCredentials) int
|
||||
ShareTokenValidatePassword func(childComplexity int, credentials models.ShareTokenCredentials) int
|
||||
SiteInfo func(childComplexity int) int
|
||||
User func(childComplexity int, order *models.Ordering, paginate *models.Pagination) int
|
||||
}
|
||||
@@ -312,15 +312,15 @@ type QueryResolver interface {
|
||||
User(ctx context.Context, order *models.Ordering, paginate *models.Pagination) ([]*models.User, error)
|
||||
MyUser(ctx context.Context) (*models.User, error)
|
||||
MyAlbums(ctx context.Context, order *models.Ordering, paginate *models.Pagination, onlyRoot *bool, showEmpty *bool, onlyWithFavorites *bool) ([]*models.Album, error)
|
||||
Album(ctx context.Context, id int) (*models.Album, error)
|
||||
Album(ctx context.Context, id int, tokenCredentials *models.ShareTokenCredentials) (*models.Album, error)
|
||||
MyMedia(ctx context.Context, order *models.Ordering, paginate *models.Pagination) ([]*models.Media, error)
|
||||
Media(ctx context.Context, id int) (*models.Media, error)
|
||||
Media(ctx context.Context, id int, tokenCredentials *models.ShareTokenCredentials) (*models.Media, error)
|
||||
MediaList(ctx context.Context, ids []int) ([]*models.Media, error)
|
||||
MyTimeline(ctx context.Context, paginate *models.Pagination, onlyFavorites *bool) ([]*models.TimelineGroup, error)
|
||||
MyMediaGeoJSON(ctx context.Context) (interface{}, error)
|
||||
MapboxToken(ctx context.Context) (*string, error)
|
||||
ShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error)
|
||||
ShareTokenValidatePassword(ctx context.Context, token string, password *string) (bool, error)
|
||||
ShareToken(ctx context.Context, credentials models.ShareTokenCredentials) (*models.ShareToken, error)
|
||||
ShareTokenValidatePassword(ctx context.Context, credentials models.ShareTokenCredentials) (bool, error)
|
||||
Search(ctx context.Context, query string, limitMedia *int, limitAlbums *int) (*models.SearchResult, error)
|
||||
MyFaceGroups(ctx context.Context, paginate *models.Pagination) ([]*models.FaceGroup, error)
|
||||
FaceGroup(ctx context.Context, id int) (*models.FaceGroup, error)
|
||||
@@ -1073,7 +1073,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.Album(childComplexity, args["id"].(int)), true
|
||||
return e.complexity.Query.Album(childComplexity, args["id"].(int), args["tokenCredentials"].(*models.ShareTokenCredentials)), true
|
||||
|
||||
case "Query.faceGroup":
|
||||
if e.complexity.Query.FaceGroup == nil {
|
||||
@@ -1104,7 +1104,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.Media(childComplexity, args["id"].(int)), true
|
||||
return e.complexity.Query.Media(childComplexity, args["id"].(int), args["tokenCredentials"].(*models.ShareTokenCredentials)), true
|
||||
|
||||
case "Query.mediaList":
|
||||
if e.complexity.Query.MediaList == nil {
|
||||
@@ -1202,7 +1202,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.ShareToken(childComplexity, args["token"].(string), args["password"].(*string)), true
|
||||
return e.complexity.Query.ShareToken(childComplexity, args["credentials"].(models.ShareTokenCredentials)), true
|
||||
|
||||
case "Query.shareTokenValidatePassword":
|
||||
if e.complexity.Query.ShareTokenValidatePassword == nil {
|
||||
@@ -1214,7 +1214,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.ShareTokenValidatePassword(childComplexity, args["token"].(string), args["password"].(*string)), true
|
||||
return e.complexity.Query.ShareTokenValidatePassword(childComplexity, args["credentials"].(models.ShareTokenCredentials)), true
|
||||
|
||||
case "Query.siteInfo":
|
||||
if e.complexity.Query.SiteInfo == nil {
|
||||
@@ -1595,6 +1595,12 @@ input Ordering {
|
||||
order_direction: OrderDirection
|
||||
}
|
||||
|
||||
"Credentials used to identify and authenticate a share token"
|
||||
input ShareTokenCredentials {
|
||||
token: String!
|
||||
password: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
siteInfo: SiteInfo!
|
||||
|
||||
@@ -1614,13 +1620,19 @@ type Query {
|
||||
"Show only albums having favorites"
|
||||
onlyWithFavorites: Boolean
|
||||
): [Album!]!
|
||||
"Get album by id, user must own the album or be admin"
|
||||
album(id: ID!): Album!
|
||||
"""
|
||||
Get album by id, user must own the album or be admin
|
||||
If valid tokenCredentials are provided, the album may be retrived without further authentication
|
||||
"""
|
||||
album(id: ID!, tokenCredentials: ShareTokenCredentials): Album!
|
||||
|
||||
"List of media owned by the logged in user"
|
||||
myMedia(order: Ordering, paginate: Pagination): [Media!]!
|
||||
"Get media by id, user must own the media or be admin"
|
||||
media(id: ID!): Media!
|
||||
"""
|
||||
Get media by id, user must own the media or be admin.
|
||||
If valid tokenCredentials are provided, the media may be retrived without further authentication
|
||||
"""
|
||||
media(id: ID!, tokenCredentials: ShareTokenCredentials): Media!
|
||||
|
||||
"Get a list of media by their ids, user must own the media or be admin"
|
||||
mediaList(ids: [ID!]!): [Media!]!
|
||||
@@ -1632,8 +1644,8 @@ type Query {
|
||||
"Get the mapbox api token, returns null if mapbox is not enabled"
|
||||
mapboxToken: String
|
||||
|
||||
shareToken(token: String!, password: String): ShareToken!
|
||||
shareTokenValidatePassword(token: String!, password: String): Boolean!
|
||||
shareToken(credentials: ShareTokenCredentials!): ShareToken!
|
||||
shareTokenValidatePassword(credentials: ShareTokenCredentials!): Boolean!
|
||||
|
||||
search(query: String!, limitMedia: Int, limitAlbums: Int): SearchResult!
|
||||
|
||||
@@ -2491,6 +2503,15 @@ func (ec *executionContext) field_Query_album_args(ctx context.Context, rawArgs
|
||||
}
|
||||
}
|
||||
args["id"] = arg0
|
||||
var arg1 *models.ShareTokenCredentials
|
||||
if tmp, ok := rawArgs["tokenCredentials"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("tokenCredentials"))
|
||||
arg1, err = ec.unmarshalOShareTokenCredentials2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenCredentials(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["tokenCredentials"] = arg1
|
||||
return args, nil
|
||||
}
|
||||
|
||||
@@ -2536,6 +2557,15 @@ func (ec *executionContext) field_Query_media_args(ctx context.Context, rawArgs
|
||||
}
|
||||
}
|
||||
args["id"] = arg0
|
||||
var arg1 *models.ShareTokenCredentials
|
||||
if tmp, ok := rawArgs["tokenCredentials"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("tokenCredentials"))
|
||||
arg1, err = ec.unmarshalOShareTokenCredentials2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenCredentials(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["tokenCredentials"] = arg1
|
||||
return args, nil
|
||||
}
|
||||
|
||||
@@ -2689,48 +2719,30 @@ func (ec *executionContext) field_Query_search_args(ctx context.Context, rawArgs
|
||||
func (ec *executionContext) field_Query_shareTokenValidatePassword_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["token"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("token"))
|
||||
arg0, err = ec.unmarshalNString2string(ctx, tmp)
|
||||
var arg0 models.ShareTokenCredentials
|
||||
if tmp, ok := rawArgs["credentials"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("credentials"))
|
||||
arg0, err = ec.unmarshalNShareTokenCredentials2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenCredentials(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["token"] = arg0
|
||||
var arg1 *string
|
||||
if tmp, ok := rawArgs["password"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password"))
|
||||
arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["password"] = arg1
|
||||
args["credentials"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Query_shareToken_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["token"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("token"))
|
||||
arg0, err = ec.unmarshalNString2string(ctx, tmp)
|
||||
var arg0 models.ShareTokenCredentials
|
||||
if tmp, ok := rawArgs["credentials"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("credentials"))
|
||||
arg0, err = ec.unmarshalNShareTokenCredentials2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenCredentials(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["token"] = arg0
|
||||
var arg1 *string
|
||||
if tmp, ok := rawArgs["password"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password"))
|
||||
arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["password"] = arg1
|
||||
args["credentials"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
|
||||
@@ -6177,7 +6189,7 @@ func (ec *executionContext) _Query_album(ctx context.Context, field graphql.Coll
|
||||
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.Query().Album(rctx, args["id"].(int))
|
||||
return ec.resolvers.Query().Album(rctx, args["id"].(int), args["tokenCredentials"].(*models.ShareTokenCredentials))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
@@ -6261,7 +6273,7 @@ func (ec *executionContext) _Query_media(ctx context.Context, field graphql.Coll
|
||||
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.Query().Media(rctx, args["id"].(int))
|
||||
return ec.resolvers.Query().Media(rctx, args["id"].(int), args["tokenCredentials"].(*models.ShareTokenCredentials))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
@@ -6454,7 +6466,7 @@ func (ec *executionContext) _Query_shareToken(ctx context.Context, field graphql
|
||||
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.Query().ShareToken(rctx, args["token"].(string), args["password"].(*string))
|
||||
return ec.resolvers.Query().ShareToken(rctx, args["credentials"].(models.ShareTokenCredentials))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
@@ -6496,7 +6508,7 @@ func (ec *executionContext) _Query_shareTokenValidatePassword(ctx context.Contex
|
||||
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.Query().ShareTokenValidatePassword(rctx, args["token"].(string), args["password"].(*string))
|
||||
return ec.resolvers.Query().ShareTokenValidatePassword(rctx, args["credentials"].(models.ShareTokenCredentials))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
@@ -9208,6 +9220,34 @@ func (ec *executionContext) unmarshalInputPagination(ctx context.Context, obj in
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputShareTokenCredentials(ctx context.Context, obj interface{}) (models.ShareTokenCredentials, error) {
|
||||
var it models.ShareTokenCredentials
|
||||
var asMap = obj.(map[string]interface{})
|
||||
|
||||
for k, v := range asMap {
|
||||
switch k {
|
||||
case "token":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("token"))
|
||||
it.Token, err = ec.unmarshalNString2string(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "password":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password"))
|
||||
it.Password, err = ec.unmarshalOString2ᚖstring(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
// endregion **************************** input.gotpl *****************************
|
||||
|
||||
// region ************************** interface.gotpl ***************************
|
||||
@@ -11300,6 +11340,11 @@ func (ec *executionContext) marshalNShareToken2ᚖgithubᚗcomᚋphotoviewᚋpho
|
||||
return ec._ShareToken(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNShareTokenCredentials2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenCredentials(ctx context.Context, v interface{}) (models.ShareTokenCredentials, error) {
|
||||
res, err := ec.unmarshalInputShareTokenCredentials(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNSiteInfo2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐSiteInfo(ctx context.Context, sel ast.SelectionSet, v models.SiteInfo) graphql.Marshaler {
|
||||
return ec._SiteInfo(ctx, sel, &v)
|
||||
}
|
||||
@@ -11843,6 +11888,14 @@ func (ec *executionContext) marshalOShareToken2ᚖgithubᚗcomᚋphotoviewᚋpho
|
||||
return ec._ShareToken(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalOShareTokenCredentials2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareTokenCredentials(ctx context.Context, v interface{}) (*models.ShareTokenCredentials, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
res, err := ec.unmarshalInputShareTokenCredentials(ctx, v)
|
||||
return &res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) {
|
||||
res, err := graphql.UnmarshalString(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
|
||||
@@ -29,7 +29,16 @@ func (a *Album) BeforeSave(tx *gorm.DB) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Album) GetChildren(db *gorm.DB) (children []*Album, err error) {
|
||||
// GetChildren performs a recursive query to get all the children of the album.
|
||||
// An optional filter can be provided that can be used to modify the query on the children.
|
||||
func (a *Album) GetChildren(db *gorm.DB, filter func(*gorm.DB) *gorm.DB) (children []*Album, err error) {
|
||||
// SELECT * FROM sub_albums
|
||||
query := db.Model(&Album{}).Table("sub_albums")
|
||||
|
||||
if filter != nil {
|
||||
query = filter(query)
|
||||
}
|
||||
|
||||
err = db.Raw(`
|
||||
WITH recursive sub_albums AS (
|
||||
SELECT * FROM albums AS root WHERE id = ?
|
||||
@@ -37,8 +46,8 @@ func (a *Album) GetChildren(db *gorm.DB) (children []*Album, err error) {
|
||||
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
|
||||
?
|
||||
`, a.ID, query).Find(&children).Error
|
||||
|
||||
return children, err
|
||||
}
|
||||
|
||||
@@ -55,6 +55,12 @@ type SearchResult struct {
|
||||
Media []*Media `json:"media"`
|
||||
}
|
||||
|
||||
// Credentials used to identify and authenticate a share token
|
||||
type ShareTokenCredentials struct {
|
||||
Token string `json:"token"`
|
||||
Password *string `json:"password"`
|
||||
}
|
||||
|
||||
type TimelineGroup struct {
|
||||
Album *Album `json:"album"`
|
||||
Media []*Media `json:"media"`
|
||||
|
||||
@@ -2,11 +2,11 @@ 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"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -60,7 +60,30 @@ func (r *queryResolver) MyAlbums(ctx context.Context, order *models.Ordering, pa
|
||||
return albums, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Album(ctx context.Context, id int) (*models.Album, error) {
|
||||
func (r *queryResolver) Album(ctx context.Context, id int, tokenCredentials *models.ShareTokenCredentials) (*models.Album, error) {
|
||||
if tokenCredentials != nil {
|
||||
|
||||
shareToken, err := r.ShareToken(ctx, *tokenCredentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if shareToken.Album != nil {
|
||||
if *shareToken.AlbumID == id {
|
||||
return shareToken.Album, nil
|
||||
}
|
||||
|
||||
subAlbum, err := shareToken.Album.GetChildren(r.Database, func(query *gorm.DB) *gorm.DB { return query.Where("sub_albums.id = ?", id) })
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "find sub album of share token (%s)", tokenCredentials.Token)
|
||||
}
|
||||
|
||||
if len(subAlbum) > 0 {
|
||||
return subAlbum[0], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user := auth.UserFromContext(ctx)
|
||||
if user == nil {
|
||||
return nil, auth.ErrUnauthorized
|
||||
|
||||
@@ -42,7 +42,19 @@ func (r *queryResolver) MyMedia(ctx context.Context, order *models.Ordering, pag
|
||||
return media, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Media(ctx context.Context, id int) (*models.Media, error) {
|
||||
func (r *queryResolver) Media(ctx context.Context, id int, tokenCredentials *models.ShareTokenCredentials) (*models.Media, error) {
|
||||
if tokenCredentials != nil {
|
||||
|
||||
shareToken, err := r.ShareToken(ctx, *tokenCredentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if *shareToken.MediaID == id {
|
||||
return shareToken.Media, nil
|
||||
}
|
||||
}
|
||||
|
||||
user := auth.UserFromContext(ctx)
|
||||
if user == nil {
|
||||
return nil, auth.ErrUnauthorized
|
||||
|
||||
@@ -40,10 +40,10 @@ func (r *shareTokenResolver) HasPassword(ctx context.Context, obj *models.ShareT
|
||||
return hasPassword, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) ShareToken(ctx context.Context, tokenValue string, password *string) (*models.ShareToken, error) {
|
||||
func (r *queryResolver) ShareToken(ctx context.Context, credentials models.ShareTokenCredentials) (*models.ShareToken, error) {
|
||||
|
||||
var token models.ShareToken
|
||||
if err := r.Database.Preload(clause.Associations).Where("value = ?", tokenValue).First(&token).Error; err != nil {
|
||||
if err := r.Database.Preload(clause.Associations).Where("value = ?", credentials.Token).First(&token).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("share not found")
|
||||
} else {
|
||||
@@ -52,7 +52,7 @@ func (r *queryResolver) ShareToken(ctx context.Context, tokenValue string, passw
|
||||
}
|
||||
|
||||
if token.Password != nil {
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(*token.Password), []byte(*password)); err != nil {
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(*token.Password), []byte(*credentials.Password)); err != nil {
|
||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
return nil, errors.New("unauthorized")
|
||||
} else {
|
||||
@@ -64,9 +64,9 @@ func (r *queryResolver) ShareToken(ctx context.Context, tokenValue string, passw
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) ShareTokenValidatePassword(ctx context.Context, tokenValue string, password *string) (bool, error) {
|
||||
func (r *queryResolver) ShareTokenValidatePassword(ctx context.Context, credentials models.ShareTokenCredentials) (bool, error) {
|
||||
var token models.ShareToken
|
||||
if err := r.Database.Where("value = ?", tokenValue).First(&token).Error; err != nil {
|
||||
if err := r.Database.Where("value = ?", credentials.Token).First(&token).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, errors.New("share not found")
|
||||
} else {
|
||||
@@ -78,11 +78,11 @@ func (r *queryResolver) ShareTokenValidatePassword(ctx context.Context, tokenVal
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if password == nil {
|
||||
if credentials.Password == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(*token.Password), []byte(*password)); err != nil {
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(*token.Password), []byte(*credentials.Password)); err != nil {
|
||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
return false, nil
|
||||
} else {
|
||||
|
||||
@@ -326,7 +326,7 @@ func (r *mutationResolver) UserRemoveRootAlbum(ctx context.Context, userID int,
|
||||
return err
|
||||
}
|
||||
|
||||
children, err := album.GetChildren(tx)
|
||||
children, err := album.GetChildren(tx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ input Ordering {
|
||||
order_direction: OrderDirection
|
||||
}
|
||||
|
||||
"Credentials used to identify and authenticate a share token"
|
||||
input ShareTokenCredentials {
|
||||
token: String!
|
||||
password: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
siteInfo: SiteInfo!
|
||||
|
||||
@@ -37,13 +43,19 @@ type Query {
|
||||
"Show only albums having favorites"
|
||||
onlyWithFavorites: Boolean
|
||||
): [Album!]!
|
||||
"Get album by id, user must own the album or be admin"
|
||||
album(id: ID!): Album!
|
||||
"""
|
||||
Get album by id, user must own the album or be admin
|
||||
If valid tokenCredentials are provided, the album may be retrived without further authentication
|
||||
"""
|
||||
album(id: ID!, tokenCredentials: ShareTokenCredentials): Album!
|
||||
|
||||
"List of media owned by the logged in user"
|
||||
myMedia(order: Ordering, paginate: Pagination): [Media!]!
|
||||
"Get media by id, user must own the media or be admin"
|
||||
media(id: ID!): Media!
|
||||
"""
|
||||
Get media by id, user must own the media or be admin.
|
||||
If valid tokenCredentials are provided, the media may be retrived without further authentication
|
||||
"""
|
||||
media(id: ID!, tokenCredentials: ShareTokenCredentials): Media!
|
||||
|
||||
"Get a list of media by their ids, user must own the media or be admin"
|
||||
mediaList(ids: [ID!]!): [Media!]!
|
||||
@@ -55,8 +67,8 @@ type Query {
|
||||
"Get the mapbox api token, returns null if mapbox is not enabled"
|
||||
mapboxToken: String
|
||||
|
||||
shareToken(token: String!, password: String): ShareToken!
|
||||
shareTokenValidatePassword(token: String!, password: String): Boolean!
|
||||
shareToken(credentials: ShareTokenCredentials!): ShareToken!
|
||||
shareTokenValidatePassword(credentials: ShareTokenCredentials!): Boolean!
|
||||
|
||||
search(query: String!, limitMedia: Int, limitAlbums: Int): SearchResult!
|
||||
|
||||
|
||||
@@ -1,51 +1,114 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
import RouterPropTypes from 'react-router-prop-types'
|
||||
import Layout from '../../Layout'
|
||||
import AlbumGallery from '../../components/albumGallery/AlbumGallery'
|
||||
import styled from 'styled-components'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
|
||||
export const SHARE_ALBUM_QUERY = gql`
|
||||
query shareAlbumQuery(
|
||||
$id: ID!
|
||||
$token: String!
|
||||
$password: String
|
||||
$limit: Int
|
||||
$offset: Int
|
||||
) {
|
||||
album(id: $id, tokenCredentials: { token: $token, password: $password }) {
|
||||
id
|
||||
title
|
||||
subAlbums(order: { order_by: "title" }) {
|
||||
id
|
||||
title
|
||||
thumbnail {
|
||||
thumbnail {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
media(paginate: { limit: $limit, offset: $offset }) {
|
||||
id
|
||||
title
|
||||
type
|
||||
thumbnail {
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
downloads {
|
||||
title
|
||||
mediaUrl {
|
||||
url
|
||||
width
|
||||
height
|
||||
fileSize
|
||||
}
|
||||
}
|
||||
highRes {
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
videoWeb {
|
||||
url
|
||||
}
|
||||
exif {
|
||||
camera
|
||||
maker
|
||||
lens
|
||||
dateShot
|
||||
exposure
|
||||
aperture
|
||||
iso
|
||||
focalLength
|
||||
flash
|
||||
exposureProgram
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const AlbumSharePageWrapper = styled.div`
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const AlbumSharePage = ({ album, match }) => {
|
||||
const SubAlbumRoute = subProps => {
|
||||
const subAlbumId = subProps.match.params.subAlbum
|
||||
const subAlbum = album.subAlbums.find(x => x.id == subAlbumId)
|
||||
const AlbumSharePage = ({ albumID, token, password }) => {
|
||||
const { data, loading, error } = useQuery(SHARE_ALBUM_QUERY, {
|
||||
variables: {
|
||||
id: albumID,
|
||||
token,
|
||||
password,
|
||||
limit: 200,
|
||||
offset: 0,
|
||||
},
|
||||
})
|
||||
|
||||
if (!subAlbum) {
|
||||
return <div>Subalbum was not found</div>
|
||||
}
|
||||
|
||||
return <AlbumSharePage album={subAlbum} {...subProps} />
|
||||
if (error) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
SubAlbumRoute.propTypes = {
|
||||
...RouterPropTypes,
|
||||
if (loading) {
|
||||
return 'Loading...'
|
||||
}
|
||||
|
||||
const customAlbumLink = albumId => {
|
||||
return `${match.url}/${albumId}`
|
||||
}
|
||||
const album = data.album
|
||||
|
||||
return (
|
||||
<AlbumSharePageWrapper data-testid="AlbumSharePage">
|
||||
<Switch>
|
||||
<Route path={`${match.url}/:subAlbum`} component={SubAlbumRoute} />
|
||||
<Route path="/">
|
||||
<Layout title={album ? album.title : 'Loading album'}>
|
||||
<AlbumGallery album={album} customAlbumLink={customAlbumLink} />
|
||||
</Layout>
|
||||
</Route>
|
||||
</Switch>
|
||||
<Layout title={album ? album.title : 'Loading album'}>
|
||||
<AlbumGallery
|
||||
album={album}
|
||||
customAlbumLink={albumId => `/share/${token}/${albumId}`}
|
||||
/>
|
||||
</Layout>
|
||||
</AlbumSharePageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
AlbumSharePage.propTypes = {
|
||||
album: PropTypes.object.isRequired,
|
||||
match: RouterPropTypes.match,
|
||||
albumID: PropTypes.string.isRequired,
|
||||
token: PropTypes.string.isRequired,
|
||||
password: PropTypes.string,
|
||||
}
|
||||
|
||||
export default AlbumSharePage
|
||||
|
||||
@@ -11,90 +11,70 @@ import MediaSharePage from './MediaSharePage'
|
||||
|
||||
export const SHARE_TOKEN_QUERY = gql`
|
||||
query SharePageToken($token: String!, $password: String) {
|
||||
shareToken(token: $token, password: $password) {
|
||||
shareToken(credentials: { token: $token, password: $password }) {
|
||||
token
|
||||
album {
|
||||
...AlbumProps
|
||||
subAlbums {
|
||||
...AlbumProps
|
||||
subAlbums {
|
||||
...AlbumProps
|
||||
}
|
||||
}
|
||||
id
|
||||
}
|
||||
media {
|
||||
...MediaProps
|
||||
id
|
||||
title
|
||||
type
|
||||
thumbnail {
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
downloads {
|
||||
title
|
||||
mediaUrl {
|
||||
url
|
||||
width
|
||||
height
|
||||
fileSize
|
||||
}
|
||||
}
|
||||
highRes {
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
videoWeb {
|
||||
url
|
||||
}
|
||||
exif {
|
||||
camera
|
||||
maker
|
||||
lens
|
||||
dateShot
|
||||
exposure
|
||||
aperture
|
||||
iso
|
||||
focalLength
|
||||
flash
|
||||
exposureProgram
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment AlbumProps on Album {
|
||||
id
|
||||
title
|
||||
thumbnail {
|
||||
thumbnail {
|
||||
url
|
||||
}
|
||||
}
|
||||
media(order: { order_by: "title", order_direction: DESC }) {
|
||||
...MediaProps
|
||||
}
|
||||
}
|
||||
|
||||
fragment MediaProps on Media {
|
||||
id
|
||||
title
|
||||
type
|
||||
thumbnail {
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
downloads {
|
||||
title
|
||||
mediaUrl {
|
||||
url
|
||||
width
|
||||
height
|
||||
fileSize
|
||||
}
|
||||
}
|
||||
highRes {
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
videoWeb {
|
||||
url
|
||||
}
|
||||
exif {
|
||||
camera
|
||||
maker
|
||||
lens
|
||||
dateShot
|
||||
exposure
|
||||
aperture
|
||||
iso
|
||||
focalLength
|
||||
flash
|
||||
exposureProgram
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const VALIDATE_TOKEN_PASSWORD_QUERY = gql`
|
||||
query ShareTokenValidatePassword($token: String!, $password: String) {
|
||||
shareTokenValidatePassword(token: $token, password: $password)
|
||||
shareTokenValidatePassword(
|
||||
credentials: { token: $token, password: $password }
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
const AuthorizedTokenRoute = ({ match }) => {
|
||||
const token = match.params.token
|
||||
const password = getSharePassword(token)
|
||||
|
||||
const { loading, error, data } = useQuery(SHARE_TOKEN_QUERY, {
|
||||
variables: {
|
||||
token,
|
||||
password: getSharePassword(token),
|
||||
password,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -102,7 +82,39 @@ const AuthorizedTokenRoute = ({ match }) => {
|
||||
if (loading) return 'Loading...'
|
||||
|
||||
if (data.shareToken.album) {
|
||||
return <AlbumSharePage album={data.shareToken.album} match={match} />
|
||||
console.log('match', match)
|
||||
|
||||
const SharedSubAlbumPage = ({ match }) => {
|
||||
console.log('subalbum match', match)
|
||||
return (
|
||||
<AlbumSharePage
|
||||
albumID={match.params.subAlbum}
|
||||
token={token}
|
||||
password={password}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
SharedSubAlbumPage.propTypes = {
|
||||
match: PropTypes.any,
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/:subAlbum`}
|
||||
component={SharedSubAlbumPage}
|
||||
/>
|
||||
<Route exact path={match.path}>
|
||||
<AlbumSharePage
|
||||
albumID={data.shareToken.album.id}
|
||||
token={token}
|
||||
password={password}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
|
||||
if (data.shareToken.media) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import SharePage, {
|
||||
} from './SharePage'
|
||||
|
||||
import { SIDEBAR_DOWNLOAD_QUERY } from '../../components/sidebar/SidebarDownload'
|
||||
import { SHARE_ALBUM_QUERY } from './AlbumSharePage'
|
||||
|
||||
describe('load correct share page, based on graphql query', () => {
|
||||
const token = 'TOKEN123'
|
||||
@@ -48,13 +49,13 @@ describe('load correct share page, based on graphql query', () => {
|
||||
request: {
|
||||
query: SIDEBAR_DOWNLOAD_QUERY,
|
||||
variables: {
|
||||
mediaId: 1,
|
||||
mediaId: '1',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
media: {
|
||||
id: 1,
|
||||
id: '1',
|
||||
downloads: [],
|
||||
},
|
||||
},
|
||||
@@ -77,7 +78,7 @@ describe('load correct share page, based on graphql query', () => {
|
||||
token: token,
|
||||
album: null,
|
||||
media: {
|
||||
id: 1,
|
||||
id: '1',
|
||||
title: 'shared_image.jpg',
|
||||
type: 'photo',
|
||||
highRes: {
|
||||
@@ -114,20 +115,42 @@ describe('load correct share page, based on graphql query', () => {
|
||||
})
|
||||
|
||||
test('load album share page', async () => {
|
||||
const albumPageMock = {
|
||||
request: {
|
||||
query: SHARE_TOKEN_QUERY,
|
||||
variables: {
|
||||
token,
|
||||
password: null,
|
||||
const albumPageMock = [
|
||||
{
|
||||
request: {
|
||||
query: SHARE_TOKEN_QUERY,
|
||||
variables: {
|
||||
token,
|
||||
password: null,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
shareToken: {
|
||||
token: token,
|
||||
album: {
|
||||
id: '1',
|
||||
},
|
||||
media: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
shareToken: {
|
||||
{
|
||||
request: {
|
||||
query: SHARE_ALBUM_QUERY,
|
||||
variables: {
|
||||
id: '1',
|
||||
token: token,
|
||||
password: null,
|
||||
limit: 200,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
album: {
|
||||
id: 1,
|
||||
id: '1',
|
||||
title: 'album_title',
|
||||
subAlbums: [],
|
||||
thumbnail: {
|
||||
@@ -135,15 +158,14 @@ describe('load correct share page, based on graphql query', () => {
|
||||
},
|
||||
media: [],
|
||||
},
|
||||
media: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
render(
|
||||
<MockedProvider
|
||||
mocks={[...graphqlMocks, albumPageMock]}
|
||||
mocks={[...graphqlMocks, ...albumPageMock]}
|
||||
addTypename={false}
|
||||
defaultOptions={{
|
||||
// disable cache, required to make fragments work
|
||||
|
||||
@@ -74,7 +74,7 @@ const linkError = onError(({ graphQLErrors, networkError }) => {
|
||||
if (graphQLErrors.find(x => x.message == 'unauthorized')) {
|
||||
console.log('Unauthorized, clearing token cookie')
|
||||
clearTokenCookie()
|
||||
location.reload()
|
||||
// location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user