fix(favorites): persist shortcut updates
- commit
- 87325b338c34ab7490045acfabf29040a8b1bf6a
- author
- brice · · 2026-06-28 08:21 UTC
- parent
- 3991a6e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +58 -9
M internal/infrastructure/persistence/sqlite/favorite_repo.go +8 -3
| @@ -27,10 +27,15 @@ func (r *favoriteRepo) Save(ctx context.Context, fav *entity.Favorite) error { | |||
| 27 | 27 | log.Debug().Str("url", fav.URL).Int64("id", int64(fav.ID)).Msg("saving favorite") | |
| 28 | 28 | ||
| 29 | 29 | if fav.ID > 0 { | |
| 30 | + | shortcutKey := sql.NullInt64{} | |
| 31 | + | if fav.ShortcutKey != nil { | |
| 32 | + | shortcutKey = sql.NullInt64{Int64: int64(*fav.ShortcutKey), Valid: true} | |
| 33 | + | } | |
| 30 | 34 | return r.queries.UpdateFavorite(ctx, sqlc.UpdateFavoriteParams{ | |
| 31 | - | Title: sql.NullString{String: fav.Title, Valid: fav.Title != ""}, | |
| 32 | - | FaviconUrl: sql.NullString{String: fav.FaviconURL, Valid: fav.FaviconURL != ""}, | |
| 33 | - | ID: int64(fav.ID), | |
| 35 | + | Title: sql.NullString{String: fav.Title, Valid: fav.Title != ""}, | |
| 36 | + | FaviconUrl: sql.NullString{String: fav.FaviconURL, Valid: fav.FaviconURL != ""}, | |
| 37 | + | ShortcutKey: shortcutKey, | |
| 38 | + | ID: int64(fav.ID), | |
| 34 | 39 | }) | |
| 35 | 40 | } | |
| 36 | 41 | ||
| @@ -27,10 +27,15 @@ func (r *favoriteRepo) Save(ctx context.Context, fav *entity.Favorite) error { | |||||
| 27 | log.Debug().Str("url", fav.URL).Int64("id", int64(fav.ID)).Msg("saving favorite") | 27 | log.Debug().Str("url", fav.URL).Int64("id", int64(fav.ID)).Msg("saving favorite") | ||
| 28 | 28 | ||||
| 29 | if fav.ID > 0 { | 29 | if fav.ID > 0 { | ||
| 30 | + | shortcutKey := sql.NullInt64{} | |||
| 31 | + | if fav.ShortcutKey != nil { | |||
| 32 | + | shortcutKey = sql.NullInt64{Int64: int64(*fav.ShortcutKey), Valid: true} | |||
| 33 | + | } | |||
| 30 | return r.queries.UpdateFavorite(ctx, sqlc.UpdateFavoriteParams{ | 34 | return r.queries.UpdateFavorite(ctx, sqlc.UpdateFavoriteParams{ | ||
| 31 | - | Title: sql.NullString{String: fav.Title, Valid: fav.Title != ""}, | 35 | + | Title: sql.NullString{String: fav.Title, Valid: fav.Title != ""}, |
| 32 | - | FaviconUrl: sql.NullString{String: fav.FaviconURL, Valid: fav.FaviconURL != ""}, | 36 | + | FaviconUrl: sql.NullString{String: fav.FaviconURL, Valid: fav.FaviconURL != ""}, |
| 33 | - | ID: int64(fav.ID), | 37 | + | ShortcutKey: shortcutKey, |
| 38 | + | ID: int64(fav.ID), | |||
| 34 | }) | 39 | }) | ||
| 35 | } | 40 | } | ||
| 36 | 41 | ||||
M internal/infrastructure/persistence/sqlite/favorite_repo_test.go +38 -0
| @@ -116,6 +116,44 @@ func TestFavoriteRepositoryNoTagsHydratesEmptyTags(t *testing.T) { | |||
| 116 | 116 | } | |
| 117 | 117 | } | |
| 118 | 118 | ||
| 119 | + | func TestFavoriteRepositorySaveUpdatesShortcutKey(t *testing.T) { | |
| 120 | + | db := openFavoriteRepoTestDB(t) | |
| 121 | + | mustExec(t, db, `INSERT INTO favorites(id, url, title, position) VALUES (1, 'https://shortcut.example', 'Shortcut', 1)`) | |
| 122 | + | repo := NewFavoriteRepository(db) | |
| 123 | + | ||
| 124 | + | fav, err := repo.FindByID(context.Background(), 1) | |
| 125 | + | if err != nil { | |
| 126 | + | t.Fatalf("FindByID: %v", err) | |
| 127 | + | } | |
| 128 | + | key := 5 | |
| 129 | + | fav.ShortcutKey = &key | |
| 130 | + | err = repo.Save(context.Background(), fav) | |
| 131 | + | if err != nil { | |
| 132 | + | t.Fatalf("Save: %v", err) | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | updated, err := repo.GetByShortcut(context.Background(), 5) | |
| 136 | + | if err != nil { | |
| 137 | + | t.Fatalf("GetByShortcut: %v", err) | |
| 138 | + | } | |
| 139 | + | if updated == nil || updated.ID != fav.ID { | |
| 140 | + | t.Fatalf("expected favorite %d by shortcut, got %#v", fav.ID, updated) | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | updated.ShortcutKey = nil | |
| 144 | + | err = repo.Save(context.Background(), updated) | |
| 145 | + | if err != nil { | |
| 146 | + | t.Fatalf("Save clear shortcut: %v", err) | |
| 147 | + | } | |
| 148 | + | cleared, err := repo.FindByID(context.Background(), updated.ID) | |
| 149 | + | if err != nil { | |
| 150 | + | t.Fatalf("FindByID after clear: %v", err) | |
| 151 | + | } | |
| 152 | + | if cleared.ShortcutKey != nil { | |
| 153 | + | t.Fatalf("expected shortcut cleared, got %d", *cleared.ShortcutKey) | |
| 154 | + | } | |
| 155 | + | } | |
| 156 | + | ||
| 119 | 157 | func assertTagNames(t *testing.T, fav *entity.Favorite, names ...string) { | |
| 120 | 158 | t.Helper() | |
| 121 | 159 | if fav == nil { | |
| @@ -116,6 +116,44 @@ func TestFavoriteRepositoryNoTagsHydratesEmptyTags(t *testing.T) { | |||||
| 116 | } | 116 | } | ||
| 117 | } | 117 | } | ||
| 118 | 118 | ||||
| 119 | + | func TestFavoriteRepositorySaveUpdatesShortcutKey(t *testing.T) { | |||
| 120 | + | db := openFavoriteRepoTestDB(t) | |||
| 121 | + | mustExec(t, db, `INSERT INTO favorites(id, url, title, position) VALUES (1, 'https://shortcut.example', 'Shortcut', 1)`) | |||
| 122 | + | repo := NewFavoriteRepository(db) | |||
| 123 | + | ||||
| 124 | + | fav, err := repo.FindByID(context.Background(), 1) | |||
| 125 | + | if err != nil { | |||
| 126 | + | t.Fatalf("FindByID: %v", err) | |||
| 127 | + | } | |||
| 128 | + | key := 5 | |||
| 129 | + | fav.ShortcutKey = &key | |||
| 130 | + | err = repo.Save(context.Background(), fav) | |||
| 131 | + | if err != nil { | |||
| 132 | + | t.Fatalf("Save: %v", err) | |||
| 133 | + | } | |||
| 134 | + | ||||
| 135 | + | updated, err := repo.GetByShortcut(context.Background(), 5) | |||
| 136 | + | if err != nil { | |||
| 137 | + | t.Fatalf("GetByShortcut: %v", err) | |||
| 138 | + | } | |||
| 139 | + | if updated == nil || updated.ID != fav.ID { | |||
| 140 | + | t.Fatalf("expected favorite %d by shortcut, got %#v", fav.ID, updated) | |||
| 141 | + | } | |||
| 142 | + | ||||
| 143 | + | updated.ShortcutKey = nil | |||
| 144 | + | err = repo.Save(context.Background(), updated) | |||
| 145 | + | if err != nil { | |||
| 146 | + | t.Fatalf("Save clear shortcut: %v", err) | |||
| 147 | + | } | |||
| 148 | + | cleared, err := repo.FindByID(context.Background(), updated.ID) | |||
| 149 | + | if err != nil { | |||
| 150 | + | t.Fatalf("FindByID after clear: %v", err) | |||
| 151 | + | } | |||
| 152 | + | if cleared.ShortcutKey != nil { | |||
| 153 | + | t.Fatalf("expected shortcut cleared, got %d", *cleared.ShortcutKey) | |||
| 154 | + | } | |||
| 155 | + | } | |||
| 156 | + | ||||
| 119 | func assertTagNames(t *testing.T, fav *entity.Favorite, names ...string) { | 157 | func assertTagNames(t *testing.T, fav *entity.Favorite, names ...string) { | ||
| 120 | t.Helper() | 158 | t.Helper() | ||
| 121 | if fav == nil { | 159 | if fav == nil { | ||
M internal/infrastructure/persistence/sqlite/queries/favorites.sql +1 -1
| @@ -32,7 +32,7 @@ RETURNING id, url, title, favicon_url, shortcut_key, position, created_at, updat | |||
| 32 | 32 | ||
| 33 | 33 | -- name: UpdateFavorite :exec | |
| 34 | 34 | UPDATE favorites | |
| 35 | - | SET title = ?, favicon_url = ?, updated_at = CURRENT_TIMESTAMP | |
| 35 | + | SET title = ?, favicon_url = ?, shortcut_key = ?, updated_at = CURRENT_TIMESTAMP | |
| 36 | 36 | WHERE id = ?; | |
| 37 | 37 | ||
| 38 | 38 | -- name: UpdateFavoritePosition :exec | |
| @@ -32,7 +32,7 @@ RETURNING id, url, title, favicon_url, shortcut_key, position, created_at, updat | |||||
| 32 | 32 | ||||
| 33 | -- name: UpdateFavorite :exec | 33 | -- name: UpdateFavorite :exec | ||
| 34 | UPDATE favorites | 34 | UPDATE favorites | ||
| 35 | - | SET title = ?, favicon_url = ?, updated_at = CURRENT_TIMESTAMP | 35 | + | SET title = ?, favicon_url = ?, shortcut_key = ?, updated_at = CURRENT_TIMESTAMP |
| 36 | WHERE id = ?; | 36 | WHERE id = ?; | ||
| 37 | 37 | ||||
| 38 | -- name: UpdateFavoritePosition :exec | 38 | -- name: UpdateFavoritePosition :exec | ||
M internal/infrastructure/persistence/sqlite/sqlc/favorites.sql.go +11 -5
| @@ -273,18 +273,24 @@ func (q *Queries) SetFavoriteShortcut(ctx context.Context, arg SetFavoriteShortc | |||
| 273 | 273 | ||
| 274 | 274 | const UpdateFavorite = `-- name: UpdateFavorite :exec | |
| 275 | 275 | UPDATE favorites | |
| 276 | - | SET title = ?, favicon_url = ?, updated_at = CURRENT_TIMESTAMP | |
| 276 | + | SET title = ?, favicon_url = ?, shortcut_key = ?, updated_at = CURRENT_TIMESTAMP | |
| 277 | 277 | WHERE id = ? | |
| 278 | 278 | ` | |
| 279 | 279 | ||
| 280 | 280 | type UpdateFavoriteParams struct { | |
| 281 | - | Title sql.NullString `json:"title"` | |
| 282 | - | FaviconUrl sql.NullString `json:"favicon_url"` | |
| 283 | - | ID int64 `json:"id"` | |
| 281 | + | Title sql.NullString `json:"title"` | |
| 282 | + | FaviconUrl sql.NullString `json:"favicon_url"` | |
| 283 | + | ShortcutKey sql.NullInt64 `json:"shortcut_key"` | |
| 284 | + | ID int64 `json:"id"` | |
| 284 | 285 | } | |
| 285 | 286 | ||
| 286 | 287 | func (q *Queries) UpdateFavorite(ctx context.Context, arg UpdateFavoriteParams) error { | |
| 287 | - | _, err := q.db.ExecContext(ctx, UpdateFavorite, arg.Title, arg.FaviconUrl, arg.ID) | |
| 288 | + | _, err := q.db.ExecContext(ctx, UpdateFavorite, | |
| 289 | + | arg.Title, | |
| 290 | + | arg.FaviconUrl, | |
| 291 | + | arg.ShortcutKey, | |
| 292 | + | arg.ID, | |
| 293 | + | ) | |
| 288 | 294 | return err | |
| 289 | 295 | } | |
| 290 | 296 | ||
| @@ -273,18 +273,24 @@ func (q *Queries) SetFavoriteShortcut(ctx context.Context, arg SetFavoriteShortc | |||||
| 273 | 273 | ||||
| 274 | const UpdateFavorite = `-- name: UpdateFavorite :exec | 274 | const UpdateFavorite = `-- name: UpdateFavorite :exec | ||
| 275 | UPDATE favorites | 275 | UPDATE favorites | ||
| 276 | - | SET title = ?, favicon_url = ?, updated_at = CURRENT_TIMESTAMP | 276 | + | SET title = ?, favicon_url = ?, shortcut_key = ?, updated_at = CURRENT_TIMESTAMP |
| 277 | WHERE id = ? | 277 | WHERE id = ? | ||
| 278 | ` | 278 | ` | ||
| 279 | 279 | ||||
| 280 | type UpdateFavoriteParams struct { | 280 | type UpdateFavoriteParams struct { | ||
| 281 | - | Title sql.NullString `json:"title"` | 281 | + | Title sql.NullString `json:"title"` |
| 282 | - | FaviconUrl sql.NullString `json:"favicon_url"` | 282 | + | FaviconUrl sql.NullString `json:"favicon_url"` |
| 283 | - | ID int64 `json:"id"` | 283 | + | ShortcutKey sql.NullInt64 `json:"shortcut_key"` |
| 284 | + | ID int64 `json:"id"` | |||
| 284 | } | 285 | } | ||
| 285 | 286 | ||||
| 286 | func (q *Queries) UpdateFavorite(ctx context.Context, arg UpdateFavoriteParams) error { | 287 | func (q *Queries) UpdateFavorite(ctx context.Context, arg UpdateFavoriteParams) error { | ||
| 287 | - | _, err := q.db.ExecContext(ctx, UpdateFavorite, arg.Title, arg.FaviconUrl, arg.ID) | 288 | + | _, err := q.db.ExecContext(ctx, UpdateFavorite, |
| 289 | + | arg.Title, | |||
| 290 | + | arg.FaviconUrl, | |||
| 291 | + | arg.ShortcutKey, | |||
| 292 | + | arg.ID, | |||
| 293 | + | ) | |||
| 288 | return err | 294 | return err | ||
| 289 | } | 295 | } | ||
| 290 | 296 | ||||