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