/
albums.go
221 lines (191 loc) Β· 8.27 KB
/
albums.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package search
import (
"strings"
"time"
"github.com/dustin/go-humanize/english"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/sortby"
"github.com/photoprism/photoprism/pkg/txt"
)
// Albums finds AlbumResults based on the search form without checking rights or permissions.
func Albums(f form.SearchAlbums) (results AlbumResults, err error) {
return UserAlbums(f, nil)
}
// UserAlbums finds AlbumResults based on the search form and user session.
func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults, err error) {
start := time.Now()
if err = f.ParseQueryString(); err != nil {
log.Debugf("albums: %s", err)
return AlbumResults{}, err
}
// Base query.
s := UnscopedDb().Table("albums").
Select("albums.*, cp.photo_count, cl.link_count, CASE WHEN albums.album_year = 0 THEN 0 ELSE 1 END AS has_year, CASE WHEN albums.album_location = '' THEN 1 ELSE 0 END AS no_location").
Joins("LEFT JOIN (SELECT album_uid, count(photo_uid) AS photo_count FROM photos_albums WHERE hidden = 0 AND missing = 0 GROUP BY album_uid) AS cp ON cp.album_uid = albums.album_uid").
Joins("LEFT JOIN (SELECT share_uid, count(share_uid) AS link_count FROM links GROUP BY share_uid) AS cl ON cl.share_uid = albums.album_uid").
Where("albums.deleted_at IS NULL")
// Check session permissions and apply as needed.
if sess != nil {
user := sess.User()
aclRole := user.AclRole()
// Determine resource to check.
var aclResource acl.Resource
switch f.Type {
case entity.AlbumManual:
aclResource = acl.ResourceAlbums
case entity.AlbumFolder:
aclResource = acl.ResourceFolders
case entity.AlbumMoment:
aclResource = acl.ResourceMoments
case entity.AlbumMonth:
aclResource = acl.ResourceCalendar
case entity.AlbumState:
aclResource = acl.ResourcePlaces
}
// Check user permissions.
if acl.Rules.DenyAll(aclResource, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary, acl.AccessShared, acl.AccessOwn}) {
return AlbumResults{}, ErrForbidden
}
// Limit results by UID, owner and path.
if sess.IsVisitor() || sess.NotRegistered() {
s = s.Where("albums.album_uid IN (?) OR albums.published_at > ?", sess.SharedUIDs(), entity.TimeStamp())
} else if acl.Rules.DenyAll(aclResource, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
s = s.Where("albums.album_uid IN (?) OR albums.created_by = ? OR albums.published_at > ?", sess.SharedUIDs(), user.UserUID, entity.TimeStamp())
}
// Exclude private content?
if acl.Rules.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) || acl.Rules.Deny(aclResource, aclRole, acl.AccessPrivate) {
f.Public = true
f.Private = false
}
}
// Set sort order.
switch f.Order {
case sortby.Count:
s = s.Order("photo_count DESC, albums.album_title, albums.album_uid DESC")
case sortby.Moment, sortby.Newest:
if f.Type == entity.AlbumManual || f.Type == entity.AlbumState {
s = s.Order("albums.album_uid DESC")
} else if f.Type == entity.AlbumMoment {
s = s.Order("has_year, albums.album_year DESC, albums.album_month DESC, albums.album_day DESC, albums.album_title, albums.album_uid DESC")
} else {
s = s.Order("albums.album_year DESC, albums.album_month DESC, albums.album_day DESC, albums.album_title, albums.album_uid DESC")
}
case sortby.Oldest:
if f.Type == entity.AlbumManual || f.Type == entity.AlbumState {
s = s.Order("albums.album_uid ASC")
} else if f.Type == entity.AlbumMoment {
s = s.Order("has_year, albums.album_year ASC, albums.album_month ASC, albums.album_day ASC, albums.album_title, albums.album_uid ASC")
} else {
s = s.Order("albums.album_year ASC, albums.album_month ASC, albums.album_day ASC, albums.album_title, albums.album_uid ASC")
}
case sortby.Added:
s = s.Order("albums.album_uid DESC")
case sortby.Edited:
s = s.Order("albums.updated_at DESC, albums.album_uid DESC")
case sortby.Place:
s = s.Order("no_location, albums.album_location, has_year, albums.album_year DESC, albums.album_month ASC, albums.album_day ASC, albums.album_title, albums.album_uid DESC")
case sortby.Path:
s = s.Order("albums.album_path, albums.album_uid DESC")
case sortby.Category:
s = s.Order("albums.album_category, albums.album_title, albums.album_uid DESC")
case sortby.Slug:
s = s.Order("albums.album_slug ASC, albums.album_uid DESC")
case sortby.Favorites:
if f.Type == entity.AlbumFolder {
s = s.Order("albums.album_favorite DESC, albums.album_path ASC, albums.album_uid DESC")
} else if f.Type == entity.AlbumMonth {
s = s.Order("albums.album_favorite DESC, albums.album_year DESC, albums.album_month DESC, albums.album_day DESC, albums.album_title, albums.album_uid DESC")
} else {
s = s.Order("albums.album_favorite DESC, albums.album_title ASC, albums.album_uid DESC")
}
case sortby.Name:
if f.Type == entity.AlbumFolder {
s = s.Order("albums.album_path ASC, albums.album_uid DESC")
} else {
s = s.Order("albums.album_title ASC, albums.album_uid DESC")
}
case sortby.NameReverse:
if f.Type == entity.AlbumFolder {
s = s.Order("albums.album_path DESC, albums.album_uid DESC")
} else {
s = s.Order("albums.album_title DESC, albums.album_uid DESC")
}
default:
s = s.Order("albums.album_favorite DESC, albums.album_title ASC, albums.album_uid DESC")
}
// Find specific UIDs only?
if txt.NotEmpty(f.UID) {
ids := SplitOr(strings.ToLower(f.UID))
if rnd.ContainsUID(ids, entity.AlbumUID) {
s = s.Where("albums.album_uid IN (?)", ids)
}
}
// Filter by title or path?
if txt.NotEmpty(f.Query) {
q := "%" + strings.Trim(f.Query, " *%") + "%"
if f.Type == entity.AlbumFolder {
s = s.Where("albums.album_title LIKE ? OR albums.album_location LIKE ? OR albums.album_path LIKE ?", q, q, q)
} else {
s = s.Where("albums.album_title LIKE ? OR albums.album_location LIKE ?", q, q)
}
}
// Albums with public pictures only?
if f.Public {
s = s.Where("albums.album_private = 0 AND (albums.album_type <> 'folder' OR albums.album_path IN (SELECT photo_path FROM photos WHERE photo_private = 0 AND photo_quality > -1 AND deleted_at IS NULL))")
} else {
s = s.Where("albums.album_type <> 'folder' OR albums.album_path IN (SELECT photo_path FROM photos WHERE photo_quality > -1 AND deleted_at IS NULL)")
}
if txt.NotEmpty(f.Type) {
s = s.Where("albums.album_type IN (?)", strings.Split(f.Type, txt.Or))
}
if txt.NotEmpty(f.Category) {
s = s.Where("albums.album_category IN (?)", strings.Split(f.Category, txt.Or))
}
if txt.NotEmpty(f.Location) {
s = s.Where("albums.album_location IN (?)", strings.Split(f.Location, txt.Or))
}
if txt.NotEmpty(f.Country) {
s = s.Where("albums.album_country IN (?)", strings.Split(f.Country, txt.Or))
}
// Favorites only?
if f.Favorite {
s = s.Where("albums.album_favorite = 1")
}
// Filter by year?
if txt.NotEmpty(f.Year) {
// Filter by the pictures included if it is a manually managed album, as these do not have an explicit
// year assigned to them, unlike calendar albums and moments for example.
if f.Type == entity.AlbumManual {
s = s.Where("? OR albums.album_uid IN (SELECT DISTINCT pay.album_uid FROM photos_albums pay "+
"JOIN photos py ON pay.photo_uid = py.photo_uid WHERE py.photo_year IN (?) AND pay.hidden = 0 AND pay.missing = 0)",
gorm.Expr(AnyInt("albums.album_year", f.Year, txt.Or, entity.UnknownYear, txt.YearMax)), strings.Split(f.Year, txt.Or))
} else {
s = s.Where(AnyInt("albums.album_year", f.Year, txt.Or, entity.UnknownYear, txt.YearMax))
}
}
// Filter by month?
if txt.NotEmpty(f.Month) {
s = s.Where(AnyInt("albums.album_month", f.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
}
// Filter by day?
if txt.NotEmpty(f.Day) {
s = s.Where(AnyInt("albums.album_day", f.Day, txt.Or, entity.UnknownDay, txt.DayMax))
}
// Limit result count.
if f.Count > 0 && f.Count <= MaxResults {
s = s.Limit(f.Count).Offset(f.Offset)
} else {
s = s.Limit(MaxResults).Offset(f.Offset)
}
// Query database.
if result := s.Scan(&results); result.Error != nil {
return results, result.Error
}
// Log number of results.
log.Debugf("albums: found %s [%s]", english.Plural(len(results), "result", "results"), time.Since(start))
return results, nil
}