Load shared albums recursively

This solves #143 and #229
This commit is contained in:
viktorstrate
2021-03-03 15:55:55 +01:00
parent c18759e6db
commit 3d9ae530af
12 changed files with 390 additions and 178 deletions

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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"`

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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!

View File

@@ -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>
if (error) {
return error.message
}
return <AlbumSharePage album={subAlbum} {...subProps} />
if (loading) {
return 'Loading...'
}
SubAlbumRoute.propTypes = {
...RouterPropTypes,
}
const album = data.album
const customAlbumLink = albumId => {
return `${match.url}/${albumId}`
}
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} />
<AlbumGallery
album={album}
customAlbumLink={albumId => `/share/${token}/${albumId}`}
/>
</Layout>
</Route>
</Switch>
</AlbumSharePageWrapper>
)
}
AlbumSharePage.propTypes = {
album: PropTypes.object.isRequired,
match: RouterPropTypes.match,
albumID: PropTypes.string.isRequired,
token: PropTypes.string.isRequired,
password: PropTypes.string,
}
export default AlbumSharePage

View File

@@ -11,37 +11,12 @@ 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
}
}
}
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
@@ -80,21 +55,26 @@ export const SHARE_TOKEN_QUERY = gql`
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) {

View File

@@ -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,7 +115,8 @@ describe('load correct share page, based on graphql query', () => {
})
test('load album share page', async () => {
const albumPageMock = {
const albumPageMock = [
{
request: {
query: SHARE_TOKEN_QUERY,
variables: {
@@ -127,7 +129,28 @@ describe('load correct share page, based on graphql query', () => {
shareToken: {
token: token,
album: {
id: 1,
id: '1',
},
media: null,
},
},
},
},
{
request: {
query: SHARE_ALBUM_QUERY,
variables: {
id: '1',
token: token,
password: null,
limit: 200,
offset: 0,
},
},
result: {
data: {
album: {
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

View File

@@ -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()
}
}