v0.27.2 5960dd8

favorite_repo.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close