mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Backend: Move SQL queries to repo package
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
@@ -13,11 +13,11 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/models"
|
"github.com/photoprism/photoprism/internal/models"
|
||||||
|
"github.com/photoprism/photoprism/internal/repo"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism"
|
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ func GetAlbums(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
router.GET("/albums", func(c *gin.Context) {
|
router.GET("/albums", func(c *gin.Context) {
|
||||||
var f form.AlbumSearch
|
var f form.AlbumSearch
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
err := c.MustBindWith(&f, binding.Form)
|
err := c.MustBindWith(&f, binding.Form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -34,7 +34,7 @@ func GetAlbums(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := search.Albums(f)
|
result, err := r.Albums(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
@@ -51,8 +51,8 @@ func GetAlbums(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
func GetAlbum(router *gin.RouterGroup, conf *config.Config) {
|
func GetAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.GET("/albums/:uuid", func(c *gin.Context) {
|
router.GET("/albums/:uuid", func(c *gin.Context) {
|
||||||
id := c.Param("uuid")
|
id := c.Param("uuid")
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
m, err := search.FindAlbumByUUID(id)
|
m, err := r.FindAlbumByUUID(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
@@ -112,9 +112,9 @@ func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id := c.Param("uuid")
|
id := c.Param("uuid")
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
m, err := search.FindAlbumByUUID(id)
|
m, err := r.FindAlbumByUUID(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
@@ -140,9 +140,9 @@ func DeleteAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id := c.Param("uuid")
|
id := c.Param("uuid")
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
m, err := search.FindAlbumByUUID(id)
|
m, err := r.FindAlbumByUUID(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
@@ -169,9 +169,9 @@ func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
album, err := search.FindAlbumByUUID(c.Param("uuid"))
|
album, err := r.FindAlbumByUUID(c.Param("uuid"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
@@ -198,9 +198,8 @@ func DislikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
|
album, err := r.FindAlbumByUUID(c.Param("uuid"))
|
||||||
album, err := search.FindAlbumByUUID(c.Param("uuid"))
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
@@ -237,8 +236,8 @@ func AddPhotosToAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
a, err := search.FindAlbumByUUID(c.Param("uuid"))
|
a, err := r.FindAlbumByUUID(c.Param("uuid"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
@@ -250,7 +249,7 @@ func AddPhotosToAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
var failed []string
|
var failed []string
|
||||||
|
|
||||||
for _, photoUUID := range f.Photos {
|
for _, photoUUID := range f.Photos {
|
||||||
if p, err := search.FindPhotoByUUID(photoUUID); err != nil {
|
if p, err := r.FindPhotoByUUID(photoUUID); err != nil {
|
||||||
failed = append(failed, photoUUID)
|
failed = append(failed, photoUUID)
|
||||||
} else {
|
} else {
|
||||||
added = append(added, models.NewPhotoAlbum(p.PhotoUUID, a.AlbumUUID).FirstOrCreate(db))
|
added = append(added, models.NewPhotoAlbum(p.PhotoUUID, a.AlbumUUID).FirstOrCreate(db))
|
||||||
@@ -288,8 +287,8 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
a, err := search.FindAlbumByUUID(c.Param("uuid"))
|
a, err := r.FindAlbumByUUID(c.Param("uuid"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
@@ -312,15 +311,15 @@ func DownloadAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
a, err := search.FindAlbumByUUID(c.Param("uuid"))
|
a, err := r.FindAlbumByUUID(c.Param("uuid"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := search.Photos(form.PhotoSearch{
|
p, err := r.Photos(form.PhotoSearch{
|
||||||
Album: a.AlbumUUID,
|
Album: a.AlbumUUID,
|
||||||
Count: 10000,
|
Count: 10000,
|
||||||
Offset: 0,
|
Offset: 0,
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/repo"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: GET /api/v1/dl/file/:hash
|
// TODO: GET /api/v1/dl/file/:hash
|
||||||
@@ -22,8 +22,8 @@ func GetDownload(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
router.GET("/download/:hash", func(c *gin.Context) {
|
router.GET("/download/:hash", func(c *gin.Context) {
|
||||||
fileHash := c.Param("hash")
|
fileHash := c.Param("hash")
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
file, err := search.FindFileByHash(fileHash)
|
file, err := r.FindFileByHash(fileHash)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": err.Error()})
|
c.AbortWithStatusJSON(404, gin.H{"error": err.Error()})
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism"
|
"github.com/photoprism/photoprism/internal/repo"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ func GetLabels(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
router.GET("/labels", func(c *gin.Context) {
|
router.GET("/labels", func(c *gin.Context) {
|
||||||
var f form.LabelSearch
|
var f form.LabelSearch
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
err := c.MustBindWith(&f, binding.Form)
|
err := c.MustBindWith(&f, binding.Form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -25,7 +25,7 @@ func GetLabels(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := search.Labels(f)
|
result, err := r.Labels(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
@@ -49,9 +49,9 @@ func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
label, err := search.FindLabelBySlug(c.Param("slug"))
|
label, err := r.FindLabelBySlug(c.Param("slug"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
@@ -76,9 +76,9 @@ func DislikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
label, err := search.FindLabelBySlug(c.Param("slug"))
|
label, err := r.FindLabelBySlug(c.Param("slug"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import (
|
|||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
"github.com/photoprism/photoprism/internal/repo"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GET /api/v1/photos
|
// GET /api/v1/photos
|
||||||
@@ -33,7 +33,7 @@ func GetPhotos(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
router.GET("/photos", func(c *gin.Context) {
|
router.GET("/photos", func(c *gin.Context) {
|
||||||
var f form.PhotoSearch
|
var f form.PhotoSearch
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
err := c.MustBindWith(&f, binding.Form)
|
err := c.MustBindWith(&f, binding.Form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -41,7 +41,7 @@ func GetPhotos(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := search.Photos(f)
|
result, err := r.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
@@ -61,8 +61,8 @@ func GetPhotos(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
// uuid: string PhotoUUID as returned by the API
|
// uuid: string PhotoUUID as returned by the API
|
||||||
func GetPhotoDownload(router *gin.RouterGroup, conf *config.Config) {
|
func GetPhotoDownload(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.GET("/photos/:uuid/download", func(c *gin.Context) {
|
router.GET("/photos/:uuid/download", func(c *gin.Context) {
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
file, err := search.FindFileByPhotoUUID(c.Param("uuid"))
|
file, err := r.FindFileByPhotoUUID(c.Param("uuid"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": err.Error()})
|
c.AbortWithStatusJSON(404, gin.H{"error": err.Error()})
|
||||||
@@ -100,8 +100,8 @@ func LikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
m, err := search.FindPhotoByUUID(c.Param("uuid"))
|
m, err := r.FindPhotoByUUID(c.Param("uuid"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
@@ -130,8 +130,8 @@ func DislikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
m, err := search.FindPhotoByUUID(c.Param("uuid"))
|
m, err := r.FindPhotoByUUID(c.Param("uuid"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/repo"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -29,8 +30,8 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
file, err := search.FindFileByHash(fileHash)
|
file, err := r.FindFileByHash(fileHash)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||||
@@ -83,11 +84,11 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
// log.Infof("Searching for label slug: %s", c.Param("slug"))
|
// log.Infof("Searching for label slug: %s", c.Param("slug"))
|
||||||
|
|
||||||
file, err := search.FindLabelThumbBySlug(c.Param("slug"))
|
file, err := r.FindLabelThumbBySlug(c.Param("slug"))
|
||||||
|
|
||||||
// log.Infof("Label thumb file: %#v", file)
|
// log.Infof("Label thumb file: %#v", file)
|
||||||
|
|
||||||
@@ -138,9 +139,9 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
file, err := search.FindAlbumThumbByUUID(uuid)
|
file, err := r.FindAlbumThumbByUUID(uuid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("album has no photos yet, using generic thumb image: %s", uuid)
|
log.Debugf("album has no photos yet, using generic thumb image: %s", uuid)
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import (
|
|||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
|
"github.com/photoprism/photoprism/internal/repo"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// POST /api/v1/zip
|
// POST /api/v1/zip
|
||||||
@@ -35,8 +35,8 @@ func CreateZip(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
r := repo.New(conf.OriginalsPath(), conf.Db())
|
||||||
files, err := search.FindFilesByUUID(f.Photos, 1000, 0)
|
files, err := r.FindFilesByUUID(f.Photos, 1000, 0)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(404, gin.H{"error": err.Error()})
|
c.AbortWithStatusJSON(404, gin.H{"error": err.Error()})
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ type Indexer struct {
|
|||||||
// NewIndexer returns a new indexer.
|
// NewIndexer returns a new indexer.
|
||||||
// TODO: Is it really necessary to return a pointer?
|
// TODO: Is it really necessary to return a pointer?
|
||||||
func NewIndexer(conf *config.Config, tensorFlow *TensorFlow) *Indexer {
|
func NewIndexer(conf *config.Config, tensorFlow *TensorFlow) *Indexer {
|
||||||
instance := &Indexer{
|
i := &Indexer{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
tensorFlow: tensorFlow,
|
tensorFlow: tensorFlow,
|
||||||
db: conf.Db(),
|
db: conf.Db(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return instance
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Indexer) originalsPath() string {
|
func (i *Indexer) originalsPath() string {
|
||||||
|
|||||||
@@ -1,469 +0,0 @@
|
|||||||
package photoprism
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gosimple/slug"
|
|
||||||
"github.com/jinzhu/gorm"
|
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
|
||||||
"github.com/photoprism/photoprism/internal/models"
|
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// About 1km ('good enough' for now)
|
|
||||||
const SearchRadius = 0.009
|
|
||||||
|
|
||||||
// Search searches given an originals path and a db instance.
|
|
||||||
type Search struct {
|
|
||||||
originalsPath string
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchCount is the total number of search hits.
|
|
||||||
type SearchCount struct {
|
|
||||||
Total int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSearch returns a new Search type with a given path and db instance.
|
|
||||||
func NewSearch(originalsPath string, db *gorm.DB) *Search {
|
|
||||||
instance := &Search{
|
|
||||||
originalsPath: originalsPath,
|
|
||||||
db: db,
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
// Photos searches for photos based on a Form and returns a PhotoSearchResult slice.
|
|
||||||
func (s *Search) Photos(f form.PhotoSearch) (results []PhotoSearchResult, err error) {
|
|
||||||
if err := f.ParseQueryString(); err != nil {
|
|
||||||
return results, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", f))
|
|
||||||
|
|
||||||
q := s.db.NewScope(nil).DB()
|
|
||||||
|
|
||||||
// q.LogMode(true)
|
|
||||||
|
|
||||||
q = q.Table("photos").
|
|
||||||
Select(`photos.*,
|
|
||||||
files.id AS file_id, files.file_uuid, files.file_primary, files.file_missing, files.file_name, files.file_hash,
|
|
||||||
files.file_type, files.file_mime, files.file_width, files.file_height, files.file_aspect_ratio,
|
|
||||||
files.file_orientation, files.file_main_color, files.file_colors, files.file_luminance, files.file_chroma,
|
|
||||||
cameras.camera_make, cameras.camera_model,
|
|
||||||
lenses.lens_make, lenses.lens_model,
|
|
||||||
countries.country_name,
|
|
||||||
locations.loc_display_name, locations.loc_name, locations.loc_city, locations.loc_postcode, locations.loc_county,
|
|
||||||
locations.loc_state, locations.loc_country, locations.loc_country_code, locations.loc_category, locations.loc_type,
|
|
||||||
GROUP_CONCAT(DISTINCT labels.label_name) AS labels,
|
|
||||||
GROUP_CONCAT(DISTINCT keywords.keyword) AS keywords`).
|
|
||||||
Joins("JOIN files ON files.photo_id = photos.id AND files.file_primary AND files.deleted_at IS NULL").
|
|
||||||
Joins("JOIN cameras ON cameras.id = photos.camera_id").
|
|
||||||
Joins("JOIN lenses ON lenses.id = photos.lens_id").
|
|
||||||
Joins("LEFT JOIN countries ON countries.id = photos.country_id").
|
|
||||||
Joins("LEFT JOIN locations ON locations.id = photos.location_id").
|
|
||||||
Joins("LEFT JOIN photos_labels ON photos_labels.photo_id = photos.id").
|
|
||||||
Joins("LEFT JOIN labels ON photos_labels.label_id = labels.id").
|
|
||||||
Joins("LEFT JOIN photos_keywords ON photos_keywords.photo_id = photos.id").
|
|
||||||
Joins("LEFT JOIN keywords ON photos_keywords.keyword_id = keywords.id").
|
|
||||||
Where("photos.deleted_at IS NULL AND files.file_missing = 0").
|
|
||||||
Group("photos.id, files.id")
|
|
||||||
var categories []models.Category
|
|
||||||
var label models.Label
|
|
||||||
var labelIds []uint
|
|
||||||
|
|
||||||
if f.Label != "" {
|
|
||||||
if result := s.db.First(&label, "label_slug = ?", strings.ToLower(f.Label)); result.Error != nil {
|
|
||||||
log.Errorf("search: label \"%s\" not found", f.Label)
|
|
||||||
return results, fmt.Errorf("label \"%s\" not found", f.Label)
|
|
||||||
} else {
|
|
||||||
labelIds = append(labelIds, label.ID)
|
|
||||||
|
|
||||||
s.db.Where("category_id = ?", label.ID).Find(&categories)
|
|
||||||
|
|
||||||
for _, category := range categories {
|
|
||||||
labelIds = append(labelIds, category.LabelID)
|
|
||||||
}
|
|
||||||
|
|
||||||
q = q.Where("labels.id IN (?)", labelIds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Location == true {
|
|
||||||
q = q.Where("location_id > 0")
|
|
||||||
|
|
||||||
if f.Query != "" {
|
|
||||||
likeString := "%" + strings.ToLower(f.Query) + "%"
|
|
||||||
q = q.Where("LOWER(locations.loc_display_name) LIKE ?", likeString)
|
|
||||||
}
|
|
||||||
} else if f.Query != "" {
|
|
||||||
slugString := slug.Make(f.Query)
|
|
||||||
lowerString := strings.ToLower(f.Query)
|
|
||||||
likeString := lowerString + "%"
|
|
||||||
|
|
||||||
if result := s.db.First(&label, "label_slug = ?", slugString); result.Error != nil {
|
|
||||||
log.Infof("search: label \"%s\" not found", f.Query)
|
|
||||||
|
|
||||||
q = q.Where("labels.label_slug = ? OR keywords.keyword LIKE ? OR files.file_main_color = ?", slugString, likeString, lowerString)
|
|
||||||
} else {
|
|
||||||
labelIds = append(labelIds, label.ID)
|
|
||||||
|
|
||||||
s.db.Where("category_id = ?", label.ID).Find(&categories)
|
|
||||||
|
|
||||||
for _, category := range categories {
|
|
||||||
labelIds = append(labelIds, category.LabelID)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("search: label \"%s\" includes %d categories", label.LabelName, len(labelIds))
|
|
||||||
|
|
||||||
q = q.Where("labels.id IN (?) OR keywords.keyword LIKE ? OR files.file_main_color = ?", labelIds, likeString, lowerString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Album != "" {
|
|
||||||
q = q.Joins("JOIN photos_albums ON photos_albums.photo_uuid = photos.photo_uuid").Where("photos_albums.album_uuid = ?", f.Album)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Camera > 0 {
|
|
||||||
q = q.Where("photos.camera_id = ?", f.Camera)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Color != "" {
|
|
||||||
q = q.Where("files.file_main_color = ?", strings.ToLower(f.Color))
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Favorites {
|
|
||||||
q = q.Where("photos.photo_favorite = 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Country != "" {
|
|
||||||
q = q.Where("locations.loc_country_code = ?", f.Country)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Title != "" {
|
|
||||||
q = q.Where("LOWER(photos.photo_title) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Title)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Description != "" {
|
|
||||||
q = q.Where("LOWER(photos.photo_description) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Description)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Notes != "" {
|
|
||||||
q = q.Where("LOWER(photos.photo_notes) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Notes)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Hash != "" {
|
|
||||||
q = q.Where("files.file_hash = ?", f.Hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Duplicate {
|
|
||||||
q = q.Where("files.file_duplicate = 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Portrait {
|
|
||||||
q = q.Where("files.file_portrait = 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Mono {
|
|
||||||
q = q.Where("files.file_chroma = 0")
|
|
||||||
} else if f.Chroma > 9 {
|
|
||||||
q = q.Where("files.file_chroma > ?", f.Chroma)
|
|
||||||
} else if f.Chroma > 0 {
|
|
||||||
q = q.Where("files.file_chroma > 0 AND files.file_chroma <= ?", f.Chroma)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Fmin > 0 {
|
|
||||||
q = q.Where("photos.photo_f_number >= ?", f.Fmin)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Fmax > 0 {
|
|
||||||
q = q.Where("photos.photo_f_number <= ?", f.Fmax)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Dist == 0 {
|
|
||||||
f.Dist = 20
|
|
||||||
} else if f.Dist > 1000 {
|
|
||||||
f.Dist = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inaccurate distance search, but probably 'good enough' for now
|
|
||||||
if f.Lat > 0 {
|
|
||||||
latMin := f.Lat - SearchRadius*float64(f.Dist)
|
|
||||||
latMax := f.Lat + SearchRadius*float64(f.Dist)
|
|
||||||
q = q.Where("photos.photo_lat BETWEEN ? AND ?", latMin, latMax)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Long > 0 {
|
|
||||||
longMin := f.Long - SearchRadius*float64(f.Dist)
|
|
||||||
longMax := f.Long + SearchRadius*float64(f.Dist)
|
|
||||||
q = q.Where("photos.photo_long BETWEEN ? AND ?", longMin, longMax)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.Before.IsZero() {
|
|
||||||
q = q.Where("photos.taken_at <= ?", f.Before.Format("2006-01-02"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.After.IsZero() {
|
|
||||||
q = q.Where("photos.taken_at >= ?", f.After.Format("2006-01-02"))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch f.Order {
|
|
||||||
case "newest":
|
|
||||||
q = q.Order("taken_at DESC")
|
|
||||||
case "oldest":
|
|
||||||
q = q.Order("taken_at")
|
|
||||||
case "imported":
|
|
||||||
q = q.Order("created_at DESC")
|
|
||||||
default:
|
|
||||||
q = q.Order("taken_at DESC")
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Count > 0 && f.Count <= 1000 {
|
|
||||||
q = q.Limit(f.Count).Offset(f.Offset)
|
|
||||||
} else {
|
|
||||||
q = q.Limit(100).Offset(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result := q.Scan(&results); result.Error != nil {
|
|
||||||
return results, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindFiles finds files returning maximum results defined by limit
|
|
||||||
// and finding them from an offest defined by offset.
|
|
||||||
func (s *Search) FindFiles(limit int, offset int) (files []models.File, err error) {
|
|
||||||
if err := s.db.Where(&models.File{}).Limit(limit).Offset(offset).Find(&files).Error; err != nil {
|
|
||||||
return files, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindFilesByUUID
|
|
||||||
func (s *Search) FindFilesByUUID(u []string, limit int, offset int) (files []models.File, err error) {
|
|
||||||
if err := s.db.Where("(photo_uuid IN (?) AND file_primary = 1) OR file_uuid IN (?)", u, u).Preload("Photo").Limit(limit).Offset(offset).Find(&files).Error; err != nil {
|
|
||||||
return files, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindFileByPhotoUUID
|
|
||||||
func (s *Search) FindFileByPhotoUUID(u string) (file models.File, err error) {
|
|
||||||
if err := s.db.Where("photo_uuid = ? AND file_primary = 1", u).Preload("Photo").First(&file).Error; err != nil {
|
|
||||||
return file, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindFileByID returns a mediafile given a certain ID.
|
|
||||||
func (s *Search) FindFileByID(id string) (file models.File, err error) {
|
|
||||||
if err := s.db.Where("id = ?", id).Preload("Photo").First(&file).Error; err != nil {
|
|
||||||
return file, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindFileByHash finds a file with a given hash string.
|
|
||||||
func (s *Search) FindFileByHash(fileHash string) (file models.File, err error) {
|
|
||||||
if err := s.db.Where("file_hash = ?", fileHash).Preload("Photo").First(&file).Error; err != nil {
|
|
||||||
return file, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindPhotoByID returns a Photo based on the ID.
|
|
||||||
func (s *Search) FindPhotoByID(photoID uint64) (photo models.Photo, err error) {
|
|
||||||
if err := s.db.Where("id = ?", photoID).First(&photo).Error; err != nil {
|
|
||||||
return photo, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return photo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindPhotoByUUID returns a Photo based on the UUID.
|
|
||||||
func (s *Search) FindPhotoByUUID(photoUUID string) (photo models.Photo, err error) {
|
|
||||||
if err := s.db.Where("photo_uuid = ?", photoUUID).First(&photo).Error; err != nil {
|
|
||||||
return photo, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return photo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindLabelBySlug returns a Label based on the slug name.
|
|
||||||
func (s *Search) FindLabelBySlug(labelSlug string) (label models.Label, err error) {
|
|
||||||
if err := s.db.Where("label_slug = ?", labelSlug).First(&label).Error; err != nil {
|
|
||||||
return label, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return label, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindLabelThumbBySlug returns a label preview file based on the slug name.
|
|
||||||
func (s *Search) FindLabelThumbBySlug(labelSlug string) (file models.File, err error) {
|
|
||||||
// s.db.LogMode(true)
|
|
||||||
|
|
||||||
if err := s.db.Where("files.file_primary AND files.deleted_at IS NULL").
|
|
||||||
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").
|
|
||||||
Order("photos_labels.label_uncertainty ASC").
|
|
||||||
First(&file).Error; err != nil {
|
|
||||||
return file, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Labels searches labels based on their name.
|
|
||||||
func (s *Search) Labels(f form.LabelSearch) (results []LabelSearchResult, err error) {
|
|
||||||
if err := f.ParseQueryString(); err != nil {
|
|
||||||
return results, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", f))
|
|
||||||
|
|
||||||
q := s.db.NewScope(nil).DB()
|
|
||||||
|
|
||||||
// q.LogMode(true)
|
|
||||||
|
|
||||||
q = q.Table("labels").
|
|
||||||
Select(`labels.*, COUNT(photos_labels.label_id) AS label_count`).
|
|
||||||
Joins("JOIN photos_labels ON photos_labels.label_id = labels.id").
|
|
||||||
Where("labels.deleted_at IS NULL").
|
|
||||||
Group("labels.id")
|
|
||||||
|
|
||||||
if f.Query != "" {
|
|
||||||
var labelIds []uint
|
|
||||||
var categories []models.Category
|
|
||||||
var label models.Label
|
|
||||||
|
|
||||||
likeString := "%" + strings.ToLower(f.Query) + "%"
|
|
||||||
|
|
||||||
if result := s.db.First(&label, "LOWER(label_name) LIKE LOWER(?)", f.Query); result.Error != nil {
|
|
||||||
log.Infof("search: label \"%s\" not found", f.Query)
|
|
||||||
|
|
||||||
q = q.Where("LOWER(labels.label_name) LIKE ?", likeString)
|
|
||||||
} else {
|
|
||||||
labelIds = append(labelIds, label.ID)
|
|
||||||
|
|
||||||
s.db.Where("category_id = ?", label.ID).Find(&categories)
|
|
||||||
|
|
||||||
for _, category := range categories {
|
|
||||||
labelIds = append(labelIds, category.LabelID)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("search: label \"%s\" includes %d categories", label.LabelName, len(labelIds))
|
|
||||||
|
|
||||||
q = q.Where("labels.id IN (?) OR LOWER(labels.label_name) LIKE ?", labelIds, likeString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Favorites {
|
|
||||||
q = q.Where("labels.label_favorite = 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Priority != 0 {
|
|
||||||
q = q.Where("labels.label_priority > ?", f.Priority)
|
|
||||||
} else {
|
|
||||||
q = q.Where("labels.label_priority >= -2")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch f.Order {
|
|
||||||
case "slug":
|
|
||||||
q = q.Order("labels.label_favorite DESC, label_slug ASC")
|
|
||||||
default:
|
|
||||||
q = q.Order("labels.label_favorite DESC, labels.label_priority DESC, label_count DESC, labels.created_at DESC")
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Count > 0 && f.Count <= 1000 {
|
|
||||||
q = q.Limit(f.Count).Offset(f.Offset)
|
|
||||||
} else {
|
|
||||||
q = q.Limit(100).Offset(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result := q.Scan(&results); result.Error != nil {
|
|
||||||
return results, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/***************** Albums *****************/
|
|
||||||
|
|
||||||
// FindAlbumByUUID returns a Album based on the UUID.
|
|
||||||
func (s *Search) FindAlbumByUUID(albumUUID string) (album models.Album, err error) {
|
|
||||||
if err := s.db.Where("album_uuid = ?", albumUUID).First(&album).Error; err != nil {
|
|
||||||
return album, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return album, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindAlbumThumbByUUID returns a album preview file based on the uuid.
|
|
||||||
func (s *Search) FindAlbumThumbByUUID(albumUUID string) (file models.File, err error) {
|
|
||||||
// s.db.LogMode(true)
|
|
||||||
|
|
||||||
if err := s.db.Where("files.file_primary AND files.deleted_at IS NULL").
|
|
||||||
Joins("JOIN albums ON albums.album_uuid = ?", albumUUID).
|
|
||||||
Joins("JOIN photos_albums pa ON pa.album_uuid = albums.album_uuid AND pa.photo_uuid = files.photo_uuid").
|
|
||||||
First(&file).Error; err != nil {
|
|
||||||
return file, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Albums searches albums based on their name.
|
|
||||||
func (s *Search) Albums(f form.AlbumSearch) (results []AlbumSearchResult, err error) {
|
|
||||||
if err := f.ParseQueryString(); err != nil {
|
|
||||||
return results, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", f))
|
|
||||||
|
|
||||||
q := s.db.NewScope(nil).DB()
|
|
||||||
|
|
||||||
// q.LogMode(true)
|
|
||||||
|
|
||||||
q = q.Table("albums").
|
|
||||||
Select(`albums.*, COUNT(photos_albums.album_uuid) AS album_count`).
|
|
||||||
Joins("LEFT JOIN photos_albums ON photos_albums.album_uuid = albums.album_uuid").
|
|
||||||
Where("albums.deleted_at IS NULL").
|
|
||||||
Group("albums.id")
|
|
||||||
|
|
||||||
if f.Query != "" {
|
|
||||||
likeString := "%" + strings.ToLower(f.Query) + "%"
|
|
||||||
q = q.Where("LOWER(albums.album_name) LIKE ?", likeString)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Favorites {
|
|
||||||
q = q.Where("albums.album_favorite = 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch f.Order {
|
|
||||||
case "slug":
|
|
||||||
q = q.Order("albums.album_favorite DESC, album_slug ASC")
|
|
||||||
default:
|
|
||||||
q = q.Order("albums.album_favorite DESC, album_count DESC, albums.created_at DESC")
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Count > 0 && f.Count <= 1000 {
|
|
||||||
q = q.Limit(f.Count).Offset(f.Offset)
|
|
||||||
} else {
|
|
||||||
q = q.Limit(100).Offset(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result := q.Scan(&results); result.Error != nil {
|
|
||||||
return results, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
package photoprism
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gosimple/slug"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PhotoSearchResult contains found photos and their main file plus other meta data.
|
|
||||||
type PhotoSearchResult struct {
|
|
||||||
// Photo
|
|
||||||
ID uint
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt time.Time
|
|
||||||
TakenAt time.Time
|
|
||||||
TakenAtLocal time.Time
|
|
||||||
TimeZone string
|
|
||||||
PhotoUUID string
|
|
||||||
PhotoPath string
|
|
||||||
PhotoName string
|
|
||||||
PhotoTitle string
|
|
||||||
PhotoDescription string
|
|
||||||
PhotoArtist string
|
|
||||||
PhotoKeywords string
|
|
||||||
PhotoColors string
|
|
||||||
PhotoColor string
|
|
||||||
PhotoFavorite bool
|
|
||||||
PhotoPrivate bool
|
|
||||||
PhotoSensitive bool
|
|
||||||
PhotoStory bool
|
|
||||||
PhotoLat float64
|
|
||||||
PhotoLong float64
|
|
||||||
PhotoAltitude int
|
|
||||||
PhotoFocalLength int
|
|
||||||
PhotoIso int
|
|
||||||
PhotoFNumber float64
|
|
||||||
PhotoExposure string
|
|
||||||
|
|
||||||
// Camera
|
|
||||||
CameraID uint
|
|
||||||
CameraModel string
|
|
||||||
CameraMake string
|
|
||||||
|
|
||||||
// Lens
|
|
||||||
LensID uint
|
|
||||||
LensModel string
|
|
||||||
LensMake string
|
|
||||||
|
|
||||||
// Country
|
|
||||||
CountryID string
|
|
||||||
CountryName string
|
|
||||||
|
|
||||||
// Location
|
|
||||||
LocationID uint
|
|
||||||
LocDisplayName string
|
|
||||||
LocName string
|
|
||||||
LocCity string
|
|
||||||
LocPostcode string
|
|
||||||
LocCounty string
|
|
||||||
LocState string
|
|
||||||
LocCountry string
|
|
||||||
LocCountryCode string
|
|
||||||
LocCategory string
|
|
||||||
LocType string
|
|
||||||
LocationChanged bool
|
|
||||||
LocationEstimated bool
|
|
||||||
|
|
||||||
// File
|
|
||||||
FileID uint
|
|
||||||
FileUUID string
|
|
||||||
FilePrimary bool
|
|
||||||
FileMissing bool
|
|
||||||
FileName string
|
|
||||||
FileHash string
|
|
||||||
FilePerceptualHash string
|
|
||||||
FileType string
|
|
||||||
FileMime string
|
|
||||||
FileWidth int
|
|
||||||
FileHeight int
|
|
||||||
FileOrientation int
|
|
||||||
FileAspectRatio float64
|
|
||||||
|
|
||||||
// List of matching labels and keywords
|
|
||||||
Labels string
|
|
||||||
Keywords string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *PhotoSearchResult) DownloadFileName() string {
|
|
||||||
var name string
|
|
||||||
|
|
||||||
if m.PhotoTitle != "" {
|
|
||||||
name = strings.Title(slug.MakeLang(m.PhotoTitle, "en"))
|
|
||||||
} else {
|
|
||||||
name = m.PhotoUUID
|
|
||||||
}
|
|
||||||
|
|
||||||
taken := m.TakenAt.Format("20060102-150405")
|
|
||||||
|
|
||||||
result := fmt.Sprintf("%s-%s.%s", taken, name, m.FileType)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// LabelSearchResult contains found labels
|
|
||||||
type LabelSearchResult struct {
|
|
||||||
// Label
|
|
||||||
ID uint
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt time.Time
|
|
||||||
LabelSlug string
|
|
||||||
LabelName string
|
|
||||||
LabelPriority int
|
|
||||||
LabelCount int
|
|
||||||
LabelFavorite bool
|
|
||||||
LabelDescription string
|
|
||||||
LabelNotes string
|
|
||||||
}
|
|
||||||
|
|
||||||
// AlbumSearchResult contains found albums
|
|
||||||
type AlbumSearchResult struct {
|
|
||||||
ID uint
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt time.Time
|
|
||||||
AlbumUUID string
|
|
||||||
AlbumSlug string
|
|
||||||
AlbumName string
|
|
||||||
AlbumCount int
|
|
||||||
AlbumFavorite bool
|
|
||||||
AlbumDescription string
|
|
||||||
AlbumNotes string
|
|
||||||
}
|
|
||||||
96
internal/repo/albums.go
Normal file
96
internal/repo/albums.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
|
"github.com/photoprism/photoprism/internal/models"
|
||||||
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AlbumResult contains found albums
|
||||||
|
type AlbumResult struct {
|
||||||
|
ID uint
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt time.Time
|
||||||
|
AlbumUUID string
|
||||||
|
AlbumSlug string
|
||||||
|
AlbumName string
|
||||||
|
AlbumCount int
|
||||||
|
AlbumFavorite bool
|
||||||
|
AlbumDescription string
|
||||||
|
AlbumNotes string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindAlbumByUUID returns a Album based on the UUID.
|
||||||
|
func (s *Repo) FindAlbumByUUID(albumUUID string) (album models.Album, err error) {
|
||||||
|
if err := s.db.Where("album_uuid = ?", albumUUID).First(&album).Error; err != nil {
|
||||||
|
return album, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return album, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindAlbumThumbByUUID returns a album preview file based on the uuid.
|
||||||
|
func (s *Repo) FindAlbumThumbByUUID(albumUUID string) (file models.File, err error) {
|
||||||
|
// s.db.LogMode(true)
|
||||||
|
|
||||||
|
if err := s.db.Where("files.file_primary AND files.deleted_at IS NULL").
|
||||||
|
Joins("JOIN albums ON albums.album_uuid = ?", albumUUID).
|
||||||
|
Joins("JOIN photos_albums pa ON pa.album_uuid = albums.album_uuid AND pa.photo_uuid = files.photo_uuid").
|
||||||
|
First(&file).Error; err != nil {
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Albums searches albums based on their name.
|
||||||
|
func (s *Repo) Albums(f form.AlbumSearch) (results []AlbumResult, err error) {
|
||||||
|
if err := f.ParseQueryString(); err != nil {
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", f))
|
||||||
|
|
||||||
|
q := s.db.NewScope(nil).DB()
|
||||||
|
|
||||||
|
// q.LogMode(true)
|
||||||
|
|
||||||
|
q = q.Table("albums").
|
||||||
|
Select(`albums.*, COUNT(photos_albums.album_uuid) AS album_count`).
|
||||||
|
Joins("LEFT JOIN photos_albums ON photos_albums.album_uuid = albums.album_uuid").
|
||||||
|
Where("albums.deleted_at IS NULL").
|
||||||
|
Group("albums.id")
|
||||||
|
|
||||||
|
if f.Query != "" {
|
||||||
|
likeString := "%" + strings.ToLower(f.Query) + "%"
|
||||||
|
q = q.Where("LOWER(albums.album_name) LIKE ?", likeString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Favorites {
|
||||||
|
q = q.Where("albums.album_favorite = 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch f.Order {
|
||||||
|
case "slug":
|
||||||
|
q = q.Order("albums.album_favorite DESC, album_slug ASC")
|
||||||
|
default:
|
||||||
|
q = q.Order("albums.album_favorite DESC, album_count DESC, albums.created_at DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Count > 0 && f.Count <= 1000 {
|
||||||
|
q = q.Limit(f.Count).Offset(f.Offset)
|
||||||
|
} else {
|
||||||
|
q = q.Limit(100).Offset(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := q.Scan(&results); result.Error != nil {
|
||||||
|
return results, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
49
internal/repo/files.go
Normal file
49
internal/repo/files.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import "github.com/photoprism/photoprism/internal/models"
|
||||||
|
|
||||||
|
// FindFiles finds files returning maximum results defined by limit
|
||||||
|
// and finding them from an offest defined by offset.
|
||||||
|
func (s *Repo) FindFiles(limit int, offset int) (files []models.File, err error) {
|
||||||
|
if err := s.db.Where(&models.File{}).Limit(limit).Offset(offset).Find(&files).Error; err != nil {
|
||||||
|
return files, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindFilesByUUID
|
||||||
|
func (s *Repo) FindFilesByUUID(u []string, limit int, offset int) (files []models.File, err error) {
|
||||||
|
if err := s.db.Where("(photo_uuid IN (?) AND file_primary = 1) OR file_uuid IN (?)", u, u).Preload("Photo").Limit(limit).Offset(offset).Find(&files).Error; err != nil {
|
||||||
|
return files, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindFileByPhotoUUID
|
||||||
|
func (s *Repo) FindFileByPhotoUUID(u string) (file models.File, err error) {
|
||||||
|
if err := s.db.Where("photo_uuid = ? AND file_primary = 1", u).Preload("Photo").First(&file).Error; err != nil {
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindFileByID returns a mediafile given a certain ID.
|
||||||
|
func (s *Repo) FindFileByID(id string) (file models.File, err error) {
|
||||||
|
if err := s.db.Where("id = ?", id).Preload("Photo").First(&file).Error; err != nil {
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindFileByHash finds a file with a given hash string.
|
||||||
|
func (s *Repo) FindFileByHash(fileHash string) (file models.File, err error) {
|
||||||
|
if err := s.db.Where("file_hash = ?", fileHash).Preload("Photo").First(&file).Error; err != nil {
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
125
internal/repo/labels.go
Normal file
125
internal/repo/labels.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
|
"github.com/photoprism/photoprism/internal/models"
|
||||||
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LabelResult contains found labels
|
||||||
|
type LabelResult struct {
|
||||||
|
// Label
|
||||||
|
ID uint
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt time.Time
|
||||||
|
LabelSlug string
|
||||||
|
LabelName string
|
||||||
|
LabelPriority int
|
||||||
|
LabelCount int
|
||||||
|
LabelFavorite bool
|
||||||
|
LabelDescription string
|
||||||
|
LabelNotes string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindLabelBySlug returns a Label based on the slug name.
|
||||||
|
func (s *Repo) FindLabelBySlug(labelSlug string) (label models.Label, err error) {
|
||||||
|
if err := s.db.Where("label_slug = ?", labelSlug).First(&label).Error; err != nil {
|
||||||
|
return label, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return label, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindLabelThumbBySlug returns a label preview file based on the slug name.
|
||||||
|
func (s *Repo) FindLabelThumbBySlug(labelSlug string) (file models.File, err error) {
|
||||||
|
// s.db.LogMode(true)
|
||||||
|
|
||||||
|
if err := s.db.Where("files.file_primary AND files.deleted_at IS NULL").
|
||||||
|
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").
|
||||||
|
Order("photos_labels.label_uncertainty ASC").
|
||||||
|
First(&file).Error; err != nil {
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Labels searches labels based on their name.
|
||||||
|
func (s *Repo) Labels(f form.LabelSearch) (results []LabelResult, err error) {
|
||||||
|
if err := f.ParseQueryString(); err != nil {
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", f))
|
||||||
|
|
||||||
|
q := s.db.NewScope(nil).DB()
|
||||||
|
|
||||||
|
// q.LogMode(true)
|
||||||
|
|
||||||
|
q = q.Table("labels").
|
||||||
|
Select(`labels.*, COUNT(photos_labels.label_id) AS label_count`).
|
||||||
|
Joins("JOIN photos_labels ON photos_labels.label_id = labels.id").
|
||||||
|
Where("labels.deleted_at IS NULL").
|
||||||
|
Group("labels.id")
|
||||||
|
|
||||||
|
if f.Query != "" {
|
||||||
|
var labelIds []uint
|
||||||
|
var categories []models.Category
|
||||||
|
var label models.Label
|
||||||
|
|
||||||
|
likeString := "%" + strings.ToLower(f.Query) + "%"
|
||||||
|
|
||||||
|
if result := s.db.First(&label, "LOWER(label_name) LIKE LOWER(?)", f.Query); result.Error != nil {
|
||||||
|
log.Infof("search: label \"%s\" not found", f.Query)
|
||||||
|
|
||||||
|
q = q.Where("LOWER(labels.label_name) LIKE ?", likeString)
|
||||||
|
} else {
|
||||||
|
labelIds = append(labelIds, label.ID)
|
||||||
|
|
||||||
|
s.db.Where("category_id = ?", label.ID).Find(&categories)
|
||||||
|
|
||||||
|
for _, category := range categories {
|
||||||
|
labelIds = append(labelIds, category.LabelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("search: label \"%s\" includes %d categories", label.LabelName, len(labelIds))
|
||||||
|
|
||||||
|
q = q.Where("labels.id IN (?) OR LOWER(labels.label_name) LIKE ?", labelIds, likeString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Favorites {
|
||||||
|
q = q.Where("labels.label_favorite = 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Priority != 0 {
|
||||||
|
q = q.Where("labels.label_priority > ?", f.Priority)
|
||||||
|
} else {
|
||||||
|
q = q.Where("labels.label_priority >= -2")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch f.Order {
|
||||||
|
case "slug":
|
||||||
|
q = q.Order("labels.label_favorite DESC, label_slug ASC")
|
||||||
|
default:
|
||||||
|
q = q.Order("labels.label_favorite DESC, labels.label_priority DESC, label_count DESC, labels.created_at DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Count > 0 && f.Count <= 1000 {
|
||||||
|
q = q.Limit(f.Count).Offset(f.Offset)
|
||||||
|
} else {
|
||||||
|
q = q.Limit(100).Offset(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := q.Scan(&results); result.Error != nil {
|
||||||
|
return results, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
324
internal/repo/photos.go
Normal file
324
internal/repo/photos.go
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gosimple/slug"
|
||||||
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
|
"github.com/photoprism/photoprism/internal/models"
|
||||||
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PhotoResult contains found photos and their main file plus other meta data.
|
||||||
|
type PhotoResult struct {
|
||||||
|
// Photo
|
||||||
|
ID uint
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt time.Time
|
||||||
|
TakenAt time.Time
|
||||||
|
TakenAtLocal time.Time
|
||||||
|
TimeZone string
|
||||||
|
PhotoUUID string
|
||||||
|
PhotoPath string
|
||||||
|
PhotoName string
|
||||||
|
PhotoTitle string
|
||||||
|
PhotoDescription string
|
||||||
|
PhotoArtist string
|
||||||
|
PhotoKeywords string
|
||||||
|
PhotoColors string
|
||||||
|
PhotoColor string
|
||||||
|
PhotoFavorite bool
|
||||||
|
PhotoPrivate bool
|
||||||
|
PhotoSensitive bool
|
||||||
|
PhotoStory bool
|
||||||
|
PhotoLat float64
|
||||||
|
PhotoLong float64
|
||||||
|
PhotoAltitude int
|
||||||
|
PhotoFocalLength int
|
||||||
|
PhotoIso int
|
||||||
|
PhotoFNumber float64
|
||||||
|
PhotoExposure string
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
CameraID uint
|
||||||
|
CameraModel string
|
||||||
|
CameraMake string
|
||||||
|
|
||||||
|
// Lens
|
||||||
|
LensID uint
|
||||||
|
LensModel string
|
||||||
|
LensMake string
|
||||||
|
|
||||||
|
// Country
|
||||||
|
CountryID string
|
||||||
|
CountryName string
|
||||||
|
|
||||||
|
// Location
|
||||||
|
LocationID uint
|
||||||
|
LocDisplayName string
|
||||||
|
LocName string
|
||||||
|
LocCity string
|
||||||
|
LocPostcode string
|
||||||
|
LocCounty string
|
||||||
|
LocState string
|
||||||
|
LocCountry string
|
||||||
|
LocCountryCode string
|
||||||
|
LocCategory string
|
||||||
|
LocType string
|
||||||
|
LocationChanged bool
|
||||||
|
LocationEstimated bool
|
||||||
|
|
||||||
|
// File
|
||||||
|
FileID uint
|
||||||
|
FileUUID string
|
||||||
|
FilePrimary bool
|
||||||
|
FileMissing bool
|
||||||
|
FileName string
|
||||||
|
FileHash string
|
||||||
|
FilePerceptualHash string
|
||||||
|
FileType string
|
||||||
|
FileMime string
|
||||||
|
FileWidth int
|
||||||
|
FileHeight int
|
||||||
|
FileOrientation int
|
||||||
|
FileAspectRatio float64
|
||||||
|
|
||||||
|
// List of matching labels and keywords
|
||||||
|
Labels string
|
||||||
|
Keywords string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PhotoResult) DownloadFileName() string {
|
||||||
|
var name string
|
||||||
|
|
||||||
|
if m.PhotoTitle != "" {
|
||||||
|
name = strings.Title(slug.MakeLang(m.PhotoTitle, "en"))
|
||||||
|
} else {
|
||||||
|
name = m.PhotoUUID
|
||||||
|
}
|
||||||
|
|
||||||
|
taken := m.TakenAt.Format("20060102-150405")
|
||||||
|
|
||||||
|
result := fmt.Sprintf("%s-%s.%s", taken, name, m.FileType)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Photos searches for photos based on a Form and returns a PhotoResult slice.
|
||||||
|
func (s *Repo) Photos(f form.PhotoSearch) (results []PhotoResult, err error) {
|
||||||
|
if err := f.ParseQueryString(); err != nil {
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", f))
|
||||||
|
|
||||||
|
q := s.db.NewScope(nil).DB()
|
||||||
|
|
||||||
|
// q.LogMode(true)
|
||||||
|
|
||||||
|
q = q.Table("photos").
|
||||||
|
Select(`photos.*,
|
||||||
|
files.id AS file_id, files.file_uuid, files.file_primary, files.file_missing, files.file_name, files.file_hash,
|
||||||
|
files.file_type, files.file_mime, files.file_width, files.file_height, files.file_aspect_ratio,
|
||||||
|
files.file_orientation, files.file_main_color, files.file_colors, files.file_luminance, files.file_chroma,
|
||||||
|
cameras.camera_make, cameras.camera_model,
|
||||||
|
lenses.lens_make, lenses.lens_model,
|
||||||
|
countries.country_name,
|
||||||
|
locations.loc_display_name, locations.loc_name, locations.loc_city, locations.loc_postcode, locations.loc_county,
|
||||||
|
locations.loc_state, locations.loc_country, locations.loc_country_code, locations.loc_category, locations.loc_type,
|
||||||
|
GROUP_CONCAT(DISTINCT labels.label_name) AS labels,
|
||||||
|
GROUP_CONCAT(DISTINCT keywords.keyword) AS keywords`).
|
||||||
|
Joins("JOIN files ON files.photo_id = photos.id AND files.file_primary AND files.deleted_at IS NULL").
|
||||||
|
Joins("JOIN cameras ON cameras.id = photos.camera_id").
|
||||||
|
Joins("JOIN lenses ON lenses.id = photos.lens_id").
|
||||||
|
Joins("LEFT JOIN countries ON countries.id = photos.country_id").
|
||||||
|
Joins("LEFT JOIN locations ON locations.id = photos.location_id").
|
||||||
|
Joins("LEFT JOIN photos_labels ON photos_labels.photo_id = photos.id").
|
||||||
|
Joins("LEFT JOIN labels ON photos_labels.label_id = labels.id").
|
||||||
|
Joins("LEFT JOIN photos_keywords ON photos_keywords.photo_id = photos.id").
|
||||||
|
Joins("LEFT JOIN keywords ON photos_keywords.keyword_id = keywords.id").
|
||||||
|
Where("photos.deleted_at IS NULL AND files.file_missing = 0").
|
||||||
|
Group("photos.id, files.id")
|
||||||
|
var categories []models.Category
|
||||||
|
var label models.Label
|
||||||
|
var labelIds []uint
|
||||||
|
|
||||||
|
if f.Label != "" {
|
||||||
|
if result := s.db.First(&label, "label_slug = ?", strings.ToLower(f.Label)); result.Error != nil {
|
||||||
|
log.Errorf("search: label \"%s\" not found", f.Label)
|
||||||
|
return results, fmt.Errorf("label \"%s\" not found", f.Label)
|
||||||
|
} else {
|
||||||
|
labelIds = append(labelIds, label.ID)
|
||||||
|
|
||||||
|
s.db.Where("category_id = ?", label.ID).Find(&categories)
|
||||||
|
|
||||||
|
for _, category := range categories {
|
||||||
|
labelIds = append(labelIds, category.LabelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
q = q.Where("labels.id IN (?)", labelIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Location == true {
|
||||||
|
q = q.Where("location_id > 0")
|
||||||
|
|
||||||
|
if f.Query != "" {
|
||||||
|
likeString := "%" + strings.ToLower(f.Query) + "%"
|
||||||
|
q = q.Where("LOWER(locations.loc_display_name) LIKE ?", likeString)
|
||||||
|
}
|
||||||
|
} else if f.Query != "" {
|
||||||
|
slugString := slug.Make(f.Query)
|
||||||
|
lowerString := strings.ToLower(f.Query)
|
||||||
|
likeString := lowerString + "%"
|
||||||
|
|
||||||
|
if result := s.db.First(&label, "label_slug = ?", slugString); result.Error != nil {
|
||||||
|
log.Infof("search: label \"%s\" not found", f.Query)
|
||||||
|
|
||||||
|
q = q.Where("labels.label_slug = ? OR keywords.keyword LIKE ? OR files.file_main_color = ?", slugString, likeString, lowerString)
|
||||||
|
} else {
|
||||||
|
labelIds = append(labelIds, label.ID)
|
||||||
|
|
||||||
|
s.db.Where("category_id = ?", label.ID).Find(&categories)
|
||||||
|
|
||||||
|
for _, category := range categories {
|
||||||
|
labelIds = append(labelIds, category.LabelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("search: label \"%s\" includes %d categories", label.LabelName, len(labelIds))
|
||||||
|
|
||||||
|
q = q.Where("labels.id IN (?) OR keywords.keyword LIKE ? OR files.file_main_color = ?", labelIds, likeString, lowerString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Album != "" {
|
||||||
|
q = q.Joins("JOIN photos_albums ON photos_albums.photo_uuid = photos.photo_uuid").Where("photos_albums.album_uuid = ?", f.Album)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Camera > 0 {
|
||||||
|
q = q.Where("photos.camera_id = ?", f.Camera)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Color != "" {
|
||||||
|
q = q.Where("files.file_main_color = ?", strings.ToLower(f.Color))
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Favorites {
|
||||||
|
q = q.Where("photos.photo_favorite = 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Country != "" {
|
||||||
|
q = q.Where("locations.loc_country_code = ?", f.Country)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Title != "" {
|
||||||
|
q = q.Where("LOWER(photos.photo_title) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Title)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Description != "" {
|
||||||
|
q = q.Where("LOWER(photos.photo_description) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Description)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Notes != "" {
|
||||||
|
q = q.Where("LOWER(photos.photo_notes) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Notes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Hash != "" {
|
||||||
|
q = q.Where("files.file_hash = ?", f.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Duplicate {
|
||||||
|
q = q.Where("files.file_duplicate = 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Portrait {
|
||||||
|
q = q.Where("files.file_portrait = 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Mono {
|
||||||
|
q = q.Where("files.file_chroma = 0")
|
||||||
|
} else if f.Chroma > 9 {
|
||||||
|
q = q.Where("files.file_chroma > ?", f.Chroma)
|
||||||
|
} else if f.Chroma > 0 {
|
||||||
|
q = q.Where("files.file_chroma > 0 AND files.file_chroma <= ?", f.Chroma)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Fmin > 0 {
|
||||||
|
q = q.Where("photos.photo_f_number >= ?", f.Fmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Fmax > 0 {
|
||||||
|
q = q.Where("photos.photo_f_number <= ?", f.Fmax)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Dist == 0 {
|
||||||
|
f.Dist = 20
|
||||||
|
} else if f.Dist > 1000 {
|
||||||
|
f.Dist = 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inaccurate distance search, but probably 'good enough' for now
|
||||||
|
if f.Lat > 0 {
|
||||||
|
latMin := f.Lat - SearchRadius*float64(f.Dist)
|
||||||
|
latMax := f.Lat + SearchRadius*float64(f.Dist)
|
||||||
|
q = q.Where("photos.photo_lat BETWEEN ? AND ?", latMin, latMax)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Long > 0 {
|
||||||
|
longMin := f.Long - SearchRadius*float64(f.Dist)
|
||||||
|
longMax := f.Long + SearchRadius*float64(f.Dist)
|
||||||
|
q = q.Where("photos.photo_long BETWEEN ? AND ?", longMin, longMax)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.Before.IsZero() {
|
||||||
|
q = q.Where("photos.taken_at <= ?", f.Before.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.After.IsZero() {
|
||||||
|
q = q.Where("photos.taken_at >= ?", f.After.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch f.Order {
|
||||||
|
case "newest":
|
||||||
|
q = q.Order("taken_at DESC")
|
||||||
|
case "oldest":
|
||||||
|
q = q.Order("taken_at")
|
||||||
|
case "imported":
|
||||||
|
q = q.Order("created_at DESC")
|
||||||
|
default:
|
||||||
|
q = q.Order("taken_at DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Count > 0 && f.Count <= 1000 {
|
||||||
|
q = q.Limit(f.Count).Offset(f.Offset)
|
||||||
|
} else {
|
||||||
|
q = q.Limit(100).Offset(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := q.Scan(&results); result.Error != nil {
|
||||||
|
return results, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindPhotoByID returns a Photo based on the ID.
|
||||||
|
func (s *Repo) FindPhotoByID(photoID uint64) (photo models.Photo, err error) {
|
||||||
|
if err := s.db.Where("id = ?", photoID).First(&photo).Error; err != nil {
|
||||||
|
return photo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return photo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindPhotoByUUID returns a Photo based on the UUID.
|
||||||
|
func (s *Repo) FindPhotoByUUID(photoUUID string) (photo models.Photo, err error) {
|
||||||
|
if err := s.db.Where("photo_uuid = ?", photoUUID).First(&photo).Error; err != nil {
|
||||||
|
return photo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return photo, nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package photoprism
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -13,7 +13,7 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
|
|
||||||
conf.CreateDirectories()
|
conf.CreateDirectories()
|
||||||
|
|
||||||
search := NewSearch(conf.OriginalsPath(), conf.Db())
|
search := New(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
t.Run("normal query", func(t *testing.T) {
|
t.Run("normal query", func(t *testing.T) {
|
||||||
var f form.PhotoSearch
|
var f form.PhotoSearch
|
||||||
40
internal/repo/repo.go
Normal file
40
internal/repo/repo.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
This package contains PhotoPrism database queries.
|
||||||
|
|
||||||
|
Additional information can be found in our Developer Guide:
|
||||||
|
|
||||||
|
https://github.com/photoprism/photoprism/wiki
|
||||||
|
*/
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = event.Log
|
||||||
|
|
||||||
|
// About 1km ('good enough' for now)
|
||||||
|
const SearchRadius = 0.009
|
||||||
|
|
||||||
|
// Repo searches given an originals path and a db instance.
|
||||||
|
type Repo struct {
|
||||||
|
originalsPath string
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchCount is the total number of search hits.
|
||||||
|
type SearchCount struct {
|
||||||
|
Total int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Repo type with a given path and db instance.
|
||||||
|
func New(originalsPath string, db *gorm.DB) *Repo {
|
||||||
|
instance := &Repo{
|
||||||
|
originalsPath: originalsPath,
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user