Backend: Move photo description to separate table

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-03-26 09:41:33 +01:00
parent ccd92a4715
commit eedaae8f91
12 changed files with 246 additions and 212 deletions

View file

@ -228,11 +228,11 @@
placeholder=""
:rows="1"
color="secondary-dark"
v-model="model.PhotoDescription"
v-model="model.Description.PhotoDescription"
></v-textarea>
</v-flex>
<v-flex xs12 class="pa-2">
<v-flex xs12 md6 class="pa-2">
<v-textarea
hide-details
auto-grow
@ -240,30 +240,10 @@
placeholder=""
:rows="1"
color="secondary-dark"
v-model="model.PhotoKeywords"
v-model="model.Description.PhotoKeywords"
></v-textarea>
</v-flex>
<v-flex xs12 sm6 md3 class="pa-2">
<v-text-field
hide-details
label="Copyright"
placeholder=""
color="secondary-dark"
v-model="model.PhotoCopyright"
></v-text-field>
</v-flex>
<v-flex xs12 sm6 md3 class="pa-2">
<v-text-field
hide-details
label="Artist"
placeholder=""
color="secondary-dark"
v-model="model.PhotoArtist"
></v-text-field>
</v-flex>
<v-flex xs12 md6 class="pa-2">
<v-textarea
hide-details
@ -272,7 +252,51 @@
placeholder=""
:rows="1"
color="secondary-dark"
v-model="model.PhotoNotes"
v-model="model.Description.PhotoNotes"
></v-textarea>
</v-flex>
<v-flex xs12 sm6 md3 class="pa-2">
<v-textarea
hide-details
auto-grow
label="Subject"
placeholder=""
:rows="1"
color="secondary-dark"
v-model="model.Description.PhotoSubject"
></v-textarea>
</v-flex>
<v-flex xs12 sm6 md3 class="pa-2">
<v-text-field
hide-details
label="Artist"
placeholder=""
color="secondary-dark"
v-model="model.Description.PhotoArtist"
></v-text-field>
</v-flex>
<v-flex xs12 sm6 md3 class="pa-2">
<v-text-field
hide-details
label="Copyright"
placeholder=""
color="secondary-dark"
v-model="model.Description.PhotoCopyright"
></v-text-field>
</v-flex>
<v-flex xs12 sm6 md3 class="pa-2">
<v-textarea
hide-details
auto-grow
label="License"
placeholder=""
:rows="1"
color="secondary-dark"
v-model="model.Description.PhotoLicense"
></v-textarea>
</v-flex>
@ -309,9 +333,7 @@
return {
config: this.$config.values,
all: {
countries: [{code: "", name: this.$gettext("Unknown")}],
cameras: [{ID: 0, CameraModel: this.$gettext("Unknown")}],
lenses: [{ID: 0, LensModel: "Unknown"}],
countries: [{code: "", name: ""}],
colors: [{label: "Unknown", name: ""}],
},
readonly: this.$config.getValue("readonly"),
@ -380,10 +402,10 @@
return this.all.countries.concat(this.config.countries);
},
cameraOptions() {
return this.all.cameras.concat(this.config.cameras);
return this.config.cameras;
},
lensOptions() {
return this.all.lenses.concat(this.config.lenses);
return this.config.lenses;
},
colorOptions() {
return this.all.colors.concat(this.config.colors);

View file

@ -18,7 +18,12 @@ class Abstract {
for (let key in values) {
if (values.hasOwnProperty(key) && key !== "__originalValues") {
this[key] = values[key];
this.__originalValues[key] = values[key];
if(typeof values[key] === "object") {
this.__originalValues[key] = JSON.parse(JSON.stringify(values[key]));
} else {
this.__originalValues[key] = values[key];
}
}
}
@ -48,7 +53,7 @@ class Abstract {
val = this[key];
}
if(!changed || val !== this.__originalValues[key]) {
if(!changed || JSON.stringify(val) !== JSON.stringify(this.__originalValues[key])) {
result[key] = val;
}
}

View file

@ -11,11 +11,6 @@ class Photo extends Abstract {
PhotoPath: "",
PhotoName: "",
PhotoTitle: "",
PhotoDescription: "",
PhotoNotes: "",
PhotoKeywords: "",
PhotoArtist: "",
PhotoCopyright: "",
PhotoFavorite: false,
PhotoPrivate: false,
PhotoNSFW: false,
@ -42,11 +37,21 @@ class Photo extends Abstract {
PhotoYear: 0,
PhotoMonth: 0,
TakenAtLocal: "",
ModifiedDate: false,
ModifiedTitle: false,
ModifiedDetails: false,
ModifiedDescription: false,
ModifiedDate: false,
ModifiedLocation: false,
ModifiedCamera: false,
TimeZone: "",
Description: {
PhotoDescription: "",
PhotoKeywords: "",
PhotoNotes: "",
PhotoSubject: "",
PhotoArtist: "",
PhotoCopyright: "",
PhotoLicense: "",
},
Files: [],
Labels: [],
Keywords: [],
@ -228,11 +233,11 @@ class Photo extends Abstract {
values.ModifiedTitle = true
}
if(values.PhotoKeywords) {
values.ModifiedKeywords = true
if(values.Description) {
values.ModifiedDescription = true
}
if(values.PhotoLat || values.PhotoLng || values.PhotoAltitude) {
if(values.PhotoLat || values.PhotoLng || values.PhotoAltitude || values.PhotoCountry) {
values.ModifiedLocation = true
}
@ -240,8 +245,8 @@ class Photo extends Abstract {
values.ModifiedDate = true
}
if(values.CameraID || values.LensID || values.PhotoCountry) {
values.ModifiedDetails = true
if(values.CameraID || values.LensID) {
values.ModifiedCamera = true
}
return Api.put(this.getEntityResource(), values).then((response) => Promise.resolve(this.setValues(response.data)));

View file

@ -63,6 +63,7 @@ func (c *Config) MigrateDb() {
&entity.Account{},
&entity.File{},
&entity.Photo{},
&entity.Description{},
&entity.Event{},
&entity.Place{},
&entity.Location{},

View file

@ -0,0 +1,57 @@
package entity
import (
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/mutex"
)
// Description stores additional metadata fields for each photo to improve search performance.
type Description struct {
PhotoID uint `gorm:"primary_key;auto_increment:false"`
PhotoDescription string `gorm:"type:text;" json:"PhotoDescription"`
PhotoKeywords string `gorm:"type:text;" json:"PhotoKeywords"`
PhotoNotes string `gorm:"type:text;" json:"PhotoNotes"`
PhotoSubject string `json:"PhotoSubject"`
PhotoArtist string `json:"PhotoArtist"`
PhotoCopyright string `json:"PhotoCopyright"`
PhotoLicense string `json:"PhotoLicense"`
}
// FirstOrCreate returns the matching entity or creates a new one.
func (m *Description) FirstOrCreate(db *gorm.DB) error {
mutex.Db.Lock()
defer mutex.Db.Unlock()
return db.FirstOrCreate(m, "photo_id = ?", m.PhotoID).Error
}
// NoDescription checks if the photo has no Description
func (m *Description) NoDescription() bool {
return m.PhotoDescription == ""
}
// NoKeywords checks if the photo has no Keywords
func (m *Description) NoKeywords() bool {
return m.PhotoKeywords == ""
}
// NoSubject checks if the photo has no Subject
func (m *Description) NoSubject() bool {
return m.PhotoSubject == ""
}
// NoNotes checks if the photo has no Notes
func (m *Description) NoNotes() bool {
return m.PhotoNotes == ""
}
// NoArtist checks if the photo has no Artist
func (m *Description) NoArtist() bool {
return m.PhotoArtist == ""
}
// NoCopyright checks if the photo has no Copyright
func (m *Description) NoCopyright() bool {
return m.PhotoCopyright == ""
}

View file

@ -1,6 +1,7 @@
package entity
import (
"strings"
"time"
"github.com/jinzhu/gorm"
@ -18,13 +19,6 @@ type Photo struct {
PhotoPath string `gorm:"type:varbinary(512);index;"`
PhotoName string `gorm:"type:varbinary(256);"`
PhotoTitle string `json:"PhotoTitle"`
PhotoSubject string `json:"PhotoSubject"`
PhotoKeywords string `json:"PhotoKeywords"`
PhotoDescription string `gorm:"type:text;" json:"PhotoDescription"`
PhotoNotes string `gorm:"type:text;" json:"PhotoNotes"`
PhotoArtist string `json:"PhotoArtist"`
PhotoCopyright string `json:"PhotoCopyright"`
PhotoLicense string `json:"PhotoLicense"`
PhotoFavorite bool `json:"PhotoFavorite"`
PhotoPrivate bool `json:"PhotoPrivate"`
PhotoNSFW bool `json:"PhotoNSFW"`
@ -39,30 +33,31 @@ type Photo struct {
CameraID uint `gorm:"index:idx_photos_camera_lens;" json:"CameraID"`
CameraSerial string `gorm:"type:varbinary(128);" json:"CameraSerial"`
LensID uint `gorm:"index:idx_photos_camera_lens;" json:"LensID"`
AccountID uint `json:"AccountID"`
PlaceID string `gorm:"type:varbinary(16);index;" json:"PlaceID"`
LocationID string `gorm:"type:varbinary(16);index;" json:"LocationID"`
LocationEstimated bool `json:"LocationEstimated"`
PhotoCountry string `gorm:"index:idx_photos_country_year_month;" json:"PhotoCountry"`
PhotoYear int `gorm:"index:idx_photos_country_year_month;"`
PhotoMonth int `gorm:"index:idx_photos_country_year_month;"`
TimeZone string `gorm:"type:varbinary(64);" json:"TimeZone"`
TakenAtLocal time.Time `gorm:"type:datetime;"`
ModifiedTitle bool `json:"ModifiedTitle"`
ModifiedDetails bool `json:"ModifiedDetails"`
ModifiedLocation bool `json:"ModifiedLocation"`
ModifiedKeywords bool `json:"ModifiedKeywords"`
ModifiedDate bool `json:"ModifiedDate"`
Camera *Camera `json:"Camera"`
Lens *Lens `json:"Lens"`
Location *Location `json:"-"`
Place *Place `json:"-"`
Account *Account `json:"-"`
Files []File
Labels []PhotoLabel
Keywords []Keyword `json:"-"`
Albums []Album `json:"-"`
CreatedAt time.Time
AccountID uint `json:"AccountID"`
PlaceID string `gorm:"type:varbinary(16);index;" json:"PlaceID"`
LocationID string `gorm:"type:varbinary(16);index;" json:"LocationID"`
LocationEstimated bool `json:"LocationEstimated"`
PhotoCountry string `gorm:"index:idx_photos_country_year_month;" json:"PhotoCountry"`
PhotoYear int `gorm:"index:idx_photos_country_year_month;"`
PhotoMonth int `gorm:"index:idx_photos_country_year_month;"`
TimeZone string `gorm:"type:varbinary(64);" json:"TimeZone"`
TakenAtLocal time.Time `gorm:"type:datetime;"`
ModifiedTitle bool `json:"ModifiedTitle"`
ModifiedDescription bool `json:"ModifiedDescription"`
ModifiedDate bool `json:"ModifiedDate"`
ModifiedLocation bool `json:"ModifiedLocation"`
ModifiedCamera bool `json:"ModifiedCamera"`
Description Description `json:"Description"`
Camera *Camera `json:"Camera"`
Lens *Lens `json:"Lens"`
Location *Location `json:"-"`
Place *Place `json:"-"`
Account *Account `json:"-"`
Files []File
Labels []PhotoLabel
Keywords []Keyword `json:"-"`
Albums []Album `json:"-"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
@ -73,6 +68,16 @@ func SavePhoto(model Photo, form form.Photo, db *gorm.DB) error {
return err
}
if form.Description.PhotoID == model.ID {
if err := deepcopier.Copy(&model.Description).From(form.Description); err != nil {
return err
}
model.Description.PhotoKeywords = strings.Join(txt.UniqueKeywords(model.Description.PhotoKeywords), ", ")
}
log.Debugf("model: %+v", model)
model.IndexKeywords(db)
return db.Save(&model).Error
@ -123,10 +128,10 @@ func (m *Photo) IndexKeywords(db *gorm.DB) {
// Add title, description and other keywords
keywords = append(keywords, txt.Keywords(m.PhotoTitle)...)
keywords = append(keywords, txt.Keywords(m.PhotoKeywords)...)
keywords = append(keywords, txt.Keywords(m.PhotoSubject)...)
keywords = append(keywords, txt.Keywords(m.PhotoArtist)...)
keywords = append(keywords, txt.Keywords(m.PhotoDescription)...)
keywords = append(keywords, txt.Keywords(m.Description.PhotoDescription)...)
keywords = append(keywords, txt.Keywords(m.Description.PhotoKeywords)...)
keywords = append(keywords, txt.Keywords(m.Description.PhotoSubject)...)
keywords = append(keywords, txt.Keywords(m.Description.PhotoArtist)...)
keywords = txt.UniqueWords(keywords)
@ -223,36 +228,6 @@ func (m *Photo) NoTitle() bool {
return m.PhotoTitle == ""
}
// NoDescription checks if the photo has no Description
func (m *Photo) NoDescription() bool {
return m.PhotoDescription == ""
}
// NoNotes checks if the photo has no Notes
func (m *Photo) NoNotes() bool {
return m.PhotoNotes == ""
}
// NoArtist checks if the photo has no Artist
func (m *Photo) NoArtist() bool {
return m.PhotoArtist == ""
}
// NoCopyright checks if the photo has no Copyright
func (m *Photo) NoCopyright() bool {
return m.PhotoCopyright == ""
}
// NoSubject checks if the photo has no Subject
func (m *Photo) NoSubject() bool {
return m.PhotoSubject == ""
}
// NoKeywords checks if the photo has no Keywords
func (m *Photo) NoKeywords() bool {
return m.PhotoKeywords == ""
}
// NoCameraSerial checks if the photo has no CameraSerial
func (m *Photo) NoCameraSerial() bool {
return m.CameraSerial == ""

View file

@ -8,13 +8,18 @@ import (
// Photo represents a photo edit form.
type Photo struct {
TakenAt time.Time `json:"TakenAt"`
PhotoTitle string `json:"PhotoTitle"`
PhotoDescription string `json:"PhotoDescription"`
PhotoNotes string `json:"PhotoNotes"`
PhotoArtist string `json:"PhotoArtist"`
PhotoKeywords string `json:"PhotoKeywords"`
PhotoCopyright string `json:"PhotoCopyright"`
TakenAt time.Time `json:"TakenAt"`
PhotoTitle string `json:"PhotoTitle"`
Description struct {
PhotoID uint `json:"PhotoID" deepcopier:"skip"`
PhotoDescription string `json:"PhotoDescription"`
PhotoKeywords string `json:"PhotoKeywords"`
PhotoNotes string `json:"PhotoNotes"`
PhotoSubject string `json:"PhotoSubject"`
PhotoArtist string `json:"PhotoArtist"`
PhotoCopyright string `json:"PhotoCopyright"`
PhotoLicense string `json:"PhotoLicense"`
} `json:"Description"`
PhotoFavorite bool `json:"PhotoFavorite"`
PhotoPrivate bool `json:"PhotoPrivate"`
PhotoNSFW bool `json:"PhotoNSFW"`
@ -24,20 +29,20 @@ type Photo struct {
PhotoAltitude int `json:"PhotoAltitude"`
PhotoFocalLength int `json:"PhotoFocalLength"`
PhotoIso int `json:"PhotoIso"`
PhotoFNumber float64 `json:"PhotoFNumber"`
PhotoExposure string `json:"PhotoExposure"`
CameraID uint `json:"CameraID"`
LensID uint `json:"LensID"`
LocationID string `json:"LocationID"`
PlaceID string `json:"PlaceID"`
PhotoCountry string `json:"PhotoCountry"`
TimeZone string `json:"TimeZone"`
TakenAtLocal time.Time `json:"TakenAtLocal"`
ModifiedTitle bool `json:"ModifiedTitle"`
ModifiedKeywords bool `json:"ModifiedKeywords"`
ModifiedDetails bool `json:"ModifiedDetails"`
ModifiedLocation bool `json:"ModifiedLocation"`
ModifiedDate bool `json:"ModifiedDate"`
PhotoFNumber float64 `json:"PhotoFNumber"`
PhotoExposure string `json:"PhotoExposure"`
CameraID uint `json:"CameraID"`
LensID uint `json:"LensID"`
LocationID string `json:"LocationID"`
PlaceID string `json:"PlaceID"`
PhotoCountry string `json:"PhotoCountry"`
TimeZone string `json:"TimeZone"`
TakenAtLocal time.Time `json:"TakenAtLocal"`
ModifiedTitle bool `json:"ModifiedTitle"`
ModifiedDescription bool `json:"ModifiedDescription"`
ModifiedDate bool `json:"ModifiedDate"`
ModifiedLocation bool `json:"ModifiedLocation"`
ModifiedCamera bool `json:"ModifiedCamera"`
}
func NewPhoto(m interface{}) (f Photo, err error) {

View file

@ -9,9 +9,6 @@ type PhotoSearch struct {
Query string `form:"q"`
ID string `form:"id"`
Title string `form:"title"`
Description string `form:"description"`
Notes string `form:"notes"`
Artist string `form:"artist"`
Hash string `form:"hash"`
Duplicate bool `form:"duplicate"`
Archived bool `form:"archived"`

View file

@ -37,7 +37,7 @@ func TestParseQueryString(t *testing.T) {
assert.Equal(t, 33.45343166666667, form.Lat)
})
t.Run("valid query 2", func(t *testing.T) {
form := &PhotoSearch{Query: "chroma:600 description:\"test\" after:2018-01-15 duplicate:false favorites:true lng:33.45343166666667"}
form := &PhotoSearch{Query: "chroma:600 title:\"test\" after:2018-01-15 duplicate:false favorites:true lng:33.45343166666667"}
err := form.ParseQueryString()
@ -48,13 +48,13 @@ func TestParseQueryString(t *testing.T) {
}
assert.Equal(t, uint(0x258), form.Chroma)
assert.Equal(t, "test", form.Description)
assert.Equal(t, "test", form.Title)
assert.Equal(t, time.Date(2018, 01, 15, 0, 0, 0, 0, time.UTC), form.After)
assert.Equal(t, false, form.Duplicate)
assert.Equal(t, 33.45343166666667, form.Lng)
})
t.Run("valid query with umlauts", func(t *testing.T) {
form := &PhotoSearch{Query: "description:\"tübingen\""}
form := &PhotoSearch{Query: "title:\"tübingen\""}
err := form.ParseQueryString()
@ -64,7 +64,7 @@ func TestParseQueryString(t *testing.T) {
t.Fatal("err should be nil")
}
assert.Equal(t, "tübingen", form.Description)
assert.Equal(t, "tübingen", form.Title)
})
t.Run("query for invalid filter", func(t *testing.T) {
form := &PhotoSearch{Query: "xxx:false"}

View file

@ -33,6 +33,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
start := time.Now()
var photo entity.Photo
var description entity.Description
var file, primaryFile entity.File
var metaData meta.Data
var photoQuery, fileQuery *gorm.DB
@ -87,6 +88,10 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
return indexResultSkipped
}
if photoExists {
ind.db.Model(&photo).Related(&description)
}
if fileHash == "" {
fileHash = m.Hash()
}
@ -132,28 +137,28 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
photo.PhotoTitle = metaData.Title
}
if photo.NoDescription() {
photo.PhotoDescription = metaData.Description
if photo.Description.NoDescription() {
photo.Description.PhotoDescription = metaData.Description
}
if photo.NoNotes() {
photo.PhotoNotes = metaData.Comment
if photo.Description.NoNotes() {
photo.Description.PhotoNotes = metaData.Comment
}
if photo.NoSubject() {
photo.PhotoSubject = metaData.Subject
if photo.Description.NoSubject() {
photo.Description.PhotoSubject = metaData.Subject
}
if photo.NoKeywords() {
photo.PhotoKeywords = metaData.Keywords
if photo.Description.NoKeywords() {
photo.Description.PhotoKeywords = metaData.Keywords
}
if photo.NoArtist() && metaData.Artist != "" {
photo.PhotoArtist = metaData.Artist
if photo.Description.NoArtist() && metaData.Artist != "" {
photo.Description.PhotoArtist = metaData.Artist
}
if photo.NoArtist() && metaData.CameraOwner != "" {
photo.PhotoArtist = metaData.CameraOwner
if photo.Description.NoArtist() && metaData.CameraOwner != "" {
photo.Description.PhotoArtist = metaData.CameraOwner
}
if photo.NoCameraSerial() {
@ -168,7 +173,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
}
}
if !photo.ModifiedDetails && (fileChanged || o.UpdateCamera) {
if !photo.ModifiedCamera && (fileChanged || o.UpdateCamera) {
// Set UpdateCamera, Lens, Focal Length and F Number
photo.Camera = entity.NewCamera(m.CameraModel(), m.CameraMake()).FirstOrCreate(ind.db)
photo.Lens = entity.NewLens(m.LensModel(), m.LensMake()).FirstOrCreate(ind.db)
@ -219,16 +224,20 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
photo.PhotoTitle = data.Title
}
if photo.NoCopyright() && data.Copyright != "" {
photo.PhotoCopyright = data.Copyright
if photo.Description.NoCopyright() && data.Copyright != "" {
photo.Description.PhotoCopyright = data.Copyright
}
if photo.NoArtist() && data.Artist != "" {
photo.PhotoArtist = data.Artist
if photo.Description.NoArtist() && data.Artist != "" {
photo.Description.PhotoArtist = data.Artist
}
if photo.NoDescription() && data.Description != "" {
photo.PhotoDescription = data.Description
if photo.Description.NoDescription() && data.Description != "" {
photo.Description.PhotoDescription = data.Description
}
if photo.Description.NoNotes() && data.Comment != "" {
photo.Description.PhotoNotes = data.Comment
}
}
}
@ -271,24 +280,22 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
}
if file.FilePrimary && (fileChanged || o.UpdateKeywords) {
w := txt.Keywords(photo.PhotoKeywords)
w := txt.Keywords(photo.Description.PhotoKeywords)
if !photo.ModifiedKeywords {
if NonCanonical(fileBase) {
w = append(w, txt.Keywords(filePath)...)
w = append(w, txt.Keywords(fileBase)...)
}
w = append(w, locKeywords...)
w = append(w, txt.Keywords(file.OriginalName)...)
w = append(w, file.FileMainColor)
w = append(w, labels.Keywords()...)
if NonCanonical(fileBase) {
w = append(w, txt.Keywords(filePath)...)
w = append(w, txt.Keywords(fileBase)...)
}
photo.PhotoKeywords = strings.Join(txt.UniqueWords(w), ", ")
w = append(w, locKeywords...)
w = append(w, txt.Keywords(file.OriginalName)...)
w = append(w, file.FileMainColor)
w = append(w, labels.Keywords()...)
if photo.PhotoKeywords != "" {
log.Debugf("index: updated photo keywords (%s)", photo.PhotoKeywords)
photo.Description.PhotoKeywords = strings.Join(txt.UniqueWords(w), ", ")
if photo.Description.PhotoKeywords != "" {
log.Debugf("index: updated photo keywords (%s)", photo.Description.PhotoKeywords)
} else {
log.Debug("index: no photo keywords")
}

View file

@ -26,14 +26,9 @@ type PhotoResult struct {
PhotoPath string
PhotoName string
PhotoTitle string
PhotoDescription string
PhotoYear int
PhotoMonth int
PhotoCountry string
PhotoArtist string
PhotoKeywords string
PhotoColors string
PhotoColor string
PhotoFavorite bool
PhotoPrivate bool
PhotoSensitive bool
@ -259,14 +254,6 @@ func (s *Repo) Photos(f form.PhotoSearch) (results []PhotoResult, err error) {
q = q.Where("LOWER(photos.photo_title) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Title)))
}
if f.Description != "" {
q = q.Where("LOWER(photos.photo_description) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Description)))
}
if f.Notes != "" {
q = q.Where("LOWER(photos.photo_notes) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Notes)))
}
if f.Hash != "" {
q = q.Where("files.file_hash = ?", f.Hash)
}
@ -350,7 +337,7 @@ func (s *Repo) Photos(f form.PhotoSearch) (results []PhotoResult, err error) {
// FindPhotoByID returns a Photo based on the ID.
func (s *Repo) FindPhotoByID(photoID uint64) (photo entity.Photo, err error) {
if err := s.db.Where("id = ?", photoID).First(&photo).Error; err != nil {
if err := s.db.Where("id = ?", photoID).Preload("Description").First(&photo).Error; err != nil {
return photo, err
}
@ -359,7 +346,7 @@ func (s *Repo) FindPhotoByID(photoID uint64) (photo entity.Photo, err error) {
// FindPhotoByUUID returns a Photo based on the UUID.
func (s *Repo) FindPhotoByUUID(photoUUID string) (photo entity.Photo, err error) {
if err := s.db.Where("photo_uuid = ?", photoUUID).First(&photo).Error; err != nil {
if err := s.db.Where("photo_uuid = ?", photoUUID).Preload("Description").First(&photo).Error; err != nil {
return photo, err
}
@ -375,6 +362,7 @@ func (s *Repo) PreloadPhotoByUUID(photoUUID string) (photo entity.Photo, err err
Preload("Labels.Label").
Preload("Camera").
Preload("Lens").
Preload("Description").
First(&photo).Error; err != nil {
return photo, err
}

View file

@ -216,34 +216,6 @@ func TestSearch_Photos_Query(t *testing.T) {
t.Logf("results: %+v", photos)
})
t.Run("form.description", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "description:xxx"
f.Count = 3
f.Offset = 0
photos, err := search.Photos(f)
if err != nil {
t.Fatal(err)
}
t.Logf("results: %+v", photos)
})
t.Run("form.notes", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "notes:xxx"
f.Count = 3
f.Offset = 0
photos, err := search.Photos(f)
if err != nil {
t.Fatal(err)
}
t.Logf("results: %+v", photos)
})
t.Run("form.hash", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "hash:xxx"