1package sql
2
3import (
4 "context"
5 "time"
6
7 "gorm.io/gorm"
8
9 "git.sr.ht/~gabrielgio/img/pkg/components/media"
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 MediaID uint
25 Media Media
26 Description *string
27 Camera *string
28 Maker *string
29 Lens *string
30 DateShot *time.Time
31 Exposure *float64
32 Aperture *float64
33 Iso *int64
34 FocalLength *float64
35 Flash *int64
36 Orientation *int64
37 ExposureProgram *int64
38 GPSLatitude *float64
39 GPSLongitude *float64
40 }
41
42 MediaRepository struct {
43 db *gorm.DB
44 }
45)
46
47var _ media.Repository = &MediaRepository{}
48
49func (self *Media) ToModel() *media.Media {
50 return &media.Media{
51 ID: self.ID,
52 Path: self.Path,
53 PathHash: self.PathHash,
54 Name: self.Name,
55 MIMEType: self.MIMEType,
56 }
57}
58
59func (m *MediaEXIF) ToModel() *media.MediaEXIF {
60 return &media.MediaEXIF{
61 Description: m.Description,
62 Camera: m.Camera,
63 Maker: m.Maker,
64 Lens: m.Lens,
65 DateShot: m.DateShot,
66 Exposure: m.Exposure,
67 Aperture: m.Aperture,
68 Iso: m.Iso,
69 FocalLength: m.FocalLength,
70 Flash: m.Flash,
71 Orientation: m.Orientation,
72 ExposureProgram: m.ExposureProgram,
73 GPSLatitude: m.GPSLatitude,
74 GPSLongitude: m.GPSLongitude,
75 }
76}
77
78func NewMediaRepository(db *gorm.DB) *MediaRepository {
79 return &MediaRepository{
80 db: db,
81 }
82}
83
84func (self *MediaRepository) Create(ctx context.Context, createMedia *media.CreateMedia) error {
85 media := &Media{
86 Name: createMedia.Name,
87 Path: createMedia.Path,
88 PathHash: createMedia.PathHash,
89 MIMEType: createMedia.MIMEType,
90 }
91
92 result := self.db.
93 WithContext(ctx).
94 Create(media)
95 if result.Error != nil {
96 return result.Error
97 }
98
99 return nil
100}
101
102func (self *MediaRepository) Exists(ctx context.Context, path string) (bool, error) {
103 var exists bool
104 result := self.db.
105 WithContext(ctx).
106 Model(&Media{}).
107 Select("count(id) > 0").
108 Where("path_hash = ?", path).
109 Find(&exists)
110
111 if result.Error != nil {
112 return false, result.Error
113 }
114
115 return exists, nil
116}
117
118func (self *MediaRepository) List(ctx context.Context, pagination *media.Pagination) ([]*media.Media, error) {
119 medias := make([]*Media, 0)
120 result := self.db.
121 WithContext(ctx).
122 Model(&Media{}).
123 Offset(pagination.Page * pagination.Size).
124 Limit(pagination.Size).
125 Order("created_at DESC").
126 Find(&medias)
127
128 if result.Error != nil {
129 return nil, result.Error
130 }
131
132 m := list.Map(medias, func(s *Media) *media.Media {
133 return s.ToModel()
134 })
135
136 return m, nil
137}
138
139func (self *MediaRepository) Get(ctx context.Context, pathHash string) (*media.Media, error) {
140 m := &Media{}
141 result := self.db.
142 WithContext(ctx).
143 Model(&Media{}).
144 Where("path_hash = ?", pathHash).
145 Limit(1).
146 Take(m)
147
148 if result.Error != nil {
149 return nil, result.Error
150 }
151
152 return m.ToModel(), nil
153}
154
155func (self *MediaRepository) GetPath(ctx context.Context, pathHash string) (string, error) {
156 var path string
157 result := self.db.
158 WithContext(ctx).
159 Model(&Media{}).
160 Select("path").
161 Where("path_hash = ?", pathHash).
162 Limit(1).
163 Find(&path)
164
165 if result.Error != nil {
166 return "", result.Error
167 }
168
169 return path, nil
170}
171
172func (m *MediaRepository) GetEXIF(ctx context.Context, mediaID uint) (*media.MediaEXIF, error) {
173 exif := &MediaEXIF{}
174 result := m.db.
175 WithContext(ctx).
176 Model(&Media{}).
177 Where("media_id = ?", mediaID).
178 Limit(1).
179 Take(m)
180
181 if result.Error != nil {
182 return nil, result.Error
183 }
184
185 return exif.ToModel(), nil
186}
187
188func (s *MediaRepository) CreateEXIF(ctx context.Context, id uint, info *media.MediaEXIF) error {
189 media := &MediaEXIF{
190 MediaID: id,
191 Description: info.Description,
192 Camera: info.Camera,
193 Maker: info.Maker,
194 Lens: info.Lens,
195 DateShot: info.DateShot,
196 Exposure: info.Exposure,
197 Aperture: info.Aperture,
198 Iso: info.Iso,
199 FocalLength: info.FocalLength,
200 Flash: info.Flash,
201 Orientation: info.Orientation,
202 ExposureProgram: info.ExposureProgram,
203 GPSLatitude: info.GPSLatitude,
204 GPSLongitude: info.GPSLongitude,
205 }
206
207 result := s.db.
208 WithContext(ctx).
209 Create(media)
210 if result.Error != nil {
211 return result.Error
212 }
213
214 return nil
215}
216
217func (r *MediaRepository) GetEmptyEXIF(ctx context.Context, pagination *media.Pagination) ([]*media.Media, error) {
218 medias := make([]*Media, 0)
219 result := r.db.
220 WithContext(ctx).
221 Model(&Media{}).
222 Joins("left join media_exifs on media.id = media_exifs.media_id").
223 Where("media_exifs.media_id IS NULL").
224 Offset(pagination.Page * pagination.Size).
225 Limit(pagination.Size).
226 Order("created_at DESC").
227 Find(&medias)
228
229 if result.Error != nil {
230 return nil, result.Error
231 }
232
233 m := list.Map(medias, func(s *Media) *media.Media {
234 return s.ToModel()
235 })
236
237 return m, nil
238}