1package sql
2
3import (
4 "context"
5 "time"
6
7 "gorm.io/gorm"
8
9 "git.sr.ht/~gabrielgio/img/pkg/database/repository"
10 "git.sr.ht/~gabrielgio/img/pkg/list"
11)
12
13type (
14 Media struct {
15 gorm.Model
16 Name string `gorm:"not null"`
17 Path string `gorm:"not null;unique"`
18 PathHash string `gorm:"not null;unique"`
19 MIMEType string `gorm:"not null"`
20 }
21
22 MediaEXIF struct {
23 gorm.Model
24 Width *float64
25 Height *float64
26 MediaID uint
27 Media Media
28 Description *string
29 Camera *string
30 Maker *string
31 Lens *string
32 DateShot *time.Time
33 Exposure *float64
34 Aperture *float64
35 Iso *int64
36 FocalLength *float64
37 Flash *int64
38 Orientation *int64
39 ExposureProgram *int64
40 GPSLatitude *float64
41 GPSLongitude *float64
42 }
43
44 MediaThumbnail struct {
45 gorm.Model
46 Path string
47 MediaID uint
48 Media Media
49 }
50
51 MediaRepository struct {
52 db *gorm.DB
53 }
54)
55
56var _ repository.MediaRepository = &MediaRepository{}
57
58func (self *Media) ToModel() *repository.Media {
59 return &repository.Media{
60 ID: self.ID,
61 Path: self.Path,
62 PathHash: self.PathHash,
63 Name: self.Name,
64 MIMEType: self.MIMEType,
65 }
66}
67
68func (m *MediaEXIF) ToModel() *repository.MediaEXIF {
69 return &repository.MediaEXIF{
70 Height: m.Height,
71 Width: m.Width,
72 Description: m.Description,
73 Camera: m.Camera,
74 Maker: m.Maker,
75 Lens: m.Lens,
76 DateShot: m.DateShot,
77 Exposure: m.Exposure,
78 Aperture: m.Aperture,
79 Iso: m.Iso,
80 FocalLength: m.FocalLength,
81 Flash: m.Flash,
82 Orientation: m.Orientation,
83 ExposureProgram: m.ExposureProgram,
84 GPSLatitude: m.GPSLatitude,
85 GPSLongitude: m.GPSLongitude,
86 }
87}
88
89func (m *MediaThumbnail) ToModel() *repository.MediaThumbnail {
90 return &repository.MediaThumbnail{
91 Path: m.Path,
92 }
93}
94
95func NewMediaRepository(db *gorm.DB) *MediaRepository {
96 return &MediaRepository{
97 db: db,
98 }
99}
100
101func (self *MediaRepository) Create(ctx context.Context, createMedia *repository.CreateMedia) error {
102 media := &Media{
103 Name: createMedia.Name,
104 Path: createMedia.Path,
105 PathHash: createMedia.PathHash,
106 MIMEType: createMedia.MIMEType,
107 }
108
109 result := self.db.
110 WithContext(ctx).
111 Create(media)
112 if result.Error != nil {
113 return result.Error
114 }
115
116 return nil
117}
118
119func (self *MediaRepository) Exists(ctx context.Context, path string) (bool, error) {
120 var exists bool
121 result := self.db.
122 WithContext(ctx).
123 Model(&Media{}).
124 Select("count(id) > 0").
125 Where("path_hash = ?", path).
126 Find(&exists)
127
128 if result.Error != nil {
129 return false, result.Error
130 }
131
132 return exists, nil
133}
134
135func (self *MediaRepository) List(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
136 medias := make([]*Media, 0)
137 result := self.db.
138 WithContext(ctx).
139 Model(&Media{}).
140 Offset(pagination.Page * pagination.Size).
141 Limit(pagination.Size).
142 Order("created_at DESC").
143 Find(&medias)
144
145 if result.Error != nil {
146 return nil, result.Error
147 }
148
149 m := list.Map(medias, func(s *Media) *repository.Media {
150 return s.ToModel()
151 })
152
153 return m, nil
154}
155
156func (self *MediaRepository) Get(ctx context.Context, pathHash string) (*repository.Media, error) {
157 m := &Media{}
158 result := self.db.
159 WithContext(ctx).
160 Model(&Media{}).
161 Where("path_hash = ?", pathHash).
162 Limit(1).
163 Take(m)
164
165 if result.Error != nil {
166 return nil, result.Error
167 }
168
169 return m.ToModel(), nil
170}
171
172func (self *MediaRepository) GetPath(ctx context.Context, pathHash string) (string, error) {
173 var path string
174 result := self.db.
175 WithContext(ctx).
176 Model(&Media{}).
177 Select("path").
178 Where("path_hash = ?", pathHash).
179 Limit(1).
180 Find(&path)
181
182 if result.Error != nil {
183 return "", result.Error
184 }
185
186 return path, nil
187}
188
189func (self *MediaRepository) GetThumbnailPath(ctx context.Context, pathHash string) (string, error) {
190 var path string
191 result := self.db.
192 WithContext(ctx).
193 Model(&Media{}).
194 Select("media_thumbnails.path").
195 Joins("left join media_thumbnails on media.id = media_thumbnails.media_id").
196 Where("media.path_hash = ?", pathHash).
197 Limit(1).
198 Find(&path)
199
200 if result.Error != nil {
201 return "", result.Error
202 }
203
204 return path, nil
205}
206
207func (m *MediaRepository) GetEXIF(ctx context.Context, mediaID uint) (*repository.MediaEXIF, error) {
208 exif := &MediaEXIF{}
209 result := m.db.
210 WithContext(ctx).
211 Model(&Media{}).
212 Where("media_id = ?", mediaID).
213 Limit(1).
214 Take(m)
215
216 if result.Error != nil {
217 return nil, result.Error
218 }
219
220 return exif.ToModel(), nil
221}
222
223func (s *MediaRepository) CreateEXIF(ctx context.Context, id uint, info *repository.MediaEXIF) error {
224 media := &MediaEXIF{
225 MediaID: id,
226 Width: info.Width,
227 Height: info.Height,
228 Description: info.Description,
229 Camera: info.Camera,
230 Maker: info.Maker,
231 Lens: info.Lens,
232 DateShot: info.DateShot,
233 Exposure: info.Exposure,
234 Aperture: info.Aperture,
235 Iso: info.Iso,
236 FocalLength: info.FocalLength,
237 Flash: info.Flash,
238 Orientation: info.Orientation,
239 ExposureProgram: info.ExposureProgram,
240 GPSLatitude: info.GPSLatitude,
241 GPSLongitude: info.GPSLongitude,
242 }
243
244 result := s.db.
245 WithContext(ctx).
246 Create(media)
247 if result.Error != nil {
248 return result.Error
249 }
250
251 return nil
252}
253
254func (r *MediaRepository) ListEmptyEXIF(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
255 medias := make([]*Media, 0)
256 result := r.db.
257 WithContext(ctx).
258 Model(&Media{}).
259 Joins("left join media_exifs on media.id = media_exifs.media_id").
260 Where("media_exifs.media_id IS NULL").
261 Offset(pagination.Page * pagination.Size).
262 Limit(pagination.Size).
263 Order("media.created_at DESC").
264 Find(&medias)
265
266 if result.Error != nil {
267 return nil, result.Error
268 }
269
270 m := list.Map(medias, func(s *Media) *repository.Media {
271 return s.ToModel()
272 })
273
274 return m, nil
275}
276
277func (r *MediaRepository) ListEmptyThumbnail(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
278 medias := make([]*Media, 0)
279 result := r.db.
280 WithContext(ctx).
281 Model(&Media{}).
282 Joins("left join media_thumbnails on media.id = media_thumbnails.media_id").
283 Where("media_thumbnails.media_id IS NULL").
284 Offset(pagination.Page * pagination.Size).
285 Limit(pagination.Size).
286 Order("media.created_at DESC").
287 Find(&medias)
288
289 if result.Error != nil {
290 return nil, result.Error
291 }
292
293 m := list.Map(medias, func(s *Media) *repository.Media {
294 return s.ToModel()
295 })
296
297 return m, nil
298}
299
300func (m *MediaRepository) GetThumbnail(ctx context.Context, mediaID uint) (*repository.MediaThumbnail, error) {
301 thumbnail := &MediaThumbnail{}
302 result := m.db.
303 WithContext(ctx).
304 Model(&Media{}).
305 Where("media_id = ?", mediaID).
306 Limit(1).
307 Take(m)
308
309 if result.Error != nil {
310 return nil, result.Error
311 }
312
313 return thumbnail.ToModel(), nil
314}
315
316func (m *MediaRepository) CreateThumbnail(ctx context.Context, mediaID uint, thumbnail *repository.MediaThumbnail) error {
317 media := &MediaThumbnail{
318 MediaID: mediaID,
319 Path: thumbnail.Path,
320 }
321
322 result := m.db.
323 WithContext(ctx).
324 Create(media)
325 if result.Error != nil {
326 return result.Error
327 }
328
329 return nil
330}