People: Use slug as search filter #22

This commit is contained in:
Michael Mayer 2021-09-20 12:36:59 +02:00
parent 94b8a6ad65
commit 04d2cfa650
9 changed files with 93 additions and 11 deletions

View file

@ -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 } };

View file

@ -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 != "" {

View file

@ -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

View file

@ -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)

View file

@ -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\""}

View file

@ -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) {

View file

@ -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))
}

View file

@ -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
}

View file

@ -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"))