From e66a9199a1a5602a06d4fa5aff9997f5ae685511 Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Mon, 27 Sep 2021 20:23:10 +0200 Subject: [PATCH] Add timeline resolver tests --- .../models/actions/timeline_actions.go | 36 ++++++ .../models/actions/timeline_actions_test.go | 112 ++++++++++++++++++ api/graphql/models/user.go | 21 ++++ api/graphql/resolvers/media.go | 18 +-- api/graphql/resolvers/timeline.go | 27 +---- 5 files changed, 172 insertions(+), 42 deletions(-) create mode 100644 api/graphql/models/actions/timeline_actions.go create mode 100644 api/graphql/models/actions/timeline_actions_test.go diff --git a/api/graphql/models/actions/timeline_actions.go b/api/graphql/models/actions/timeline_actions.go new file mode 100644 index 00000000..d80a1c62 --- /dev/null +++ b/api/graphql/models/actions/timeline_actions.go @@ -0,0 +1,36 @@ +package actions + +import ( + "time" + + "github.com/photoview/photoview/api/graphql/models" + "gorm.io/gorm" +) + +func MyTimeline(db *gorm.DB, user *models.User, paginate *models.Pagination, onlyFavorites *bool, fromDate *time.Time) ([]*models.Media, error) { + query := db. + Joins("JOIN albums ON media.album_id = albums.id"). + Where("albums.id IN (?)", db.Table("user_albums").Select("user_albums.album_id").Where("user_id = ?", user.ID)). + Order("YEAR(media.date_shot) DESC"). + Order("MONTH(media.date_shot) DESC"). + Order("DAY(media.date_shot) DESC"). + Order("albums.title ASC"). + Order("TIME(media.date_shot) DESC") + + if fromDate != nil { + query = query.Where("media.date_shot < ?", fromDate) + } + + if onlyFavorites != nil && *onlyFavorites == true { + query = query.Where("media.id IN (?)", db.Table("user_media_data").Select("user_media_data.media_id").Where("user_media_data.user_id = ?", user.ID).Where("user_media_data.favorite = 1")) + } + + query = models.FormatSQL(query, nil, paginate) + + var media []*models.Media + if err := query.Find(&media).Error; err != nil { + return nil, err + } + + return media, nil +} diff --git a/api/graphql/models/actions/timeline_actions_test.go b/api/graphql/models/actions/timeline_actions_test.go new file mode 100644 index 00000000..92fee1cc --- /dev/null +++ b/api/graphql/models/actions/timeline_actions_test.go @@ -0,0 +1,112 @@ +package actions_test + +import ( + "testing" + "time" + + "github.com/photoview/photoview/api/graphql/models" + "github.com/photoview/photoview/api/graphql/models/actions" + "github.com/photoview/photoview/api/test_utils" + "github.com/stretchr/testify/assert" +) + +func TestMyTimeline(t *testing.T) { + db := test_utils.DatabaseTest(t) + + password := "1234" + user, err := models.RegisterUser(db, "user", &password, false) + assert.NoError(t, err) + + rootAlbum := models.Album{ + Title: "root", + Path: "/photos", + } + + assert.NoError(t, db.Save(&rootAlbum).Error) + + childAlbum := models.Album{ + Title: "subalbum", + Path: "/photos/subalbum", + ParentAlbumID: &rootAlbum.ID, + } + + assert.NoError(t, db.Save(&childAlbum).Error) + + assert.NoError(t, db.Model(&user).Association("Albums").Append(&rootAlbum)) + assert.NoError(t, db.Model(&user).Association("Albums").Append(&childAlbum)) + + media := []models.Media{ + { + Title: "pic1", + Path: "/photos/pic1", + AlbumID: rootAlbum.ID, + DateShot: time.Unix(1632758400, 0), // Sep 27 2021 16:00:00 + }, + { + Title: "pic2", + Path: "/photos/pic2", + AlbumID: rootAlbum.ID, + DateShot: time.Unix(1628762400, 0), // Aug 12 2021 10:00:00 + }, + { + Title: "pic3", + Path: "/photos/subalbum/pic3", + AlbumID: childAlbum.ID, + DateShot: time.Unix(1632763800, 0), // Sep 27 2021 17:30:00 + }, + { + Title: "pic4", + Path: "/photos/subalbum/pic4", + AlbumID: childAlbum.ID, + DateShot: time.Unix(1628775900, 0), // Aug 12 2021 13:45:00 + }, + } + + assert.NoError(t, db.Save(&media).Error) + + _, err = user.FavoriteMedia(db, media[0].ID, true) + assert.NoError(t, err) + + // Add media not owned by first user + anotherUser, err := models.RegisterUser(db, "user2", &password, false) + assert.NoError(t, err) + + anotherAlbum := models.Album{ + Title: "AnotherAlbum", + Path: "/another", + } + + assert.NoError(t, db.Save(&anotherAlbum).Error) + + anotherMedia := models.Media{ + Title: "anotherPic", + Path: "/another/anotherPic", + AlbumID: anotherAlbum.ID, + } + + assert.NoError(t, db.Save(&anotherMedia).Error) + assert.NoError(t, db.Model(&anotherUser).Association("Albums").Append(&anotherAlbum)) + + t.Run("MyTimeline with no filters", func(t *testing.T) { + timelineMedia, err := actions.MyTimeline(db, user, nil, nil, nil) + + assert.NoError(t, err) + assert.Len(t, timelineMedia, 4) + }) + + t.Run("MyTimeline with only favorites", func(t *testing.T) { + favorites := true + timelineMedia, err := actions.MyTimeline(db, user, nil, &favorites, nil) + + assert.NoError(t, err) + assert.Len(t, timelineMedia, 1) + }) + + t.Run("MyTimeline before date", func(t *testing.T) { + beforeDate := time.Unix(1629792000, 0) // Aug 24 2021 08:00:00 + timelineMedia, err := actions.MyTimeline(db, user, nil, nil, &beforeDate) + + assert.NoError(t, err) + assert.Len(t, timelineMedia, 2) + }) +} diff --git a/api/graphql/models/user.go b/api/graphql/models/user.go index 49d021a5..854885e0 100644 --- a/api/graphql/models/user.go +++ b/api/graphql/models/user.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" + "gorm.io/gorm/clause" ) type User struct { @@ -185,3 +186,23 @@ func (user *User) OwnsAlbum(db *gorm.DB, album *Album) (bool, error) { return len(ownedParents) > 0, nil } + +// FavoriteMedia sets/clears a media as favorite for the user +func (user *User) FavoriteMedia(db *gorm.DB, mediaID int, favorite bool) (*Media, error) { + userMediaData := UserMediaData{ + UserID: user.ID, + MediaID: mediaID, + Favorite: favorite, + } + + if err := db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&userMediaData).Error; err != nil { + return nil, errors.Wrapf(err, "update user favorite media in database") + } + + var media Media + if err := db.First(&media, mediaID).Error; err != nil { + return nil, errors.Wrap(err, "get media from database after favorite update") + } + + return &media, nil +} diff --git a/api/graphql/resolvers/media.go b/api/graphql/resolvers/media.go index a6601756..66c5a711 100644 --- a/api/graphql/resolvers/media.go +++ b/api/graphql/resolvers/media.go @@ -11,7 +11,6 @@ import ( "github.com/photoview/photoview/api/graphql/models/actions" "github.com/photoview/photoview/api/scanner/face_detection" "github.com/pkg/errors" - "gorm.io/gorm/clause" ) func (r *queryResolver) MyMedia(ctx context.Context, order *models.Ordering, paginate *models.Pagination) ([]*models.Media, error) { @@ -198,22 +197,7 @@ func (r *mutationResolver) FavoriteMedia(ctx context.Context, mediaID int, favor return nil, auth.ErrUnauthorized } - userMediaData := models.UserMediaData{ - UserID: user.ID, - MediaID: mediaID, - Favorite: favorite, - } - - 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 + return user.FavoriteMedia(r.Database, mediaID, favorite) } func (r *mediaResolver) Faces(ctx context.Context, media *models.Media) ([]*models.ImageFace, error) { diff --git a/api/graphql/resolvers/timeline.go b/api/graphql/resolvers/timeline.go index a26f7dd0..aef3f6ea 100644 --- a/api/graphql/resolvers/timeline.go +++ b/api/graphql/resolvers/timeline.go @@ -6,6 +6,7 @@ import ( "github.com/photoview/photoview/api/graphql/auth" "github.com/photoview/photoview/api/graphql/models" + "github.com/photoview/photoview/api/graphql/models/actions" ) func (r *queryResolver) MyTimeline(ctx context.Context, paginate *models.Pagination, onlyFavorites *bool, fromDate *time.Time) ([]*models.Media, error) { @@ -14,29 +15,5 @@ func (r *queryResolver) MyTimeline(ctx context.Context, paginate *models.Paginat return nil, auth.ErrUnauthorized } - query := r.Database. - Joins("JOIN albums ON media.album_id = albums.id"). - Where("albums.id IN (?)", r.Database.Table("user_albums").Select("user_albums.album_id").Where("user_id = ?", user.ID)). - Order("YEAR(media.date_shot) DESC"). - Order("MONTH(media.date_shot) DESC"). - Order("DAY(media.date_shot) DESC"). - Order("albums.title ASC"). - Order("TIME(media.date_shot) DESC") - - if fromDate != nil { - query = query.Where("media.date_shot < ?", fromDate) - } - - if onlyFavorites != nil && *onlyFavorites == true { - query = query.Where("media.id IN (?)", r.Database.Table("user_media_data").Select("user_media_data.media_id").Where("user_media_data.user_id = ?", user.ID).Where("user_media_data.favorite = 1")) - } - - query = models.FormatSQL(query, nil, paginate) - - var media []*models.Media - if err := query.Find(&media).Error; err != nil { - return nil, err - } - - return media, nil + return actions.MyTimeline(r.Database, user, paginate, onlyFavorites, fromDate) }