Skip to content

fix: store ai_insights with users.id and add FK + cascade delete#1765

Open
Ridanshi wants to merge 1 commit into
Priyanshu-byte-coder:mainfrom
Ridanshi:fix/ai-insights-user-id-ownership
Open

fix: store ai_insights with users.id and add FK + cascade delete#1765
Ridanshi wants to merge 1 commit into
Priyanshu-byte-coder:mainfrom
Ridanshi:fix/ai-insights-user-id-ownership

Conversation

@Ridanshi
Copy link
Copy Markdown
Contributor

Closes #1750

Problem

GET /api/ai-insights stored insight rows using session.githubId as user_id:

const userId = session.githubId;  // e.g. "12345678" — GitHub numeric ID
// ...
await supabaseAdmin.from("ai_insights").upsert({ user_id: userId, ... });

session.githubId is the GitHub numeric account ID, not the application UUID (users.id). The ai_insights table had no foreign-key constraint pointing at users, so:

  1. Orphaned records — when a user deleted their account, all their ai_insights rows remained forever. The account-deletion handler (data-export/route.ts) did not include ai_insights in its explicit deletion list, and there was no ON DELETE CASCADE to fill in.

  2. No referential integrity — inserts could reference any arbitrary string as user_id with no guarantee that a matching user existed.

  3. Broken cascade — even if the cascade were to fire in the future, it would compare against users.id (UUID), not users.github_id (numeric string), so it would never match existing rows.

Root cause (three locations)

Location Problem
src/app/api/ai-insights/route.ts line 51 const userId = session.githubId — uses wrong identity
src/app/api/user/data-export/route.tstablesToDelete array ai_insights not listed; rows survive account deletion
supabase/schema.sqlai_insights table No FK, no cascade

Fix

src/app/api/ai-insights/route.ts

  • Call resolveAppUser(session.githubId, session.githubLogin) to obtain user.id (the stable UUID) and use it as user_id throughout.
  • Return 404 if the user cannot be resolved.
  • Add insight_type validation against the allowlist ('weekly_summary' | 'pattern' | 'recommendation') that mirrors the DB CHECK constraint. Unrecognised types now return 400 instead of a Supabase constraint-violation 500.

src/app/api/user/data-export/route.ts

  • Prepend "ai_insights" to tablesToDelete so explicit account deletion always removes insight rows — even before the FK migration is applied.

supabase/migrations/20260531000000_fix_ai_insights_user_id.sql

Three steps inside a transaction:

  1. Remap existing ai_insights.user_id values from github_id strings to the corresponding users.id UUIDs (via JOIN users ON users.github_id = ai_insights.user_id).
  2. Delete any rows that cannot be matched — these are already orphaned records belonging to deleted accounts.
  3. Add FK FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, enforcing integrity going forward and enabling automatic cleanup on account deletion.

Identity chain (after fix)

GitHub OAuth sign-in  →  profile.id  →  users.github_id  (immutable)
                                     →  users.id (UUID)  ← ai_insights.user_id now points here

Tests

test/ai-insights-ownership.test.ts — 13 new tests:

Category Cases
Auth guards no session → 401, unresolved user → 404
Ownership resolveAppUser called with session credentials; upsert uses UUID; cache query uses UUID
Regression #1750 no DB operation receives the raw githubId as user_id
Cache hit returns { cached: true, data }
insight_type validation invalid → 400; weekly_summary, pattern, recommendation → 200
Conflict target onConflict: "user_id,insight_type" present
Cache miss shape { data, cached: false }

All 13 pass. The single pre-existing failure in test/dateUtils.test.ts (timezone boundary) is unrelated to this change.

Root cause
----------
src/app/api/ai-insights/route.ts set user_id = session.githubId, which
is the GitHub numeric account ID (e.g. "12345678"). The ai_insights table
had no foreign-key constraint, so:

  1. Rows referenced a non-existent concept of "user" — github_id is not
     the primary key of the users table and carries no referential weight.
  2. ON DELETE CASCADE could not fire because no FK existed, so deleting a
     user account left all their ai_insights rows permanently orphaned.
  3. The account deletion handler in data-export/route.ts did not include
     ai_insights in its explicit deletion list, compounding the problem.

Changes
-------

src/app/api/ai-insights/route.ts
- Import and call resolveAppUser(session.githubId, session.githubLogin) to
  obtain the application UUID (users.id) before touching the database.
- Use user.id (UUID) for all DB reads and writes instead of session.githubId.
- Return 404 if resolveAppUser returns null (unregistered session).
- Add insight_type validation against the DB CHECK constraint allowlist
  ('weekly_summary' | 'pattern' | 'recommendation') so unrecognised types
  receive a clear 400 instead of a Supabase constraint-violation 500.

src/app/api/user/data-export/route.ts
- Add "ai_insights" to the tablesToDelete array in the DELETE handler.
  The ON DELETE CASCADE on the new FK would handle removal automatically,
  but the explicit delete is a defense-in-depth safeguard that works even
  if the migration has not yet been applied.

supabase/migrations/20260531000000_fix_ai_insights_user_id.sql
- Remap existing ai_insights.user_id values from github_id strings to the
  corresponding users.id UUIDs via a JOIN on users.github_id.
- Delete any rows that cannot be matched (accounts already deleted — these
  are the orphaned records the issue describes).
- Add FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE to
  enforce referential integrity going forward.

test/ai-insights-ownership.test.ts — 13 new tests:
- Auth guards (no session, unresolved user)
- resolveAppUser is called with the session github credentials
- Upsert and cache-read both use the UUID, not the GitHub numeric ID
- Regression for Priyanshu-byte-coder#1750: no DB operation receives githubId as user_id
- Cache-hit returns content with cached: true
- insight_type validation: invalid type → 400, valid types → 200
- Upsert conflict target is "user_id,insight_type"
- Cache-miss response shape is { data, cached: false }

Closes Priyanshu-byte-coder#1750
@vercel
Copy link
Copy Markdown

vercel Bot commented May 31, 2026

@Ridanshi is attempting to deploy a commit to the PRIYANSHU DOSHI's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added gssoc26 GSSoC 2026 contribution type:bug GSSoC type bonus: bug fix type:feature GSSoC type bonus: new feature type:testing GSSoC type bonus: tests (+10 pts) labels May 31, 2026
@github-actions
Copy link
Copy Markdown

GSSoC Label Checklist 🏷️

@Priyanshu-byte-coder — please apply the appropriate labels before merging:

Difficulty (pick one):

  • level:beginner — 20 pts
  • level:intermediate — 35 pts
  • level:advanced — 55 pts
  • level:critical — 80 pts

Quality (optional):

  • quality:clean — ×1.2 multiplier
  • quality:exceptional — ×1.5 multiplier

Validation (required to score):

  • gssoc:approved — counts for points
  • gssoc:invalid / gssoc:spam / gssoc:ai-slop — does not score

Type labels (type:*) are auto-detected from files and title. Review and adjust if needed.
Points formula: (difficulty × quality_multiplier) + type_bonus

@Priyanshu-byte-coder Priyanshu-byte-coder added level2 GSSoC Level 2 - Medium complexity (25 points) gssoc:approved GSSoC: PR approved for scoring labels May 31, 2026
@Priyanshu-byte-coder
Copy link
Copy Markdown
Owner

This PR has developed merge conflicts after recent merges to main. Please rebase onto the latest main branch, resolve all conflicts, and force-push. Once the PR is conflict-free it will be reviewed for merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved GSSoC: PR approved for scoring gssoc26 GSSoC 2026 contribution level2 GSSoC Level 2 - Medium complexity (25 points) type:bug GSSoC type bonus: bug fix type:feature GSSoC type bonus: new feature type:testing GSSoC type bonus: tests (+10 pts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] AI insights records become orphaned because user references are stored using GitHub IDs instead of application user IDs

2 participants