mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Albums: Remove photo from review when adding it to an album #4229
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -384,22 +384,22 @@ func CloneAlbums(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
var added []entity.PhotoAlbum
|
var added []entity.PhotoAlbum
|
||||||
|
|
||||||
for _, uid := range f.Albums {
|
for _, albumUid := range f.Albums {
|
||||||
cloneAlbum, err := query.AlbumByUID(uid)
|
cloneAlbum, queryErr := query.AlbumByUID(albumUid)
|
||||||
|
|
||||||
if err != nil {
|
if queryErr != nil {
|
||||||
log.Errorf("album: %s", err)
|
log.Errorf("album: %s", queryErr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
photos, err := search.AlbumPhotos(cloneAlbum, 10000, false)
|
photos, queryErr := search.AlbumPhotos(cloneAlbum, 100000, false)
|
||||||
|
|
||||||
if err != nil {
|
if queryErr != nil {
|
||||||
log.Errorf("album: %s", err)
|
log.Errorf("album: %s", queryErr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
added = append(added, a.AddPhotos(photos.UIDs())...)
|
added = append(added, a.AddPhotos(photos)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(added) > 0 {
|
if len(added) > 0 {
|
||||||
@@ -466,7 +466,9 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
added := a.AddPhotos(photos.UIDs())
|
conf := get.Config()
|
||||||
|
|
||||||
|
added := a.AddPhotos(photos)
|
||||||
|
|
||||||
if len(added) > 0 {
|
if len(added) > 0 {
|
||||||
if len(added) == 1 {
|
if len(added) == 1 {
|
||||||
@@ -481,6 +483,34 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
// Update album YAML backup.
|
// Update album YAML backup.
|
||||||
SaveAlbumAsYaml(a)
|
SaveAlbumAsYaml(a)
|
||||||
|
|
||||||
|
// Auto-approve photos that have been added to an album,
|
||||||
|
// see https://github.com/photoprism/photoprism/issues/4229
|
||||||
|
if conf.Settings().Features.Review {
|
||||||
|
var approved entity.Photos
|
||||||
|
|
||||||
|
for _, p := range photos {
|
||||||
|
// Skip photos that are not in review.
|
||||||
|
if p.Approved() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approve photo and update YAML backup file.
|
||||||
|
if err = p.Approve(); err != nil {
|
||||||
|
log.Errorf("approve: %s", err)
|
||||||
|
} else {
|
||||||
|
approved = append(approved, p)
|
||||||
|
SavePhotoAsYaml(&p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update client UI and counts if photos has been approved.
|
||||||
|
if len(approved) > 0 {
|
||||||
|
UpdateClientConfig()
|
||||||
|
|
||||||
|
event.EntitiesUpdated("photos", approved)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgChangesSaved), "album": a, "photos": photos.UIDs(), "added": added})
|
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgChangesSaved), "album": a, "photos": photos.UIDs(), "added": added})
|
||||||
|
|||||||
@@ -57,10 +57,10 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range photos {
|
for _, p := range photos {
|
||||||
if err := p.Archive(); err != nil {
|
if archiveErr := p.Archive(); archiveErr != nil {
|
||||||
log.Errorf("archive: %s", err)
|
log.Errorf("archive: %s", archiveErr)
|
||||||
} else {
|
} else {
|
||||||
SavePhotoAsYaml(p)
|
SavePhotoAsYaml(&p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if err := entity.Db().Where("photo_uid IN (?)", f.Photos).Delete(&entity.Photo{}).Error; err != nil {
|
} else if err := entity.Db().Where("photo_uid IN (?)", f.Photos).Delete(&entity.Photo{}).Error; err != nil {
|
||||||
@@ -120,10 +120,10 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range photos {
|
for _, p := range photos {
|
||||||
if err := p.Restore(); err != nil {
|
if err = p.Restore(); err != nil {
|
||||||
log.Errorf("restore: %s", err)
|
log.Errorf("restore: %s", err)
|
||||||
} else {
|
} else {
|
||||||
SavePhotoAsYaml(p)
|
SavePhotoAsYaml(&p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if err := entity.Db().Unscoped().Model(&entity.Photo{}).Where("photo_uid IN (?)", f.Photos).
|
} else if err := entity.Db().Unscoped().Model(&entity.Photo{}).Where("photo_uid IN (?)", f.Photos).
|
||||||
@@ -187,7 +187,7 @@ func BatchPhotosApprove(router *gin.RouterGroup) {
|
|||||||
log.Errorf("approve: %s", err)
|
log.Errorf("approve: %s", err)
|
||||||
} else {
|
} else {
|
||||||
approved = append(approved, p)
|
approved = append(approved, p)
|
||||||
SavePhotoAsYaml(p)
|
SavePhotoAsYaml(&p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +278,7 @@ func BatchPhotosPrivate(router *gin.RouterGroup) {
|
|||||||
// Fetch selection from index.
|
// Fetch selection from index.
|
||||||
if photos, err := query.SelectedPhotos(f); err == nil {
|
if photos, err := query.SelectedPhotos(f); err == nil {
|
||||||
for _, p := range photos {
|
for _, p := range photos {
|
||||||
SavePhotoAsYaml(p)
|
SavePhotoAsYaml(&p)
|
||||||
}
|
}
|
||||||
|
|
||||||
event.EntitiesUpdated("photos", photos)
|
event.EntitiesUpdated("photos", photos)
|
||||||
@@ -405,12 +405,12 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
|
|||||||
event.AuditWarn([]string{ClientIP(c), s.UserName, "delete", path.Join(p.PhotoPath, p.PhotoName+"*")})
|
event.AuditWarn([]string{ClientIP(c), s.UserName, "delete", path.Join(p.PhotoPath, p.PhotoName+"*")})
|
||||||
|
|
||||||
// Remove all related files from storage.
|
// Remove all related files from storage.
|
||||||
n, err := photoprism.DeletePhoto(p, true, true)
|
n, deleteErr := photoprism.DeletePhoto(&p, true, true)
|
||||||
|
|
||||||
numFiles += n
|
numFiles += n
|
||||||
|
|
||||||
if err != nil {
|
if deleteErr != nil {
|
||||||
log.Errorf("delete: %s", err)
|
log.Errorf("delete: %s", deleteErr)
|
||||||
} else {
|
} else {
|
||||||
deleted = append(deleted, p)
|
deleted = append(deleted, p)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SavePhotoAsYaml saves photo data as YAML file.
|
// SavePhotoAsYaml saves photo data as YAML file.
|
||||||
func SavePhotoAsYaml(p entity.Photo) {
|
func SavePhotoAsYaml(p *entity.Photo) {
|
||||||
|
if p == nil {
|
||||||
|
log.Debugf("api: photo is nil (update yaml)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c := get.Config()
|
c := get.Config()
|
||||||
|
|
||||||
// Write YAML sidecar file (optional).
|
// Write YAML sidecar file (optional).
|
||||||
@@ -114,7 +119,7 @@ func UpdatePhoto(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SavePhotoAsYaml(p)
|
SavePhotoAsYaml(&p)
|
||||||
|
|
||||||
UpdateClientConfig()
|
UpdateClientConfig()
|
||||||
|
|
||||||
@@ -225,7 +230,7 @@ func ApprovePhoto(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SavePhotoAsYaml(m)
|
SavePhotoAsYaml(&m)
|
||||||
|
|
||||||
PublishPhotoEvent(StatusUpdated, id, c)
|
PublishPhotoEvent(StatusUpdated, id, c)
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func LikePhoto(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SavePhotoAsYaml(m)
|
SavePhotoAsYaml(&m)
|
||||||
PublishPhotoEvent(StatusUpdated, id, c)
|
PublishPhotoEvent(StatusUpdated, id, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ func DislikePhoto(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SavePhotoAsYaml(m)
|
SavePhotoAsYaml(&m)
|
||||||
PublishPhotoEvent(StatusUpdated, id, c)
|
PublishPhotoEvent(StatusUpdated, id, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func CreateUserPasscode(router *gin.RouterGroup) {
|
|||||||
event.AuditErr([]string{ClientIP(c), "session %s", authn.Users, user.UserName, authn.ErrPasscodeGenerateFailed.Error(), clean.Error(err)}, s.RefID)
|
event.AuditErr([]string{ClientIP(c), "session %s", authn.Users, user.UserName, authn.ErrPasscodeGenerateFailed.Error(), clean.Error(err)}, s.RefID)
|
||||||
Abort(c, http.StatusInternalServerError, i18n.ErrUnexpected)
|
Abort(c, http.StatusInternalServerError, i18n.ErrUnexpected)
|
||||||
return
|
return
|
||||||
} else if passcode, err = entity.NewPasscode(user.UID(), key.String(), rnd.RecoveryCode()); err != nil {
|
} else if passcode, err = entity.NewPasscode(user.GetUID(), key.String(), rnd.RecoveryCode()); err != nil {
|
||||||
event.AuditErr([]string{ClientIP(c), "session %s", authn.Users, user.UserName, authn.ErrPasscodeCreateFailed.Error(), clean.Error(err)}, s.RefID)
|
event.AuditErr([]string{ClientIP(c), "session %s", authn.Users, user.UserName, authn.ErrPasscodeCreateFailed.Error(), clean.Error(err)}, s.RefID)
|
||||||
Abort(c, http.StatusInternalServerError, i18n.ErrUnexpected)
|
Abort(c, http.StatusInternalServerError, i18n.ErrUnexpected)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func UpdateUserPassword(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update tokens if user matches with session.
|
// Update tokens if user matches with session.
|
||||||
if s.User().UserUID == u.UID() {
|
if s.User().UserUID == u.GetUID() {
|
||||||
s.SetPreviewToken(u.PreviewToken)
|
s.SetPreviewToken(u.PreviewToken)
|
||||||
s.SetDownloadToken(u.DownloadToken)
|
s.SetDownloadToken(u.DownloadToken)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func wsWriter(ws *websocket.Conn, writeMutex *sync.Mutex, connId string) {
|
|||||||
}
|
}
|
||||||
case 4:
|
case 4:
|
||||||
ev = strings.Join(ch[2:4], ".")
|
ev = strings.Join(ch[2:4], ".")
|
||||||
if acl.ChannelUser.Equal(ch[0]) && ch[1] == user.UID() || acl.Events.AllowAll(acl.Resource(ch[2]), user.AclRole(), wsSubscribePerms) {
|
if acl.ChannelUser.Equal(ch[0]) && ch[1] == user.GetUID() || acl.Events.AllowAll(acl.Resource(ch[2]), user.AclRole(), wsSubscribePerms) {
|
||||||
// Send to matching user uid.
|
// Send to matching user uid.
|
||||||
wsSendMessage(ev, msg.Fields, ws, writeMutex)
|
wsSendMessage(ev, msg.Fields, ws, writeMutex)
|
||||||
} else if acl.ChannelSession.Equal(ch[0]) && ch[1] == sid {
|
} else if acl.ChannelSession.Equal(ch[0]) && ch[1] == sid {
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func clientsAddAction(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows[0] = []string{
|
rows[0] = []string{
|
||||||
client.UID(),
|
client.GetUID(),
|
||||||
client.Name(),
|
client.Name(),
|
||||||
client.AuthInfo(),
|
client.AuthInfo(),
|
||||||
client.UserInfo(),
|
client.UserInfo(),
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func clientsListAction(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows[i] = []string{
|
rows[i] = []string{
|
||||||
client.UID(),
|
client.GetUID(),
|
||||||
client.Name(),
|
client.Name(),
|
||||||
client.AuthInfo(),
|
client.AuthInfo(),
|
||||||
client.UserInfo(),
|
client.UserInfo(),
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ func clientsRemoveAction(ctx *cli.Context) error {
|
|||||||
|
|
||||||
if !ctx.Bool("force") {
|
if !ctx.Bool("force") {
|
||||||
actionPrompt := promptui.Prompt{
|
actionPrompt := promptui.Prompt{
|
||||||
Label: fmt.Sprintf("Delete client %s?", m.UID()),
|
Label: fmt.Sprintf("Delete client %s?", m.GetUID()),
|
||||||
IsConfirm: true,
|
IsConfirm: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := actionPrompt.Run(); err != nil {
|
if _, err := actionPrompt.Run(); err != nil {
|
||||||
log.Infof("client %s was not deleted", m.UID())
|
log.Infof("client %s was not deleted", m.GetUID())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ func clientsRemoveAction(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("client %s has been deleted", m.UID())
|
log.Infof("client %s has been deleted", m.GetUID())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func usersListAction(ctx *cli.Context) error {
|
|||||||
// Display report.
|
// Display report.
|
||||||
for i, user := range users {
|
for i, user := range users {
|
||||||
rows[i] = []string{
|
rows[i] = []string{
|
||||||
user.UID(),
|
user.GetUID(),
|
||||||
user.Username(),
|
user.Username(),
|
||||||
user.AclRole().Pretty(),
|
user.AclRole().Pretty(),
|
||||||
user.AuthInfo(),
|
user.AuthInfo(),
|
||||||
|
|||||||
@@ -783,19 +783,21 @@ func (m *Album) ZipName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddPhotos adds photos to an existing album.
|
// AddPhotos adds photos to an existing album.
|
||||||
func (m *Album) AddPhotos(UIDs []string) (added PhotoAlbums) {
|
func (m *Album) AddPhotos(photos PhotosInterface) (added PhotoAlbums) {
|
||||||
if !m.HasID() {
|
if !m.HasID() {
|
||||||
return added
|
return added
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add album entries.
|
// Add album entries.
|
||||||
for _, uid := range UIDs {
|
for _, photoUid := range photos.UIDs() {
|
||||||
if !rnd.IsUID(uid, PhotoUID) {
|
if !rnd.IsUID(photoUid, PhotoUID) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := PhotoAlbum{AlbumUID: m.AlbumUID, PhotoUID: uid, Hidden: false}
|
// Add photo to album.
|
||||||
|
entry := PhotoAlbum{AlbumUID: m.AlbumUID, PhotoUID: photoUid, Hidden: false}
|
||||||
|
|
||||||
|
// Save album entry.
|
||||||
if err := entry.Save(); err != nil {
|
if err := entry.Save(); err != nil {
|
||||||
log.Errorf("album: %s (add to album %s)", err.Error(), m)
|
log.Errorf("album: %s (add to album %s)", err.Error(), m)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -518,7 +518,12 @@ func TestAlbum_AddPhotos(t *testing.T) {
|
|||||||
AlbumType: AlbumManual,
|
AlbumType: AlbumManual,
|
||||||
AlbumTitle: "Test Title",
|
AlbumTitle: "Test Title",
|
||||||
}
|
}
|
||||||
added := album.AddPhotos([]string{"ps6sg6be2lvl0yh7", "ps6sg6be2lvl0yh8"})
|
|
||||||
|
photo1 := PhotoFixtures.Get("19800101_000002_D640C559")
|
||||||
|
photo2 := PhotoFixtures.Get("Photo01")
|
||||||
|
photos := Photos{photo1, photo2}
|
||||||
|
|
||||||
|
added := album.AddPhotos(photos)
|
||||||
|
|
||||||
var entries PhotoAlbums
|
var entries PhotoAlbums
|
||||||
|
|
||||||
@@ -532,7 +537,7 @@ func TestAlbum_AddPhotos(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(entries) < 2 {
|
if len(entries) < 2 {
|
||||||
t.Error("at least one album entry expected")
|
t.Fatal("at least one album entry expected")
|
||||||
}
|
}
|
||||||
|
|
||||||
var a Album
|
var a Album
|
||||||
@@ -542,17 +547,17 @@ func TestAlbum_AddPhotos(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
first_photo_updatedAt := strings.Split(entries[0].UpdatedAt.String(), ".")[0]
|
firstUpdatedAt := strings.Split(entries[0].UpdatedAt.String(), ".")[0]
|
||||||
second_photo_updatedAt := strings.Split(entries[1].UpdatedAt.String(), ".")[0]
|
secondUpdatedAt := strings.Split(entries[1].UpdatedAt.String(), ".")[0]
|
||||||
album_updatedAt := strings.Split(a.UpdatedAt.String(), ".")[0]
|
albumUpdatedAt := strings.Split(a.UpdatedAt.String(), ".")[0]
|
||||||
|
|
||||||
assert.Truef(
|
assert.Truef(
|
||||||
t, first_photo_updatedAt <= album_updatedAt,
|
t, firstUpdatedAt <= albumUpdatedAt,
|
||||||
"Expected the UpdatedAt field of an album to be updated when"+
|
"Expected the UpdatedAt field of an album to be updated when"+
|
||||||
" new photos are added",
|
" new photos are added",
|
||||||
)
|
)
|
||||||
assert.Truef(
|
assert.Truef(
|
||||||
t, second_photo_updatedAt <= album_updatedAt,
|
t, secondUpdatedAt <= albumUpdatedAt,
|
||||||
"Expected the UpdatedAt field of an album to be updated when"+
|
"Expected the UpdatedAt field of an album to be updated when"+
|
||||||
" new photos are added",
|
" new photos are added",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ func FindClientByUID(uid string) *Client {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// UID returns the client uid string.
|
// GetUID returns the client uid string.
|
||||||
func (m *Client) UID() string {
|
func (m *Client) GetUID() string {
|
||||||
return m.ClientUID
|
return m.ClientUID
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ func (m *Client) String() string {
|
|||||||
if m == nil {
|
if m == nil {
|
||||||
return report.NotAssigned
|
return report.NotAssigned
|
||||||
} else if m.HasUID() {
|
} else if m.HasUID() {
|
||||||
return m.UID()
|
return m.GetUID()
|
||||||
} else if m.HasName() {
|
} else if m.HasName() {
|
||||||
return m.Name()
|
return m.Name()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func TestFindClient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, m.UserUID, UserFixtures.Get("alice").UserUID)
|
assert.Equal(t, m.UserUID, UserFixtures.Get("alice").UserUID)
|
||||||
assert.Equal(t, expected.ClientUID, m.UID())
|
assert.Equal(t, expected.ClientUID, m.GetUID())
|
||||||
assert.NotEmpty(t, m.CreatedAt)
|
assert.NotEmpty(t, m.CreatedAt)
|
||||||
assert.NotEmpty(t, m.UpdatedAt)
|
assert.NotEmpty(t, m.UpdatedAt)
|
||||||
})
|
})
|
||||||
@@ -44,7 +44,7 @@ func TestFindClient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, m.UserUID, UserFixtures.Get("bob").UserUID)
|
assert.Equal(t, m.UserUID, UserFixtures.Get("bob").UserUID)
|
||||||
assert.Equal(t, expected.ClientUID, m.UID())
|
assert.Equal(t, expected.ClientUID, m.GetUID())
|
||||||
assert.NotEmpty(t, m.CreatedAt)
|
assert.NotEmpty(t, m.CreatedAt)
|
||||||
assert.NotEmpty(t, m.UpdatedAt)
|
assert.NotEmpty(t, m.UpdatedAt)
|
||||||
})
|
})
|
||||||
@@ -58,7 +58,7 @@ func TestFindClient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Empty(t, m.UserUID)
|
assert.Empty(t, m.UserUID)
|
||||||
assert.Equal(t, expected.ClientUID, m.UID())
|
assert.Equal(t, expected.ClientUID, m.GetUID())
|
||||||
assert.NotEmpty(t, m.CreatedAt)
|
assert.NotEmpty(t, m.CreatedAt)
|
||||||
assert.NotEmpty(t, m.UpdatedAt)
|
assert.NotEmpty(t, m.UpdatedAt)
|
||||||
})
|
})
|
||||||
@@ -411,7 +411,7 @@ func TestClient_VerifySecret(t *testing.T) {
|
|||||||
t.Fatal("result should not be nil")
|
t.Fatal("result should not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, expected.ClientUID, m.UID())
|
assert.Equal(t, expected.ClientUID, m.GetUID())
|
||||||
assert.False(t, m.VerifySecret("xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
assert.False(t, m.VerifySecret("xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
||||||
assert.False(t, m.VerifySecret("aaCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
assert.False(t, m.VerifySecret("aaCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
||||||
assert.False(t, m.VerifySecret(""))
|
assert.False(t, m.VerifySecret(""))
|
||||||
@@ -430,7 +430,7 @@ func TestClient_VerifySecret(t *testing.T) {
|
|||||||
t.Fatal("result should not be nil")
|
t.Fatal("result should not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, expected.ClientUID, m.UID())
|
assert.Equal(t, expected.ClientUID, m.GetUID())
|
||||||
assert.True(t, m.VerifySecret("xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
assert.True(t, m.VerifySecret("xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
||||||
assert.False(t, m.VerifySecret("aaCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
assert.False(t, m.VerifySecret("aaCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"))
|
||||||
assert.False(t, m.VerifySecret(""))
|
assert.False(t, m.VerifySecret(""))
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ func (m *Session) SetClient(c *Client) *Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.client = c
|
m.client = c
|
||||||
m.ClientUID = c.UID()
|
m.ClientUID = c.GetUID()
|
||||||
m.ClientName = c.ClientName
|
m.ClientName = c.ClientName
|
||||||
m.AuthProvider = c.Provider().String()
|
m.AuthProvider = c.Provider().String()
|
||||||
m.AuthMethod = c.Method().String()
|
m.AuthMethod = c.Method().String()
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func DeleteClientSessions(client *Client, authMethod authn.MethodType, limit int
|
|||||||
q := Db()
|
q := Db()
|
||||||
|
|
||||||
if client.HasUID() {
|
if client.HasUID() {
|
||||||
q = q.Where("client_uid = ?", client.UID())
|
q = q.Where("client_uid = ?", client.GetUID())
|
||||||
} else if client.HasName() {
|
} else if client.HasName() {
|
||||||
q = q.Where("client_name = ?", client.Name())
|
q = q.Where("client_name = ?", client.Name())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -203,8 +203,8 @@ func FindUserByUID(uid string) *User {
|
|||||||
return FindUser(User{UserUID: uid})
|
return FindUser(User{UserUID: uid})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UID returns the unique id as string.
|
// GetUID returns the unique id as string.
|
||||||
func (m *User) UID() string {
|
func (m *User) GetUID() string {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -591,7 +591,7 @@ func (m *User) Passcode(t authn.KeyType) *Passcode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return FindPasscode(Passcode{UID: m.UID(), KeyType: t.String()})
|
return FindPasscode(Passcode{UID: m.GetUID(), KeyType: t.String()})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Username returns the user's login name as sanitized string.
|
// Username returns the user's login name as sanitized string.
|
||||||
@@ -711,7 +711,7 @@ func (m *User) Settings() *UserSettings {
|
|||||||
if m.UserSettings != nil {
|
if m.UserSettings != nil {
|
||||||
m.UserSettings.UserUID = m.UserUID
|
m.UserSettings.UserUID = m.UserUID
|
||||||
return m.UserSettings
|
return m.UserSettings
|
||||||
} else if m.UID() == "" {
|
} else if m.GetUID() == "" {
|
||||||
m.UserSettings = &UserSettings{}
|
m.UserSettings = &UserSettings{}
|
||||||
return m.UserSettings
|
return m.UserSettings
|
||||||
} else if err := CreateUserSettings(m); err != nil {
|
} else if err := CreateUserSettings(m); err != nil {
|
||||||
@@ -726,7 +726,7 @@ func (m *User) Details() *UserDetails {
|
|||||||
if m.UserDetails != nil {
|
if m.UserDetails != nil {
|
||||||
m.UserDetails.UserUID = m.UserUID
|
m.UserDetails.UserUID = m.UserUID
|
||||||
return m.UserDetails
|
return m.UserDetails
|
||||||
} else if m.UID() == "" {
|
} else if m.GetUID() == "" {
|
||||||
m.UserDetails = &UserDetails{}
|
m.UserDetails = &UserDetails{}
|
||||||
return m.UserDetails
|
return m.UserDetails
|
||||||
} else if err := CreateUserDetails(m); err != nil {
|
} else if err := CreateUserDetails(m); err != nil {
|
||||||
@@ -1080,7 +1080,7 @@ func (m *User) RegenerateTokens() error {
|
|||||||
|
|
||||||
// RefreshShares updates the list of shares.
|
// RefreshShares updates the list of shares.
|
||||||
func (m *User) RefreshShares() *User {
|
func (m *User) RefreshShares() *User {
|
||||||
m.UserShares = FindUserShares(m.UID())
|
m.UserShares = FindUserShares(m.GetUID())
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1133,8 +1133,8 @@ func (m *User) RedeemToken(token string) (n int) {
|
|||||||
|
|
||||||
// Find shares.
|
// Find shares.
|
||||||
for _, link := range links {
|
for _, link := range links {
|
||||||
if found := FindUserShare(UserShare{UserUID: m.UID(), ShareUID: link.ShareUID}); found == nil {
|
if found := FindUserShare(UserShare{UserUID: m.GetUID(), ShareUID: link.ShareUID}); found == nil {
|
||||||
share := NewUserShare(m.UID(), link.ShareUID, link.Perm, link.ExpiresAt())
|
share := NewUserShare(m.GetUID(), link.ShareUID, link.Perm, link.ExpiresAt())
|
||||||
share.LinkUID = link.LinkUID
|
share.LinkUID = link.LinkUID
|
||||||
share.Comment = link.Comment
|
share.Comment = link.Comment
|
||||||
|
|
||||||
|
|||||||
@@ -69,13 +69,13 @@ func CreateUserDetails(user *User) error {
|
|||||||
return fmt.Errorf("user is nil")
|
return fmt.Errorf("user is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.UID() == "" {
|
if user.GetUID() == "" {
|
||||||
return fmt.Errorf("empty user uid")
|
return fmt.Errorf("empty user uid")
|
||||||
}
|
}
|
||||||
|
|
||||||
user.UserDetails = NewUserDetails(user.UID())
|
user.UserDetails = NewUserDetails(user.GetUID())
|
||||||
|
|
||||||
if err := Db().Where("user_uid = ?", user.UID()).First(user.UserDetails).Error; err == nil {
|
if err := Db().Where("user_uid = ?", user.GetUID()).First(user.UserDetails).Error; err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ func CreateUserSettings(user *User) error {
|
|||||||
return fmt.Errorf("user is nil")
|
return fmt.Errorf("user is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.UID() == "" {
|
if user.GetUID() == "" {
|
||||||
return fmt.Errorf("empty user uid")
|
return fmt.Errorf("empty user uid")
|
||||||
}
|
}
|
||||||
|
|
||||||
user.UserSettings = &UserSettings{}
|
user.UserSettings = &UserSettings{}
|
||||||
|
|
||||||
if err := Db().Where("user_uid = ?", user.UID()).First(user.UserSettings).Error; err == nil {
|
if err := Db().Where("user_uid = ?", user.GetUID()).First(user.UserSettings).Error; err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func TestUserShares_Contains(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewUserShare(t *testing.T) {
|
func TestNewUserShare(t *testing.T) {
|
||||||
expires := TimeStamp().Add(time.Hour * 48)
|
expires := TimeStamp().Add(time.Hour * 48)
|
||||||
m := NewUserShare(Admin.UID(), AlbumFixtures.Get("berlin-2019").AlbumUID, PermReact, &expires)
|
m := NewUserShare(Admin.GetUID(), AlbumFixtures.Get("berlin-2019").AlbumUID, PermReact, &expires)
|
||||||
|
|
||||||
assert.True(t, m.HasID())
|
assert.True(t, m.HasID())
|
||||||
assert.True(t, rnd.IsRefID(m.RefID))
|
assert.True(t, rnd.IsRefID(m.RefID))
|
||||||
@@ -76,7 +76,7 @@ func TestFindUserShare(t *testing.T) {
|
|||||||
|
|
||||||
func TestFindUserShares(t *testing.T) {
|
func TestFindUserShares(t *testing.T) {
|
||||||
t.Run("Alice", func(t *testing.T) {
|
t.Run("Alice", func(t *testing.T) {
|
||||||
found := FindUserShares(UserFixtures.Pointer("alice").UID())
|
found := FindUserShares(UserFixtures.Pointer("alice").GetUID())
|
||||||
assert.NotNil(t, found)
|
assert.NotNil(t, found)
|
||||||
assert.Len(t, found, 1)
|
assert.Len(t, found, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Map is an alias for map[string]interface{}.
|
// Map is an alias for map[string]interface{}.
|
||||||
type Map map[string]interface{}
|
type Map = map[string]interface{}
|
||||||
|
|
||||||
// ModelValues extracts Values from an entity model.
|
// ModelValues extracts Values from an entity model.
|
||||||
func ModelValues(m interface{}, omit ...string) (result Map, omitted []interface{}, err error) {
|
func ModelValues(m interface{}, omit ...string) (result Map, omitted []interface{}, err error) {
|
||||||
|
|||||||
@@ -32,19 +32,6 @@ var MetadataEstimateInterval = 24 * 7 * time.Hour // 7 Days
|
|||||||
|
|
||||||
var photoMutex = sync.Mutex{}
|
var photoMutex = sync.Mutex{}
|
||||||
|
|
||||||
type Photos []Photo
|
|
||||||
|
|
||||||
// UIDs returns a slice of photo UIDs.
|
|
||||||
func (m Photos) UIDs() []string {
|
|
||||||
result := make([]string, len(m))
|
|
||||||
|
|
||||||
for i, el := range m {
|
|
||||||
result[i] = el.PhotoUID
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapKey returns a key referencing time and location for indexing.
|
// MapKey returns a key referencing time and location for indexing.
|
||||||
func MapKey(takenAt time.Time, cellId string) string {
|
func MapKey(takenAt time.Time, cellId string) string {
|
||||||
return path.Join(strconv.FormatInt(takenAt.Unix(), 36), cellId)
|
return path.Join(strconv.FormatInt(takenAt.Unix(), 36), cellId)
|
||||||
@@ -222,6 +209,21 @@ func SavePhotoForm(model Photo, form form.Photo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetID returns the numeric entity ID.
|
||||||
|
func (m *Photo) GetID() uint {
|
||||||
|
return m.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasID checks if the photo has an id and uid assigned to it.
|
||||||
|
func (m *Photo) HasID() bool {
|
||||||
|
return m.ID > 0 && m.PhotoUID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUID returns the unique entity id.
|
||||||
|
func (m *Photo) GetUID() string {
|
||||||
|
return m.PhotoUID
|
||||||
|
}
|
||||||
|
|
||||||
// String returns the id or name as string.
|
// String returns the id or name as string.
|
||||||
func (m *Photo) String() string {
|
func (m *Photo) String() string {
|
||||||
if m.PhotoName != "" {
|
if m.PhotoName != "" {
|
||||||
@@ -527,11 +529,6 @@ func (m *Photo) PreloadMany() {
|
|||||||
m.PreloadAlbums()
|
m.PreloadAlbums()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasID checks if the photo has an id and uid assigned to it.
|
|
||||||
func (m *Photo) HasID() bool {
|
|
||||||
return m.ID > 0 && m.PhotoUID != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoCameraSerial checks if the photo has no CameraSerial
|
// NoCameraSerial checks if the photo has no CameraSerial
|
||||||
func (m *Photo) NoCameraSerial() bool {
|
func (m *Photo) NoCameraSerial() bool {
|
||||||
return m.CameraSerial == ""
|
return m.CameraSerial == ""
|
||||||
@@ -731,11 +728,17 @@ func (m *Photo) AllFiles() (files Files) {
|
|||||||
|
|
||||||
// Archive removes the photo from albums and flags it as archived (soft delete).
|
// Archive removes the photo from albums and flags it as archived (soft delete).
|
||||||
func (m *Photo) Archive() error {
|
func (m *Photo) Archive() error {
|
||||||
|
if !m.HasID() {
|
||||||
|
return fmt.Errorf("photo has no id")
|
||||||
|
} else if m.DeletedAt != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
deletedAt := TimeStamp()
|
deletedAt := TimeStamp()
|
||||||
|
|
||||||
if err := Db().Model(&PhotoAlbum{}).Where("photo_uid = ?", m.PhotoUID).UpdateColumn("hidden", true).Error; err != nil {
|
if err := Db().Model(&PhotoAlbum{}).Where("photo_uid = ?", m.PhotoUID).UpdateColumn("hidden", true).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err := m.Update("deleted_at", deletedAt); err != nil {
|
} else if err = m.Update("deleted_at", deletedAt); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,8 +747,14 @@ func (m *Photo) Archive() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore removes the archive flag (undo soft delete).
|
// Restore removes the photo from the archive (reverses soft delete).
|
||||||
func (m *Photo) Restore() error {
|
func (m *Photo) Restore() error {
|
||||||
|
if !m.HasID() {
|
||||||
|
return fmt.Errorf("photo has no id")
|
||||||
|
} else if m.DeletedAt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := m.Update("deleted_at", gorm.Expr("NULL")); err != nil {
|
if err := m.Update("deleted_at", gorm.Expr("NULL")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -757,7 +766,7 @@ func (m *Photo) Restore() error {
|
|||||||
|
|
||||||
// Delete deletes the photo from the index.
|
// Delete deletes the photo from the index.
|
||||||
func (m *Photo) Delete(permanently bool) (files Files, err error) {
|
func (m *Photo) Delete(permanently bool) (files Files, err error) {
|
||||||
if m.ID < 1 || m.PhotoUID == "" {
|
if !m.HasID() {
|
||||||
return files, fmt.Errorf("invalid photo id %d / uid %s", m.ID, clean.Log(m.PhotoUID))
|
return files, fmt.Errorf("invalid photo id %d / uid %s", m.ID, clean.Log(m.PhotoUID))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -834,7 +843,7 @@ func (m *Photo) React(user *User, reaction react.Emoji) error {
|
|||||||
return m.UnReact(user)
|
return m.UnReact(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewReaction(m.PhotoUID, user.UID()).React(reaction).Save()
|
return NewReaction(m.PhotoUID, user.GetUID()).React(reaction).Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnReact deletes a previous user reaction, if any.
|
// UnReact deletes a previous user reaction, if any.
|
||||||
@@ -843,7 +852,7 @@ func (m *Photo) UnReact(user *User) error {
|
|||||||
return fmt.Errorf("unknown user")
|
return fmt.Errorf("unknown user")
|
||||||
}
|
}
|
||||||
|
|
||||||
if r := FindReaction(m.PhotoUID, user.UID()); r != nil {
|
if r := FindReaction(m.PhotoUID, user.GetUID()); r != nil {
|
||||||
return r.Delete()
|
return r.Delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,13 +893,31 @@ func (m *Photo) SetStack(stack int8) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Approve approves a photo in review.
|
// Approved checks if the photo is not in review.
|
||||||
|
func (m *Photo) Approved() bool {
|
||||||
|
if !m.HasID() {
|
||||||
|
return false
|
||||||
|
} else if m.PhotoQuality >= 3 || m.PhotoType != MediaImage || m.EditedAt != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approve approves the photo if it is in review.
|
||||||
func (m *Photo) Approve() error {
|
func (m *Photo) Approve() error {
|
||||||
if m.PhotoQuality >= 3 {
|
if !m.HasID() {
|
||||||
|
return fmt.Errorf("photo has no id")
|
||||||
|
} else if m.PhotoQuality >= 3 {
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore photo if archived.
|
||||||
|
if err := m.Restore(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
edited := TimeStamp()
|
edited := TimeStamp()
|
||||||
m.EditedAt = &edited
|
m.EditedAt = &edited
|
||||||
m.PhotoQuality = m.QualityScore()
|
m.PhotoQuality = m.QualityScore()
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ func (m *Photo) EstimateLocation(force bool) {
|
|||||||
m.RemoveLocationLabels()
|
m.RemoveLocationLabels()
|
||||||
m.EstimateCountry()
|
m.EstimateCountry()
|
||||||
} else if len(mostRecent) == 1 || m.UnknownCamera() {
|
} else if len(mostRecent) == 1 || m.UnknownCamera() {
|
||||||
m.AdoptPlace(recentPhoto, SrcEstimate, false)
|
m.AdoptPlace(&recentPhoto, SrcEstimate, false)
|
||||||
} else {
|
} else {
|
||||||
p1 := mostRecent[0]
|
p1 := mostRecent[0]
|
||||||
p2 := mostRecent[1]
|
p2 := mostRecent[1]
|
||||||
@@ -143,7 +143,7 @@ func (m *Photo) EstimateLocation(force bool) {
|
|||||||
if estimate := movement.EstimatePosition(m.TakenAt); movement.Km() < 100 && estimate.Accuracy < Accuracy1Km {
|
if estimate := movement.EstimatePosition(m.TakenAt); movement.Km() < 100 && estimate.Accuracy < Accuracy1Km {
|
||||||
m.SetPosition(estimate, SrcEstimate, false)
|
m.SetPosition(estimate, SrcEstimate, false)
|
||||||
} else {
|
} else {
|
||||||
m.AdoptPlace(recentPhoto, SrcEstimate, false)
|
m.AdoptPlace(&recentPhoto, SrcEstimate, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if recentPhoto.HasCountry() {
|
} else if recentPhoto.HasCountry() {
|
||||||
|
|||||||
16
internal/entity/photo_interface.go
Normal file
16
internal/entity/photo_interface.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
// PhotoInterface represents an abstract Photo entity interface.
|
||||||
|
type PhotoInterface interface {
|
||||||
|
GetID() uint
|
||||||
|
HasID() bool
|
||||||
|
GetUID() string
|
||||||
|
Approve() error
|
||||||
|
Restore() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// PhotosInterface represents a Photo slice provider interface.
|
||||||
|
type PhotosInterface interface {
|
||||||
|
UIDs() []string
|
||||||
|
Photos() []PhotoInterface
|
||||||
|
}
|
||||||
@@ -86,8 +86,10 @@ func (m *Photo) SetPosition(pos geo.Position, source string, force bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AdoptPlace sets the place based on another photo.
|
// AdoptPlace sets the place based on another photo.
|
||||||
func (m *Photo) AdoptPlace(other Photo, source string, force bool) {
|
func (m *Photo) AdoptPlace(other *Photo, source string, force bool) {
|
||||||
if SrcPriority[m.PlaceSrc] > SrcPriority[source] && !force {
|
if other == nil {
|
||||||
|
return
|
||||||
|
} else if SrcPriority[m.PlaceSrc] > SrcPriority[source] && !force {
|
||||||
return
|
return
|
||||||
} else if other.Place == nil {
|
} else if other.Place == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func TestPhoto_AdoptPlace(t *testing.T) {
|
|||||||
place := PlaceFixtures.Get("mexico")
|
place := PlaceFixtures.Get("mexico")
|
||||||
t.Run("SrcAuto", func(t *testing.T) {
|
t.Run("SrcAuto", func(t *testing.T) {
|
||||||
p := Photo{ID: 1, Place: nil, PlaceID: "", CellID: "s2:479a03fda123", PhotoLat: -1, PhotoLng: 1, PlaceSrc: SrcAuto}
|
p := Photo{ID: 1, Place: nil, PlaceID: "", CellID: "s2:479a03fda123", PhotoLat: -1, PhotoLng: 1, PlaceSrc: SrcAuto}
|
||||||
o := Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 15, PhotoLng: -11, PlaceSrc: SrcManual}
|
o := &Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 15, PhotoLng: -11, PlaceSrc: SrcManual}
|
||||||
assert.Nil(t, p.Place)
|
assert.Nil(t, p.Place)
|
||||||
assert.Equal(t, "", p.PlaceID)
|
assert.Equal(t, "", p.PlaceID)
|
||||||
assert.Equal(t, "s2:479a03fda123", p.CellID)
|
assert.Equal(t, "s2:479a03fda123", p.CellID)
|
||||||
@@ -47,7 +47,7 @@ func TestPhoto_AdoptPlace(t *testing.T) {
|
|||||||
})
|
})
|
||||||
t.Run("SrcManual", func(t *testing.T) {
|
t.Run("SrcManual", func(t *testing.T) {
|
||||||
p := Photo{ID: 1, Place: nil, PlaceID: "", CellID: "s2:479a03fda123", PhotoLat: 0, PhotoLng: 0, PlaceSrc: SrcManual}
|
p := Photo{ID: 1, Place: nil, PlaceID: "", CellID: "s2:479a03fda123", PhotoLat: 0, PhotoLng: 0, PlaceSrc: SrcManual}
|
||||||
o := Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 1, PhotoLng: -1, PlaceSrc: SrcManual}
|
o := &Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 1, PhotoLng: -1, PlaceSrc: SrcManual}
|
||||||
assert.Nil(t, p.Place)
|
assert.Nil(t, p.Place)
|
||||||
assert.Equal(t, "", p.PlaceID)
|
assert.Equal(t, "", p.PlaceID)
|
||||||
assert.Equal(t, "s2:479a03fda123", p.CellID)
|
assert.Equal(t, "s2:479a03fda123", p.CellID)
|
||||||
@@ -62,7 +62,7 @@ func TestPhoto_AdoptPlace(t *testing.T) {
|
|||||||
})
|
})
|
||||||
t.Run("Force", func(t *testing.T) {
|
t.Run("Force", func(t *testing.T) {
|
||||||
p := Photo{ID: 1, Place: nil, PlaceID: "", CellID: "s2:479a03fda123", PhotoLat: 1, PhotoLng: -1, PlaceSrc: SrcManual}
|
p := Photo{ID: 1, Place: nil, PlaceID: "", CellID: "s2:479a03fda123", PhotoLat: 1, PhotoLng: -1, PlaceSrc: SrcManual}
|
||||||
o := Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 0, PhotoLng: 0, PlaceSrc: SrcManual}
|
o := &Photo{ID: 1, Place: &place, PlaceID: place.ID, CellID: "s2:479a03fda18c", PhotoLat: 0, PhotoLng: 0, PlaceSrc: SrcManual}
|
||||||
assert.Nil(t, p.Place)
|
assert.Nil(t, p.Place)
|
||||||
assert.Equal(t, "", p.PlaceID)
|
assert.Equal(t, "", p.PlaceID)
|
||||||
assert.Equal(t, "s2:479a03fda123", p.CellID)
|
assert.Equal(t, "s2:479a03fda123", p.CellID)
|
||||||
|
|||||||
@@ -808,8 +808,8 @@ func TestPhoto_AllFiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPhoto_Archive(t *testing.T) {
|
func TestPhoto_Archive(t *testing.T) {
|
||||||
t.Run("archive not yet archived photo", func(t *testing.T) {
|
t.Run("NotYetArchived", func(t *testing.T) {
|
||||||
m := &Photo{PhotoTitle: "HappyLilly"}
|
m := &Photo{ID: 10000, PhotoUID: "csd7ybn092yzcp52", PhotoTitle: "HappyLilly"}
|
||||||
assert.Empty(t, m.DeletedAt)
|
assert.Empty(t, m.DeletedAt)
|
||||||
err := m.Archive()
|
err := m.Archive()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
26
internal/entity/photos.go
Normal file
26
internal/entity/photos.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
// Photos represents a list of photos.
|
||||||
|
type Photos []Photo
|
||||||
|
|
||||||
|
// Photos returns the result as a slice of Photo.
|
||||||
|
func (m Photos) Photos() []PhotoInterface {
|
||||||
|
result := make([]PhotoInterface, len(m))
|
||||||
|
|
||||||
|
for i := range m {
|
||||||
|
result[i] = &m[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// UIDs returns tbe photo UIDs as string slice.
|
||||||
|
func (m Photos) UIDs() []string {
|
||||||
|
result := make([]string, len(m))
|
||||||
|
|
||||||
|
for i, photo := range m {
|
||||||
|
result[i] = photo.GetUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -85,9 +85,9 @@ func (w *CleanUp) Start(opt CleanUpOptions) (thumbs int, orphans int, sidecars i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deletes the index entry and remaining sidecar files outside the "originals" folder.
|
// Deletes the index entry and remaining sidecar files outside the "originals" folder.
|
||||||
if n, err := DeletePhoto(p, true, false); err != nil {
|
if n, deleteErr := DeletePhoto(&p, true, false); deleteErr != nil {
|
||||||
sidecars += n
|
sidecars += n
|
||||||
log.Errorf("cleanup: %s (remove orphans)", err)
|
log.Errorf("cleanup: %s (remove orphans)", deleteErr)
|
||||||
} else {
|
} else {
|
||||||
orphans++
|
orphans++
|
||||||
sidecars += n
|
sidecars += n
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package photoprism
|
package photoprism
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -10,7 +11,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// DeletePhoto removes a photo from the index and optionally all related media files.
|
// DeletePhoto removes a photo from the index and optionally all related media files.
|
||||||
func DeletePhoto(p entity.Photo, mediaFiles bool, originals bool) (numFiles int, err error) {
|
func DeletePhoto(p *entity.Photo, mediaFiles bool, originals bool) (numFiles int, err error) {
|
||||||
|
if p == nil {
|
||||||
|
return 0, errors.New("photo is nil")
|
||||||
|
}
|
||||||
|
|
||||||
yamlFileName := p.YamlFileName(Config().OriginalsPath(), Config().SidecarPath())
|
yamlFileName := p.YamlFileName(Config().OriginalsPath(), Config().SidecarPath())
|
||||||
|
|
||||||
// Permanently remove photo from index.
|
// Permanently remove photo from index.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type ImportOptions struct {
|
|||||||
// SetUser sets the user who performs the import operation.
|
// SetUser sets the user who performs the import operation.
|
||||||
func (o *ImportOptions) SetUser(user *entity.User) *ImportOptions {
|
func (o *ImportOptions) SetUser(user *entity.User) *ImportOptions {
|
||||||
if o != nil && user != nil {
|
if o != nil && user != nil {
|
||||||
o.UID = user.UID()
|
o.UID = user.GetUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
return o
|
return o
|
||||||
@@ -28,7 +28,7 @@ func (o *ImportOptions) SetUser(user *entity.User) *ImportOptions {
|
|||||||
// ImportOptionsCopy returns import options for copying files to originals (read-only).
|
// ImportOptionsCopy returns import options for copying files to originals (read-only).
|
||||||
func ImportOptionsCopy(importPath, destFolder string) ImportOptions {
|
func ImportOptionsCopy(importPath, destFolder string) ImportOptions {
|
||||||
result := ImportOptions{
|
result := ImportOptions{
|
||||||
UID: entity.Admin.UID(),
|
UID: entity.Admin.GetUID(),
|
||||||
Action: ActionImport,
|
Action: ActionImport,
|
||||||
Path: importPath,
|
Path: importPath,
|
||||||
Move: false,
|
Move: false,
|
||||||
@@ -45,7 +45,7 @@ func ImportOptionsCopy(importPath, destFolder string) ImportOptions {
|
|||||||
// ImportOptionsMove returns import options for moving files to originals (modifies import directory).
|
// ImportOptionsMove returns import options for moving files to originals (modifies import directory).
|
||||||
func ImportOptionsMove(importPath, destFolder string) ImportOptions {
|
func ImportOptionsMove(importPath, destFolder string) ImportOptions {
|
||||||
result := ImportOptions{
|
result := ImportOptions{
|
||||||
UID: entity.Admin.UID(),
|
UID: entity.Admin.GetUID(),
|
||||||
Action: ActionImport,
|
Action: ActionImport,
|
||||||
Path: importPath,
|
Path: importPath,
|
||||||
Move: true,
|
Move: true,
|
||||||
@@ -62,7 +62,7 @@ func ImportOptionsMove(importPath, destFolder string) ImportOptions {
|
|||||||
// ImportOptionsUpload returns options for importing user uploads.
|
// ImportOptionsUpload returns options for importing user uploads.
|
||||||
func ImportOptionsUpload(uploadPath, destFolder string) ImportOptions {
|
func ImportOptionsUpload(uploadPath, destFolder string) ImportOptions {
|
||||||
result := ImportOptions{
|
result := ImportOptions{
|
||||||
UID: entity.Admin.UID(),
|
UID: entity.Admin.GetUID(),
|
||||||
Action: ActionUpload,
|
Action: ActionUpload,
|
||||||
Path: uploadPath,
|
Path: uploadPath,
|
||||||
Move: true,
|
Move: true,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func TestIndex_MediaFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Equal(t, "", mediaFile.metaData.Title)
|
assert.Equal(t, "", mediaFile.metaData.Title)
|
||||||
|
|
||||||
result := ind.UserMediaFile(mediaFile, indexOpt, "blue-go-video.mp4", "", entity.Admin.UID())
|
result := ind.UserMediaFile(mediaFile, indexOpt, "blue-go-video.mp4", "", entity.Admin.GetUID())
|
||||||
|
|
||||||
assert.Equal(t, "Blue Gopher", mediaFile.metaData.Title)
|
assert.Equal(t, "Blue Gopher", mediaFile.metaData.Title)
|
||||||
assert.Equal(t, IndexStatus("added"), result.Status)
|
assert.Equal(t, IndexStatus("added"), result.Status)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type IndexOptions struct {
|
|||||||
// NewIndexOptions returns new index options instance.
|
// NewIndexOptions returns new index options instance.
|
||||||
func NewIndexOptions(path string, rescan, convert, stack, facesOnly, skipArchived bool) IndexOptions {
|
func NewIndexOptions(path string, rescan, convert, stack, facesOnly, skipArchived bool) IndexOptions {
|
||||||
result := IndexOptions{
|
result := IndexOptions{
|
||||||
UID: entity.Admin.UID(),
|
UID: entity.Admin.GetUID(),
|
||||||
Action: ActionIndex,
|
Action: ActionIndex,
|
||||||
Path: path,
|
Path: path,
|
||||||
Rescan: rescan,
|
Rescan: rescan,
|
||||||
@@ -42,7 +42,7 @@ func (o *IndexOptions) SkipUnchanged() bool {
|
|||||||
// SetUser sets the user who performs the index operation.
|
// SetUser sets the user who performs the index operation.
|
||||||
func (o *IndexOptions) SetUser(user *entity.User) *IndexOptions {
|
func (o *IndexOptions) SetUser(user *entity.User) *IndexOptions {
|
||||||
if o != nil && user != nil {
|
if o != nil && user != nil {
|
||||||
o.UID = user.UID()
|
o.UID = user.GetUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
return o
|
return o
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gosimple/slug"
|
"github.com/gosimple/slug"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
"github.com/ulule/deepcopier"
|
"github.com/ulule/deepcopier"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -98,14 +100,91 @@ type Photo struct {
|
|||||||
UpdatedAt time.Time `json:"UpdatedAt" select:"photos.updated_at"`
|
UpdatedAt time.Time `json:"UpdatedAt" select:"photos.updated_at"`
|
||||||
EditedAt time.Time `json:"EditedAt,omitempty" select:"photos.edited_at"`
|
EditedAt time.Time `json:"EditedAt,omitempty" select:"photos.edited_at"`
|
||||||
CheckedAt time.Time `json:"CheckedAt,omitempty" select:"photos.checked_at"`
|
CheckedAt time.Time `json:"CheckedAt,omitempty" select:"photos.checked_at"`
|
||||||
DeletedAt time.Time `json:"DeletedAt,omitempty" select:"photos.deleted_at"`
|
DeletedAt *time.Time `json:"DeletedAt,omitempty" select:"photos.deleted_at"`
|
||||||
|
|
||||||
Files []entity.File `json:"Files"`
|
Files []entity.File `json:"Files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetID returns the numeric entity ID.
|
||||||
|
func (m *Photo) GetID() uint {
|
||||||
|
return m.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasID checks if the photo has an id and uid assigned to it.
|
||||||
|
func (m *Photo) HasID() bool {
|
||||||
|
return m.ID > 0 && m.PhotoUID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUID returns the unique entity id.
|
||||||
|
func (m *Photo) GetUID() string {
|
||||||
|
return m.PhotoUID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approve approves the photo if it is in review.
|
||||||
|
func (m *Photo) Approve() error {
|
||||||
|
if !m.HasID() {
|
||||||
|
return fmt.Errorf("photo has no id")
|
||||||
|
} else if m.PhotoQuality >= 3 {
|
||||||
|
// Nothing to do.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore photo if archived.
|
||||||
|
if err := m.Restore(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
edited := entity.TimeStamp()
|
||||||
|
|
||||||
|
if err := UnscopedDb().
|
||||||
|
Table(entity.Photo{}.TableName()).
|
||||||
|
Where("photo_uid = ?", m.GetUID()).
|
||||||
|
UpdateColumns(entity.Map{
|
||||||
|
"deleted_at": gorm.Expr("NULL"),
|
||||||
|
"edited_at": &edited,
|
||||||
|
"photo_quality": 3}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.EditedAt = edited
|
||||||
|
m.PhotoQuality = 3
|
||||||
|
m.DeletedAt = nil
|
||||||
|
|
||||||
|
// Update precalculated photo and file counts.
|
||||||
|
if err := entity.UpdateCounts(); err != nil {
|
||||||
|
log.Warnf("index: %s (update counts)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Publish("count.review", event.Data{
|
||||||
|
"count": -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore removes the photo from the archive (reverses soft delete).
|
||||||
|
func (m *Photo) Restore() error {
|
||||||
|
if !m.HasID() {
|
||||||
|
return fmt.Errorf("photo has no id")
|
||||||
|
} else if m.DeletedAt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := UnscopedDb().
|
||||||
|
Table(entity.Photo{}.TableName()).
|
||||||
|
Where("photo_uid = ?", m.GetUID()).
|
||||||
|
UpdateColumn("deleted_at", gorm.Expr("NULL")).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.DeletedAt = nil
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsPlayable returns true if the photo has a related video/animation that is playable.
|
// IsPlayable returns true if the photo has a related video/animation that is playable.
|
||||||
func (photo *Photo) IsPlayable() bool {
|
func (m *Photo) IsPlayable() bool {
|
||||||
switch photo.PhotoType {
|
switch m.PhotoType {
|
||||||
case entity.MediaVideo, entity.MediaLive, entity.MediaAnimated:
|
case entity.MediaVideo, entity.MediaLive, entity.MediaAnimated:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
@@ -114,31 +193,42 @@ func (photo *Photo) IsPlayable() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ShareBase returns a meaningful file name for sharing.
|
// ShareBase returns a meaningful file name for sharing.
|
||||||
func (photo *Photo) ShareBase(seq int) string {
|
func (m *Photo) ShareBase(seq int) string {
|
||||||
var name string
|
var name string
|
||||||
|
|
||||||
if photo.PhotoTitle != "" {
|
if m.PhotoTitle != "" {
|
||||||
name = txt.Title(slug.MakeLang(photo.PhotoTitle, "en"))
|
name = txt.Title(slug.MakeLang(m.PhotoTitle, "en"))
|
||||||
} else {
|
} else {
|
||||||
name = photo.PhotoUID
|
name = m.PhotoUID
|
||||||
}
|
}
|
||||||
|
|
||||||
taken := photo.TakenAtLocal.Format("20060102-150405")
|
taken := m.TakenAtLocal.Format("20060102-150405")
|
||||||
|
|
||||||
if seq > 0 {
|
if seq > 0 {
|
||||||
return fmt.Sprintf("%s-%s (%d).%s", taken, name, seq, photo.FileType)
|
return fmt.Sprintf("%s-%s (%d).%s", taken, name, seq, m.FileType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s-%s.%s", taken, name, photo.FileType)
|
return fmt.Sprintf("%s-%s.%s", taken, name, m.FileType)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PhotoResults []Photo
|
type PhotoResults []Photo
|
||||||
|
|
||||||
// UIDs returns a slice of photo UIDs.
|
// Photos returns the result as a slice of Photo.
|
||||||
func (photos PhotoResults) UIDs() []string {
|
func (m PhotoResults) Photos() []entity.PhotoInterface {
|
||||||
result := make([]string, len(photos))
|
result := make([]entity.PhotoInterface, len(m))
|
||||||
|
|
||||||
for i, el := range photos {
|
for i := range m {
|
||||||
|
result[i] = &m[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// UIDs returns a slice of photo UIDs.
|
||||||
|
func (m PhotoResults) UIDs() []string {
|
||||||
|
result := make([]string, len(m))
|
||||||
|
|
||||||
|
for i, el := range m {
|
||||||
result[i] = el.PhotoUID
|
result[i] = el.PhotoUID
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,14 +236,14 @@ func (photos PhotoResults) UIDs() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge consecutive file results that belong to the same photo.
|
// Merge consecutive file results that belong to the same photo.
|
||||||
func (photos PhotoResults) Merge() (merged PhotoResults, count int, err error) {
|
func (m PhotoResults) Merge() (merged PhotoResults, count int, err error) {
|
||||||
count = len(photos)
|
count = len(m)
|
||||||
merged = make(PhotoResults, 0, count)
|
merged = make(PhotoResults, 0, count)
|
||||||
|
|
||||||
var i int
|
var i int
|
||||||
var photoId uint
|
var photoId uint
|
||||||
|
|
||||||
for _, photo := range photos {
|
for _, photo := range m {
|
||||||
file := entity.File{}
|
file := entity.File{}
|
||||||
|
|
||||||
if err = deepcopier.Copy(&file).From(photo); err != nil {
|
if err = deepcopier.Copy(&file).From(photo); err != nil {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func TestPhotosResults_Merged(t *testing.T) {
|
|||||||
ID: 111111,
|
ID: 111111,
|
||||||
CreatedAt: time.Time{},
|
CreatedAt: time.Time{},
|
||||||
UpdatedAt: time.Time{},
|
UpdatedAt: time.Time{},
|
||||||
DeletedAt: time.Time{},
|
DeletedAt: &time.Time{},
|
||||||
TakenAt: time.Time{},
|
TakenAt: time.Time{},
|
||||||
TakenAtLocal: time.Time{},
|
TakenAtLocal: time.Time{},
|
||||||
TakenSrc: "",
|
TakenSrc: "",
|
||||||
@@ -71,7 +71,7 @@ func TestPhotosResults_Merged(t *testing.T) {
|
|||||||
ID: 22222,
|
ID: 22222,
|
||||||
CreatedAt: time.Time{},
|
CreatedAt: time.Time{},
|
||||||
UpdatedAt: time.Time{},
|
UpdatedAt: time.Time{},
|
||||||
DeletedAt: time.Time{},
|
DeletedAt: &time.Time{},
|
||||||
TakenAt: time.Time{},
|
TakenAt: time.Time{},
|
||||||
TakenAtLocal: time.Time{},
|
TakenAtLocal: time.Time{},
|
||||||
TakenSrc: "",
|
TakenSrc: "",
|
||||||
@@ -141,7 +141,7 @@ func TestPhotosResults_UIDs(t *testing.T) {
|
|||||||
ID: 111111,
|
ID: 111111,
|
||||||
CreatedAt: time.Time{},
|
CreatedAt: time.Time{},
|
||||||
UpdatedAt: time.Time{},
|
UpdatedAt: time.Time{},
|
||||||
DeletedAt: time.Time{},
|
DeletedAt: &time.Time{},
|
||||||
TakenAt: time.Time{},
|
TakenAt: time.Time{},
|
||||||
TakenAtLocal: time.Time{},
|
TakenAtLocal: time.Time{},
|
||||||
TakenSrc: "",
|
TakenSrc: "",
|
||||||
@@ -200,7 +200,7 @@ func TestPhotosResults_UIDs(t *testing.T) {
|
|||||||
ID: 22222,
|
ID: 22222,
|
||||||
CreatedAt: time.Time{},
|
CreatedAt: time.Time{},
|
||||||
UpdatedAt: time.Time{},
|
UpdatedAt: time.Time{},
|
||||||
DeletedAt: time.Time{},
|
DeletedAt: &time.Time{},
|
||||||
TakenAt: time.Time{},
|
TakenAt: time.Time{},
|
||||||
TakenAtLocal: time.Time{},
|
TakenAtLocal: time.Time{},
|
||||||
TakenSrc: "",
|
TakenSrc: "",
|
||||||
@@ -267,7 +267,7 @@ func TestPhotosResult_ShareFileName(t *testing.T) {
|
|||||||
ID: 111111,
|
ID: 111111,
|
||||||
CreatedAt: time.Time{},
|
CreatedAt: time.Time{},
|
||||||
UpdatedAt: time.Time{},
|
UpdatedAt: time.Time{},
|
||||||
DeletedAt: time.Time{},
|
DeletedAt: &time.Time{},
|
||||||
TakenAt: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC),
|
TakenAt: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||||
TakenAtLocal: time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC),
|
TakenAtLocal: time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||||
TakenSrc: "",
|
TakenSrc: "",
|
||||||
@@ -330,7 +330,7 @@ func TestPhotosResult_ShareFileName(t *testing.T) {
|
|||||||
ID: 111111,
|
ID: 111111,
|
||||||
CreatedAt: time.Time{},
|
CreatedAt: time.Time{},
|
||||||
UpdatedAt: time.Time{},
|
UpdatedAt: time.Time{},
|
||||||
DeletedAt: time.Time{},
|
DeletedAt: &time.Time{},
|
||||||
TakenAt: time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC),
|
TakenAt: time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||||
TakenAtLocal: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC),
|
TakenAtLocal: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||||
TakenSrc: "",
|
TakenSrc: "",
|
||||||
@@ -394,7 +394,7 @@ func TestPhotosResult_ShareFileName(t *testing.T) {
|
|||||||
ID: 111111,
|
ID: 111111,
|
||||||
CreatedAt: time.Time{},
|
CreatedAt: time.Time{},
|
||||||
UpdatedAt: time.Time{},
|
UpdatedAt: time.Time{},
|
||||||
DeletedAt: time.Time{},
|
DeletedAt: &time.Time{},
|
||||||
TakenAt: time.Date(2022, 11, 11, 9, 7, 18, 0, time.UTC),
|
TakenAt: time.Date(2022, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||||
TakenAtLocal: time.Date(2022, 11, 11, 9, 7, 18, 0, time.UTC),
|
TakenAtLocal: time.Date(2022, 11, 11, 9, 7, 18, 0, time.UTC),
|
||||||
TakenSrc: "",
|
TakenSrc: "",
|
||||||
|
|||||||
@@ -25,40 +25,40 @@ func UserPhotosViewerResults(f form.SearchPhotos, sess *entity.Session, contentU
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ViewerResult returns a new photo viewer result.
|
// ViewerResult returns a new photo viewer result.
|
||||||
func (photo Photo) ViewerResult(contentUri, apiUri, previewToken, downloadToken string) viewer.Result {
|
func (m Photo) ViewerResult(contentUri, apiUri, previewToken, downloadToken string) viewer.Result {
|
||||||
return viewer.Result{
|
return viewer.Result{
|
||||||
UID: photo.PhotoUID,
|
UID: m.PhotoUID,
|
||||||
Title: photo.PhotoTitle,
|
Title: m.PhotoTitle,
|
||||||
TakenAtLocal: photo.TakenAtLocal,
|
TakenAtLocal: m.TakenAtLocal,
|
||||||
Description: photo.PhotoDescription,
|
Description: m.PhotoDescription,
|
||||||
Favorite: photo.PhotoFavorite,
|
Favorite: m.PhotoFavorite,
|
||||||
Playable: photo.IsPlayable(),
|
Playable: m.IsPlayable(),
|
||||||
DownloadUrl: viewer.DownloadUrl(photo.FileHash, apiUri, downloadToken),
|
DownloadUrl: viewer.DownloadUrl(m.FileHash, apiUri, downloadToken),
|
||||||
Width: photo.FileWidth,
|
Width: m.FileWidth,
|
||||||
Height: photo.FileHeight,
|
Height: m.FileHeight,
|
||||||
Thumbs: thumb.Public{
|
Thumbs: thumb.Public{
|
||||||
Fit720: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit720], contentUri, previewToken),
|
Fit720: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit720], contentUri, previewToken),
|
||||||
Fit1280: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit1280], contentUri, previewToken),
|
Fit1280: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit1280], contentUri, previewToken),
|
||||||
Fit1920: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit1920], contentUri, previewToken),
|
Fit1920: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit1920], contentUri, previewToken),
|
||||||
Fit2048: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit2048], contentUri, previewToken),
|
Fit2048: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit2048], contentUri, previewToken),
|
||||||
Fit2560: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit2560], contentUri, previewToken),
|
Fit2560: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit2560], contentUri, previewToken),
|
||||||
Fit3840: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit3840], contentUri, previewToken),
|
Fit3840: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit3840], contentUri, previewToken),
|
||||||
Fit4096: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit4096], contentUri, previewToken),
|
Fit4096: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit4096], contentUri, previewToken),
|
||||||
Fit7680: thumb.New(photo.FileWidth, photo.FileHeight, photo.FileHash, thumb.Sizes[thumb.Fit7680], contentUri, previewToken),
|
Fit7680: thumb.New(m.FileWidth, m.FileHeight, m.FileHash, thumb.Sizes[thumb.Fit7680], contentUri, previewToken),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewerJSON returns the results as photo viewer JSON.
|
// ViewerJSON returns the results as photo viewer JSON.
|
||||||
func (photos PhotoResults) ViewerJSON(contentUri, apiUri, previewToken, downloadToken string) ([]byte, error) {
|
func (m PhotoResults) ViewerJSON(contentUri, apiUri, previewToken, downloadToken string) ([]byte, error) {
|
||||||
return json.Marshal(photos.ViewerResults(contentUri, apiUri, previewToken, downloadToken))
|
return json.Marshal(m.ViewerResults(contentUri, apiUri, previewToken, downloadToken))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewerResults returns the results photo viewer formatted.
|
// ViewerResults returns the results photo viewer formatted.
|
||||||
func (photos PhotoResults) ViewerResults(contentUri, apiUri, previewToken, downloadToken string) (results viewer.Results) {
|
func (m PhotoResults) ViewerResults(contentUri, apiUri, previewToken, downloadToken string) (results viewer.Results) {
|
||||||
results = make(viewer.Results, 0, len(photos))
|
results = make(viewer.Results, 0, len(m))
|
||||||
|
|
||||||
for _, p := range photos {
|
for _, p := range m {
|
||||||
results = append(results, p.ViewerResult(contentUri, apiUri, previewToken, downloadToken))
|
results = append(results, p.ViewerResult(contentUri, apiUri, previewToken, downloadToken))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func TestPhotoResults_ViewerJSON(t *testing.T) {
|
|||||||
ID: 111111,
|
ID: 111111,
|
||||||
CreatedAt: time.Time{},
|
CreatedAt: time.Time{},
|
||||||
UpdatedAt: time.Time{},
|
UpdatedAt: time.Time{},
|
||||||
DeletedAt: time.Time{},
|
DeletedAt: &time.Time{},
|
||||||
TakenAt: time.Time{},
|
TakenAt: time.Time{},
|
||||||
TakenAtLocal: time.Time{},
|
TakenAtLocal: time.Time{},
|
||||||
TakenSrc: "",
|
TakenSrc: "",
|
||||||
@@ -71,7 +71,7 @@ func TestPhotoResults_ViewerJSON(t *testing.T) {
|
|||||||
ID: 22222,
|
ID: 22222,
|
||||||
CreatedAt: time.Time{},
|
CreatedAt: time.Time{},
|
||||||
UpdatedAt: time.Time{},
|
UpdatedAt: time.Time{},
|
||||||
DeletedAt: time.Time{},
|
DeletedAt: &time.Time{},
|
||||||
TakenAt: time.Time{},
|
TakenAt: time.Time{},
|
||||||
TakenAtLocal: time.Time{},
|
TakenAtLocal: time.Time{},
|
||||||
TakenSrc: "",
|
TakenSrc: "",
|
||||||
|
|||||||
Reference in New Issue
Block a user