Use AlbumType to distinguish between manual collections and moments #154
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
2efd419862
commit
dcc610d7a9
23 changed files with 375 additions and 85 deletions
|
@ -187,8 +187,8 @@
|
|||
this.searchExpanded = false;
|
||||
}
|
||||
|
||||
if (this.filter.order !== this.album.AlbumOrder) {
|
||||
this.album.AlbumOrder = this.filter.order;
|
||||
if (this.filter.order !== this.album.Order) {
|
||||
this.album.Order = this.filter.order;
|
||||
this.updateAlbum()
|
||||
}
|
||||
},
|
||||
|
|
|
@ -294,7 +294,7 @@
|
|||
return this.model.find(this.uid).then(m => {
|
||||
this.model = m;
|
||||
|
||||
this.filter.order = m.AlbumOrder;
|
||||
this.filter.order = m.Order;
|
||||
window.document.title = `PhotoPrism: ${this.model.Title}`;
|
||||
|
||||
return Promise.resolve(this.model)
|
||||
|
@ -322,8 +322,8 @@
|
|||
this.dirty = true;
|
||||
this.scrollDisabled = false;
|
||||
|
||||
if (this.filter.order !== this.model.AlbumOrder) {
|
||||
this.filter.order = this.model.AlbumOrder;
|
||||
if (this.filter.order !== this.model.Order) {
|
||||
this.filter.order = this.model.Order;
|
||||
this.updateQuery();
|
||||
} else {
|
||||
this.loadMore();
|
||||
|
|
|
@ -41,12 +41,24 @@
|
|||
<v-container grid-list-xs fluid class="pa-2 p-albums p-albums-cards">
|
||||
<v-card v-if="results.length === 0" class="p-albums-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<div v-if="staticFilter.type === 'moment'">
|
||||
<h3 class="title mb-3">
|
||||
{{$gettext("No albums matched your search")}}
|
||||
<translate key=">No moments">No moments matched your search</translate>
|
||||
</h3>
|
||||
<div>
|
||||
{{$gettext("Try again using a different term or create a new album from a selection in Photos.")}}
|
||||
<translate key=">Wait until">Wait until PhotoPrism has analyzed your library or try
|
||||
again using a different term.
|
||||
</translate>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h3 class="title mb-3">
|
||||
<translate key=">No albums">No albums matched your search</translate>
|
||||
</h3>
|
||||
<div>
|
||||
<translate key="Try again">Try again using a different term or create a new album from a
|
||||
selection in Photos.
|
||||
</translate>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
@ -246,7 +258,7 @@
|
|||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if(this.results[index]) {
|
||||
if (this.results[index]) {
|
||||
this.selectRange(index, this.results);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,11 +33,19 @@ export default [
|
|||
meta: {title: c.subtitle, auth: true},
|
||||
props: {staticFilter: {photo: "true"}},
|
||||
},
|
||||
{
|
||||
name: "moments",
|
||||
path: "/moments",
|
||||
component: Albums,
|
||||
meta: {title: "Moments", auth: true},
|
||||
props: {staticFilter: {type: "moment"}},
|
||||
},
|
||||
{
|
||||
name: "albums",
|
||||
path: "/albums",
|
||||
component: Albums,
|
||||
meta: {title: "Albums", auth: true},
|
||||
props: {staticFilter: {type: "album"}},
|
||||
},
|
||||
{
|
||||
name: "album",
|
||||
|
@ -111,13 +119,6 @@ export default [
|
|||
meta: {title: "All photos and videos", auth: true},
|
||||
props: {staticFilter: {quality: 0}},
|
||||
},
|
||||
{
|
||||
name: "moments",
|
||||
path: "/moments",
|
||||
component: Photos,
|
||||
meta: {title: "Moments", auth: true},
|
||||
props: {},
|
||||
},
|
||||
{
|
||||
name: "people",
|
||||
path: "/people",
|
||||
|
|
|
@ -87,7 +87,7 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
m := entity.NewAlbum(f.AlbumTitle, entity.TypeDefault)
|
||||
m := entity.NewAlbum(f.AlbumTitle, entity.TypeAlbum)
|
||||
m.AlbumFavorite = f.AlbumFavorite
|
||||
|
||||
log.Debugf("create album: %+v %+v", f, m)
|
||||
|
|
|
@ -18,7 +18,7 @@ func GetMomentsTime(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
result, err := query.GetMomentsTime()
|
||||
result, err := query.MomentsTime(1)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())})
|
||||
|
|
|
@ -19,7 +19,7 @@ type Album struct {
|
|||
CoverUID string `gorm:"type:varbinary(36);" json:"CoverUID" yaml:"CoverUID,omitempty"`
|
||||
FolderUID string `gorm:"type:varbinary(36);index;" json:"FolderUID" yaml:"FolderUID,omitempty"`
|
||||
AlbumSlug string `gorm:"type:varbinary(255);index;" json:"Slug" yaml:"Slug"`
|
||||
AlbumType string `gorm:"type:varbinary(8);" json:"Type" yaml:"Type,omitempty"`
|
||||
AlbumType string `gorm:"type:varbinary(8);default:'album';" json:"Type" yaml:"Type,omitempty"`
|
||||
AlbumTitle string `gorm:"type:varchar(255);" json:"Title" yaml:"Title"`
|
||||
AlbumCategory string `gorm:"type:varchar(255);index;" json:"Category" yaml:"Category,omitempty"`
|
||||
AlbumCaption string `gorm:"type:text;" json:"Caption" yaml:"Caption,omitempty"`
|
||||
|
@ -52,6 +52,10 @@ func (m *Album) BeforeCreate(scope *gorm.Scope) error {
|
|||
func NewAlbum(albumTitle, albumType string) *Album {
|
||||
now := time.Now().UTC()
|
||||
|
||||
if albumType == "" {
|
||||
albumType = TypeAlbum
|
||||
}
|
||||
|
||||
result := &Album{
|
||||
AlbumUID: rnd.PPID('a'),
|
||||
AlbumOrder: SortOrderOldest,
|
||||
|
|
|
@ -11,7 +11,7 @@ func (m AlbumMap) Get(name string) Album {
|
|||
return result
|
||||
}
|
||||
|
||||
return *NewAlbum(name, TypeDefault)
|
||||
return *NewAlbum(name, TypeAlbum)
|
||||
}
|
||||
|
||||
func (m AlbumMap) Pointer(name string) *Album {
|
||||
|
@ -19,7 +19,7 @@ func (m AlbumMap) Pointer(name string) *Album {
|
|||
return &result
|
||||
}
|
||||
|
||||
return NewAlbum(name, TypeDefault)
|
||||
return NewAlbum(name, TypeAlbum)
|
||||
}
|
||||
|
||||
var AlbumFixtures = AlbumMap{
|
||||
|
@ -28,7 +28,7 @@ var AlbumFixtures = AlbumMap{
|
|||
CoverUID: "",
|
||||
AlbumUID: "at9lxuqxpogaaba7",
|
||||
AlbumSlug: "christmas2030",
|
||||
AlbumType: TypeDefault,
|
||||
AlbumType: TypeAlbum,
|
||||
AlbumTitle: "Christmas2030",
|
||||
AlbumDescription: "Wonderful christmas",
|
||||
AlbumNotes: "",
|
||||
|
@ -45,7 +45,7 @@ var AlbumFixtures = AlbumMap{
|
|||
CoverUID: "",
|
||||
AlbumUID: "at9lxuqxpogaaba8",
|
||||
AlbumSlug: "holiday-2030",
|
||||
AlbumType: TypeDefault,
|
||||
AlbumType: TypeAlbum,
|
||||
AlbumTitle: "Holiday2030",
|
||||
AlbumDescription: "Wonderful christmas",
|
||||
AlbumNotes: "",
|
||||
|
@ -62,7 +62,7 @@ var AlbumFixtures = AlbumMap{
|
|||
CoverUID: "",
|
||||
AlbumUID: "at9lxuqxpogaaba9",
|
||||
AlbumSlug: "berlin-2019",
|
||||
AlbumType: TypeDefault,
|
||||
AlbumType: TypeAlbum,
|
||||
AlbumTitle: "Berlin2019",
|
||||
AlbumDescription: "Wonderful christmas",
|
||||
AlbumNotes: "",
|
||||
|
|
|
@ -12,12 +12,12 @@ import (
|
|||
|
||||
func TestNewAlbum(t *testing.T) {
|
||||
t.Run("name Christmas 2018", func(t *testing.T) {
|
||||
album := NewAlbum("Christmas 2018", TypeDefault)
|
||||
album := NewAlbum("Christmas 2018", TypeAlbum)
|
||||
assert.Equal(t, "Christmas 2018", album.AlbumTitle)
|
||||
assert.Equal(t, "christmas-2018", album.AlbumSlug)
|
||||
})
|
||||
t.Run("name empty", func(t *testing.T) {
|
||||
album := NewAlbum("", TypeDefault)
|
||||
album := NewAlbum("", TypeAlbum)
|
||||
|
||||
defaultName := time.Now().Format("January 2006")
|
||||
defaultSlug := slug.Make(defaultName)
|
||||
|
@ -29,7 +29,7 @@ func TestNewAlbum(t *testing.T) {
|
|||
|
||||
func TestAlbum_SetName(t *testing.T) {
|
||||
t.Run("valid name", func(t *testing.T) {
|
||||
album := NewAlbum("initial name", TypeDefault)
|
||||
album := NewAlbum("initial name", TypeAlbum)
|
||||
assert.Equal(t, "initial name", album.AlbumTitle)
|
||||
assert.Equal(t, "initial-name", album.AlbumSlug)
|
||||
album.SetTitle("New Album Name")
|
||||
|
@ -37,7 +37,7 @@ func TestAlbum_SetName(t *testing.T) {
|
|||
assert.Equal(t, "new-album-name", album.AlbumSlug)
|
||||
})
|
||||
t.Run("empty name", func(t *testing.T) {
|
||||
album := NewAlbum("initial name", TypeDefault)
|
||||
album := NewAlbum("initial name", TypeAlbum)
|
||||
assert.Equal(t, "initial name", album.AlbumTitle)
|
||||
assert.Equal(t, "initial-name", album.AlbumSlug)
|
||||
|
||||
|
@ -57,7 +57,7 @@ The discrepancy of 1 second meridian arc length between equator and pole is abou
|
|||
is an oblate spheroid.`
|
||||
expected := txt.Clip(longName, txt.ClipDefault)
|
||||
slugExpected := txt.Clip(longName, txt.ClipSlug)
|
||||
album := NewAlbum(longName, TypeDefault)
|
||||
album := NewAlbum(longName, TypeAlbum)
|
||||
assert.Equal(t, expected, album.AlbumTitle)
|
||||
assert.Contains(t, album.AlbumSlug, slug.Make(slugExpected))
|
||||
})
|
||||
|
@ -65,7 +65,7 @@ is an oblate spheroid.`
|
|||
|
||||
func TestAlbum_Save(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
album := NewAlbum("Old Name", TypeDefault)
|
||||
album := NewAlbum("Old Name", TypeAlbum)
|
||||
|
||||
assert.Equal(t, "Old Name", album.AlbumTitle)
|
||||
assert.Equal(t, "old-name", album.AlbumSlug)
|
||||
|
|
|
@ -28,6 +28,7 @@ const (
|
|||
TitleUnknown = "Unknown"
|
||||
|
||||
TypeDefault = ""
|
||||
TypeAlbum = "album"
|
||||
TypeFolder = "folder"
|
||||
TypeMoment = "moment"
|
||||
TypeImage = "image"
|
||||
|
|
|
@ -25,7 +25,7 @@ var LocationFixtures = LocationMap{
|
|||
LocUID: "85d1ea7d382c",
|
||||
PlaceUID: PlaceFixtures.Get("mexico").PlaceUID,
|
||||
LocName: "Adosada Platform",
|
||||
LocCategory: "tourism",
|
||||
LocCategory: "botanical garden",
|
||||
Place: PlaceFixtures.Pointer("mexico"),
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
|
@ -54,7 +54,7 @@ var LocationFixtures = LocationMap{
|
|||
PlaceUID: PlaceFixtures.Get("zinkwazi").PlaceUID,
|
||||
Place: PlaceFixtures.Pointer("zinkwazi"),
|
||||
LocName: "Zinkwazi Beach",
|
||||
LocCategory: "",
|
||||
LocCategory: "beach",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
|
@ -64,7 +64,7 @@ var LocationFixtures = LocationMap{
|
|||
PlaceUID: PlaceFixtures.Get("holidaypark").PlaceUID,
|
||||
Place: PlaceFixtures.Pointer("holidaypark"),
|
||||
LocName: "Holiday Park",
|
||||
LocCategory: "",
|
||||
LocCategory: "park",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
|
@ -74,7 +74,7 @@ var LocationFixtures = LocationMap{
|
|||
PlaceUID: PlaceFixtures.Get("emptyNameLongCity").PlaceUID,
|
||||
Place: PlaceFixtures.Pointer("emptyNameLongCity"),
|
||||
LocName: "",
|
||||
LocCategory: "",
|
||||
LocCategory: "botanical garden",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
|
@ -84,7 +84,7 @@ var LocationFixtures = LocationMap{
|
|||
PlaceUID: PlaceFixtures.Get("emptyNameShortCity").PlaceUID,
|
||||
Place: PlaceFixtures.Pointer("emptyNameShortCity"),
|
||||
LocName: "",
|
||||
LocCategory: "",
|
||||
LocCategory: "botanical garden",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
|
@ -94,7 +94,7 @@ var LocationFixtures = LocationMap{
|
|||
PlaceUID: PlaceFixtures.Get("veryLongLocName").PlaceUID,
|
||||
Place: PlaceFixtures.Pointer("veryLongLocName"),
|
||||
LocName: "longlonglonglonglonglonglonglonglonglonglonglonglongName",
|
||||
LocCategory: "",
|
||||
LocCategory: "cape",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
|
@ -104,7 +104,7 @@ var LocationFixtures = LocationMap{
|
|||
PlaceUID: PlaceFixtures.Get("mediumLongLocName").PlaceUID,
|
||||
Place: PlaceFixtures.Pointer("mediumLongLocName"),
|
||||
LocName: "longlonglonglonglonglongName",
|
||||
LocCategory: "",
|
||||
LocCategory: "botanical garden",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
|
|
|
@ -37,7 +37,7 @@ func TestLocation_Keywords(t *testing.T) {
|
|||
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)
|
||||
assert.Equal(t, []string{"adosada", "ancient", "botanical", "garden", "mexico", "platform", "pyramid", "state-of-mexico", "teotihuacán"}, r)
|
||||
})
|
||||
t.Run("caravan park", func(t *testing.T) {
|
||||
m := LocationFixtures["caravan park"]
|
||||
|
@ -55,6 +55,11 @@ func TestLocation_Find(t *testing.T) {
|
|||
t.Run("invalid api", func(t *testing.T) {
|
||||
l := NewLocation(2, 1)
|
||||
err := l.Find("")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("error expected")
|
||||
}
|
||||
|
||||
assert.Equal(t, "maps: reverse lookup disabled", err.Error())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ var PlaceFixtures = PlacesMap{
|
|||
PlaceUID: "85d1ea7d3278",
|
||||
LocLabel: "Teotihuacán, Mexico, Mexico",
|
||||
LocCity: "Teotihuacán",
|
||||
LocState: "Mexico",
|
||||
LocState: "State of Mexico",
|
||||
LocCountry: "mx",
|
||||
LocKeywords: "ancient, pyramid",
|
||||
LocNotes: "",
|
||||
|
|
|
@ -9,7 +9,7 @@ func TestPlaceMap_Get(t *testing.T) {
|
|||
t.Run("get existing place", func(t *testing.T) {
|
||||
r := PlaceFixtures.Get("mexico")
|
||||
assert.Equal(t, "Teotihuacán", r.LocCity)
|
||||
assert.Equal(t, "Mexico", r.LocState)
|
||||
assert.Equal(t, "State of Mexico", r.LocState)
|
||||
assert.IsType(t, Place{}, r)
|
||||
})
|
||||
t.Run("get not existing place", func(t *testing.T) {
|
||||
|
@ -24,7 +24,7 @@ func TestPlaceMap_Pointer(t *testing.T) {
|
|||
t.Run("get existing place pointer", func(t *testing.T) {
|
||||
r := PlaceFixtures.Pointer("mexico")
|
||||
assert.Equal(t, "Teotihuacán", r.LocCity)
|
||||
assert.Equal(t, "Mexico", r.LocState)
|
||||
assert.Equal(t, "State of Mexico", r.LocState)
|
||||
assert.IsType(t, &Place{}, r)
|
||||
})
|
||||
t.Run("get not existing place pointer", func(t *testing.T) {
|
||||
|
|
|
@ -4,6 +4,8 @@ package form
|
|||
type AlbumSearch struct {
|
||||
Query string `form:"q"`
|
||||
ID string `form:"id"`
|
||||
Type string `form:"type"`
|
||||
Category string `form:"category"`
|
||||
Slug string `form:"slug"`
|
||||
Title string `form:"title"`
|
||||
Country string `json:"country"`
|
||||
|
|
18
internal/maps/country.go
Normal file
18
internal/maps/country.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package maps
|
||||
|
||||
import "strings"
|
||||
|
||||
// CountryName tries to find a matching country name for a code.
|
||||
func CountryName(code string) string {
|
||||
if code == "" {
|
||||
code = "zz"
|
||||
} else {
|
||||
code = strings.ToLower(code)
|
||||
}
|
||||
|
||||
if name, ok := CountryNames[code]; ok {
|
||||
return name
|
||||
}
|
||||
|
||||
return CountryNames["zz"]
|
||||
}
|
19
internal/maps/country_test.go
Normal file
19
internal/maps/country_test.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package maps
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCountryName(t *testing.T) {
|
||||
t.Run("gb", func(t *testing.T) {
|
||||
result := CountryName("gb")
|
||||
assert.Equal(t, "United Kingdom", result)
|
||||
})
|
||||
|
||||
t.Run("us", func(t *testing.T) {
|
||||
result := CountryName("us")
|
||||
assert.Equal(t, "USA", result)
|
||||
})
|
||||
}
|
|
@ -82,7 +82,7 @@ func AlbumSearch(f form.AlbumSearch) (results []AlbumResult, err error) {
|
|||
Group("albums.id")
|
||||
|
||||
if f.ID != "" {
|
||||
s = s.Where("albums.album_uid = ?", f.ID)
|
||||
s = s.Where("albums.album_uid IN (?)", strings.Split(f.ID, ","))
|
||||
|
||||
if result := s.Scan(&results); result.Error != nil {
|
||||
return results, result.Error
|
||||
|
@ -96,6 +96,14 @@ func AlbumSearch(f form.AlbumSearch) (results []AlbumResult, err error) {
|
|||
s = s.Where("LOWER(albums.album_title) LIKE ?", likeString)
|
||||
}
|
||||
|
||||
if f.Type != "" {
|
||||
s = s.Where("albums.album_type IN (?)", strings.Split(f.Type, ","))
|
||||
}
|
||||
|
||||
if f.Category != "" {
|
||||
s = s.Where("albums.album_category IN (?)", strings.Split(f.Category, ","))
|
||||
}
|
||||
|
||||
if f.Favorite {
|
||||
s = s.Where("albums.album_favorite = 1")
|
||||
}
|
||||
|
|
149
internal/query/moments.go
Normal file
149
internal/query/moments.go
Normal file
|
@ -0,0 +1,149 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/photoprism/photoprism/internal/maps"
|
||||
)
|
||||
|
||||
// Moment contains photo counts per month and year
|
||||
type Moment struct {
|
||||
PhotoCategory string `json:"Category"`
|
||||
PhotoCountry string `json:"Country"`
|
||||
PhotoState string `json:"State"`
|
||||
PhotoYear int `json:"Year"`
|
||||
PhotoMonth int `json:"Month"`
|
||||
PhotoCount int `json:"Count"`
|
||||
}
|
||||
|
||||
var MomentCategory = map[string]string{
|
||||
"botanical garden": "Botanical Gardens",
|
||||
"nature reserve": "Nature Reserves",
|
||||
"bay": "Bays, Capes & Beaches",
|
||||
"beach": "Bays, Capes & Beaches",
|
||||
"cape": "Bays, Capes & Beaches",
|
||||
}
|
||||
|
||||
// Slug returns an identifier string for a moment.
|
||||
func (m Moment) Slug() string {
|
||||
return slug.Make(m.Title())
|
||||
}
|
||||
|
||||
// Title returns an english title for the moment.
|
||||
func (m Moment) Title() string {
|
||||
if m.PhotoYear == 0 && m.PhotoMonth == 0 {
|
||||
if m.PhotoCategory != "" {
|
||||
return MomentCategory[m.PhotoCategory]
|
||||
}
|
||||
|
||||
country := maps.CountryName(m.PhotoCountry)
|
||||
|
||||
if strings.Contains(m.PhotoState, country) {
|
||||
return m.PhotoState
|
||||
}
|
||||
|
||||
if m.PhotoState == "" {
|
||||
return m.PhotoCountry
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s / %s", m.PhotoState, country)
|
||||
}
|
||||
|
||||
if m.PhotoCountry != "" && m.PhotoYear > 1900 && m.PhotoMonth == 0 {
|
||||
if m.PhotoState != "" {
|
||||
return fmt.Sprintf("%s / %s / %d", m.PhotoState, maps.CountryName(m.PhotoCountry), m.PhotoYear)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %d", maps.CountryName(m.PhotoCountry), m.PhotoYear)
|
||||
}
|
||||
|
||||
if m.PhotoYear > 1900 && m.PhotoMonth > 0 && m.PhotoMonth <= 12 {
|
||||
date := time.Date(m.PhotoYear, time.Month(m.PhotoMonth), 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
if m.PhotoCountry == "" {
|
||||
return date.Format("January 2006")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s / %s", maps.CountryName(m.PhotoCountry), date.Format("January 2006"))
|
||||
}
|
||||
|
||||
if m.PhotoMonth > 0 && m.PhotoMonth <= 12 {
|
||||
return time.Month(m.PhotoMonth).String()
|
||||
}
|
||||
|
||||
return maps.CountryName(m.PhotoCountry)
|
||||
}
|
||||
|
||||
type Moments []Moment
|
||||
|
||||
// MomentsTime counts photos by month and year.
|
||||
func MomentsTime(threshold int) (results Moments, err error) {
|
||||
db := UnscopedDb().Table("photos").
|
||||
Where("photos.photo_quality >= 3 AND deleted_at IS NULL AND photo_year > 0 AND photo_month > 0").
|
||||
Select("photos.photo_year, photos.photo_month, COUNT(*) AS photo_count").
|
||||
Group("photos.photo_year, photos.photo_month").
|
||||
Order("photos.photo_year DESC, photos.photo_month DESC").
|
||||
Having("photo_count >= ?", threshold)
|
||||
|
||||
if err := db.Scan(&results).Error; err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// MomentsCountries returns the most popular countries by year.
|
||||
func MomentsCountries(threshold int) (results Moments, err error) {
|
||||
db := UnscopedDb().Table("photos").
|
||||
Where("photos.photo_quality >= 3 AND deleted_at IS NULL AND photo_country <> 'zz' AND photo_year > 0").
|
||||
Select("photo_country, photo_year, COUNT(*) AS photo_count ").
|
||||
Group("photo_country, photo_year").
|
||||
Having("photo_count >= ?", threshold)
|
||||
|
||||
if err := db.Scan(&results).Error; err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// MomentsStates returns the most popular states and countries by year.
|
||||
func MomentsStates(threshold int) (results Moments, err error) {
|
||||
db := UnscopedDb().Table("photos").
|
||||
Joins("JOIN places p ON p.place_uid = photos.place_uid").
|
||||
Where("photos.photo_quality >= 3 AND photos.deleted_at IS NULL AND p.loc_state <> '' AND p.loc_country <> 'zz'").
|
||||
Select("p.loc_country AS photo_country, p.loc_state AS photo_state, COUNT(*) AS photo_count").
|
||||
Group("photo_country, photo_state").
|
||||
Having("photo_count >= ?", threshold)
|
||||
|
||||
if err := db.Scan(&results).Error; err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// MomentsCategories returns the most popular photo categories.
|
||||
func MomentsCategories(threshold int) (results Moments, err error) {
|
||||
var cats []string
|
||||
|
||||
for cat, _ := range MomentCategory {
|
||||
cats = append(cats, cat)
|
||||
}
|
||||
|
||||
db := UnscopedDb().Table("photos").
|
||||
Joins("JOIN locations l ON l.loc_uid = photos.loc_uid AND photos.loc_uid <> 'zz'").
|
||||
Where("photos.photo_quality >= 3 AND photos.deleted_at IS NULL AND l.loc_category IN (?)", cats).
|
||||
Select("l.loc_category AS photo_category, COUNT(*) AS photo_count").
|
||||
Group("photo_category").
|
||||
Having("photo_count >= ?", threshold)
|
||||
|
||||
if err := db.Scan(&results).Error; err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
112
internal/query/moments_test.go
Normal file
112
internal/query/moments_test.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMomentsTime(t *testing.T) {
|
||||
t.Run("result found", func(t *testing.T) {
|
||||
results, err := MomentsTime(1)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(results) < 4 {
|
||||
t.Error("at least 4 results expected")
|
||||
}
|
||||
|
||||
t.Logf("MomentsTime %+v", results)
|
||||
|
||||
for _, moment := range results {
|
||||
assert.Len(t, moment.PhotoCountry, 0)
|
||||
assert.GreaterOrEqual(t, moment.PhotoYear, 1990)
|
||||
assert.LessOrEqual(t, moment.PhotoYear, 2800)
|
||||
assert.GreaterOrEqual(t, moment.PhotoMonth, 1)
|
||||
assert.LessOrEqual(t, moment.PhotoMonth, 12)
|
||||
assert.GreaterOrEqual(t, moment.PhotoCount, 1)
|
||||
t.Logf("Title: %s", moment.Title())
|
||||
t.Logf("Slug: %s", moment.Slug())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMomentsCountries(t *testing.T) {
|
||||
t.Run("result found", func(t *testing.T) {
|
||||
results, err := MomentsCountries(1)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("MomentsCountries %+v", results)
|
||||
|
||||
if len(results) < 1 {
|
||||
t.Error("at least one result expected")
|
||||
}
|
||||
|
||||
for _, moment := range results {
|
||||
assert.Len(t, moment.PhotoCountry, 2)
|
||||
assert.GreaterOrEqual(t, moment.PhotoYear, 1990)
|
||||
assert.LessOrEqual(t, moment.PhotoYear, 2800)
|
||||
assert.Equal(t, moment.PhotoMonth, 0)
|
||||
assert.GreaterOrEqual(t, moment.PhotoCount, 1)
|
||||
t.Logf("Title: %s", moment.Title())
|
||||
t.Logf("Slug: %s", moment.Slug())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMomentsStates(t *testing.T) {
|
||||
t.Run("result found", func(t *testing.T) {
|
||||
results, err := MomentsStates(1)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("MomentsStates %+v", results)
|
||||
|
||||
if len(results) < 1 {
|
||||
t.Error("at least one result expected")
|
||||
}
|
||||
|
||||
for _, moment := range results {
|
||||
assert.Len(t, moment.PhotoCountry, 2)
|
||||
assert.NotEmpty(t, moment.PhotoState)
|
||||
assert.Equal(t, moment.PhotoYear, 0)
|
||||
assert.Equal(t, moment.PhotoMonth, 0)
|
||||
assert.GreaterOrEqual(t, moment.PhotoCount, 1)
|
||||
t.Logf("Title: %s", moment.Title())
|
||||
t.Logf("Slug: %s", moment.Slug())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMomentsCategories(t *testing.T) {
|
||||
t.Run("result found", func(t *testing.T) {
|
||||
results, err := MomentsCategories(1)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("MomentsCategories %+v", results)
|
||||
|
||||
if len(results) < 1 {
|
||||
t.Error("at least one result expected")
|
||||
}
|
||||
|
||||
for _, moment := range results {
|
||||
assert.NotEmpty(t, moment.PhotoCategory)
|
||||
assert.Empty(t, moment.PhotoCountry)
|
||||
assert.Empty(t, moment.PhotoState)
|
||||
assert.Equal(t, moment.PhotoYear, 0)
|
||||
assert.Equal(t, moment.PhotoMonth, 0)
|
||||
assert.GreaterOrEqual(t, moment.PhotoCount, 1)
|
||||
t.Logf("Title: %s", moment.Title())
|
||||
t.Logf("Slug: %s", moment.Slug())
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package query
|
||||
|
||||
// MomentsTimeResult contains photo counts per month and year
|
||||
type MomentsTimeResult struct {
|
||||
PhotoYear int
|
||||
PhotoMonth int
|
||||
Count int
|
||||
}
|
||||
|
||||
// GetMomentsTime counts photos per month and year
|
||||
func GetMomentsTime() (results []MomentsTimeResult, err error) {
|
||||
s := UnscopedDb()
|
||||
|
||||
s = s.Table("photos").
|
||||
Where("deleted_at IS NULL").
|
||||
Select("photos.photo_year, photos.photo_month, COUNT(*) AS count").
|
||||
Group("photos.photo_year, photos.photo_month").
|
||||
Order("photos.photo_year DESC, photos.photo_month DESC")
|
||||
|
||||
if result := s.Scan(&results); result.Error != nil {
|
||||
return results, result.Error
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetMomentsTime(t *testing.T) {
|
||||
t.Run("result found", func(t *testing.T) {
|
||||
result, err := GetMomentsTime()
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2790, result[0].PhotoYear)
|
||||
assert.Equal(t, 2, result[0].Count)
|
||||
})
|
||||
}
|
|
@ -142,6 +142,7 @@ MX:UMS
|
|||
CO:Colombia
|
||||
CO:Republic of Colombia
|
||||
US:California
|
||||
US:Washington
|
||||
US:New York
|
||||
US:NYC
|
||||
US:Florida
|
||||
|
@ -286,7 +287,6 @@ PT:Portugal
|
|||
ER:Eritrea
|
||||
IS:Iceland
|
||||
IS:Ísland
|
||||
IS:Island
|
||||
AF:Afghanistan
|
||||
AF:Islamic Republic of Afghanistan
|
||||
IN:India
|
||||
|
@ -419,6 +419,7 @@ CA:Toronto
|
|||
CA:Ottawa
|
||||
CA:Quebec
|
||||
CA:Ontario
|
||||
CA:Alberta
|
||||
IL:Israel
|
||||
IL:State of Israel
|
||||
IL:Holy Land
|
||||
|
|
Loading…
Reference in a new issue