ReviewOS

stacks/wildloop

Add unique constraints to prevent duplicate kudos and saved trails

#972
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Problem

kudos has no unique index on (giver_id, activity_id) and saved_trails has none on (user_id, trail_id), so a user can kudos/save the same thing repeatedly.

Acceptance

  • Migrations adding the composite unique indexes.
  • Store actions handle the conflict gracefully (idempotent toggle).
9 days ago

Done in bde68a4.

Composite unique indexes (declared via the model indexes key, generated into migrations):

  • kudos (giver_id, activity_id) and saved_trails (user_id, trail_id) per the issue, plus the same-shaped gaps: follows (follower_id, following_id), user_achievements (user_id, achievement_id), trail_reviews (user_id, trail_id), and territory_stats (user_id) (a dup stats row would corrupt leaderboards).

Framework fix required to make any of this work: the vendored SQLite migration preprocessor blanket-skipped every migration consisting only of CREATE UNIQUE INDEX (assumed 'already inline on table') — so these files were generated and then silently never executed (even users.email unique wasn't real). It now skips only single-column indexes whose column genuinely has an inline UNIQUE in its create-table SQL; everything else executes. Skip-and-keep behavior for true redundancies is preserved for MySQL/Postgres driver switches.

Graceful conflicts: KudosToggle/FollowToggle treat a unique-constraint violation (concurrent double-tap racing the existence check) as 'already exists' — toggle stays idempotent, no 500. TrailReviewStore catches the race and updates the winner's row.

Verified by scripts/verify-counters.ts: all six indexes present in sqlite_master, raw duplicate INSERT rejected with SQLITE_CONSTRAINT_UNIQUE, kudos/follow toggles on→off with no dup rows. All 7 suites green.

Sign in to comment on this issue.