1package sqlite
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7
8 "github.com/bnema/dumber/internal/domain/entity"
9 "github.com/bnema/dumber/internal/domain/repository"
10 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite/sqlc"
11 "github.com/bnema/dumber/internal/logging"
12)
13
14// ==================== Favorite Repository ====================
15
16type favoriteRepo struct {
17 queries *sqlc.Queries
18}
19
20// NewFavoriteRepository creates a new SQLite-backed favorite repository.
21func NewFavoriteRepository(db *sql.DB) repository.FavoriteRepository {
22 return &favoriteRepo{queries: sqlc.New(db)}
23}
24
25func (r *favoriteRepo) Save(ctx context.Context, fav *entity.Favorite) error {
26 log := logging.FromContext(ctx)
27 log.Debug().Str("url", fav.URL).Msg("saving favorite")
28
29 var folderID sql.NullInt64
30 if fav.FolderID != nil {
31 folderID = sql.NullInt64{Int64: int64(*fav.FolderID), Valid: true}
32 }
33
34 row, err := r.queries.CreateFavorite(ctx, sqlc.CreateFavoriteParams{
35 Url: fav.URL,
36 Title: sql.NullString{String: fav.Title, Valid: fav.Title != ""},
37 FaviconUrl: sql.NullString{String: fav.FaviconURL, Valid: fav.FaviconURL != ""},
38 FolderID: folderID,
39 })
40 if err != nil {
41 return err
42 }
43 fav.ID = entity.FavoriteID(row.ID)
44 return nil
45}
46
47func (r *favoriteRepo) FindByID(ctx context.Context, id entity.FavoriteID) (*entity.Favorite, error) {
48 row, err := r.queries.GetFavoriteByID(ctx, int64(id))
49 if err != nil {
50 if errors.Is(err, sql.ErrNoRows) {
51 return nil, nil
52 }
53 return nil, err
54 }
55 return favoriteFromRow(row), nil
56}
57
58func (r *favoriteRepo) FindByURL(ctx context.Context, url string) (*entity.Favorite, error) {
59 row, err := r.queries.GetFavoriteByURL(ctx, url)
60 if err != nil {
61 if errors.Is(err, sql.ErrNoRows) {
62 return nil, nil
63 }
64 return nil, err
65 }
66 return favoriteFromRow(row), nil
67}
68
69func (r *favoriteRepo) GetAll(ctx context.Context) ([]*entity.Favorite, error) {
70 rows, err := r.queries.GetAllFavorites(ctx)
71 if err != nil {
72 return nil, err
73 }
74 return favoritesFromRows(rows), nil
75}
76
77func (r *favoriteRepo) GetByFolder(ctx context.Context, folderID *entity.FolderID) ([]*entity.Favorite, error) {
78 var rows []sqlc.Favorite
79 var err error
80
81 if folderID == nil {
82 rows, err = r.queries.GetFavoritesWithoutFolder(ctx)
83 } else {
84 rows, err = r.queries.GetFavoritesByFolder(ctx, sql.NullInt64{Int64: int64(*folderID), Valid: true})
85 }
86 if err != nil {
87 return nil, err
88 }
89 return favoritesFromRows(rows), nil
90}
91
92func (r *favoriteRepo) GetByShortcut(ctx context.Context, key int) (*entity.Favorite, error) {
93 row, err := r.queries.GetFavoriteByShortcut(ctx, sql.NullInt64{Int64: int64(key), Valid: true})
94 if err != nil {
95 if errors.Is(err, sql.ErrNoRows) {
96 return nil, nil
97 }
98 return nil, err
99 }
100 return favoriteFromRow(row), nil
101}
102
103func (r *favoriteRepo) UpdatePosition(ctx context.Context, id entity.FavoriteID, position int) error {
104 return r.queries.UpdateFavoritePosition(ctx, sqlc.UpdateFavoritePositionParams{
105 Position: int64(position),
106 ID: int64(id),
107 })
108}
109
110func (r *favoriteRepo) SetFolder(ctx context.Context, id entity.FavoriteID, folderID *entity.FolderID) error {
111 var fid sql.NullInt64
112 if folderID != nil {
113 fid = sql.NullInt64{Int64: int64(*folderID), Valid: true}
114 }
115 return r.queries.SetFavoriteFolder(ctx, sqlc.SetFavoriteFolderParams{
116 FolderID: fid,
117 ID: int64(id),
118 })
119}
120
121func (r *favoriteRepo) SetShortcut(ctx context.Context, id entity.FavoriteID, key *int) error {
122 var k sql.NullInt64
123 if key != nil {
124 k = sql.NullInt64{Int64: int64(*key), Valid: true}
125 }
126 return r.queries.SetFavoriteShortcut(ctx, sqlc.SetFavoriteShortcutParams{
127 ShortcutKey: k,
128 ID: int64(id),
129 })
130}
131
132func (r *favoriteRepo) Delete(ctx context.Context, id entity.FavoriteID) error {
133 return r.queries.DeleteFavorite(ctx, int64(id))
134}
135
136func favoriteFromRow(row sqlc.Favorite) *entity.Favorite {
137 fav := &entity.Favorite{
138 ID: entity.FavoriteID(row.ID),
139 URL: row.Url,
140 Title: row.Title.String,
141 FaviconURL: row.FaviconUrl.String,
142 Position: int(row.Position),
143 CreatedAt: row.CreatedAt.Time,
144 UpdatedAt: row.UpdatedAt.Time,
145 }
146 if row.FolderID.Valid {
147 fid := entity.FolderID(row.FolderID.Int64)
148 fav.FolderID = &fid
149 }
150 if row.ShortcutKey.Valid {
151 key := int(row.ShortcutKey.Int64)
152 fav.ShortcutKey = &key
153 }
154 return fav
155}
156
157func favoritesFromRows(rows []sqlc.Favorite) []*entity.Favorite {
158 favorites := make([]*entity.Favorite, len(rows))
159 for i := range rows {
160 favorites[i] = favoriteFromRow(rows[i])
161 }
162 return favorites
163}
164
165// ==================== Folder Repository ====================
166
167type folderRepo struct {
168 queries *sqlc.Queries
169}
170
171// NewFolderRepository creates a new SQLite-backed folder repository.
172func NewFolderRepository(db *sql.DB) repository.FolderRepository {
173 return &folderRepo{queries: sqlc.New(db)}
174}
175
176func (r *folderRepo) Save(ctx context.Context, folder *entity.Folder) error {
177 log := logging.FromContext(ctx)
178 log.Debug().Str("name", folder.Name).Msg("saving folder")
179
180 var parentID sql.NullInt64
181 if folder.ParentID != nil {
182 parentID = sql.NullInt64{Int64: int64(*folder.ParentID), Valid: true}
183 }
184
185 row, err := r.queries.CreateFolder(ctx, sqlc.CreateFolderParams{
186 Name: folder.Name,
187 Icon: sql.NullString{String: folder.Icon, Valid: folder.Icon != ""},
188 ParentID: parentID,
189 })
190 if err != nil {
191 return err
192 }
193 folder.ID = entity.FolderID(row.ID)
194 return nil
195}
196
197func (r *folderRepo) FindByID(ctx context.Context, id entity.FolderID) (*entity.Folder, error) {
198 row, err := r.queries.GetFolderByID(ctx, int64(id))
199 if err != nil {
200 if errors.Is(err, sql.ErrNoRows) {
201 return nil, nil
202 }
203 return nil, err
204 }
205 return folderFromRow(row), nil
206}
207
208func (r *folderRepo) GetAll(ctx context.Context) ([]*entity.Folder, error) {
209 rows, err := r.queries.GetAllFolders(ctx)
210 if err != nil {
211 return nil, err
212 }
213 return foldersFromRows(rows), nil
214}
215
216func (r *folderRepo) GetChildren(ctx context.Context, parentID *entity.FolderID) ([]*entity.Folder, error) {
217 var rows []sqlc.FavoriteFolder
218 var err error
219
220 if parentID == nil {
221 rows, err = r.queries.GetRootFolders(ctx)
222 } else {
223 rows, err = r.queries.GetChildFolders(ctx, sql.NullInt64{Int64: int64(*parentID), Valid: true})
224 }
225 if err != nil {
226 return nil, err
227 }
228 return foldersFromRows(rows), nil
229}
230
231func (r *folderRepo) UpdatePosition(ctx context.Context, id entity.FolderID, position int) error {
232 return r.queries.UpdateFolderPosition(ctx, sqlc.UpdateFolderPositionParams{
233 Position: int64(position),
234 ID: int64(id),
235 })
236}
237
238func (r *folderRepo) Delete(ctx context.Context, id entity.FolderID) error {
239 return r.queries.DeleteFolder(ctx, int64(id))
240}
241
242func folderFromRow(row sqlc.FavoriteFolder) *entity.Folder {
243 folder := &entity.Folder{
244 ID: entity.FolderID(row.ID),
245 Name: row.Name,
246 Icon: row.Icon.String,
247 Position: int(row.Position),
248 CreatedAt: row.CreatedAt.Time,
249 }
250 if row.ParentID.Valid {
251 pid := entity.FolderID(row.ParentID.Int64)
252 folder.ParentID = &pid
253 }
254 return folder
255}
256
257func foldersFromRows(rows []sqlc.FavoriteFolder) []*entity.Folder {
258 folders := make([]*entity.Folder, len(rows))
259 for i, row := range rows {
260 folders[i] = folderFromRow(row)
261 }
262 return folders
263}
264
265// ==================== Tag Repository ====================
266
267type tagRepo struct {
268 queries *sqlc.Queries
269}
270
271// NewTagRepository creates a new SQLite-backed tag repository.
272func NewTagRepository(db *sql.DB) repository.TagRepository {
273 return &tagRepo{queries: sqlc.New(db)}
274}
275
276func (r *tagRepo) Save(ctx context.Context, tag *entity.Tag) error {
277 log := logging.FromContext(ctx)
278 log.Debug().Str("name", tag.Name).Msg("saving tag")
279
280 row, err := r.queries.CreateTag(ctx, sqlc.CreateTagParams{
281 Name: tag.Name,
282 Color: tag.Color,
283 })
284 if err != nil {
285 return err
286 }
287 tag.ID = entity.TagID(row.ID)
288 return nil
289}
290
291func (r *tagRepo) FindByID(ctx context.Context, id entity.TagID) (*entity.Tag, error) {
292 row, err := r.queries.GetTagByID(ctx, int64(id))
293 if err != nil {
294 if errors.Is(err, sql.ErrNoRows) {
295 return nil, nil
296 }
297 return nil, err
298 }
299 return tagFromRow(row), nil
300}
301
302func (r *tagRepo) FindByName(ctx context.Context, name string) (*entity.Tag, error) {
303 row, err := r.queries.GetTagByName(ctx, name)
304 if err != nil {
305 if errors.Is(err, sql.ErrNoRows) {
306 return nil, nil
307 }
308 return nil, err
309 }
310 return tagFromRow(row), nil
311}
312
313func (r *tagRepo) GetAll(ctx context.Context) ([]*entity.Tag, error) {
314 rows, err := r.queries.GetAllTags(ctx)
315 if err != nil {
316 return nil, err
317 }
318 return tagsFromRows(rows), nil
319}
320
321func (r *tagRepo) AssignToFavorite(ctx context.Context, tagID entity.TagID, favID entity.FavoriteID) error {
322 return r.queries.AssignTagToFavorite(ctx, sqlc.AssignTagToFavoriteParams{
323 FavoriteID: int64(favID),
324 TagID: int64(tagID),
325 })
326}
327
328func (r *tagRepo) RemoveFromFavorite(ctx context.Context, tagID entity.TagID, favID entity.FavoriteID) error {
329 return r.queries.RemoveTagFromFavorite(ctx, sqlc.RemoveTagFromFavoriteParams{
330 FavoriteID: int64(favID),
331 TagID: int64(tagID),
332 })
333}
334
335func (r *tagRepo) GetForFavorite(ctx context.Context, favID entity.FavoriteID) ([]*entity.Tag, error) {
336 rows, err := r.queries.GetTagsForFavorite(ctx, int64(favID))
337 if err != nil {
338 return nil, err
339 }
340 return tagsFromRows(rows), nil
341}
342
343func (r *tagRepo) Delete(ctx context.Context, id entity.TagID) error {
344 return r.queries.DeleteTag(ctx, int64(id))
345}
346
347func tagFromRow(row sqlc.FavoriteTag) *entity.Tag {
348 return &entity.Tag{
349 ID: entity.TagID(row.ID),
350 Name: row.Name,
351 Color: row.Color,
352 CreatedAt: row.CreatedAt.Time,
353 }
354}
355
356func tagsFromRows(rows []sqlc.FavoriteTag) []*entity.Tag {
357 tags := make([]*entity.Tag, len(rows))
358 for i, row := range rows {
359 tags[i] = tagFromRow(row)
360 }
361 return tags
362}