diff --git a/internal/config/client.go b/internal/config/client.go index 74dae8531..a0a1c379d 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -131,15 +131,16 @@ func (c *Config) ClientConfig() ClientConfig { Take(&position) var count = struct { - Photos uint `json:"photos"` - Hidden uint `json:"hidden"` - Favorites uint `json:"favorites"` - Private uint `json:"private"` - Stories uint `json:"stories"` - Labels uint `json:"labels"` - Albums uint `json:"albums"` - Countries uint `json:"countries"` - Places uint `json:"places"` + Photos uint `json:"photos"` + Hidden uint `json:"hidden"` + Favorites uint `json:"favorites"` + Private uint `json:"private"` + Stories uint `json:"stories"` + Albums uint `json:"albums"` + Countries uint `json:"countries"` + Places uint `json:"places"` + Labels uint `json:"labels"` + LabelMaxPhotos uint `json:"labelMaxPhotos"` }{} db.Table("photos"). @@ -148,8 +149,10 @@ func (c *Config) ClientConfig() ClientConfig { Take(&count) db.Table("labels"). - Select("COUNT(*) AS labels"). - Where("(label_priority >= 0 || label_favorite = 1) && deleted_at IS NULL"). + Select("MAX(photo_count) as label_max_photos, COUNT(*) AS labels"). + Where("photo_count > 0"). + Where("deleted_at IS NULL"). + Where("(label_priority >= 0 || label_favorite = 1)"). Take(&count) db.Table("albums"). diff --git a/internal/entity/account_fixtures.go b/internal/entity/account_fixtures.go index 9123bda66..17e905c38 100644 --- a/internal/entity/account_fixtures.go +++ b/internal/entity/account_fixtures.go @@ -5,7 +5,9 @@ import ( "time" ) -var AccountFixtures = map[string]Account{ +type AccountMap map[string]Account + +var AccountFixtures = AccountMap{ "webdav-dummy": { ID: 1000000, AccName: "Test Account", diff --git a/internal/entity/album_fixtures.go b/internal/entity/album_fixtures.go index 716a166d8..c6609e2fc 100644 --- a/internal/entity/album_fixtures.go +++ b/internal/entity/album_fixtures.go @@ -4,7 +4,9 @@ import ( "time" ) -var AlbumFixtures = map[string]Album{ +type AlbumMap map[string]Album + +var AlbumFixtures = AlbumMap{ "christmas2030": { ID: 1000000, CoverUUID: "", diff --git a/internal/entity/camera_fixtures.go b/internal/entity/camera_fixtures.go index 07d4c3096..0f31e6f63 100644 --- a/internal/entity/camera_fixtures.go +++ b/internal/entity/camera_fixtures.go @@ -4,7 +4,9 @@ import ( "time" ) -var CameraFixtures = map[string]Camera{ +type CameraMap map[string]Camera + +var CameraFixtures = CameraMap{ "apple-iphone-se": { ID: 1000000, CameraSlug: "apple-iphone-se", diff --git a/internal/entity/category_fixtures.go b/internal/entity/category_fixtures.go index 49ff0e42b..1c070c8a0 100644 --- a/internal/entity/category_fixtures.go +++ b/internal/entity/category_fixtures.go @@ -1,17 +1,17 @@ package entity var CategoryFixtures = map[string]Category{ - "1": { + "flower_landscape": { LabelID: 1000001, + Label: LabelFixtures.Pointer("flower"), CategoryID: 1000000, - Label: &LabelFixtureFlower, - Category: &LabelFixtureLandscape, + Category: LabelFixtures.Pointer("landscape"), }, } // CreateCategoryFixtures inserts known entities into the database for testing. func CreateCategoryFixtures() { - for _, entity := range KeywordFixtures { + for _, entity := range CategoryFixtures { Db().Create(&entity) } } diff --git a/internal/entity/country_fixtures.go b/internal/entity/country_fixtures.go index c336c36a9..388edc551 100644 --- a/internal/entity/country_fixtures.go +++ b/internal/entity/country_fixtures.go @@ -1,6 +1,8 @@ package entity -var CountryFixtures = map[string]Country{ +type CountryMap map[string]Country + +var CountryFixtures = CountryMap{ "apple-iphone-se": { ID: "de", CountrySlug: "germany", diff --git a/internal/entity/description_fixtures.go b/internal/entity/description_fixtures.go index 2f921bb69..8c99c349b 100644 --- a/internal/entity/description_fixtures.go +++ b/internal/entity/description_fixtures.go @@ -1,6 +1,26 @@ package entity -var DescriptionFixtures = map[string]Description{ +type DescriptionMap map[string]Description + +func (m DescriptionMap) Get(name string, photoId uint) Description { + if result, ok := m[name]; ok { + result.PhotoID = photoId + return result + } + + return Description{PhotoID: photoId} +} + +func (m DescriptionMap) Pointer(name string, photoId uint) *Description { + if result, ok := m[name]; ok { + result.PhotoID = photoId + return &result + } + + return &Description{PhotoID: photoId} +} + +var DescriptionFixtures = DescriptionMap{ "lake": { PhotoID: 1000000, PhotoDescription: "photo description", @@ -12,11 +32,3 @@ var DescriptionFixtures = map[string]Description{ PhotoLicense: "MIT", }, } -var DescriptionFixtureLake = DescriptionFixtures["lake"] - -// CreateDescriptionFixtures inserts known entities into the database for testing. -func CreateDescriptionFixtures() { - for _, entity := range DescriptionFixtures { - Db().Create(&entity) - } -} diff --git a/internal/entity/file_fixtures.go b/internal/entity/file_fixtures.go index 052ce67a6..7814d1944 100644 --- a/internal/entity/file_fixtures.go +++ b/internal/entity/file_fixtures.go @@ -7,7 +7,7 @@ import ( var FileFixtures = map[string]File{ "exampleFileName.jpg": { ID: 1000000, - Photo: &PhotoFixture19800101_000002_D640C559, + Photo: PhotoFixtures.Pointer("19800101_000002_D640C559"), PhotoID: 1000000, PhotoUUID: "pt9jtdre2lvl0yh7", FileUUID: "ft8es39w45bnlqdw", @@ -46,7 +46,7 @@ var FileFixtures = map[string]File{ }, "exampleDNGFile.dng": { ID: 1000001, - Photo: &PhotoFixturePhoto01, + Photo: PhotoFixtures.Pointer("Photo01"), PhotoID: 1000001, PhotoUUID: "pt9jtdre2lvl0yh8", FileUUID: "ft9es39w45bnlqdw", @@ -85,7 +85,7 @@ var FileFixtures = map[string]File{ }, "exampleXmpFile.xmp": { ID: 1000002, - Photo: &PhotoFixturePhoto01, + Photo: PhotoFixtures.Pointer("Photo01"), PhotoID: 1000001, PhotoUUID: "pt9jtdre2lvl0yh8", FileUUID: "ft1es39w45bnlqdw", @@ -124,7 +124,7 @@ var FileFixtures = map[string]File{ }, "bridge.jpg": { ID: 1000003, - Photo: &PhotoFixturePhoto04, + Photo: PhotoFixtures.Pointer("Photo04"), PhotoID: 1000004, PhotoUUID: "pt9jtdre2lvl0y11", FileUUID: "ft2es39w45bnlqdw", @@ -163,7 +163,7 @@ var FileFixtures = map[string]File{ }, "reunion.jpg": { ID: 1000004, - Photo: &PhotoFixturePhoto05, + Photo: PhotoFixtures.Pointer("Photo05"), PhotoID: 1000005, PhotoUUID: "pt9jtdre2lvl0y12", FileUUID: "ft3es39w45bnlqdw", diff --git a/internal/entity/fixtures.go b/internal/entity/fixtures.go index a9147c8d2..fc0245c79 100644 --- a/internal/entity/fixtures.go +++ b/internal/entity/fixtures.go @@ -14,8 +14,6 @@ func CreateTestFixtures() { CreateKeywordFixtures() CreatePhotoKeywordFixtures() CreateCategoryFixtures() - CreatePhotoLabelFixtures() CreateLocationFixtures() CreatePlaceFixtures() - CreateDescriptionFixtures() } diff --git a/internal/entity/keyword_fixtures.go b/internal/entity/keyword_fixtures.go index 621f1ecca..18d5ddd5d 100644 --- a/internal/entity/keyword_fixtures.go +++ b/internal/entity/keyword_fixtures.go @@ -1,6 +1,8 @@ package entity -var KeywordFixtures = map[string]Keyword{ +type KeywordMap map[string]Keyword + +var KeywordFixtures = KeywordMap{ "bridge": { ID: 1000000, Keyword: "bridge", diff --git a/internal/entity/label.go b/internal/entity/label.go index 284374a97..af522be33 100644 --- a/internal/entity/label.go +++ b/internal/entity/label.go @@ -23,7 +23,7 @@ type Label struct { LabelNotes string `gorm:"type:text;"` LabelCategories []*Label `gorm:"many2many:categories;association_jointable_foreignkey:category_id"` Links []Link `gorm:"foreignkey:ShareUUID;association_foreignkey:LabelUUID"` - PhotoCount int + PhotoCount int `gorm:"default:1"` CreatedAt time.Time UpdatedAt time.Time DeletedAt *time.Time `sql:"index"` @@ -55,6 +55,7 @@ func NewLabel(name string, priority int) *Label { CustomSlug: labelSlug, LabelName: labelName, LabelPriority: priority, + PhotoCount: 1, } return result diff --git a/internal/entity/label_fixtures.go b/internal/entity/label_fixtures.go index 0a70e26a4..a7aed5fe4 100644 --- a/internal/entity/label_fixtures.go +++ b/internal/entity/label_fixtures.go @@ -4,7 +4,34 @@ import ( "time" ) -var LabelFixtures = map[string]Label{ +type LabelMap map[string]Label + +func (m LabelMap) Get(name string) Label { + if result, ok := m[name]; ok { + return result + } + + return *NewLabel(name, 0) +} + +func (m LabelMap) Pointer(name string) *Label { + if result, ok := m[name]; ok { + return &result + } + + return NewLabel(name, 0) +} + +func (m LabelMap) PhotoLabel(photoId uint, labelName string, uncertainty int, source string) PhotoLabel { + label := m.Get(labelName) + + photoLabel := NewPhotoLabel(photoId, label.ID, uncertainty, source) + photoLabel.Label = &label + + return *photoLabel +} + +var LabelFixtures = LabelMap{ "landscape": { ID: 1000000, LabelUUID: "lt9k3pw1wowuy3c2", @@ -15,6 +42,7 @@ var LabelFixtures = map[string]Label{ LabelFavorite: true, LabelDescription: "", LabelNotes: "", + PhotoCount: 1, LabelCategories: []*Label{}, Links: []Link{}, CreatedAt: time.Now(), @@ -32,6 +60,7 @@ var LabelFixtures = map[string]Label{ LabelFavorite: true, LabelDescription: "", LabelNotes: "", + PhotoCount: 2, LabelCategories: []*Label{}, Links: []Link{}, CreatedAt: time.Now(), @@ -49,6 +78,7 @@ var LabelFixtures = map[string]Label{ LabelFavorite: false, LabelDescription: "", LabelNotes: "", + PhotoCount: 3, LabelCategories: []*Label{}, Links: []Link{}, CreatedAt: time.Now(), @@ -66,6 +96,7 @@ var LabelFixtures = map[string]Label{ LabelFavorite: true, LabelDescription: "", LabelNotes: "", + PhotoCount: 4, LabelCategories: []*Label{}, Links: []Link{}, CreatedAt: time.Now(), @@ -83,6 +114,7 @@ var LabelFixtures = map[string]Label{ LabelFavorite: true, LabelDescription: "", LabelNotes: "", + PhotoCount: 5, LabelCategories: []*Label{}, Links: []Link{}, CreatedAt: time.Now(), @@ -100,6 +132,7 @@ var LabelFixtures = map[string]Label{ LabelFavorite: false, LabelDescription: "", LabelNotes: "", + PhotoCount: 1, LabelCategories: []*Label{}, Links: []Link{}, CreatedAt: time.Now(), @@ -117,6 +150,7 @@ var LabelFixtures = map[string]Label{ LabelFavorite: false, LabelDescription: "", LabelNotes: "", + PhotoCount: 1, LabelCategories: []*Label{}, Links: []Link{}, CreatedAt: time.Now(), @@ -126,12 +160,6 @@ var LabelFixtures = map[string]Label{ }, } -var LabelFixtureLandscape = LabelFixtures["landscape"] -var LabelFixtureFlower = LabelFixtures["flower"] -var LabelFixtureCake = LabelFixtures["cake"] -var LabelFixtureCow = LabelFixtures["cow"] -var LabelFixtureUpdatePhotoLabel = LabelFixtures["updatePhotoLabel"] - // CreateLabelFixtures inserts known entities into the database for testing. func CreateLabelFixtures() { for _, entity := range LabelFixtures { diff --git a/internal/entity/label_test.go b/internal/entity/label_test.go index 881cd09b4..e68e049ef 100644 --- a/internal/entity/label_test.go +++ b/internal/entity/label_test.go @@ -53,7 +53,8 @@ func TestLabel_SetName(t *testing.T) { } func TestLabel_FirstOrCreate(t *testing.T) { - r := LabelFixtureFlower.FirstOrCreate() + label := LabelFixtures.Get("flower") + r := label.FirstOrCreate() assert.Equal(t, "Flower", r.LabelName) assert.Equal(t, "flower", r.LabelSlug) } @@ -100,7 +101,7 @@ func TestLabel_Update(t *testing.T) { }) t.Run("update name and Categories", func(t *testing.T) { classifyLabel := &classify.Label{Name: "classify", Uncertainty: 30, Source: "manual", Priority: 5, Categories: []string{"flower", "plant"}} - Label := &Label{LabelName: "label34", LabelSlug: "labelslug2", CustomSlug: "labelslug2", LabelPriority: 5, LabelCategories: []*Label{&LabelFixtureFlower}} + Label := &Label{LabelName: "label34", LabelSlug: "labelslug2", CustomSlug: "labelslug2", LabelPriority: 5, LabelCategories: []*Label{LabelFixtures.Pointer("flower")}} assert.Equal(t, 5, Label.LabelPriority) assert.Equal(t, "labelslug2", Label.LabelSlug) diff --git a/internal/entity/link_fixtures.go b/internal/entity/link_fixtures.go index 08eeed865..ce28651bc 100644 --- a/internal/entity/link_fixtures.go +++ b/internal/entity/link_fixtures.go @@ -4,7 +4,9 @@ import "time" var date = time.Date(2050, 3, 6, 2, 6, 51, 0, time.UTC) -var LinkFixtures = map[string]Link{ +type LinkMap map[string]Link + +var LinkFixtures = LinkMap{ "1jxf3jfn2k": { LinkToken: "1jxf3jfn2k", LinkPassword: "somepassword", diff --git a/internal/entity/location_fixtures.go b/internal/entity/location_fixtures.go index 841a63e6f..587cfabbd 100644 --- a/internal/entity/location_fixtures.go +++ b/internal/entity/location_fixtures.go @@ -2,21 +2,31 @@ package entity import "time" -var LocationFixtures = map[string]Location{ +type LocationMap map[string]Location + +var LocationFixtures = LocationMap{ "mexico": { - ID: "1000000", - PlaceID: "1000000", + ID: "85d1ea7d382c", + PlaceID: PlaceFixtures.Get("teotihuacan").ID, LocName: "Adosada Platform", LocCategory: "tourism", - Place: &PlaceFixtureTeotihuacan, + Place: PlaceFixtures.Pointer("teotihuacan"), LocSource: "places", CreatedAt: time.Now(), UpdatedAt: time.Now(), }, "caravan park": { - ID: "1000001", - PlaceID: "", - Place: nil, + ID: "1ef75a71a36c", + PlaceID: "1ef75a71a36c", + Place: &Place{ + ID: "1ef75a71a36", + LocLabel: "Mandeni, KwaZulu-Natal, South Africa", + LocCity: "Mandeni", + LocState: "KwaZulu-Natal", + LocCountry: "za", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, LocName: "Lobotes Caravan Park", LocCategory: "camping", LocSource: "manual", @@ -24,9 +34,9 @@ var LocationFixtures = map[string]Location{ UpdatedAt: time.Now(), }, "zinkwazi": { - ID: "1000002", - PlaceID: "", - Place: &PlaceFixtureZinkwazi, + ID: "1ef744d1e28c", + PlaceID: PlaceFixtures.Get("zinkwazi").ID, + Place: PlaceFixtures.Pointer("zinkwazi"), LocName: "Zinkwazi Beach", LocCategory: "", LocSource: "places", @@ -35,10 +45,6 @@ var LocationFixtures = map[string]Location{ }, } -var LocationFixtureMexico = LocationFixtures["mexico"] -var LocationFixtureCaravanPark = LocationFixtures["caravan park"] -var LocationFixtureZinkawzi = LocationFixtures["zinkwazi"] - // CreateLocationFixtures inserts known entities into the database for testing. func CreateLocationFixtures() { for _, entity := range LocationFixtures { diff --git a/internal/entity/location_test.go b/internal/entity/location_test.go index 15451bb85..8cf15f25f 100644 --- a/internal/entity/location_test.go +++ b/internal/entity/location_test.go @@ -11,7 +11,7 @@ func TestNewLocation(t *testing.T) { l := NewLocation(1, 1) l.LocCategory = "restaurant" l.LocName = "LocationName" - l.Place = &PlaceFixtureZinkwazi + l.Place = PlaceFixtures.Pointer("zinkwazi") l.LocSource = "places" assert.Equal(t, "restaurant", l.Category()) @@ -34,19 +34,22 @@ func TestNewLocation(t *testing.T) { } func TestLocation_Keywords(t *testing.T) { - t.Run("location with place", func(t *testing.T) { - r := LocationFixtureMexico.Keywords() + t.Run("mexico", func(t *testing.T) { + m := LocationFixtures["mexico"] + r := m.Keywords() assert.Equal(t, []string{"adosada", "ancient", "mexico", "platform", "pyramid", "teotihuacán", "tourism"}, r) }) - t.Run("location without place", func(t *testing.T) { - r := LocationFixtureCaravanPark.Keywords() - assert.Nil(t, r) + t.Run("caravan park", func(t *testing.T) { + m := LocationFixtures["caravan park"] + r := m.Keywords() + assert.Equal(t, []string{"camping", "caravan", "kwazulu-natal", "lobotes", "mandeni", "park", "south-africa"}, r) }) } func TestLocation_Find(t *testing.T) { t.Run("place in db", func(t *testing.T) { - r := LocationFixtureMexico.Find("") + m := LocationFixtures["mexico"] + r := m.Find("") assert.Nil(t, r) }) t.Run("invalid api", func(t *testing.T) { diff --git a/internal/entity/photo_album_fixtures.go b/internal/entity/photo_album_fixtures.go index 88c2e685c..ddd56a33e 100644 --- a/internal/entity/photo_album_fixtures.go +++ b/internal/entity/photo_album_fixtures.go @@ -9,7 +9,7 @@ var PhotoAlbumFixtures = map[string]PhotoAlbum{ Order: 0, CreatedAt: time.Date(2020, 3, 6, 2, 6, 51, 0, time.UTC), UpdatedAt: time.Date(2020, 3, 28, 14, 6, 0, 0, time.UTC), - Photo: &PhotoFixture19800101_000002_D640C559, + Photo: PhotoFixtures.Pointer("19800101_000002_D640C559"), Album: &AlbumFixtureHoliday2030, }, "2": { @@ -18,7 +18,7 @@ var PhotoAlbumFixtures = map[string]PhotoAlbum{ Order: 0, CreatedAt: time.Date(2020, 2, 6, 2, 6, 51, 0, time.UTC), UpdatedAt: time.Date(2020, 4, 28, 14, 6, 0, 0, time.UTC), - Photo: &PhotoFixturePhoto04, + Photo: PhotoFixtures.Pointer("Photo04"), Album: &AlbumFixtureBerlin2019, }, } diff --git a/internal/entity/photo_fixtures.go b/internal/entity/photo_fixtures.go index 96f708939..61b0a4633 100644 --- a/internal/entity/photo_fixtures.go +++ b/internal/entity/photo_fixtures.go @@ -6,7 +6,25 @@ import ( var editTime = time.Date(2008, 1, 1, 0, 0, 0, 0, time.UTC) -var PhotoFixtures = map[string]Photo{ +type PhotoMap map[string]Photo + +func (m PhotoMap) Get(name string) Photo { + if result, ok := m[name]; ok { + return result + } + + return Photo{PhotoName: name} +} + +func (m PhotoMap) Pointer(name string) *Photo { + if result, ok := m[name]; ok { + return &result + } + + return &Photo{PhotoName: name} +} + +var PhotoFixtures = PhotoMap{ "19800101_000002_D640C559": { ID: 1000000, PhotoUUID: "pt9jtdre2lvl0yh7", @@ -40,7 +58,7 @@ var PhotoFixtures = map[string]Photo{ PhotoCountry: "zz", PhotoYear: 2790, PhotoMonth: 2, - Description: DescriptionFixtureLake, + Description: DescriptionFixtures.Get("lake", 1000000), DescriptionSrc: "", Camera: &CameraFixtureEOS6D, Lens: nil, @@ -50,7 +68,10 @@ var PhotoFixtures = map[string]Photo{ Keywords: []Keyword{}, Albums: []Album{}, Files: []File{}, - Labels: []PhotoLabel{}, + Labels: []PhotoLabel{ + LabelFixtures.PhotoLabel(1000000, "flower", 38, "image"), + LabelFixtures.PhotoLabel(1000000, "cake", 38, "manual"), + }, CreatedAt: time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2008, 1, 1, 0, 0, 0, 0, time.UTC), EditedAt: nil, @@ -148,7 +169,7 @@ var PhotoFixtures = map[string]Photo{ Keywords: []Keyword{}, Albums: []Album{}, Files: []File{}, - Labels: []PhotoLabel{}, + Labels: []PhotoLabel{LabelFixtures.PhotoLabel(1000002, "cake", 20, "image")}, CreatedAt: time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2008, 1, 1, 0, 0, 0, 0, time.UTC), EditedAt: nil, @@ -197,7 +218,10 @@ var PhotoFixtures = map[string]Photo{ Keywords: []Keyword{}, Albums: []Album{}, Files: []File{}, - Labels: []PhotoLabel{}, + Labels: []PhotoLabel{ + LabelFixtures.PhotoLabel(1000003, "cow", 20, "image"), + LabelFixtures.PhotoLabel(1000003, "updatePhotoLabel", 20, "manual"), + }, CreatedAt: time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2008, 1, 1, 0, 0, 0, 0, time.UTC), EditedAt: nil, @@ -246,7 +270,7 @@ var PhotoFixtures = map[string]Photo{ Keywords: []Keyword{}, Albums: []Album{}, Files: []File{}, - Labels: []PhotoLabel{}, + Labels: []PhotoLabel{LabelFixtures.PhotoLabel(1000004, "batchdelete", 20, "image")}, CreatedAt: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), EditedAt: nil, @@ -295,7 +319,7 @@ var PhotoFixtures = map[string]Photo{ Keywords: []Keyword{}, Albums: []Album{}, Files: []File{}, - Labels: []PhotoLabel{}, + Labels: []PhotoLabel{LabelFixtures.PhotoLabel(1000005, "updateLabel", 20, "image")}, CreatedAt: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), EditedAt: nil, @@ -344,7 +368,7 @@ var PhotoFixtures = map[string]Photo{ Keywords: []Keyword{}, Albums: []Album{}, Files: []File{}, - Labels: []PhotoLabel{}, + Labels: []PhotoLabel{LabelFixtures.PhotoLabel(1000006, "updatePhotoLabel", 20, "image")}, CreatedAt: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), EditedAt: nil, @@ -393,7 +417,7 @@ var PhotoFixtures = map[string]Photo{ Keywords: []Keyword{}, Albums: []Album{}, Files: []File{}, - Labels: []PhotoLabel{}, + Labels: []PhotoLabel{LabelFixtures.PhotoLabel(1000007, "landscape", 20, "image")}, CreatedAt: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), EditedAt: &editTime, @@ -442,7 +466,7 @@ var PhotoFixtures = map[string]Photo{ Keywords: []Keyword{}, Albums: []Album{}, Files: []File{}, - Labels: []PhotoLabel{}, + Labels: []PhotoLabel{LabelFixtures.PhotoLabel(1000008, "landscape", 20, "image")}, CreatedAt: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), UpdatedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), EditedAt: nil, @@ -450,15 +474,6 @@ var PhotoFixtures = map[string]Photo{ }, } -var PhotoFixture19800101_000002_D640C559 = PhotoFixtures["19800101_000002_D640C559"] -var PhotoFixturePhoto04 = PhotoFixtures["Photo04"] -var PhotoFixturePhoto01 = PhotoFixtures["Photo01"] -var PhotoFixturePhoto05 = PhotoFixtures["Photo05"] -var PhotoFixturePhoto03 = PhotoFixtures["Photo03"] -var PhotoFixturePhoto06 = PhotoFixtures["Photo06"] -var PhotoFixturePhoto07 = PhotoFixtures["Photo07"] -var PhotoFixturePhoto08 = PhotoFixtures["Photo08"] - // CreatePhotoFixtures inserts known entities into the database for testing. func CreatePhotoFixtures() { for _, entity := range PhotoFixtures { diff --git a/internal/entity/photo_keyword_fixtures.go b/internal/entity/photo_keyword_fixtures.go index 5b3d6eecb..b78098928 100644 --- a/internal/entity/photo_keyword_fixtures.go +++ b/internal/entity/photo_keyword_fixtures.go @@ -1,14 +1,14 @@ package entity -var PhotoKeywordFixtures = map[string]PhotoKeyword{ +type PhotoKeywordMap map[string]PhotoKeyword + +var PhotoKeywordFixtures = PhotoKeywordMap{ "1": { PhotoID: 1000004, KeywordID: 1000000, }, } -var PhotoKeywordFixture1 = PhotoKeywordFixtures["1"] - // CreatePhotoKeywordFixtures inserts known entities into the database for testing. func CreatePhotoKeywordFixtures() { for _, entity := range PhotoKeywordFixtures { diff --git a/internal/entity/photo_keyword_test.go b/internal/entity/photo_keyword_test.go index 80d6c4270..60417f596 100644 --- a/internal/entity/photo_keyword_test.go +++ b/internal/entity/photo_keyword_test.go @@ -22,6 +22,7 @@ func TestPhotoKeyword_TableName(t *testing.T) { } func TestPhotoKeywords_FirstOrCreate(t *testing.T) { - r := PhotoKeywordFixture1.FirstOrCreate() + m := PhotoKeywordFixtures["1"] + r := m.FirstOrCreate() assert.Equal(t, uint(0xf4244), r.PhotoID) } diff --git a/internal/entity/photo_label_fixtures.go b/internal/entity/photo_label_fixtures.go deleted file mode 100644 index 8f4d923da..000000000 --- a/internal/entity/photo_label_fixtures.go +++ /dev/null @@ -1,37 +0,0 @@ -package entity - -var PhotoLabelFixtures = map[string]PhotoLabel{ - "1": { - PhotoID: 1000000, - LabelID: 1000001, - LabelSrc: "image", - Uncertainty: 38, - Photo: &PhotoFixture19800101_000002_D640C559, - Label: &LabelFixtureFlower, - }, - "2": { - PhotoID: 1000000, - LabelID: 1000002, - LabelSrc: "manual", - Uncertainty: 38, - Photo: &PhotoFixture19800101_000002_D640C559, - Label: &LabelFixtureCake, - }, - "3": { - PhotoID: 1000003, - LabelID: 1000006, - LabelSrc: "manual", - Uncertainty: 20, - Photo: &PhotoFixturePhoto03, - Label: &LabelFixtureUpdatePhotoLabel, - }, -} - -var PhotoLabelFixture1 = PhotoLabelFixtures["1"] - -// CreatePhotoLabelFixtures inserts known entities into the database for testing. -func CreatePhotoLabelFixtures() { - for _, entity := range PhotoLabelFixtures { - Db().Create(&entity) - } -} diff --git a/internal/entity/photo_label_test.go b/internal/entity/photo_label_test.go index 1ff30ff18..1f5b15d40 100644 --- a/internal/entity/photo_label_test.go +++ b/internal/entity/photo_label_test.go @@ -23,13 +23,15 @@ func TestPhotoLabel_TableName(t *testing.T) { } func TestPhotoLabel_FirstOrCreate(t *testing.T) { - r := PhotoLabelFixture1.FirstOrCreate() - assert.Equal(t, uint(0xf4240), r.PhotoID) + pl := LabelFixtures.PhotoLabel(1000000, "flower", 38, "image") + r := pl.FirstOrCreate() + assert.Equal(t, uint(1000000), r.PhotoID) } func TestPhotoLabel_ClassifyLabel(t *testing.T) { t.Run("success", func(t *testing.T) { - r := PhotoLabelFixture1.ClassifyLabel() + pl := LabelFixtures.PhotoLabel(1000000, "flower", 38, "image") + r := pl.ClassifyLabel() assert.Equal(t, "Flower", r.Name) assert.Equal(t, 38, r.Uncertainty) assert.Equal(t, "image", r.Source) diff --git a/internal/entity/photo_quality_test.go b/internal/entity/photo_quality_test.go index 717f4ea19..4f502e9a4 100644 --- a/internal/entity/photo_quality_test.go +++ b/internal/entity/photo_quality_test.go @@ -7,15 +7,15 @@ import ( func TestPhoto_QualityScore(t *testing.T) { t.Run("PhotoFixture19800101_000002_D640C559", func(t *testing.T) { - assert.Equal(t, 4, PhotoFixture19800101_000002_D640C559.QualityScore()) + assert.Equal(t, 4, PhotoFixtures.Pointer("19800101_000002_D640C559").QualityScore()) }) t.Run("PhotoFixturePhoto01 - favorite true - taken at before 2008", func(t *testing.T) { - assert.Equal(t, 7, PhotoFixturePhoto01.QualityScore()) + assert.Equal(t, 7, PhotoFixtures.Pointer("Photo01").QualityScore()) }) t.Run("PhotoFixturePhoto06 - taken at after 2012 - resolution 2", func(t *testing.T) { - assert.Equal(t, 4, PhotoFixturePhoto06.QualityScore()) + assert.Equal(t, 4, PhotoFixtures.Pointer("Photo06").QualityScore()) }) t.Run("PhotoFixturePhoto07 - score < 3 bit edited", func(t *testing.T) { - assert.Equal(t, 3, PhotoFixturePhoto07.QualityScore()) + assert.Equal(t, 3, PhotoFixtures.Pointer("Photo07").QualityScore()) }) } diff --git a/internal/entity/photo_test.go b/internal/entity/photo_test.go index eb6f8819e..942e8ab60 100644 --- a/internal/entity/photo_test.go +++ b/internal/entity/photo_test.go @@ -91,7 +91,8 @@ func TestPhoto_Save(t *testing.T) { } }) t.Run("existing photo", func(t *testing.T) { - err := PhotoFixture19800101_000002_D640C559.Save() + m := PhotoFixtures.Get("19800101_000002_D640C559") + err := m.Save() if err != nil { t.Fatal(err) } @@ -100,13 +101,13 @@ func TestPhoto_Save(t *testing.T) { func TestPhoto_ClassifyLabels(t *testing.T) { t.Run("new photo", func(t *testing.T) { - m := PhotoFixturePhoto01 + m := PhotoFixtures.Get("Photo01") Db().Set("gorm:auto_preload", true).Model(&m).Related(&m.Labels) labels := m.ClassifyLabels() assert.Empty(t, labels) }) t.Run("existing photo", func(t *testing.T) { - m := PhotoFixture19800101_000002_D640C559 + m := PhotoFixtures.Get("19800101_000002_D640C559") Db().Set("gorm:auto_preload", true).Model(&m).Related(&m.Labels) labels := m.ClassifyLabels() assert.LessOrEqual(t, 2, labels.Len()) diff --git a/internal/entity/places_fixtures.go b/internal/entity/place_fixtures.go similarity index 65% rename from internal/entity/places_fixtures.go rename to internal/entity/place_fixtures.go index baffccf12..081f20fca 100644 --- a/internal/entity/places_fixtures.go +++ b/internal/entity/place_fixtures.go @@ -2,9 +2,27 @@ package entity import "time" -var PlaceFixtures = map[string]Place{ +type PlacesMap map[string]Place + +func (m PlacesMap) Get(name string) Place { + if result, ok := m[name]; ok { + return result + } + + return UnknownPlace +} + +func (m PlacesMap) Pointer(name string) *Place { + if result, ok := m[name]; ok { + return &result + } + + return &UnknownPlace +} + +var PlaceFixtures = PlacesMap{ "teotihuacan": { - ID: "1000000", + ID: "85d1ea7d382c", LocLabel: "Teotihuacán, Mexico, Mexico", LocCity: "Teotihuacán", LocState: "Mexico", @@ -12,11 +30,12 @@ var PlaceFixtures = map[string]Place{ LocKeywords: "ancient, pyramid", LocNotes: "", LocFavorite: false, + PhotoCount: 1, CreatedAt: time.Now(), UpdatedAt: time.Now(), }, "zinkwazi": { - ID: "1000001", + ID: "1ef744d1e28c", LocLabel: "KwaDukuza, KwaZulu-Natal, South Africa", LocCity: "KwaDukuza", LocState: "KwaZulu-Natal", @@ -24,14 +43,12 @@ var PlaceFixtures = map[string]Place{ LocKeywords: "", LocNotes: "africa", LocFavorite: true, + PhotoCount: 2, CreatedAt: time.Now(), UpdatedAt: time.Now(), }, } -var PlaceFixtureTeotihuacan = PlaceFixtures["teotihuacan"] -var PlaceFixtureZinkwazi = PlaceFixtures["zinkwazi"] - // CreatePlaceFixtures inserts known entities into the database for testing. func CreatePlaceFixtures() { for _, entity := range PlaceFixtures { diff --git a/internal/entity/place_test.go b/internal/entity/place_test.go index a12e0bf00..c891e2112 100644 --- a/internal/entity/place_test.go +++ b/internal/entity/place_test.go @@ -14,22 +14,36 @@ func TestCreateUnknownPlace(t *testing.T) { func TestFindPlaceByLabel(t *testing.T) { t.Run("find by id", func(t *testing.T) { - r := FindPlaceByLabel("1000000", "") + r := FindPlaceByLabel("85d1ea7d382c", "") + + if r == nil { + t.Fatal("result should not be nil") + } + assert.Equal(t, "mx", r.LocCountry) }) t.Run("find by label", func(t *testing.T) { r := FindPlaceByLabel("", "KwaDukuza, KwaZulu-Natal, South Africa") + + if r == nil { + t.Fatal("result should not be nil") + } + assert.Equal(t, "za", r.LocCountry) }) t.Run("not matching", func(t *testing.T) { r := FindPlaceByLabel("111", "xxx") - assert.Nil(t, r) + + if r != nil { + t.Fatal("result should be nil") + } }) } func TestPlace_Find(t *testing.T) { t.Run("record exists", func(t *testing.T) { - r := PlaceFixtureTeotihuacan.Find() + m := PlaceFixtures.Get("teotihuacan") + r := m.Find() assert.Nil(t, r) }) t.Run("record does not exist", func(t *testing.T) { @@ -53,6 +67,7 @@ func TestPlace_Find(t *testing.T) { } func TestPlace_FirstOrCreate(t *testing.T) { - r := PlaceFixtureZinkwazi.FirstOrCreate() + m := PlaceFixtures.Pointer("zinkwazi") + r := m.FirstOrCreate() assert.Equal(t, "KwaDukuza, KwaZulu-Natal, South Africa", r.LocLabel) } diff --git a/internal/query/label.go b/internal/query/label.go new file mode 100644 index 000000000..16c0dc1ec --- /dev/null +++ b/internal/query/label.go @@ -0,0 +1,72 @@ +package query + +import ( + "github.com/photoprism/photoprism/internal/entity" +) + +// PhotoLabel returns a photo label entity if exists. +func PhotoLabel(photoID, labelID uint) (label entity.PhotoLabel, err error) { + if err := Db().Where("photo_id = ? AND label_id = ?", photoID, labelID).Preload("Photo").Preload("Label").First(&label).Error; err != nil { + return label, err + } + + return label, nil +} + +// LabelBySlug returns a Label based on the slug name. +func LabelBySlug(labelSlug string) (label entity.Label, err error) { + if err := Db().Where("label_slug = ? OR custom_slug = ?", labelSlug, labelSlug).Preload("Links").First(&label).Error; err != nil { + return label, err + } + + return label, nil +} + +// LabelByUUID returns a Label based on the label UUID. +func LabelByUUID(labelUUID string) (label entity.Label, err error) { + if err := Db().Where("label_uuid = ?", labelUUID).Preload("Links").First(&label).Error; err != nil { + return label, err + } + + return label, nil +} + +// LabelThumbBySlug returns a label preview file based on the slug name. +func LabelThumbBySlug(labelSlug string) (file entity.File, err error) { + if err := Db().Where("files.file_primary AND files.deleted_at IS NULL"). + Joins("JOIN labels ON labels.label_slug = ?", labelSlug). + Joins("JOIN photos_labels ON photos_labels.label_id = labels.id AND photos_labels.photo_id = files.photo_id AND photos_labels.uncertainty < 100"). + Joins("JOIN photos ON photos.id = files.photo_id AND photos.photo_private = 0 AND photos.deleted_at IS NULL"). + Order("photos.photo_quality DESC, photos_labels.uncertainty ASC"). + First(&file).Error; err != nil { + return file, err + } + + return file, nil +} + +// LabelThumbByUUID returns a label preview file based on the label UUID. +func LabelThumbByUUID(labelUUID string) (file entity.File, err error) { + // Search matching label + err = Db().Where("files.file_primary AND files.deleted_at IS NULL"). + Joins("JOIN labels ON labels.label_uuid = ?", labelUUID). + Joins("JOIN photos_labels ON photos_labels.label_id = labels.id AND photos_labels.photo_id = files.photo_id AND photos_labels.uncertainty < 100"). + Joins("JOIN photos ON photos.id = files.photo_id AND photos.photo_private = 0 AND photos.deleted_at IS NULL"). + Order("photos.photo_quality DESC, photos_labels.uncertainty ASC"). + First(&file).Error + + if err == nil { + return file, nil + } + + // If failed, search for category instead + err = Db().Where("files.file_primary AND files.deleted_at IS NULL"). + Joins("JOIN photos_labels ON photos_labels.photo_id = files.photo_id AND photos_labels.uncertainty < 100"). + Joins("JOIN categories c ON photos_labels.label_id = c.label_id"). + Joins("JOIN labels ON c.category_id = labels.id AND labels.label_uuid= ?", labelUUID). + Joins("JOIN photos ON photos.id = files.photo_id AND photos.photo_private = 0 AND photos.deleted_at IS NULL"). + Order("photos.photo_quality DESC, photos_labels.uncertainty ASC"). + First(&file).Error + + return file, err +} diff --git a/internal/query/label_result.go b/internal/query/label_result.go index 1c27c8da8..ac3ad7325 100644 --- a/internal/query/label_result.go +++ b/internal/query/label_result.go @@ -16,8 +16,8 @@ type LabelResult struct { CustomSlug string LabelName string LabelPriority int - LabelCount int LabelFavorite bool LabelDescription string LabelNotes string + PhotoCount int } diff --git a/internal/query/label_test.go b/internal/query/label_test.go new file mode 100644 index 000000000..15583ffd1 --- /dev/null +++ b/internal/query/label_test.go @@ -0,0 +1,83 @@ +package query + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLabelBySlug(t *testing.T) { + t.Run("files found", func(t *testing.T) { + label, err := LabelBySlug("flower") + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "Flower", label.LabelName) + }) + + t.Run("no files found", func(t *testing.T) { + label, err := LabelBySlug("111") + + assert.Error(t, err, "record not found") + assert.Empty(t, label.ID) + }) +} + +func TestLabelByUUID(t *testing.T) { + t.Run("files found", func(t *testing.T) { + label, err := LabelByUUID("lt9k3pw1wowuy3c5") + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "COW", label.LabelName) + }) + + t.Run("no files found", func(t *testing.T) { + label, err := LabelByUUID("111") + + assert.Error(t, err, "record not found") + assert.Empty(t, label.ID) + }) +} + +func TestLabelThumbBySlug(t *testing.T) { + t.Run("files found", func(t *testing.T) { + file, err := LabelThumbBySlug("flower") + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "exampleFileName.jpg", file.FileName) + }) + + t.Run("no files found", func(t *testing.T) { + file, err := LabelThumbBySlug("cow") + + assert.Error(t, err, "record not found") + t.Log(file) + }) +} + +func TestLabelThumbByUUID(t *testing.T) { + t.Run("files found", func(t *testing.T) { + file, err := LabelThumbByUUID("lt9k3pw1wowuy3c4") + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "exampleFileName.jpg", file.FileName) + }) + + t.Run("no files found", func(t *testing.T) { + file, err := LabelThumbByUUID("14") + + assert.Error(t, err, "record not found") + t.Log(file) + }) +} diff --git a/internal/query/labels.go b/internal/query/labels.go index 6f1199d7d..9f46b2610 100644 --- a/internal/query/labels.go +++ b/internal/query/labels.go @@ -12,73 +12,6 @@ import ( "github.com/photoprism/photoprism/pkg/txt" ) -// PhotoLabel returns a photo label entity if exists. -func PhotoLabel(photoID, labelID uint) (label entity.PhotoLabel, err error) { - if err := Db().Where("photo_id = ? AND label_id = ?", photoID, labelID).Preload("Photo").Preload("Label").First(&label).Error; err != nil { - return label, err - } - - return label, nil -} - -// LabelBySlug returns a Label based on the slug name. -func LabelBySlug(labelSlug string) (label entity.Label, err error) { - if err := Db().Where("label_slug = ? OR custom_slug = ?", labelSlug, labelSlug).Preload("Links").First(&label).Error; err != nil { - return label, err - } - - return label, nil -} - -// LabelByUUID returns a Label based on the label UUID. -func LabelByUUID(labelUUID string) (label entity.Label, err error) { - if err := Db().Where("label_uuid = ?", labelUUID).Preload("Links").First(&label).Error; err != nil { - return label, err - } - - return label, nil -} - -// LabelThumbBySlug returns a label preview file based on the slug name. -func LabelThumbBySlug(labelSlug string) (file entity.File, err error) { - if err := Db().Where("files.file_primary AND files.deleted_at IS NULL"). - Joins("JOIN labels ON labels.label_slug = ?", labelSlug). - Joins("JOIN photos_labels ON photos_labels.label_id = labels.id AND photos_labels.photo_id = files.photo_id AND photos_labels.uncertainty < 100"). - Joins("JOIN photos ON photos.id = files.photo_id AND photos.photo_private = 0 AND photos.deleted_at IS NULL"). - Order("photos.photo_quality DESC, photos_labels.uncertainty ASC"). - First(&file).Error; err != nil { - return file, err - } - - return file, nil -} - -// LabelThumbByUUID returns a label preview file based on the label UUID. -func LabelThumbByUUID(labelUUID string) (file entity.File, err error) { - // Search matching label - err = Db().Where("files.file_primary AND files.deleted_at IS NULL"). - Joins("JOIN labels ON labels.label_uuid = ?", labelUUID). - Joins("JOIN photos_labels ON photos_labels.label_id = labels.id AND photos_labels.photo_id = files.photo_id AND photos_labels.uncertainty < 100"). - Joins("JOIN photos ON photos.id = files.photo_id AND photos.photo_private = 0 AND photos.deleted_at IS NULL"). - Order("photos.photo_quality DESC, photos_labels.uncertainty ASC"). - First(&file).Error - - if err == nil { - return file, nil - } - - // If failed, search for category instead - err = Db().Where("files.file_primary AND files.deleted_at IS NULL"). - Joins("JOIN photos_labels ON photos_labels.photo_id = files.photo_id AND photos_labels.uncertainty < 100"). - Joins("JOIN categories c ON photos_labels.label_id = c.label_id"). - Joins("JOIN labels ON c.category_id = labels.id AND labels.label_uuid= ?", labelUUID). - Joins("JOIN photos ON photos.id = files.photo_id AND photos.photo_private = 0 AND photos.deleted_at IS NULL"). - Order("photos.photo_quality DESC, photos_labels.uncertainty ASC"). - First(&file).Error - - return file, err -} - // Labels searches labels based on their name. func Labels(f form.LabelSearch) (results []LabelResult, err error) { if err := f.ParseQueryString(); err != nil { @@ -94,6 +27,7 @@ func Labels(f form.LabelSearch) (results []LabelResult, err error) { s = s.Table("labels"). Select(`labels.*`). Where("labels.deleted_at IS NULL"). + Where("labels.photo_count > 0"). Group("labels.id") if f.ID != "" { diff --git a/internal/query/labels_test.go b/internal/query/labels_test.go index 19db75f58..3b9ef89d6 100644 --- a/internal/query/labels_test.go +++ b/internal/query/labels_test.go @@ -8,82 +8,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestLabelBySlug(t *testing.T) { - t.Run("files found", func(t *testing.T) { - label, err := LabelBySlug("flower") - - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "Flower", label.LabelName) - }) - - t.Run("no files found", func(t *testing.T) { - label, err := LabelBySlug("111") - - assert.Error(t, err, "record not found") - assert.Empty(t, label.ID) - }) -} - -func TestLabelByUUID(t *testing.T) { - t.Run("files found", func(t *testing.T) { - label, err := LabelByUUID("lt9k3pw1wowuy3c5") - - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "COW", label.LabelName) - }) - - t.Run("no files found", func(t *testing.T) { - label, err := LabelByUUID("111") - - assert.Error(t, err, "record not found") - assert.Empty(t, label.ID) - }) -} - -func TestLabelThumbBySlug(t *testing.T) { - t.Run("files found", func(t *testing.T) { - file, err := LabelThumbBySlug("flower") - - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "exampleFileName.jpg", file.FileName) - }) - - t.Run("no files found", func(t *testing.T) { - file, err := LabelThumbBySlug("cow") - - assert.Error(t, err, "record not found") - t.Log(file) - }) -} - -func TestLabelThumbByUUID(t *testing.T) { - t.Run("files found", func(t *testing.T) { - file, err := LabelThumbByUUID("lt9k3pw1wowuy3c4") - - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "exampleFileName.jpg", file.FileName) - }) - - t.Run("no files found", func(t *testing.T) { - file, err := LabelThumbByUUID("14") - - assert.Error(t, err, "record not found") - t.Log(file) - }) -} - func TestLabels(t *testing.T) { t.Run("search with query", func(t *testing.T) { query := form.NewLabelSearch("Query:C Count:1005 Order:slug") diff --git a/internal/query/photo_counts.go b/internal/query/photo_counts.go index bdb4fbef3..e9b6c4c36 100644 --- a/internal/query/photo_counts.go +++ b/internal/query/photo_counts.go @@ -14,16 +14,19 @@ func UpdatePhotoCounts() error { } if err := Db().Table("labels"). - UpdateColumn("photo_count", gorm.Expr("(SELECT COUNT(*) FROM photos_labels pl "+ - "JOIN photos ph ON pl.photo_id = ph.id "+ - "LEFT JOIN categories c ON c.label_id = pl.label_id "+ - "LEFT JOIN labels lc ON lc.id = c.category_id "+ - "WHERE pl.label_id = labels.id "+ - "AND lc.deleted_at IS NULL "+ - "AND pl.uncertainty < 100 "+ - "AND ph.photo_quality >= 0 "+ - "AND ph.photo_private = 0 "+ - "AND ph.deleted_at IS NULL)")).Error; err != nil { + UpdateColumn("photo_count", gorm.Expr(`(SELECT COUNT(DISTINCT ph.id) FROM labels l + LEFT JOIN categories c ON c.category_id = l.id + LEFT JOIN photos_labels pl ON pl.label_id = l.id + LEFT JOIN photos_labels plc ON plc.label_id = c.label_id + LEFT JOIN labels lc ON lc.id = plc.label_id + LEFT JOIN photos ph ON pl.photo_id = ph.id OR plc.photo_id = ph.id + WHERE l.id = labels.id + AND lc.deleted_at IS NULL + AND (pl.uncertainty < 100 OR pl.uncertainty IS NULL) + AND (plc.uncertainty < 100 OR plc.uncertainty IS NULL) + AND ph.photo_quality >= 0 + AND ph.photo_private = 0 + AND ph.deleted_at IS NULL)`)).Error; err != nil { return err }