mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -91,10 +91,10 @@ func CreateAlbum(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Album
|
||||
var frm form.Album
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
@@ -102,8 +102,8 @@ func CreateAlbum(router *gin.RouterGroup) {
|
||||
albumMutex.Lock()
|
||||
defer albumMutex.Unlock()
|
||||
|
||||
a := entity.NewUserAlbum(f.AlbumTitle, entity.AlbumManual, s.UserUID)
|
||||
a.AlbumFavorite = f.AlbumFavorite
|
||||
a := entity.NewUserAlbum(frm.AlbumTitle, entity.AlbumManual, s.UserUID)
|
||||
a.AlbumFavorite = frm.AlbumFavorite
|
||||
|
||||
// Existing album?
|
||||
if found := a.Find(); found == nil {
|
||||
@@ -174,7 +174,7 @@ func UpdateAlbum(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
f, err := form.NewAlbum(a)
|
||||
frm, err := form.NewAlbum(a)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -183,7 +183,7 @@ func UpdateAlbum(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err = c.BindJSON(&f); err != nil {
|
||||
if err = c.BindJSON(frm); err != nil {
|
||||
log.Error(err)
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
@@ -192,7 +192,7 @@ func UpdateAlbum(router *gin.RouterGroup) {
|
||||
albumMutex.Lock()
|
||||
defer albumMutex.Unlock()
|
||||
|
||||
if err = a.SaveForm(f); err != nil {
|
||||
if err = a.SaveForm(frm); err != nil {
|
||||
log.Error(err)
|
||||
AbortSaveFailed(c)
|
||||
return
|
||||
@@ -421,17 +421,17 @@ func CloneAlbums(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err = c.BindJSON(&f); err != nil {
|
||||
if err = c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
var added []entity.PhotoAlbum
|
||||
|
||||
for _, albumUid := range f.Albums {
|
||||
for _, albumUid := range frm.Albums {
|
||||
cloneAlbum, queryErr := query.AlbumByUID(albumUid)
|
||||
|
||||
if queryErr != nil {
|
||||
@@ -481,10 +481,10 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
@@ -507,13 +507,13 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
|
||||
} else if !a.HasID() {
|
||||
AbortAlbumNotFound(c)
|
||||
return
|
||||
} else if f.Empty() {
|
||||
} else if frm.Empty() {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoItemsSelected)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch selection from index.
|
||||
photos, err := query.SelectedPhotos(f)
|
||||
photos, err := query.SelectedPhotos(frm)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("album: %s", err)
|
||||
@@ -591,15 +591,15 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if len(f.Photos) == 0 {
|
||||
if len(frm.Photos) == 0 {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoItemsSelected)
|
||||
return
|
||||
}
|
||||
@@ -624,7 +624,7 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
removed := a.RemovePhotos(f.Photos)
|
||||
removed := a.RemovePhotos(frm.Photos)
|
||||
|
||||
if len(removed) > 0 {
|
||||
if len(removed) == 1 {
|
||||
@@ -641,6 +641,6 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup) {
|
||||
SaveAlbumYaml(a)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgChangesSaved), "album": a, "photos": f.Photos, "removed": removed})
|
||||
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": i18n.Msg(i18n.MsgChangesSaved), "album": a, "photos": frm.Photos, "removed": removed})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ func SearchAlbums(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
var err error
|
||||
var f form.SearchAlbums
|
||||
var frm form.SearchAlbums
|
||||
|
||||
// Abort if request params are invalid.
|
||||
if err = c.MustBindWith(&f, binding.Form); err != nil {
|
||||
if err = c.MustBindWith(&frm, binding.Form); err != nil {
|
||||
event.AuditWarn([]string{ClientIP(c), "session %s", "albums", "search", "form invalid", "%s"}, s.RefID, err)
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
@@ -50,11 +50,11 @@ func SearchAlbums(router *gin.RouterGroup) {
|
||||
|
||||
// Ignore private flag if feature is disabled.
|
||||
if !settings.Features.Private {
|
||||
f.Public = false
|
||||
frm.Public = false
|
||||
}
|
||||
|
||||
// Find matching albums.
|
||||
result, err := search.UserAlbums(f, s)
|
||||
result, err := search.UserAlbums(frm, s)
|
||||
|
||||
// Ok?
|
||||
if err != nil {
|
||||
@@ -64,8 +64,8 @@ func SearchAlbums(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
AddCountHeader(c, len(result))
|
||||
AddLimitHeader(c, f.Count)
|
||||
AddOffsetHeader(c, f.Offset)
|
||||
AddLimitHeader(c, frm.Count)
|
||||
AddOffsetHeader(c, frm.Offset)
|
||||
AddTokenHeaders(c, s)
|
||||
|
||||
// Return as JSON.
|
||||
|
||||
@@ -39,24 +39,24 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if len(f.Photos) == 0 {
|
||||
if len(frm.Photos) == 0 {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoItemsSelected)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("photos: archiving %s", clean.Log(f.String()))
|
||||
log.Infof("photos: archiving %s", clean.Log(frm.String()))
|
||||
|
||||
if get.Config().SidecarYaml() {
|
||||
// Fetch selection from index.
|
||||
photos, err := query.SelectedPhotos(f)
|
||||
photos, err := query.SelectedPhotos(frm)
|
||||
|
||||
if err != nil {
|
||||
AbortEntityNotFound(c)
|
||||
@@ -70,12 +70,12 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
|
||||
SaveSidecarYaml(&p)
|
||||
}
|
||||
}
|
||||
} else if err := entity.Db().Where("photo_uid IN (?)", f.Photos).Delete(&entity.Photo{}).Error; err != nil {
|
||||
log.Errorf("archive: failed to archive %d pictures (%s)", len(f.Photos), err)
|
||||
} else if err := entity.Db().Where("photo_uid IN (?)", frm.Photos).Delete(&entity.Photo{}).Error; err != nil {
|
||||
log.Errorf("archive: failed to archive %d pictures (%s)", len(frm.Photos), err)
|
||||
AbortSaveFailed(c)
|
||||
return
|
||||
} else if err = entity.Db().Model(&entity.PhotoAlbum{}).Where("photo_uid IN (?)", f.Photos).UpdateColumn("hidden", true).Error; err != nil {
|
||||
log.Errorf("archive: failed to flag %d pictures as hidden (%s)", len(f.Photos), err)
|
||||
} else if err = entity.Db().Model(&entity.PhotoAlbum{}).Where("photo_uid IN (?)", frm.Photos).UpdateColumn("hidden", true).Error; err != nil {
|
||||
log.Errorf("archive: failed to flag %d pictures as hidden (%s)", len(frm.Photos), err)
|
||||
}
|
||||
|
||||
// Update precalculated photo and file counts.
|
||||
@@ -86,7 +86,7 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
|
||||
|
||||
UpdateClientConfig()
|
||||
|
||||
event.EntitiesArchived("photos", f.Photos)
|
||||
event.EntitiesArchived("photos", frm.Photos)
|
||||
|
||||
c.JSON(http.StatusOK, i18n.NewResponse(http.StatusOK, i18n.MsgSelectionArchived))
|
||||
})
|
||||
@@ -110,23 +110,23 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if len(f.Photos) == 0 {
|
||||
if len(frm.Photos) == 0 {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoItemsSelected)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("photos: restoring %s", clean.Log(f.String()))
|
||||
log.Infof("photos: restoring %s", clean.Log(frm.String()))
|
||||
|
||||
if get.Config().SidecarYaml() {
|
||||
// Fetch selection from index.
|
||||
photos, err := query.SelectedPhotos(f)
|
||||
photos, err := query.SelectedPhotos(frm)
|
||||
|
||||
if err != nil {
|
||||
AbortEntityNotFound(c)
|
||||
@@ -140,7 +140,7 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
|
||||
SaveSidecarYaml(&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 (?)", frm.Photos).
|
||||
UpdateColumn("deleted_at", gorm.Expr("NULL")).Error; err != nil {
|
||||
log.Errorf("restore: %s", err)
|
||||
AbortSaveFailed(c)
|
||||
@@ -155,7 +155,7 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
|
||||
|
||||
UpdateClientConfig()
|
||||
|
||||
event.EntitiesRestored("photos", f.Photos)
|
||||
event.EntitiesRestored("photos", frm.Photos)
|
||||
|
||||
c.JSON(http.StatusOK, i18n.NewResponse(http.StatusOK, i18n.MsgSelectionRestored))
|
||||
})
|
||||
@@ -179,22 +179,22 @@ func BatchPhotosApprove(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if len(f.Photos) == 0 {
|
||||
if len(frm.Photos) == 0 {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoItemsSelected)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("photos: approving %s", clean.Log(f.String()))
|
||||
log.Infof("photos: approving %s", clean.Log(frm.String()))
|
||||
|
||||
// Fetch selection from index.
|
||||
photos, err := query.SelectedPhotos(f)
|
||||
photos, err := query.SelectedPhotos(frm)
|
||||
|
||||
if err != nil {
|
||||
AbortEntityNotFound(c)
|
||||
@@ -238,22 +238,22 @@ func BatchAlbumsDelete(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
// Get album UIDs.
|
||||
albumUIDs := f.Albums
|
||||
albumUIDs := frm.Albums
|
||||
|
||||
if len(albumUIDs) == 0 {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoAlbumsSelected)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("albums: deleting %s", clean.Log(f.String()))
|
||||
log.Infof("albums: deleting %s", clean.Log(frm.String()))
|
||||
|
||||
// Fetch albums.
|
||||
albums, queryErr := query.AlbumsByUID(albumUIDs, false)
|
||||
@@ -311,21 +311,21 @@ func BatchPhotosPrivate(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if len(f.Photos) == 0 {
|
||||
if len(frm.Photos) == 0 {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoItemsSelected)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("photos: updating private flag for %s", clean.Log(f.String()))
|
||||
log.Infof("photos: updating private flag for %s", clean.Log(frm.String()))
|
||||
|
||||
if err := entity.Db().Model(entity.Photo{}).Where("photo_uid IN (?)", f.Photos).UpdateColumn("photo_private",
|
||||
if err := entity.Db().Model(entity.Photo{}).Where("photo_uid IN (?)", frm.Photos).UpdateColumn("photo_private",
|
||||
gorm.Expr("CASE WHEN photo_private > 0 THEN 0 ELSE 1 END")).Error; err != nil {
|
||||
log.Errorf("private: %s", err)
|
||||
AbortSaveFailed(c)
|
||||
@@ -336,7 +336,7 @@ func BatchPhotosPrivate(router *gin.RouterGroup) {
|
||||
logWarn("index", entity.UpdateCounts())
|
||||
|
||||
// Fetch selection from index.
|
||||
if photos, err := query.SelectedPhotos(f); err == nil {
|
||||
if photos, err := query.SelectedPhotos(frm); err == nil {
|
||||
for _, p := range photos {
|
||||
SaveSidecarYaml(&p)
|
||||
}
|
||||
@@ -370,24 +370,24 @@ func BatchLabelsDelete(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if len(f.Labels) == 0 {
|
||||
if len(frm.Labels) == 0 {
|
||||
log.Error("no labels selected")
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoLabelsSelected)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("labels: deleting %s", clean.Log(f.String()))
|
||||
log.Infof("labels: deleting %s", clean.Log(frm.String()))
|
||||
|
||||
var labels entity.Labels
|
||||
|
||||
if err := entity.Db().Where("label_uid IN (?)", f.Labels).Find(&labels).Error; err != nil {
|
||||
if err := entity.Db().Where("label_uid IN (?)", frm.Labels).Find(&labels).Error; err != nil {
|
||||
Error(c, http.StatusInternalServerError, err, i18n.ErrDeleteFailed)
|
||||
return
|
||||
}
|
||||
@@ -398,7 +398,7 @@ func BatchLabelsDelete(router *gin.RouterGroup) {
|
||||
|
||||
UpdateClientConfig()
|
||||
|
||||
event.EntitiesDeleted("labels", f.Labels)
|
||||
event.EntitiesDeleted("labels", frm.Labels)
|
||||
|
||||
c.JSON(http.StatusOK, i18n.NewResponse(http.StatusOK, i18n.MsgLabelsDeleted))
|
||||
})
|
||||
@@ -429,9 +429,9 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
@@ -442,19 +442,19 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
|
||||
var err error
|
||||
|
||||
// Abort if user wants to delete all but does not have sufficient privileges.
|
||||
if f.All && !acl.Rules.AllowAll(acl.ResourcePhotos, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) {
|
||||
if frm.All && !acl.Rules.AllowAll(acl.ResourcePhotos, s.UserRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) {
|
||||
AbortForbidden(c)
|
||||
return
|
||||
}
|
||||
|
||||
// Get selection or all archived photos if f.All is true.
|
||||
if len(f.Photos) == 0 && !f.All {
|
||||
if len(frm.Photos) == 0 && !frm.All {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoItemsSelected)
|
||||
return
|
||||
} else if f.All {
|
||||
} else if frm.All {
|
||||
photos, err = query.ArchivedPhotos(1000000, 0)
|
||||
} else {
|
||||
photos, err = query.SelectedPhotos(f)
|
||||
photos, err = query.SelectedPhotos(frm)
|
||||
}
|
||||
|
||||
// Abort if the query failed or no photos were found.
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func TestBatchPhotosArchive(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetPhoto(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/photos/ps6sg6be2lvl0yh7")
|
||||
@@ -40,7 +40,7 @@ func TestBatchPhotosArchive(t *testing.T) {
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrNoItemsSelected), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
BatchPhotosArchive(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/batch/photos/archive", `{"photos": 123}`)
|
||||
@@ -49,7 +49,7 @@ func TestBatchPhotosArchive(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBatchPhotosRestore(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
// Register routes.
|
||||
@@ -85,7 +85,7 @@ func TestBatchPhotosRestore(t *testing.T) {
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrNoItemsSelected), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
BatchPhotosRestore(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/batch/photos/restore", `{"photos": 123}`)
|
||||
@@ -100,7 +100,7 @@ func TestBatchAlbumsDelete(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
uid := gjson.Get(r.Body.String(), "UID").String()
|
||||
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
// Register routes.
|
||||
@@ -129,7 +129,7 @@ func TestBatchAlbumsDelete(t *testing.T) {
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrNoAlbumsSelected), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
BatchAlbumsDelete(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/batch/albums/delete", `{"albums": 123}`)
|
||||
@@ -138,7 +138,7 @@ func TestBatchAlbumsDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBatchPhotosPrivate(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
// Register routes.
|
||||
@@ -168,7 +168,7 @@ func TestBatchPhotosPrivate(t *testing.T) {
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrNoItemsSelected), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
BatchPhotosPrivate(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/batch/photos/private", `{"photos": 123}`)
|
||||
@@ -177,7 +177,7 @@ func TestBatchPhotosPrivate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBatchLabelsDelete(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
// Register routes.
|
||||
@@ -214,7 +214,7 @@ func TestBatchLabelsDelete(t *testing.T) {
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrNoLabelsSelected), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
BatchLabelsDelete(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/batch/labels/delete", `{"labels": 123}`)
|
||||
@@ -223,7 +223,7 @@ func TestBatchLabelsDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBatchPhotosApprove(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
// Register routes.
|
||||
@@ -257,7 +257,7 @@ func TestBatchPhotosApprove(t *testing.T) {
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrNoItemsSelected), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
BatchPhotosApprove(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/batch/photos/approve", `{"photos": 123}`)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestGetConfig(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetClientConfig(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/config")
|
||||
|
||||
@@ -26,17 +26,17 @@ func Connect(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Connect
|
||||
var frm form.Connect
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
log.Warnf("connect: invalid form values (%s)", clean.Log(name))
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrAccountConnect)
|
||||
return
|
||||
}
|
||||
|
||||
if f.Invalid() {
|
||||
log.Warnf("connect: invalid token %s", clean.Log(f.Token))
|
||||
if frm.Invalid() {
|
||||
log.Warnf("connect: invalid token %s", clean.Log(frm.Token))
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrAccountConnect)
|
||||
return
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func Connect(router *gin.RouterGroup) {
|
||||
switch name {
|
||||
case "hub":
|
||||
old := conf.Hub().Session
|
||||
err = conf.RenewApiKeysWithToken(f.Token)
|
||||
err = conf.RenewApiKeysWithToken(frm.Token)
|
||||
restart = old != conf.Hub().Session
|
||||
default:
|
||||
log.Errorf("connect: invalid service %s", clean.Log(name))
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestGetErrors(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetErrors(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/errors")
|
||||
@@ -18,7 +18,7 @@ func TestGetErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteErrors(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
DeleteErrors(router)
|
||||
r := PerformRequest(app, "DELETE", "/api/v1/errors")
|
||||
|
||||
@@ -65,10 +65,10 @@ func UpdateFace(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Face
|
||||
var frm form.Face
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
@@ -82,17 +82,17 @@ func UpdateFace(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Change visibility?
|
||||
if !f.FaceHidden && f.FaceHidden == m.FaceHidden {
|
||||
if !frm.FaceHidden && frm.FaceHidden == m.FaceHidden {
|
||||
// Do nothing.
|
||||
} else if err := m.Update("FaceHidden", f.FaceHidden); err != nil {
|
||||
} else if err := m.Update("FaceHidden", frm.FaceHidden); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
// Change subject?
|
||||
if f.SubjUID == "" {
|
||||
if frm.SubjUID == "" {
|
||||
// Do nothing.
|
||||
} else if err := m.SetSubjectUID(f.SubjUID); err != nil {
|
||||
} else if err := m.SetSubjectUID(frm.SubjUID); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -35,16 +35,16 @@ func SearchFaces(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.SearchFaces
|
||||
var frm form.SearchFaces
|
||||
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
err := c.MustBindWith(&frm, binding.Form)
|
||||
|
||||
if err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := search.Faces(f)
|
||||
result, err := search.Faces(frm)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(400, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
@@ -52,8 +52,8 @@ func SearchFaces(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
AddCountHeader(c, len(result))
|
||||
AddLimitHeader(c, f.Count)
|
||||
AddOffsetHeader(c, f.Offset)
|
||||
AddLimitHeader(c, frm.Count)
|
||||
AddOffsetHeader(c, frm.Offset)
|
||||
AddTokenHeaders(c, s)
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
|
||||
@@ -32,20 +32,20 @@ func SendFeedback(router *gin.RouterGroup) {
|
||||
|
||||
conf.RenewApiKeys()
|
||||
|
||||
var f form.Feedback
|
||||
var frm form.Feedback
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if f.Empty() {
|
||||
if frm.Empty() {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoItemsSelected)
|
||||
return
|
||||
}
|
||||
|
||||
if err := conf.Hub().SendFeedback(f); err != nil {
|
||||
if err := conf.Hub().SendFeedback(frm); err != nil {
|
||||
log.Error(err)
|
||||
AbortSaveFailed(c)
|
||||
return
|
||||
|
||||
@@ -57,7 +57,7 @@ func ChangeFileOrientation(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Init form with model values
|
||||
f, err := form.NewFile(m)
|
||||
frm, err := form.NewFile(m)
|
||||
|
||||
if err != nil {
|
||||
Abort(c, http.StatusInternalServerError, i18n.ErrSaveFailed)
|
||||
@@ -65,25 +65,25 @@ func ChangeFileOrientation(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err = c.BindJSON(&f); err != nil {
|
||||
if err = c.BindJSON(&frm); err != nil {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Update orientation if it was changed.
|
||||
if m.Orientation() != f.Orientation() {
|
||||
if m.Orientation() != frm.Orientation() {
|
||||
fileName := photoprism.FileName(m.FileRoot, m.FileName)
|
||||
mf, err := photoprism.NewMediaFile(fileName)
|
||||
mf, fileErr := photoprism.NewMediaFile(fileName)
|
||||
|
||||
// Check if file exists.
|
||||
if err != nil {
|
||||
if fileErr != nil {
|
||||
Abort(c, http.StatusInternalServerError, i18n.ErrFileNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Update file header.
|
||||
if err = mf.ChangeOrientation(f.Orientation()); err != nil {
|
||||
log.Debugf("file: %s in %s (change orientation)", err, clean.Log(mf.BaseName()))
|
||||
if fileErr = mf.ChangeOrientation(frm.Orientation()); fileErr != nil {
|
||||
log.Debugf("file: %s in %s (change orientation)", fileErr, clean.Log(mf.BaseName()))
|
||||
Abort(c, http.StatusInternalServerError, i18n.ErrSaveFailed)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -52,10 +52,10 @@ func SearchFolders(router *gin.RouterGroup, urlPath, rootName, rootPath string)
|
||||
return
|
||||
}
|
||||
|
||||
var f form.SearchFolders
|
||||
var frm form.SearchFolders
|
||||
|
||||
start := time.Now()
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
err := c.MustBindWith(&frm, binding.Form)
|
||||
|
||||
if err != nil {
|
||||
AbortBadRequest(c)
|
||||
@@ -67,19 +67,19 @@ func SearchFolders(router *gin.RouterGroup, urlPath, rootName, rootPath string)
|
||||
|
||||
// Exclude private content?
|
||||
if !get.Config().Settings().Features.Private {
|
||||
f.Public = false
|
||||
frm.Public = false
|
||||
} else if acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) {
|
||||
f.Public = true
|
||||
frm.Public = true
|
||||
}
|
||||
|
||||
cache := get.FolderCache()
|
||||
recursive := f.Recursive
|
||||
listFiles := f.Files
|
||||
uncached := listFiles || f.Uncached
|
||||
recursive := frm.Recursive
|
||||
listFiles := frm.Files
|
||||
uncached := listFiles || frm.Uncached
|
||||
resp := FoldersResponse{Root: rootName, Recursive: recursive, Cached: !uncached}
|
||||
path := clean.UserPath(c.Param("path"))
|
||||
|
||||
cacheKey := fmt.Sprintf("folder:%s:%t:%t:%t", filepath.Join(rootName, path), recursive, listFiles, f.Public)
|
||||
cacheKey := fmt.Sprintf("folder:%s:%t:%t:%t", filepath.Join(rootName, path), recursive, listFiles, frm.Public)
|
||||
|
||||
if !uncached {
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
@@ -101,7 +101,7 @@ func SearchFolders(router *gin.RouterGroup, urlPath, rootName, rootPath string)
|
||||
}
|
||||
|
||||
if listFiles {
|
||||
if files, err := query.FilesByPath(f.Count, f.Offset, rootName, path, f.Public); err != nil {
|
||||
if files, err := query.FilesByPath(frm.Count, frm.Offset, rootName, path, frm.Public); err != nil {
|
||||
log.Errorf("folder: %s", err)
|
||||
} else {
|
||||
resp.Files = files
|
||||
@@ -115,8 +115,8 @@ func SearchFolders(router *gin.RouterGroup, urlPath, rootName, rootPath string)
|
||||
|
||||
AddFileCountHeaders(c, len(resp.Files), len(resp.Folders))
|
||||
AddCountHeader(c, len(resp.Files)+len(resp.Folders))
|
||||
AddLimitHeader(c, f.Count)
|
||||
AddOffsetHeader(c, f.Offset)
|
||||
AddLimitHeader(c, frm.Count)
|
||||
AddOffsetHeader(c, frm.Offset)
|
||||
AddTokenHeaders(c, s)
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
|
||||
@@ -55,10 +55,10 @@ func StartImport(router *gin.RouterGroup) {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
var f form.ImportOptions
|
||||
var frm form.ImportOptions
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
@@ -69,8 +69,8 @@ func StartImport(router *gin.RouterGroup) {
|
||||
// Import from subfolder?
|
||||
if srcFolder = c.Param("path"); srcFolder != "" && srcFolder != "/" {
|
||||
srcFolder = clean.UserPath(srcFolder)
|
||||
} else if f.Path != "" {
|
||||
srcFolder = clean.UserPath(f.Path)
|
||||
} else if frm.Path != "" {
|
||||
srcFolder = clean.UserPath(frm.Path)
|
||||
}
|
||||
|
||||
// To avoid conflicts, uploads are imported from "import_path/upload/session_ref/timestamp".
|
||||
@@ -98,7 +98,7 @@ func StartImport(router *gin.RouterGroup) {
|
||||
var opt photoprism.ImportOptions
|
||||
|
||||
// Copy or move files to the destination folder?
|
||||
if f.Move {
|
||||
if frm.Move {
|
||||
event.InfoMsg(i18n.MsgMovingFilesFrom, clean.Log(filepath.Base(importPath)))
|
||||
opt = photoprism.ImportOptionsMove(importPath, destFolder)
|
||||
} else {
|
||||
@@ -107,10 +107,10 @@ func StartImport(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Add imported files to albums if allowed.
|
||||
if len(f.Albums) > 0 &&
|
||||
if len(frm.Albums) > 0 &&
|
||||
acl.Rules.AllowAny(acl.ResourceAlbums, s.UserRole(), acl.Permissions{acl.ActionCreate, acl.ActionUpload}) {
|
||||
log.Debugf("import: adding files to album %s", clean.Log(strings.Join(f.Albums, " and ")))
|
||||
opt.Albums = f.Albums
|
||||
log.Debugf("import: adding files to album %s", clean.Log(strings.Join(frm.Albums, " and ")))
|
||||
opt.Albums = frm.Albums
|
||||
}
|
||||
|
||||
// Set user UID if known.
|
||||
@@ -159,7 +159,7 @@ func StartImport(router *gin.RouterGroup) {
|
||||
event.Publish("import.completed", eventData)
|
||||
event.Publish("index.completed", eventData)
|
||||
|
||||
for _, uid := range f.Albums {
|
||||
for _, uid := range frm.Albums {
|
||||
PublishAlbumEvent(StatusUpdated, uid, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCancelImport(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
CancelImport(router)
|
||||
r := PerformRequest(app, "DELETE", "/api/v1/import")
|
||||
|
||||
@@ -47,10 +47,10 @@ func StartIndexing(router *gin.RouterGroup) {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
var f form.IndexOptions
|
||||
var frm form.IndexOptions
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func StartIndexing(router *gin.RouterGroup) {
|
||||
convert := settings.Index.Convert && conf.SidecarWritable()
|
||||
skipArchived := settings.Index.SkipArchived
|
||||
|
||||
indOpt := photoprism.NewIndexOptions(filepath.Clean(f.Path), f.Rescan, convert, true, false, skipArchived)
|
||||
indOpt := photoprism.NewIndexOptions(filepath.Clean(frm.Path), frm.Rescan, convert, true, false, skipArchived)
|
||||
indOpt.SetUser(s.User())
|
||||
|
||||
if len(indOpt.Path) > 1 {
|
||||
@@ -103,7 +103,7 @@ func StartIndexing(router *gin.RouterGroup) {
|
||||
|
||||
// Purge worker options.
|
||||
opt := photoprism.PurgeOptions{
|
||||
Path: filepath.Clean(f.Path),
|
||||
Path: filepath.Clean(frm.Path),
|
||||
Ignore: found,
|
||||
Force: forceUpdate,
|
||||
}
|
||||
@@ -119,7 +119,7 @@ func StartIndexing(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Delete orphaned index entries, sidecar files and thumbnails?
|
||||
if f.Cleanup && s.User().IsAdmin() {
|
||||
if frm.Cleanup && s.User().IsAdmin() {
|
||||
event.Publish("index.updating", event.Data{
|
||||
"uid": indOpt.UID,
|
||||
"action": indOpt.Action,
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCancelIndex(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
CancelIndexing(router)
|
||||
r := PerformRequest(app, "DELETE", "/api/v1/index")
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/entity/query"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
@@ -36,14 +35,7 @@ func UpdateLabel(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Label
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
// Find label by UID.
|
||||
id := clean.UID(c.Param("uid"))
|
||||
m, err := query.LabelByUID(id)
|
||||
|
||||
@@ -52,8 +44,26 @@ func UpdateLabel(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
m.SetName(f.LabelName)
|
||||
entity.Db().Save(&m)
|
||||
// Create new label form.
|
||||
f, formErr := form.NewLabel(m)
|
||||
|
||||
if formErr != nil {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Set form values from request.
|
||||
if formErr = c.BindJSON(f); formErr != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
// Save label and return new model values if successful.
|
||||
if err = m.SaveForm(f); err != nil {
|
||||
log.Error(err)
|
||||
AbortSaveFailed(c)
|
||||
return
|
||||
}
|
||||
|
||||
event.SuccessMsg(i18n.MsgLabelSaved)
|
||||
|
||||
@@ -130,7 +140,7 @@ func DislikeLabel(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := label.Update("LabelFavorite", false); err != nil {
|
||||
if err = label.Update("LabelFavorite", false); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -33,16 +33,16 @@ func SearchLabels(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.SearchLabels
|
||||
var frm form.SearchLabels
|
||||
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
err := c.MustBindWith(&frm, binding.Form)
|
||||
|
||||
if err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := search.Labels(f)
|
||||
result, err := search.Labels(frm)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(400, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
@@ -50,8 +50,8 @@ func SearchLabels(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// TODO c.Header("X-Count", strconv.Itoa(count))
|
||||
AddLimitHeader(c, f.Count)
|
||||
AddOffsetHeader(c, f.Offset)
|
||||
AddLimitHeader(c, frm.Count)
|
||||
AddOffsetHeader(c, frm.Offset)
|
||||
AddTokenHeaders(c, s)
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestSearchLabels(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
SearchLabels(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/labels?count=15")
|
||||
@@ -18,7 +18,7 @@ func TestSearchLabels(t *testing.T) {
|
||||
assert.LessOrEqual(t, int64(4), count.Int())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
SearchLabels(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/labels?xxx=15")
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
)
|
||||
|
||||
func TestUpdateLabel(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdateLabel(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/labels/ls6sg6b1wowuy3c7", `{"Name": "Updated01", "Priority": 2}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/labels/ls6sg6b1wowuy3c7", `{"Name": "Updated01", "Priority": 2, "Favorite": true}`)
|
||||
val := gjson.Get(r.Body.String(), "Name")
|
||||
assert.Equal(t, "Updated01", val.String())
|
||||
val2 := gjson.Get(r.Body.String(), "CustomSlug")
|
||||
@@ -21,14 +21,14 @@ func TestUpdateLabel(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdateLabel(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/labels/ls6sg6b1wowuy3c7", `{"Name": 123, "Priority": 4, "Uncertainty": 80}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdateLabel(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/labels/xxx", `{"Name": "Updated01", "Priority": 4, "Uncertainty": 80}`)
|
||||
|
||||
@@ -25,10 +25,10 @@ func UpdateLink(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Link
|
||||
var frm form.Link
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
log.Debugf("share: %s", err)
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
@@ -36,16 +36,16 @@ func UpdateLink(c *gin.Context) {
|
||||
|
||||
link := entity.FindLink(clean.Token(c.Param("link")))
|
||||
|
||||
link.SetSlug(f.ShareSlug)
|
||||
link.MaxViews = f.MaxViews
|
||||
link.LinkExpires = f.LinkExpires
|
||||
link.SetSlug(frm.ShareSlug)
|
||||
link.MaxViews = frm.MaxViews
|
||||
link.LinkExpires = frm.LinkExpires
|
||||
|
||||
if f.LinkToken != "" {
|
||||
link.LinkToken = strings.TrimSpace(strings.ToLower(f.LinkToken))
|
||||
if frm.LinkToken != "" {
|
||||
link.LinkToken = strings.TrimSpace(strings.ToLower(frm.LinkToken))
|
||||
}
|
||||
|
||||
if f.Password != "" {
|
||||
if err := link.SetPassword(f.Password); err != nil {
|
||||
if frm.Password != "" {
|
||||
if err := link.SetPassword(frm.Password); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
@@ -106,9 +106,9 @@ func CreateLink(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Link
|
||||
var frm form.Link
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
log.Debugf("share: %s", err)
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
@@ -116,12 +116,12 @@ func CreateLink(c *gin.Context) {
|
||||
|
||||
link := entity.NewUserLink(uid, s.UserUID)
|
||||
|
||||
link.SetSlug(f.ShareSlug)
|
||||
link.MaxViews = f.MaxViews
|
||||
link.LinkExpires = f.LinkExpires
|
||||
link.SetSlug(frm.ShareSlug)
|
||||
link.MaxViews = frm.MaxViews
|
||||
link.LinkExpires = frm.LinkExpires
|
||||
|
||||
if f.Password != "" {
|
||||
if err := link.SetPassword(f.Password); err != nil {
|
||||
if frm.Password != "" {
|
||||
if err := link.SetPassword(frm.Password); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestCreateAlbumLink(t *testing.T) {
|
||||
val := gjson.Get(resp.Body.String(), "error")
|
||||
assert.Equal(t, "Album not found", val.String())
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
CreateAlbumLink(router)
|
||||
@@ -73,7 +73,7 @@ func TestUpdateAlbumLink(t *testing.T) {
|
||||
assert.Equal(t, "0", val2.String())
|
||||
uid := gjson.Get(r.Body.String(), "UID").String()
|
||||
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdateAlbumLink(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/albums/as6sg6bxpogaaba7/links/"+uid, `{"Token": "newToken", "Expires": 8000, "Password": "1234nhfhfd"}`)
|
||||
@@ -121,7 +121,7 @@ func TestDeleteAlbumLink(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetAlbumLinks(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
CreateAlbumLink(router)
|
||||
@@ -138,7 +138,7 @@ func TestGetAlbumLinks(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, r2.Code)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetAlbumLinks(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/xxx/links")
|
||||
@@ -181,7 +181,7 @@ func TestCreatePhotoLink(t *testing.T) {
|
||||
t.Fatal(resp.Body.String())
|
||||
}
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
CreatePhotoLink(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/ps6sg6be2lvl0yh7/links", `{"xxx": 123, "Expires": "abc", "CanEdit": "xxx"}`)
|
||||
@@ -203,7 +203,7 @@ func TestUpdatePhotoLink(t *testing.T) {
|
||||
assert.Equal(t, "0", val2.String())
|
||||
uid := gjson.Get(r.Body.String(), "UID").String()
|
||||
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdatePhotoLink(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/ps6sg6be2lvl0yh7/links/"+uid, `{"Token": "newToken", "Expires": 8000, "Password": "1234nhfhfd"}`)
|
||||
@@ -253,7 +253,7 @@ func TestDeletePhotoLink(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetPhotoLinks(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
CreatePhotoLink(router)
|
||||
@@ -269,7 +269,7 @@ func TestGetPhotoLinks(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, r2.Code)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetPhotoLinks(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/photos/xxx/links")
|
||||
@@ -308,7 +308,7 @@ func TestCreateLabelLink(t *testing.T) {
|
||||
t.Fatal(resp.Body.String())
|
||||
}
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
CreateLabelLink(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/labels/ls6sg6b1wowuy3c2/links", `{"xxx": 123, "Expires": "abc", "CanEdit": "xxx"}`)
|
||||
@@ -330,7 +330,7 @@ func TestUpdateLabelLink(t *testing.T) {
|
||||
assert.Equal(t, "0", val2.String())
|
||||
uid := gjson.Get(r.Body.String(), "UID").String()
|
||||
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdateLabelLink(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/labels/ls6sg6b1wowuy3c2/links/"+uid, `{"Token": "newToken", "Expires": 8000, "Password": "1234nhfhfd"}`)
|
||||
@@ -378,7 +378,7 @@ func TestDeleteLabelLink(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetLabelLinks(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
CreateLabelLink(router)
|
||||
@@ -395,7 +395,7 @@ func TestGetLabelLinks(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, r2.Code)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetLabelLinks(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/labels/xxx/links")
|
||||
|
||||
@@ -303,7 +303,7 @@ func TestUpdateMarker(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClearMarkerSubject(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
GetPhoto(router)
|
||||
|
||||
@@ -51,8 +51,8 @@ func OAuthRevoke(router *gin.RouterGroup) {
|
||||
var role acl.Role
|
||||
var err error
|
||||
|
||||
// Token revokation request form.
|
||||
var f form.OAuthRevokeToken
|
||||
// Token revocation request form.
|
||||
var frm form.OAuthRevokeToken
|
||||
|
||||
// Get token and session from request header.
|
||||
if authToken = AuthToken(c); authToken == "" {
|
||||
@@ -73,29 +73,29 @@ func OAuthRevoke(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Get the auth token to be revoked from the submitted form values or the request header.
|
||||
if err = c.ShouldBind(&f); err != nil && authToken == "" {
|
||||
if err = c.ShouldBind(&frm); err != nil && authToken == "" {
|
||||
event.AuditWarn([]string{clientIp, "oauth2", actor, action, "%s"}, err)
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
} else if f.Empty() {
|
||||
f.Token = authToken
|
||||
f.TokenTypeHint = form.AccessToken
|
||||
} else if frm.Empty() {
|
||||
frm.Token = authToken
|
||||
frm.TokenTypeHint = form.AccessToken
|
||||
}
|
||||
|
||||
// Validate revokation form values.
|
||||
if err = f.Validate(); err != nil {
|
||||
// Validate revocation form values.
|
||||
if err = frm.Validate(); err != nil {
|
||||
event.AuditWarn([]string{clientIp, "oauth2", actor, action, "%s"}, err)
|
||||
AbortInvalidCredentials(c)
|
||||
return
|
||||
}
|
||||
|
||||
// Find session to be revoked.
|
||||
switch f.TokenTypeHint {
|
||||
switch frm.TokenTypeHint {
|
||||
case form.RefID:
|
||||
if s == nil || sUserUID == "" || role == acl.RoleNone {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, i18n.NewResponse(http.StatusForbidden, i18n.ErrForbidden))
|
||||
return
|
||||
} else if sess = entity.FindSessionByRefID(f.Token); sess == nil {
|
||||
} else if sess = entity.FindSessionByRefID(frm.Token); sess == nil {
|
||||
AbortInvalidCredentials(c)
|
||||
return
|
||||
}
|
||||
@@ -105,9 +105,9 @@ func OAuthRevoke(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
sess, err = entity.FindSession(f.Token)
|
||||
sess, err = entity.FindSession(frm.Token)
|
||||
case form.AccessToken:
|
||||
sess, err = entity.FindSession(rnd.SessionID(f.Token))
|
||||
sess, err = entity.FindSession(rnd.SessionID(frm.Token))
|
||||
}
|
||||
|
||||
// If not already set, get the log role and actor from the session to be revoked.
|
||||
@@ -124,7 +124,7 @@ func OAuthRevoke(router *gin.RouterGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check revokation request and abort if invalid.
|
||||
// Check revocation request and abort if invalid.
|
||||
if err != nil {
|
||||
event.AuditErr([]string{clientIp, "oauth2", actor, action, "delete %s as %s", "%s"}, clean.Log(sess.RefID), role.String(), err.Error())
|
||||
AbortInvalidCredentials(c)
|
||||
|
||||
@@ -47,24 +47,24 @@ func OAuthToken(router *gin.RouterGroup) {
|
||||
c.Header(header.CacheControl, header.CacheControlNoStore)
|
||||
|
||||
// Token create request form.
|
||||
var f form.OAuthCreateToken
|
||||
var frm form.OAuthCreateToken
|
||||
var sess *entity.Session
|
||||
var client *entity.Client
|
||||
var err error
|
||||
|
||||
// Allow authentication with basic auth and form values.
|
||||
if clientId, clientSecret, _ := header.BasicAuth(c); clientId != "" && clientSecret != "" {
|
||||
f.GrantType = authn.GrantClientCredentials
|
||||
f.ClientID = clientId
|
||||
f.ClientSecret = clientSecret
|
||||
} else if err = c.ShouldBind(&f); err != nil {
|
||||
frm.GrantType = authn.GrantClientCredentials
|
||||
frm.ClientID = clientId
|
||||
frm.ClientSecret = clientSecret
|
||||
} else if err = c.ShouldBind(&frm); err != nil {
|
||||
event.AuditWarn([]string{clientIp, "oauth2", actor, action, "%s"}, err)
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
// Check the credentials for completeness and the correct format.
|
||||
if err = f.Validate(); err != nil {
|
||||
if err = frm.Validate(); err != nil {
|
||||
event.AuditWarn([]string{clientIp, "oauth2", actor, action, "%s"}, err)
|
||||
AbortInvalidCredentials(c)
|
||||
return
|
||||
@@ -79,19 +79,19 @@ func OAuthToken(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
if f.ClientID != "" {
|
||||
actor = fmt.Sprintf("client %s", clean.Log(f.ClientID))
|
||||
} else if f.Username != "" {
|
||||
actor = fmt.Sprintf("user %s", clean.Log(f.Username))
|
||||
} else if f.GrantType == authn.GrantPassword {
|
||||
if frm.ClientID != "" {
|
||||
actor = fmt.Sprintf("client %s", clean.Log(frm.ClientID))
|
||||
} else if frm.Username != "" {
|
||||
actor = fmt.Sprintf("user %s", clean.Log(frm.Username))
|
||||
} else if frm.GrantType == authn.GrantPassword {
|
||||
actor = "unknown user"
|
||||
}
|
||||
|
||||
// Create a new session (access token) based on the grant type specified in the request.
|
||||
switch f.GrantType {
|
||||
switch frm.GrantType {
|
||||
case authn.GrantClientCredentials, authn.GrantUndefined:
|
||||
// Find client with the specified ID.
|
||||
client = entity.FindClientByUID(f.ClientID)
|
||||
client = entity.FindClientByUID(frm.ClientID)
|
||||
|
||||
// Check if a client has been found, it is enabled, and the credentials are valid.
|
||||
if client == nil {
|
||||
@@ -106,7 +106,7 @@ func OAuthToken(router *gin.RouterGroup) {
|
||||
event.AuditWarn([]string{clientIp, "oauth2", actor, action, "method %s not supported"}, clean.LogQuote(method.String()))
|
||||
AbortInvalidCredentials(c)
|
||||
return
|
||||
} else if client.InvalidSecret(f.ClientSecret) {
|
||||
} else if client.InvalidSecret(frm.ClientSecret) {
|
||||
event.AuditWarn([]string{clientIp, "oauth2", actor, action, authn.ErrInvalidClientSecret.Error()})
|
||||
AbortInvalidCredentials(c)
|
||||
return
|
||||
@@ -138,7 +138,7 @@ func OAuthToken(router *gin.RouterGroup) {
|
||||
if s.User().Provider().SupportsPasswordAuthentication() {
|
||||
loginForm := form.Login{
|
||||
Username: s.Username(),
|
||||
Password: f.Password,
|
||||
Password: frm.Password,
|
||||
}
|
||||
|
||||
authUser, authProvider, authMethod, authErr := entity.Auth(loginForm, nil, c)
|
||||
@@ -159,12 +159,12 @@ func OAuthToken(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
f.GrantType = authn.GrantPassword
|
||||
frm.GrantType = authn.GrantPassword
|
||||
} else {
|
||||
f.GrantType = authn.GrantSession
|
||||
frm.GrantType = authn.GrantSession
|
||||
}
|
||||
|
||||
sess = entity.NewClientSession(f.ClientName, f.ExpiresIn, f.Scope, f.GrantType, s.User())
|
||||
sess = entity.NewClientSession(frm.ClientName, frm.ExpiresIn, frm.Scope, frm.GrantType, s.User())
|
||||
|
||||
// Return the reserved request rate limit tokens after successful authentication.
|
||||
r.Success()
|
||||
|
||||
@@ -42,15 +42,15 @@ func AddPhotoLabel(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Label
|
||||
frm := &form.Label{}
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err = c.BindJSON(&f); err != nil {
|
||||
if err = c.BindJSON(frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
labelEntity := entity.FirstOrCreateLabel(entity.NewLabel(f.LabelName, f.LabelPriority))
|
||||
labelEntity := entity.FirstOrCreateLabel(entity.NewLabel(frm.LabelName, frm.LabelPriority))
|
||||
|
||||
if labelEntity == nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to create label"})
|
||||
@@ -62,19 +62,19 @@ func AddPhotoLabel(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
photoLabel := entity.FirstOrCreatePhotoLabel(entity.NewPhotoLabel(m.ID, labelEntity.ID, f.Uncertainty, "manual"))
|
||||
photoLabel := entity.FirstOrCreatePhotoLabel(entity.NewPhotoLabel(m.ID, labelEntity.ID, frm.Uncertainty, "manual"))
|
||||
|
||||
if photoLabel == nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to update photo label"})
|
||||
return
|
||||
}
|
||||
|
||||
if photoLabel.Uncertainty > f.Uncertainty {
|
||||
if err := photoLabel.Updates(map[string]interface{}{
|
||||
"Uncertainty": f.Uncertainty,
|
||||
if photoLabel.Uncertainty > frm.Uncertainty {
|
||||
if updateErr := photoLabel.Updates(map[string]interface{}{
|
||||
"Uncertainty": frm.Uncertainty,
|
||||
"LabelSrc": entity.SrcManual,
|
||||
}); err != nil {
|
||||
log.Errorf("label: %s", err)
|
||||
}); updateErr != nil {
|
||||
log.Errorf("label: %s", updateErr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Code clean-up, simplify
|
||||
// TODO: Clean up and simplify this.
|
||||
|
||||
m, err := query.PhotoByUID(clean.UID(c.Param("uid")))
|
||||
|
||||
@@ -213,12 +213,12 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.BindJSON(&label); err != nil {
|
||||
if err = c.BindJSON(label); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := label.Save(); err != nil {
|
||||
if err = label.Save(); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
@@ -230,7 +230,7 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := p.SaveLabels(); err != nil {
|
||||
if err = p.SaveLabels(); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestAddPhotoLabel(t *testing.T) {
|
||||
val := gjson.Get(r.Body.String(), "Labels.#(LabelID==1000001).Uncertainty")
|
||||
assert.Equal(t, "10", val.String())
|
||||
})
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
AddPhotoLabel(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/xxx/label", `{"Name": "Flower", "Uncertainty": 10, "Priority": 2}`)
|
||||
@@ -34,7 +34,7 @@ func TestAddPhotoLabel(t *testing.T) {
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrEntityNotFound), val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
AddPhotoLabel(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/ps6sg6be2lvl0yh8/label", `{"Name": 123, "Uncertainty": 10, "Priority": 2}`)
|
||||
@@ -93,7 +93,7 @@ func TestRemovePhotoLabel(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdatePhotoLabel(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdatePhotoLabel(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/ps6sg6be2lvl0yh0/label/1000006", `{"Label": {"Name": "NewLabelName"}}`)
|
||||
|
||||
@@ -92,7 +92,7 @@ func UpdatePhoto(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// 1) Init form with model values
|
||||
f, err := form.NewPhoto(m)
|
||||
frm, err := form.NewPhoto(m)
|
||||
|
||||
if err != nil {
|
||||
Abort(c, http.StatusInternalServerError, i18n.ErrSaveFailed)
|
||||
@@ -100,16 +100,16 @@ func UpdatePhoto(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// 2) Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 3) Save model with values from form
|
||||
if err := entity.SavePhotoForm(m, f); err != nil {
|
||||
if err := entity.SavePhotoForm(m, frm); err != nil {
|
||||
Abort(c, http.StatusInternalServerError, i18n.ErrSaveFailed)
|
||||
return
|
||||
} else if f.PhotoPrivate {
|
||||
} else if frm.PhotoPrivate {
|
||||
FlushCoverCache()
|
||||
}
|
||||
|
||||
|
||||
@@ -38,36 +38,36 @@ import (
|
||||
// @Router /api/v1/photos [get]
|
||||
func SearchPhotos(router *gin.RouterGroup) {
|
||||
// searchPhotos checking authorization and parses the search request.
|
||||
searchForm := func(c *gin.Context) (f form.SearchPhotos, s *entity.Session, err error) {
|
||||
searchForm := func(c *gin.Context) (frm form.SearchPhotos, s *entity.Session, err error) {
|
||||
s = AuthAny(c, acl.ResourcePhotos, acl.Permissions{acl.ActionSearch, acl.ActionView, acl.AccessShared})
|
||||
|
||||
// Abort if permission was not granted.
|
||||
if s.Abort(c) {
|
||||
return f, s, i18n.Error(i18n.ErrForbidden)
|
||||
return frm, s, i18n.Error(i18n.ErrForbidden)
|
||||
}
|
||||
|
||||
// Abort if request params are invalid.
|
||||
if err = c.MustBindWith(&f, binding.Form); err != nil {
|
||||
if err = c.MustBindWith(&frm, binding.Form); err != nil {
|
||||
event.AuditWarn([]string{ClientIP(c), "session %s", string(acl.ResourcePhotos), "form invalid", "%s"}, s.RefID, err)
|
||||
AbortBadRequest(c)
|
||||
return f, s, err
|
||||
return frm, s, err
|
||||
}
|
||||
|
||||
settings := get.Config().Settings()
|
||||
|
||||
// Ignore private flag if feature is disabled.
|
||||
if !settings.Features.Private {
|
||||
f.Public = false
|
||||
frm.Public = false
|
||||
}
|
||||
|
||||
// Ignore private flag if feature is disabled.
|
||||
if f.Scope == "" &&
|
||||
if frm.Scope == "" &&
|
||||
settings.Features.Review &&
|
||||
acl.Rules.Deny(acl.ResourcePhotos, s.UserRole(), acl.ActionManage) {
|
||||
f.Quality = 3
|
||||
frm.Quality = 3
|
||||
}
|
||||
|
||||
return f, s, nil
|
||||
return frm, s, nil
|
||||
}
|
||||
|
||||
// defaultHandler a standard JSON result with all fields.
|
||||
|
||||
@@ -44,10 +44,10 @@ func SearchGeo(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
var err error
|
||||
var f form.SearchPhotosGeo
|
||||
var frm form.SearchPhotosGeo
|
||||
|
||||
// Abort if request params are invalid.
|
||||
if err = c.MustBindWith(&f, binding.Form); err != nil {
|
||||
if err = c.MustBindWith(&frm, binding.Form); err != nil {
|
||||
event.AuditWarn([]string{ClientIP(c), "session %s", string(acl.ResourcePlaces), "form invalid", "%s"}, s.RefID, err)
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
@@ -58,18 +58,18 @@ func SearchGeo(router *gin.RouterGroup) {
|
||||
|
||||
// Ignore private flag if feature is disabled.
|
||||
if !settings.Features.Private {
|
||||
f.Public = false
|
||||
frm.Public = false
|
||||
}
|
||||
|
||||
// Ignore private flag if feature is disabled.
|
||||
if f.Scope == "" &&
|
||||
if frm.Scope == "" &&
|
||||
settings.Features.Review &&
|
||||
acl.Rules.Deny(acl.ResourcePhotos, s.UserRole(), acl.ActionManage) {
|
||||
f.Quality = 3
|
||||
frm.Quality = 3
|
||||
}
|
||||
|
||||
// Find matching pictures.
|
||||
photos, err := search.UserPhotosGeo(f, s)
|
||||
photos, err := search.UserPhotosGeo(frm, s)
|
||||
|
||||
// Ok?
|
||||
if err != nil {
|
||||
@@ -80,8 +80,8 @@ func SearchGeo(router *gin.RouterGroup) {
|
||||
|
||||
// Add response headers.
|
||||
AddCountHeader(c, len(photos))
|
||||
AddLimitHeader(c, f.Count)
|
||||
AddOffsetHeader(c, f.Offset)
|
||||
AddLimitHeader(c, frm.Count)
|
||||
AddOffsetHeader(c, frm.Offset)
|
||||
AddTokenHeaders(c, s)
|
||||
|
||||
var resp []byte
|
||||
|
||||
@@ -124,21 +124,21 @@ func AddService(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Service
|
||||
var frm form.Service
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.Discovery(); err != nil {
|
||||
if err := frm.Discovery(); err != nil {
|
||||
log.Error(err)
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrConnectionFailed)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := entity.AddService(f)
|
||||
m, err := entity.AddService(frm)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -178,7 +178,7 @@ func UpdateService(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// 1) Init form with model values
|
||||
f, err := form.NewService(m)
|
||||
frm, err := form.NewService(m)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -187,14 +187,14 @@ func UpdateService(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// 2) Update form with values from request
|
||||
if err = c.BindJSON(&f); err != nil {
|
||||
if err = c.BindJSON(&frm); err != nil {
|
||||
log.Error(err)
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
// 3) Save model with values from form
|
||||
if err = m.SaveForm(f); err != nil {
|
||||
if err = m.SaveForm(frm); err != nil {
|
||||
log.Error(err)
|
||||
AbortSaveFailed(c)
|
||||
return
|
||||
|
||||
@@ -31,16 +31,16 @@ func SearchServices(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.SearchServices
|
||||
var frm form.SearchServices
|
||||
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
err := c.MustBindWith(&frm, binding.Form)
|
||||
|
||||
if err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := search.Accounts(f)
|
||||
result, err := search.Accounts(frm)
|
||||
|
||||
if err != nil {
|
||||
AbortBadRequest(c)
|
||||
@@ -48,8 +48,8 @@ func SearchServices(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// TODO c.Header("X-Count", strconv.Itoa(count))
|
||||
AddLimitHeader(c, f.Count)
|
||||
AddOffsetHeader(c, f.Offset)
|
||||
AddLimitHeader(c, frm.Count)
|
||||
AddOffsetHeader(c, frm.Offset)
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestSearchServices(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
SearchServices(router)
|
||||
sess := AuthenticateAdmin(app, router)
|
||||
@@ -21,7 +21,7 @@ func TestSearchServices(t *testing.T) {
|
||||
assert.Equal(t, "http://dummy-webdav/", val.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
SearchServices(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/services?xxx=10")
|
||||
|
||||
@@ -37,19 +37,19 @@ func UploadToService(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.SyncUpload
|
||||
var frm form.SyncUpload
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err = c.BindJSON(&f); err != nil {
|
||||
if err = c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
folder := f.Folder
|
||||
folder := frm.Folder
|
||||
|
||||
// Find files to share.
|
||||
selection := query.ShareSelection(m.ShareOriginals())
|
||||
files, err := query.SelectedFiles(f.Selection, selection)
|
||||
files, err := query.SelectedFiles(frm.Selection, selection)
|
||||
|
||||
if err != nil {
|
||||
AbortEntityNotFound(c)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestUploadToService(t *testing.T) {
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UploadToService(router)
|
||||
r := PerformRequest(app, "POST", "/api/v1/services/1000000/upload")
|
||||
|
||||
@@ -29,12 +29,12 @@ func CreateSession(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Login
|
||||
var frm form.Login
|
||||
|
||||
clientIp := ClientIP(c)
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
event.AuditWarn([]string{clientIp, "create session", "invalid request", "%s"}, err)
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
@@ -59,7 +59,7 @@ func CreateSession(router *gin.RouterGroup) {
|
||||
|
||||
// Check request rate limit.
|
||||
var r *limiter.Request
|
||||
if f.HasPasscode() {
|
||||
if frm.HasPasscode() {
|
||||
r = limiter.Login.RequestN(clientIp, 3)
|
||||
} else {
|
||||
r = limiter.Login.Request(clientIp)
|
||||
@@ -86,7 +86,7 @@ func CreateSession(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Check authentication credentials.
|
||||
if err = sess.LogIn(f, c); err != nil {
|
||||
if err = sess.LogIn(frm, c); err != nil {
|
||||
if sess.Method().IsNot(authn.Method2FA) {
|
||||
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||
} else if errors.Is(err, authn.ErrPasscodeRequired) {
|
||||
|
||||
@@ -73,28 +73,28 @@ func SharePreview(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.SearchPhotos
|
||||
var frm form.SearchPhotos
|
||||
|
||||
// Covers may only contain public content in shared albums.
|
||||
f.Album = shared
|
||||
f.Public = true
|
||||
f.Private = false
|
||||
f.Hidden = false
|
||||
f.Archived = false
|
||||
f.Review = false
|
||||
f.Primary = true
|
||||
frm.Album = shared
|
||||
frm.Public = true
|
||||
frm.Private = false
|
||||
frm.Hidden = false
|
||||
frm.Archived = false
|
||||
frm.Review = false
|
||||
frm.Primary = true
|
||||
|
||||
// Get first 12 album entries.
|
||||
f.Count = 6
|
||||
f.Order = a.AlbumOrder
|
||||
frm.Count = 6
|
||||
frm.Order = a.AlbumOrder
|
||||
|
||||
if parseErr := f.ParseQueryString(); parseErr != nil {
|
||||
if parseErr := frm.ParseQueryString(); parseErr != nil {
|
||||
log.Errorf("preview: %s", parseErr)
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
|
||||
return
|
||||
}
|
||||
|
||||
p, count, err := search.Photos(f)
|
||||
p, count, err := search.Photos(frm)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestGetStatus(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetStatus(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/status")
|
||||
|
||||
@@ -77,21 +77,21 @@ func UpdateSubject(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Create request value form.
|
||||
f, err := form.NewSubject(*m)
|
||||
frm, err := form.NewSubject(*m)
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err != nil {
|
||||
log.Errorf("subject: %s (new form)", err)
|
||||
AbortSaveFailed(c)
|
||||
return
|
||||
} else if err = c.BindJSON(&f); err != nil {
|
||||
} else if err = c.BindJSON(frm); err != nil {
|
||||
log.Errorf("subject: %s (update form)", err)
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
// Update subject from form values.
|
||||
if changed, err := m.SaveForm(f); err != nil {
|
||||
if changed, err := m.SaveForm(frm); err != nil {
|
||||
log.Errorf("subject: %s", err)
|
||||
AbortSaveFailed(c)
|
||||
return
|
||||
|
||||
@@ -35,16 +35,16 @@ func SearchSubjects(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.SearchSubjects
|
||||
var frm form.SearchSubjects
|
||||
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
err := c.MustBindWith(&frm, binding.Form)
|
||||
|
||||
if err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := search.Subjects(f)
|
||||
result, err := search.Subjects(frm)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(400, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
@@ -52,8 +52,8 @@ func SearchSubjects(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
AddCountHeader(c, len(result))
|
||||
AddLimitHeader(c, f.Count)
|
||||
AddOffsetHeader(c, f.Offset)
|
||||
AddLimitHeader(c, frm.Count)
|
||||
AddOffsetHeader(c, frm.Offset)
|
||||
AddTokenHeaders(c, s)
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
|
||||
@@ -106,14 +106,14 @@ func TestUpdateSubject(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
t.Run("InvalidRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdateSubject(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/subjects/js6sg6b1qekk9jx8", `{"Name": 123}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdateSubject(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/subjectss/xxx", `{"Name": "Updated Name"}`)
|
||||
|
||||
@@ -42,8 +42,8 @@ func FindUserSessions(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Init search request form.
|
||||
var f form.SearchSessions
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
var frm form.SearchSessions
|
||||
err := c.MustBindWith(&frm, binding.Form)
|
||||
|
||||
// Abort if invalid.
|
||||
if err != nil {
|
||||
@@ -52,21 +52,21 @@ func FindUserSessions(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Find applications that belong to the current user and sort them by name.
|
||||
f.UID = s.UserUID
|
||||
f.Order = sortby.ClientName
|
||||
f.Provider = authn.ProviderApplication.String()
|
||||
f.Method = authn.MethodDefault.String()
|
||||
frm.UID = s.UserUID
|
||||
frm.Order = sortby.ClientName
|
||||
frm.Provider = authn.ProviderApplication.String()
|
||||
frm.Method = authn.MethodDefault.String()
|
||||
|
||||
// Perform search.
|
||||
result, err := search.Sessions(f)
|
||||
result, err := search.Sessions(frm)
|
||||
|
||||
if err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
AddLimitHeader(c, f.Count)
|
||||
AddOffsetHeader(c, f.Offset)
|
||||
AddLimitHeader(c, frm.Count)
|
||||
AddOffsetHeader(c, frm.Offset)
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
})
|
||||
|
||||
@@ -168,10 +168,10 @@ func ProcessUserUpload(router *gin.RouterGroup) {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
var f form.UploadOptions
|
||||
var frm form.UploadOptions
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
@@ -198,10 +198,10 @@ func ProcessUserUpload(router *gin.RouterGroup) {
|
||||
opt := photoprism.ImportOptionsUpload(uploadPath, destFolder)
|
||||
|
||||
// Add imported files to albums if allowed.
|
||||
if len(f.Albums) > 0 &&
|
||||
if len(frm.Albums) > 0 &&
|
||||
acl.Rules.AllowAny(acl.ResourceAlbums, s.UserRole(), acl.Permissions{acl.ActionCreate, acl.ActionUpload}) {
|
||||
log.Debugf("upload: adding files to album %s", clean.Log(strings.Join(f.Albums, " and ")))
|
||||
opt.Albums = f.Albums
|
||||
log.Debugf("upload: adding files to album %s", clean.Log(strings.Join(frm.Albums, " and ")))
|
||||
opt.Albums = frm.Albums
|
||||
}
|
||||
|
||||
// Set user UID if known.
|
||||
@@ -243,7 +243,7 @@ func ProcessUserUpload(router *gin.RouterGroup) {
|
||||
event.Publish("index.completed", event.Data{"uid": opt.UID, "path": uploadPath, "seconds": elapsed})
|
||||
event.Publish("upload.completed", event.Data{"uid": opt.UID, "path": uploadPath, "seconds": elapsed})
|
||||
|
||||
for _, uid := range f.Albums {
|
||||
for _, uid := range frm.Albums {
|
||||
PublishAlbumEvent(StatusUpdated, uid, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,16 +42,16 @@ func ZipCreate(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Selection
|
||||
var frm form.Selection
|
||||
start := time.Now()
|
||||
|
||||
// Assign and validate request form values.
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
if err := c.BindJSON(&frm); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if f.Empty() {
|
||||
if frm.Empty() {
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrNoItemsSelected)
|
||||
return
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func ZipCreate(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Find files to download.
|
||||
files, err := query.SelectedFiles(f, selection)
|
||||
files, err := query.SelectedFiles(frm, selection)
|
||||
|
||||
if err != nil {
|
||||
Error(c, http.StatusBadRequest, err, i18n.ErrZipFailed)
|
||||
|
||||
@@ -603,7 +603,11 @@ func (m *Album) UpdateTitleAndState(title, slug, stateName, countryCode string)
|
||||
}
|
||||
|
||||
// SaveForm updates the entity using form data and stores it in the database.
|
||||
func (m *Album) SaveForm(f form.Album) error {
|
||||
func (m *Album) SaveForm(f *form.Album) error {
|
||||
if f == nil {
|
||||
return fmt.Errorf("form is nil")
|
||||
}
|
||||
|
||||
if err := deepcopier.Copy(m).From(f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -736,18 +736,19 @@ func TestAlbum_SaveForm(t *testing.T) {
|
||||
|
||||
album2 := Album{ID: 123, AlbumTitle: "New name", AlbumDescription: "new description", AlbumCategory: "family"}
|
||||
|
||||
albumForm, err := form.NewAlbum(album2)
|
||||
frm, err := form.NewAlbum(album2)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = album.SaveForm(albumForm)
|
||||
err = album.SaveForm(frm)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.IsType(t, &form.Album{}, frm)
|
||||
assert.Equal(t, "New name", album.AlbumTitle)
|
||||
assert.Equal(t, "new description", album.AlbumDescription)
|
||||
assert.Equal(t, "Family", album.AlbumCategory)
|
||||
|
||||
@@ -19,15 +19,15 @@ import (
|
||||
)
|
||||
|
||||
// Auth checks if the credentials are valid and returns the user and authentication provider.
|
||||
var Auth = func(f form.Login, s *Session, c *gin.Context) (user *User, provider authn.ProviderType, method authn.MethodType, err error) {
|
||||
var Auth = func(frm form.Login, s *Session, c *gin.Context) (user *User, provider authn.ProviderType, method authn.MethodType, err error) {
|
||||
// Get sanitized username from login form.
|
||||
nameName := f.CleanUsername()
|
||||
nameName := frm.CleanUsername()
|
||||
|
||||
// Find registered user account.
|
||||
user = FindUserByName(nameName)
|
||||
|
||||
// Try local authentication.
|
||||
provider, method, err = AuthLocal(user, f, s, c)
|
||||
provider, method, err = AuthLocal(user, frm, s, c)
|
||||
|
||||
if err != nil {
|
||||
return user, provider, method, err
|
||||
@@ -40,17 +40,17 @@ var Auth = func(f form.Login, s *Session, c *gin.Context) (user *User, provider
|
||||
}
|
||||
|
||||
// AuthSession returns the client session that belongs to the auth token provided, or returns nil if it was not found.
|
||||
func AuthSession(f form.Login, c *gin.Context) (sess *Session, user *User, err error) {
|
||||
if f.Password == "" {
|
||||
func AuthSession(frm form.Login, c *gin.Context) (sess *Session, user *User, err error) {
|
||||
if frm.Password == "" {
|
||||
// Abort authentication if no token was provided.
|
||||
return nil, nil, authn.ErrPasscodeRequired
|
||||
} else if !rnd.IsAppPassword(f.Password, true) {
|
||||
} else if !rnd.IsAppPassword(frm.Password, true) {
|
||||
// Abort authentication if token doesn't match expected format.
|
||||
return nil, nil, authn.ErrInvalidPassword
|
||||
}
|
||||
|
||||
// Get session ID for the auth token provided.
|
||||
sid := rnd.SessionID(f.Password)
|
||||
sid := rnd.SessionID(frm.Password)
|
||||
|
||||
// Find the session based on the hashed token used as session ID and return it.
|
||||
sess, err = FindSession(sid)
|
||||
@@ -69,7 +69,7 @@ func AuthSession(f form.Login, c *gin.Context) (sess *Session, user *User, err e
|
||||
}
|
||||
|
||||
// AuthLocal authenticates against the local user database with the specified username and password.
|
||||
func AuthLocal(user *User, f form.Login, s *Session, c *gin.Context) (provider authn.ProviderType, method authn.MethodType, err error) {
|
||||
func AuthLocal(user *User, frm form.Login, s *Session, c *gin.Context) (provider authn.ProviderType, method authn.MethodType, err error) {
|
||||
// Set defaults.
|
||||
provider = authn.ProviderNone
|
||||
method = authn.MethodUndefined
|
||||
@@ -78,7 +78,7 @@ func AuthLocal(user *User, f form.Login, s *Session, c *gin.Context) (provider a
|
||||
clientIp := header.ClientIP(c)
|
||||
|
||||
// Get sanitized username from login form.
|
||||
username := f.CleanUsername()
|
||||
username := frm.CleanUsername()
|
||||
|
||||
// Check if user account exists.
|
||||
if user == nil {
|
||||
@@ -107,7 +107,7 @@ func AuthLocal(user *User, f form.Login, s *Session, c *gin.Context) (provider a
|
||||
}
|
||||
|
||||
// Authentication with personal access token if a valid secret has been provided as password.
|
||||
if authSess, authUser, authErr := AuthSession(f, c); authSess != nil && authUser != nil && authErr == nil {
|
||||
if authSess, authUser, authErr := AuthSession(frm, c); authSess != nil && authUser != nil && authErr == nil {
|
||||
if !authUser.IsRegistered() || authUser.UserUID != user.UserUID {
|
||||
message := authn.ErrInvalidUser.Error()
|
||||
|
||||
@@ -177,7 +177,7 @@ func AuthLocal(user *User, f form.Login, s *Session, c *gin.Context) (provider a
|
||||
}
|
||||
|
||||
// Check password.
|
||||
if user.InvalidPassword(f.Password) {
|
||||
if user.InvalidPassword(frm.Password) {
|
||||
message := authn.ErrInvalidPassword.Error()
|
||||
|
||||
if s != nil {
|
||||
@@ -193,7 +193,7 @@ func AuthLocal(user *User, f form.Login, s *Session, c *gin.Context) (provider a
|
||||
|
||||
// Check two-factor authentication, if enabled.
|
||||
if method = user.Method(); method.Is(authn.Method2FA) {
|
||||
if code := f.Passcode(); code == "" {
|
||||
if code := frm.Passcode(); code == "" {
|
||||
err = authn.ErrPasscodeRequired
|
||||
|
||||
if s != nil {
|
||||
@@ -234,7 +234,7 @@ func AuthLocal(user *User, f form.Login, s *Session, c *gin.Context) (provider a
|
||||
}
|
||||
|
||||
// LogIn performs authentication checks against the specified login form.
|
||||
func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
|
||||
func (m *Session) LogIn(frm form.Login, c *gin.Context) (err error) {
|
||||
if c != nil {
|
||||
m.SetContext(c)
|
||||
}
|
||||
@@ -247,12 +247,12 @@ func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
|
||||
var method authn.MethodType
|
||||
|
||||
// Log in with username and password?
|
||||
if f.HasCredentials() {
|
||||
if frm.HasCredentials() {
|
||||
if m.IsRegistered() {
|
||||
m.Regenerate()
|
||||
}
|
||||
|
||||
user, provider, method, err = Auth(f, m, c)
|
||||
user, provider, method, err = Auth(frm, m, c)
|
||||
|
||||
m.SetProvider(provider)
|
||||
m.SetMethod(method)
|
||||
@@ -266,23 +266,23 @@ func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
|
||||
}
|
||||
|
||||
// Try to redeem link share token, if provided.
|
||||
if f.HasShareToken() {
|
||||
if frm.HasShareToken() {
|
||||
user = m.User()
|
||||
|
||||
// Redeem token.
|
||||
if user.IsRegistered() {
|
||||
if shares := user.RedeemToken(f.Token); shares == 0 {
|
||||
if shares := user.RedeemToken(frm.Token); shares == 0 {
|
||||
message := authn.ErrInvalidShareToken.Error()
|
||||
event.AuditWarn([]string{m.IP(), "session %s", message}, m.RefID)
|
||||
m.Status = http.StatusNotFound
|
||||
return i18n.Error(i18n.ErrInvalidLink)
|
||||
} else {
|
||||
event.AuditInfo([]string{m.IP(), "session %s", "token redeemed for %d shares"}, m.RefID, user.RedeemToken(f.Token))
|
||||
event.AuditInfo([]string{m.IP(), "session %s", "token redeemed for %d shares"}, m.RefID, user.RedeemToken(frm.Token))
|
||||
}
|
||||
} else if data := m.Data(); data == nil {
|
||||
m.Status = http.StatusInternalServerError
|
||||
return i18n.Error(i18n.ErrUnexpected)
|
||||
} else if shares := data.RedeemToken(f.Token); shares == 0 {
|
||||
} else if shares := data.RedeemToken(frm.Token); shares == 0 {
|
||||
message := authn.ErrInvalidShareToken.Error()
|
||||
event.AuditWarn([]string{m.IP(), "session %s", message}, m.RefID)
|
||||
event.LoginError(m.IP(), "api", "", m.UserAgent, message)
|
||||
|
||||
@@ -460,7 +460,7 @@ func TestSession_RefreshUser(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "bob", m.Username())
|
||||
})
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
m := &Session{}
|
||||
assert.Equal(t, "", m.RefreshUser().UserUID)
|
||||
})
|
||||
|
||||
@@ -1274,36 +1274,36 @@ func (m *User) Form() (form.User, error) {
|
||||
}
|
||||
|
||||
// PrivilegeLevelChange checks if saving the form changes the user privileges.
|
||||
func (m *User) PrivilegeLevelChange(f form.User) bool {
|
||||
return m.UserRole != f.Role() ||
|
||||
m.SuperAdmin != f.SuperAdmin ||
|
||||
m.CanLogin != f.CanLogin ||
|
||||
m.WebDAV != f.WebDAV ||
|
||||
m.UserAttr != f.Attr() ||
|
||||
m.AuthProvider != f.AuthProvider ||
|
||||
m.AuthMethod != f.AuthMethod ||
|
||||
m.AuthIssuer != f.AuthIssuer ||
|
||||
m.AuthID != f.AuthID ||
|
||||
m.BasePath != f.BasePath ||
|
||||
m.UploadPath != f.UploadPath
|
||||
func (m *User) PrivilegeLevelChange(frm form.User) bool {
|
||||
return m.UserRole != frm.Role() ||
|
||||
m.SuperAdmin != frm.SuperAdmin ||
|
||||
m.CanLogin != frm.CanLogin ||
|
||||
m.WebDAV != frm.WebDAV ||
|
||||
m.UserAttr != frm.Attr() ||
|
||||
m.AuthProvider != frm.AuthProvider ||
|
||||
m.AuthMethod != frm.AuthMethod ||
|
||||
m.AuthIssuer != frm.AuthIssuer ||
|
||||
m.AuthID != frm.AuthID ||
|
||||
m.BasePath != frm.BasePath ||
|
||||
m.UploadPath != frm.UploadPath
|
||||
}
|
||||
|
||||
// SaveForm updates the entity using form data and stores it in the database.
|
||||
func (m *User) SaveForm(f form.User, u *User) error {
|
||||
func (m *User) SaveForm(frm form.User, u *User) error {
|
||||
if m.UserName == "" || m.ID <= 0 {
|
||||
return fmt.Errorf("system users cannot be modified")
|
||||
} else if (m.ID == 1 || f.SuperAdmin) && acl.RoleAdmin.NotEqual(f.Role()) {
|
||||
} else if (m.ID == 1 || frm.SuperAdmin) && acl.RoleAdmin.NotEqual(frm.Role()) {
|
||||
return fmt.Errorf("super admin must not have a non-admin role")
|
||||
} else if f.BasePath != "" && clean.UserPath(f.BasePath) == "" {
|
||||
} else if frm.BasePath != "" && clean.UserPath(frm.BasePath) == "" {
|
||||
return fmt.Errorf("invalid base folder")
|
||||
} else if f.UploadPath != "" && clean.UserPath(f.UploadPath) == "" {
|
||||
} else if frm.UploadPath != "" && clean.UserPath(frm.UploadPath) == "" {
|
||||
return fmt.Errorf("invalid upload folder")
|
||||
}
|
||||
|
||||
// Ignore details if not set.
|
||||
if f.UserDetails == nil {
|
||||
if frm.UserDetails == nil {
|
||||
// Ignore.
|
||||
} else if err := deepcopier.Copy(f.UserDetails).To(m.UserDetails); err != nil {
|
||||
} else if err := deepcopier.Copy(frm.UserDetails).To(m.UserDetails); err != nil {
|
||||
return err
|
||||
} else {
|
||||
m.UserDetails.UserAbout = txt.Clip(m.UserDetails.UserAbout, txt.ClipComment)
|
||||
@@ -1311,7 +1311,7 @@ func (m *User) SaveForm(f form.User, u *User) error {
|
||||
}
|
||||
|
||||
// Sanitize display name.
|
||||
if n := clean.Name(f.DisplayName); n != "" && n != m.DisplayName {
|
||||
if n := clean.Name(frm.DisplayName); n != "" && n != m.DisplayName {
|
||||
m.SetDisplayName(n, SrcManual)
|
||||
}
|
||||
|
||||
@@ -1321,7 +1321,7 @@ func (m *User) SaveForm(f form.User, u *User) error {
|
||||
}
|
||||
|
||||
// Sanitize email address.
|
||||
if email := f.Email(); email != "" && email != m.UserEmail {
|
||||
if email := frm.Email(); email != "" && email != m.UserEmail {
|
||||
m.UserEmail = email
|
||||
m.VerifiedAt = nil
|
||||
m.VerifyToken = GenerateToken()
|
||||
@@ -1336,29 +1336,29 @@ func (m *User) SaveForm(f form.User, u *User) error {
|
||||
m.SetRole(acl.RoleAdmin.String())
|
||||
m.CanLogin = true
|
||||
} else {
|
||||
m.SetRole(f.Role())
|
||||
m.CanLogin = f.CanLogin
|
||||
m.SetRole(frm.Role())
|
||||
m.CanLogin = frm.CanLogin
|
||||
}
|
||||
|
||||
m.WebDAV = f.WebDAV
|
||||
m.UserAttr = f.Attr()
|
||||
m.WebDAV = frm.WebDAV
|
||||
m.UserAttr = frm.Attr()
|
||||
|
||||
// Only allow super admins to change the authentication method and make other users super admins.
|
||||
if u.IsSuperAdmin() {
|
||||
if !u.Equal(m) {
|
||||
m.SuperAdmin = f.SuperAdmin
|
||||
m.SuperAdmin = frm.SuperAdmin
|
||||
}
|
||||
|
||||
if !u.Equal(m) || f.Provider() != authn.ProviderNone {
|
||||
m.SetProvider(f.Provider())
|
||||
if !u.Equal(m) || frm.Provider() != authn.ProviderNone {
|
||||
m.SetProvider(frm.Provider())
|
||||
}
|
||||
|
||||
m.SetMethod(f.Method())
|
||||
m.SetAuthID(f.AuthID, f.AuthIssuer)
|
||||
m.SetMethod(frm.Method())
|
||||
m.SetAuthID(frm.AuthID, frm.AuthIssuer)
|
||||
}
|
||||
|
||||
m.SetBasePath(f.BasePath)
|
||||
m.SetUploadPath(f.UploadPath)
|
||||
m.SetBasePath(frm.BasePath)
|
||||
m.SetUploadPath(frm.UploadPath)
|
||||
}
|
||||
|
||||
// Ensure super admins never have a non-admin role.
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestFirstOrCreateDetails(t *testing.T) {
|
||||
t.Fatal("details should not be nil")
|
||||
}
|
||||
})
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
details := &Details{PhotoID: 0}
|
||||
assert.Nil(t, FirstOrCreateDetails(details))
|
||||
})
|
||||
@@ -145,7 +145,7 @@ func TestNewDetails(t *testing.T) {
|
||||
|
||||
// TODO fails on mariadb
|
||||
func TestDetails_Create(t *testing.T) {
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
details := Details{PhotoID: 0}
|
||||
|
||||
assert.Error(t, details.Create())
|
||||
@@ -177,7 +177,7 @@ func TestDetails_Save(t *testing.T) {
|
||||
assert.True(t, afterDate.After(initialDate))
|
||||
})
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
details := Details{PhotoID: 0}
|
||||
|
||||
assert.Error(t, details.Save())
|
||||
|
||||
@@ -235,8 +235,8 @@ func (m *Folder) Updates(values interface{}) error {
|
||||
}
|
||||
|
||||
// SetForm updates the entity properties based on form values.
|
||||
func (m *Folder) SetForm(f form.Folder) error {
|
||||
if err := deepcopier.Copy(m).From(f); err != nil {
|
||||
func (m *Folder) SetForm(frm form.Folder) error {
|
||||
if err := deepcopier.Copy(m).From(frm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/ulule/deepcopier"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/ai/classify"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
@@ -102,6 +104,26 @@ func (m *Label) Save() error {
|
||||
return Db().Save(m).Error
|
||||
}
|
||||
|
||||
// SaveForm updates the entity using form data and stores it in the database.
|
||||
func (m *Label) SaveForm(f *form.Label) error {
|
||||
if f == nil {
|
||||
return fmt.Errorf("form is nil")
|
||||
} else if f.LabelName == "" {
|
||||
return fmt.Errorf("missing name")
|
||||
}
|
||||
|
||||
labelMutex.Lock()
|
||||
defer labelMutex.Unlock()
|
||||
|
||||
if err := deepcopier.Copy(m).From(f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.SetName(f.LabelName)
|
||||
|
||||
return Db().Save(m).Error
|
||||
}
|
||||
|
||||
// Create inserts the label to the database.
|
||||
func (m *Label) Create() error {
|
||||
labelMutex.Lock()
|
||||
@@ -157,7 +179,7 @@ func FirstOrCreateLabel(m *Label) *Label {
|
||||
}
|
||||
|
||||
return m
|
||||
} else if err := UnscopedDb().Where("label_slug = ? OR custom_slug = ?", m.LabelSlug, m.CustomSlug).First(&result).Error; err == nil {
|
||||
} else if err = UnscopedDb().Where("label_slug = ? OR custom_slug = ?", m.LabelSlug, m.CustomSlug).First(&result).Error; err == nil {
|
||||
return &result
|
||||
} else {
|
||||
log.Errorf("label: %s (find or create %s)", createErr, m.LabelSlug)
|
||||
@@ -167,33 +189,33 @@ func FirstOrCreateLabel(m *Label) *Label {
|
||||
}
|
||||
|
||||
// FindLabel find the matching label based on the string provided or an error if not found.
|
||||
func FindLabel(s string, cached bool) (m Label, err error) {
|
||||
func FindLabel(s string, cached bool) (*Label, error) {
|
||||
labelSlug := txt.Slug(s)
|
||||
|
||||
if labelSlug == "" {
|
||||
return m, fmt.Errorf("invalid label slug %s", clean.LogQuote(labelSlug))
|
||||
return &Label{}, fmt.Errorf("invalid label slug %s", clean.LogQuote(labelSlug))
|
||||
}
|
||||
|
||||
// Return cached label, if found.
|
||||
if cached {
|
||||
if cacheData, ok := labelCache.Get(labelSlug); ok {
|
||||
log.Tracef("label: cache hit for %s", labelSlug)
|
||||
return cacheData.(Label), nil
|
||||
return cacheData.(*Label), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch and cache label from database.
|
||||
m = Label{}
|
||||
result := &Label{}
|
||||
|
||||
if r := Db().First(&m, "label_slug = ? OR custom_slug = ?", labelSlug, labelSlug); r.RecordNotFound() {
|
||||
if find := Db().First(result, "label_slug = ? OR custom_slug = ?", labelSlug, labelSlug); find.RecordNotFound() {
|
||||
labelCache.Delete(labelSlug)
|
||||
return m, fmt.Errorf("label not found")
|
||||
} else if r.Error != nil {
|
||||
return result, fmt.Errorf("label not found")
|
||||
} else if find.Error != nil {
|
||||
labelCache.Delete(labelSlug)
|
||||
return m, r.Error
|
||||
return result, find.Error
|
||||
} else {
|
||||
labelCache.SetDefault(m.LabelSlug, m)
|
||||
return m, nil
|
||||
labelCache.SetDefault(result.LabelSlug, result)
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -177,18 +177,18 @@ func (m *Marker) SetName(name, src string) (changed bool, err error) {
|
||||
}
|
||||
|
||||
// SaveForm updates the entity using form data and stores it in the database.
|
||||
func (m *Marker) SaveForm(f form.Marker) (changed bool, err error) {
|
||||
if m.MarkerInvalid != f.MarkerInvalid {
|
||||
m.MarkerInvalid = f.MarkerInvalid
|
||||
func (m *Marker) SaveForm(frm form.Marker) (changed bool, err error) {
|
||||
if m.MarkerInvalid != frm.MarkerInvalid {
|
||||
m.MarkerInvalid = frm.MarkerInvalid
|
||||
changed = true
|
||||
}
|
||||
|
||||
if m.MarkerReview != f.MarkerReview {
|
||||
m.MarkerReview = f.MarkerReview
|
||||
if m.MarkerReview != frm.MarkerReview {
|
||||
m.MarkerReview = frm.MarkerReview
|
||||
changed = true
|
||||
}
|
||||
|
||||
if nameChanged, err := m.SetName(f.MarkerName, f.SubjSrc); err != nil {
|
||||
if nameChanged, err := m.SetName(frm.MarkerName, frm.SubjSrc); err != nil {
|
||||
return changed, err
|
||||
} else if nameChanged {
|
||||
changed = true
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestErrors(t *testing.T) {
|
||||
}
|
||||
assert.Empty(t, errors)
|
||||
})
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
errors, err := Errors(1000, 0, "errors")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -335,23 +335,23 @@ func RemovePeopleAndFaces() (err error) {
|
||||
}
|
||||
|
||||
// Reset people label.
|
||||
if label, err := LabelBySlug("people"); err != nil {
|
||||
return err
|
||||
} else if err = UnscopedDb().
|
||||
Delete(entity.PhotoLabel{}, "label_id = ?", label.ID).Error; err != nil {
|
||||
return err
|
||||
} else if err = label.Update("PhotoCount", 0); err != nil {
|
||||
return err
|
||||
if label, labelErr := LabelBySlug("people"); labelErr != nil {
|
||||
return labelErr
|
||||
} else if labelErr = UnscopedDb().
|
||||
Delete(entity.PhotoLabel{}, "label_id = ?", label.ID).Error; labelErr != nil {
|
||||
return labelErr
|
||||
} else if labelErr = label.Update("PhotoCount", 0); labelErr != nil {
|
||||
return labelErr
|
||||
}
|
||||
|
||||
// Reset portrait label.
|
||||
if label, err := LabelBySlug("portrait"); err != nil {
|
||||
return err
|
||||
} else if err = UnscopedDb().
|
||||
Delete(entity.PhotoLabel{}, "label_id = ?", label.ID).Error; err != nil {
|
||||
return err
|
||||
} else if err = label.Update("PhotoCount", 0); err != nil {
|
||||
return err
|
||||
if label, labelErr := LabelBySlug("portrait"); labelErr != nil {
|
||||
return labelErr
|
||||
} else if labelErr = UnscopedDb().
|
||||
Delete(entity.PhotoLabel{}, "label_id = ?", label.ID).Error; labelErr != nil {
|
||||
return labelErr
|
||||
} else if labelErr = label.Update("PhotoCount", 0); labelErr != nil {
|
||||
return labelErr
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -83,16 +83,16 @@ func ShareSelection(originals bool) FileSelection {
|
||||
}
|
||||
|
||||
// SelectedFiles finds files based on the given selection form, e.g. for downloading or sharing.
|
||||
func SelectedFiles(f form.Selection, o FileSelection) (results entity.Files, err error) {
|
||||
if f.Empty() {
|
||||
func SelectedFiles(frm form.Selection, o FileSelection) (results entity.Files, err error) {
|
||||
if frm.Empty() {
|
||||
return results, errors.New("no items selected")
|
||||
}
|
||||
|
||||
// Resolve photos in smart albums.
|
||||
if photoIds, err := AlbumsPhotoUIDs(f.Albums, false, o.Private); err != nil {
|
||||
if photoIds, err := AlbumsPhotoUIDs(frm.Albums, false, o.Private); err != nil {
|
||||
log.Warnf("query: %s", err.Error())
|
||||
} else if len(photoIds) > 0 {
|
||||
f.Photos = append(f.Photos, photoIds...)
|
||||
frm.Photos = append(frm.Photos, photoIds...)
|
||||
}
|
||||
|
||||
var concat string
|
||||
@@ -123,7 +123,7 @@ func SelectedFiles(f form.Selection, o FileSelection) (results entity.Files, err
|
||||
Select("files.*").
|
||||
Joins("JOIN photos ON photos.id = files.photo_id").
|
||||
Where("files.file_missing = 0 AND files.file_name <> '' AND files.file_hash <> ''").
|
||||
Where(where, f.Photos, f.Places, f.Files, f.Files, f.Files, f.Albums, f.Subjects, f.Labels, f.Labels).
|
||||
Where(where, frm.Photos, frm.Places, frm.Files, frm.Files, frm.Files, frm.Albums, frm.Subjects, frm.Labels, frm.Labels).
|
||||
Group("files.id")
|
||||
|
||||
// File size limit?
|
||||
|
||||
@@ -108,7 +108,7 @@ func TestFilesByUID(t *testing.T) {
|
||||
assert.Equal(t, 0, len(files))
|
||||
})
|
||||
//TODO fails on mariadb
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
files, err := FilesByUID([]string{"fs6sg6bw45bnlxxx"}, -100, 0)
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
@@ -6,58 +6,68 @@ import (
|
||||
)
|
||||
|
||||
// PhotoLabel returns a photo label entity if exists.
|
||||
func PhotoLabel(photoID, labelID uint) (label entity.PhotoLabel, err error) {
|
||||
if err := Db().Where("photo_id = ? AND label_id = ?", photoID, labelID).Preload("Photo").Preload("Label").First(&label).Error; err != nil {
|
||||
return label, err
|
||||
func PhotoLabel(photoID, labelID uint) (*entity.PhotoLabel, error) {
|
||||
result := &entity.PhotoLabel{}
|
||||
|
||||
if err := Db().Where("photo_id = ? AND label_id = ?", photoID, labelID).Preload("Photo").Preload("Label").First(result).Error; err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return label, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// LabelBySlug returns a Label based on the slug name.
|
||||
func LabelBySlug(labelSlug string) (label entity.Label, err error) {
|
||||
if err := Db().Where("label_slug = ? OR custom_slug = ?", labelSlug, labelSlug).First(&label).Error; err != nil {
|
||||
return label, err
|
||||
func LabelBySlug(labelSlug string) (*entity.Label, error) {
|
||||
result := &entity.Label{}
|
||||
|
||||
if err := Db().Where("label_slug = ? OR custom_slug = ?", labelSlug, labelSlug).First(result).Error; err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return label, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// LabelByUID returns a Label based on the label UID.
|
||||
func LabelByUID(labelUID string) (label entity.Label, err error) {
|
||||
if err := Db().Where("label_uid = ?", labelUID).First(&label).Error; err != nil {
|
||||
return label, err
|
||||
func LabelByUID(labelUID string) (*entity.Label, error) {
|
||||
result := &entity.Label{}
|
||||
|
||||
if err := Db().Where("label_uid = ?", labelUID).First(result).Error; err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return label, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// LabelThumbBySlug returns a label cover file based on the slug name.
|
||||
func LabelThumbBySlug(labelSlug string) (file entity.File, err error) {
|
||||
func LabelThumbBySlug(labelSlug string) (*entity.File, error) {
|
||||
result := &entity.File{}
|
||||
|
||||
if err := Db().Where("files.file_primary AND files.file_type IN (?) AND files.deleted_at IS NULL", media.PreviewExpr).
|
||||
Joins("JOIN labels ON labels.label_slug = ?", labelSlug).
|
||||
Joins("JOIN photos_labels ON photos_labels.label_id = labels.id AND photos_labels.photo_id = files.photo_id AND photos_labels.uncertainty < 100").
|
||||
Joins("JOIN photos ON photos.id = files.photo_id AND photos.photo_private = 0 AND photos.deleted_at IS NULL").
|
||||
Order("photos.photo_quality DESC, photos_labels.uncertainty ASC").
|
||||
First(&file).Error; err != nil {
|
||||
return file, err
|
||||
First(result).Error; err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// LabelThumbByUID returns a label cover file based on the label UID.
|
||||
func LabelThumbByUID(labelUID string) (file entity.File, err error) {
|
||||
func LabelThumbByUID(labelUID string) (*entity.File, error) {
|
||||
result := &entity.File{}
|
||||
|
||||
// Search matching label
|
||||
err = Db().Where("files.file_primary AND files.deleted_at IS NULL").
|
||||
err := Db().Where("files.file_primary AND files.deleted_at IS NULL").
|
||||
Joins("JOIN labels ON labels.label_uid = ?", labelUID).
|
||||
Joins("JOIN photos_labels ON photos_labels.label_id = labels.id AND photos_labels.photo_id = files.photo_id AND photos_labels.uncertainty < 100").
|
||||
Joins("JOIN photos ON photos.id = files.photo_id AND photos.photo_private = 0 AND photos.deleted_at IS NULL").
|
||||
Order("photos.photo_quality DESC, photos_labels.uncertainty ASC").
|
||||
First(&file).Error
|
||||
First(result).Error
|
||||
|
||||
if err == nil {
|
||||
return file, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// If failed, search for category instead
|
||||
@@ -67,7 +77,7 @@ func LabelThumbByUID(labelUID string) (file entity.File, err error) {
|
||||
Joins("JOIN labels ON c.category_id = labels.id AND labels.label_uid= ?", labelUID).
|
||||
Joins("JOIN photos ON photos.id = files.photo_id AND photos.photo_private = 0 AND photos.deleted_at IS NULL").
|
||||
Order("photos.photo_quality DESC, photos_labels.uncertainty ASC").
|
||||
First(&file).Error
|
||||
First(result).Error
|
||||
|
||||
return file, err
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -4,96 +4,108 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
)
|
||||
|
||||
func TestLabelBySlug(t *testing.T) {
|
||||
t.Run("file found", func(t *testing.T) {
|
||||
label, err := LabelBySlug("flower")
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
result, err := LabelBySlug("flower")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "Flower", label.LabelName)
|
||||
assert.IsType(t, &entity.Label{}, result)
|
||||
assert.Equal(t, "Flower", result.LabelName)
|
||||
})
|
||||
|
||||
t.Run("no file found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
label, err := LabelBySlug("111")
|
||||
|
||||
assert.IsType(t, &entity.Label{}, label)
|
||||
assert.Error(t, err, "record not found")
|
||||
assert.Empty(t, label.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLabelByUID(t *testing.T) {
|
||||
t.Run("file found", func(t *testing.T) {
|
||||
label, err := LabelByUID("ls6sg6b1wowuy3c5")
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
result, err := LabelByUID("ls6sg6b1wowuy3c5")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "COW", label.LabelName)
|
||||
assert.IsType(t, &entity.Label{}, result)
|
||||
assert.Equal(t, "COW", result.LabelName)
|
||||
})
|
||||
|
||||
t.Run("no file found", func(t *testing.T) {
|
||||
label, err := LabelByUID("111")
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
result, err := LabelByUID("111")
|
||||
|
||||
assert.IsType(t, &entity.Label{}, result)
|
||||
assert.Error(t, err, "record not found")
|
||||
assert.Empty(t, label.ID)
|
||||
assert.Empty(t, result.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLabelThumbBySlug(t *testing.T) {
|
||||
t.Run("file found", func(t *testing.T) {
|
||||
file, err := LabelThumbBySlug("cow")
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
result, err := LabelThumbBySlug("cow")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "1990/04/bridge2.jpg", file.FileName)
|
||||
assert.IsType(t, &entity.File{}, result)
|
||||
assert.Equal(t, "1990/04/bridge2.jpg", result.FileName)
|
||||
})
|
||||
|
||||
t.Run("no file found", func(t *testing.T) {
|
||||
file, err := LabelThumbBySlug("no-jpeg")
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
result, err := LabelThumbBySlug("no-jpeg")
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("did not expect to find file: %+v", file)
|
||||
t.Fatalf("did not expect to find file: %+v", result)
|
||||
}
|
||||
|
||||
assert.IsType(t, &entity.File{}, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLabelThumbByUID(t *testing.T) {
|
||||
t.Run("file found", func(t *testing.T) {
|
||||
file, err := LabelThumbByUID("ls6sg6b1wowuy3c5")
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
result, err := LabelThumbByUID("ls6sg6b1wowuy3c5")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "1990/04/bridge2.jpg", file.FileName)
|
||||
assert.IsType(t, &entity.File{}, result)
|
||||
assert.Equal(t, "1990/04/bridge2.jpg", result.FileName)
|
||||
})
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
result, err := LabelThumbByUID("14")
|
||||
|
||||
t.Run("no file found", func(t *testing.T) {
|
||||
file, err := LabelThumbByUID("14")
|
||||
|
||||
assert.IsType(t, &entity.File{}, result)
|
||||
assert.Error(t, err, "record not found")
|
||||
t.Log(file)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPhotoLabel(t *testing.T) {
|
||||
t.Run("photo label found", func(t *testing.T) {
|
||||
r, err := PhotoLabel(uint(1000000), uint(1000001))
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
result, err := PhotoLabel(uint(1000000), uint(1000001))
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, 38, r.Uncertainty)
|
||||
|
||||
assert.IsType(t, &entity.PhotoLabel{}, result)
|
||||
assert.Equal(t, 38, result.Uncertainty)
|
||||
})
|
||||
t.Run("no photo label found", func(t *testing.T) {
|
||||
r, err := PhotoLabel(uint(1000000), uint(1000003))
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
result, err := PhotoLabel(uint(1000000), uint(1000003))
|
||||
|
||||
assert.IsType(t, &entity.PhotoLabel{}, result)
|
||||
assert.Equal(t, "record not found", err.Error())
|
||||
t.Log(r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,16 +9,16 @@ import (
|
||||
)
|
||||
|
||||
// SelectedPhotos finds photos based on the given selection form, e.g. for adding them to an album.
|
||||
func SelectedPhotos(f form.Selection) (results entity.Photos, err error) {
|
||||
if f.Empty() {
|
||||
func SelectedPhotos(frm form.Selection) (results entity.Photos, err error) {
|
||||
if frm.Empty() {
|
||||
return results, errors.New("no items selected")
|
||||
}
|
||||
|
||||
// Resolve photos in smart albums.
|
||||
if photoIds, err := AlbumsPhotoUIDs(f.Albums, false, false); err != nil {
|
||||
if photoIds, err := AlbumsPhotoUIDs(frm.Albums, false, false); err != nil {
|
||||
log.Warnf("query: %s", err.Error())
|
||||
} else if len(photoIds) > 0 {
|
||||
f.Photos = append(f.Photos, photoIds...)
|
||||
frm.Photos = append(frm.Photos, photoIds...)
|
||||
}
|
||||
|
||||
var concat string
|
||||
@@ -46,7 +46,7 @@ func SelectedPhotos(f form.Selection) (results entity.Photos, err error) {
|
||||
|
||||
s := UnscopedDb().Table("photos").
|
||||
Select("photos.*").
|
||||
Where(where, f.Photos, f.Places, f.Files, f.Files, f.Files, f.Albums, f.Subjects, f.Labels, f.Labels)
|
||||
Where(where, frm.Photos, frm.Places, frm.Files, frm.Files, frm.Files, frm.Albums, frm.Subjects, frm.Labels, frm.Labels)
|
||||
|
||||
if result := s.Scan(&results); result.Error != nil {
|
||||
return results, result.Error
|
||||
|
||||
@@ -6,27 +6,27 @@ import (
|
||||
)
|
||||
|
||||
// Accounts returns a list of accounts.
|
||||
func Accounts(f form.SearchServices) (result entity.Services, err error) {
|
||||
func Accounts(frm form.SearchServices) (result entity.Services, err error) {
|
||||
s := Db().Where(&entity.Service{})
|
||||
|
||||
if f.Share {
|
||||
if frm.Share {
|
||||
s = s.Where("acc_share = 1")
|
||||
}
|
||||
|
||||
if f.Sync {
|
||||
if frm.Sync {
|
||||
s = s.Where("acc_sync = 1")
|
||||
}
|
||||
|
||||
if f.Status != "" {
|
||||
s = s.Where("sync_status = ?", f.Status)
|
||||
if frm.Status != "" {
|
||||
s = s.Where("sync_status = ?", frm.Status)
|
||||
}
|
||||
|
||||
s = s.Order("acc_name ASC")
|
||||
|
||||
if f.Count > 0 && f.Count <= MaxResults {
|
||||
s = s.Limit(f.Count).Offset(f.Offset)
|
||||
if frm.Count > 0 && frm.Count <= MaxResults {
|
||||
s = s.Limit(frm.Count).Offset(frm.Offset)
|
||||
} else {
|
||||
s = s.Limit(MaxResults).Offset(f.Offset)
|
||||
s = s.Limit(MaxResults).Offset(frm.Offset)
|
||||
}
|
||||
|
||||
if err := s.Find(&result).Error; err != nil {
|
||||
|
||||
@@ -16,15 +16,15 @@ import (
|
||||
)
|
||||
|
||||
// Albums finds AlbumResults based on the search form without checking rights or permissions.
|
||||
func Albums(f form.SearchAlbums) (results AlbumResults, err error) {
|
||||
return UserAlbums(f, nil)
|
||||
func Albums(frm form.SearchAlbums) (results AlbumResults, err error) {
|
||||
return UserAlbums(frm, nil)
|
||||
}
|
||||
|
||||
// UserAlbums finds AlbumResults based on the search form and user session.
|
||||
func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults, err error) {
|
||||
func UserAlbums(frm form.SearchAlbums, sess *entity.Session) (results AlbumResults, err error) {
|
||||
start := time.Now()
|
||||
|
||||
if err = f.ParseQueryString(); err != nil {
|
||||
if err = frm.ParseQueryString(); err != nil {
|
||||
log.Debugf("albums: %s", err)
|
||||
return AlbumResults{}, err
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults
|
||||
|
||||
// Determine resource to check.
|
||||
var aclResource acl.Resource
|
||||
switch f.Type {
|
||||
switch frm.Type {
|
||||
case entity.AlbumManual:
|
||||
aclResource = acl.ResourceAlbums
|
||||
case entity.AlbumFolder:
|
||||
@@ -70,27 +70,27 @@ func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults
|
||||
|
||||
// Exclude private content?
|
||||
if acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) || acl.Rules.Deny(aclResource, aclRole, acl.AccessPrivate) {
|
||||
f.Public = true
|
||||
f.Private = false
|
||||
frm.Public = true
|
||||
frm.Private = false
|
||||
}
|
||||
}
|
||||
|
||||
// Set sort order.
|
||||
switch f.Order {
|
||||
switch frm.Order {
|
||||
case sortby.Count:
|
||||
s = s.Order("photo_count DESC, albums.album_title, albums.album_uid DESC")
|
||||
case sortby.Moment, sortby.Newest:
|
||||
if f.Type == entity.AlbumManual || f.Type == entity.AlbumState {
|
||||
if frm.Type == entity.AlbumManual || frm.Type == entity.AlbumState {
|
||||
s = s.Order("albums.album_uid DESC")
|
||||
} else if f.Type == entity.AlbumMoment {
|
||||
} else if frm.Type == entity.AlbumMoment {
|
||||
s = s.Order("has_year, albums.album_year DESC, albums.album_month DESC, albums.album_day DESC, albums.album_title, albums.album_uid DESC")
|
||||
} else {
|
||||
s = s.Order("albums.album_year DESC, albums.album_month DESC, albums.album_day DESC, albums.album_title, albums.album_uid DESC")
|
||||
}
|
||||
case sortby.Oldest:
|
||||
if f.Type == entity.AlbumManual || f.Type == entity.AlbumState {
|
||||
if frm.Type == entity.AlbumManual || frm.Type == entity.AlbumState {
|
||||
s = s.Order("albums.album_uid ASC")
|
||||
} else if f.Type == entity.AlbumMoment {
|
||||
} else if frm.Type == entity.AlbumMoment {
|
||||
s = s.Order("has_year, albums.album_year ASC, albums.album_month ASC, albums.album_day ASC, albums.album_title, albums.album_uid ASC")
|
||||
} else {
|
||||
s = s.Order("albums.album_year ASC, albums.album_month ASC, albums.album_day ASC, albums.album_title, albums.album_uid ASC")
|
||||
@@ -108,21 +108,21 @@ func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults
|
||||
case sortby.Slug:
|
||||
s = s.Order("albums.album_slug ASC, albums.album_uid DESC")
|
||||
case sortby.Favorites:
|
||||
if f.Type == entity.AlbumFolder {
|
||||
if frm.Type == entity.AlbumFolder {
|
||||
s = s.Order("albums.album_favorite DESC, albums.album_path ASC, albums.album_uid DESC")
|
||||
} else if f.Type == entity.AlbumMonth {
|
||||
} else if frm.Type == entity.AlbumMonth {
|
||||
s = s.Order("albums.album_favorite DESC, albums.album_year DESC, albums.album_month DESC, albums.album_day DESC, albums.album_title, albums.album_uid DESC")
|
||||
} else {
|
||||
s = s.Order("albums.album_favorite DESC, albums.album_title ASC, albums.album_uid DESC")
|
||||
}
|
||||
case sortby.Name:
|
||||
if f.Type == entity.AlbumFolder {
|
||||
if frm.Type == entity.AlbumFolder {
|
||||
s = s.Order("albums.album_path ASC, albums.album_uid DESC")
|
||||
} else {
|
||||
s = s.Order("albums.album_title ASC, albums.album_uid DESC")
|
||||
}
|
||||
case sortby.NameReverse:
|
||||
if f.Type == entity.AlbumFolder {
|
||||
if frm.Type == entity.AlbumFolder {
|
||||
s = s.Order("albums.album_path DESC, albums.album_uid DESC")
|
||||
} else {
|
||||
s = s.Order("albums.album_title DESC, albums.album_uid DESC")
|
||||
@@ -132,8 +132,8 @@ func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults
|
||||
}
|
||||
|
||||
// Find specific UIDs only?
|
||||
if txt.NotEmpty(f.UID) {
|
||||
ids := SplitOr(strings.ToLower(f.UID))
|
||||
if txt.NotEmpty(frm.UID) {
|
||||
ids := SplitOr(strings.ToLower(frm.UID))
|
||||
|
||||
if rnd.ContainsUID(ids, entity.AlbumUID) {
|
||||
s = s.Where("albums.album_uid IN (?)", ids)
|
||||
@@ -141,10 +141,10 @@ func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults
|
||||
}
|
||||
|
||||
// Filter by title or path?
|
||||
if txt.NotEmpty(f.Query) {
|
||||
q := "%" + strings.Trim(f.Query, " *%") + "%"
|
||||
if txt.NotEmpty(frm.Query) {
|
||||
q := "%" + strings.Trim(frm.Query, " *%") + "%"
|
||||
|
||||
if f.Type == entity.AlbumFolder {
|
||||
if frm.Type == entity.AlbumFolder {
|
||||
s = s.Where("albums.album_title LIKE ? OR albums.album_location LIKE ? OR albums.album_path LIKE ?", q, q, q)
|
||||
} else {
|
||||
s = s.Where("albums.album_title LIKE ? OR albums.album_location LIKE ?", q, q)
|
||||
@@ -152,61 +152,61 @@ func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults
|
||||
}
|
||||
|
||||
// Albums with public pictures only?
|
||||
if f.Public {
|
||||
if frm.Public {
|
||||
s = s.Where("albums.album_private = 0 AND (albums.album_type <> 'folder' OR albums.album_path IN (SELECT photo_path FROM photos WHERE photo_private = 0 AND photo_quality > -1 AND deleted_at IS NULL))")
|
||||
} else {
|
||||
s = s.Where("albums.album_type <> 'folder' OR albums.album_path IN (SELECT photo_path FROM photos WHERE photo_quality > -1 AND deleted_at IS NULL)")
|
||||
}
|
||||
|
||||
if txt.NotEmpty(f.Type) {
|
||||
s = s.Where("albums.album_type IN (?)", strings.Split(f.Type, txt.Or))
|
||||
if txt.NotEmpty(frm.Type) {
|
||||
s = s.Where("albums.album_type IN (?)", strings.Split(frm.Type, txt.Or))
|
||||
}
|
||||
|
||||
if txt.NotEmpty(f.Category) {
|
||||
s = s.Where("albums.album_category IN (?)", strings.Split(f.Category, txt.Or))
|
||||
if txt.NotEmpty(frm.Category) {
|
||||
s = s.Where("albums.album_category IN (?)", strings.Split(frm.Category, txt.Or))
|
||||
}
|
||||
|
||||
if txt.NotEmpty(f.Location) {
|
||||
s = s.Where("albums.album_location IN (?)", strings.Split(f.Location, txt.Or))
|
||||
if txt.NotEmpty(frm.Location) {
|
||||
s = s.Where("albums.album_location IN (?)", strings.Split(frm.Location, txt.Or))
|
||||
}
|
||||
|
||||
if txt.NotEmpty(f.Country) {
|
||||
s = s.Where("albums.album_country IN (?)", strings.Split(f.Country, txt.Or))
|
||||
if txt.NotEmpty(frm.Country) {
|
||||
s = s.Where("albums.album_country IN (?)", strings.Split(frm.Country, txt.Or))
|
||||
}
|
||||
|
||||
// Favorites only?
|
||||
if f.Favorite {
|
||||
if frm.Favorite {
|
||||
s = s.Where("albums.album_favorite = 1")
|
||||
}
|
||||
|
||||
// Filter by year?
|
||||
if txt.NotEmpty(f.Year) {
|
||||
if txt.NotEmpty(frm.Year) {
|
||||
// Filter by the pictures included if it is a manually managed album, as these do not have an explicit
|
||||
// year assigned to them, unlike calendar albums and moments for example.
|
||||
if f.Type == entity.AlbumManual {
|
||||
if frm.Type == entity.AlbumManual {
|
||||
s = s.Where("? OR albums.album_uid IN (SELECT DISTINCT pay.album_uid FROM photos_albums pay "+
|
||||
"JOIN photos py ON pay.photo_uid = py.photo_uid WHERE py.photo_year IN (?) AND pay.hidden = 0 AND pay.missing = 0)",
|
||||
gorm.Expr(AnyInt("albums.album_year", f.Year, txt.Or, entity.UnknownYear, txt.YearMax)), strings.Split(f.Year, txt.Or))
|
||||
gorm.Expr(AnyInt("albums.album_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax)), strings.Split(frm.Year, txt.Or))
|
||||
} else {
|
||||
s = s.Where(AnyInt("albums.album_year", f.Year, txt.Or, entity.UnknownYear, txt.YearMax))
|
||||
s = s.Where(AnyInt("albums.album_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax))
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by month?
|
||||
if txt.NotEmpty(f.Month) {
|
||||
s = s.Where(AnyInt("albums.album_month", f.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
|
||||
if txt.NotEmpty(frm.Month) {
|
||||
s = s.Where(AnyInt("albums.album_month", frm.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
|
||||
}
|
||||
|
||||
// Filter by day?
|
||||
if txt.NotEmpty(f.Day) {
|
||||
s = s.Where(AnyInt("albums.album_day", f.Day, txt.Or, entity.UnknownDay, txt.DayMax))
|
||||
if txt.NotEmpty(frm.Day) {
|
||||
s = s.Where(AnyInt("albums.album_day", frm.Day, txt.Or, entity.UnknownDay, txt.DayMax))
|
||||
}
|
||||
|
||||
// Limit result count.
|
||||
if f.Count > 0 && f.Count <= MaxResults {
|
||||
s = s.Limit(f.Count).Offset(f.Offset)
|
||||
if frm.Count > 0 && frm.Count <= MaxResults {
|
||||
s = s.Limit(frm.Count).Offset(frm.Offset)
|
||||
} else {
|
||||
s = s.Limit(MaxResults).Offset(f.Offset)
|
||||
s = s.Limit(MaxResults).Offset(frm.Offset)
|
||||
}
|
||||
|
||||
// Query database.
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestLikeAny(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
if w := LikeAny("k.keyword", "", true, false); len(w) > 0 {
|
||||
t.Fatal("no where condition expected")
|
||||
}
|
||||
@@ -282,7 +282,7 @@ func TestAnySlug(t *testing.T) {
|
||||
assert.Equal(t, "custom_slug = 'img'", where)
|
||||
})
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", "", " ")
|
||||
assert.Equal(t, "", where)
|
||||
})
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
)
|
||||
|
||||
// Faces searches faces and returns them.
|
||||
func Faces(f form.SearchFaces) (results FaceResults, err error) {
|
||||
if err := f.ParseQueryString(); err != nil {
|
||||
func Faces(frm form.SearchFaces) (results FaceResults, err error) {
|
||||
if err = frm.ParseQueryString(); err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
@@ -20,18 +20,18 @@ func Faces(f form.SearchFaces) (results FaceResults, err error) {
|
||||
// Base query.
|
||||
s := UnscopedDb().Table(facesTable)
|
||||
|
||||
if f.Markers {
|
||||
if frm.Markers {
|
||||
s = s.Select(fmt.Sprintf(`%s.*, m.marker_uid, m.file_uid, m.marker_name, m.subj_src, m.marker_src,
|
||||
m.marker_type, m.marker_review, m.marker_invalid, m.size, m.score, m.thumb, m.face_dist`, facesTable))
|
||||
|
||||
if txt.Yes(f.Unknown) {
|
||||
if txt.Yes(frm.Unknown) {
|
||||
s = s.Joins(`JOIN (
|
||||
SELECT face_id, MIN(marker_uid) AS marker_uid FROM markers
|
||||
WHERE face_id <> '' AND subj_uid = '' AND marker_name = '' AND marker_type = 'face' AND marker_src = 'image'
|
||||
AND marker_invalid = 0 AND face_dist <= 0.64 AND size >= 80 AND score >= 15
|
||||
GROUP BY face_id) fm
|
||||
ON faces.id = fm.face_id`)
|
||||
} else if txt.No(f.Unknown) {
|
||||
} else if txt.No(frm.Unknown) {
|
||||
s = s.Joins(`JOIN (
|
||||
SELECT face_id, MIN(marker_uid) AS marker_uid FROM markers
|
||||
WHERE face_id <> '' AND subj_uid <> '' AND marker_name <> '' AND marker_type = 'face' AND marker_src = 'image'
|
||||
@@ -53,14 +53,14 @@ func Faces(f form.SearchFaces) (results FaceResults, err error) {
|
||||
}
|
||||
|
||||
// Limit result count.
|
||||
if f.Count > 0 && f.Count <= MaxResults {
|
||||
s = s.Limit(f.Count).Offset(f.Offset)
|
||||
if frm.Count > 0 && frm.Count <= MaxResults {
|
||||
s = s.Limit(frm.Count).Offset(frm.Offset)
|
||||
} else {
|
||||
s = s.Limit(MaxResults).Offset(f.Offset)
|
||||
s = s.Limit(MaxResults).Offset(frm.Offset)
|
||||
}
|
||||
|
||||
// Set sort order.
|
||||
switch f.Order {
|
||||
switch frm.Order {
|
||||
case "subject":
|
||||
s = s.Order(fmt.Sprintf("%s.subj_uid", facesTable))
|
||||
case "added":
|
||||
@@ -72,8 +72,8 @@ func Faces(f form.SearchFaces) (results FaceResults, err error) {
|
||||
}
|
||||
|
||||
// Find specific IDs?
|
||||
if f.UID != "" {
|
||||
s = s.Where(fmt.Sprintf("%s.id IN (?)", facesTable), strings.Split(strings.ToUpper(f.UID), txt.Or))
|
||||
if frm.UID != "" {
|
||||
s = s.Where(fmt.Sprintf("%s.id IN (?)", facesTable), strings.Split(strings.ToUpper(frm.UID), txt.Or))
|
||||
|
||||
if result := s.Scan(&results); result.Error != nil {
|
||||
return results, result.Error
|
||||
@@ -83,14 +83,14 @@ func Faces(f form.SearchFaces) (results FaceResults, err error) {
|
||||
}
|
||||
|
||||
// Exclude unknown faces?
|
||||
if txt.Yes(f.Unknown) {
|
||||
if txt.Yes(frm.Unknown) {
|
||||
s = s.Where(fmt.Sprintf("%s.subj_uid = '' OR %s.subj_uid IS NULL", facesTable, facesTable))
|
||||
} else if txt.No(f.Unknown) {
|
||||
} else if txt.No(frm.Unknown) {
|
||||
s = s.Where(fmt.Sprintf("%s.subj_uid <> '' AND %s.subj_uid IS NOT NULL", facesTable, facesTable))
|
||||
}
|
||||
|
||||
// Show hidden faces?
|
||||
if !txt.Yes(f.Hidden) {
|
||||
if !txt.Yes(frm.Hidden) {
|
||||
s = s.Where(fmt.Sprintf("%s.face_hidden = 0", facesTable))
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
)
|
||||
|
||||
// Labels searches labels based on their name.
|
||||
func Labels(f form.SearchLabels) (results []Label, err error) {
|
||||
if err := f.ParseQueryString(); err != nil {
|
||||
func Labels(frm form.SearchLabels) (results []Label, err error) {
|
||||
if err = frm.ParseQueryString(); err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
@@ -26,22 +26,22 @@ func Labels(f form.SearchLabels) (results []Label, err error) {
|
||||
Group("labels.id")
|
||||
|
||||
// Limit result count.
|
||||
if f.Count > 0 && f.Count <= MaxResults {
|
||||
s = s.Limit(f.Count).Offset(f.Offset)
|
||||
if frm.Count > 0 && frm.Count <= MaxResults {
|
||||
s = s.Limit(frm.Count).Offset(frm.Offset)
|
||||
} else {
|
||||
s = s.Limit(MaxResults).Offset(f.Offset)
|
||||
s = s.Limit(MaxResults).Offset(frm.Offset)
|
||||
}
|
||||
|
||||
// Set sort order.
|
||||
switch f.Order {
|
||||
switch frm.Order {
|
||||
case "slug":
|
||||
s = s.Order("labels.label_favorite DESC, custom_slug ASC")
|
||||
default:
|
||||
s = s.Order("labels.label_favorite DESC, custom_slug ASC")
|
||||
}
|
||||
|
||||
if f.UID != "" {
|
||||
s = s.Where("labels.label_uid IN (?)", strings.Split(strings.ToLower(f.UID), txt.Or))
|
||||
if frm.UID != "" {
|
||||
s = s.Where("labels.label_uid IN (?)", strings.Split(strings.ToLower(frm.UID), txt.Or))
|
||||
|
||||
if result := s.Scan(&results); result.Error != nil {
|
||||
return results, result.Error
|
||||
@@ -50,16 +50,16 @@ func Labels(f form.SearchLabels) (results []Label, err error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
if f.Query != "" {
|
||||
if frm.Query != "" {
|
||||
var labelIds []uint
|
||||
var categories []entity.Category
|
||||
var label entity.Label
|
||||
|
||||
slugString := txt.Slug(f.Query)
|
||||
likeString := "%" + f.Query + "%"
|
||||
slugString := txt.Slug(frm.Query)
|
||||
likeString := "%" + frm.Query + "%"
|
||||
|
||||
if result := Db().First(&label, "label_slug = ? OR custom_slug = ?", slugString, slugString); result.Error != nil {
|
||||
log.Infof("search: label %s not found", clean.Log(f.Query))
|
||||
log.Infof("search: label %s not found", clean.Log(frm.Query))
|
||||
|
||||
s = s.Where("labels.label_name LIKE ?", likeString)
|
||||
} else {
|
||||
@@ -77,11 +77,11 @@ func Labels(f form.SearchLabels) (results []Label, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if f.Favorite {
|
||||
if frm.Favorite {
|
||||
s = s.Where("labels.label_favorite = 1")
|
||||
}
|
||||
|
||||
if !f.All {
|
||||
if !frm.All {
|
||||
s = s.Where("labels.label_priority >= 0 OR labels.label_favorite = 1")
|
||||
}
|
||||
|
||||
|
||||
@@ -32,51 +32,51 @@ var PhotosColsAll = SelectString(Photo{}, []string{"*"})
|
||||
var PhotosColsView = SelectString(Photo{}, SelectCols(GeoResult{}, []string{"*"}))
|
||||
|
||||
// Photos finds PhotoResults based on the search form without checking rights or permissions.
|
||||
func Photos(f form.SearchPhotos) (results PhotoResults, count int, err error) {
|
||||
return searchPhotos(f, nil, PhotosColsAll)
|
||||
func Photos(frm form.SearchPhotos) (results PhotoResults, count int, err error) {
|
||||
return searchPhotos(frm, nil, PhotosColsAll)
|
||||
}
|
||||
|
||||
// UserPhotos finds PhotoResults based on the search form and user session.
|
||||
func UserPhotos(f form.SearchPhotos, sess *entity.Session) (results PhotoResults, count int, err error) {
|
||||
return searchPhotos(f, sess, PhotosColsAll)
|
||||
func UserPhotos(frm form.SearchPhotos, sess *entity.Session) (results PhotoResults, count int, err error) {
|
||||
return searchPhotos(frm, sess, PhotosColsAll)
|
||||
}
|
||||
|
||||
// PhotoIds finds photo and file ids based on the search form provided and returns them as PhotoResults.
|
||||
func PhotoIds(f form.SearchPhotos) (files PhotoResults, count int, err error) {
|
||||
f.Merged = false
|
||||
f.Primary = true
|
||||
return searchPhotos(f, nil, "photos.id, photos.photo_uid, files.file_uid")
|
||||
func PhotoIds(frm form.SearchPhotos) (files PhotoResults, count int, err error) {
|
||||
frm.Merged = false
|
||||
frm.Primary = true
|
||||
return searchPhotos(frm, nil, "photos.id, photos.photo_uid, files.file_uid")
|
||||
}
|
||||
|
||||
// searchPhotos finds photos based on the search form and user session then returns them as PhotoResults.
|
||||
func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string) (results PhotoResults, count int, err error) {
|
||||
func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string) (results PhotoResults, count int, err error) {
|
||||
start := time.Now()
|
||||
|
||||
// Parse query string and filter.
|
||||
if err = f.ParseQueryString(); err != nil {
|
||||
if err = frm.ParseQueryString(); err != nil {
|
||||
log.Debugf("search: %s", err)
|
||||
return PhotoResults{}, 0, ErrBadRequest
|
||||
}
|
||||
|
||||
// Find photos near another?
|
||||
if txt.NotEmpty(f.Near) {
|
||||
if txt.NotEmpty(frm.Near) {
|
||||
photo := Photo{}
|
||||
|
||||
// Find a nearby picture using the UID or return an empty result otherwise.
|
||||
if err = Db().First(&photo, "photo_uid = ?", f.Near).Error; err != nil {
|
||||
if err = Db().First(&photo, "photo_uid = ?", frm.Near).Error; err != nil {
|
||||
log.Debugf("search: %s (find nearby)", err)
|
||||
return PhotoResults{}, 0, ErrNotFound
|
||||
}
|
||||
|
||||
// Set the S2 Cell ID to search for.
|
||||
f.S2 = photo.CellID
|
||||
frm.S2 = photo.CellID
|
||||
}
|
||||
|
||||
// Set default search distance.
|
||||
if f.Dist <= 0 {
|
||||
f.Dist = geo.DefaultDist
|
||||
} else if f.Dist > geo.DistLimit {
|
||||
f.Dist = geo.DistLimit
|
||||
if frm.Dist <= 0 {
|
||||
frm.Dist = geo.DefaultDist
|
||||
} else if frm.Dist > geo.DistLimit {
|
||||
frm.Dist = geo.DistLimit
|
||||
}
|
||||
|
||||
// Specify table names and joins.
|
||||
@@ -87,41 +87,41 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
Joins("LEFT JOIN places ON photos.place_id = places.id")
|
||||
|
||||
// Accept the album UID as scope for backward compatibility.
|
||||
if rnd.IsUID(f.Album, entity.AlbumUID) {
|
||||
if txt.Empty(f.Scope) {
|
||||
f.Scope = f.Album
|
||||
if rnd.IsUID(frm.Album, entity.AlbumUID) {
|
||||
if txt.Empty(frm.Scope) {
|
||||
frm.Scope = frm.Album
|
||||
}
|
||||
|
||||
f.Album = ""
|
||||
frm.Album = ""
|
||||
}
|
||||
|
||||
// Limit search results to a specific UID scope, e.g. when sharing.
|
||||
if txt.NotEmpty(f.Scope) {
|
||||
f.Scope = strings.ToLower(f.Scope)
|
||||
if txt.NotEmpty(frm.Scope) {
|
||||
frm.Scope = strings.ToLower(frm.Scope)
|
||||
|
||||
if idType, idPrefix := rnd.IdType(f.Scope); idType != rnd.TypeUID || idPrefix != entity.AlbumUID {
|
||||
if idType, idPrefix := rnd.IdType(frm.Scope); idType != rnd.TypeUID || idPrefix != entity.AlbumUID {
|
||||
return PhotoResults{}, 0, ErrInvalidId
|
||||
} else if a, err := entity.CachedAlbumByUID(f.Scope); err != nil || a.AlbumUID == "" {
|
||||
} else if a, err := entity.CachedAlbumByUID(frm.Scope); err != nil || a.AlbumUID == "" {
|
||||
return PhotoResults{}, 0, ErrInvalidId
|
||||
} else if a.AlbumFilter == "" {
|
||||
s = s.Joins("JOIN photos_albums ON photos_albums.photo_uid = files.photo_uid").
|
||||
Where("photos_albums.hidden = 0 AND photos_albums.album_uid = ?", a.AlbumUID)
|
||||
} else if formErr := form.Unserialize(&f, a.AlbumFilter); formErr != nil {
|
||||
} else if formErr := form.Unserialize(&frm, a.AlbumFilter); formErr != nil {
|
||||
log.Debugf("search: %s (%s)", clean.Error(formErr), clean.Log(a.AlbumFilter))
|
||||
return PhotoResults{}, 0, ErrBadFilter
|
||||
} else {
|
||||
f.Filter = a.AlbumFilter
|
||||
frm.Filter = a.AlbumFilter
|
||||
s = s.Where("files.photo_uid NOT IN (SELECT photo_uid FROM photos_albums pa WHERE pa.hidden = 1 AND pa.album_uid = ?)", a.AlbumUID)
|
||||
}
|
||||
|
||||
// Enforce search distance range (km).
|
||||
if f.Dist <= 0 {
|
||||
f.Dist = geo.DefaultDist
|
||||
} else if f.Dist > geo.ScopeDistLimit {
|
||||
f.Dist = geo.ScopeDistLimit
|
||||
if frm.Dist <= 0 {
|
||||
frm.Dist = geo.DefaultDist
|
||||
} else if frm.Dist > geo.ScopeDistLimit {
|
||||
frm.Dist = geo.ScopeDistLimit
|
||||
}
|
||||
} else {
|
||||
f.Scope = ""
|
||||
frm.Scope = ""
|
||||
}
|
||||
|
||||
// Check session permissions and apply as needed.
|
||||
@@ -131,30 +131,30 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
|
||||
// Exclude private content.
|
||||
if acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) {
|
||||
f.Public = true
|
||||
f.Private = false
|
||||
frm.Public = true
|
||||
frm.Private = false
|
||||
}
|
||||
|
||||
// Exclude archived content.
|
||||
if acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.ActionDelete) {
|
||||
f.Archived = false
|
||||
f.Review = false
|
||||
frm.Archived = false
|
||||
frm.Review = false
|
||||
}
|
||||
|
||||
// Exclude hidden files.
|
||||
if acl.Rules.Deny(acl.ResourceFiles, aclRole, acl.AccessAll) {
|
||||
f.Hidden = false
|
||||
frm.Hidden = false
|
||||
}
|
||||
|
||||
// Visitors and other restricted users can only access shared content.
|
||||
if f.Scope != "" && !sess.HasShare(f.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePhotos) || sess.NotRegistered()) ||
|
||||
f.Scope == "" && acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.ActionSearch) {
|
||||
if frm.Scope != "" && !sess.HasShare(frm.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePhotos) || sess.NotRegistered()) ||
|
||||
frm.Scope == "" && acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.ActionSearch) {
|
||||
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", authn.Denied}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePhotos), aclRole)
|
||||
return PhotoResults{}, 0, ErrForbidden
|
||||
}
|
||||
|
||||
// Limit results for external users.
|
||||
if f.Scope == "" && acl.Rules.DenyAll(acl.ResourcePhotos, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
||||
if frm.Scope == "" && acl.Rules.DenyAll(acl.ResourcePhotos, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
||||
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() {
|
||||
@@ -169,7 +169,7 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Set sort order.
|
||||
switch f.Order {
|
||||
switch frm.Order {
|
||||
case sortby.Edited:
|
||||
s = s.Where("photos.edited_at IS NOT NULL").Order("photos.edited_at DESC, files.media_id")
|
||||
case sortby.Updated:
|
||||
@@ -177,7 +177,7 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
case sortby.Archived:
|
||||
s = s.Order("photos.deleted_at DESC, files.media_id")
|
||||
case sortby.Relevance:
|
||||
if f.Label != "" {
|
||||
if frm.Label != "" {
|
||||
s = s.Order("photos.photo_quality DESC, photos_labels.uncertainty ASC, files.time_index")
|
||||
} else {
|
||||
s = s.Order("photos.photo_quality DESC, files.time_index")
|
||||
@@ -206,8 +206,8 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Exclude files with errors by default.
|
||||
if !f.Hidden {
|
||||
if f.Error {
|
||||
if !frm.Hidden {
|
||||
if frm.Error {
|
||||
s = s.Where("files.file_error <> ''")
|
||||
} else {
|
||||
s = s.Where("files.file_error = ''")
|
||||
@@ -215,13 +215,13 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Find primary files only?
|
||||
if f.Primary {
|
||||
if frm.Primary {
|
||||
s = s.Where("files.file_primary = 1")
|
||||
} else if f.Order == sortby.Size {
|
||||
} else if frm.Order == sortby.Size {
|
||||
s = s.Where("files.file_root <> 'sidecar' AND files.file_sidecar = 0")
|
||||
} else if f.Order == sortby.Similar {
|
||||
} else if frm.Order == sortby.Similar {
|
||||
s = s.Where("files.file_primary = 1 OR files.media_type = ?", media.Video)
|
||||
} else if f.Order == sortby.Random {
|
||||
} else if frm.Order == sortby.Random {
|
||||
s = s.Where("files.file_primary = 1 AND photos.photo_type NOT IN ('live','video') OR photos.photo_type IN ('live','video') AND files.media_type IN ('live','video')")
|
||||
} else {
|
||||
// Otherwise, find all matching media except sidecar files.
|
||||
@@ -229,8 +229,8 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Find specific UIDs only.
|
||||
if txt.NotEmpty(f.UID) {
|
||||
ids := SplitOr(strings.ToLower(f.UID))
|
||||
if txt.NotEmpty(frm.UID) {
|
||||
ids := SplitOr(strings.ToLower(frm.UID))
|
||||
|
||||
idType, prefix := rnd.ContainsType(ids)
|
||||
|
||||
@@ -250,14 +250,14 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Find UIDs only to improve performance.
|
||||
if sess == nil && f.FindUidOnly() {
|
||||
if sess == nil && frm.FindUidOnly() {
|
||||
if result := s.Scan(&results); result.Error != nil {
|
||||
return results, 0, result.Error
|
||||
}
|
||||
|
||||
log.Debugf("photos: found %s for %s [%s]", english.Plural(len(results), "result", "results"), f.SerializeAll(), time.Since(start))
|
||||
log.Debugf("photos: found %s for %s [%s]", english.Plural(len(results), "result", "results"), frm.SerializeAll(), time.Since(start))
|
||||
|
||||
if f.Merged {
|
||||
if frm.Merged {
|
||||
return results.Merge()
|
||||
}
|
||||
|
||||
@@ -266,8 +266,8 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Find Unique Image ID (Exif), Document ID, or Instance ID (XMP).
|
||||
if txt.NotEmpty(f.ID) {
|
||||
for _, id := range SplitAnd(strings.ToLower(f.ID)) {
|
||||
if txt.NotEmpty(frm.ID) {
|
||||
for _, id := range SplitAnd(strings.ToLower(frm.ID)) {
|
||||
if ids := SplitOr(id); len(ids) > 0 {
|
||||
s = s.Where("files.instance_id IN (?) OR photos.uuid IN (?)", ids, ids)
|
||||
}
|
||||
@@ -278,9 +278,9 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
var categories []entity.Category
|
||||
var labels []entity.Label
|
||||
var labelIds []uint
|
||||
if txt.NotEmpty(f.Label) {
|
||||
if labelErr := Db().Where(AnySlug("label_slug", f.Label, txt.Or)).Or(AnySlug("custom_slug", f.Label, txt.Or)).Find(&labels).Error; len(labels) == 0 || labelErr != nil {
|
||||
log.Debugf("search: label %s not found", txt.LogParamLower(f.Label))
|
||||
if txt.NotEmpty(frm.Label) {
|
||||
if labelErr := Db().Where(AnySlug("label_slug", frm.Label, txt.Or)).Or(AnySlug("custom_slug", frm.Label, txt.Or)).Find(&labels).Error; len(labels) == 0 || labelErr != nil {
|
||||
log.Debugf("search: label %s not found", txt.LogParamLower(frm.Label))
|
||||
return PhotoResults{}, 0, nil
|
||||
} else {
|
||||
for _, l := range labels {
|
||||
@@ -300,83 +300,83 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Set search filters based on search terms.
|
||||
if terms := txt.SearchTerms(f.Query); f.Query != "" && len(terms) == 0 {
|
||||
if f.Title == "" {
|
||||
f.Title = fmt.Sprintf("%s*", strings.Trim(f.Query, "%*"))
|
||||
f.Query = ""
|
||||
if terms := txt.SearchTerms(frm.Query); frm.Query != "" && len(terms) == 0 {
|
||||
if frm.Title == "" {
|
||||
frm.Title = fmt.Sprintf("%s*", strings.Trim(frm.Query, "%*"))
|
||||
frm.Query = ""
|
||||
}
|
||||
} else if len(terms) > 0 {
|
||||
switch {
|
||||
case terms["faces"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "faces", "")
|
||||
f.Faces = "true"
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "faces", "")
|
||||
frm.Faces = "true"
|
||||
case terms["people"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "people", "")
|
||||
f.Faces = "true"
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "people", "")
|
||||
frm.Faces = "true"
|
||||
case terms["videos"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "videos", "")
|
||||
f.Video = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "videos", "")
|
||||
frm.Video = true
|
||||
case terms["video"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "video", "")
|
||||
f.Video = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "video", "")
|
||||
frm.Video = true
|
||||
case terms["vectors"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "vectors", "")
|
||||
f.Vector = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "vectors", "")
|
||||
frm.Vector = true
|
||||
case terms["vector"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "vector", "")
|
||||
f.Vector = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "vector", "")
|
||||
frm.Vector = true
|
||||
case terms["animated"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "animated", "")
|
||||
f.Animated = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "animated", "")
|
||||
frm.Animated = true
|
||||
case terms["gifs"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "gifs", "")
|
||||
f.Animated = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "gifs", "")
|
||||
frm.Animated = true
|
||||
case terms["gif"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "gif", "")
|
||||
f.Animated = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "gif", "")
|
||||
frm.Animated = true
|
||||
case terms["live"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "live", "")
|
||||
f.Live = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "live", "")
|
||||
frm.Live = true
|
||||
case terms["raws"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "raws", "")
|
||||
f.Raw = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "raws", "")
|
||||
frm.Raw = true
|
||||
case terms["raw"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "raw", "")
|
||||
f.Raw = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "raw", "")
|
||||
frm.Raw = true
|
||||
case terms["favorites"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "favorites", "")
|
||||
f.Favorite = "true"
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "favorites", "")
|
||||
frm.Favorite = "true"
|
||||
case terms["stacks"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "stacks", "")
|
||||
f.Stack = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "stacks", "")
|
||||
frm.Stack = true
|
||||
case terms["panoramas"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "panoramas", "")
|
||||
f.Panorama = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "panoramas", "")
|
||||
frm.Panorama = true
|
||||
case terms["scans"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "scans", "")
|
||||
f.Scan = "true"
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "scans", "")
|
||||
frm.Scan = "true"
|
||||
case terms["monochrome"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "monochrome", "")
|
||||
f.Mono = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "monochrome", "")
|
||||
frm.Mono = true
|
||||
case terms["mono"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "mono", "")
|
||||
f.Mono = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "mono", "")
|
||||
frm.Mono = true
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by location info.
|
||||
if txt.No(f.Geo) {
|
||||
if txt.No(frm.Geo) {
|
||||
s = s.Where("photos.cell_id = 'zz'")
|
||||
} else if txt.NotEmpty(f.Geo) {
|
||||
} else if txt.NotEmpty(frm.Geo) {
|
||||
s = s.Where("photos.cell_id <> 'zz'")
|
||||
}
|
||||
|
||||
// Filter by query string.
|
||||
if f.Query != "" {
|
||||
if err := Db().Where(AnySlug("custom_slug", f.Query, " ")).Find(&labels).Error; len(labels) == 0 || err != nil {
|
||||
log.Tracef("search: label %s not found, using fuzzy search", txt.LogParamLower(f.Query))
|
||||
if frm.Query != "" {
|
||||
if err := Db().Where(AnySlug("custom_slug", frm.Query, " ")).Find(&labels).Error; len(labels) == 0 || err != nil {
|
||||
log.Tracef("search: label %s not found, using fuzzy search", txt.LogParamLower(frm.Query))
|
||||
|
||||
for _, where := range LikeAnyKeyword("k.keyword", f.Query) {
|
||||
for _, where := range LikeAnyKeyword("k.keyword", frm.Query) {
|
||||
s = s.Where("files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where))
|
||||
}
|
||||
} else {
|
||||
@@ -392,7 +392,7 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
}
|
||||
|
||||
if wheres := LikeAnyKeyword("k.keyword", f.Query); len(wheres) > 0 {
|
||||
if wheres := LikeAnyKeyword("k.keyword", frm.Query); len(wheres) > 0 {
|
||||
for _, where := range wheres {
|
||||
s = s.Where("files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?)) OR "+
|
||||
"files.photo_id IN (SELECT pl.photo_id FROM photos_labels pl WHERE pl.uncertainty < 100 AND pl.label_id IN (?))", gorm.Expr(where), labelIds)
|
||||
@@ -404,51 +404,51 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Search for one or more keywords.
|
||||
if txt.NotEmpty(f.Keywords) {
|
||||
for _, where := range LikeAnyWord("k.keyword", f.Keywords) {
|
||||
if txt.NotEmpty(frm.Keywords) {
|
||||
for _, where := range LikeAnyWord("k.keyword", frm.Keywords) {
|
||||
s = s.Where("files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where))
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by number of faces.
|
||||
if f.Faces == "" {
|
||||
if frm.Faces == "" {
|
||||
// Do nothing.
|
||||
} else if txt.IsUInt(f.Faces) {
|
||||
s = s.Where("photos.photo_faces >= ?", txt.Int(f.Faces))
|
||||
} else if txt.New(f.Faces) && f.Face == "" {
|
||||
f.Face = f.Faces
|
||||
f.Faces = ""
|
||||
} else if txt.Yes(f.Faces) {
|
||||
} else if txt.IsUInt(frm.Faces) {
|
||||
s = s.Where("photos.photo_faces >= ?", txt.Int(frm.Faces))
|
||||
} else if txt.New(frm.Faces) && frm.Face == "" {
|
||||
frm.Face = frm.Faces
|
||||
frm.Faces = ""
|
||||
} else if txt.Yes(frm.Faces) {
|
||||
s = s.Where("photos.photo_faces > 0")
|
||||
} else if txt.No(f.Faces) {
|
||||
} else if txt.No(frm.Faces) {
|
||||
s = s.Where("photos.photo_faces = 0")
|
||||
}
|
||||
|
||||
// Filter for specific face clusters? Example: PLJ7A3G4MBGZJRMVDIUCBLC46IAP4N7O
|
||||
if f.Face == "" {
|
||||
if frm.Face == "" {
|
||||
// Do nothing.
|
||||
} else if len(f.Face) >= 32 {
|
||||
for _, f := range SplitAnd(strings.ToUpper(f.Face)) {
|
||||
} else if len(frm.Face) >= 32 {
|
||||
for _, f := range SplitAnd(strings.ToUpper(frm.Face)) {
|
||||
s = s.Where(fmt.Sprintf("files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 WHERE face_id IN (?))",
|
||||
entity.Marker{}.TableName()), SplitOr(f))
|
||||
}
|
||||
} else if txt.New(f.Face) {
|
||||
} else if txt.New(frm.Face) {
|
||||
s = s.Where(fmt.Sprintf("files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? WHERE subj_uid IS NULL OR subj_uid = '')",
|
||||
entity.Marker{}.TableName()), entity.MarkerFace)
|
||||
} else if txt.No(f.Face) {
|
||||
} else if txt.No(frm.Face) {
|
||||
s = s.Where(fmt.Sprintf("files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? WHERE face_id IS NULL OR face_id = '')",
|
||||
entity.Marker{}.TableName()), entity.MarkerFace)
|
||||
} else if txt.Yes(f.Face) {
|
||||
} else if txt.Yes(frm.Face) {
|
||||
s = s.Where(fmt.Sprintf("files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? WHERE face_id IS NOT NULL AND face_id <> '')",
|
||||
entity.Marker{}.TableName()), entity.MarkerFace)
|
||||
} else if txt.IsUInt(f.Face) {
|
||||
} else if txt.IsUInt(frm.Face) {
|
||||
s = s.Where("files.photo_id IN (SELECT photo_id FROM files f JOIN markers m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? JOIN faces ON faces.id = m.face_id WHERE m.face_id IS NOT NULL AND m.face_id <> '' AND faces.face_kind = ?)",
|
||||
entity.MarkerFace, txt.Int(f.Face))
|
||||
entity.MarkerFace, txt.Int(frm.Face))
|
||||
}
|
||||
|
||||
// Filter for one or more subjects.
|
||||
if txt.NotEmpty(f.Subject) {
|
||||
for _, subj := range SplitAnd(strings.ToLower(f.Subject)) {
|
||||
if txt.NotEmpty(frm.Subject) {
|
||||
for _, subj := range SplitAnd(strings.ToLower(frm.Subject)) {
|
||||
if subjects := SplitOr(subj); rnd.ContainsUID(subjects, 'j') {
|
||||
s = s.Where(fmt.Sprintf("files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 WHERE subj_uid IN (?))",
|
||||
entity.Marker{}.TableName()), subjects)
|
||||
@@ -457,187 +457,187 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(AnySlug("s.subj_slug", subj, txt.Or)))
|
||||
}
|
||||
}
|
||||
} else if txt.NotEmpty(f.Subjects) {
|
||||
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, f.Subjects) {
|
||||
} else if txt.NotEmpty(frm.Subjects) {
|
||||
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, frm.Subjects) {
|
||||
s = s.Where(fmt.Sprintf("files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 JOIN %s s ON s.subj_uid = m.subj_uid WHERE (?))",
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(where))
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by status.
|
||||
if f.Hidden {
|
||||
if frm.Hidden {
|
||||
s = s.Where("photos.photo_quality = -1")
|
||||
s = s.Where("photos.deleted_at IS NULL")
|
||||
} else if f.Archived {
|
||||
} else if frm.Archived {
|
||||
s = s.Where("photos.photo_quality > -1")
|
||||
s = s.Where("photos.deleted_at IS NOT NULL")
|
||||
} else {
|
||||
s = s.Where("photos.deleted_at IS NULL")
|
||||
|
||||
if f.Private {
|
||||
if frm.Private {
|
||||
s = s.Where("photos.photo_private = 1")
|
||||
} else if f.Public {
|
||||
} else if frm.Public {
|
||||
s = s.Where("photos.photo_private = 0")
|
||||
}
|
||||
|
||||
if f.Review {
|
||||
if frm.Review {
|
||||
s = s.Where("photos.photo_quality < 3")
|
||||
} else if f.Quality != 0 && f.Private == false {
|
||||
s = s.Where("photos.photo_quality >= ?", f.Quality)
|
||||
} else if frm.Quality != 0 && frm.Private == false {
|
||||
s = s.Where("photos.photo_quality >= ?", frm.Quality)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by camera id or name.
|
||||
if txt.IsPosInt(f.Camera) {
|
||||
s = s.Where("photos.camera_id = ?", txt.UInt(f.Camera))
|
||||
} else if txt.NotEmpty(f.Camera) {
|
||||
v := strings.Trim(f.Camera, "*%") + "%"
|
||||
if txt.IsPosInt(frm.Camera) {
|
||||
s = s.Where("photos.camera_id = ?", txt.UInt(frm.Camera))
|
||||
} else if txt.NotEmpty(frm.Camera) {
|
||||
v := strings.Trim(frm.Camera, "*%") + "%"
|
||||
s = s.Where("cameras.camera_name LIKE ? OR cameras.camera_model LIKE ? OR cameras.camera_slug LIKE ?", v, v, v)
|
||||
}
|
||||
|
||||
// Filter by lens id or name.
|
||||
if txt.IsPosInt(f.Lens) {
|
||||
s = s.Where("photos.lens_id = ?", txt.UInt(f.Lens))
|
||||
} else if txt.NotEmpty(f.Lens) {
|
||||
v := strings.Trim(f.Lens, "*%") + "%"
|
||||
if txt.IsPosInt(frm.Lens) {
|
||||
s = s.Where("photos.lens_id = ?", txt.UInt(frm.Lens))
|
||||
} else if txt.NotEmpty(frm.Lens) {
|
||||
v := strings.Trim(frm.Lens, "*%") + "%"
|
||||
s = s.Where("lenses.lens_name LIKE ? OR lenses.lens_model LIKE ? OR lenses.lens_slug LIKE ?", v, v, v)
|
||||
}
|
||||
|
||||
// Filter by ISO Number (light sensitivity) range.
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(f.Iso, 0, 10000000); rangeErr == nil {
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(frm.Iso, 0, 10000000); rangeErr == nil {
|
||||
s = s.Where("photos.photo_iso >= ? AND photos.photo_iso <= ?", rangeStart, rangeEnd)
|
||||
}
|
||||
|
||||
// Filter by Focal Length (35mm equivalent) range.
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(f.Mm, 0, 10000000); rangeErr == nil {
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(frm.Mm, 0, 10000000); rangeErr == nil {
|
||||
s = s.Where("photos.photo_focal_length >= ? AND photos.photo_focal_length <= ?", rangeStart, rangeEnd)
|
||||
}
|
||||
|
||||
// Filter by Aperture (f-number) range.
|
||||
if rangeStart, rangeEnd, rangeErr := txt.FloatRange(f.F, 0, 10000000); rangeErr == nil {
|
||||
if rangeStart, rangeEnd, rangeErr := txt.FloatRange(frm.F, 0, 10000000); rangeErr == nil {
|
||||
s = s.Where("photos.photo_f_number >= ? AND photos.photo_f_number <= ?", rangeStart-0.01, rangeEnd+0.01)
|
||||
}
|
||||
|
||||
// Filter by year.
|
||||
if f.Year != "" {
|
||||
s = s.Where(AnyInt("photos.photo_year", f.Year, txt.Or, entity.UnknownYear, txt.YearMax))
|
||||
if frm.Year != "" {
|
||||
s = s.Where(AnyInt("photos.photo_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax))
|
||||
}
|
||||
|
||||
// Filter by month.
|
||||
if f.Month != "" {
|
||||
s = s.Where(AnyInt("photos.photo_month", f.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
|
||||
if frm.Month != "" {
|
||||
s = s.Where(AnyInt("photos.photo_month", frm.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
|
||||
}
|
||||
|
||||
// Filter by day.
|
||||
if f.Day != "" {
|
||||
s = s.Where(AnyInt("photos.photo_day", f.Day, txt.Or, entity.UnknownDay, txt.DayMax))
|
||||
if frm.Day != "" {
|
||||
s = s.Where(AnyInt("photos.photo_day", frm.Day, txt.Or, entity.UnknownDay, txt.DayMax))
|
||||
}
|
||||
|
||||
// Filter by Resolution in Megapixels (MP).
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(f.Mp, 0, 32000); rangeErr == nil {
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(frm.Mp, 0, 32000); rangeErr == nil {
|
||||
s = s.Where("photos.photo_resolution >= ? AND photos.photo_resolution <= ?", rangeStart, rangeEnd)
|
||||
}
|
||||
|
||||
// Find panoramic pictures only.
|
||||
if f.Panorama {
|
||||
if frm.Panorama {
|
||||
s = s.Where("photos.photo_panorama = 1")
|
||||
}
|
||||
|
||||
// Find portrait/landscape/square pictures only.
|
||||
if f.Portrait {
|
||||
if frm.Portrait {
|
||||
s = s.Where("files.file_portrait = 1")
|
||||
} else if f.Landscape {
|
||||
} else if frm.Landscape {
|
||||
s = s.Where("files.file_aspect_ratio > 1.25")
|
||||
} else if f.Square {
|
||||
} else if frm.Square {
|
||||
s = s.Where("files.file_aspect_ratio = 1")
|
||||
}
|
||||
|
||||
// Filter by main color.
|
||||
if f.Color != "" {
|
||||
s = s.Where("files.file_main_color IN (?)", SplitOr(strings.ToLower(f.Color)))
|
||||
if frm.Color != "" {
|
||||
s = s.Where("files.file_main_color IN (?)", SplitOr(strings.ToLower(frm.Color)))
|
||||
}
|
||||
|
||||
// Filter by chroma.
|
||||
if f.Mono {
|
||||
if frm.Mono {
|
||||
s = s.Where("files.file_chroma = 0")
|
||||
} else if f.Chroma > 9 {
|
||||
s = s.Where("files.file_chroma > ?", f.Chroma)
|
||||
} else if f.Chroma > 0 {
|
||||
s = s.Where("files.file_chroma > 0 AND files.file_chroma <= ?", f.Chroma)
|
||||
} else if frm.Chroma > 9 {
|
||||
s = s.Where("files.file_chroma > ?", frm.Chroma)
|
||||
} else if frm.Chroma > 0 {
|
||||
s = s.Where("files.file_chroma > 0 AND files.file_chroma <= ?", frm.Chroma)
|
||||
}
|
||||
|
||||
if f.Diff != 0 {
|
||||
s = s.Where("files.file_diff = ?", f.Diff)
|
||||
if frm.Diff != 0 {
|
||||
s = s.Where("files.file_diff = ?", frm.Diff)
|
||||
}
|
||||
|
||||
// Filter by favorite flag.
|
||||
if txt.No(f.Favorite) {
|
||||
if txt.No(frm.Favorite) {
|
||||
s = s.Where("photos.photo_favorite = 0")
|
||||
} else if txt.NotEmpty(f.Favorite) {
|
||||
} else if txt.NotEmpty(frm.Favorite) {
|
||||
s = s.Where("photos.photo_favorite = 1")
|
||||
}
|
||||
|
||||
// Filter by scan flag.
|
||||
if txt.No(f.Scan) {
|
||||
if txt.No(frm.Scan) {
|
||||
s = s.Where("photos.photo_scan = 0")
|
||||
} else if txt.NotEmpty(f.Scan) {
|
||||
} else if txt.NotEmpty(frm.Scan) {
|
||||
s = s.Where("photos.photo_scan = 1")
|
||||
}
|
||||
|
||||
// Filter by stack flag.
|
||||
if f.Stackable {
|
||||
if frm.Stackable {
|
||||
s = s.Where("photos.photo_stack > -1")
|
||||
} else if f.Unstacked {
|
||||
} else if frm.Unstacked {
|
||||
s = s.Where("photos.photo_stack = -1")
|
||||
}
|
||||
|
||||
// Filter by location country.
|
||||
if txt.NotEmpty(f.Country) {
|
||||
s = s.Where("photos.photo_country IN (?)", SplitOr(strings.ToLower(f.Country)))
|
||||
if txt.NotEmpty(frm.Country) {
|
||||
s = s.Where("photos.photo_country IN (?)", SplitOr(strings.ToLower(frm.Country)))
|
||||
}
|
||||
|
||||
// Filter by location state.
|
||||
if txt.NotEmpty(f.State) {
|
||||
s = s.Where("places.place_state IN (?)", SplitOr(f.State))
|
||||
if txt.NotEmpty(frm.State) {
|
||||
s = s.Where("places.place_state IN (?)", SplitOr(frm.State))
|
||||
}
|
||||
|
||||
// Filter by location city.
|
||||
if txt.NotEmpty(f.City) {
|
||||
s = s.Where("places.place_city IN (?)", SplitOr(f.City))
|
||||
if txt.NotEmpty(frm.City) {
|
||||
s = s.Where("places.place_city IN (?)", SplitOr(frm.City))
|
||||
}
|
||||
|
||||
// Filter by location category.
|
||||
if txt.NotEmpty(f.Category) {
|
||||
if txt.NotEmpty(frm.Category) {
|
||||
s = s.Joins("JOIN cells ON photos.cell_id = cells.id").
|
||||
Where("cells.cell_category IN (?)", SplitOr(strings.ToLower(f.Category)))
|
||||
Where("cells.cell_category IN (?)", SplitOr(strings.ToLower(frm.Category)))
|
||||
}
|
||||
|
||||
// Filter by media type.
|
||||
if txt.NotEmpty(f.Type) {
|
||||
s = s.Where("photos.photo_type IN (?)", SplitOr(strings.ToLower(f.Type)))
|
||||
} else if f.Animated {
|
||||
if txt.NotEmpty(frm.Type) {
|
||||
s = s.Where("photos.photo_type IN (?)", SplitOr(strings.ToLower(frm.Type)))
|
||||
} else if frm.Animated {
|
||||
s = s.Where("photos.photo_type = ?", media.Animated)
|
||||
} else if f.Audio {
|
||||
} else if frm.Audio {
|
||||
s = s.Where("photos.photo_type = ?", media.Audio)
|
||||
} else if f.Document {
|
||||
} else if frm.Document {
|
||||
s = s.Where("photos.photo_type = ?", media.Document)
|
||||
} else if f.Image {
|
||||
} else if frm.Image {
|
||||
s = s.Where("photos.photo_type = ?", media.Image)
|
||||
} else if f.Live {
|
||||
} else if frm.Live {
|
||||
s = s.Where("photos.photo_type = ?", media.Live)
|
||||
} else if f.Raw {
|
||||
} else if frm.Raw {
|
||||
s = s.Where("photos.photo_type = ?", media.Raw)
|
||||
} else if f.Vector {
|
||||
} else if frm.Vector {
|
||||
s = s.Where("photos.photo_type = ?", media.Vector)
|
||||
} else if f.Video {
|
||||
} else if frm.Video {
|
||||
s = s.Where("photos.photo_type = ?", media.Video)
|
||||
} else if f.Photo {
|
||||
} else if frm.Photo {
|
||||
s = s.Where("photos.photo_type IN ('image','live','animated','vector','raw')")
|
||||
}
|
||||
|
||||
// Filter by storage path.
|
||||
if txt.NotEmpty(f.Path) {
|
||||
p := f.Path
|
||||
if txt.NotEmpty(frm.Path) {
|
||||
p := frm.Path
|
||||
|
||||
if strings.HasPrefix(p, "/") {
|
||||
p = p[1:]
|
||||
@@ -652,8 +652,8 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Filter by primary file name without path and extension.
|
||||
if txt.NotEmpty(f.Name) {
|
||||
where, names := OrLike("photos.photo_name", f.Name)
|
||||
if txt.NotEmpty(frm.Name) {
|
||||
where, names := OrLike("photos.photo_name", frm.Name)
|
||||
|
||||
// Omit file path and known extensions.
|
||||
for i := range names {
|
||||
@@ -664,114 +664,114 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Filter by complete file names.
|
||||
if txt.NotEmpty(f.Filename) {
|
||||
where, values := OrLike("files.file_name", f.Filename)
|
||||
if txt.NotEmpty(frm.Filename) {
|
||||
where, values := OrLike("files.file_name", frm.Filename)
|
||||
s = s.Where(where, values...)
|
||||
}
|
||||
|
||||
// Filter by original file name.
|
||||
if txt.NotEmpty(f.Original) {
|
||||
where, values := OrLike("photos.original_name", f.Original)
|
||||
if txt.NotEmpty(frm.Original) {
|
||||
where, values := OrLike("photos.original_name", frm.Original)
|
||||
s = s.Where(where, values...)
|
||||
}
|
||||
|
||||
// Filter by title.
|
||||
if txt.NotEmpty(f.Title) {
|
||||
where, values := OrLike("photos.photo_title", f.Title)
|
||||
if txt.NotEmpty(frm.Title) {
|
||||
where, values := OrLike("photos.photo_title", frm.Title)
|
||||
s = s.Where(where, values...)
|
||||
}
|
||||
|
||||
// Filter by hash.
|
||||
if txt.NotEmpty(f.Hash) {
|
||||
s = s.Where("files.file_hash IN (?)", SplitOr(strings.ToLower(f.Hash)))
|
||||
if txt.NotEmpty(frm.Hash) {
|
||||
s = s.Where("files.file_hash IN (?)", SplitOr(strings.ToLower(frm.Hash)))
|
||||
}
|
||||
|
||||
// Filter by location code.
|
||||
if txt.NotEmpty(f.S2) {
|
||||
if txt.NotEmpty(frm.S2) {
|
||||
// S2 Cell ID.
|
||||
s2Min, s2Max := s2.PrefixedRange(f.S2, s2.Level(f.Dist))
|
||||
s2Min, s2Max := s2.PrefixedRange(frm.S2, s2.Level(frm.Dist))
|
||||
s = s.Where("photos.cell_id BETWEEN ? AND ?", s2Min, s2Max)
|
||||
} else if txt.NotEmpty(f.Olc) {
|
||||
} else if txt.NotEmpty(frm.Olc) {
|
||||
// Open Location Code (OLC).
|
||||
s2Min, s2Max := s2.PrefixedRange(pluscode.S2(f.Olc), s2.Level(f.Dist))
|
||||
s2Min, s2Max := s2.PrefixedRange(pluscode.S2(frm.Olc), s2.Level(frm.Dist))
|
||||
s = s.Where("photos.cell_id BETWEEN ? AND ?", s2Min, s2Max)
|
||||
}
|
||||
|
||||
// Filter by GPS Bounds (Lat N, Lng E, Lat S, Lng W).
|
||||
if latN, lngE, latS, lngW, boundsErr := clean.GPSBounds(f.Latlng); boundsErr == nil {
|
||||
if latN, lngE, latS, lngW, boundsErr := clean.GPSBounds(frm.Latlng); boundsErr == nil {
|
||||
s = s.Where("photos.photo_lat BETWEEN ? AND ?", latS, latN)
|
||||
s = s.Where("photos.photo_lng BETWEEN ? AND ?", lngW, lngE)
|
||||
}
|
||||
|
||||
// Filter by GPS Latitude range (from +90 to -90 degrees).
|
||||
if latN, latS, latErr := clean.GPSLatRange(f.Lat, f.Dist); latErr == nil {
|
||||
if latN, latS, latErr := clean.GPSLatRange(frm.Lat, frm.Dist); latErr == nil {
|
||||
s = s.Where("photos.photo_lat BETWEEN ? AND ?", latS, latN)
|
||||
}
|
||||
|
||||
// Filter by GPS Longitude range (from -180 to +180 degrees)
|
||||
if lngE, lngW, lngErr := clean.GPSLngRange(f.Lat, f.Lng, f.Dist); lngErr == nil {
|
||||
if lngE, lngW, lngErr := clean.GPSLngRange(frm.Lat, frm.Lng, frm.Dist); lngErr == nil {
|
||||
s = s.Where("photos.photo_lng BETWEEN ? AND ?", lngW, lngE)
|
||||
}
|
||||
|
||||
// Filter by GPS Altitude (m) range.
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(f.Alt, -6378000, 1000000000); rangeErr == nil {
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(frm.Alt, -6378000, 1000000000); rangeErr == nil {
|
||||
s = s.Where("photos.photo_altitude BETWEEN ? AND ?", rangeStart, rangeEnd)
|
||||
}
|
||||
|
||||
// Find pictures added at or after this time (UTC).
|
||||
if !f.Added.IsZero() {
|
||||
s = s.Where("photos.created_at >= ?", f.Added.UTC().Format("2006-01-02 15:04:05"))
|
||||
if !frm.Added.IsZero() {
|
||||
s = s.Where("photos.created_at >= ?", frm.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.UTC().Format("2006-01-02 15:04:05"))
|
||||
if !frm.Updated.IsZero() {
|
||||
s = s.Where("photos.updated_at >= ?", frm.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"))
|
||||
if !frm.Edited.IsZero() {
|
||||
s = s.Where("photos.edited_at >= ?", frm.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.UTC().Format("2006-01-02"))
|
||||
if !frm.Taken.IsZero() {
|
||||
s = s.Where("DATE(photos.taken_at) = DATE(?)", frm.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.UTC().Format("2006-01-02"))
|
||||
if !frm.Before.IsZero() {
|
||||
s = s.Where("photos.taken_at <= ?", frm.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.UTC().Format("2006-01-02"))
|
||||
if !frm.After.IsZero() {
|
||||
s = s.Where("photos.taken_at >= ?", frm.After.UTC().Format("2006-01-02"))
|
||||
}
|
||||
|
||||
// Find stacks only.
|
||||
if f.Stack {
|
||||
if frm.Stack {
|
||||
s = s.Where("photos.id IN (SELECT a.photo_id FROM files a JOIN files b ON a.id != b.id AND a.photo_id = b.photo_id AND a.file_type = b.file_type WHERE a.file_type='jpg')")
|
||||
}
|
||||
|
||||
// Find photos in albums or not in an album, unless search results are limited to a scope.
|
||||
if f.Scope == "" {
|
||||
if f.Unsorted {
|
||||
if frm.Scope == "" {
|
||||
if frm.Unsorted {
|
||||
s = s.Where("photos.photo_uid NOT IN (SELECT photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid WHERE pa.hidden = 0 AND a.deleted_at IS NULL)")
|
||||
} else if txt.NotEmpty(f.Album) {
|
||||
v := strings.Trim(f.Album, "*%") + "%"
|
||||
} else if txt.NotEmpty(frm.Album) {
|
||||
v := strings.Trim(frm.Album, "*%") + "%"
|
||||
s = s.Where("photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (a.album_title LIKE ? OR a.album_slug LIKE ?))", v, v)
|
||||
} else if txt.NotEmpty(f.Albums) {
|
||||
for _, where := range LikeAnyWord("a.album_title", f.Albums) {
|
||||
} else if txt.NotEmpty(frm.Albums) {
|
||||
for _, where := range LikeAnyWord("a.album_title", frm.Albums) {
|
||||
s = s.Where("photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (?))", gorm.Expr(where))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Limit offset and count.
|
||||
if f.Count > 0 && f.Count <= MaxResults {
|
||||
s = s.Limit(f.Count).Offset(f.Offset)
|
||||
if frm.Count > 0 && frm.Count <= MaxResults {
|
||||
s = s.Limit(frm.Count).Offset(frm.Offset)
|
||||
} else {
|
||||
s = s.Limit(MaxResults).Offset(f.Offset)
|
||||
s = s.Limit(MaxResults).Offset(frm.Offset)
|
||||
}
|
||||
|
||||
// Query database.
|
||||
@@ -780,10 +780,10 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
||||
}
|
||||
|
||||
// Log number of results.
|
||||
log.Debugf("photos: found %s for %s [%s]", english.Plural(len(results), "result", "results"), f.SerializeAll(), time.Since(start))
|
||||
log.Debugf("photos: found %s for %s [%s]", english.Plural(len(results), "result", "results"), frm.SerializeAll(), time.Since(start))
|
||||
|
||||
// Merge files that belong to the same photo.
|
||||
if f.Merged {
|
||||
if frm.Merged {
|
||||
// Return merged files.
|
||||
return results.Merge()
|
||||
}
|
||||
|
||||
@@ -28,44 +28,44 @@ import (
|
||||
var GeoCols = SelectString(GeoResult{}, []string{"*"})
|
||||
|
||||
// PhotosGeo finds GeoResults based on the search form without checking rights or permissions.
|
||||
func PhotosGeo(f form.SearchPhotosGeo) (results GeoResults, err error) {
|
||||
return UserPhotosGeo(f, nil)
|
||||
func PhotosGeo(frm form.SearchPhotosGeo) (results GeoResults, err error) {
|
||||
return UserPhotosGeo(frm, nil)
|
||||
}
|
||||
|
||||
// UserPhotosGeo finds photos based on the search form and user session then returns them as GeoResults.
|
||||
func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoResults, err error) {
|
||||
func UserPhotosGeo(frm form.SearchPhotosGeo, sess *entity.Session) (results GeoResults, err error) {
|
||||
start := time.Now()
|
||||
|
||||
// Parse query string and filter.
|
||||
if err = f.ParseQueryString(); err != nil {
|
||||
if err = frm.ParseQueryString(); err != nil {
|
||||
log.Debugf("search: %s", err)
|
||||
return GeoResults{}, ErrBadRequest
|
||||
}
|
||||
|
||||
// Find photos near another?
|
||||
if txt.NotEmpty(f.Near) {
|
||||
if txt.NotEmpty(frm.Near) {
|
||||
photo := Photo{}
|
||||
|
||||
// Find a nearby picture using the UID or return an empty result otherwise.
|
||||
if err = Db().First(&photo, "photo_uid = ?", f.Near).Error; err != nil {
|
||||
if err = Db().First(&photo, "photo_uid = ?", frm.Near).Error; err != nil {
|
||||
log.Debugf("search: %s (find nearby)", err)
|
||||
return GeoResults{}, ErrNotFound
|
||||
}
|
||||
|
||||
// Set the S2 Cell ID to search for.
|
||||
f.S2 = photo.CellID
|
||||
frm.S2 = photo.CellID
|
||||
|
||||
// Set the search distance if unspecified.
|
||||
if f.Dist <= 0 {
|
||||
f.Dist = geo.DefaultDist
|
||||
if frm.Dist <= 0 {
|
||||
frm.Dist = geo.DefaultDist
|
||||
}
|
||||
}
|
||||
|
||||
// Set default search distance.
|
||||
if f.Dist <= 0 {
|
||||
f.Dist = geo.DefaultDist
|
||||
} else if f.Dist > geo.DistLimit {
|
||||
f.Dist = geo.DistLimit
|
||||
if frm.Dist <= 0 {
|
||||
frm.Dist = geo.DefaultDist
|
||||
} else if frm.Dist > geo.DistLimit {
|
||||
frm.Dist = geo.DistLimit
|
||||
}
|
||||
|
||||
// Specify table names and joins.
|
||||
@@ -76,41 +76,41 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
Where("photos.photo_lat <> 0")
|
||||
|
||||
// Accept the album UID as scope for backward compatibility.
|
||||
if rnd.IsUID(f.Album, entity.AlbumUID) {
|
||||
if txt.Empty(f.Scope) {
|
||||
f.Scope = f.Album
|
||||
if rnd.IsUID(frm.Album, entity.AlbumUID) {
|
||||
if txt.Empty(frm.Scope) {
|
||||
frm.Scope = frm.Album
|
||||
}
|
||||
|
||||
f.Album = ""
|
||||
frm.Album = ""
|
||||
}
|
||||
|
||||
// Limit search results to a specific UID scope, e.g. when sharing.
|
||||
if txt.NotEmpty(f.Scope) {
|
||||
f.Scope = strings.ToLower(f.Scope)
|
||||
if txt.NotEmpty(frm.Scope) {
|
||||
frm.Scope = strings.ToLower(frm.Scope)
|
||||
|
||||
if idType, idPrefix := rnd.IdType(f.Scope); idType != rnd.TypeUID || idPrefix != entity.AlbumUID {
|
||||
if idType, idPrefix := rnd.IdType(frm.Scope); idType != rnd.TypeUID || idPrefix != entity.AlbumUID {
|
||||
return GeoResults{}, ErrInvalidId
|
||||
} else if a, err := entity.CachedAlbumByUID(f.Scope); err != nil || a.AlbumUID == "" {
|
||||
} else if a, err := entity.CachedAlbumByUID(frm.Scope); err != nil || a.AlbumUID == "" {
|
||||
return GeoResults{}, ErrInvalidId
|
||||
} else if a.AlbumFilter == "" {
|
||||
s = s.Joins("JOIN photos_albums ON photos_albums.photo_uid = files.photo_uid").
|
||||
Where("photos_albums.hidden = 0 AND photos_albums.album_uid = ?", a.AlbumUID)
|
||||
} else if formErr := form.Unserialize(&f, a.AlbumFilter); formErr != nil {
|
||||
} else if formErr := form.Unserialize(&frm, a.AlbumFilter); formErr != nil {
|
||||
log.Debugf("search: %s (%s)", clean.Error(formErr), clean.Log(a.AlbumFilter))
|
||||
return GeoResults{}, ErrBadFilter
|
||||
} else {
|
||||
f.Filter = a.AlbumFilter
|
||||
frm.Filter = a.AlbumFilter
|
||||
s = s.Where("files.photo_uid NOT IN (SELECT photo_uid FROM photos_albums pa WHERE pa.hidden = 1 AND pa.album_uid = ?)", a.AlbumUID)
|
||||
}
|
||||
|
||||
// Enforce search distance range (km).
|
||||
if f.Dist <= 0 {
|
||||
f.Dist = geo.DefaultDist
|
||||
} else if f.Dist > geo.ScopeDistLimit {
|
||||
f.Dist = geo.ScopeDistLimit
|
||||
if frm.Dist <= 0 {
|
||||
frm.Dist = geo.DefaultDist
|
||||
} else if frm.Dist > geo.ScopeDistLimit {
|
||||
frm.Dist = geo.ScopeDistLimit
|
||||
}
|
||||
} else {
|
||||
f.Scope = ""
|
||||
frm.Scope = ""
|
||||
}
|
||||
|
||||
// Check session permissions and apply as needed.
|
||||
@@ -120,25 +120,25 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
|
||||
// Exclude private content.
|
||||
if acl.Rules.Deny(acl.ResourcePlaces, aclRole, acl.AccessPrivate) {
|
||||
f.Public = true
|
||||
f.Private = false
|
||||
frm.Public = true
|
||||
frm.Private = false
|
||||
}
|
||||
|
||||
// Exclude archived content.
|
||||
if acl.Rules.Deny(acl.ResourcePlaces, aclRole, acl.ActionDelete) {
|
||||
f.Archived = false
|
||||
f.Review = false
|
||||
frm.Archived = false
|
||||
frm.Review = false
|
||||
}
|
||||
|
||||
// Visitors and other restricted users can only access shared content.
|
||||
if f.Scope != "" && !sess.HasShare(f.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePlaces) || sess.NotRegistered()) ||
|
||||
f.Scope == "" && acl.Rules.Deny(acl.ResourcePlaces, aclRole, acl.ActionSearch) {
|
||||
if frm.Scope != "" && !sess.HasShare(frm.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePlaces) || sess.NotRegistered()) ||
|
||||
frm.Scope == "" && acl.Rules.Deny(acl.ResourcePlaces, aclRole, acl.ActionSearch) {
|
||||
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", authn.Denied}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePlaces), aclRole)
|
||||
return GeoResults{}, ErrForbidden
|
||||
}
|
||||
|
||||
// Limit results for external users.
|
||||
if f.Scope == "" && acl.Rules.DenyAll(acl.ResourcePlaces, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
||||
if frm.Scope == "" && acl.Rules.DenyAll(acl.ResourcePlaces, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
||||
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() {
|
||||
@@ -153,16 +153,16 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
}
|
||||
|
||||
// Set sort order.
|
||||
if f.Near == "" {
|
||||
if frm.Near == "" {
|
||||
s = s.Order("taken_at, photos.photo_uid")
|
||||
} else {
|
||||
// Sort by distance to UID.
|
||||
s = s.Order(gorm.Expr("(photos.photo_uid = ?) DESC, ABS(? - photos.photo_lat)+ABS(? - photos.photo_lng)", f.Near, f.Lat, f.Lng))
|
||||
s = s.Order(gorm.Expr("(photos.photo_uid = ?) DESC, ABS(? - photos.photo_lat)+ABS(? - photos.photo_lng)", frm.Near, frm.Lat, frm.Lng))
|
||||
}
|
||||
|
||||
// Find specific UIDs only.
|
||||
if txt.NotEmpty(f.UID) {
|
||||
ids := SplitOr(strings.ToLower(f.UID))
|
||||
if txt.NotEmpty(frm.UID) {
|
||||
ids := SplitOr(strings.ToLower(frm.UID))
|
||||
idType, prefix := rnd.ContainsType(ids)
|
||||
|
||||
if idType == rnd.TypeUnknown {
|
||||
@@ -181,21 +181,21 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
}
|
||||
|
||||
// Find UIDs only to improve performance.
|
||||
if sess == nil && f.FindUidOnly() {
|
||||
if sess == nil && frm.FindUidOnly() {
|
||||
// Fetch results.
|
||||
if result := s.Scan(&results); result.Error != nil {
|
||||
return results, result.Error
|
||||
}
|
||||
|
||||
log.Debugf("places: found %s for %s [%s]", english.Plural(len(results), "result", "results"), f.SerializeAll(), time.Since(start))
|
||||
log.Debugf("places: found %s for %s [%s]", english.Plural(len(results), "result", "results"), frm.SerializeAll(), time.Since(start))
|
||||
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Find Unique Image ID (Exif), Document ID, or Instance ID (XMP).
|
||||
if txt.NotEmpty(f.ID) {
|
||||
for _, id := range SplitAnd(strings.ToLower(f.ID)) {
|
||||
if txt.NotEmpty(frm.ID) {
|
||||
for _, id := range SplitAnd(strings.ToLower(frm.ID)) {
|
||||
if ids := SplitOr(id); len(ids) > 0 {
|
||||
s = s.Where("files.instance_id IN (?) OR photos.uuid IN (?)", ids, ids)
|
||||
}
|
||||
@@ -206,9 +206,9 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
var categories []entity.Category
|
||||
var labels []entity.Label
|
||||
var labelIds []uint
|
||||
if txt.NotEmpty(f.Label) {
|
||||
if labelErr := Db().Where(AnySlug("label_slug", f.Label, txt.Or)).Or(AnySlug("custom_slug", f.Label, txt.Or)).Find(&labels).Error; len(labels) == 0 || labelErr != nil {
|
||||
log.Debugf("search: label %s not found", txt.LogParamLower(f.Label))
|
||||
if txt.NotEmpty(frm.Label) {
|
||||
if labelErr := Db().Where(AnySlug("label_slug", frm.Label, txt.Or)).Or(AnySlug("custom_slug", frm.Label, txt.Or)).Find(&labels).Error; len(labels) == 0 || labelErr != nil {
|
||||
log.Debugf("search: label %s not found", txt.LogParamLower(frm.Label))
|
||||
return GeoResults{}, nil
|
||||
} else {
|
||||
for _, l := range labels {
|
||||
@@ -228,77 +228,77 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
}
|
||||
|
||||
// Set search filters based on search terms.
|
||||
if terms := txt.SearchTerms(f.Query); f.Query != "" && len(terms) == 0 {
|
||||
if f.Title == "" {
|
||||
f.Title = fmt.Sprintf("%s*", strings.Trim(f.Query, "%*"))
|
||||
f.Query = ""
|
||||
if terms := txt.SearchTerms(frm.Query); frm.Query != "" && len(terms) == 0 {
|
||||
if frm.Title == "" {
|
||||
frm.Title = fmt.Sprintf("%s*", strings.Trim(frm.Query, "%*"))
|
||||
frm.Query = ""
|
||||
}
|
||||
} else if len(terms) > 0 {
|
||||
switch {
|
||||
case terms["faces"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "faces", "")
|
||||
f.Faces = "true"
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "faces", "")
|
||||
frm.Faces = "true"
|
||||
case terms["people"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "people", "")
|
||||
f.Faces = "true"
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "people", "")
|
||||
frm.Faces = "true"
|
||||
case terms["videos"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "videos", "")
|
||||
f.Video = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "videos", "")
|
||||
frm.Video = true
|
||||
case terms["video"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "video", "")
|
||||
f.Video = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "video", "")
|
||||
frm.Video = true
|
||||
case terms["vectors"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "vectors", "")
|
||||
f.Vector = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "vectors", "")
|
||||
frm.Vector = true
|
||||
case terms["vector"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "vector", "")
|
||||
f.Vector = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "vector", "")
|
||||
frm.Vector = true
|
||||
case terms["animated"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "animated", "")
|
||||
f.Animated = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "animated", "")
|
||||
frm.Animated = true
|
||||
case terms["gifs"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "gifs", "")
|
||||
f.Animated = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "gifs", "")
|
||||
frm.Animated = true
|
||||
case terms["gif"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "gif", "")
|
||||
f.Animated = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "gif", "")
|
||||
frm.Animated = true
|
||||
case terms["live"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "live", "")
|
||||
f.Live = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "live", "")
|
||||
frm.Live = true
|
||||
case terms["raws"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "raws", "")
|
||||
f.Raw = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "raws", "")
|
||||
frm.Raw = true
|
||||
case terms["raw"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "raw", "")
|
||||
f.Raw = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "raw", "")
|
||||
frm.Raw = true
|
||||
case terms["favorites"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "favorites", "")
|
||||
f.Favorite = "true"
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "favorites", "")
|
||||
frm.Favorite = "true"
|
||||
case terms["panoramas"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "panoramas", "")
|
||||
f.Panorama = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "panoramas", "")
|
||||
frm.Panorama = true
|
||||
case terms["scans"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "scans", "")
|
||||
f.Scan = "true"
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "scans", "")
|
||||
frm.Scan = "true"
|
||||
case terms["monochrome"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "monochrome", "")
|
||||
f.Mono = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "monochrome", "")
|
||||
frm.Mono = true
|
||||
case terms["mono"]:
|
||||
f.Query = strings.ReplaceAll(f.Query, "mono", "")
|
||||
f.Mono = true
|
||||
frm.Query = strings.ReplaceAll(frm.Query, "mono", "")
|
||||
frm.Mono = true
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by label, label category, and keywords.
|
||||
if f.Query != "" {
|
||||
if frm.Query != "" {
|
||||
var categories []entity.Category
|
||||
var labels []entity.Label
|
||||
var labelIds []uint
|
||||
|
||||
if err := Db().Where(AnySlug("custom_slug", f.Query, " ")).Find(&labels).Error; len(labels) == 0 || err != nil {
|
||||
log.Tracef("search: label %s not found, using fuzzy search", txt.LogParamLower(f.Query))
|
||||
if err := Db().Where(AnySlug("custom_slug", frm.Query, " ")).Find(&labels).Error; len(labels) == 0 || err != nil {
|
||||
log.Tracef("search: label %s not found, using fuzzy search", txt.LogParamLower(frm.Query))
|
||||
|
||||
for _, where := range LikeAnyKeyword("k.keyword", f.Query) {
|
||||
for _, where := range LikeAnyKeyword("k.keyword", frm.Query) {
|
||||
s = s.Where("photos.id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where))
|
||||
}
|
||||
} else {
|
||||
@@ -313,7 +313,7 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
}
|
||||
}
|
||||
|
||||
if wheres := LikeAnyKeyword("k.keyword", f.Query); len(wheres) > 0 {
|
||||
if wheres := LikeAnyKeyword("k.keyword", frm.Query); len(wheres) > 0 {
|
||||
for _, where := range wheres {
|
||||
s = s.Where("photos.id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?)) OR "+
|
||||
"photos.id IN (SELECT pl.photo_id FROM photos_labels pl WHERE pl.uncertainty < 100 AND pl.label_id IN (?))", gorm.Expr(where), labelIds)
|
||||
@@ -325,51 +325,51 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
}
|
||||
|
||||
// Search for one or more keywords.
|
||||
if f.Keywords != "" {
|
||||
for _, where := range LikeAnyWord("k.keyword", f.Keywords) {
|
||||
if frm.Keywords != "" {
|
||||
for _, where := range LikeAnyWord("k.keyword", frm.Keywords) {
|
||||
s = s.Where("photos.id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where))
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by number of faces.
|
||||
if f.Faces == "" {
|
||||
if frm.Faces == "" {
|
||||
// Do nothing.
|
||||
} else if txt.IsUInt(f.Faces) {
|
||||
s = s.Where("photos.photo_faces >= ?", txt.Int(f.Faces))
|
||||
} else if txt.New(f.Faces) && f.Face == "" {
|
||||
f.Face = f.Faces
|
||||
f.Faces = ""
|
||||
} else if txt.Yes(f.Faces) {
|
||||
} else if txt.IsUInt(frm.Faces) {
|
||||
s = s.Where("photos.photo_faces >= ?", txt.Int(frm.Faces))
|
||||
} else if txt.New(frm.Faces) && frm.Face == "" {
|
||||
frm.Face = frm.Faces
|
||||
frm.Faces = ""
|
||||
} else if txt.Yes(frm.Faces) {
|
||||
s = s.Where("photos.photo_faces > 0")
|
||||
} else if txt.No(f.Faces) {
|
||||
} else if txt.No(frm.Faces) {
|
||||
s = s.Where("photos.photo_faces = 0")
|
||||
}
|
||||
|
||||
// Filter for specific face clusters? Example: PLJ7A3G4MBGZJRMVDIUCBLC46IAP4N7O
|
||||
if f.Face == "" {
|
||||
if frm.Face == "" {
|
||||
// Do nothing.
|
||||
} else if len(f.Face) >= 32 {
|
||||
for _, f := range SplitAnd(strings.ToUpper(f.Face)) {
|
||||
} else if len(frm.Face) >= 32 {
|
||||
for _, f := range SplitAnd(strings.ToUpper(frm.Face)) {
|
||||
s = s.Where(fmt.Sprintf("photos.id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 WHERE face_id IN (?))",
|
||||
entity.Marker{}.TableName()), SplitOr(f))
|
||||
}
|
||||
} else if txt.New(f.Face) {
|
||||
} else if txt.New(frm.Face) {
|
||||
s = s.Where(fmt.Sprintf("photos.id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? WHERE subj_uid IS NULL OR subj_uid = '')",
|
||||
entity.Marker{}.TableName()), entity.MarkerFace)
|
||||
} else if txt.No(f.Face) {
|
||||
} else if txt.No(frm.Face) {
|
||||
s = s.Where(fmt.Sprintf("photos.id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? WHERE face_id IS NULL OR face_id = '')",
|
||||
entity.Marker{}.TableName()), entity.MarkerFace)
|
||||
} else if txt.Yes(f.Face) {
|
||||
} else if txt.Yes(frm.Face) {
|
||||
s = s.Where(fmt.Sprintf("photos.id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? WHERE face_id IS NOT NULL AND face_id <> '')",
|
||||
entity.Marker{}.TableName()), entity.MarkerFace)
|
||||
} else if txt.IsUInt(f.Face) {
|
||||
} else if txt.IsUInt(frm.Face) {
|
||||
s = s.Where("files.photo_id IN (SELECT photo_id FROM files f JOIN markers m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? JOIN faces ON faces.id = m.face_id WHERE m.face_id IS NOT NULL AND m.face_id <> '' AND faces.face_kind = ?)",
|
||||
entity.MarkerFace, txt.Int(f.Face))
|
||||
entity.MarkerFace, txt.Int(frm.Face))
|
||||
}
|
||||
|
||||
// Filter for one or more subjects.
|
||||
if f.Subject != "" {
|
||||
for _, subj := range SplitAnd(strings.ToLower(f.Subject)) {
|
||||
if frm.Subject != "" {
|
||||
for _, subj := range SplitAnd(strings.ToLower(frm.Subject)) {
|
||||
if subjects := SplitOr(subj); rnd.ContainsUID(subjects, 'j') {
|
||||
s = s.Where(fmt.Sprintf("photos.id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 WHERE subj_uid IN (?))",
|
||||
entity.Marker{}.TableName()), subjects)
|
||||
@@ -378,161 +378,161 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(AnySlug("s.subj_slug", subj, txt.Or)))
|
||||
}
|
||||
}
|
||||
} else if f.Subjects != "" {
|
||||
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, f.Subjects) {
|
||||
} else if frm.Subjects != "" {
|
||||
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, frm.Subjects) {
|
||||
s = s.Where(fmt.Sprintf("photos.id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 JOIN %s s ON s.subj_uid = m.subj_uid WHERE (?))",
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(where))
|
||||
}
|
||||
}
|
||||
|
||||
// Find photos in albums or not in an album, unless search results are limited to a scope.
|
||||
if f.Scope == "" {
|
||||
if f.Unsorted {
|
||||
if frm.Scope == "" {
|
||||
if frm.Unsorted {
|
||||
s = s.Where("photos.photo_uid NOT IN (SELECT photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid WHERE pa.hidden = 0 AND a.deleted_at IS NULL)")
|
||||
} else if txt.NotEmpty(f.Album) {
|
||||
v := strings.Trim(f.Album, "*%") + "%"
|
||||
} else if txt.NotEmpty(frm.Album) {
|
||||
v := strings.Trim(frm.Album, "*%") + "%"
|
||||
s = s.Where("photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (a.album_title LIKE ? OR a.album_slug LIKE ?))", v, v)
|
||||
} else if txt.NotEmpty(f.Albums) {
|
||||
for _, where := range LikeAnyWord("a.album_title", f.Albums) {
|
||||
} else if txt.NotEmpty(frm.Albums) {
|
||||
for _, where := range LikeAnyWord("a.album_title", frm.Albums) {
|
||||
s = s.Where("photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (?))", gorm.Expr(where))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by camera.
|
||||
if f.Camera > 0 {
|
||||
s = s.Where("photos.camera_id = ?", f.Camera)
|
||||
if frm.Camera > 0 {
|
||||
s = s.Where("photos.camera_id = ?", frm.Camera)
|
||||
}
|
||||
|
||||
// Filter by camera lens.
|
||||
if f.Lens > 0 {
|
||||
s = s.Where("photos.lens_id = ?", f.Lens)
|
||||
if frm.Lens > 0 {
|
||||
s = s.Where("photos.lens_id = ?", frm.Lens)
|
||||
}
|
||||
|
||||
// Filter by ISO Number (light sensitivity) range.
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(f.Iso, 0, 10000000); rangeErr == nil {
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(frm.Iso, 0, 10000000); rangeErr == nil {
|
||||
s = s.Where("photos.photo_iso >= ? AND photos.photo_iso <= ?", rangeStart, rangeEnd)
|
||||
}
|
||||
|
||||
// Filter by Focal Length (35mm equivalent) range.
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(f.Mm, 0, 10000000); rangeErr == nil {
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(frm.Mm, 0, 10000000); rangeErr == nil {
|
||||
s = s.Where("photos.photo_focal_length >= ? AND photos.photo_focal_length <= ?", rangeStart, rangeEnd)
|
||||
}
|
||||
|
||||
// Filter by Aperture (f-number) range.
|
||||
if rangeStart, rangeEnd, rangeErr := txt.FloatRange(f.F, 0, 10000000); rangeErr == nil {
|
||||
if rangeStart, rangeEnd, rangeErr := txt.FloatRange(frm.F, 0, 10000000); rangeErr == nil {
|
||||
s = s.Where("photos.photo_f_number >= ? AND photos.photo_f_number <= ?", rangeStart-0.01, rangeEnd+0.01)
|
||||
}
|
||||
|
||||
// Filter by year.
|
||||
if f.Year != "" {
|
||||
s = s.Where(AnyInt("photos.photo_year", f.Year, txt.Or, entity.UnknownYear, txt.YearMax))
|
||||
if frm.Year != "" {
|
||||
s = s.Where(AnyInt("photos.photo_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax))
|
||||
}
|
||||
|
||||
// Filter by month.
|
||||
if f.Month != "" {
|
||||
s = s.Where(AnyInt("photos.photo_month", f.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
|
||||
if frm.Month != "" {
|
||||
s = s.Where(AnyInt("photos.photo_month", frm.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
|
||||
}
|
||||
|
||||
// Filter by day.
|
||||
if f.Day != "" {
|
||||
s = s.Where(AnyInt("photos.photo_day", f.Day, txt.Or, entity.UnknownDay, txt.DayMax))
|
||||
if frm.Day != "" {
|
||||
s = s.Where(AnyInt("photos.photo_day", frm.Day, txt.Or, entity.UnknownDay, txt.DayMax))
|
||||
}
|
||||
|
||||
// Filter by Resolution in Megapixels (MP).
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(f.Mp, 0, 32000); rangeErr == nil {
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(frm.Mp, 0, 32000); rangeErr == nil {
|
||||
s = s.Where("photos.photo_resolution >= ? AND photos.photo_resolution <= ?", rangeStart, rangeEnd)
|
||||
}
|
||||
|
||||
// Find panoramic pictures only.
|
||||
if f.Panorama {
|
||||
if frm.Panorama {
|
||||
s = s.Where("photos.photo_panorama = 1")
|
||||
}
|
||||
|
||||
// Find portrait/landscape/square pictures only.
|
||||
if f.Portrait {
|
||||
if frm.Portrait {
|
||||
s = s.Where("files.file_portrait = 1")
|
||||
} else if f.Landscape {
|
||||
} else if frm.Landscape {
|
||||
s = s.Where("files.file_aspect_ratio > 1.25")
|
||||
} else if f.Square {
|
||||
} else if frm.Square {
|
||||
s = s.Where("files.file_aspect_ratio = 1")
|
||||
}
|
||||
|
||||
// Filter by main color.
|
||||
if f.Color != "" {
|
||||
s = s.Where("files.file_main_color IN (?)", SplitOr(strings.ToLower(f.Color)))
|
||||
if frm.Color != "" {
|
||||
s = s.Where("files.file_main_color IN (?)", SplitOr(strings.ToLower(frm.Color)))
|
||||
}
|
||||
|
||||
// Filter by chroma.
|
||||
if f.Mono {
|
||||
if frm.Mono {
|
||||
s = s.Where("files.file_chroma = 0")
|
||||
} else if f.Chroma > 9 {
|
||||
s = s.Where("files.file_chroma > ?", f.Chroma)
|
||||
} else if f.Chroma > 0 {
|
||||
s = s.Where("files.file_chroma > 0 AND files.file_chroma <= ?", f.Chroma)
|
||||
} else if frm.Chroma > 9 {
|
||||
s = s.Where("files.file_chroma > ?", frm.Chroma)
|
||||
} else if frm.Chroma > 0 {
|
||||
s = s.Where("files.file_chroma > 0 AND files.file_chroma <= ?", frm.Chroma)
|
||||
}
|
||||
|
||||
// Filter by favorite flag.
|
||||
if txt.No(f.Favorite) {
|
||||
if txt.No(frm.Favorite) {
|
||||
s = s.Where("photos.photo_favorite = 0")
|
||||
} else if txt.NotEmpty(f.Favorite) {
|
||||
} else if txt.NotEmpty(frm.Favorite) {
|
||||
s = s.Where("photos.photo_favorite = 1")
|
||||
}
|
||||
|
||||
// Filter by scan flag.
|
||||
if txt.No(f.Scan) {
|
||||
if txt.No(frm.Scan) {
|
||||
s = s.Where("photos.photo_scan = 0")
|
||||
} else if txt.NotEmpty(f.Scan) {
|
||||
} else if txt.NotEmpty(frm.Scan) {
|
||||
s = s.Where("photos.photo_scan = 1")
|
||||
}
|
||||
|
||||
// Filter by location country.
|
||||
if f.Country != "" {
|
||||
s = s.Where("photos.photo_country IN (?)", SplitOr(strings.ToLower(f.Country)))
|
||||
if frm.Country != "" {
|
||||
s = s.Where("photos.photo_country IN (?)", SplitOr(strings.ToLower(frm.Country)))
|
||||
}
|
||||
|
||||
// Filter by location state.
|
||||
if txt.NotEmpty(f.State) {
|
||||
s = s.Where("places.place_state IN (?)", SplitOr(f.State))
|
||||
if txt.NotEmpty(frm.State) {
|
||||
s = s.Where("places.place_state IN (?)", SplitOr(frm.State))
|
||||
}
|
||||
|
||||
// Filter by location city.
|
||||
if txt.NotEmpty(f.City) {
|
||||
s = s.Where("places.place_city IN (?)", SplitOr(f.City))
|
||||
if txt.NotEmpty(frm.City) {
|
||||
s = s.Where("places.place_city IN (?)", SplitOr(frm.City))
|
||||
}
|
||||
|
||||
// Filter by location category.
|
||||
if txt.NotEmpty(f.Category) {
|
||||
if txt.NotEmpty(frm.Category) {
|
||||
s = s.Joins("JOIN cells ON photos.cell_id = cells.id").
|
||||
Where("cells.cell_category IN (?)", SplitOr(strings.ToLower(f.Category)))
|
||||
Where("cells.cell_category IN (?)", SplitOr(strings.ToLower(frm.Category)))
|
||||
}
|
||||
|
||||
// Filter by media type.
|
||||
if txt.NotEmpty(f.Type) {
|
||||
s = s.Where("photos.photo_type IN (?)", SplitOr(strings.ToLower(f.Type)))
|
||||
} else if f.Animated {
|
||||
if txt.NotEmpty(frm.Type) {
|
||||
s = s.Where("photos.photo_type IN (?)", SplitOr(strings.ToLower(frm.Type)))
|
||||
} else if frm.Animated {
|
||||
s = s.Where("photos.photo_type = ?", media.Animated)
|
||||
} else if f.Audio {
|
||||
} else if frm.Audio {
|
||||
s = s.Where("photos.photo_type = ?", media.Audio)
|
||||
} else if f.Document {
|
||||
} else if frm.Document {
|
||||
s = s.Where("photos.photo_type = ?", media.Document)
|
||||
} else if f.Image {
|
||||
} else if frm.Image {
|
||||
s = s.Where("photos.photo_type = ?", media.Image)
|
||||
} else if f.Live {
|
||||
} else if frm.Live {
|
||||
s = s.Where("photos.photo_type = ?", media.Live)
|
||||
} else if f.Raw {
|
||||
} else if frm.Raw {
|
||||
s = s.Where("photos.photo_type = ?", media.Raw)
|
||||
} else if f.Vector {
|
||||
} else if frm.Vector {
|
||||
s = s.Where("photos.photo_type = ?", media.Vector)
|
||||
} else if f.Video {
|
||||
} else if frm.Video {
|
||||
s = s.Where("photos.photo_type = ?", media.Video)
|
||||
} else if f.Photo {
|
||||
} else if frm.Photo {
|
||||
s = s.Where("photos.photo_type IN ('image','raw','live','animated')")
|
||||
}
|
||||
|
||||
// Filter by storage path.
|
||||
if f.Path != "" {
|
||||
p := f.Path
|
||||
if frm.Path != "" {
|
||||
p := frm.Path
|
||||
|
||||
if strings.HasPrefix(p, "/") {
|
||||
p = p[1:]
|
||||
@@ -547,8 +547,8 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
}
|
||||
|
||||
// Filter by primary file name without path and extension.
|
||||
if f.Name != "" {
|
||||
where, names := OrLike("photos.photo_name", f.Name)
|
||||
if frm.Name != "" {
|
||||
where, names := OrLike("photos.photo_name", frm.Name)
|
||||
|
||||
// Omit file path and known extensions.
|
||||
for i := range names {
|
||||
@@ -559,98 +559,98 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
}
|
||||
|
||||
// Filter by title.
|
||||
if f.Title != "" {
|
||||
where, values := OrLike("photos.photo_title", f.Title)
|
||||
if frm.Title != "" {
|
||||
where, values := OrLike("photos.photo_title", frm.Title)
|
||||
s = s.Where(where, values...)
|
||||
}
|
||||
|
||||
// Filter by status.
|
||||
if f.Archived {
|
||||
if frm.Archived {
|
||||
s = s.Where("photos.photo_quality > -1")
|
||||
s = s.Where("photos.deleted_at IS NOT NULL")
|
||||
} else {
|
||||
s = s.Where("photos.deleted_at IS NULL")
|
||||
|
||||
if f.Private {
|
||||
if frm.Private {
|
||||
s = s.Where("photos.photo_private = 1")
|
||||
} else if f.Public {
|
||||
} else if frm.Public {
|
||||
s = s.Where("photos.photo_private = 0")
|
||||
}
|
||||
|
||||
if f.Review {
|
||||
if frm.Review {
|
||||
s = s.Where("photos.photo_quality < 3")
|
||||
} else if f.Quality != 0 && f.Private == false {
|
||||
s = s.Where("photos.photo_quality >= ?", f.Quality)
|
||||
} else if frm.Quality != 0 && frm.Private == false {
|
||||
s = s.Where("photos.photo_quality >= ?", frm.Quality)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by location code.
|
||||
if txt.NotEmpty(f.S2) {
|
||||
if txt.NotEmpty(frm.S2) {
|
||||
// S2 Cell ID.
|
||||
s2Min, s2Max := s2.PrefixedRange(f.S2, s2.Level(f.Dist))
|
||||
s2Min, s2Max := s2.PrefixedRange(frm.S2, s2.Level(frm.Dist))
|
||||
s = s.Where("photos.cell_id BETWEEN ? AND ?", s2Min, s2Max)
|
||||
} else if txt.NotEmpty(f.Olc) {
|
||||
} else if txt.NotEmpty(frm.Olc) {
|
||||
// Open Location Code (OLC).
|
||||
s2Min, s2Max := s2.PrefixedRange(pluscode.S2(f.Olc), s2.Level(f.Dist))
|
||||
s2Min, s2Max := s2.PrefixedRange(pluscode.S2(frm.Olc), s2.Level(frm.Dist))
|
||||
s = s.Where("photos.cell_id BETWEEN ? AND ?", s2Min, s2Max)
|
||||
}
|
||||
|
||||
// Filter by GPS Bounds (Lat N, Lng E, Lat S, Lng W).
|
||||
if latN, lngE, latS, lngW, boundsErr := clean.GPSBounds(f.Latlng); boundsErr == nil {
|
||||
if latN, lngE, latS, lngW, boundsErr := clean.GPSBounds(frm.Latlng); boundsErr == nil {
|
||||
s = s.Where("photos.photo_lat BETWEEN ? AND ?", latS, latN)
|
||||
s = s.Where("photos.photo_lng BETWEEN ? AND ?", lngW, lngE)
|
||||
}
|
||||
|
||||
// Filter by GPS Latitude range (from +90 to -90 degrees).
|
||||
if latN, latS, latErr := clean.GPSLatRange(f.Lat, f.Dist); latErr == nil {
|
||||
if latN, latS, latErr := clean.GPSLatRange(frm.Lat, frm.Dist); latErr == nil {
|
||||
s = s.Where("photos.photo_lat BETWEEN ? AND ?", latS, latN)
|
||||
}
|
||||
|
||||
// Filter by GPS Longitude range (from -180 to +180 degrees).
|
||||
if lngE, lngW, lngErr := clean.GPSLngRange(f.Lat, f.Lng, f.Dist); lngErr == nil {
|
||||
if lngE, lngW, lngErr := clean.GPSLngRange(frm.Lat, frm.Lng, frm.Dist); lngErr == nil {
|
||||
s = s.Where("photos.photo_lng BETWEEN ? AND ?", lngW, lngE)
|
||||
}
|
||||
|
||||
// Filter by GPS Altitude (m) range.
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(f.Alt, -6378000, 1000000000); rangeErr == nil {
|
||||
if rangeStart, rangeEnd, rangeErr := txt.IntRange(frm.Alt, -6378000, 1000000000); rangeErr == nil {
|
||||
s = s.Where("photos.photo_altitude BETWEEN ? AND ?", rangeStart, rangeEnd)
|
||||
}
|
||||
|
||||
// Find pictures added at or after this time (UTC).
|
||||
if !f.Added.IsZero() {
|
||||
s = s.Where("photos.created_at >= ?", f.Added.UTC().Format("2006-01-02 15:04:05"))
|
||||
if !frm.Added.IsZero() {
|
||||
s = s.Where("photos.created_at >= ?", frm.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.UTC().Format("2006-01-02 15:04:05"))
|
||||
if !frm.Updated.IsZero() {
|
||||
s = s.Where("photos.updated_at >= ?", frm.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"))
|
||||
if !frm.Edited.IsZero() {
|
||||
s = s.Where("photos.edited_at >= ?", frm.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.UTC().Format("2006-01-02"))
|
||||
if !frm.Taken.IsZero() {
|
||||
s = s.Where("DATE(photos.taken_at) = DATE(?)", frm.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.UTC().Format("2006-01-02"))
|
||||
if !frm.Before.IsZero() {
|
||||
s = s.Where("photos.taken_at <= ?", frm.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.UTC().Format("2006-01-02"))
|
||||
if !frm.After.IsZero() {
|
||||
s = s.Where("photos.taken_at >= ?", frm.After.UTC().Format("2006-01-02"))
|
||||
}
|
||||
|
||||
// Limit offset and count.
|
||||
if f.Count > 0 {
|
||||
s = s.Limit(f.Count).Offset(f.Offset)
|
||||
if frm.Count > 0 {
|
||||
s = s.Limit(frm.Count).Offset(frm.Offset)
|
||||
} else {
|
||||
s = s.Limit(1000000).Offset(f.Offset)
|
||||
s = s.Limit(1000000).Offset(frm.Offset)
|
||||
}
|
||||
|
||||
// Fetch results.
|
||||
@@ -658,7 +658,7 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
||||
return results, result.Error
|
||||
}
|
||||
|
||||
log.Debugf("places: found %s for %s [%s]", english.Plural(len(results), "result", "results"), f.SerializeAll(), time.Since(start))
|
||||
log.Debugf("places: found %s for %s [%s]", english.Plural(len(results), "result", "results"), frm.SerializeAll(), time.Since(start))
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
)
|
||||
|
||||
// PhotosViewerResults finds photos based on the search form provided and returns them as viewer.Results.
|
||||
func PhotosViewerResults(f form.SearchPhotos, contentUri, apiUri, previewToken, downloadToken string) (viewer.Results, int, error) {
|
||||
return UserPhotosViewerResults(f, nil, contentUri, apiUri, previewToken, downloadToken)
|
||||
func PhotosViewerResults(frm form.SearchPhotos, contentUri, apiUri, previewToken, downloadToken string) (viewer.Results, int, error) {
|
||||
return UserPhotosViewerResults(frm, nil, contentUri, apiUri, previewToken, downloadToken)
|
||||
}
|
||||
|
||||
// UserPhotosViewerResults finds photos based on the search form and user session and returns them as viewer.Results.
|
||||
func UserPhotosViewerResults(f form.SearchPhotos, sess *entity.Session, contentUri, apiUri, previewToken, downloadToken string) (viewer.Results, int, error) {
|
||||
if results, count, err := searchPhotos(f, sess, PhotosColsView); err != nil {
|
||||
func UserPhotosViewerResults(frm form.SearchPhotos, sess *entity.Session, contentUri, apiUri, previewToken, downloadToken string) (viewer.Results, int, error) {
|
||||
if results, count, err := searchPhotos(frm, sess, PhotosColsView); err != nil {
|
||||
return viewer.Results{}, count, err
|
||||
} else {
|
||||
return results.ViewerResults(contentUri, apiUri, previewToken, downloadToken), count, err
|
||||
|
||||
@@ -11,16 +11,16 @@ import (
|
||||
)
|
||||
|
||||
// Sessions finds user sessions.
|
||||
func Sessions(f form.SearchSessions) (result entity.Sessions, err error) {
|
||||
func Sessions(frm form.SearchSessions) (result entity.Sessions, err error) {
|
||||
result = entity.Sessions{}
|
||||
stmt := Db()
|
||||
|
||||
userUid := strings.TrimSpace(f.UID)
|
||||
search := strings.TrimSpace(f.Query)
|
||||
userUid := strings.TrimSpace(frm.UID)
|
||||
search := strings.TrimSpace(frm.Query)
|
||||
|
||||
order := f.Order
|
||||
limit := f.Count
|
||||
offset := f.Offset
|
||||
order := frm.Order
|
||||
limit := frm.Count
|
||||
offset := frm.Offset
|
||||
|
||||
// Limit maximum number of results.
|
||||
if limit > MaxResults {
|
||||
@@ -45,13 +45,13 @@ func Sessions(f form.SearchSessions) (result entity.Sessions, err error) {
|
||||
}
|
||||
|
||||
// Filter by authentication providers?
|
||||
if f.Provider != "" {
|
||||
stmt = stmt.Where("auth_provider IN (?)", f.AuthProviders())
|
||||
if frm.Provider != "" {
|
||||
stmt = stmt.Where("auth_provider IN (?)", frm.AuthProviders())
|
||||
}
|
||||
|
||||
// Filter by authentication methods?
|
||||
if f.Method != "" {
|
||||
stmt = stmt.Where("auth_method IN (?)", f.AuthMethods())
|
||||
if frm.Method != "" {
|
||||
stmt = stmt.Where("auth_method IN (?)", frm.AuthMethods())
|
||||
}
|
||||
|
||||
// Sort results?
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
// Subjects searches subjects and returns them.
|
||||
func Subjects(f form.SearchSubjects) (results SubjectResults, err error) {
|
||||
if err := f.ParseQueryString(); err != nil {
|
||||
func Subjects(frm form.SearchSubjects) (results SubjectResults, err error) {
|
||||
if err = frm.ParseQueryString(); err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ func Subjects(f form.SearchSubjects) (results SubjectResults, err error) {
|
||||
Select(fmt.Sprintf("%s.*", subjTable))
|
||||
|
||||
// Limit result count.
|
||||
if f.Count > 0 && f.Count <= MaxResults {
|
||||
s = s.Limit(f.Count).Offset(f.Offset)
|
||||
if frm.Count > 0 && frm.Count <= MaxResults {
|
||||
s = s.Limit(frm.Count).Offset(frm.Offset)
|
||||
} else {
|
||||
s = s.Limit(MaxResults).Offset(f.Offset)
|
||||
s = s.Limit(MaxResults).Offset(frm.Offset)
|
||||
}
|
||||
|
||||
// Set sort order.
|
||||
switch f.Order {
|
||||
switch frm.Order {
|
||||
case "name":
|
||||
s = s.Order("subj_name")
|
||||
case "count":
|
||||
@@ -45,8 +45,8 @@ func Subjects(f form.SearchSubjects) (results SubjectResults, err error) {
|
||||
s = s.Order("subj_favorite DESC, subj_name")
|
||||
}
|
||||
|
||||
if f.UID != "" {
|
||||
s = s.Where(fmt.Sprintf("%s.subj_uid IN (?)", subjTable), strings.Split(strings.ToLower(f.UID), txt.Or))
|
||||
if frm.UID != "" {
|
||||
s = s.Where(fmt.Sprintf("%s.subj_uid IN (?)", subjTable), strings.Split(strings.ToLower(frm.UID), txt.Or))
|
||||
|
||||
if result := s.Scan(&results); result.Error != nil {
|
||||
return results, result.Error
|
||||
@@ -55,44 +55,44 @@ func Subjects(f form.SearchSubjects) (results SubjectResults, err error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
if f.Query != "" {
|
||||
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, f.Query) {
|
||||
if frm.Query != "" {
|
||||
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, frm.Query) {
|
||||
s = s.Where("?", gorm.Expr(where))
|
||||
}
|
||||
}
|
||||
|
||||
if f.Files > 0 {
|
||||
s = s.Where("file_count >= ?", f.Files)
|
||||
if frm.Files > 0 {
|
||||
s = s.Where("file_count >= ?", frm.Files)
|
||||
}
|
||||
|
||||
if f.Photos > 0 {
|
||||
s = s.Where("photo_count >= ?", f.Photos)
|
||||
if frm.Photos > 0 {
|
||||
s = s.Where("photo_count >= ?", frm.Photos)
|
||||
}
|
||||
|
||||
if f.Type != "" {
|
||||
s = s.Where("subj_type IN (?)", strings.Split(f.Type, txt.Or))
|
||||
if frm.Type != "" {
|
||||
s = s.Where("subj_type IN (?)", strings.Split(frm.Type, txt.Or))
|
||||
}
|
||||
|
||||
if !f.All {
|
||||
if txt.Yes(f.Favorite) {
|
||||
if !frm.All {
|
||||
if txt.Yes(frm.Favorite) {
|
||||
s = s.Where("subj_favorite = 1")
|
||||
} else if txt.No(f.Favorite) {
|
||||
} else if txt.No(frm.Favorite) {
|
||||
s = s.Where("subj_favorite = 0")
|
||||
}
|
||||
|
||||
if !txt.Yes(f.Hidden) {
|
||||
if !txt.Yes(frm.Hidden) {
|
||||
s = s.Where("subj_hidden = 0")
|
||||
}
|
||||
|
||||
if txt.Yes(f.Private) {
|
||||
if txt.Yes(frm.Private) {
|
||||
s = s.Where("subj_private = 1")
|
||||
} else if txt.No(f.Private) {
|
||||
} else if txt.No(frm.Private) {
|
||||
s = s.Where("subj_private = 0")
|
||||
}
|
||||
|
||||
if txt.Yes(f.Excluded) {
|
||||
if txt.Yes(frm.Excluded) {
|
||||
s = s.Where("subj_excluded = 1")
|
||||
} else if txt.No(f.Excluded) {
|
||||
} else if txt.No(frm.Excluded) {
|
||||
s = s.Where("subj_excluded = 0")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,14 @@ import (
|
||||
)
|
||||
|
||||
// Users finds registered users.
|
||||
func Users(f form.SearchUsers) (result entity.Users, err error) {
|
||||
func Users(frm form.SearchUsers) (result entity.Users, err error) {
|
||||
result = entity.Users{}
|
||||
stmt := Db()
|
||||
|
||||
search := strings.TrimSpace(f.Query)
|
||||
sortOrder := f.Order
|
||||
limit := f.Count
|
||||
offset := f.Offset
|
||||
search := strings.TrimSpace(frm.Query)
|
||||
sortOrder := frm.Order
|
||||
limit := frm.Count
|
||||
offset := frm.Offset
|
||||
|
||||
if search == "all" {
|
||||
// Don't filter.
|
||||
|
||||
@@ -320,13 +320,15 @@ func (m *Subject) Visible() bool {
|
||||
}
|
||||
|
||||
// SaveForm updates the subject from form values.
|
||||
func (m *Subject) SaveForm(f form.Subject) (changed bool, err error) {
|
||||
if m.SubjUID == "" {
|
||||
func (m *Subject) SaveForm(frm *form.Subject) (changed bool, err error) {
|
||||
if frm == nil {
|
||||
return false, fmt.Errorf("form is nil")
|
||||
} else if m.SubjUID == "" {
|
||||
return false, fmt.Errorf("subject has no uid")
|
||||
}
|
||||
|
||||
// Change name?
|
||||
if name := clean.Name(f.SubjName); name != "" && name != m.SubjName {
|
||||
if name := clean.Name(frm.SubjName); name != "" && name != m.SubjName {
|
||||
existing, err := m.UpdateName(name)
|
||||
|
||||
if existing.SubjUID != m.SubjUID || err != nil {
|
||||
@@ -337,16 +339,16 @@ func (m *Subject) SaveForm(f form.Subject) (changed bool, err error) {
|
||||
}
|
||||
|
||||
// Change favorite status?
|
||||
if m.SubjFavorite != f.SubjFavorite {
|
||||
m.SubjFavorite = f.SubjFavorite
|
||||
if m.SubjFavorite != frm.SubjFavorite {
|
||||
m.SubjFavorite = frm.SubjFavorite
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Change visibility?
|
||||
if m.SubjHidden != f.SubjHidden || m.SubjPrivate != f.SubjPrivate || m.SubjExcluded != f.SubjExcluded {
|
||||
m.SubjHidden = f.SubjHidden
|
||||
m.SubjPrivate = f.SubjPrivate
|
||||
m.SubjExcluded = f.SubjExcluded
|
||||
if m.SubjHidden != frm.SubjHidden || m.SubjPrivate != frm.SubjPrivate || m.SubjExcluded != frm.SubjExcluded {
|
||||
m.SubjHidden = frm.SubjHidden
|
||||
m.SubjPrivate = frm.SubjPrivate
|
||||
m.SubjExcluded = frm.SubjExcluded
|
||||
|
||||
// Update counter.
|
||||
if !m.IsPerson() {
|
||||
|
||||
@@ -4,8 +4,6 @@ import "github.com/ulule/deepcopier"
|
||||
|
||||
// Album represents an album edit form.
|
||||
type Album struct {
|
||||
Thumb string `json:"Thumb"`
|
||||
ThumbSrc string `json:"ThumbSrc"`
|
||||
AlbumType string `json:"Type"`
|
||||
AlbumTitle string `json:"Title"`
|
||||
AlbumLocation string `json:"Location"`
|
||||
@@ -19,10 +17,14 @@ type Album struct {
|
||||
AlbumCountry string `json:"Country"`
|
||||
AlbumFavorite bool `json:"Favorite"`
|
||||
AlbumPrivate bool `json:"Private"`
|
||||
Thumb string `json:"Thumb"`
|
||||
ThumbSrc string `json:"ThumbSrc"`
|
||||
}
|
||||
|
||||
func NewAlbum(m interface{}) (f Album, err error) {
|
||||
err = deepcopier.Copy(m).To(&f)
|
||||
// NewAlbum creates a new form struct based on the interface values.
|
||||
func NewAlbum(m interface{}) (*Album, error) {
|
||||
frm := &Album{}
|
||||
err := deepcopier.Copy(m).To(frm)
|
||||
|
||||
return f, err
|
||||
return frm, err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewAlbum(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
var album = struct {
|
||||
AlbumTitle string
|
||||
AlbumDescription string
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewFace(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
var m = struct {
|
||||
FaceHidden bool `json:"Hidden"`
|
||||
SubjUID string `json:"SubjUID"`
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewFolder(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
var folder = struct {
|
||||
Path string
|
||||
Root string
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
package form
|
||||
|
||||
import "github.com/ulule/deepcopier"
|
||||
|
||||
// Label represents a label edit form.
|
||||
type Label struct {
|
||||
LabelName string `json:"Name"`
|
||||
Uncertainty int `json:"Uncertainty"`
|
||||
LabelPriority int `json:"Priority"`
|
||||
LabelFavorite bool `json:"Favorite"`
|
||||
LabelDescription string `json:"Description"`
|
||||
LabelNotes string `json:"Notes"`
|
||||
Thumb string `json:"Thumb"`
|
||||
ThumbSrc string `json:"ThumbSrc"`
|
||||
Uncertainty int `json:"Uncertainty"`
|
||||
}
|
||||
|
||||
// NewLabel creates a new form struct based on the interface values.
|
||||
func NewLabel(m interface{}) (*Label, error) {
|
||||
frm := &Label{}
|
||||
err := deepcopier.Copy(m).To(frm)
|
||||
return frm, err
|
||||
}
|
||||
|
||||
60
internal/form/label_test.go
Normal file
60
internal/form/label_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewLabel(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
var album = struct {
|
||||
LabelName string
|
||||
Uncertainty int
|
||||
LabelPriority int
|
||||
LabelFavorite bool
|
||||
}{
|
||||
LabelName: "New Label",
|
||||
Uncertainty: 50,
|
||||
LabelPriority: -5,
|
||||
LabelFavorite: false,
|
||||
}
|
||||
|
||||
result, err := NewLabel(album)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.IsType(t, &Label{}, result)
|
||||
assert.Equal(t, "New Label", result.LabelName)
|
||||
assert.Equal(t, 50, result.Uncertainty)
|
||||
assert.Equal(t, -5, result.LabelPriority)
|
||||
assert.Equal(t, false, result.LabelFavorite)
|
||||
})
|
||||
t.Run("Favorite", func(t *testing.T) {
|
||||
var album = struct {
|
||||
LabelName string
|
||||
Uncertainty int
|
||||
LabelPriority int
|
||||
LabelFavorite bool
|
||||
}{
|
||||
LabelName: "Foo",
|
||||
Uncertainty: 10,
|
||||
LabelPriority: 5,
|
||||
LabelFavorite: true,
|
||||
}
|
||||
|
||||
result, err := NewLabel(album)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.IsType(t, &Label{}, result)
|
||||
assert.Equal(t, "Foo", result.LabelName)
|
||||
assert.Equal(t, 10, result.Uncertainty)
|
||||
assert.Equal(t, 5, result.LabelPriority)
|
||||
assert.Equal(t, true, result.LabelFavorite)
|
||||
})
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewMarker(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
var m = struct {
|
||||
SubjSrc string
|
||||
MarkerName string
|
||||
|
||||
@@ -11,7 +11,7 @@ const (
|
||||
AccessToken = "access_token"
|
||||
)
|
||||
|
||||
// OAuthRevokeToken represents a token revokation form.
|
||||
// OAuthRevokeToken represents a token revocation form.
|
||||
type OAuthRevokeToken struct {
|
||||
Token string `form:"token" binding:"required" json:"token,omitempty"`
|
||||
TokenTypeHint string `form:"token_type_hint" json:" token_type_hint,omitempty"`
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewPhoto(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
photo := Photo{
|
||||
TakenAt: time.Date(2008, 1, 1, 2, 0, 0, 0, time.UTC),
|
||||
TakenAtLocal: time.Date(2008, 1, 1, 2, 0, 0, 0, time.UTC),
|
||||
|
||||
@@ -32,21 +32,21 @@ func TestSelection_Empty(t *testing.T) {
|
||||
assert.Equal(t, false, sel.Empty())
|
||||
assert.Equal(t, []string{"jqzkpo13j8ngpgv4", "jqzkq8j10hj39sxp"}, sel.Subjects)
|
||||
})
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
sel := Selection{Photos: []string{}, Albums: []string{}, Labels: []string{}}
|
||||
assert.Equal(t, true, sel.Empty())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSelection_Get(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
sel := Selection{Photos: []string{"p123", "p456"}, Albums: []string{"a123"}, Labels: []string{"l123", "l456", "l789"}, Files: []string{"f567", "f111"}, Places: []string{"p568"}, Subjects: []string{"jqzkpo13j8ngpgv4"}}
|
||||
assert.Equal(t, []string{"p123", "p456", "a123", "l123", "l456", "l789", "p568", "jqzkpo13j8ngpgv4"}, sel.Get())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSelection_String(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
sel := Selection{Photos: []string{"p123", "p456"}, Albums: []string{"a123"}, Labels: []string{"l123", "l456", "l789"}, Files: []string{"f567", "f111"}, Places: []string{"p568"}}
|
||||
assert.Equal(t, "p123, p456, a123, l123, l456, l789, p568", sel.String())
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewService(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
service := Service{AccName: "Foo", AccOwner: "bar", AccURL: "test.com", AccType: "test", AccKey: "123", AccUser: "testuser", AccPass: "testpass",
|
||||
AccError: "", AccShare: true, AccSync: true, RetryLimit: 4, SharePath: "/home", ShareSize: "500", ShareExpires: 3500, SyncPath: "/sync",
|
||||
SyncInterval: 5, SyncUpload: true, SyncDownload: false, SyncFilenames: true, SyncRaw: false}
|
||||
|
||||
@@ -13,10 +13,13 @@ type Subject struct {
|
||||
SubjHidden bool `json:"Hidden"`
|
||||
SubjPrivate bool `json:"Private"`
|
||||
SubjExcluded bool `json:"Excluded"`
|
||||
Thumb string `json:"Thumb"`
|
||||
ThumbSrc string `json:"ThumbSrc"`
|
||||
}
|
||||
|
||||
func NewSubject(m interface{}) (f Subject, err error) {
|
||||
err = deepcopier.Copy(m).To(&f)
|
||||
func NewSubject(m interface{}) (*Subject, error) {
|
||||
frm := &Subject{}
|
||||
err := deepcopier.Copy(m).To(frm)
|
||||
|
||||
return f, err
|
||||
return frm, err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewSubject(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
var m = struct {
|
||||
SubjName string `json:"Name"`
|
||||
SubjAlias string `json:"Alias"`
|
||||
|
||||
@@ -165,7 +165,7 @@ func TestData_HasTimeAndPlace(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestData_CellID(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
data := Data{
|
||||
Lat: 1.334,
|
||||
Lng: 4.567,
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestDuration(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
d := Duration("")
|
||||
assert.Equal(t, "0s", d.String())
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestData_AddKeywords(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
data := NewData()
|
||||
|
||||
assert.Equal(t, "", data.Keywords.String())
|
||||
@@ -33,7 +33,7 @@ func TestData_AddKeywords(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestData_AutoAddKeywords(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
data := NewData()
|
||||
|
||||
assert.Equal(t, "", data.Keywords.String())
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestActivity_Start(t *testing.T) {
|
||||
|
||||
assert.Error(t, b.Start(), "already running")
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
b := Activity{}
|
||||
|
||||
assert.Nil(t, b.Start())
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestCacheName(t *testing.T) {
|
||||
assert.Empty(t, r)
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
r, err := CacheName("abcdghoj", "test", "juh")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestIndex_MediaFile(t *testing.T) {
|
||||
assert.Equal(t, "Blue Gopher", mediaFile.metaData.Title)
|
||||
assert.Equal(t, IndexStatus("added"), result.Status)
|
||||
})
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
cfg := config.TestConfig()
|
||||
|
||||
cfg.InitializeTestData()
|
||||
|
||||
@@ -2414,7 +2414,7 @@ func TestMediaFile_SkipTranscoding(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMediaFile_RenameSidecarFiles(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
jpegExample := filepath.Join(conf.ExamplesPath(), "/limes.jpg")
|
||||
@@ -2464,7 +2464,7 @@ func TestMediaFile_RenameSidecarFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMediaFile_RemoveSidecarFiles(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
jpegExample := filepath.Join(conf.ExamplesPath(), "/limes.jpg")
|
||||
|
||||
@@ -52,17 +52,17 @@ func TestResample_Start(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestThumb_Filename(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
c := config.TestConfig()
|
||||
|
||||
thumbsPath := conf.CachePath() + "/_tmp"
|
||||
thumbsPath := c.CachePath() + "/_tmp"
|
||||
|
||||
defer os.RemoveAll(thumbsPath)
|
||||
|
||||
if err := conf.CreateDirectories(); err != nil {
|
||||
if err := c.CreateDirectories(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
filename, err := thumb.FileName("99988", thumbsPath, 150, 150, thumb.ResampleFit, thumb.ResampleNearestNeighbor)
|
||||
|
||||
if err != nil {
|
||||
@@ -71,7 +71,7 @@ func TestThumb_Filename(t *testing.T) {
|
||||
|
||||
assert.True(t, strings.HasSuffix(filename, "/storage/testdata/cache/_tmp/9/9/9/99988_150x150_fit.jpg"))
|
||||
})
|
||||
t.Run("hash too short", func(t *testing.T) {
|
||||
t.Run("InvalidHash", func(t *testing.T) {
|
||||
_, err := thumb.FileName("999", thumbsPath, 150, 150, thumb.ResampleFit, thumb.ResampleNearestNeighbor)
|
||||
|
||||
if err == nil {
|
||||
@@ -80,21 +80,21 @@ func TestThumb_Filename(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "thumb: file hash is empty or too short (999)", err.Error())
|
||||
})
|
||||
t.Run("invalid width", func(t *testing.T) {
|
||||
t.Run("InvalidWidth", func(t *testing.T) {
|
||||
_, err := thumb.FileName("99988", thumbsPath, -4, 150, thumb.ResampleFit, thumb.ResampleNearestNeighbor)
|
||||
if err == nil {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "thumb: width exceeds limit (-4)", err.Error())
|
||||
})
|
||||
t.Run("invalid height", func(t *testing.T) {
|
||||
t.Run("InvalidHeight", func(t *testing.T) {
|
||||
_, err := thumb.FileName("99988", thumbsPath, 200, -1, thumb.ResampleFit, thumb.ResampleNearestNeighbor)
|
||||
if err == nil {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, "thumb: height exceeds limit (-1)", err.Error())
|
||||
})
|
||||
t.Run("empty thumbpath", func(t *testing.T) {
|
||||
t.Run("EmptyPath", func(t *testing.T) {
|
||||
path := ""
|
||||
_, err := thumb.FileName("99988", path, 200, 150, thumb.ResampleFit, thumb.ResampleNearestNeighbor)
|
||||
if err == nil {
|
||||
@@ -105,19 +105,19 @@ func TestThumb_Filename(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestThumb_FromFile(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
c := config.TestConfig()
|
||||
|
||||
thumbsPath := conf.CachePath() + "/_tmp"
|
||||
thumbsPath := c.CachePath() + "/_tmp"
|
||||
|
||||
defer os.RemoveAll(thumbsPath)
|
||||
|
||||
if err := conf.CreateDirectories(); err != nil {
|
||||
if err := c.CreateDirectories(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
t.Run("valid parameter", func(t *testing.T) {
|
||||
file := &entity.File{
|
||||
FileName: conf.ExamplesPath() + "/elephants.jpg",
|
||||
FileName: c.ExamplesPath() + "/elephants.jpg",
|
||||
FileHash: "1234568889",
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ func TestThumb_FromFile(t *testing.T) {
|
||||
|
||||
t.Run("hash too short", func(t *testing.T) {
|
||||
file := &entity.File{
|
||||
FileName: conf.ExamplesPath() + "/elephants.jpg",
|
||||
FileName: c.ExamplesPath() + "/elephants.jpg",
|
||||
FileHash: "123",
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestConfig_MapKey(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
c := NewConfig("test", "testdata/new.yml", "zqkunt22r0bewti9", "test", "PhotoPrism/Test", "test")
|
||||
assert.Equal(t, "", c.MapKey())
|
||||
})
|
||||
|
||||
@@ -48,14 +48,14 @@ func NewFeedback(version, serial, env, partnerId string) *Feedback {
|
||||
}
|
||||
|
||||
// SendFeedback sends a feedback message.
|
||||
func (c *Config) SendFeedback(f form.Feedback) (err error) {
|
||||
func (c *Config) SendFeedback(frm form.Feedback) (err error) {
|
||||
feedback := NewFeedback(c.Version, c.Serial, c.Env, c.PartnerID)
|
||||
feedback.Category = f.Category
|
||||
feedback.Subject = txt.Shorten(f.Message, 50, "...")
|
||||
feedback.Message = f.Message
|
||||
feedback.UserName = f.UserName
|
||||
feedback.UserEmail = f.UserEmail
|
||||
feedback.UserAgent = f.UserAgent
|
||||
feedback.Category = frm.Category
|
||||
feedback.Subject = txt.Shorten(frm.Message, 50, "...")
|
||||
feedback.Message = frm.Message
|
||||
feedback.UserName = frm.UserName
|
||||
feedback.UserEmail = frm.UserEmail
|
||||
feedback.UserAgent = frm.UserAgent
|
||||
feedback.ApiKey = c.Key
|
||||
|
||||
// Create new http.Client instance.
|
||||
@@ -87,7 +87,7 @@ func (c *Config) SendFeedback(f form.Feedback) (err error) {
|
||||
req.Header.Set("User-Agent", "PhotoPrism/Test")
|
||||
}
|
||||
|
||||
req.Header.Add("Accept-Language", f.UserLocales)
|
||||
req.Header.Add("Accept-Language", frm.UserLocales)
|
||||
req.Header.Add(header.ContentType, header.ContentTypeJson)
|
||||
|
||||
var r *http.Response
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewFeedback(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
feedback := NewFeedback("xxx", "zqkunt22r0bewti9", "test", "test")
|
||||
assert.Equal(t, "xxx", feedback.ClientVersion)
|
||||
assert.Equal(t, "zqkunt22r0bewti9", feedback.ClientSerial)
|
||||
@@ -16,7 +16,7 @@ func TestNewFeedback(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSendFeedback(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
c := NewConfig("test", "testdata/new.yml", "zqkunt22r0bewti9", "test", "PhotoPrism/Test", "test")
|
||||
|
||||
feedback := Feedback{
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestNewRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfig_Refresh(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
fileName := fmt.Sprintf("testdata/hub.%s.yml", Token(8))
|
||||
|
||||
c := NewConfig("test", fileName, "zqkunt22r0bewti9", "test", "PhotoPrism/Test", "test")
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestSession_Expired(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
session := Session{
|
||||
MapKey: "",
|
||||
ExpiresAt: "",
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestCountryName(t *testing.T) {
|
||||
assert.Equal(t, "United States", result)
|
||||
})
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
result := CountryName("")
|
||||
assert.Equal(t, "Unknown", result)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user