lens @ c9e5d242cbba5e3b9d3ddc4465a9a1367e44cd16

  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 `gorm:"not null"`
 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 `gorm:"not null"`
 48		Media   Media
 49	}
 50
 51	MediaAlbum struct {
 52		gorm.Model
 53		ParentID *uint
 54		Parent   *MediaAlbum
 55		Name     string
 56		Path     string `gorm:"not null; unique"`
 57	}
 58
 59	MediaAlbumFile struct {
 60		gorm.Model
 61		MediaID uint
 62		Media   Media
 63		AlbumID uint
 64		Album   MediaAlbum
 65	}
 66
 67	MediaRepository struct {
 68		db *gorm.DB
 69	}
 70)
 71
 72var _ repository.MediaRepository = &MediaRepository{}
 73
 74func (m *Media) ToModel() *repository.Media {
 75	return &repository.Media{
 76		ID:       m.ID,
 77		Path:     m.Path,
 78		PathHash: m.PathHash,
 79		Name:     m.Name,
 80		MIMEType: m.MIMEType,
 81	}
 82}
 83
 84func (m *MediaEXIF) ToModel() *repository.MediaEXIF {
 85	return &repository.MediaEXIF{
 86		Height:          m.Height,
 87		Width:           m.Width,
 88		Description:     m.Description,
 89		Camera:          m.Camera,
 90		Maker:           m.Maker,
 91		Lens:            m.Lens,
 92		DateShot:        m.DateShot,
 93		Exposure:        m.Exposure,
 94		Aperture:        m.Aperture,
 95		Iso:             m.Iso,
 96		FocalLength:     m.FocalLength,
 97		Flash:           m.Flash,
 98		Orientation:     m.Orientation,
 99		ExposureProgram: m.ExposureProgram,
100		GPSLatitude:     m.GPSLatitude,
101		GPSLongitude:    m.GPSLongitude,
102	}
103}
104
105func (a *MediaAlbum) ToModel() *repository.Album {
106	return &repository.Album{
107		ID:   a.ID,
108		Name: a.Name,
109		Path: a.Path,
110	}
111}
112
113func (m *MediaThumbnail) ToModel() *repository.MediaThumbnail {
114	return &repository.MediaThumbnail{
115		Path: m.Path,
116	}
117}
118
119func NewMediaRepository(db *gorm.DB) *MediaRepository {
120	return &MediaRepository{
121		db: db,
122	}
123}
124
125func (self *MediaRepository) Create(ctx context.Context, createMedia *repository.CreateMedia) error {
126	media := &Media{
127		Name:     createMedia.Name,
128		Path:     createMedia.Path,
129		PathHash: createMedia.PathHash,
130		MIMEType: createMedia.MIMEType,
131	}
132
133	result := self.db.
134		WithContext(ctx).
135		Create(media)
136	if result.Error != nil {
137		return result.Error
138	}
139
140	return nil
141}
142
143func (self *MediaRepository) Exists(ctx context.Context, path string) (bool, error) {
144	var exists bool
145	result := self.db.
146		WithContext(ctx).
147		Model(&Media{}).
148		Select("count(id) > 0").
149		Where("path_hash = ?", path).
150		Find(&exists)
151
152	if result.Error != nil {
153		return false, result.Error
154	}
155
156	return exists, nil
157}
158
159func (self *MediaRepository) List(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
160	medias := make([]*Media, 0)
161	result := self.db.
162		WithContext(ctx).
163		Model(&Media{}).
164		Offset(pagination.Page * pagination.Size).
165		Limit(pagination.Size).
166		Where("path like '" + pagination.Path + "%'").
167		Order("created_at DESC").
168		Find(&medias)
169
170	if result.Error != nil {
171		return nil, result.Error
172	}
173
174	m := list.Map(medias, func(s *Media) *repository.Media {
175		return s.ToModel()
176	})
177
178	return m, nil
179}
180
181func (self *MediaRepository) Get(ctx context.Context, pathHash string) (*repository.Media, error) {
182	m := &Media{}
183	result := self.db.
184		WithContext(ctx).
185		Model(&Media{}).
186		Where("path_hash = ?", pathHash).
187		Limit(1).
188		Take(m)
189
190	if result.Error != nil {
191		return nil, result.Error
192	}
193
194	return m.ToModel(), nil
195}
196
197func (self *MediaRepository) GetPath(ctx context.Context, pathHash string) (string, error) {
198	var path string
199	result := self.db.
200		WithContext(ctx).
201		Model(&Media{}).
202		Select("path").
203		Where("path_hash = ?", pathHash).
204		Limit(1).
205		Find(&path)
206
207	if result.Error != nil {
208		return "", result.Error
209	}
210
211	return path, nil
212}
213
214func (self *MediaRepository) GetThumbnailPath(ctx context.Context, pathHash string) (string, error) {
215	var path string
216	result := self.db.
217		WithContext(ctx).
218		Model(&Media{}).
219		Select("media_thumbnails.path").
220		Joins("left join media_thumbnails on media.id = media_thumbnails.media_id").
221		Where("media.path_hash = ?", pathHash).
222		Limit(1).
223		Find(&path)
224
225	if result.Error != nil {
226		return "", result.Error
227	}
228
229	return path, nil
230}
231
232func (m *MediaRepository) GetEXIF(ctx context.Context, mediaID uint) (*repository.MediaEXIF, error) {
233	exif := &MediaEXIF{}
234	result := m.db.
235		WithContext(ctx).
236		Model(&Media{}).
237		Where("media_id = ?", mediaID).
238		Limit(1).
239		Take(m)
240
241	if result.Error != nil {
242		return nil, result.Error
243	}
244
245	return exif.ToModel(), nil
246}
247
248func (s *MediaRepository) CreateEXIF(ctx context.Context, id uint, info *repository.MediaEXIF) error {
249	media := &MediaEXIF{
250		MediaID:         id,
251		Width:           info.Width,
252		Height:          info.Height,
253		Description:     info.Description,
254		Camera:          info.Camera,
255		Maker:           info.Maker,
256		Lens:            info.Lens,
257		DateShot:        info.DateShot,
258		Exposure:        info.Exposure,
259		Aperture:        info.Aperture,
260		Iso:             info.Iso,
261		FocalLength:     info.FocalLength,
262		Flash:           info.Flash,
263		Orientation:     info.Orientation,
264		ExposureProgram: info.ExposureProgram,
265		GPSLatitude:     info.GPSLatitude,
266		GPSLongitude:    info.GPSLongitude,
267	}
268
269	result := s.db.
270		WithContext(ctx).
271		Create(media)
272	if result.Error != nil {
273		return result.Error
274	}
275
276	return nil
277}
278
279func (r *MediaRepository) ListEmptyEXIF(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
280	medias := make([]*Media, 0)
281	result := r.db.
282		WithContext(ctx).
283		Model(&Media{}).
284		Joins("left join media_exifs on media.id = media_exifs.media_id").
285		Where("media_exifs.media_id IS NULL AND media.path like '" + pagination.Path + "%'").
286		Offset(pagination.Page * pagination.Size).
287		Limit(pagination.Size).
288		Order("media.created_at DESC").
289		Find(&medias)
290
291	if result.Error != nil {
292		return nil, result.Error
293	}
294
295	m := list.Map(medias, func(s *Media) *repository.Media {
296		return s.ToModel()
297	})
298
299	return m, nil
300}
301
302func (r *MediaRepository) ListEmptyThumbnail(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
303	medias := make([]*Media, 0)
304	result := r.db.
305		WithContext(ctx).
306		Model(&Media{}).
307		Joins("left join media_thumbnails on media.id = media_thumbnails.media_id").
308		Where("media_thumbnails.media_id IS NULL AND media.path like '" + pagination.Path + "%'").
309		Offset(pagination.Page * pagination.Size).
310		Limit(pagination.Size).
311		Order("media.created_at DESC").
312		Find(&medias)
313
314	if result.Error != nil {
315		return nil, result.Error
316	}
317
318	m := list.Map(medias, func(s *Media) *repository.Media {
319		return s.ToModel()
320	})
321
322	return m, nil
323}
324
325func (m *MediaRepository) GetThumbnail(ctx context.Context, mediaID uint) (*repository.MediaThumbnail, error) {
326	thumbnail := &MediaThumbnail{}
327	result := m.db.
328		WithContext(ctx).
329		Model(&Media{}).
330		Where("media_id = ?", mediaID).
331		Limit(1).
332		Take(m)
333
334	if result.Error != nil {
335		return nil, result.Error
336	}
337
338	return thumbnail.ToModel(), nil
339}
340
341func (m *MediaRepository) CreateThumbnail(ctx context.Context, mediaID uint, thumbnail *repository.MediaThumbnail) error {
342	media := &MediaThumbnail{
343		MediaID: mediaID,
344		Path:    thumbnail.Path,
345	}
346
347	result := m.db.
348		WithContext(ctx).
349		Create(media)
350	if result.Error != nil {
351		return result.Error
352	}
353
354	return nil
355}
356
357func (r *MediaRepository) ListEmptyAlbums(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
358	medias := make([]*Media, 0)
359	result := r.db.
360		WithContext(ctx).
361		Model(&Media{}).
362		Joins("left join media_album_files on media.id = media_album_files.media_id").
363		Where("media_album_files.media_id IS NULL").
364		Offset(pagination.Page * pagination.Size).
365		Limit(pagination.Size).
366		Order("media.created_at DESC").
367		Find(&medias)
368
369	if result.Error != nil {
370		return nil, result.Error
371	}
372
373	m := list.Map(medias, func(s *Media) *repository.Media {
374		return s.ToModel()
375	})
376
377	return m, nil
378}
379
380func (m *MediaRepository) ExistsAlbumByAbsolutePath(ctx context.Context, path string) (bool, error) {
381	var exists bool
382	result := m.db.
383		WithContext(ctx).
384		Model(&MediaAlbum{}).
385		Select("count(id) > 0").
386		Where("path = ?", path).
387		Find(&exists)
388
389	if result.Error != nil {
390		return false, result.Error
391	}
392
393	return exists, nil
394}
395
396func (r *MediaRepository) GetAlbumByAbsolutePath(ctx context.Context, path string) (*repository.Album, error) {
397	m := &MediaAlbum{}
398	result := r.db.
399		WithContext(ctx).
400		Model(&MediaAlbum{}).
401		Where("path = ?", path).
402		Limit(1).
403		Take(m)
404
405	if result.Error != nil {
406		return nil, result.Error
407	}
408
409	return m.ToModel(), nil
410}
411
412func (r *MediaRepository) GetAlbum(ctx context.Context, albumID uint) (*repository.Album, error) {
413	m := &MediaAlbum{}
414	result := r.db.
415		WithContext(ctx).
416		Model(&MediaAlbum{}).
417		Where("id = ?", albumID).
418		Limit(1).
419		Take(m)
420
421	if result.Error != nil {
422		return nil, result.Error
423	}
424
425	return m.ToModel(), nil
426}
427
428func (m *MediaRepository) CreateAlbum(ctx context.Context, createAlbum *repository.CreateAlbum) (*repository.Album, error) {
429	album := &MediaAlbum{
430		ParentID: createAlbum.ParentID,
431		Name:     createAlbum.Name,
432		Path:     createAlbum.Path,
433	}
434
435	result := m.db.
436		WithContext(ctx).
437		Create(album)
438	if result.Error != nil {
439		return nil, result.Error
440	}
441
442	return album.ToModel(), nil
443}
444
445func (m *MediaRepository) CreateAlbumFile(ctx context.Context, createAlbumFile *repository.CreateAlbumFile) error {
446	albumFile := &MediaAlbumFile{
447		MediaID: createAlbumFile.MediaID,
448		AlbumID: createAlbumFile.AlbumID,
449	}
450
451	result := m.db.
452		WithContext(ctx).
453		Create(albumFile)
454	if result.Error != nil {
455		return result.Error
456	}
457
458	return nil
459}
460
461func (m *MediaRepository) ListAlbums(ctx context.Context, albumID uint) ([]*repository.Album, error) {
462	albums := make([]*MediaAlbum, 0)
463
464	result := m.db.
465		WithContext(ctx).
466		Model(&MediaAlbum{}).
467		Where("parent_id = ?", albumID).
468		Find(&albums)
469
470	if result.Error != nil {
471		return nil, result.Error
472	}
473
474	return list.Map(albums, func(a *MediaAlbum) *repository.Album {
475		return a.ToModel()
476	}), nil
477}