package sql import ( "context" "time" "gorm.io/gorm" "git.sr.ht/~gabrielgio/img/pkg/database/repository" "git.sr.ht/~gabrielgio/img/pkg/list" ) type ( Media struct { gorm.Model Name string `gorm:"not null"` Path string `gorm:"not null;unique"` PathHash string `gorm:"not null;unique"` MIMEType string `gorm:"not null"` } MediaEXIF struct { gorm.Model Width *float64 Height *float64 MediaID uint Media Media Description *string Camera *string Maker *string Lens *string DateShot *time.Time Exposure *float64 Aperture *float64 Iso *int64 FocalLength *float64 Flash *int64 Orientation *int64 ExposureProgram *int64 GPSLatitude *float64 GPSLongitude *float64 } MediaThumbnail struct { gorm.Model Path string MediaID uint Media Media } MediaRepository struct { db *gorm.DB } ) var _ repository.MediaRepository = &MediaRepository{} func (self *Media) ToModel() *repository.Media { return &repository.Media{ ID: self.ID, Path: self.Path, PathHash: self.PathHash, Name: self.Name, MIMEType: self.MIMEType, } } func (m *MediaEXIF) ToModel() *repository.MediaEXIF { return &repository.MediaEXIF{ Height: m.Height, Width: m.Width, Description: m.Description, Camera: m.Camera, Maker: m.Maker, Lens: m.Lens, DateShot: m.DateShot, Exposure: m.Exposure, Aperture: m.Aperture, Iso: m.Iso, FocalLength: m.FocalLength, Flash: m.Flash, Orientation: m.Orientation, ExposureProgram: m.ExposureProgram, GPSLatitude: m.GPSLatitude, GPSLongitude: m.GPSLongitude, } } func (m *MediaThumbnail) ToModel() *repository.MediaThumbnail { return &repository.MediaThumbnail{ Path: m.Path, } } func NewMediaRepository(db *gorm.DB) *MediaRepository { return &MediaRepository{ db: db, } } func (self *MediaRepository) Create(ctx context.Context, createMedia *repository.CreateMedia) error { media := &Media{ Name: createMedia.Name, Path: createMedia.Path, PathHash: createMedia.PathHash, MIMEType: createMedia.MIMEType, } result := self.db. WithContext(ctx). Create(media) if result.Error != nil { return result.Error } return nil } func (self *MediaRepository) Exists(ctx context.Context, path string) (bool, error) { var exists bool result := self.db. WithContext(ctx). Model(&Media{}). Select("count(id) > 0"). Where("path_hash = ?", path). Find(&exists) if result.Error != nil { return false, result.Error } return exists, nil } func (self *MediaRepository) List(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) { medias := make([]*Media, 0) result := self.db. WithContext(ctx). Model(&Media{}). Offset(pagination.Page * pagination.Size). Limit(pagination.Size). Where("path like '" + pagination.Path + "%'"). Order("created_at DESC"). Find(&medias) if result.Error != nil { return nil, result.Error } m := list.Map(medias, func(s *Media) *repository.Media { return s.ToModel() }) return m, nil } func (self *MediaRepository) Get(ctx context.Context, pathHash string) (*repository.Media, error) { m := &Media{} result := self.db. WithContext(ctx). Model(&Media{}). Where("path_hash = ?", pathHash). Limit(1). Take(m) if result.Error != nil { return nil, result.Error } return m.ToModel(), nil } func (self *MediaRepository) GetPath(ctx context.Context, pathHash string) (string, error) { var path string result := self.db. WithContext(ctx). Model(&Media{}). Select("path"). Where("path_hash = ?", pathHash). Limit(1). Find(&path) if result.Error != nil { return "", result.Error } return path, nil } func (self *MediaRepository) GetThumbnailPath(ctx context.Context, pathHash string) (string, error) { var path string result := self.db. WithContext(ctx). Model(&Media{}). Select("media_thumbnails.path"). Joins("left join media_thumbnails on media.id = media_thumbnails.media_id"). Where("media.path_hash = ?", pathHash). Limit(1). Find(&path) if result.Error != nil { return "", result.Error } return path, nil } func (m *MediaRepository) GetEXIF(ctx context.Context, mediaID uint) (*repository.MediaEXIF, error) { exif := &MediaEXIF{} result := m.db. WithContext(ctx). Model(&Media{}). Where("media_id = ?", mediaID). Limit(1). Take(m) if result.Error != nil { return nil, result.Error } return exif.ToModel(), nil } func (s *MediaRepository) CreateEXIF(ctx context.Context, id uint, info *repository.MediaEXIF) error { media := &MediaEXIF{ MediaID: id, Width: info.Width, Height: info.Height, Description: info.Description, Camera: info.Camera, Maker: info.Maker, Lens: info.Lens, DateShot: info.DateShot, Exposure: info.Exposure, Aperture: info.Aperture, Iso: info.Iso, FocalLength: info.FocalLength, Flash: info.Flash, Orientation: info.Orientation, ExposureProgram: info.ExposureProgram, GPSLatitude: info.GPSLatitude, GPSLongitude: info.GPSLongitude, } result := s.db. WithContext(ctx). Create(media) if result.Error != nil { return result.Error } return nil } func (r *MediaRepository) ListEmptyEXIF(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) { medias := make([]*Media, 0) result := r.db. WithContext(ctx). Model(&Media{}). Joins("left join media_exifs on media.id = media_exifs.media_id"). Where("media_exifs.media_id IS NULL AND media.path like '" + pagination.Path + "%'"). Offset(pagination.Page * pagination.Size). Limit(pagination.Size). Order("media.created_at DESC"). Find(&medias) if result.Error != nil { return nil, result.Error } m := list.Map(medias, func(s *Media) *repository.Media { return s.ToModel() }) return m, nil } func (r *MediaRepository) ListEmptyThumbnail(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) { medias := make([]*Media, 0) result := r.db. WithContext(ctx). Model(&Media{}). Joins("left join media_thumbnails on media.id = media_thumbnails.media_id"). Where("media_thumbnails.media_id IS NULL AND media.path like '" + pagination.Path + "%'"). Offset(pagination.Page * pagination.Size). Limit(pagination.Size). Order("media.created_at DESC"). Find(&medias) if result.Error != nil { return nil, result.Error } m := list.Map(medias, func(s *Media) *repository.Media { return s.ToModel() }) return m, nil } func (m *MediaRepository) GetThumbnail(ctx context.Context, mediaID uint) (*repository.MediaThumbnail, error) { thumbnail := &MediaThumbnail{} result := m.db. WithContext(ctx). Model(&Media{}). Where("media_id = ?", mediaID). Limit(1). Take(m) if result.Error != nil { return nil, result.Error } return thumbnail.ToModel(), nil } func (m *MediaRepository) CreateThumbnail(ctx context.Context, mediaID uint, thumbnail *repository.MediaThumbnail) error { media := &MediaThumbnail{ MediaID: mediaID, Path: thumbnail.Path, } result := m.db. WithContext(ctx). Create(media) if result.Error != nil { return result.Error } return nil }