Skip to content

Commit 316c34d

Browse files
explodedclaude
andcommitted
Fix favourite ratings rejected by stale CHECK constraint
CREATE TABLE IF NOT EXISTS skips existing tables, so adding 'favourite' to the schema had no effect on existing databases. Add a startup migration that recreates the ratings table with the correct constraint. Also sort GetLikedShows favourites-first so the profile top-rated section prioritises them. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dc4e3fa commit 316c34d

3 files changed

Lines changed: 37 additions & 2 deletions

File tree

cmd/wtw/main.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"os"
1010
"os/signal"
11+
"strings"
1112
"sync"
1213
"syscall"
1314
"time"
@@ -130,6 +131,12 @@ func main() {
130131
os.Exit(1)
131132
}
132133

134+
// Migrate ratings CHECK constraint to include 'favourite'
135+
if err := migrateRatingsConstraint(database); err != nil {
136+
slog.Error("failed to migrate ratings constraint", "error", err)
137+
os.Exit(1)
138+
}
139+
133140
// Seed shows
134141
if err := db.SeedAllShows(database); err != nil {
135142
slog.Error("failed to seed shows", "error", err)
@@ -233,6 +240,34 @@ func main() {
233240
slog.Info("server exited")
234241
}
235242

243+
// migrateRatingsConstraint recreates the ratings table if the CHECK constraint
244+
// does not include 'favourite'. Needed because CREATE TABLE IF NOT EXISTS skips
245+
// existing tables, so the schema update alone cannot fix old databases.
246+
func migrateRatingsConstraint(db *sql.DB) error {
247+
var tableSql string
248+
err := db.QueryRow("SELECT sql FROM sqlite_master WHERE type='table' AND name='ratings'").Scan(&tableSql)
249+
if err != nil {
250+
return nil // table doesn't exist yet, schema will create it
251+
}
252+
if strings.Contains(tableSql, "'favourite'") {
253+
return nil // already migrated
254+
}
255+
_, err = db.Exec(`
256+
CREATE TABLE ratings_new (
257+
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
258+
show_id TEXT NOT NULL REFERENCES shows(id),
259+
rating TEXT NOT NULL CHECK (rating IN ('favourite','liked','disliked','unseen')),
260+
rated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
261+
PRIMARY KEY (user_id, show_id)
262+
);
263+
INSERT INTO ratings_new SELECT * FROM ratings;
264+
DROP TABLE ratings;
265+
ALTER TABLE ratings_new RENAME TO ratings;
266+
CREATE INDEX IF NOT EXISTS idx_ratings_user ON ratings(user_id);
267+
`)
268+
return err
269+
}
270+
236271
func cacheStaticAssets(next http.Handler) http.Handler {
237272
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
238273
w.Header().Set("Cache-Control", "public, max-age=604800, immutable")

internal/db/queries.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ ORDER BY r.rated_at DESC;
7979
-- name: GetLikedShows :many
8080
SELECT s.* FROM shows s
8181
JOIN ratings r ON r.show_id = s.id AND r.user_id = ? AND r.rating IN ('liked', 'favourite')
82-
ORDER BY r.rated_at DESC;
82+
ORDER BY CASE WHEN r.rating = 'favourite' THEN 0 ELSE 1 END, r.rated_at DESC;
8383

8484
-- name: GetDislikedShows :many
8585
SELECT s.* FROM shows s

internal/db/queries.sql.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)