mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 22:19:07 +00:00
Finish localization
- Add user preferences section to settings - Make settings page available for all users - Move log out button to settings page - Make it possible for each user to choose their desired language
This commit is contained in:
@@ -6,17 +6,22 @@ import (
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/photoview/photoview/api/graphql/auth"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func IsAdmin(database *gorm.DB) func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
|
||||
return func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
|
||||
|
||||
user := auth.UserFromContext(ctx)
|
||||
if user == nil || user.Admin == false {
|
||||
return nil, errors.New("user must be admin")
|
||||
}
|
||||
|
||||
return next(ctx)
|
||||
func IsAdmin(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
|
||||
user := auth.UserFromContext(ctx)
|
||||
if user == nil || user.Admin == false {
|
||||
return nil, errors.New("user must be admin")
|
||||
}
|
||||
|
||||
return next(ctx)
|
||||
}
|
||||
|
||||
func IsAuthorized(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
|
||||
user := auth.UserFromContext(ctx)
|
||||
if user == nil {
|
||||
return nil, auth.ErrUnauthorized
|
||||
}
|
||||
|
||||
return next(ctx)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,6 +68,47 @@ type TimelineGroup struct {
|
||||
Date time.Time `json:"date"`
|
||||
}
|
||||
|
||||
type LanguageTranslation string
|
||||
|
||||
const (
|
||||
LanguageTranslationEn LanguageTranslation = "en"
|
||||
LanguageTranslationDa LanguageTranslation = "da"
|
||||
)
|
||||
|
||||
var AllLanguageTranslation = []LanguageTranslation{
|
||||
LanguageTranslationEn,
|
||||
LanguageTranslationDa,
|
||||
}
|
||||
|
||||
func (e LanguageTranslation) IsValid() bool {
|
||||
switch e {
|
||||
case LanguageTranslationEn, LanguageTranslationDa:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e LanguageTranslation) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *LanguageTranslation) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = LanguageTranslation(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid LanguageTranslation", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e LanguageTranslation) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
type MediaType string
|
||||
|
||||
const (
|
||||
|
||||
@@ -36,6 +36,13 @@ type AccessToken struct {
|
||||
Expire time.Time `gorm:"not null;index"`
|
||||
}
|
||||
|
||||
type UserPreferences struct {
|
||||
Model
|
||||
UserID int `gorm:"not null;index"`
|
||||
User User `gorm:"constraint:OnDelete:CASCADE;"`
|
||||
Language *LanguageTranslation
|
||||
}
|
||||
|
||||
var ErrorInvalidUserCredentials = errors.New("invalid credentials")
|
||||
|
||||
func AuthorizeUser(db *gorm.DB, username string, password string) (*User, error) {
|
||||
|
||||
@@ -154,6 +154,49 @@ func (r *mutationResolver) InitialSetupWizard(ctx context.Context, username stri
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) MyUserPreferences(ctx context.Context) (*models.UserPreferences, error) {
|
||||
user := auth.UserFromContext(ctx)
|
||||
if user == nil {
|
||||
return nil, auth.ErrUnauthorized
|
||||
}
|
||||
|
||||
userPref := models.UserPreferences{
|
||||
UserID: user.ID,
|
||||
}
|
||||
if err := r.Database.Where("user_id = ?", user.ID).FirstOrCreate(&userPref).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &userPref, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) ChangeUserPreferences(ctx context.Context, language *string) (*models.UserPreferences, error) {
|
||||
user := auth.UserFromContext(ctx)
|
||||
if user == nil {
|
||||
return nil, auth.ErrUnauthorized
|
||||
}
|
||||
|
||||
var langTrans *models.LanguageTranslation = nil
|
||||
if language != nil {
|
||||
lng := models.LanguageTranslation(*language)
|
||||
langTrans = &lng
|
||||
}
|
||||
|
||||
var userPref models.UserPreferences
|
||||
if err := r.Database.Where("user_id = ?", user.ID).FirstOrInit(&userPref).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userPref.UserID = user.ID
|
||||
userPref.Language = langTrans
|
||||
|
||||
if err := r.Database.Save(&userPref).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &userPref, nil
|
||||
}
|
||||
|
||||
// Admin queries
|
||||
func (r *mutationResolver) UpdateUser(ctx context.Context, id int, username *string, password *string, admin *bool) (*models.User, error) {
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
directive @isAuthorized on FIELD_DEFINITION
|
||||
directive @isAdmin on FIELD_DEFINITION
|
||||
|
||||
scalar Time
|
||||
@@ -30,7 +31,9 @@ type Query {
|
||||
"List of registered users, must be admin to call"
|
||||
user(order: Ordering, paginate: Pagination): [User!]! @isAdmin
|
||||
"Information about the currently logged in user"
|
||||
myUser: User!
|
||||
myUser: User! @isAuthorized
|
||||
|
||||
myUserPreferences: UserPreferences! @isAuthorized
|
||||
|
||||
"List of albums owned by the logged in user."
|
||||
myAlbums(
|
||||
@@ -42,7 +45,7 @@ type Query {
|
||||
showEmpty: Boolean
|
||||
"Show only albums having favorites"
|
||||
onlyWithFavorites: Boolean
|
||||
): [Album!]!
|
||||
): [Album!]! @isAuthorized
|
||||
"""
|
||||
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
|
||||
@@ -50,7 +53,7 @@ type Query {
|
||||
album(id: ID!, tokenCredentials: ShareTokenCredentials): Album!
|
||||
|
||||
"List of media owned by the logged in user"
|
||||
myMedia(order: Ordering, paginate: Pagination): [Media!]!
|
||||
myMedia(order: Ordering, paginate: Pagination): [Media!]! @isAuthorized
|
||||
"""
|
||||
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
|
||||
@@ -60,10 +63,10 @@ type Query {
|
||||
"Get a list of media by their ids, user must own the media or be admin"
|
||||
mediaList(ids: [ID!]!): [Media!]!
|
||||
|
||||
myTimeline(paginate: Pagination, onlyFavorites: Boolean): [TimelineGroup!]!
|
||||
myTimeline(paginate: Pagination, onlyFavorites: Boolean): [TimelineGroup!]! @isAuthorized
|
||||
|
||||
"Get media owned by the logged in user, returned in GeoJson format"
|
||||
myMediaGeoJson: Any!
|
||||
myMediaGeoJson: Any! @isAuthorized
|
||||
"Get the mapbox api token, returns null if mapbox is not enabled"
|
||||
mapboxToken: String
|
||||
|
||||
@@ -72,8 +75,8 @@ type Query {
|
||||
|
||||
search(query: String!, limitMedia: Int, limitAlbums: Int): SearchResult!
|
||||
|
||||
myFaceGroups(paginate: Pagination): [FaceGroup!]!
|
||||
faceGroup(id: ID!): FaceGroup!
|
||||
myFaceGroups(paginate: Pagination): [FaceGroup!]! @isAuthorized
|
||||
faceGroup(id: ID!): FaceGroup! @isAuthorized
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@@ -89,19 +92,19 @@ type Mutation {
|
||||
"Scan all users for new media"
|
||||
scanAll: ScannerResult! @isAdmin
|
||||
"Scan a single user for new media"
|
||||
scanUser(userId: ID!): ScannerResult!
|
||||
scanUser(userId: ID!): ScannerResult! @isAdmin
|
||||
|
||||
"Generate share token for album"
|
||||
shareAlbum(albumId: ID!, expire: Time, password: String): ShareToken
|
||||
shareAlbum(albumId: ID!, expire: Time, password: String): ShareToken @isAuthorized
|
||||
"Generate share token for media"
|
||||
shareMedia(mediaId: ID!, expire: Time, password: String): ShareToken
|
||||
shareMedia(mediaId: ID!, expire: Time, password: String): ShareToken @isAuthorized
|
||||
"Delete a share token by it's token value"
|
||||
deleteShareToken(token: String!): ShareToken
|
||||
deleteShareToken(token: String!): ShareToken @isAuthorized
|
||||
"Set a password for a token, if null is passed for the password argument, the password will be cleared"
|
||||
protectShareToken(token: String!, password: String): ShareToken
|
||||
protectShareToken(token: String!, password: String): ShareToken @isAuthorized
|
||||
|
||||
"Mark or unmark a media as being a favorite"
|
||||
favoriteMedia(mediaId: ID!, favorite: Boolean!): Media
|
||||
favoriteMedia(mediaId: ID!, favorite: Boolean!): Media @isAuthorized
|
||||
|
||||
updateUser(
|
||||
id: ID!
|
||||
@@ -124,21 +127,23 @@ type Mutation {
|
||||
Set how often, in seconds, the server should automatically scan for new media,
|
||||
a value of 0 will disable periodic scans
|
||||
"""
|
||||
setPeriodicScanInterval(interval: Int!): Int!
|
||||
setPeriodicScanInterval(interval: Int!): Int! @isAdmin
|
||||
|
||||
"Set max number of concurrent scanner jobs running at once"
|
||||
setScannerConcurrentWorkers(workers: Int!): Int!
|
||||
setScannerConcurrentWorkers(workers: Int!): Int! @isAdmin
|
||||
|
||||
changeUserPreferences(language: String): UserPreferences! @isAuthorized
|
||||
|
||||
"Assign a label to a face group, set label to null to remove the current one"
|
||||
setFaceGroupLabel(faceGroupID: ID!, label: String): FaceGroup!
|
||||
setFaceGroupLabel(faceGroupID: ID!, label: String): FaceGroup! @isAuthorized
|
||||
"Merge two face groups into a single one, all ImageFaces from source will be moved to destination"
|
||||
combineFaceGroups(destinationFaceGroupID: ID!, sourceFaceGroupID: ID!): FaceGroup!
|
||||
combineFaceGroups(destinationFaceGroupID: ID!, sourceFaceGroupID: ID!): FaceGroup! @isAuthorized
|
||||
"Move a list of ImageFaces to another face group"
|
||||
moveImageFaces(imageFaceIDs: [ID!]!, destinationFaceGroupID: ID!): FaceGroup!
|
||||
moveImageFaces(imageFaceIDs: [ID!]!, destinationFaceGroupID: ID!): FaceGroup! @isAuthorized
|
||||
"Check all unlabeled faces to see if they match a labeled FaceGroup, and move them if they match"
|
||||
recognizeUnlabeledFaces: [ImageFace!]!
|
||||
recognizeUnlabeledFaces: [ImageFace!]! @isAuthorized
|
||||
"Move a list of ImageFaces to a new face group"
|
||||
detachImageFaces(imageFaceIDs: [ID!]!): FaceGroup!
|
||||
detachImageFaces(imageFaceIDs: [ID!]!): FaceGroup! @isAuthorized
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
@@ -216,6 +221,16 @@ type User {
|
||||
#shareTokens: [ShareToken]
|
||||
}
|
||||
|
||||
enum LanguageTranslation {
|
||||
en,
|
||||
da
|
||||
}
|
||||
|
||||
type UserPreferences {
|
||||
id: ID!
|
||||
language: LanguageTranslation
|
||||
}
|
||||
|
||||
type Album {
|
||||
id: ID!
|
||||
title: String!
|
||||
|
||||
Reference in New Issue
Block a user