2021-09-18 15:32:39 +02:00
|
|
|
package search
|
2021-09-02 16:12:31 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
2021-09-18 15:32:39 +02:00
|
|
|
|
|
|
|
"github.com/photoprism/photoprism/pkg/txt"
|
2021-09-02 16:12:31 +02:00
|
|
|
|
|
|
|
"github.com/jinzhu/gorm"
|
|
|
|
"github.com/photoprism/photoprism/internal/entity"
|
|
|
|
"github.com/photoprism/photoprism/internal/form"
|
|
|
|
)
|
|
|
|
|
2021-09-18 15:32:39 +02:00
|
|
|
// Subjects searches subjects and returns them.
|
|
|
|
func Subjects(f form.SubjectSearch) (results SubjectResults, err error) {
|
2021-09-02 16:12:31 +02:00
|
|
|
if err := f.ParseQueryString(); err != nil {
|
|
|
|
return results, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Base query.
|
|
|
|
s := UnscopedDb().Table(entity.Subject{}.TableName()).
|
2021-09-17 18:51:24 +02:00
|
|
|
Select(fmt.Sprintf("%s.*, m.file_hash, m.crop_area", entity.Subject{}.TableName()))
|
|
|
|
|
|
|
|
// Join markers table for face thumbs.
|
|
|
|
s = s.Joins(fmt.Sprintf("LEFT JOIN %s m ON m.marker_uid = %s.marker_uid", entity.Marker{}.TableName(), entity.Subject{}.TableName()))
|
2021-09-02 16:12:31 +02:00
|
|
|
|
|
|
|
// 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 {
|
2021-09-17 14:26:12 +02:00
|
|
|
case "name":
|
|
|
|
s = s.Order("subj_name")
|
2021-09-02 16:12:31 +02:00
|
|
|
case "count":
|
|
|
|
s = s.Order("file_count DESC")
|
2021-09-17 14:26:12 +02:00
|
|
|
case "added":
|
2021-09-17 18:51:24 +02:00
|
|
|
s = s.Order(fmt.Sprintf("%s.created_at DESC", entity.Subject{}.TableName()))
|
2021-09-17 14:26:12 +02:00
|
|
|
case "relevance":
|
|
|
|
s = s.Order("subj_favorite DESC, subj_name")
|
2021-09-02 16:12:31 +02:00
|
|
|
default:
|
2021-09-17 14:26:12 +02:00
|
|
|
s = s.Order("subj_favorite DESC, subj_name")
|
2021-09-02 16:12:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if f.ID != "" {
|
2021-09-18 15:32:39 +02:00
|
|
|
s = s.Where(fmt.Sprintf("%s.subj_uid IN (?)", entity.Subject{}.TableName()), strings.Split(f.ID, txt.Or))
|
2021-09-02 16:12:31 +02:00
|
|
|
|
|
|
|
if result := s.Scan(&results); result.Error != nil {
|
|
|
|
return results, result.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
2021-09-19 17:48:48 +02:00
|
|
|
// Clip to reasonable size and normalize operators.
|
|
|
|
f.Query = txt.NormalizeQuery(f.Query)
|
|
|
|
|
2021-09-02 16:12:31 +02:00
|
|
|
if f.Query != "" {
|
2021-09-19 17:48:48 +02:00
|
|
|
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, f.Query) {
|
|
|
|
s = s.Where("?", gorm.Expr(where))
|
2021-09-02 16:12:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-17 18:51:24 +02:00
|
|
|
if f.Files > 0 {
|
|
|
|
s = s.Where("file_count >= ?", f.Files)
|
|
|
|
}
|
|
|
|
|
2021-09-02 16:12:31 +02:00
|
|
|
if f.Type != "" {
|
2021-09-18 15:32:39 +02:00
|
|
|
s = s.Where("subj_type IN (?)", strings.Split(f.Type, txt.Or))
|
2021-09-02 16:12:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if f.Favorite {
|
2021-09-17 14:26:12 +02:00
|
|
|
s = s.Where("subj_favorite = 1")
|
2021-09-02 16:12:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if f.Private {
|
2021-09-17 14:26:12 +02:00
|
|
|
s = s.Where("subj_private = 1")
|
2021-09-02 16:12:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if f.Excluded {
|
2021-09-17 14:26:12 +02:00
|
|
|
s = s.Where("subj_excluded = 1")
|
2021-09-02 16:12:31 +02:00
|
|
|
}
|
|
|
|
|
2021-09-17 14:26:12 +02:00
|
|
|
// Omit deleted rows.
|
2021-09-17 18:51:24 +02:00
|
|
|
s = s.Where(fmt.Sprintf("%s.deleted_at IS NULL", entity.Subject{}.TableName()))
|
2021-09-02 16:12:31 +02:00
|
|
|
|
|
|
|
if result := s.Scan(&results); result.Error != nil {
|
|
|
|
return results, result.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
2021-09-18 15:32:39 +02:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|