Files
photoview/api/graphql/resolvers/user.go
viktorstrate a14b12b8d4 Fix bug where DetectFaces would be called with the wrong media.
This happend because the go routine did not capture the media variable,
and so the it would change before the go routine could start
and call the DetectFaces function.
2021-03-16 22:27:27 +01:00

390 lines
9.0 KiB
Go

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/photoview/photoview/api/scanner/face_detection"
"github.com/photoview/photoview/api/utils"
"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, order *models.Ordering, paginate *models.Pagination) ([]*models.User, error) {
var users []*models.User
if err := models.FormatSQL(r.Database.Model(models.User{}), order, paginate).Scan(&users).Error; err != nil {
return nil, err
}
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)
if user == nil {
return nil, auth.ErrUnauthorized
}
return user, nil
}
func (r *mutationResolver) AuthorizeUser(ctx context.Context, username string, password string) (*models.AuthorizeResult, error) {
user, err := models.AuthorizeUser(r.Database, username, password)
if err != nil {
return &models.AuthorizeResult{
Success: false,
Status: err.Error(),
}, nil
}
var token *models.AccessToken
transactionError := r.Database.Transaction(func(tx *gorm.DB) error {
token, err = user.GenerateAccessToken(tx)
if err != nil {
return err
}
return nil
})
if transactionError != nil {
return nil, 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)
if err != nil {
return nil, err
}
if !siteInfo.InitialSetup {
return nil, errors.New("not initial setup")
}
rootPath = path.Clean(rootPath)
var token *models.AccessToken
transactionError := r.Database.Transaction(func(tx *gorm.DB) error {
if err := tx.Exec("UPDATE site_info SET initial_setup = false").Error; err != nil {
return err
}
user, err := models.RegisterUser(tx, username, &password, true)
if err != nil {
return err
}
_, err = scanner.NewRootAlbum(tx, rootPath, user)
if err != nil {
return err
}
token, err = user.GenerateAccessToken(tx)
if err != nil {
return err
}
return nil
})
if transactionError != nil {
return &models.AuthorizeResult{
Success: false,
Status: err.Error(),
}, nil
}
return &models.AuthorizeResult{
Success: true,
Status: "ok",
Token: &token.Value,
}, nil
}
// Admin queries
func (r *mutationResolver) UpdateUser(ctx context.Context, id int, username *string, password *string, admin *bool) (*models.User, error) {
if username == nil && password == nil && admin == nil {
return nil, errors.New("no updates requested")
}
var user models.User
if err := r.Database.First(&user, id).Error; err != nil {
return nil, err
}
if username != nil {
user.Username = *username
}
if password != nil {
hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(*password), 12)
if err != nil {
return nil, err
}
hashedPass := string(hashedPassBytes)
user.Password = &hashedPass
}
if admin != nil {
user.Admin = *admin
}
if err := r.Database.Save(&user).Error; err != nil {
return nil, errors.Wrap(err, "failed to update user")
}
return &user, nil
}
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, admin)
if err != nil {
return err
}
return nil
})
if transactionError != nil {
return nil, transactionError
}
return user, nil
}
func (r *mutationResolver) DeleteUser(ctx context.Context, id int) (*models.User, error) {
var user models.User
deletedAlbumIDs := make([]int, 0)
err := r.Database.Transaction(func(tx *gorm.DB) error {
if err := tx.First(&user, id).Error; err != nil {
return err
}
userAlbums := user.Albums
if err := tx.Model(&user).Association("Albums").Find(&userAlbums); err != nil {
return err
}
if err := tx.Model(&user).Association("Albums").Clear(); err != nil {
return err
}
for _, album := range userAlbums {
var associatedUsers = tx.Model(album).Association("Owners").Count()
if associatedUsers == 0 {
deletedAlbumIDs = append(deletedAlbumIDs, album.ID)
if err := tx.Delete(album).Error; err != nil {
return err
}
}
}
if err := tx.Delete(&user).Error; err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
// If there is only one associated user, clean up the cache folder and delete the album row
for _, deletedAlbumID := range deletedAlbumIDs {
cachePath := path.Join(utils.MediaCachePath(), strconv.Itoa(int(deletedAlbumID)))
if err := os.RemoveAll(cachePath); err != nil {
return &user, err
}
}
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
transactionError := 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, nil)
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 userAlbumCount int
if err := tx.Raw("SELECT COUNT(user_id) FROM user_albums WHERE album_id = ?", albumID).Scan(&userAlbumCount).Error; err != nil {
return err
}
if userAlbumCount == 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 transactionError != nil {
return nil, transactionError
}
if deletedAlbumIDs != nil {
// Delete albums from cache
for _, id := range deletedAlbumIDs {
cacheAlbumPath := path.Join(utils.MediaCachePath(), strconv.Itoa(id))
if err := os.RemoveAll(cacheAlbumPath); err != nil {
return nil, err
}
}
// Reload faces as media might have been deleted
if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(r.Database); err != nil {
return nil, err
}
}
return &album, nil
}