From 04d2cfa650e7e4f94aa69aa20e63df4590f82f50 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Mon, 20 Sep 2021 12:36:59 +0200 Subject: [PATCH] People: Use slug as search filter #22 --- frontend/src/model/subject.js | 4 ++-- internal/form/photo_search.go | 10 +++++++++- internal/form/photo_search_geo.go | 10 +++++++++- internal/form/photo_search_geo_test.go | 17 ++++++++++++++++- internal/form/photo_search_test.go | 15 +++++++++++++++ internal/search/photos.go | 13 ++++++++++--- internal/search/photos_geo.go | 13 ++++++++++--- pkg/rnd/uid.go | 15 +++++++++++++++ pkg/rnd/uid_test.go | 7 +++++++ 9 files changed, 93 insertions(+), 11 deletions(-) diff --git a/frontend/src/model/subject.js b/frontend/src/model/subject.js index e877dc91e..6ef6020a3 100644 --- a/frontend/src/model/subject.js +++ b/frontend/src/model/subject.js @@ -63,8 +63,8 @@ export class Subject extends RestModel { } route(view) { - if (!this.Type || this.Type === SubjPerson) { - return { name: view, query: { q: `people:"${this.Name}"` } }; + if (this.Slug && (!this.Type || this.Type === SubjPerson)) { + return { name: view, query: { q: `person:${this.Slug}` } }; } return { name: view, query: { q: "subject:" + this.UID } }; diff --git a/internal/form/photo_search.go b/internal/form/photo_search.go index 9c24c3fb2..55076488d 100644 --- a/internal/form/photo_search.go +++ b/internal/form/photo_search.go @@ -52,6 +52,7 @@ type PhotoSearch struct { Day int `form:"day"` // Moments Face string `form:"face"` // UIDs Subject string `form:"subject"` // UIDs + Person string `form:"person"` // Alias for Subject Subjects string `form:"subjects"` // Text People string `form:"people"` // Alias for Subjects Album string `form:"album"` // UIDs @@ -85,10 +86,17 @@ func (f *PhotoSearch) ParseQueryString() error { if f.Path == "" && f.Folder != "" { f.Path = f.Folder + f.Folder = "" } - if f.Subjects == "" { + if f.Subject == "" && f.Person != "" { + f.Subject = f.Person + f.Person = "" + } + + if f.Subjects == "" && f.People != "" { f.Subjects = f.People + f.People = "" } if f.Filter != "" { diff --git a/internal/form/photo_search_geo.go b/internal/form/photo_search_geo.go index c7bffc0f9..df1a49cfa 100644 --- a/internal/form/photo_search_geo.go +++ b/internal/form/photo_search_geo.go @@ -27,6 +27,7 @@ type PhotoSearchGeo struct { Dist uint `form:"dist"` Face string `form:"face"` // UIDs Subject string `form:"subject"` // UIDs + Person string `form:"person"` // Alias for Subject Subjects string `form:"subjects"` // Text People string `form:"people"` // Alias for Subjects Keywords string `form:"keywords"` @@ -57,10 +58,17 @@ func (f *PhotoSearchGeo) ParseQueryString() error { if f.Path == "" && f.Folder != "" { f.Path = f.Folder + f.Folder = "" } - if f.Subjects == "" { + if f.Subject == "" && f.Person != "" { + f.Subject = f.Person + f.Person = "" + } + + if f.Subjects == "" && f.People != "" { f.Subjects = f.People + f.People = "" } return err diff --git a/internal/form/photo_search_geo_test.go b/internal/form/photo_search_geo_test.go index 70e7583f7..cbf6107e0 100644 --- a/internal/form/photo_search_geo_test.go +++ b/internal/form/photo_search_geo_test.go @@ -19,6 +19,21 @@ func TestGeoSearch(t *testing.T) { assert.Equal(t, "Jens Mander", form.Subjects) }) + t.Run("aliases", func(t *testing.T) { + form := &PhotoSearchGeo{Query: "people:\"Jens & Mander\" folder:Foo person:Bar"} + + err := form.ParseQueryString() + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "", form.Folder) + assert.Equal(t, "", form.Person) + assert.Equal(t, "", form.People) + assert.Equal(t, "Foo", form.Path) + assert.Equal(t, "Bar", form.Subject) + assert.Equal(t, "Jens & Mander", form.Subjects) + }) t.Run("keywords", func(t *testing.T) { form := &PhotoSearchGeo{Query: "keywords:\"Foo Bar\""} @@ -59,7 +74,7 @@ func TestGeoSearch(t *testing.T) { assert.Equal(t, "fooBar baz", form.Query) assert.Equal(t, "test", form.Path) - assert.Equal(t, "test", form.Folder) + assert.Equal(t, "", form.Folder) assert.Equal(t, time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC), form.Before) assert.Equal(t, uint(0x61a8), form.Dist) assert.Equal(t, float32(33.45343), form.Lat) diff --git a/internal/form/photo_search_test.go b/internal/form/photo_search_test.go index 7e69bf38c..6038bf4fa 100644 --- a/internal/form/photo_search_test.go +++ b/internal/form/photo_search_test.go @@ -25,6 +25,21 @@ func TestParseQueryString(t *testing.T) { assert.Equal(t, "Jens & Mander", form.Subjects) }) + t.Run("aliases", func(t *testing.T) { + form := &PhotoSearch{Query: "people:\"Jens & Mander\" folder:Foo person:Bar"} + + err := form.ParseQueryString() + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "", form.Folder) + assert.Equal(t, "", form.Person) + assert.Equal(t, "", form.People) + assert.Equal(t, "Foo", form.Path) + assert.Equal(t, "Bar", form.Subject) + assert.Equal(t, "Jens & Mander", form.Subjects) + }) t.Run("keywords", func(t *testing.T) { form := &PhotoSearch{Query: "keywords:\"Foo Bar\""} diff --git a/internal/search/photos.go b/internal/search/photos.go index ca6a98331..5f0bd5fd1 100644 --- a/internal/search/photos.go +++ b/internal/search/photos.go @@ -5,6 +5,8 @@ import ( "strings" "time" + "github.com/photoprism/photoprism/pkg/rnd" + "github.com/photoprism/photoprism/pkg/fs" "github.com/jinzhu/gorm" @@ -22,7 +24,7 @@ func Photos(f form.PhotoSearch) (results PhotoResults, count int, err error) { } s := UnscopedDb() - // s.LogMode(true) + // s = s.LogMode(true) // Base query. s = s.Table("photos"). @@ -233,8 +235,13 @@ func Photos(f form.PhotoSearch) (results PhotoResults, count int, err error) { // Filter for one or more subjects? if f.Subject != "" { for _, subj := range strings.Split(strings.ToLower(f.Subject), txt.And) { - 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()), strings.Split(subj, txt.Or)) + if subjects := strings.Split(subj, txt.Or); rnd.ContainsUIDs(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) + } else { + 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(AnySlug("s.subj_slug", subj, txt.Or))) + } } } else if f.Subjects != "" { for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, f.Subjects) { diff --git a/internal/search/photos_geo.go b/internal/search/photos_geo.go index d0a60844f..d817311f0 100644 --- a/internal/search/photos_geo.go +++ b/internal/search/photos_geo.go @@ -5,6 +5,8 @@ import ( "strings" "time" + "github.com/photoprism/photoprism/pkg/rnd" + "github.com/photoprism/photoprism/pkg/fs" "github.com/jinzhu/gorm" @@ -123,11 +125,16 @@ func PhotosGeo(f form.PhotoSearchGeo) (results GeoResults, err error) { // Filter for one or more subjects? if f.Subject != "" { for _, subj := range strings.Split(strings.ToLower(f.Subject), txt.And) { - 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()), strings.Split(subj, txt.Or)) + if subjects := strings.Split(subj, txt.Or); rnd.ContainsUIDs(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) + } else { + 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(AnySlug("s.subj_slug", subj, txt.Or))) + } } } else if f.Subjects != "" { - for _, where := range LikeAnyWord("s.subj_name", f.Subjects) { + for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, f.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)) } diff --git a/pkg/rnd/uid.go b/pkg/rnd/uid.go index 3789f5c2f..ad881d48a 100644 --- a/pkg/rnd/uid.go +++ b/pkg/rnd/uid.go @@ -72,3 +72,18 @@ func IsUID(s string, prefix byte) bool { return IsPPID(s, prefix) } + +// ContainsUIDs tests if a slice of strings contains UIDs only. +func ContainsUIDs(s []string, prefix byte) bool { + if len(s) < 1 { + return false + } + + for _, id := range s { + if !IsUID(id, prefix) { + return false + } + } + + return true +} diff --git a/pkg/rnd/uid_test.go b/pkg/rnd/uid_test.go index 4b160f7db..40784fd9c 100644 --- a/pkg/rnd/uid_test.go +++ b/pkg/rnd/uid_test.go @@ -57,6 +57,13 @@ func TestIsUID(t *testing.T) { assert.False(t, IsUID("", '_')) } +func TestContainsUIDs(t *testing.T) { + assert.True(t, ContainsUIDs([]string{"lt9k3pw1wowuy3c2", "ltxk3pwawowuy0c0"}, 'l')) + assert.True(t, ContainsUIDs([]string{"dafbfeb8-a129-4e7c-9cf0-e7996a701cdb"}, 'l')) + assert.False(t, ContainsUIDs([]string{"_"}, '_')) + assert.False(t, ContainsUIDs([]string{""}, '_')) +} + func TestIsLowerAlnum(t *testing.T) { assert.False(t, IsLowerAlnum("dafbfeb8-a129-4e7c-9cf0-e7996a701cdb")) assert.True(t, IsLowerAlnum("dafbe7996a701cdb"))