photoprism/internal/search/subjects.go

167 lines
3.6 KiB
Go

package search
import (
"fmt"
"strings"
"github.com/photoprism/photoprism/pkg/txt"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
)
// Subjects searches subjects and returns them.
func Subjects(f form.SubjectSearch) (results SubjectResults, err error) {
if err := f.ParseQueryString(); err != nil {
return results, err
}
subjTable := entity.Subject{}.TableName()
// Base query.
s := UnscopedDb().Table(subjTable).
Select(fmt.Sprintf("%s.*", subjTable))
// Limit result count.
if f.Count > 0 && f.Count <= MaxResults {
s = s.Limit(f.Count).Offset(f.Offset)
} else {
s = s.Limit(MaxResults).Offset(f.Offset)
}
// Set sort order.
switch f.Order {
case "name":
s = s.Order("subj_name")
case "count":
s = s.Order("file_count DESC")
case "added":
s = s.Order(fmt.Sprintf("%s.created_at DESC", subjTable))
case "relevance":
s = s.Order("subj_favorite DESC, photo_count DESC")
default:
s = s.Order("subj_favorite DESC, subj_name")
}
if f.ID != "" {
s = s.Where(fmt.Sprintf("%s.subj_uid IN (?)", subjTable), strings.Split(f.ID, txt.Or))
if result := s.Scan(&results); result.Error != nil {
return results, result.Error
}
return results, nil
}
// Clip to reasonable size and normalize operators.
f.Query = txt.NormalizeQuery(f.Query)
if f.Query != "" {
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, f.Query) {
s = s.Where("?", gorm.Expr(where))
}
}
if f.Files > 0 {
s = s.Where("file_count >= ?", f.Files)
}
if f.Photos > 0 {
s = s.Where("photo_count >= ?", f.Photos)
}
if f.Type != "" {
s = s.Where("subj_type IN (?)", strings.Split(f.Type, txt.Or))
}
if !f.All {
if txt.Yes(f.Favorite) {
s = s.Where("subj_favorite = 1")
} else if txt.No(f.Favorite) {
s = s.Where("subj_favorite = 0")
}
if !txt.Yes(f.Hidden) {
s = s.Where("subj_hidden = 0")
}
if txt.Yes(f.Private) {
s = s.Where("subj_private = 1")
} else if txt.No(f.Private) {
s = s.Where("subj_private = 0")
}
if txt.Yes(f.Excluded) {
s = s.Where("subj_excluded = 1")
} else if txt.No(f.Excluded) {
s = s.Where("subj_excluded = 0")
}
}
// Omit deleted rows.
s = s.Where(fmt.Sprintf("%s.deleted_at IS NULL", subjTable))
if result := s.Scan(&results); result.Error != nil {
return results, result.Error
}
return results, nil
}
// SubjectUIDs finds subject UIDs matching the search string, and removes names from the remaining query.
func SubjectUIDs(s string) (result []string, names []string, remaining string) {
if s == "" {
return result, names, s
}
type Matches struct {
SubjUID string
SubjName string
SubjAlias string
}
var matches []Matches
wheres := LikeAllNames(Cols{"subj_name", "subj_alias"}, s)
if len(wheres) == 0 {
return result, names, s
}
remaining = s
for _, where := range wheres {
var subj []string
stmt := Db().Model(entity.Subject{})
stmt = stmt.Where("?", gorm.Expr(where))
if err := stmt.Scan(&matches).Error; err != nil {
log.Errorf("search: %s while finding subjects", err)
} else if len(matches) == 0 {
continue
}
for _, m := range matches {
subj = append(subj, m.SubjUID)
names = append(names, m.SubjName)
for _, r := range txt.Words(strings.ToLower(m.SubjName)) {
if len(r) > 1 {
remaining = strings.ReplaceAll(remaining, r, "")
}
}
for _, r := range txt.Words(strings.ToLower(m.SubjAlias)) {
if len(r) > 1 {
remaining = strings.ReplaceAll(remaining, r, "")
}
}
}
result = append(result, strings.Join(subj, txt.Or))
}
return result, names, txt.NormalizeQuery(remaining)
}