diff --git a/frontend/src/common/gallery.js b/frontend/src/common/gallery.js index f32874487..bb9967249 100644 --- a/frontend/src/common/gallery.js +++ b/frontend/src/common/gallery.js @@ -76,7 +76,7 @@ class Gallery { counterEl: false, arrowEl: true, preloaderEl: true, - getImageURLForShare: function() { return gallery.currItem.download_url}, + getImageURLForShare: function() { return gallery.currItem.download_url;}, }; let photosWithSizes = this.photosWithSizes(); diff --git a/internal/forms/forms_test.go b/internal/forms/forms_test.go new file mode 100644 index 000000000..bd8c01df6 --- /dev/null +++ b/internal/forms/forms_test.go @@ -0,0 +1,14 @@ +package forms + +import ( + "os" + "testing" + + log "github.com/sirupsen/logrus" +) + +func TestMain(m *testing.M) { + log.SetLevel(log.DebugLevel) + code := m.Run() + os.Exit(code) +} diff --git a/internal/forms/photo_search.go b/internal/forms/photo_search.go index b0288ee1a..910272876 100644 --- a/internal/forms/photo_search.go +++ b/internal/forms/photo_search.go @@ -1,21 +1,100 @@ package forms import ( + "bytes" + "fmt" + "reflect" + "strconv" + "strings" "time" + "unicode" ) // Query parameters for GET /api/v1/photos type PhotoSearchForm struct { - Query string `form:"q"` - Location bool `form:"location"` - Tags string `form:"tags"` - Cat string `form:"cat"` - Country string `form:"country"` - CameraID int `form:"camera"` - Order string `form:"order"` - Count int `form:"count" binding:"required"` - Offset int `form:"offset"` - Before time.Time `form:"before" time_format:"2006-01-02"` - After time.Time `form:"after" time_format:"2006-01-02"` - FavoritesOnly bool `form:"favorites"` + Query string `form:"q"` + Location bool `form:"location"` + Tags string `form:"tags"` + Cat string `form:"cat"` + Country string `form:"country"` + Color string `form:"color"` + Camera int `form:"camera"` + Order string `form:"order"` + Count int `form:"count" binding:"required"` + Offset int `form:"offset"` + Before time.Time `form:"before" time_format:"2006-01-02"` + After time.Time `form:"after" time_format:"2006-01-02"` + Favorites bool `form:"favorites"` +} + +func (f *PhotoSearchForm) ParseQueryString() (result error) { + var key, value []byte + var escaped, isKeyValue bool + + query := f.Query + + f.Query = "" + + formValues := reflect.ValueOf(f).Elem() + + query = strings.TrimSpace(query) + "\n" + + for _, char := range query { + if unicode.IsSpace(char) && !escaped { + if isKeyValue { + fieldName := string(bytes.Title(bytes.ToLower(key))) + + field := formValues.FieldByName(fieldName) + valueString := string(bytes.ToLower(value)) + + if field.CanSet() { + switch field.Interface().(type) { + case int, int64: + if i, err := strconv.Atoi(valueString); err == nil { + field.SetInt(int64(i)) + } else { + result = err + } + case uint, uint64: + if i, err := strconv.Atoi(valueString); err == nil { + field.SetUint(uint64(i)) + } else { + result = err + } + case string: + field.SetString(valueString) + case bool: + if valueString == "1" || valueString == "true" || valueString == "yes" { + field.SetBool(true) + } else if valueString == "0" || valueString == "false" || valueString == "no" { + field.SetBool(false) + } else { + result = fmt.Errorf("not a bool value: %s", fieldName) + } + default: + result = fmt.Errorf("unsupported field type: %s", fieldName) + } + } else { + result = fmt.Errorf("unknown form field: %s", fieldName) + } + } else { + f.Query = string(bytes.ToLower(key)) + } + + escaped = false + isKeyValue = false + key = key[:0] + value = value[:0] + } else if char == ':' { + isKeyValue = true + } else if char == '"' { + escaped = !escaped + } else if isKeyValue { + value = append(value, byte(char)) + } else { + key = append(key, byte(char)) + } + } + + return result } diff --git a/internal/forms/photo_search_test.go b/internal/forms/photo_search_test.go index cef4e1d56..5733d3fe5 100644 --- a/internal/forms/photo_search_test.go +++ b/internal/forms/photo_search_test.go @@ -4,6 +4,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + log "github.com/sirupsen/logrus" ) func TestPhotoSearchForm(t *testing.T) { @@ -11,3 +13,16 @@ func TestPhotoSearchForm(t *testing.T) { assert.IsType(t, new(PhotoSearchForm), form) } + +func TestParseQueryString(t *testing.T) { + form := &PhotoSearchForm{Query: "tags:foo,bar query:\"fooBar baz\" camera:1"} + + err := form.ParseQueryString() + + log.Debugf("%+v\n", form) + + assert.Nil(t, err) + assert.Equal(t, "foo,bar", form.Tags) + assert.Equal(t, "foobar baz", form.Query) + +} diff --git a/internal/photoprism/search.go b/internal/photoprism/search.go index d8d170453..179c9bb59 100644 --- a/internal/photoprism/search.go +++ b/internal/photoprism/search.go @@ -7,6 +7,8 @@ import ( "github.com/jinzhu/gorm" "github.com/photoprism/photoprism/internal/forms" "github.com/photoprism/photoprism/internal/models" + + log "github.com/sirupsen/logrus" ) // Search searches given an originals path and a db instance. @@ -95,7 +97,13 @@ func NewSearch(originalsPath string, db *gorm.DB) *Search { } // Photos searches for photos based on a Form and returns a PhotoSearchResult slice. -func (s *Search) Photos(form forms.PhotoSearchForm) ([]PhotoSearchResult, error) { +func (s *Search) Photos(form forms.PhotoSearchForm) (results []PhotoSearchResult, err error) { + if err := form.ParseQueryString(); err != nil { + return results, err + } + + log.Infof("%+v\n", form) + q := s.db.NewScope(nil).DB() q = q.Table("photos"). Select(`SQL_CALC_FOUND_ROWS photos.*, @@ -130,8 +138,16 @@ func (s *Search) Photos(form forms.PhotoSearchForm) ([]PhotoSearchResult, error) q = q.Where("tags.tag_label LIKE ? OR LOWER(photo_title) LIKE ? OR LOWER(files.file_main_color) LIKE ?", likeString, likeString, likeString) } - if form.CameraID > 0 { - q = q.Where("photos.camera_id = ?", form.CameraID) + if form.Camera > 0 { + q = q.Where("photos.camera_id = ?", form.Camera) + } + + if form.Color != "" { + q = q.Where("files.file_main_color = ?", form.Color) + } + + if form.Favorites { + q = q.Where("photos.photo_favorite = 1") } if form.Country != "" { @@ -183,8 +199,6 @@ func (s *Search) Photos(form forms.PhotoSearchForm) ([]PhotoSearchResult, error) q = q.Limit(100).Offset(0) } - var results []PhotoSearchResult - if result := q.Scan(&results); result.Error != nil { return results, result.Error } diff --git a/internal/photoprism/search_test.go b/internal/photoprism/search_test.go index 31ac6f058..9c6b070d7 100644 --- a/internal/photoprism/search_test.go +++ b/internal/photoprism/search_test.go @@ -52,7 +52,7 @@ func TestSearch_Photos_Camera(t *testing.T) { var form forms.PhotoSearchForm form.Query = "" - form.CameraID = 2 + form.Camera = 2 form.Count = 3 form.Offset = 0