From 7430adbb333570ce8f6a118c10a0931c335b579f Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Mon, 27 May 2024 09:16:21 +0200 Subject: [PATCH] Search: Change time_format to RFC3339 and add "edited" filter #4300 Signed-off-by: Michael Mayer --- internal/api/share_preview.go | 2 +- internal/entity/album.go | 20 +-- internal/entity/auth_session.go | 4 +- internal/entity/auth_user.go | 2 +- internal/entity/auth_user_fixtures.go | 2 +- internal/entity/auth_user_share.go | 8 +- internal/entity/auth_user_share_fixtures.go | 4 +- internal/entity/auth_user_share_test.go | 2 +- internal/entity/camera_fixtures.go | 4 +- internal/entity/cell_fixtures.go | 40 ++--- internal/entity/details.go | 2 +- internal/entity/details_fixtures.go | 12 +- internal/entity/entity_save_test.go | 6 +- internal/entity/entity_time.go | 20 ++- internal/entity/entity_time_test.go | 24 ++- internal/entity/entity_update_test.go | 6 +- internal/entity/face.go | 4 +- internal/entity/face_fixtures.go | 30 ++-- internal/entity/file.go | 2 +- internal/entity/folder.go | 2 +- internal/entity/label_fixtures.go | 128 +++++++-------- internal/entity/link.go | 8 +- internal/entity/link_test.go | 2 +- internal/entity/marker.go | 8 +- internal/entity/passcode.go | 8 +- internal/entity/photo.go | 12 +- internal/entity/photo_datetime_test.go | 12 +- internal/entity/photo_estimate.go | 6 +- internal/entity/photo_estimate_test.go | 6 +- internal/entity/photo_fixtures.go | 12 +- internal/entity/photo_merge.go | 6 +- internal/entity/photo_merge_test.go | 12 +- internal/entity/photo_optimize.go | 2 +- internal/entity/place_fixtures.go | 32 ++-- internal/entity/reaction.go | 4 +- internal/entity/reaction_fixtures.go | 6 +- internal/entity/service_fixtures.go | 12 +- internal/entity/subject_fixtures.go | 24 +-- internal/entity/subject_test.go | 6 +- internal/form/search_photos.go | 51 +++--- internal/form/search_photos_geo.go | 5 +- internal/form/search_photos_geo_test.go | 67 ++++++++ internal/form/search_photos_test.go | 70 +++++++- internal/photoprism/faces_cluster.go | 2 +- internal/photoprism/faces_match.go | 2 +- internal/photoprism/index.go | 2 +- internal/query/markers_test.go | 4 +- internal/search/albums.go | 4 +- internal/search/photos.go | 21 ++- internal/search/photos_filter_time_test.go | 152 ++++++++++++++++++ internal/search/photos_geo.go | 21 ++- .../search/photos_geo_filter_time_test.go | 139 ++++++++++++++++ internal/search/photos_results.go | 2 +- 53 files changed, 751 insertions(+), 293 deletions(-) create mode 100644 internal/search/photos_filter_time_test.go create mode 100644 internal/search/photos_geo_filter_time_test.go diff --git a/internal/api/share_preview.go b/internal/api/share_preview.go index 99edfb069c5..fe77f82b6e6 100644 --- a/internal/api/share_preview.go +++ b/internal/api/share_preview.go @@ -51,7 +51,7 @@ func SharePreview(router *gin.RouterGroup) { previewFilename := filepath.Join(thumbPath, shared+fs.ExtJPEG) - expires := entity.TimeStamp().Add(-1 * time.Hour) + expires := entity.Now().Add(-1 * time.Hour) if info, err := os.Stat(previewFilename); err != nil { log.Debugf("share: creating new preview for %s", clean.Log(shared)) diff --git a/internal/entity/album.go b/internal/entity/album.go index e61e78c3108..c613d743b26 100644 --- a/internal/entity/album.go +++ b/internal/entity/album.go @@ -138,7 +138,7 @@ func AddPhotoToUserAlbums(photoUid string, albums []string, userUid string) (err } // Refresh updated timestamp. - err = UpdateAlbum(albumUid, Map{"updated_at": TimePointer()}) + err = UpdateAlbum(albumUid, Map{"updated_at": TimeStamp()}) } } @@ -152,7 +152,7 @@ func NewAlbum(albumTitle, albumType string) *Album { // NewUserAlbum creates a new album owned by a user. func NewUserAlbum(albumTitle, albumType, userUid string) *Album { - now := TimeStamp() + now := Now() // Set default type. if albumType == "" { @@ -182,7 +182,7 @@ func NewFolderAlbum(albumTitle, albumPath, albumFilter string) *Album { return nil } - now := TimeStamp() + now := Now() result := &Album{ AlbumOrder: sortby.Added, @@ -205,7 +205,7 @@ func NewMomentsAlbum(albumTitle, albumSlug, albumFilter string) *Album { return nil } - now := TimeStamp() + now := Now() result := &Album{ AlbumOrder: sortby.Oldest, @@ -230,7 +230,7 @@ func NewStateAlbum(albumTitle, albumSlug, albumFilter string) *Album { return nil } - now := TimeStamp() + now := Now() result := &Album{ AlbumOrder: sortby.Newest, @@ -261,7 +261,7 @@ func NewMonthAlbum(albumTitle, albumSlug string, year, month int) *Album { Public: true, } - now := TimeStamp() + now := Now() result := &Album{ AlbumOrder: sortby.Oldest, @@ -392,7 +392,7 @@ func FindAlbum(find Album) *Album { // Filter by creator if the album has not been published yet. if find.CreatedBy != "" { - stmt = stmt.Where("published_at > ? OR created_by = ?", TimeStamp(), find.CreatedBy) + stmt = stmt.Where("published_at > ? OR created_by = ?", Now(), find.CreatedBy) } // Find first matching record. @@ -710,7 +710,7 @@ func (m *Album) Delete() error { return nil } - now := TimeStamp() + now := Now() if err := UnscopedDb().Model(m).UpdateColumns(Map{"updated_at": now, "deleted_at": now}).Error; err != nil { return err @@ -817,7 +817,7 @@ func (m *Album) AddPhotos(photos PhotosInterface) (added PhotoAlbums) { } // Refresh updated timestamp. - if err := UpdateAlbum(m.AlbumUID, Map{"updated_at": TimePointer()}); err != nil { + if err := UpdateAlbum(m.AlbumUID, Map{"updated_at": TimeStamp()}); err != nil { log.Errorf("album: %s (update %s)", err.Error(), m) } @@ -845,7 +845,7 @@ func (m *Album) RemovePhotos(UIDs []string) (removed PhotoAlbums) { } // Refresh updated timestamp. - if err := UpdateAlbum(m.AlbumUID, Map{"updated_at": TimePointer()}); err != nil { + if err := UpdateAlbum(m.AlbumUID, Map{"updated_at": TimeStamp()}); err != nil { log.Errorf("album: %s (update %s)", err.Error(), m) } diff --git a/internal/entity/auth_session.go b/internal/entity/auth_session.go index 9777baad3da..77b095a49f6 100644 --- a/internal/entity/auth_session.go +++ b/internal/entity/auth_session.go @@ -156,7 +156,7 @@ func (m *Session) Regenerate() *Session { m.RefID = rnd.RefID(SessionPrefix) // Get current time. - now := TimeStamp() + now := Now() // Set timestamps to now. m.CreatedAt = now @@ -961,7 +961,7 @@ func (m *Session) SetClientIP(ip string) { if m.LoginIP == "" { m.LoginIP = ip - m.LoginAt = TimeStamp() + m.LoginAt = Now() } return diff --git a/internal/entity/auth_user.go b/internal/entity/auth_user.go index 2efe65d4902..96f807e0322 100644 --- a/internal/entity/auth_user.go +++ b/internal/entity/auth_user.go @@ -388,7 +388,7 @@ func (m *User) UpdateLoginTime() *time.Time { return nil } - timeStamp := TimePointer() + timeStamp := TimeStamp() if err := Db().Model(m).UpdateColumn("LoginAt", timeStamp).Error; err != nil { return nil diff --git a/internal/entity/auth_user_fixtures.go b/internal/entity/auth_user_fixtures.go index d1927ea8265..373b3340626 100644 --- a/internal/entity/auth_user_fixtures.go +++ b/internal/entity/auth_user_fixtures.go @@ -118,7 +118,7 @@ var UserFixtures = UserMap{ CanLogin: false, WebDAV: true, CanInvite: false, - DeletedAt: TimePointer(), + DeletedAt: TimeStamp(), UserSettings: &UserSettings{ UITheme: "", MapsStyle: "", diff --git a/internal/entity/auth_user_share.go b/internal/entity/auth_user_share.go index 63fa30a9bcf..fe5729db226 100644 --- a/internal/entity/auth_user_share.go +++ b/internal/entity/auth_user_share.go @@ -86,8 +86,8 @@ func NewUserShare(userUID, shareUid string, perm uint, expires *time.Time) *User ShareUID: shareUid, Perm: perm, RefID: rnd.RefID(SharePrefix), - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), ExpiresAt: expires, } @@ -119,7 +119,7 @@ func FindUserShares(userUid string) UserShares { } // Find matching record. - if err := UnscopedDb().Find(&found, "user_uid = ? AND (expires_at IS NULL OR expires_at > ?)", userUid, TimeStamp()).Error; err != nil { + if err := UnscopedDb().Find(&found, "user_uid = ? AND (expires_at IS NULL OR expires_at > ?)", userUid, Now()).Error; err != nil { event.AuditWarn([]string{"user %s", "find shares", "%s"}, clean.Log(userUid), err) return nil } @@ -156,7 +156,7 @@ func (m *UserShare) UpdateLink(link Link) error { m.LinkUID = link.LinkUID m.Comment = link.Comment m.Perm = link.Perm - m.UpdatedAt = TimeStamp() + m.UpdatedAt = Now() m.ExpiresAt = link.ExpiresAt() values := Map{ diff --git a/internal/entity/auth_user_share_fixtures.go b/internal/entity/auth_user_share_fixtures.go index de70f5f672d..24c458b5bb8 100644 --- a/internal/entity/auth_user_share_fixtures.go +++ b/internal/entity/auth_user_share_fixtures.go @@ -33,8 +33,8 @@ var UserShareFixtures = UserShareMap{ Comment: "The quick brown fox jumps over the lazy dog.", Perm: PermShare, RefID: rnd.RefID(SharePrefix), - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, } diff --git a/internal/entity/auth_user_share_test.go b/internal/entity/auth_user_share_test.go index 645c42c0a06..88fbdb7bcea 100644 --- a/internal/entity/auth_user_share_test.go +++ b/internal/entity/auth_user_share_test.go @@ -25,7 +25,7 @@ func TestUserShares_Contains(t *testing.T) { } func TestNewUserShare(t *testing.T) { - expires := TimeStamp().Add(time.Hour * 48) + expires := Now().Add(time.Hour * 48) m := NewUserShare(Admin.GetUID(), AlbumFixtures.Get("berlin-2019").AlbumUID, PermReact, &expires) assert.True(t, m.HasID()) diff --git a/internal/entity/camera_fixtures.go b/internal/entity/camera_fixtures.go index cb495e6f35c..34e3d3a820c 100644 --- a/internal/entity/camera_fixtures.go +++ b/internal/entity/camera_fixtures.go @@ -97,8 +97,8 @@ var CameraFixtures = CameraMap{ CameraType: "", CameraDescription: "", CameraNotes: "", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, }, } diff --git a/internal/entity/cell_fixtures.go b/internal/entity/cell_fixtures.go index ae8d1565486..ab36e71a227 100644 --- a/internal/entity/cell_fixtures.go +++ b/internal/entity/cell_fixtures.go @@ -29,8 +29,8 @@ var CellFixtures = CellMap{ CellName: "Adosada Platform", CellCategory: "botanical garden", Place: PlaceFixtures.Pointer("mexico"), - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "caravan park": { ID: s2.TokenPrefix + "1ef75a71a36c", @@ -41,13 +41,13 @@ var CellFixtures = CellMap{ PlaceCity: "Mandeni", PlaceState: "KwaZulu-Natal", PlaceCountry: "za", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, CellName: "Lobotes Caravan Park", CellCategory: "camping", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "zinkwazi": { ID: s2.TokenPrefix + "1ef744d1e28c", @@ -55,8 +55,8 @@ var CellFixtures = CellMap{ Place: PlaceFixtures.Pointer("zinkwazi"), CellName: "Zinkwazi Beach", CellCategory: "beach", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "hassloch": { ID: s2.TokenPrefix + "1ef744d1e280", @@ -64,8 +64,8 @@ var CellFixtures = CellMap{ Place: PlaceFixtures.Pointer("holidaypark"), CellName: "Holiday Park", CellCategory: "park", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "emptyNameLongCity": { ID: s2.TokenPrefix + "1ef744d1e281", @@ -73,8 +73,8 @@ var CellFixtures = CellMap{ Place: PlaceFixtures.Pointer("emptyNameLongCity"), CellName: "", CellCategory: "botanical garden", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "emptyNameShortCity": { ID: s2.TokenPrefix + "1ef744d1e282", @@ -82,8 +82,8 @@ var CellFixtures = CellMap{ Place: PlaceFixtures.Pointer("emptyNameShortCity"), CellName: "", CellCategory: "botanical garden", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "veryLongLocName": { ID: s2.TokenPrefix + "1ef744d1e283", @@ -91,8 +91,8 @@ var CellFixtures = CellMap{ Place: PlaceFixtures.Pointer("veryLongLocName"), CellName: "longlonglonglonglonglonglonglonglonglonglonglonglongName", CellCategory: "cape", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "mediumLongLocName": { ID: s2.TokenPrefix + "1ef744d1e283", @@ -100,8 +100,8 @@ var CellFixtures = CellMap{ Place: PlaceFixtures.Pointer("mediumLongLocName"), CellName: "longlonglonglonglonglongName", CellCategory: "botanical garden", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "Neckarbrücke": { ID: s2.TokenPrefix + "1ef744d1e284", @@ -109,8 +109,8 @@ var CellFixtures = CellMap{ Place: PlaceFixtures.Pointer("Germany"), CellName: "Neckarbrücke", CellCategory: "", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, } diff --git a/internal/entity/details.go b/internal/entity/details.go index a0645023f08..84e48758c2e 100644 --- a/internal/entity/details.go +++ b/internal/entity/details.go @@ -70,7 +70,7 @@ func FirstOrCreateDetails(m *Details) *Details { return m } else if err := Db().Where("photo_id = ?", m.PhotoID).First(&result).Error; err == nil { if m.CreatedAt.IsZero() { - m.CreatedAt = TimeStamp() + m.CreatedAt = Now() } return &result diff --git a/internal/entity/details_fixtures.go b/internal/entity/details_fixtures.go index ac5adad3c63..2710e06e80b 100644 --- a/internal/entity/details_fixtures.go +++ b/internal/entity/details_fixtures.go @@ -31,8 +31,8 @@ var DetailsFixtures = DetailsMap{ Artist: "Hans", Copyright: "copy", License: "MIT", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), KeywordsSrc: "meta", NotesSrc: "manual", SubjectSrc: "meta", @@ -48,8 +48,8 @@ var DetailsFixtures = DetailsMap{ Artist: "Hans", Copyright: "copy", License: "MIT", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), KeywordsSrc: "", NotesSrc: "", SubjectSrc: "meta", @@ -65,8 +65,8 @@ var DetailsFixtures = DetailsMap{ Artist: "Jens Mander", Copyright: "Copyright 2020", License: report.NotAssigned, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), KeywordsSrc: "meta", NotesSrc: "manual", SubjectSrc: "meta", diff --git a/internal/entity/entity_save_test.go b/internal/entity/entity_save_test.go index 58d0142372b..d79edc6b91f 100644 --- a/internal/entity/entity_save_test.go +++ b/internal/entity/entity_save_test.go @@ -12,7 +12,7 @@ import ( func TestSave(t *testing.T) { t.Run("HasCreatedUpdatedAt", func(t *testing.T) { id := 99999 + rand.IntN(10000) - m := Photo{ID: uint(id), PhotoUID: rnd.GenerateUID(PhotoUID), UpdatedAt: TimeStamp(), CreatedAt: TimeStamp()} + m := Photo{ID: uint(id), PhotoUID: rnd.GenerateUID(PhotoUID), UpdatedAt: Now(), CreatedAt: Now()} if err := m.Save(); err != nil { t.Fatal(err) @@ -23,7 +23,7 @@ func TestSave(t *testing.T) { }) t.Run("HasCreatedAt", func(t *testing.T) { id := 99999 + rand.IntN(10000) - m := Photo{ID: uint(id), PhotoUID: rnd.GenerateUID(PhotoUID), CreatedAt: TimeStamp()} + m := Photo{ID: uint(id), PhotoUID: rnd.GenerateUID(PhotoUID), CreatedAt: Now()} if err := m.Save(); err != nil { t.Fatal(err) @@ -34,7 +34,7 @@ func TestSave(t *testing.T) { }) t.Run("NoCreatedAt", func(t *testing.T) { id := 99999 + rand.IntN(10000) - m := Photo{ID: uint(id), PhotoUID: rnd.GenerateUID(PhotoUID), CreatedAt: TimeStamp()} + m := Photo{ID: uint(id), PhotoUID: rnd.GenerateUID(PhotoUID), CreatedAt: Now()} if err := m.Save(); err != nil { t.Fatal(err) diff --git a/internal/entity/entity_time.go b/internal/entity/entity_time.go index 0bfc81c9ee9..77df064d5ab 100644 --- a/internal/entity/entity_time.go +++ b/internal/entity/entity_time.go @@ -12,17 +12,27 @@ func UTC() time.Time { return time.Now().UTC() } -// TimeStamp returns the current timestamp in UTC rounded to seconds. -func TimeStamp() time.Time { +// Now returns the current time in UTC, truncated to seconds. +func Now() time.Time { return UTC().Truncate(time.Second) } -// TimePointer returns a pointer to the current timestamp. -func TimePointer() *time.Time { - t := TimeStamp() +// TimeStamp returns a reference to the current time in UTC, truncated to seconds. +func TimeStamp() *time.Time { + t := Now() return &t } +// Time returns a reference to the specified time in UTC, truncated to seconds, or nil if it is invalid. +func Time(s string) *time.Time { + if t, err := time.Parse(time.RFC3339, s); err == nil { + t = t.UTC().Truncate(time.Second) + return &t + } + + return nil +} + // Seconds converts an int to a duration in seconds. func Seconds(s int) time.Duration { return time.Duration(s) * time.Second diff --git a/internal/entity/entity_time_test.go b/internal/entity/entity_time_test.go index 36c2c2206c1..1786617ffe9 100644 --- a/internal/entity/entity_time_test.go +++ b/internal/entity/entity_time_test.go @@ -36,19 +36,19 @@ func TestUTC(t *testing.T) { }) } -func TestTimeStamp(t *testing.T) { +func TestNow(t *testing.T) { t.Run("UTC", func(t *testing.T) { - if TimeStamp().Location() != time.UTC { + if Now().Location() != time.UTC { t.Fatal("timestamp zone must be UTC") } }) t.Run("Past", func(t *testing.T) { - if TimeStamp().After(time.Now().Add(time.Second)) { + if Now().After(time.Now().Add(time.Second)) { t.Fatal("timestamp should be in the past from now") } }) t.Run("JSON", func(t *testing.T) { - t1 := TimeStamp().Add(time.Nanosecond * 123456) + t1 := Now().Add(time.Nanosecond * 123456) if b, err := t1.MarshalJSON(); err != nil { t.Fatal(err) @@ -58,7 +58,7 @@ func TestTimeStamp(t *testing.T) { }) t.Run("UnixMicro", func(t *testing.T) { t1 := time.Date(-3000, 1, 1, 1, 1, 1, 0, time.UTC) - t2 := TimeStamp().Add(time.Nanosecond * 123456) + t2 := Now().Add(time.Nanosecond * 123456) t3 := time.Date(3000, 1, 1, 1, 1, 1, 0, time.UTC) ms1 := t1.UnixMilli() @@ -94,8 +94,8 @@ func TestTimeStamp(t *testing.T) { }) } -func TestTimePointer(t *testing.T) { - result := TimePointer() +func TestTimeStamp(t *testing.T) { + result := TimeStamp() if result == nil { t.Fatal("result must not be nil") @@ -110,6 +110,16 @@ func TestTimePointer(t *testing.T) { } } +func TestTime(t *testing.T) { + result := Time("2022-01-02T13:04:05+01:00") + + if result == nil { + t.Fatal("result must not be nil") + } + + assert.Equal(t, "2022-01-02T12:04:05Z", result.Format("2006-01-02T15:04:05Z07:00")) +} + func TestSeconds(t *testing.T) { result := Seconds(23) diff --git a/internal/entity/entity_update_test.go b/internal/entity/entity_update_test.go index 83954f3dcd1..6f43875b3e6 100644 --- a/internal/entity/entity_update_test.go +++ b/internal/entity/entity_update_test.go @@ -13,7 +13,7 @@ import ( func TestUpdate(t *testing.T) { t.Run("IDMissing", func(t *testing.T) { uid := rnd.GenerateUID(PhotoUID) - m := &Photo{ID: 0, PhotoUID: uid, UpdatedAt: TimeStamp(), CreatedAt: TimeStamp(), PhotoTitle: "Foo"} + m := &Photo{ID: 0, PhotoUID: uid, UpdatedAt: Now(), CreatedAt: Now(), PhotoTitle: "Foo"} updatedAt := m.UpdatedAt err := Update(m, "ID", "PhotoUID") @@ -27,7 +27,7 @@ func TestUpdate(t *testing.T) { }) t.Run("UIDMissing", func(t *testing.T) { id := 99999 + rand.IntN(10000) - m := &Photo{ID: uint(id), PhotoUID: "", UpdatedAt: TimeStamp(), CreatedAt: TimeStamp(), PhotoTitle: "Foo"} + m := &Photo{ID: uint(id), PhotoUID: "", UpdatedAt: Now(), CreatedAt: Now(), PhotoTitle: "Foo"} updatedAt := m.UpdatedAt err := Update(m, "ID", "PhotoUID") @@ -42,7 +42,7 @@ func TestUpdate(t *testing.T) { t.Run("NotUpdated", func(t *testing.T) { id := 99999 + rand.IntN(10000) uid := rnd.GenerateUID(PhotoUID) - m := &Photo{ID: uint(id), PhotoUID: uid, UpdatedAt: time.Now(), CreatedAt: TimeStamp(), PhotoTitle: "Foo"} + m := &Photo{ID: uint(id), PhotoUID: uid, UpdatedAt: time.Now(), CreatedAt: Now(), PhotoTitle: "Foo"} updatedAt := m.UpdatedAt err := Update(m, "ID", "PhotoUID") diff --git a/internal/entity/face.go b/internal/entity/face.go index 2af650e4690..306aa2299e7 100644 --- a/internal/entity/face.go +++ b/internal/entity/face.go @@ -114,7 +114,7 @@ func (m *Face) SetEmbeddings(embeddings face.Embeddings) (err error) { // Matched updates the match timestamp. func (m *Face) Matched() error { - m.MatchedAt = TimePointer() + m.MatchedAt = TimeStamp() return UnscopedDb().Model(m).UpdateColumns(Map{"MatchedAt": m.MatchedAt}).Error } @@ -192,7 +192,7 @@ func (m *Face) ResolveCollision(embeddings face.Embeddings) (resolved bool, err log.Warnf("faces: %s has ambiguous subject %s with a similar face at dist %f with source %s", m.ID, SubjNames.Log(m.SubjUID), dist, SrcString(m.FaceSrc)) m.FaceKind = int(face.AmbiguousFace) - m.UpdatedAt = TimeStamp() + m.UpdatedAt = Now() m.MatchedAt = &m.UpdatedAt m.Collisions++ m.CollisionRadius = dist diff --git a/internal/entity/face_fixtures.go b/internal/entity/face_fixtures.go index 214d37b960a..2c92a8c29c0 100644 --- a/internal/entity/face_fixtures.go +++ b/internal/entity/face_fixtures.go @@ -4,7 +4,7 @@ package entity var UnknownFace = Face{ ID: UnknownID, FaceSrc: SrcDefault, - MatchedAt: TimePointer(), + MatchedAt: TimeStamp(), SubjUID: "", EmbeddingJSON: []byte{}, } @@ -36,8 +36,8 @@ var FaceFixtures = FaceMap{ SampleRadius: 0.8, Samples: 5, Collisions: 1, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "unknown": Face{ ID: "IW2P73ISBCUFPIAWSIOZKRDCHHFHC35S", @@ -48,8 +48,8 @@ var FaceFixtures = FaceMap{ Samples: 1, Collisions: 0, MatchedAt: &editTime, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "joe-biden": Face{ ID: "VF7ANLDET2BKZNT4VQWJMMC6HBEFDOG6", @@ -60,8 +60,8 @@ var FaceFixtures = FaceMap{ Samples: 33, Collisions: 0, CollisionRadius: 0, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "jane-doe": Face{ ID: "VF7ANLDET2BKZNT4VQWJMMC6HBEFDOG7", @@ -72,8 +72,8 @@ var FaceFixtures = FaceMap{ Samples: 3, Collisions: 0, CollisionRadius: 0, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "fa-gr": Face{ ID: "TOSCDXCS4VI3PGIUTCNIQCNI6HSFXQVZ", @@ -84,8 +84,8 @@ var FaceFixtures = FaceMap{ Samples: 4, Collisions: 0, CollisionRadius: 0, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "actress-1": Face{ ID: "GMH5NISEEULNJL6RATITOA3TMZXMTMCI", @@ -96,8 +96,8 @@ var FaceFixtures = FaceMap{ Samples: 4, Collisions: 0, CollisionRadius: 0, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "actor-1": Face{ ID: "PI6A2XGOTUXEFI7CBF4KCI5I2I3JEJHS", @@ -108,8 +108,8 @@ var FaceFixtures = FaceMap{ Samples: 4, Collisions: 0, CollisionRadius: 0, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, } diff --git a/internal/entity/file.go b/internal/entity/file.go index 5475a239c32..c708d075cd6 100644 --- a/internal/entity/file.go +++ b/internal/entity/file.go @@ -362,7 +362,7 @@ func (m *File) Delete(permanently bool) error { // Purge removes a file from the index by marking it as missing. func (m *File) Purge() error { - deletedAt := TimeStamp() + deletedAt := Now() m.FileMissing = true m.FilePrimary = false m.DeletedAt = &deletedAt diff --git a/internal/entity/folder.go b/internal/entity/folder.go index ed59c1ad4f1..15e8bd84aaf 100644 --- a/internal/entity/folder.go +++ b/internal/entity/folder.go @@ -63,7 +63,7 @@ func (m *Folder) BeforeCreate(scope *gorm.Scope) error { // NewFolder creates a new file system directory entity. func NewFolder(root, pathName string, modTime time.Time) Folder { - now := TimeStamp() + now := Now() pathName = strings.Trim(pathName, string(os.PathSeparator)) diff --git a/internal/entity/label_fixtures.go b/internal/entity/label_fixtures.go index fde32c8288e..03230014b23 100644 --- a/internal/entity/label_fixtures.go +++ b/internal/entity/label_fixtures.go @@ -40,8 +40,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -57,8 +57,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 2, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -74,8 +74,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 3, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -91,8 +91,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 4, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -108,8 +108,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 5, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -125,8 +125,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -142,8 +142,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -159,8 +159,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -176,8 +176,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 4, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -193,8 +193,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -210,8 +210,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -227,8 +227,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -244,8 +244,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -261,8 +261,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -278,8 +278,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -295,8 +295,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -312,8 +312,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -329,8 +329,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -346,8 +346,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -363,8 +363,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -380,8 +380,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -397,8 +397,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -414,8 +414,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -431,8 +431,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -448,8 +448,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -465,8 +465,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -482,8 +482,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -499,8 +499,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -516,8 +516,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -533,8 +533,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -550,8 +550,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, @@ -567,8 +567,8 @@ var LabelFixtures = LabelMap{ LabelNotes: "", PhotoCount: 1, LabelCategories: []*Label{}, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, New: false, }, diff --git a/internal/entity/link.go b/internal/entity/link.go index 984d773381a..493c291746c 100644 --- a/internal/entity/link.go +++ b/internal/entity/link.go @@ -64,7 +64,7 @@ func NewLink(shareUid string, canComment, canEdit bool) Link { // NewUserLink creates a sharing link owned by a user. func NewUserLink(shareUid, userUid string) Link { - now := TimeStamp() + now := Now() result := Link{ LinkUID: rnd.GenerateUID(LinkUID), @@ -95,7 +95,7 @@ func (m *Link) ExpiresAt() *time.Time { return nil } - expires := TimeStamp() + expires := Now() expires = m.ModifiedAt.Add(Seconds(m.LinkExpires)) return &expires @@ -110,7 +110,7 @@ func (m *Link) Expired() bool { if expires := m.ExpiresAt(); expires == nil { return false } else { - return TimeStamp().After(*expires) + return Now().After(*expires) } } @@ -157,7 +157,7 @@ func (m *Link) Save() error { return fmt.Errorf("empty link token") } - m.ModifiedAt = TimeStamp() + m.ModifiedAt = Now() return Db().Save(m).Error } diff --git a/internal/entity/link_test.go b/internal/entity/link_test.go index dc69b711c80..c936847a182 100644 --- a/internal/entity/link_test.go +++ b/internal/entity/link_test.go @@ -19,7 +19,7 @@ func TestLink_Expired(t *testing.T) { link := NewLink("ss6sg6bxpogaaba1", true, false) - link.ModifiedAt = TimeStamp().Add(-7 * Day) + link.ModifiedAt = Now().Add(-7 * Day) link.LinkExpires = 0 assert.False(t, link.Expired()) diff --git a/internal/entity/marker.go b/internal/entity/marker.go index 796d5717c02..89dc296856c 100644 --- a/internal/entity/marker.go +++ b/internal/entity/marker.go @@ -255,7 +255,7 @@ func (m *Marker) SetFace(f *Face, dist float64) (updated bool, err error) { // Skip update if the same face is already set. if m.SubjUID == f.SubjUID && m.FaceID == f.ID { // Update matching timestamp. - m.MatchedAt = TimePointer() + m.MatchedAt = TimeStamp() return false, m.Updates(Map{"MatchedAt": m.MatchedAt}) } @@ -300,7 +300,7 @@ func (m *Marker) SetFace(f *Face, dist float64) (updated bool, err error) { updated = m.FaceID != faceID || m.SubjUID != subjUID || m.SubjSrc != subjSrc // Update matching timestamp. - m.MatchedAt = TimePointer() + m.MatchedAt = TimeStamp() if err := m.Updates(Map{"FaceID": m.FaceID, "FaceDist": m.FaceDist, "SubjUID": m.SubjUID, "SubjSrc": m.SubjSrc, "MarkerReview": false, "MatchedAt": m.MatchedAt}); err != nil { return false, err @@ -547,7 +547,7 @@ func (m *Marker) ClearFace() (updated bool, err error) { // Remove face references. m.face = nil m.FaceID = "" - m.MatchedAt = TimePointer() + m.MatchedAt = TimeStamp() // Remove subject if set automatically. if m.SubjSrc == SrcAuto { @@ -584,7 +584,7 @@ func (m *Marker) RefreshPhotos() error { // Matched updates the match timestamp. func (m *Marker) Matched() error { - m.MatchedAt = TimePointer() + m.MatchedAt = TimeStamp() return UnscopedDb().Model(m).UpdateColumns(Map{"MatchedAt": m.MatchedAt}).Error } diff --git a/internal/entity/passcode.go b/internal/entity/passcode.go index f8407cb78b0..b2103e97f74 100644 --- a/internal/entity/passcode.go +++ b/internal/entity/passcode.go @@ -41,8 +41,8 @@ func NewPasscode(uid string, keyUrl, recoveryCode string) (*Passcode, error) { UID: uid, KeyURL: keyUrl, RecoveryCode: clean.Token(recoveryCode), - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), VerifiedAt: nil, ActivatedAt: nil, } @@ -284,7 +284,7 @@ func (m *Passcode) Valid(code string) (valid bool, recovery bool, err error) { // Set verified timestamp if nil. if valid && m.VerifiedAt == nil { - m.VerifiedAt = TimePointer() + m.VerifiedAt = TimeStamp() err = m.Updates(Map{"VerifiedAt": m.VerifiedAt}) } @@ -303,7 +303,7 @@ func (m *Passcode) Activate() (err error) { } else if m.ActivatedAt != nil { return authn.ErrPasscodeAlreadyActivated } else { - m.ActivatedAt = TimePointer() + m.ActivatedAt = TimeStamp() err = m.Updates(Map{"ActivatedAt": m.ActivatedAt}) } diff --git a/internal/entity/photo.go b/internal/entity/photo.go index d2e1a9a4a3d..e918992e468 100644 --- a/internal/entity/photo.go +++ b/internal/entity/photo.go @@ -193,7 +193,7 @@ func SavePhotoForm(model Photo, form form.Photo) error { log.Errorf("photo: %s %s while indexing keywords", model.String(), err.Error()) } - edited := TimeStamp() + edited := Now() model.EditedAt = &edited model.PhotoQuality = model.QualityScore() @@ -395,7 +395,7 @@ func (m *Photo) ClassifyLabels() classify.Labels { // BeforeCreate creates a random UID if needed before inserting a new row to the database. func (m *Photo) BeforeCreate(scope *gorm.Scope) error { if m.TakenAt.IsZero() || m.TakenAtLocal.IsZero() { - now := TimeStamp() + now := Now() if err := scope.SetColumn("TakenAt", now); err != nil { return err @@ -418,7 +418,7 @@ func (m *Photo) BeforeCreate(scope *gorm.Scope) error { // BeforeSave ensures the existence of TakenAt properties before indexing or updating a photo func (m *Photo) BeforeSave(scope *gorm.Scope) error { if m.TakenAt.IsZero() || m.TakenAtLocal.IsZero() { - now := TimeStamp() + now := Now() if err := scope.SetColumn("TakenAt", now); err != nil { return err @@ -747,7 +747,7 @@ func (m *Photo) Archive() error { return nil } - deletedAt := TimeStamp() + deletedAt := Now() if err := Db().Model(&PhotoAlbum{}).Where("photo_uid = ?", m.PhotoUID).UpdateColumn("hidden", true).Error; err != nil { return err @@ -795,7 +795,7 @@ func (m *Photo) Delete(permanently bool) (files Files, err error) { } } - return files, m.Updates(map[string]interface{}{"DeletedAt": TimeStamp(), "PhotoQuality": -1}) + return files, m.Updates(map[string]interface{}{"DeletedAt": Now(), "PhotoQuality": -1}) } // DeletePermanently permanently removes a photo from the index. @@ -931,7 +931,7 @@ func (m *Photo) Approve() error { return err } - edited := TimeStamp() + edited := Now() m.EditedAt = &edited m.PhotoQuality = m.QualityScore() diff --git a/internal/entity/photo_datetime_test.go b/internal/entity/photo_datetime_test.go index 2f39826dcee..f33afb4a4df 100644 --- a/internal/entity/photo_datetime_test.go +++ b/internal/entity/photo_datetime_test.go @@ -11,30 +11,30 @@ import ( func TestPhoto_TrustedTime(t *testing.T) { t.Run("MissingTakenAt", func(t *testing.T) { - m := Photo{ID: 1, TakenAt: time.Time{}, TakenAtLocal: TimeStamp(), TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} + m := Photo{ID: 1, TakenAt: time.Time{}, TakenAtLocal: Now(), TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} assert.False(t, m.TrustedTime()) }) t.Run("MissingTakenAtLocal", func(t *testing.T) { - m := Photo{ID: 1, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} + m := Photo{ID: 1, TakenAt: Now(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} assert.False(t, m.TrustedTime()) }) t.Run("MissingTimeZone", func(t *testing.T) { - n := TimeStamp() + n := Now() m := Photo{ID: 1, TakenAt: n, TakenAtLocal: n, TakenSrc: SrcMeta, TimeZone: ""} assert.False(t, m.TrustedTime()) }) t.Run("SrcAuto", func(t *testing.T) { - n := TimeStamp() + n := Now() m := Photo{ID: 1, TakenAt: n, TakenAtLocal: n, TakenSrc: SrcAuto, TimeZone: "Europe/Berlin"} assert.False(t, m.TrustedTime()) }) t.Run("SrcEstimate", func(t *testing.T) { - n := TimeStamp() + n := Now() m := Photo{ID: 1, TakenAt: n, TakenAtLocal: n, TakenSrc: SrcEstimate, TimeZone: "Europe/Berlin"} assert.False(t, m.TrustedTime()) }) t.Run("SrcMeta", func(t *testing.T) { - n := TimeStamp() + n := Now() m := Photo{ID: 1, TakenAt: n, TakenAtLocal: n, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} assert.True(t, m.TrustedTime()) }) diff --git a/internal/entity/photo_estimate.go b/internal/entity/photo_estimate.go index d76508a2cd6..d0eea1aab87 100644 --- a/internal/entity/photo_estimate.go +++ b/internal/entity/photo_estimate.go @@ -52,7 +52,7 @@ func (m *Photo) EstimateCountry() { if countryCode != unknown { m.PhotoCountry = countryCode m.PlaceSrc = SrcEstimate - m.EstimatedAt = TimePointer() + m.EstimatedAt = TimeStamp() log.Debugf("photo: estimated country for %s is %s", m, clean.Log(m.CountryName())) } } @@ -64,12 +64,12 @@ func (m *Photo) EstimateLocation(force bool) { return } else if force || m.EstimatedAt == nil { // Proceed. - } else if hours := TimeStamp().Sub(*m.EstimatedAt); hours < MetadataEstimateInterval { + } else if hours := Now().Sub(*m.EstimatedAt); hours < MetadataEstimateInterval { // Ignore if location has been estimated recently (in the last 7 days by default). return } - m.EstimatedAt = TimePointer() + m.EstimatedAt = TimeStamp() // Don't estimate if it seems to be a non-photographic image. if m.UnknownCamera() && m.PhotoType == MediaImage { diff --git a/internal/entity/photo_estimate_test.go b/internal/entity/photo_estimate_test.go index 3d70792ba56..9f1ba3bf6ef 100644 --- a/internal/entity/photo_estimate_test.go +++ b/internal/entity/photo_estimate_test.go @@ -68,7 +68,7 @@ func TestPhoto_EstimateLocation(t *testing.T) { assert.Equal(t, "Mexico", m.CountryName()) }) t.Run("RecentlyEstimates", func(t *testing.T) { - m2 := Photo{CameraID: 2, TakenSrc: SrcMeta, PhotoName: "PhotoWithoutLocation", OriginalName: "demo/xyy.jpg", EstimatedAt: TimePointer(), TakenAt: time.Date(2016, 11, 11, 8, 7, 18, 0, time.UTC)} + m2 := Photo{CameraID: 2, TakenSrc: SrcMeta, PhotoName: "PhotoWithoutLocation", OriginalName: "demo/xyy.jpg", EstimatedAt: TimeStamp(), TakenAt: time.Date(2016, 11, 11, 8, 7, 18, 0, time.UTC)} assert.Equal(t, UnknownID, m2.CountryCode()) m2.EstimateLocation(false) assert.Equal(t, "zz", m2.CountryCode()) @@ -76,7 +76,7 @@ func TestPhoto_EstimateLocation(t *testing.T) { assert.Equal(t, SrcAuto, m2.PlaceSrc) }) t.Run("NotRecentlyEstimated", func(t *testing.T) { - estimateTime := TimeStamp().Add(-1 * (MetadataEstimateInterval + 2*time.Hour)) + estimateTime := Now().Add(-1 * (MetadataEstimateInterval + 2*time.Hour)) m2 := Photo{ CameraID: 2, TakenSrc: SrcMeta, @@ -96,7 +96,7 @@ func TestPhoto_EstimateLocation(t *testing.T) { TakenSrc: SrcMeta, PhotoName: "PhotoWithoutLocation", OriginalName: "demo/xyy.jpg", - EstimatedAt: TimePointer(), + EstimatedAt: TimeStamp(), TakenAt: time.Date(2016, 11, 11, 8, 7, 18, 0, time.UTC)} assert.Equal(t, UnknownID, m2.CountryCode()) m2.EstimateLocation(true) diff --git a/internal/entity/photo_fixtures.go b/internal/entity/photo_fixtures.go index 38d79ccdb00..77a7dd68fbc 100644 --- a/internal/entity/photo_fixtures.go +++ b/internal/entity/photo_fixtures.go @@ -272,7 +272,7 @@ var PhotoFixtures = PhotoMap{ }, CreatedAt: time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2008, 1, 1, 0, 0, 0, 0, time.UTC), - EditedAt: nil, + EditedAt: Time("2022-01-01T00:00:00Z"), CheckedAt: &checkedTime, DeletedAt: nil, PhotoColor: 12, @@ -336,7 +336,7 @@ var PhotoFixtures = PhotoMap{ Labels: []PhotoLabel{LabelFixtures.PhotoLabel(1000004, "batchdelete", 20, "image")}, CreatedAt: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - EditedAt: nil, + EditedAt: Time("2020-06-01T00:00:00Z"), CheckedAt: &checkedTime, DeletedAt: nil, PhotoColor: 12, @@ -1763,8 +1763,8 @@ var PhotoFixtures = PhotoMap{ LensID: LensFixtures.Pointer("lens-f-380").ID, Details: &Details{ PhotoID: 1000028, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, Keywords: []Keyword{}, Albums: []Album{}, @@ -1829,8 +1829,8 @@ var PhotoFixtures = PhotoMap{ LensID: LensFixtures.Pointer("lens-f-380").ID, Details: &Details{ PhotoID: 1000029, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, Keywords: []Keyword{}, Albums: []Album{}, diff --git a/internal/entity/photo_merge.go b/internal/entity/photo_merge.go index 6028667b559..4d9a84ddb3b 100644 --- a/internal/entity/photo_merge.go +++ b/internal/entity/photo_merge.go @@ -101,10 +101,10 @@ func (m *Photo) Merge(mergeMeta, mergeUuid bool) (original Photo, merged Photos, continue } - deleted := TimeStamp() + deleted := Now() logResult(UnscopedDb().Exec("UPDATE files SET photo_id = ?, photo_uid = ?, file_primary = 0 WHERE photo_id = ?", original.ID, original.PhotoUID, merge.ID)) - logResult(UnscopedDb().Exec("UPDATE photos SET photo_quality = -1, deleted_at = ? WHERE id = ?", TimeStamp(), merge.ID)) + logResult(UnscopedDb().Exec("UPDATE photos SET photo_quality = -1, deleted_at = ? WHERE id = ?", Now(), merge.ID)) switch DbDialect() { case MySQL: @@ -126,7 +126,7 @@ func (m *Photo) Merge(mergeMeta, mergeUuid bool) (original Photo, merged Photos, } if original.ID != m.ID { - deleted := TimeStamp() + deleted := Now() m.DeletedAt = &deleted m.PhotoQuality = -1 } diff --git a/internal/entity/photo_merge_test.go b/internal/entity/photo_merge_test.go index 0901b5bc0be..b8366beebdf 100644 --- a/internal/entity/photo_merge_test.go +++ b/internal/entity/photo_merge_test.go @@ -9,27 +9,27 @@ import ( func TestPhoto_Stackable(t *testing.T) { t.Run("IsStackable", func(t *testing.T) { - m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsStackable, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} + m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsStackable, TakenAt: Now(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} assert.True(t, m.Stackable()) }) t.Run("IsStacked", func(t *testing.T) { - m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsStacked, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} + m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsStacked, TakenAt: Now(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} assert.True(t, m.Stackable()) }) t.Run("NoName", func(t *testing.T) { - m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "", TakenAt: time.Time{}, TakenAtLocal: TimeStamp(), TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} + m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "", TakenAt: time.Time{}, TakenAtLocal: Now(), TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} assert.False(t, m.Stackable()) }) t.Run("IsUnstacked", func(t *testing.T) { - m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsUnstacked, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} + m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsUnstacked, TakenAt: Now(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} assert.False(t, m.Stackable()) }) t.Run("NoID", func(t *testing.T) { - m := Photo{ID: 0, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsStacked, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} + m := Photo{ID: 0, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsStacked, TakenAt: Now(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} assert.False(t, m.Stackable()) }) t.Run("NoPhotoUID", func(t *testing.T) { - m := Photo{ID: 1, PhotoUID: "", PhotoName: "foo", PhotoStack: IsStacked, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} + m := Photo{ID: 1, PhotoUID: "", PhotoName: "foo", PhotoStack: IsStacked, TakenAt: Now(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"} assert.False(t, m.Stackable()) }) } diff --git a/internal/entity/photo_optimize.go b/internal/entity/photo_optimize.go index 50838e7624d..d6c39ecd27e 100644 --- a/internal/entity/photo_optimize.go +++ b/internal/entity/photo_optimize.go @@ -52,7 +52,7 @@ func (m *Photo) Optimize(mergeMeta, mergeUuid, estimatePlace, force bool) (updat m.PhotoQuality = m.QualityScore() - checked := TimeStamp() + checked := Now() if reflect.DeepEqual(*m, current) { return false, merged, m.Update("CheckedAt", &checked) diff --git a/internal/entity/place_fixtures.go b/internal/entity/place_fixtures.go index 86d958fa502..ba4e3052e63 100644 --- a/internal/entity/place_fixtures.go +++ b/internal/entity/place_fixtures.go @@ -32,8 +32,8 @@ var PlaceFixtures = PlacesMap{ PlaceKeywords: "ancient, pyramid", PlaceFavorite: false, PhotoCount: 1, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "zinkwazi": { ID: "za:Rc1K7dTWRzBD", @@ -44,8 +44,8 @@ var PlaceFixtures = PlacesMap{ PlaceKeywords: "", PlaceFavorite: true, PhotoCount: 2, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "holidaypark": { ID: "de:HFqPHxa2Hsol", @@ -57,8 +57,8 @@ var PlaceFixtures = PlacesMap{ PlaceKeywords: "", PlaceFavorite: true, PhotoCount: 2, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "emptyNameLongCity": { ID: s2.TokenPrefix + "1ef744d1e281", @@ -69,8 +69,8 @@ var PlaceFixtures = PlacesMap{ PlaceKeywords: "", PlaceFavorite: true, PhotoCount: 2, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "emptyNameShortCity": { ID: s2.TokenPrefix + "1ef744d1e282", @@ -81,8 +81,8 @@ var PlaceFixtures = PlacesMap{ PlaceKeywords: "", PlaceFavorite: true, PhotoCount: 2, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "veryLongLocName": { ID: s2.TokenPrefix + "1ef744d1e283", @@ -93,8 +93,8 @@ var PlaceFixtures = PlacesMap{ PlaceKeywords: "", PlaceFavorite: true, PhotoCount: 2, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "mediumLongLocName": { ID: s2.TokenPrefix + "1ef744d1e284", @@ -105,8 +105,8 @@ var PlaceFixtures = PlacesMap{ PlaceKeywords: "", PlaceFavorite: true, PhotoCount: 2, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, "Germany": { ID: s2.TokenPrefix + "1ef744d1e285", @@ -117,8 +117,8 @@ var PlaceFixtures = PlacesMap{ PlaceKeywords: "", PlaceFavorite: false, PhotoCount: 1, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), }, } diff --git a/internal/entity/reaction.go b/internal/entity/reaction.go index 2e68dcd69ed..3758977c827 100644 --- a/internal/entity/reaction.go +++ b/internal/entity/reaction.go @@ -87,7 +87,7 @@ func (m *Reaction) Save() (err error) { return m.Create() } - reactedAt := TimePointer() + reactedAt := TimeStamp() values := Map{"reaction": m.Reaction, "reacted": gorm.Expr("reacted + 1"), "reacted_at": reactedAt} @@ -107,7 +107,7 @@ func (m *Reaction) Create() (err error) { return fmt.Errorf("reaction invalid") } - r := &Reaction{UID: m.UID, UserUID: m.UserUID, Reaction: m.Reaction, Reacted: m.Reacted, ReactedAt: TimePointer()} + r := &Reaction{UID: m.UID, UserUID: m.UserUID, Reaction: m.Reaction, Reacted: m.Reacted, ReactedAt: TimeStamp()} if err = Db().Create(r).Error; err == nil { m.ReactedAt = r.ReactedAt diff --git a/internal/entity/reaction_fixtures.go b/internal/entity/reaction_fixtures.go index 24856443ec6..3d3ac344546 100644 --- a/internal/entity/reaction_fixtures.go +++ b/internal/entity/reaction_fixtures.go @@ -26,21 +26,21 @@ var ReactionFixtures = ReactionMap{ UserUID: UserFixtures.Get("alice").UserUID, Reaction: react.Like.String(), Reacted: 1, - ReactedAt: TimePointer(), + ReactedAt: TimeStamp(), }, "PhotoAliceLove": Reaction{ UID: PhotoFixtures.Get("Photo01").PhotoUID, UserUID: UserFixtures.Pointer("alice").UserUID, Reaction: react.Love.String(), Reacted: 3, - ReactedAt: TimePointer(), + ReactedAt: TimeStamp(), }, "PhotoBobLove": Reaction{ UID: PhotoFixtures.Get("Photo01").PhotoUID, UserUID: UserFixtures.Pointer("bob").UserUID, Reaction: react.Love.String(), Reacted: 1, - ReactedAt: TimePointer(), + ReactedAt: TimeStamp(), }, } diff --git a/internal/entity/service_fixtures.go b/internal/entity/service_fixtures.go index 0c03948222a..a90bf5920d0 100644 --- a/internal/entity/service_fixtures.go +++ b/internal/entity/service_fixtures.go @@ -27,13 +27,13 @@ var ServiceFixtures = ServiceMap{ SyncPath: "/Photos", SyncStatus: "refresh", SyncInterval: 3600, - SyncDate: sql.NullTime{Time: TimeStamp()}, + SyncDate: sql.NullTime{Time: Now()}, SyncUpload: true, SyncDownload: true, SyncFilenames: true, SyncRaw: true, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, }, "dummy-webdav2": { @@ -56,13 +56,13 @@ var ServiceFixtures = ServiceMap{ SyncPath: "/Photos", SyncStatus: "refresh", SyncInterval: 3600, - SyncDate: sql.NullTime{Time: TimeStamp()}, + SyncDate: sql.NullTime{Time: Now()}, SyncUpload: true, SyncDownload: true, SyncFilenames: true, SyncRaw: true, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, }, } diff --git a/internal/entity/subject_fixtures.go b/internal/entity/subject_fixtures.go index 4c34403572c..1b0f7b5ef6c 100644 --- a/internal/entity/subject_fixtures.go +++ b/internal/entity/subject_fixtures.go @@ -32,8 +32,8 @@ var SubjectFixtures = SubjectMap{ SubjNotes: "Short Note", FileCount: 1, PhotoCount: 1, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, }, "joe-biden": Subject{ @@ -49,8 +49,8 @@ var SubjectFixtures = SubjectMap{ SubjNotes: "", FileCount: 1, PhotoCount: 1, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, }, "dangling": Subject{ @@ -68,8 +68,8 @@ var SubjectFixtures = SubjectMap{ Thumb: "2cad9168fa6acc5c5c2965ddf6ec465ca42fd818", FileCount: 0, PhotoCount: 0, - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, }, "jane-doe": Subject{ @@ -85,8 +85,8 @@ var SubjectFixtures = SubjectMap{ SubjNotes: "", FileCount: 3, PhotoCount: 2, - CreatedAt: TimeStamp().AddDate(0, 0, 1), - UpdatedAt: TimeStamp(), + CreatedAt: Now().AddDate(0, 0, 1), + UpdatedAt: Now(), DeletedAt: nil, }, "actress-1": Subject{ @@ -98,8 +98,8 @@ var SubjectFixtures = SubjectMap{ SubjFavorite: false, SubjPrivate: false, SubjNotes: "", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, }, "actor-1": Subject{ @@ -111,8 +111,8 @@ var SubjectFixtures = SubjectMap{ SubjFavorite: false, SubjPrivate: false, SubjNotes: "", - CreatedAt: TimeStamp(), - UpdatedAt: TimeStamp(), + CreatedAt: Now(), + UpdatedAt: Now(), DeletedAt: nil, }, } diff --git a/internal/entity/subject_test.go b/internal/entity/subject_test.go index 070cb21549e..1af7c4e0730 100644 --- a/internal/entity/subject_test.go +++ b/internal/entity/subject_test.go @@ -152,7 +152,7 @@ func TestSubject_Delete(t *testing.T) { } assert.False(t, m.Deleted()) - time := TimeStamp() + time := Now() m.DeletedAt = &time assert.True(t, m.Deleted()) @@ -227,7 +227,7 @@ func TestFindSubjectByName(t *testing.T) { t.Run("RestoreDeleted", func(t *testing.T) { m := NewSubject("Jim Doe", SubjPerson, SrcAuto) - time := TimeStamp() + time := Now() m.DeletedAt = &time err := m.Save() @@ -473,7 +473,7 @@ func TestSubject_DeletePermanently(t *testing.T) { assert.Nil(t, m.DeletePermanently()) - time := TimeStamp() + time := Now() m.DeletedAt = &time if err := m.Save(); err != nil { diff --git a/internal/form/search_photos.go b/internal/form/search_photos.go index c0db7fae1a9..a315326605d 100644 --- a/internal/form/search_photos.go +++ b/internal/form/search_photos.go @@ -65,31 +65,32 @@ type SearchPhotos struct { Keywords string `form:"keywords" example:"keywords:\"sand&water\"" notes:"Keywords (combinable with & and |)"` Label string `form:"label" example:"label:cat|dog" notes:"Label Names (separate with |)"` Category string `form:"category" example:"category:airport" notes:"Location Category"` - Country string `form:"country" example:"country:\"de|us\"" notes:"Location Country Code (separate with |)"` // Moments - State string `form:"state" example:"state:\"Baden-Württemberg\"" notes:"Location State (separate with |)"` // Moments - City string `form:"city" example:"city:\"Berlin\"" notes:"Location City (separate with |)"` // Moments - Year string `form:"year" example:"year:1990|2003" notes:"Year (separate with |)"` // Moments - Month string `form:"month" example:"month:7|10" notes:"Month (1-12, separate with |)"` // Moments - Day string `form:"day" example:"day:3|13" notes:"Day of Month (1-31, separate with |)"` // Moments - Face string `form:"face" example:"face:PN6QO5INYTUSAATOFL43LL2ABAV5ACZG" notes:"Face ID, yes, no, new, or kind"` // UIDs - Faces string `form:"faces" example:"faces:yes faces:3" notes:"Minimum number of Faces (yes = 1)"` // Find or exclude faces if detected. - Subject string `form:"subject" example:"subject:\"Jane Doe & John Doe\"" notes:"Alias for person"` // UIDs - Person string `form:"person" example:"person:\"Jane Doe & John Doe\"" notes:"Subject Names, exact matches (combinable with & and |)"` // Alias for Subject - Subjects string `form:"subjects" example:"subjects:\"Jane & John\"" notes:"Alias for people"` // People names - People string `form:"people" example:"people:\"Jane & John\"" notes:"Subject Names (combinable with & and |)"` // Alias for Subjects - Album string `form:"album" example:"album:berlin" notes:"Album UID or Name, supports * wildcards"` // Album UIDs or name - Albums string `form:"albums" example:"albums:\"South Africa & Birds\"" notes:"Album Names (combinable with & and |)"` // Multi search with and/or - Quality int `form:"quality" notes:"Minimum quality score (1-7)"` // Photo quality score - Review bool `form:"review" notes:"Finds pictures in review"` // Find photos in review - Added time.Time `form:"added" example:"added:\"2006-01-02 15:04:05\"" time_format:"2006-01-02 15:04:05" notes:"Finds pictures added at or after this time (UTC)"` // Finds pictures added at or after this time (UTC) - Updated time.Time `form:"updated" example:"updated:\"2006-01-02 15:04:05\"" time_format:"2006-01-02 15:04:05" notes:"Finds pictures updated at or after this time (UTC)"` // Finds pictures updated at or after this time (UTC) - Taken time.Time `form:"taken" time_format:"2006-01-02" notes:"Finds pictures taken on the specified date"` // Finds pictures taken on the specified date - Before time.Time `form:"before" time_format:"2006-01-02" notes:"Finds pictures taken on or before this date"` // Finds pictures taken on or before this date" - After time.Time `form:"after" time_format:"2006-01-02" notes:"Finds pictures taken on or after this date"` // Finds pictures taken on or after this date - Count int `form:"count" binding:"required" serialize:"-"` // Result FILE limit - Offset int `form:"offset" serialize:"-"` // Result FILE offset - Order string `form:"order" serialize:"-"` // Sort order - Merged bool `form:"merged" serialize:"-"` // Merge FILES in response + Country string `form:"country" example:"country:\"de|us\"" notes:"Location Country Code (separate with |)"` // Moments + State string `form:"state" example:"state:\"Baden-Württemberg\"" notes:"Location State (separate with |)"` // Moments + City string `form:"city" example:"city:\"Berlin\"" notes:"Location City (separate with |)"` // Moments + Year string `form:"year" example:"year:1990|2003" notes:"Year (separate with |)"` // Moments + Month string `form:"month" example:"month:7|10" notes:"Month (1-12, separate with |)"` // Moments + Day string `form:"day" example:"day:3|13" notes:"Day of Month (1-31, separate with |)"` // Moments + Face string `form:"face" example:"face:PN6QO5INYTUSAATOFL43LL2ABAV5ACZG" notes:"Face ID, yes, no, new, or kind"` // UIDs + Faces string `form:"faces" example:"faces:yes faces:3" notes:"Minimum number of Faces (yes = 1)"` // Find or exclude faces if detected. + Subject string `form:"subject" example:"subject:\"Jane Doe & John Doe\"" notes:"Alias for person"` // UIDs + Person string `form:"person" example:"person:\"Jane Doe & John Doe\"" notes:"Subject Names, exact matches (combinable with & and |)"` // Alias for Subject + Subjects string `form:"subjects" example:"subjects:\"Jane & John\"" notes:"Alias for people"` // People names + People string `form:"people" example:"people:\"Jane & John\"" notes:"Subject Names (combinable with & and |)"` // Alias for Subjects + Album string `form:"album" example:"album:berlin" notes:"Album UID or Name, supports * wildcards"` // Album UIDs or name + Albums string `form:"albums" example:"albums:\"South Africa & Birds\"" notes:"Album Names (combinable with & and |)"` // Multi search with and/or + Quality int `form:"quality" notes:"Minimum quality score (1-7)"` // Photo quality score + Review bool `form:"review" notes:"Finds pictures in review"` // Find photos in review + Added time.Time `form:"added" example:"added:\"2006-01-02T15:04:05Z\"" time_format:"2006-01-02T15:04:05Z07:00" notes:"Finds pictures added at or after this time (UTC)"` // Finds pictures added at or after this time (UTC) + Updated time.Time `form:"updated" example:"updated:\"2006-01-02T15:04:05Z\"" time_format:"2006-01-02T15:04:05Z07:00" notes:"Finds pictures updated at or after this time (UTC)"` // Finds pictures updated at or after this time (UTC) + Edited time.Time `form:"edited" example:"edited:\"2006-01-02T15:04:05Z\"" time_format:"2006-01-02T15:04:05Z07:00" notes:"Finds pictures edited at or after this time (UTC)"` // Finds pictures edited at or after this time (UTC) + Taken time.Time `form:"taken" time_format:"2006-01-02" notes:"Finds pictures taken on the specified date"` // Finds pictures taken on the specified date + Before time.Time `form:"before" time_format:"2006-01-02" notes:"Finds pictures taken on or before this date"` // Finds pictures taken on or before this date" + After time.Time `form:"after" time_format:"2006-01-02" notes:"Finds pictures taken on or after this date"` // Finds pictures taken on or after this date + Count int `form:"count" binding:"required" serialize:"-"` // Result FILE limit + Offset int `form:"offset" serialize:"-"` // Result FILE offset + Order string `form:"order" serialize:"-"` // Sort order + Merged bool `form:"merged" serialize:"-"` // Merge FILES in response } func (f *SearchPhotos) GetQuery() string { diff --git a/internal/form/search_photos_geo.go b/internal/form/search_photos_geo.go index 0d26c06a7e3..94b43b915bd 100644 --- a/internal/form/search_photos_geo.go +++ b/internal/form/search_photos_geo.go @@ -18,8 +18,9 @@ type SearchPhotosGeo struct { Folder string `form:"folder"` // Alias for Path Name string `form:"name"` Title string `form:"title"` - Added time.Time `form:"added" example:"added:\"2006-01-02 15:04:05\"" time_format:"2006-01-02 15:04:05" notes:"Finds pictures added at or after this time (UTC)"` - Updated time.Time `form:"updated" example:"updated:\"2006-01-02 15:04:05\"" time_format:"2006-01-02 15:04:05" notes:"Finds pictures updated at or after this time (UTC)"` + Added time.Time `form:"added" example:"added:\"2006-01-02T15:04:05Z\"" time_format:"2006-01-02T15:04:05Z07:00" notes:"Finds pictures added at or after this time (UTC)"` + Updated time.Time `form:"updated" example:"updated:\"2006-01-02T15:04:05Z\"" time_format:"2006-01-02T15:04:05Z07:00" notes:"Finds pictures updated at or after this time (UTC)"` + Edited time.Time `form:"edited" example:"edited:\"2006-01-02T15:04:05Z\"" time_format:"2006-01-02T15:04:05Z07:00" notes:"Finds pictures edited at or after this time (UTC)"` Taken time.Time `form:"taken" time_format:"2006-01-02" notes:"Finds pictures taken on the specified date"` Before time.Time `form:"before" time_format:"2006-01-02" notes:"Finds pictures taken on or before this date"` After time.Time `form:"after" time_format:"2006-01-02" notes:"Finds pictures taken on or after this date"` diff --git a/internal/form/search_photos_geo_test.go b/internal/form/search_photos_geo_test.go index 3c87905f554..1c7a2f6c36d 100644 --- a/internal/form/search_photos_geo_test.go +++ b/internal/form/search_photos_geo_test.go @@ -1,9 +1,15 @@ package form import ( + "net/http" + "net/http/httptest" + "net/url" "testing" "time" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/stretchr/testify/assert" ) @@ -239,6 +245,67 @@ func TestSearchPhotosGeo(t *testing.T) { assert.Contains(t, err.Error(), "invalid syntax") }) + t.Run("Added", func(t *testing.T) { + form := &SearchPhotosGeo{Query: "added:\"2022-01-02T13:04:05+01:00\""} + + err := form.ParseQueryString() + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "2022-01-02 13:04:05 +0100 UTC+01:00", form.Added.String()) + assert.Equal(t, "2022-01-02 12:04:05 +0000 UTC", form.Added.UTC().String()) + assert.Equal(t, "2022-01-02T13:04:05+01:00", form.Added.Format(time.RFC3339)) + assert.Equal(t, "2022-01-02T12:04:05Z", form.Added.UTC().Format(time.RFC3339)) + }) + t.Run("Updated", func(t *testing.T) { + form := &SearchPhotosGeo{Query: "updated:\"2001-01-02 17:04:05\""} + + err := form.ParseQueryString() + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "2001-01-02 17:04:05 +0000 UTC", form.Updated.String()) + assert.Equal(t, "2001-01-02 17:04:05 +0000 UTC", form.Updated.UTC().String()) + assert.Equal(t, "2001-01-02T17:04:05Z", form.Updated.Format(time.RFC3339)) + assert.Equal(t, "2001-01-02T17:04:05Z", form.Updated.UTC().Format(time.RFC3339)) + }) + t.Run("MustBindWith", func(t *testing.T) { + form := &SearchPhotosGeo{} + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + u, err := url.Parse("https://www.photoprism.app/api/v1/photos?count=100&offset=0&order=added&added=2022-01-02T13:04:05-01:00&updated=2001-01-02T17:04:05Z&q=") + + if err != nil { + t.Fatal(err) + } + + c.Request = &http.Request{ + Header: make(http.Header), + URL: u, + } + + // Abort if request params are invalid. + if err = c.MustBindWith(form, binding.Form); err != nil { + t.Fatal(err) + } + + err = form.ParseQueryString() + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "2022-01-02T13:04:05-01:00", form.Added.Format(time.RFC3339)) + assert.Equal(t, "2022-01-02T14:04:05Z", form.Added.UTC().Format(time.RFC3339)) + assert.Equal(t, "2001-01-02T17:04:05Z", form.Updated.Format(time.RFC3339)) + assert.Equal(t, 100, form.Count) + assert.Equal(t, 0, form.Offset) + }) } func TestSearchPhotosGeo_Serialize(t *testing.T) { diff --git a/internal/form/search_photos_test.go b/internal/form/search_photos_test.go index 2a6535595ac..a967cde48d9 100644 --- a/internal/form/search_photos_test.go +++ b/internal/form/search_photos_test.go @@ -1,11 +1,17 @@ package form import ( + "net/http" + "net/http/httptest" + "net/url" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/photoprism/photoprism/pkg/txt" ) @@ -692,7 +698,7 @@ func TestParseQueryString(t *testing.T) { assert.Equal(t, "invalid after date", err.Error()) }) - t.Run("id", func(t *testing.T) { + t.Run("Id", func(t *testing.T) { form := &SearchPhotos{Query: "id:\"ii3e4567-e89b-hdgtr\""} err := form.ParseQueryString() @@ -703,6 +709,68 @@ func TestParseQueryString(t *testing.T) { assert.Equal(t, "ii3e4567-e89b-hdgtr", form.ID) }) + t.Run("Added", func(t *testing.T) { + form := &SearchPhotos{Query: "added:\"2022-01-02T13:04:05+01:00\""} + + err := form.ParseQueryString() + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "2022-01-02 13:04:05 +0100 UTC+01:00", form.Added.String()) + assert.Equal(t, "2022-01-02 12:04:05 +0000 UTC", form.Added.UTC().String()) + assert.Equal(t, "2022-01-02T13:04:05+01:00", form.Added.Format(time.RFC3339)) + assert.Equal(t, "2022-01-02T12:04:05Z", form.Added.UTC().Format(time.RFC3339)) + }) + t.Run("Updated", func(t *testing.T) { + form := &SearchPhotos{Query: "updated:\"2001-01-02 17:04:05\""} + + err := form.ParseQueryString() + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "2001-01-02 17:04:05 +0000 UTC", form.Updated.String()) + assert.Equal(t, "2001-01-02 17:04:05 +0000 UTC", form.Updated.UTC().String()) + assert.Equal(t, "2001-01-02T17:04:05Z", form.Updated.Format(time.RFC3339)) + assert.Equal(t, "2001-01-02T17:04:05Z", form.Updated.UTC().Format(time.RFC3339)) + }) + t.Run("MustBindWith", func(t *testing.T) { + form := &SearchPhotos{} + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + u, err := url.Parse("https://www.photoprism.app/api/v1/photos?count=100&offset=0&order=added&added=2022-01-02T13:04:05-01:00&updated=2001-01-02T17:04:05Z&q=") + + if err != nil { + t.Fatal(err) + } + + c.Request = &http.Request{ + Header: make(http.Header), + URL: u, + } + + // Abort if request params are invalid. + if err = c.MustBindWith(form, binding.Form); err != nil { + t.Fatal(err) + } + + err = form.ParseQueryString() + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "added", form.Order) + assert.Equal(t, "2022-01-02T13:04:05-01:00", form.Added.Format(time.RFC3339)) + assert.Equal(t, "2022-01-02T14:04:05Z", form.Added.UTC().Format(time.RFC3339)) + assert.Equal(t, "2001-01-02T17:04:05Z", form.Updated.Format(time.RFC3339)) + assert.Equal(t, 100, form.Count) + assert.Equal(t, 0, form.Offset) + }) } func TestNewSearchPhotos(t *testing.T) { diff --git a/internal/photoprism/faces_cluster.go b/internal/photoprism/faces_cluster.go index 4a1196d2ab3..75893e4cbdb 100644 --- a/internal/photoprism/faces_cluster.go +++ b/internal/photoprism/faces_cluster.go @@ -78,7 +78,7 @@ func (w *Faces) Cluster(opt FacesOptions) (added entity.Faces, err error) { } else if err := f.Create(); err == nil { added = append(added, *f) log.Debugf("faces: added cluster %s based on %s, radius %f", f.ID, english.Plural(f.Samples, "sample", "samples"), f.SampleRadius) - } else if err := f.Updates(entity.Map{"UpdatedAt": entity.TimeStamp()}); err != nil { + } else if err := f.Updates(entity.Map{"UpdatedAt": entity.Now()}); err != nil { log.Errorf("faces: %s", err) } else { log.Debugf("faces: updated cluster %s", f.ID) diff --git a/internal/photoprism/faces_match.go b/internal/photoprism/faces_match.go index d211a8c4d42..43d873b11cb 100644 --- a/internal/photoprism/faces_match.go +++ b/internal/photoprism/faces_match.go @@ -41,7 +41,7 @@ func (w *Faces) Match(opt FacesOptions) (result FacesMatchResult, err error) { log.Debugf("faces: found no unmatched markers") } - matchedAt := entity.TimePointer() + matchedAt := entity.TimeStamp() if opt.Force || unmatchedMarkers > 0 { faces, err := query.Faces(false, false, false, false) diff --git a/internal/photoprism/index.go b/internal/photoprism/index.go index 00ffb5ec5d6..3eb516dc13a 100644 --- a/internal/photoprism/index.go +++ b/internal/photoprism/index.go @@ -330,7 +330,7 @@ func (ind *Index) Start(o IndexOptions) (found fs.Done, updated int) { runtime.GC() - ind.lastRun = entity.TimeStamp() + ind.lastRun = entity.Now() ind.lastFound = len(found) return found, updated diff --git a/internal/query/markers_test.go b/internal/query/markers_test.go index 29a8944c338..2346fdc75f3 100644 --- a/internal/query/markers_test.go +++ b/internal/query/markers_test.go @@ -27,7 +27,7 @@ func TestMarkerByUID(t *testing.T) { func TestMarkers(t *testing.T) { t.Run("find umatched", func(t *testing.T) { - results, err := Markers(3, 0, entity.MarkerFace, false, false, entity.TimeStamp()) + results, err := Markers(3, 0, entity.MarkerFace, false, false, entity.Now()) if err != nil { t.Fatal(err) @@ -87,7 +87,7 @@ func TestUnmatchedFaceMarkers(t *testing.T) { assert.Equal(t, 3, len(results)) }) t.Run("before", func(t *testing.T) { - results, err := UnmatchedFaceMarkers(3, 0, entity.TimePointer()) + results, err := UnmatchedFaceMarkers(3, 0, entity.TimeStamp()) if err != nil { t.Fatal(err) diff --git a/internal/search/albums.go b/internal/search/albums.go index 08fe3b9f924..7a5672158ea 100644 --- a/internal/search/albums.go +++ b/internal/search/albums.go @@ -63,9 +63,9 @@ func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults // Limit results by UID, owner and path. if sess.IsVisitor() || sess.NotRegistered() { - s = s.Where("albums.album_uid IN (?) OR albums.published_at > ?", sess.SharedUIDs(), entity.TimeStamp()) + s = s.Where("albums.album_uid IN (?) OR albums.published_at > ?", sess.SharedUIDs(), entity.Now()) } else if acl.Rules.DenyAll(aclResource, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) { - s = s.Where("albums.album_uid IN (?) OR albums.created_by = ? OR albums.published_at > ?", sess.SharedUIDs(), user.UserUID, entity.TimeStamp()) + s = s.Where("albums.album_uid IN (?) OR albums.created_by = ? OR albums.published_at > ?", sess.SharedUIDs(), user.UserUID, entity.Now()) } // Exclude private content? diff --git a/internal/search/photos.go b/internal/search/photos.go index 2948e95a05f..47932061b5d 100644 --- a/internal/search/photos.go +++ b/internal/search/photos.go @@ -158,12 +158,12 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string) sharedAlbums := "photos.photo_uid IN (SELECT photo_uid FROM photos_albums WHERE hidden = 0 AND missing = 0 AND album_uid IN (?)) OR " if sess.IsVisitor() || sess.NotRegistered() { - s = s.Where(sharedAlbums+"photos.published_at > ?", sess.SharedUIDs(), entity.TimeStamp()) + s = s.Where(sharedAlbums+"photos.published_at > ?", sess.SharedUIDs(), entity.Now()) } else if basePath := user.GetBasePath(); basePath == "" { - s = s.Where(sharedAlbums+"photos.created_by = ? OR photos.published_at > ?", sess.SharedUIDs(), user.UserUID, entity.TimeStamp()) + s = s.Where(sharedAlbums+"photos.created_by = ? OR photos.published_at > ?", sess.SharedUIDs(), user.UserUID, entity.Now()) } else { s = s.Where(sharedAlbums+"photos.created_by = ? OR photos.published_at > ? OR photos.photo_path = ? OR photos.photo_path LIKE ?", - sess.SharedUIDs(), user.UserUID, entity.TimeStamp(), basePath, basePath+"/%") + sess.SharedUIDs(), user.UserUID, entity.Now(), basePath, basePath+"/%") } } } @@ -710,27 +710,32 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string) // Find pictures added at or after this time (UTC). if !f.Added.IsZero() { - s = s.Where("photos.created_at >= ?", f.Added.Format("2006-01-02 15:04:05")) + s = s.Where("photos.created_at >= ?", f.Added.UTC().Format("2006-01-02 15:04:05")) } // Find pictures updated at or after this time (UTC). if !f.Updated.IsZero() { - s = s.Where("photos.updated_at >= ?", f.Updated.Format("2006-01-02 15:04:05")) + s = s.Where("photos.updated_at >= ?", f.Updated.UTC().Format("2006-01-02 15:04:05")) + } + + // Find pictures edited at or after this time (UTC). + if !f.Edited.IsZero() { + s = s.Where("photos.edited_at >= ?", f.Edited.UTC().Format("2006-01-02 15:04:05")) } // Find pictures taken on the specified date. if !f.Taken.IsZero() { - s = s.Where("DATE(photos.taken_at) = DATE(?)", f.Taken.Format("2006-01-02")) + s = s.Where("DATE(photos.taken_at) = DATE(?)", f.Taken.UTC().Format("2006-01-02")) } // Finds pictures taken on or before this date. if !f.Before.IsZero() { - s = s.Where("photos.taken_at <= ?", f.Before.Format("2006-01-02")) + s = s.Where("photos.taken_at <= ?", f.Before.UTC().Format("2006-01-02")) } // Finds pictures taken on or after this date. if !f.After.IsZero() { - s = s.Where("photos.taken_at >= ?", f.After.Format("2006-01-02")) + s = s.Where("photos.taken_at >= ?", f.After.UTC().Format("2006-01-02")) } // Find stacks only. diff --git a/internal/search/photos_filter_time_test.go b/internal/search/photos_filter_time_test.go new file mode 100644 index 00000000000..c406b741449 --- /dev/null +++ b/internal/search/photos_filter_time_test.go @@ -0,0 +1,152 @@ +package search + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/photoprism/photoprism/internal/form" + "github.com/photoprism/photoprism/pkg/sortby" +) + +func TestPhotosFilterTime(t *testing.T) { + t.Run("Added", func(t *testing.T) { + var f form.SearchPhotos + + timeStamp, err := time.Parse(time.RFC3339, "2021-01-02T00:00:00Z") + + if err != nil { + t.Fatal(err) + } + + f.Added = timeStamp + f.Order = sortby.Added + f.Merged = true + + photos, _, err := Photos(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("Added: %#v", photos) + + assert.GreaterOrEqual(t, 40, len(photos)) + }) + t.Run("Updated", func(t *testing.T) { + var f form.SearchPhotos + + timeStamp, err := time.Parse(time.RFC3339, "2022-01-02T13:04:05-01:00") + + if err != nil { + t.Fatal(err) + } + + f.Updated = timeStamp + f.Order = sortby.Updated + f.Merged = true + + photos, _, err := Photos(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("Updated: %#v", photos) + + assert.GreaterOrEqual(t, 49, len(photos)) + }) + t.Run("Edited", func(t *testing.T) { + var f form.SearchPhotos + + timeStamp, err := time.Parse(time.RFC3339, "2020-01-01T12:00:00Z") + + if err != nil { + t.Fatal(err) + } + + f.Edited = timeStamp + f.Order = sortby.Edited + f.Merged = true + + photos, _, err := Photos(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("Edited: %#v", photos) + + assert.GreaterOrEqual(t, 2, len(photos)) + }) + t.Run("Taken", func(t *testing.T) { + var f form.SearchPhotos + + timeStamp, err := time.Parse(time.RFC3339, "2014-07-17T15:42:12Z") + + if err != nil { + t.Fatal(err) + } + + f.Taken = timeStamp + f.Order = sortby.Added + f.Merged = true + + photos, _, err := Photos(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("Taken: %#v", photos) + + assert.GreaterOrEqual(t, 1, len(photos)) + }) + t.Run("Before", func(t *testing.T) { + var f form.SearchPhotos + + timeStamp, err := time.Parse(time.RFC3339, "2022-01-02T13:04:05Z") + + if err != nil { + t.Fatal(err) + } + + f.Before = timeStamp + f.Order = sortby.Added + f.Merged = true + + photos, _, err := Photos(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("Before: %#v", photos) + + assert.GreaterOrEqual(t, 47, len(photos)) + }) + t.Run("After", func(t *testing.T) { + var f form.SearchPhotos + + timeStamp, err := time.Parse(time.RFC3339, "2022-01-02T13:04:05Z") + + if err != nil { + t.Fatal(err) + } + + f.After = timeStamp + f.Order = sortby.Added + f.Merged = true + + photos, _, err := Photos(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("After: %#v", photos) + + assert.GreaterOrEqual(t, 2, len(photos)) + }) +} diff --git a/internal/search/photos_geo.go b/internal/search/photos_geo.go index 387ce6ed7b0..3f9f0d3186a 100644 --- a/internal/search/photos_geo.go +++ b/internal/search/photos_geo.go @@ -141,12 +141,12 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes sharedAlbums := "photos.photo_uid IN (SELECT photo_uid FROM photos_albums WHERE hidden = 0 AND missing = 0 AND album_uid IN (?)) OR " if sess.IsVisitor() || sess.NotRegistered() { - s = s.Where(sharedAlbums+"photos.published_at > ?", sess.SharedUIDs(), entity.TimeStamp()) + s = s.Where(sharedAlbums+"photos.published_at > ?", sess.SharedUIDs(), entity.Now()) } else if basePath := user.GetBasePath(); basePath == "" { - s = s.Where(sharedAlbums+"photos.created_by = ? OR photos.published_at > ?", sess.SharedUIDs(), user.UserUID, entity.TimeStamp()) + s = s.Where(sharedAlbums+"photos.created_by = ? OR photos.published_at > ?", sess.SharedUIDs(), user.UserUID, entity.Now()) } else { s = s.Where(sharedAlbums+"photos.created_by = ? OR photos.published_at > ? OR photos.photo_path = ? OR photos.photo_path LIKE ?", - sess.SharedUIDs(), user.UserUID, entity.TimeStamp(), basePath, basePath+"/%") + sess.SharedUIDs(), user.UserUID, entity.Now(), basePath, basePath+"/%") } } } @@ -611,27 +611,32 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes // Find pictures added at or after this time (UTC). if !f.Added.IsZero() { - s = s.Where("photos.created_at >= ?", f.Added.Format("2006-01-02 15:04:05")) + s = s.Where("photos.created_at >= ?", f.Added.UTC().Format("2006-01-02 15:04:05")) } // Find pictures updated at or after this time (UTC). if !f.Updated.IsZero() { - s = s.Where("photos.updated_at >= ?", f.Updated.Format("2006-01-02 15:04:05")) + s = s.Where("photos.updated_at >= ?", f.Updated.UTC().Format("2006-01-02 15:04:05")) + } + + // Find pictures edited at or after this time (UTC). + if !f.Edited.IsZero() { + s = s.Where("photos.edited_at >= ?", f.Edited.UTC().Format("2006-01-02 15:04:05")) } // Find pictures taken on the specified date. if !f.Taken.IsZero() { - s = s.Where("DATE(photos.taken_at) = DATE(?)", f.Taken.Format("2006-01-02")) + s = s.Where("DATE(photos.taken_at) = DATE(?)", f.Taken.UTC().Format("2006-01-02")) } // Finds pictures taken on or before this date. if !f.Before.IsZero() { - s = s.Where("photos.taken_at <= ?", f.Before.Format("2006-01-02")) + s = s.Where("photos.taken_at <= ?", f.Before.UTC().Format("2006-01-02")) } // Finds pictures taken on or after this date. if !f.After.IsZero() { - s = s.Where("photos.taken_at >= ?", f.After.Format("2006-01-02")) + s = s.Where("photos.taken_at >= ?", f.After.UTC().Format("2006-01-02")) } // Limit offset and count. diff --git a/internal/search/photos_geo_filter_time_test.go b/internal/search/photos_geo_filter_time_test.go new file mode 100644 index 00000000000..0ef62c791ef --- /dev/null +++ b/internal/search/photos_geo_filter_time_test.go @@ -0,0 +1,139 @@ +package search + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/photoprism/photoprism/internal/form" +) + +func TestPhotosGeoFilterTime(t *testing.T) { + t.Run("Added", func(t *testing.T) { + var f form.SearchPhotosGeo + + timeStamp, err := time.Parse(time.RFC3339, "2021-01-02T00:00:00Z") + + if err != nil { + t.Fatal(err) + } + + f.Added = timeStamp + + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("Added: %#v", photos) + + assert.GreaterOrEqual(t, 40, len(photos)) + }) + t.Run("Updated", func(t *testing.T) { + var f form.SearchPhotosGeo + + timeStamp, err := time.Parse(time.RFC3339, "2022-01-02T13:04:05-01:00") + + if err != nil { + t.Fatal(err) + } + + f.Updated = timeStamp + + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("Updated: %#v", photos) + + assert.GreaterOrEqual(t, 49, len(photos)) + }) + t.Run("Edited", func(t *testing.T) { + var f form.SearchPhotosGeo + + timeStamp, err := time.Parse(time.RFC3339, "2020-01-01T12:00:00Z") + + if err != nil { + t.Fatal(err) + } + + f.Edited = timeStamp + + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("Edited: %#v", photos) + + assert.GreaterOrEqual(t, 2, len(photos)) + }) + t.Run("Taken", func(t *testing.T) { + var f form.SearchPhotosGeo + + timeStamp, err := time.Parse(time.RFC3339, "2014-07-17T15:42:12Z") + + if err != nil { + t.Fatal(err) + } + + f.Taken = timeStamp + + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("Taken: %#v", photos) + + assert.GreaterOrEqual(t, 1, len(photos)) + }) + t.Run("Before", func(t *testing.T) { + var f form.SearchPhotosGeo + + timeStamp, err := time.Parse(time.RFC3339, "2022-01-02T13:04:05Z") + + if err != nil { + t.Fatal(err) + } + + f.Before = timeStamp + + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("Before: %#v", photos) + + assert.GreaterOrEqual(t, 47, len(photos)) + }) + t.Run("After", func(t *testing.T) { + var f form.SearchPhotosGeo + + timeStamp, err := time.Parse(time.RFC3339, "2022-01-02T13:04:05Z") + + if err != nil { + t.Fatal(err) + } + + f.After = timeStamp + + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + // t.Logf("After: %#v", photos) + + assert.GreaterOrEqual(t, 2, len(photos)) + }) +} diff --git a/internal/search/photos_results.go b/internal/search/photos_results.go index 9d0d99bb944..2beeb7f8247 100644 --- a/internal/search/photos_results.go +++ b/internal/search/photos_results.go @@ -134,7 +134,7 @@ func (m *Photo) Approve() error { return err } - edited := entity.TimeStamp() + edited := entity.Now() if err := UnscopedDb(). Table(entity.Photo{}.TableName()).