Labels: Add cache, fix bugs & improve SQL queries

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer
2019-12-16 23:33:52 +01:00
parent 2d490812d2
commit 1cc8cefc92
24 changed files with 181 additions and 68 deletions

View File

@@ -1610,6 +1610,7 @@ zebra:
categories:
- animal
- mammal
- wildlife
hog:
threshold: 0.66
@@ -1629,11 +1630,13 @@ warthog:
categories:
- animal
- mammal
- wildlife
hippopotamus:
label: hippo
categories:
- animal
- wildlife
ox:
label: cow
@@ -1743,10 +1746,10 @@ three-toed sloth:
- animal
ape:
label: ape
priority: 2
categories:
- animal
- ape
- mammal
orangutan:
@@ -1782,6 +1785,7 @@ baboon:
categories:
- animal
- monkey
- wildlife
macaque:
see: monkey

View File

@@ -8,7 +8,7 @@ class Label extends Abstract {
}
getId() {
return this.LabelSlug;
return this.LabelUUID;
}
getTitle() {

View File

@@ -54,11 +54,11 @@
const query = this.$route.query;
this.uuid = this.$route.params.uuid;
this.filter.q = query['q'];
this.filter.q = query['q'] ? query['q'] : '';
this.filter.camera = query['camera'] ? parseInt(query['camera']) : 0;
this.filter.country = query['country'] ? query['country'] : '';
this.settings.view = this.viewType();
this.lastFilter = {};
this.loading = true;
this.routeName = this.$route.name;
this.findAlbum();
this.search();
@@ -94,10 +94,10 @@
methods: {
viewType() {
let queryParam = this.$route.query['view'];
let storedType = window.localStorage.getItem("photo_view_type");
let storedType = window.localStorage.getItem("album_view_type");
if (queryParam) {
window.localStorage.setItem("photo_view_type", queryParam);
window.localStorage.setItem("album_view_type", queryParam);
return queryParam;
} else if (storedType) {
return storedType;
@@ -206,6 +206,7 @@
Object.assign(this.lastFilter, this.filter);
this.offset = 0;
this.loading = true;
const params = this.searchParams();

View File

@@ -7,6 +7,7 @@
<v-text-field class="pt-3 pr-3"
single-line
:label="labels.search"
browser-autocomplete="off"
prepend-inner-icon="search"
clearable
color="secondary-dark"
@@ -18,18 +19,25 @@
<v-spacer></v-spacer>
<v-btn icon @click.stop="refresh" class="hidden-xs-only">
<v-icon>refresh</v-icon>
</v-btn>
<v-btn icon @click.prevent="create">
<v-icon>add</v-icon>
</v-btn>
</v-toolbar>
</v-form>
<v-container fluid class="pa-2">
<v-container fluid class="pa-4" v-if="loading">
<v-progress-linear color="secondary-dark" :indeterminate="true"></v-progress-linear>
</v-container>
<v-container fluid class="pa-0" v-else>
<p-scroll-top></p-scroll-top>
<p-album-clipboard :refresh="refresh" :selection="selection"></p-album-clipboard>
<v-container grid-list-xs fluid class="pa-0 p-albums p-albums-details">
<v-container grid-list-xs fluid class="pa-2 p-albums p-albums-details">
<v-card v-if="results.length === 0" class="p-albums-empty secondary-light lighten-1" flat>
<v-card-title primary-title>
<div>
@@ -134,7 +142,7 @@
'$route'() {
const query = this.$route.query;
this.filter.q = query['q'];
this.filter.q = query['q'] ? query['q'] : '';
this.lastFilter = {};
this.routeName = this.$route.name;
this.search();
@@ -149,6 +157,7 @@
return {
results: [],
loading: true,
scrollDisabled: true,
pageSize: 24,
offset: 0,
@@ -210,6 +219,10 @@
}
}
if (JSON.stringify(this.$route.query) === JSON.stringify(query)) {
return
}
this.$router.replace({query: query});
},
searchParams() {
@@ -238,10 +251,12 @@
Object.assign(this.lastFilter, this.filter);
this.offset = 0;
this.loading = true;
const params = this.searchParams();
Album.search(params).then(response => {
this.loading = false;
this.results = response.models;
this.scrollDisabled = (response.models.length < this.pageSize);
@@ -259,7 +274,7 @@
this.$nextTick(() => this.$emit("scrollRefresh"));
}
});
}).catch(() => this.loading = false);
},
refresh() {
this.lastFilter = {};

View File

@@ -8,6 +8,7 @@
single-line
:label="labels.search"
prepend-inner-icon="search"
browser-autocomplete="off"
clearable
color="secondary-dark"
@click:clear="clearQuery"
@@ -17,14 +18,21 @@
></v-text-field>
<v-spacer></v-spacer>
<v-btn icon @click.stop="refresh" class="hidden-xs-only">
<v-icon>refresh</v-icon>
</v-btn>
</v-toolbar>
</v-form>
<v-container fluid class="pa-2">
<v-container fluid class="pa-4" v-if="loading">
<v-progress-linear color="secondary-dark" :indeterminate="true"></v-progress-linear>
</v-container>
<v-container fluid class="pa-0" v-else>
<p-scroll-top></p-scroll-top>
<v-container grid-list-xs fluid class="pa-0 p-labels p-labels-details">
<v-card v-if="results.length === 0" class="p-labels-empty" flat>
<v-container grid-list-xs fluid class="pa-2 p-labels p-labels-details">
<v-card v-if="results.length === 0" class="p-labels-empty secondary-light lighten-1" flat>
<v-card-title primary-title>
<div>
<h3 class="title mb-3"><translate>No labels matched your search</translate></h3>
@@ -89,7 +97,7 @@
'$route'() {
const query = this.$route.query;
this.filter.q = query['q'];
this.filter.q = query['q'] ? query['q'] : '';
this.lastFilter = {};
this.routeName = this.$route.name;
this.search();
@@ -105,6 +113,7 @@
return {
results: [],
scrollDisabled: true,
loading: true,
pageSize: 24,
offset: 0,
selection: this.$clipboard.selection,
@@ -120,7 +129,7 @@
methods: {
clearQuery() {
this.filter.q = '';
this.search();
this.updateQuery();
},
openLabel(index) {
const label = this.results[index];
@@ -163,6 +172,10 @@
}
}
if (JSON.stringify(this.$route.query) === JSON.stringify(query)) {
return
}
this.$router.replace({query: query});
},
searchParams() {
@@ -179,6 +192,14 @@
return params;
},
refresh() {
this.lastFilter = {};
const pageSize = this.pageSize;
this.pageSize = this.offset + pageSize;
this.search();
this.offset = this.pageSize;
this.pageSize = pageSize;
},
search() {
this.scrollDisabled = true;
@@ -191,10 +212,13 @@
Object.assign(this.lastFilter, this.filter);
this.offset = 0;
this.loading = true;
const params = this.searchParams();
Label.search(params).then(response => {
this.loading = false;
this.results = response.models;
this.scrollDisabled = (response.models.length < this.pageSize);
@@ -206,7 +230,7 @@
this.$nextTick(() => this.$emit("scrollRefresh"));
}
});
}).catch(() => this.loading = false);
},
},
created() {

View File

@@ -47,13 +47,13 @@
'$route'() {
const query = this.$route.query;
this.filter.q = query['q'];
this.filter.q = query['q'] ? query['q'] : '';
this.filter.camera = query['camera'] ? parseInt(query['camera']) : 0;
this.filter.country = query['country'] ? query['country'] : '';
this.filter.before = query['before'] ? query['before'] : '';
this.filter.after = query['after'] ? query['after'] : '';
this.settings.view = this.viewType();
this.lastFilter = {};
this.loading = true;
this.routeName = this.$route.name;
this.search();
}
@@ -197,6 +197,7 @@
Object.assign(this.lastFilter, this.filter);
this.offset = 0;
this.loading = true;
const params = this.searchParams();

View File

@@ -13,49 +13,49 @@ mock
describe("model/label", () => {
it("should get label entity name", () => {
const values = {id: 5, LabelName: "Black Cat", LabelSlug: "black-cat"};
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
const label = new Label(values);
const result = label.getEntityName();
assert.equal(result, "black-cat");
});
it("should get label id", () => {
const values = {id: 5, LabelName: "Black Cat", LabelSlug: "black-cat"};
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
const label = new Label(values);
const result = label.getId();
assert.equal(result, "black-cat");
assert.equal(result, "ABC123");
});
it("should get label title", () => {
const values = {id: 5, LabelName: "Black Cat", LabelSlug: "black-cat"};
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
const label = new Label(values);
const result = label.getTitle();
assert.equal(result, "Black Cat");
});
it("should get thumbnail url", () => {
const values = {id: 5, LabelName: "Black Cat", LabelSlug: "black-cat"};
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
const label = new Label(values);
const result = label.getThumbnailUrl("xyz");
assert.equal(result, "/api/v1/labels/black-cat/thumbnail/xyz");
assert.equal(result, "/api/v1/labels/ABC123/thumbnail/xyz");
});
it("should get thumbnail src set", () => {
const values = {id: 5, LabelName: "Black Cat", LabelSlug: "black-cat"};
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
const label = new Label(values);
const result = label.getThumbnailSrcset("");
assert.equal(result, "/api/v1/labels/black-cat/thumbnail/fit_720 720w, /api/v1/labels/black-cat/thumbnail/fit_1280 1280w, /api/v1/labels/black-cat/thumbnail/fit_1920 1920w, /api/v1/labels/black-cat/thumbnail/fit_2560 2560w, /api/v1/labels/black-cat/thumbnail/fit_3840 3840w");
assert.equal(result, "/api/v1/labels/ABC123/thumbnail/fit_720 720w, /api/v1/labels/ABC123/thumbnail/fit_1280 1280w, /api/v1/labels/ABC123/thumbnail/fit_1920 1920w, /api/v1/labels/ABC123/thumbnail/fit_2560 2560w, /api/v1/labels/ABC123/thumbnail/fit_3840 3840w");
});
it("should get thumbnail sizes", () => {
const values = {id: 5, LabelName: "Black Cat", LabelSlug: "black-cat"};
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
const label = new Label(values);
const result = label.getThumbnailSizes();
assert.equal(result, "(min-width: 2560px) 3840px, (min-width: 1920px) 2560px, (min-width: 1280px) 1920px, (min-width: 720px) 1280px, 720px");
});
it("should get date string", () => {
const values = {ID: 5, LabelName: "Black Cat", LabelSlug: "black-cat", CreatedAt: "2012-07-08T14:45:39Z"};
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat", CreatedAt: "2012-07-08T14:45:39Z"};
const label = new Label(values);
const result = label.getDateString();
assert.equal(result, "Jul 8, 2012, 2:45 PM");
@@ -72,7 +72,7 @@ describe("model/label", () => {
});
it("should like label", () => {
const values = {ID: 5, LabelName: "Black Cat", LabelSlug: "black-cat", LabelFavorite: false};
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat", LabelFavorite: false};
const label = new Label(values);
assert.equal(label.LabelFavorite, false);
label.like();
@@ -80,7 +80,7 @@ describe("model/label", () => {
});
it("should unlike label", () => {
const values = {ID: 5, LabelName: "Black Cat", LabelSlug: "black-cat", LabelFavorite: true};
const values = {ID: 5, LabelUUID: "ABC123",LabelName: "Black Cat", LabelSlug: "black-cat", LabelFavorite: true};
const label = new Label(values);
assert.equal(label.LabelFavorite, true);
label.unlike();
@@ -88,7 +88,7 @@ describe("model/label", () => {
});
it("should toggle like", () => {
const values = {ID: 5, LabelName: "Black Cat", LabelSlug: "black-cat", LabelFavorite: true};
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat", LabelFavorite: true};
const label = new Label(values);
assert.equal(label.LabelFavorite, true);
label.toggleLike();

View File

@@ -2,13 +2,16 @@ package api
import (
"fmt"
"io/ioutil"
"net/http"
"path"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/repo"
@@ -41,12 +44,12 @@ func GetLabels(router *gin.RouterGroup, conf *config.Config) {
})
}
// POST /api/v1/labels/:slug/like
// POST /api/v1/labels/:uuid/like
//
// Parameters:
// slug: string Label slug name
// uuid: string Label UUID
func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
router.POST("/labels/:slug/like", func(c *gin.Context) {
router.POST("/labels/:uuid/like", func(c *gin.Context) {
if Unauthorized(c, conf) {
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
return
@@ -54,7 +57,7 @@ func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
r := repo.New(conf.OriginalsPath(), conf.Db())
label, err := r.FindLabelBySlug(c.Param("slug"))
label, err := r.FindLabelByUUID(c.Param("uuid"))
if err != nil {
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
@@ -64,16 +67,22 @@ func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
label.LabelFavorite = true
conf.Db().Save(&label)
if label.LabelPriority < 0 {
event.Publish("count.labels", event.Data{
"count": 1,
})
}
c.JSON(http.StatusOK, http.Response{})
})
}
// DELETE /api/v1/labels/:slug/like
// DELETE /api/v1/labels/:uuid/like
//
// Parameters:
// slug: string Label slug name
// uuid: string Label UUID
func DislikeLabel(router *gin.RouterGroup, conf *config.Config) {
router.DELETE("/labels/:slug/like", func(c *gin.Context) {
router.DELETE("/labels/:uuid/like", func(c *gin.Context) {
if Unauthorized(c, conf) {
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
return
@@ -81,7 +90,7 @@ func DislikeLabel(router *gin.RouterGroup, conf *config.Config) {
r := repo.New(conf.OriginalsPath(), conf.Db())
label, err := r.FindLabelBySlug(c.Param("slug"))
label, err := r.FindLabelByUUID(c.Param("uuid"))
if err != nil {
c.AbortWithStatusJSON(404, gin.H{"error": util.UcFirst(err.Error())})
@@ -91,20 +100,28 @@ func DislikeLabel(router *gin.RouterGroup, conf *config.Config) {
label.LabelFavorite = false
conf.Db().Save(&label)
if label.LabelPriority < 0 {
event.Publish("count.labels", event.Data{
"count": -1,
})
}
c.JSON(http.StatusOK, http.Response{})
})
}
// GET /api/v1/labels/:slug/thumbnail/:type
// GET /api/v1/labels/:uuid/thumbnail/:type
//
// Example: /api/v1/labels/cheetah/thumbnail/tile_500
//
// Parameters:
// slug: string Label slug name
// uuid: string Label UUID
// type: string Thumbnail type, see photoprism.ThumbnailTypes
func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
router.GET("/labels/:slug/thumbnail/:type", func(c *gin.Context) {
router.GET("/labels/:uuid/thumbnail/:type", func(c *gin.Context) {
typeName := c.Param("type")
labelUUID := c.Param("uuid")
start := time.Now()
thumbType, ok := photoprism.ThumbnailTypes[typeName]
@@ -116,11 +133,16 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
r := repo.New(conf.OriginalsPath(), conf.Db())
// log.Infof("Searching for label slug: %s", c.Param("slug"))
gc := conf.Cache()
cacheKey := fmt.Sprintf("label-thumbnail:%s:%s", labelUUID, typeName)
file, err := r.FindLabelThumbBySlug(c.Param("slug"))
if cacheData, ok := gc.Get(cacheKey); ok {
log.Debugf("%s cache hit [%s]", cacheKey, time.Since(start))
c.Data(http.StatusOK, "image/jpeg", cacheData.([]byte))
return
}
// log.Infof("Label thumb file: %#v", file)
file, err := r.FindLabelThumbByUUID(labelUUID)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": util.UcFirst(err.Error())})
@@ -140,16 +162,24 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
}
if thumbnail, err := photoprism.ThumbnailFromFile(fileName, file.FileHash, conf.ThumbnailsPath(), thumbType.Width, thumbType.Height, thumbType.Options...); err == nil {
if c.Query("download") != "" {
downloadFileName := file.DownloadFileName()
thumbData, err := ioutil.ReadFile(thumbnail)
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", downloadFileName))
if err != nil {
log.Errorf("could not read thumbnail: %s", err)
c.Data(http.StatusInternalServerError, "image/svg+xml", photoIconSvg)
return
}
c.File(thumbnail)
gc.Set(cacheKey, thumbData, time.Hour * 4)
log.Debugf("%s cached [%s]", cacheKey, time.Since(start))
c.Data(http.StatusOK, "image/jpeg", thumbData)
} else {
log.Errorf("could not create thumbnail: %s", err)
c.Data(http.StatusBadRequest, "image/svg+xml", photoIconSvg)
c.Data(http.StatusInternalServerError, "image/svg+xml", photoIconSvg)
return
}
})
}

View File

@@ -595,7 +595,7 @@ func (c *Config) ClientConfig() ClientConfig {
db.Table("labels").
Select("COUNT(*) AS labels").
Where("label_priority >= 0 && deleted_at IS NULL").
Where("(label_priority >= 0 || label_favorite = 1) && deleted_at IS NULL").
Take(&count)
db.Table("albums").

View File

@@ -7,7 +7,6 @@ import (
"github.com/gosimple/slug"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/util"
uuid "github.com/satori/go.uuid"
)
// Photo album
@@ -31,7 +30,7 @@ type Album struct {
}
func (m *Album) BeforeCreate(scope *gorm.Scope) error {
if err := scope.SetColumn("AlbumUUID", uuid.NewV4().String()); err != nil {
if err := scope.SetColumn("AlbumUUID", util.UUID()); err != nil {
return err
}

View File

@@ -4,8 +4,7 @@ import (
"time"
"github.com/jinzhu/gorm"
uuid "github.com/satori/go.uuid"
"github.com/photoprism/photoprism/internal/util"
)
// Events
@@ -31,5 +30,5 @@ func (Event) TableName() string {
}
func (e *Event) BeforeCreate(scope *gorm.Scope) error {
return scope.SetColumn("EventUUID", uuid.NewV4().String())
return scope.SetColumn("EventUUID", util.UUID())
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/gosimple/slug"
"github.com/jinzhu/gorm"
uuid "github.com/satori/go.uuid"
"github.com/photoprism/photoprism/internal/util"
)
// An image or sidecar file that belongs to a photo
@@ -49,7 +49,7 @@ func FindFileByHash(db *gorm.DB, fileHash string) (File, error) {
}
func (m *File) BeforeCreate(scope *gorm.Scope) error {
return scope.SetColumn("FileUUID", uuid.NewV4().String())
return scope.SetColumn("FileUUID", util.UUID())
}
func (m *File) DownloadFileName() string {

View File

@@ -5,11 +5,13 @@ import (
"github.com/gosimple/slug"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/util"
)
// Labels for photo, album and location categorization
type Label struct {
Model
LabelUUID string `gorm:"unique_index;"`
LabelSlug string `gorm:"type:varchar(128);index;"`
LabelName string `gorm:"type:varchar(128);"`
LabelPriority int
@@ -20,6 +22,14 @@ type Label struct {
New bool `gorm:"-"`
}
func (m *Label) BeforeCreate(scope *gorm.Scope) error {
if err := scope.SetColumn("LabelUUID", util.UUID()); err != nil {
return err
}
return nil
}
func NewLabel(labelName string, labelPriority int) *Label {
labelName = strings.TrimSpace(labelName)

View File

@@ -6,7 +6,6 @@ import (
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/util"
uuid "github.com/satori/go.uuid"
)
// A photo can have multiple images and sidecar files
@@ -55,7 +54,7 @@ type Photo struct {
}
func (m *Photo) BeforeCreate(scope *gorm.Scope) error {
if err := scope.SetColumn("PhotoUUID", uuid.NewV4().String()); err != nil {
if err := scope.SetColumn("PhotoUUID", util.UUID()); err != nil {
return err
}

View File

@@ -5,8 +5,6 @@ import (
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/util"
uuid "github.com/satori/go.uuid"
)
// Shared photos and/or albums
@@ -14,6 +12,7 @@ type Share struct {
ShareUUID string `gorm:"primary_key;auto_increment:false"`
PhotoUUID string
AlbumUUID string
LabelUUID string
ShareViews uint
ShareUrl string `gorm:"type:varchar(64);"`
ShareToken string `gorm:"type:varchar(64);"`
@@ -31,7 +30,7 @@ func (Share) TableName() string {
}
func (s *Share) BeforeCreate(scope *gorm.Scope) error {
if err := scope.SetColumn("ShareUUID", uuid.NewV4().String()); err != nil {
if err := scope.SetColumn("ShareUUID", util.UUID()); err != nil {
return err
}

View File

@@ -5,6 +5,7 @@ import (
"strings"
"time"
"github.com/gosimple/slug"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/util"
@@ -17,6 +18,7 @@ type LabelResult struct {
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
LabelUUID string
LabelSlug string
LabelName string
LabelPriority int
@@ -35,6 +37,15 @@ func (s *Repo) FindLabelBySlug(labelSlug string) (label entity.Label, err error)
return label, nil
}
// FindLabelByUUID returns a Label based on the label UUID.
func (s *Repo) FindLabelByUUID(labelUUID string) (label entity.Label, err error) {
if err := s.db.Where("label_uuid = ?", labelUUID).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 entity.File, err error) {
// s.db.LogMode(true)
@@ -50,6 +61,21 @@ func (s *Repo) FindLabelThumbBySlug(labelSlug string) (file entity.File, err err
return file, nil
}
// FindLabelThumbByUUID returns a label preview file based on the label UUID.
func (s *Repo) FindLabelThumbByUUID(labelUUID string) (file entity.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_uuid = ?", labelUUID).
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 {
@@ -63,8 +89,7 @@ func (s *Repo) Labels(f form.LabelSearch) (results []LabelResult, err error) {
// 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").
Select(`labels.*`).
Where("labels.deleted_at IS NULL").
Group("labels.id")
@@ -73,9 +98,10 @@ func (s *Repo) Labels(f form.LabelSearch) (results []LabelResult, err error) {
var categories []entity.Category
var label entity.Label
slugString := slug.Make(f.Query)
likeString := "%" + strings.ToLower(f.Query) + "%"
if result := s.db.First(&label, "LOWER(label_name) LIKE LOWER(?)", f.Query); result.Error != nil {
if result := s.db.First(&label, "label_slug = ?", slugString); result.Error != nil {
log.Infof("search: label \"%s\" not found", f.Query)
q = q.Where("LOWER(labels.label_name) LIKE ?", likeString)
@@ -90,7 +116,7 @@ func (s *Repo) Labels(f form.LabelSearch) (results []LabelResult, err error) {
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)
q = q.Where("labels.id IN (?)", labelIds)
}
}
@@ -101,7 +127,7 @@ func (s *Repo) Labels(f form.LabelSearch) (results []LabelResult, err error) {
if f.Priority != 0 {
q = q.Where("labels.label_priority > ?", f.Priority)
} else {
q = q.Where("labels.label_priority >= 0")
q = q.Where("labels.label_priority >= 0 OR labels.label_favorite = 1")
}
switch f.Order {

View File

@@ -3,6 +3,8 @@ package util
import (
"crypto/rand"
"fmt"
uuid "github.com/satori/go.uuid"
)
func RandomToken(size int) string {
@@ -14,3 +16,7 @@ func RandomToken(size int) string {
return fmt.Sprintf("%x", b)
}
func UUID() string {
return uuid.NewV4().String()
}