Skip to content

Add Supabase account provisioning#11

Merged
gentamura merged 7 commits intomainfrom
feature/add-account-provisioning
Oct 20, 2025
Merged

Add Supabase account provisioning#11
gentamura merged 7 commits intomainfrom
feature/add-account-provisioning

Conversation

@gentamura
Copy link
Member

@gentamura gentamura commented Oct 20, 2025

Summary by CodeRabbit

  • New Features

    • Automatic account provisioning on sign-in: creates user profiles and a default "Inbox" system category.
    • Supabase provisioning-enabled authentication option that performs provisioning during sign-in.
    • Public account provisioning API and exports for use by other modules.
    • Default category name/kind ("Inbox", "system") now exposed.
  • Database

    • Prevent duplicate system categories via a new uniqueness constraint.
  • Tests

    • Added tests for provisioning flow, email capture, and token validation.

@coderabbitai
Copy link

coderabbitai bot commented Oct 20, 2025

Warning

Rate limit exceeded

@gentamura has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 10 minutes and 16 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 832e500 and 386374e.

📒 Files selected for processing (1)
  • packages/db/src/schema/index.ts (2 hunks)

Walkthrough

Adds transactional account provisioning (profile + default category), integrates provisioning into Supabase authentication, exposes DB category defaults, and makes DB schemaPath resolution file-URL-aware.

Changes

Cohort / File(s) Change Summary
Auth: package config
packages/auth/package.json
Added workspace dependency @listee/db.
Auth: account provisioning
packages/auth/src/account/provision-account.ts
New AccountProvisioner API and factory (createAccountProvisioner); transactional provisioning that upserts a profile and conditionally creates a default category; DI for database and RLS client; email resolution logic; public types exported.
Auth: authentication exports
packages/auth/src/authentication/index.ts
Re-exported createProvisioningSupabaseAuthentication alongside createSupabaseAuthentication.
Auth: provisioning integration & tests
packages/auth/src/authentication/supabase.ts, packages/auth/src/authentication/supabase.test.ts
Added createProvisioningSupabaseAuthentication which wraps base Supabase auth to call AccountProvisioner when token is a Supabase token (extracts email); tests added for provisioning behavior and token/email cases.
Auth: public exports
packages/auth/src/index.ts
Exported types AccountProvisioner, AccountProvisionerDependencies, ProvisionAccountParams and createAccountProvisioner; added createProvisioningSupabaseAuthentication to public exports.
DB: category constants
packages/db/src/constants/category.ts
Added exported constants DEFAULT_CATEGORY_NAME = "Inbox" and DEFAULT_CATEGORY_KIND = "system".
DB: index & schema path
packages/db/src/index.ts
Exported ./constants/category.js; replaced schemaModuleUrl.pathname with resolveSchemaPath() that uses fileURLToPath when appropriate and falls back to "./schema/index.js"; imported fileURLToPath from node:url.
DB: schema/table constraint
packages/db/src/schema/index.ts
Added uniqueIndex import and introduced a partial unique index categories_system_name_idx on (createdBy, name) where kind = "system" in the categories table.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ProvAuth as Provisioning\nSupabaseAuth
    participant BaseAuth as Base\nSupabaseAuth
    participant Provisioner as Account\nProvisioner
    participant RLS as RLS\nClient
    participant DB as Database

    Client->>ProvAuth: authenticate(credentials)
    ProvAuth->>BaseAuth: delegate authenticate()
    BaseAuth-->>ProvAuth: authResult (token, userId, maybe email)

    alt token is Supabase
        ProvAuth->>Provisioner: provision({ userId, token, email? })
        Provisioner->>RLS: createRlsClient(token)
        RLS->>DB: begin transaction
        Provisioner->>DB: upsert profile (id=userId, email)
        Provisioner->>DB: conditional insert default category (createdBy=userId, kind=system)
        DB-->>RLS: commit
        Provisioner-->>ProvAuth: provision complete
    else not Supabase token
        ProvAuth-->>Client: authResult (no provisioning)
    end

    ProvAuth-->>Client: return authResult
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop to the DB with a token so bright,
I plant an Inbox seed by soft moonlight,
Profiles take root within one tidy run,
A default category sprouts — voilà, it's done,
Hooray — new users arrive safe and right! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Add Supabase account provisioning" directly and accurately reflects the main objective of the pull request. The changeset introduces a new AccountProvisioner feature to provision user accounts, a new createProvisioningSupabaseAuthentication function that integrates this provisioning with Supabase authentication, and supporting infrastructure (database constants and schema updates). The title is concise, clear, and specific enough that a teammate reviewing the repository history would immediately understand that this PR adds account provisioning capabilities to the Supabase authentication system.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (10)
packages/db/src/constants/category.ts (1)

1-2: Consider a single object “defaultCategory” export for cohesion.

Optional: export a frozen object (name/kind) to keep the pair from drifting and simplify wiring.

+export const DEFAULT_CATEGORY = Object.freeze({
+  name: "Inbox",
+  kind: "system",
+} as const);
-
-export const DEFAULT_CATEGORY_NAME = "Inbox";
-export const DEFAULT_CATEGORY_KIND = "system";
packages/db/src/index.ts (1)

221-222: Nit: pass the URL object to fileURLToPath.

Slightly more idiomatic and avoids subtle encoding surprises.

-const schemaUrl = new URL("./schema/index.js", import.meta.url);
-export const schemaPath = fileURLToPath(schemaUrl.href);
+const schemaUrl = new URL("./schema/index.js", import.meta.url);
+export const schemaPath = fileURLToPath(schemaUrl);
packages/auth/src/authentication/supabase.ts (4)

75-76: Add JWKS fetch resilience knobs (timeout/cache/custom fetch).

To avoid request‑thread stalls on JWKS fetches and to control caching, expose and pass RemoteJWKSet options (e.g., timeoutDuration, cacheMaxAge, cooldown).

Example:

-  const remoteJwkSet = createRemoteJWKSet(jwksUrl);
+  const remoteJwkSet = createRemoteJWKSet(jwksUrl, {
+    // consider wiring these via options
+    // timeoutDuration: 5000,
+    // cacheMaxAge: 60_000,
+    // cooldownDuration: 30_000,
+    // fetch: options.fetch ?? globalThis.fetch,
+  });

Please confirm jose@v6.x support for these fields in your environment; wire them through SupabaseAuthenticationOptions if you agree.


122-132: Email extraction is fine; consider provider variations.

Optional: also look for provider‑specific locations (e.g., user_metadata.email) if you expect third‑party identities without top‑level email.


134-171: Define and test provisioning failure policy (fail‑open vs fail‑closed).

Right now, any provisioning error will reject authenticate(), coupling availability to DB. Clarify intent and encode it.

Possible pattern:

 interface ProvisioningDependencies extends AccountProvisionerDependencies {
   readonly accountProvisioner?: AccountProvisioner;
   readonly authenticationProvider?: AuthenticationProvider;
+  readonly failOpenOnProvisioningError?: boolean; // default true
 }

 async function authenticate(...) {
   const result = await baseProvider.authenticate(context);
   if (!isSupabaseToken(result.user.token)) return result;
-  const email = extractEmailFromToken(result.user.token);
-  await accountProvisioner.provision({ userId: result.user.id, token: result.user.token, email });
+  const email = extractEmailFromToken(result.user.token);
+  if (dependencies.failOpenOnProvisioningError !== false) {
+    try {
+      await accountProvisioner.provision({ userId: result.user.id, token: result.user.token, email });
+    } catch {
+      // optional: log
+    }
+  } else {
+    await accountProvisioner.provision({ userId: result.user.id, token: result.user.token, email });
+  }
   return result;
 }

Also, ensure the underlying category creation is idempotent under concurrency (use a unique index and onConflictDoNothing on categories by (createdBy, kind)). Based on learnings.


173-179: Type guard is minimal; optionally tighten.

If you need stricter detection, check for an issuer ending in “/auth/v1” or presence of a role claim before provisioning. Current check is acceptable.

packages/auth/src/account/provision-account.ts (4)

1-10: Fix Biome “Organize Imports”: sort specifiers.

Apply this diff (keeps sources the same, sorts specifiers):

-import type { Database, RlsClient, RlsTransaction } from "@listee/db";
-import {
-  DEFAULT_CATEGORY_KIND,
-  DEFAULT_CATEGORY_NAME,
-  and,
-  categories,
-  createRlsClient,
-  eq,
-  profiles,
-} from "@listee/db";
+import type { Database, RlsClient, RlsTransaction } from "@listee/db";
+import {
+  and,
+  categories,
+  createRlsClient,
+  DEFAULT_CATEGORY_KIND,
+  DEFAULT_CATEGORY_NAME,
+  eq,
+  profiles,
+} from "@listee/db";

As per coding guidelines.


34-38: Normalize email casing.

Lowercase after trim to avoid case-sensitive duplicates with a unique email constraint.

Apply:

-    const trimmed = email.trim();
+    const trimmed = email.trim().toLowerCase();

72-80: Optional: make the profiles insert conflict target explicit.

Not required, but clearer and slightly safer.

Apply:

       await tx
         .insert(profiles)
         .values({
           id: params.userId,
           email,
         })
-        .onConflictDoNothing();
+        .onConflictDoNothing({ target: profiles.id });

96-101: Optional: set profiles.defaultCategoryId when creating the default category.

If product semantics expect a default category selected, update the profile after insertion. Requires capturing the inserted id; skip if existing already found.

I can draft this if you confirm the desired behavior.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc3fb47 and 892e02f.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • packages/auth/package.json (1 hunks)
  • packages/auth/src/account/provision-account.ts (1 hunks)
  • packages/auth/src/authentication/index.ts (1 hunks)
  • packages/auth/src/authentication/supabase.test.ts (2 hunks)
  • packages/auth/src/authentication/supabase.ts (2 hunks)
  • packages/auth/src/index.ts (1 hunks)
  • packages/db/src/constants/category.ts (1 hunks)
  • packages/db/src/index.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
packages/*/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

packages/*/src/**/*.{ts,tsx}: Avoid implicit any; TypeScript is run with strict enabled
Prefer type guards or the satisfies operator over as casts where appropriate
Prefer unknown for external inputs
Use PascalCase for types and enums
Use camelCase for variables and functions
Ensure source comments are written in English

Files:

  • packages/db/src/constants/category.ts
  • packages/auth/src/authentication/index.ts
  • packages/auth/src/index.ts
  • packages/auth/src/authentication/supabase.test.ts
  • packages/auth/src/account/provision-account.ts
  • packages/db/src/index.ts
  • packages/auth/src/authentication/supabase.ts
packages/*/package.json

📄 CodeRabbit inference engine (AGENTS.md)

Reference shared dependencies via the "catalog:" protocol in package manifests

Files:

  • packages/auth/package.json
packages/auth/src/authentication/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Place runtime-specific authentication adapters under packages/auth/src/authentication/ (e.g., header.ts, supabase.ts, shared utilities, errors.ts)

Files:

  • packages/auth/src/authentication/index.ts
  • packages/auth/src/authentication/supabase.test.ts
  • packages/auth/src/authentication/supabase.ts
packages/auth/src/authentication/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Extend Supabase JWT verification (createSupabaseAuthentication using .well-known/jwks.json) only via dedicated modules to keep caching and claim validation centralized

Files:

  • packages/auth/src/authentication/index.ts
  • packages/auth/src/authentication/supabase.test.ts
  • packages/auth/src/authentication/supabase.ts
packages/*/src/**/{*.test.ts,__tests__/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

packages/*/src/**/{*.test.ts,__tests__/**/*.ts}: Write test names and descriptions in English
Co-locate tests as *.test.ts or under tests/ using Bun’s test runner

Files:

  • packages/auth/src/authentication/supabase.test.ts
🧠 Learnings (2)
📚 Learning: 2025-10-02T12:40:33.718Z
Learnt from: CR
PR: listee-dev/listee-libs#0
File: AGENTS.md:0-0
Timestamp: 2025-10-02T12:40:33.718Z
Learning: Applies to packages/auth/src/authentication/**/*.ts : Extend Supabase JWT verification (createSupabaseAuthentication using .well-known/jwks.json) only via dedicated modules to keep caching and claim validation centralized

Applied to files:

  • packages/auth/src/authentication/index.ts
  • packages/auth/src/authentication/supabase.test.ts
  • packages/auth/src/authentication/supabase.ts
📚 Learning: 2025-10-02T12:40:33.718Z
Learnt from: CR
PR: listee-dev/listee-libs#0
File: AGENTS.md:0-0
Timestamp: 2025-10-02T12:40:33.718Z
Learning: Applies to packages/auth/src/authentication/**/*.{ts,tsx} : Place runtime-specific authentication adapters under packages/auth/src/authentication/ (e.g., header.ts, supabase.ts, shared utilities, errors.ts)

Applied to files:

  • packages/auth/src/authentication/index.ts
🧬 Code graph analysis (3)
packages/auth/src/authentication/supabase.test.ts (3)
packages/db/src/index.ts (1)
  • SupabaseToken (153-162)
packages/types/src/authentication.ts (2)
  • AuthenticationProvider (24-26)
  • HeaderToken (3-7)
packages/auth/src/authentication/supabase.ts (1)
  • createProvisioningSupabaseAuthentication (134-171)
packages/auth/src/account/provision-account.ts (3)
packages/db/src/index.ts (7)
  • SupabaseToken (153-162)
  • Database (130-130)
  • RlsClient (172-174)
  • createRlsClient (176-208)
  • RlsTransaction (164-166)
  • and (217-217)
  • eq (217-217)
packages/db/src/constants/category.ts (2)
  • DEFAULT_CATEGORY_NAME (1-1)
  • DEFAULT_CATEGORY_KIND (2-2)
packages/db/src/schema/index.ts (2)
  • profiles (22-53)
  • categories (57-98)
packages/auth/src/authentication/supabase.ts (3)
packages/auth/src/account/provision-account.ts (3)
  • AccountProvisionerDependencies (23-28)
  • AccountProvisioner (19-21)
  • createAccountProvisioner (59-106)
packages/types/src/authentication.ts (4)
  • AuthenticationProvider (24-26)
  • AuthenticationContext (16-18)
  • AuthenticationResult (20-22)
  • AuthenticatedToken (9-9)
packages/db/src/index.ts (1)
  • SupabaseToken (153-162)
🪛 GitHub Actions: CI
packages/auth/src/account/provision-account.ts

[error] 1-1: Biome: Organize Imports failed. The imports and exports are not sorted.

🔇 Additional comments (8)
packages/auth/src/authentication/index.ts (1)

3-6: Exports look good.

Re-exporting the provisioning variant alongside the base provider keeps the public API tidy and discoverable.
If consumers rely on deep imports from ./supabase.js today, consider noting this addition in the changelog to steer them to ./authentication. Based on learnings.

packages/auth/package.json (1)

17-20: Dependency addition makes sense; confirm runtime vs. types-only.

Since provisioning invokes DB/RLS, keeping @listee/db as a runtime dependency is appropriate.
Please confirm there isn’t a second, divergent SupabaseToken type in @listee/types vs @listee/db to avoid type drift across packages.

packages/auth/src/authentication/supabase.test.ts (3)

10-14: Import surface update is correct.

Aligns tests with the new provisioning entry point; types are imported from @listee/types as expected.


89-133: Great: verifies provisioning after successful auth.

Good isolation using a stub provider and capturing params.
Do we want auth to “fail-open” if provisioning throws? If yes, add a test that makes provision() reject and assert authenticate() still resolves. If no, assert the rejection. This locks behavior. Based on learnings.


171-208: Nice negative test for non‑Supabase tokens.

Confirms the wrapper ignores opaque header tokens.

packages/db/src/index.ts (1)

218-219: Public export for category constants is good.

Makes defaults available to provisioners without deep imports.

packages/auth/src/authentication/supabase.ts (1)

63-120: Auth core looks solid and centralized.

Uses remote JWKS with issuer/audience/clock tolerance and role gating; returns normalized result. This aligns with keeping verification centralized. Based on learnings.

packages/auth/src/index.ts (1)

12-17: No issues found—the re-export is valid.

The function createProvisioningSupabaseAuthentication is properly exported from packages/auth/src/authentication/index.ts, eliminating the concern about a broken barrel. The re-export in packages/auth/src/index.ts (lines 12-17) is functioning correctly.

Comment on lines 81 to 101
const existing = await tx
.select({ id: categories.id })
.from(categories)
.where(
and(
eq(categories.createdBy, params.userId),
eq(categories.kind, defaultCategoryKind),
),
)
.limit(1);

if (existing.length > 0) {
return;
}

await tx.insert(categories).values({
name: defaultCategoryName,
kind: defaultCategoryKind,
createdBy: params.userId,
updatedBy: params.userId,
});
Copy link

@coderabbitai coderabbitai bot Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Make default-category creation idempotent and precise.

  • Current existence check only filters by kind + createdBy; it can skip creating the Inbox if another system category exists.
  • Select-then-insert is racy; concurrent logins can create duplicates.

Apply both changes:

  1. Include name in the existence check.
  2. Rely on ON CONFLICT DO NOTHING with a composite unique (created_by, kind, name).

Code changes:

-      const existing = await tx
+      const existing = await tx
         .select({ id: categories.id })
         .from(categories)
         .where(
           and(
             eq(categories.createdBy, params.userId),
             eq(categories.kind, defaultCategoryKind),
+            eq(categories.name, defaultCategoryName),
           ),
         )
         .limit(1);

       if (existing.length > 0) {
         return;
       }

-      await tx.insert(categories).values({
-        name: defaultCategoryName,
-        kind: defaultCategoryKind,
-        createdBy: params.userId,
-        updatedBy: params.userId,
-      });
+      await tx
+        .insert(categories)
+        .values({
+          name: defaultCategoryName,
+          kind: defaultCategoryKind,
+          createdBy: params.userId,
+          updatedBy: params.userId,
+        })
+        .onConflictDoNothing({
+          // requires a unique index on (created_by, kind, name)
+          target: [categories.createdBy, categories.kind, categories.name],
+        });

Also add a composite unique index in the DB schema/migration:

  • uniqueIndex("categories_owner_kind_name_unique").on(table.createdBy, table.kind, table.name)

Check if such a unique index already exists:


🏁 Script executed:

#!/bin/bash
rg -nP -C2 'unique(Index|index)?\(|uniqueIndex\(' packages/db/src/schema

Length of output: 332


I need to search for the categories table definition to check if the composite unique index exists.


🏁 Script executed:

#!/bin/bash
# Search for categories table definition and any indexes on it
fd -t f '*.ts' packages/db/src/schema -x grep -l "categories\|categories\s*=" {} \;

Length of output: 283


🏁 Script executed:

#!/bin/bash
# Look for the categories table definition more broadly
rg -nP -C5 'table\s*\(\s*["\']categories' packages/db/src/schema

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Also check for migrations that might define the index
fd -t f '*.ts' packages/db -x grep -l "created_by.*kind.*name\|uniqueIndex.*categories" {} \;

Length of output: 283


🏁 Script executed:

#!/bin/bash
# View the actual code at the lines mentioned in the review
sed -n '81,101p' packages/auth/src/account/provision-account.ts

Length of output: 526


🏁 Script executed:

#!/bin/bash
# Check if onConflictDoNothing is already being used
rg -nP 'onConflictDoNothing' packages/auth/src/account/provision-account.ts

Length of output: 34


🏁 Script executed:

#!/bin/bash
# Look at categories schema file directly
find packages/db/src/schema -name '*.ts' -exec grep -l categories {} \;

Length of output: 31


🏁 Script executed:

#!/bin/bash
# Try to find where categories table is defined
rg -nP -A10 'categories\s*=\s*table\(' packages/db/src/schema

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for where categories might be imported or defined
rg -nP 'export.*categories|const categories' packages/db/src/schema

Length of output: 160


🏁 Script executed:

#!/bin/bash
# Check schema/index.ts for exports
cat packages/db/src/schema/index.ts

Length of output: 4107


🏁 Script executed:

#!/bin/bash
# Look for Drizzle migrations or migration files
fd -t f '*.ts' packages/db/src -path '*/migrat*' -exec ls -la {} \;

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Broader search in db schema directory
ls -la packages/db/src/schema/

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Look for all table definitions
rg -nP '\btable\s*\(' packages/db/src/schema -A5

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for Drizzle indexes
rg -nP '\bindex\s*\(|\.index\s*\(' packages/db/src/schema -A2 -B2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Exhaustive search for categories references
rg -nP -C3 'categories' packages/db/src/schema/

Length of output: 3909


🏁 Script executed:

#!/bin/bash
# Search for uniqueIndex specifically
rg -nP 'uniqueIndex' packages/db/src/ -C5

Length of output: 0


🏁 Script executed:

#!/bin/bash
# List all schema files
find packages/db/src/schema -type f -name '*.ts' | sort

Length of output: 31


🏁 Script executed:

#!/bin/bash
# Check the main index.ts file for table definitions
head -100 packages/db/src/schema/index.ts

Length of output: 2511


🏁 Script executed:

#!/bin/bash
# Search for sqliteTable or similar Drizzle DSL
rg -nP 'sqliteTable|pgTable|mysqlTable' packages/db/src/schema -A20 | head -150

Length of output: 5074


🏁 Script executed:

#!/bin/bash
# Simpler approach - just find all references to categories in schema dir
rg 'categories' packages/db/src/schema -l

Length of output: 31


🏁 Script executed:

#!/bin/bash
# Search for category-related files
find packages/db/src -name '*categor*' -o -name '*categ*'

Length of output: 37


🏁 Script executed:

#!/bin/bash
# Check if there's a separate categories schema file
fd -i categor packages/db/src/schema

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for the complete schema structure
rg -nP -B5 -A15 'table\(' packages/db/src/schema/index.ts

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for table definition syntax across all schema files
find packages/db/src/schema -name '*.ts' -exec head -200 {} + | head -500

Length of output: 4107


🏁 Script executed:

#!/bin/bash
# Try different keywords
rg -nP '(categories|category)' packages/db/src/schema/

Length of output: 1177


🏁 Script executed:

#!/bin/bash
# Check for schema.ts file
cat packages/db/src/schema.ts 2>/dev/null || echo "File not found"

Length of output: 14


🏁 Script executed:

#!/bin/bash
# Try the parent structure
ls -la packages/db/src/

Length of output: 347


🏁 Script executed:

#!/bin/bash
# Look for drizzle config
fd drizzle packages/db/

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if categories table is defined anywhere in auth package
rg -nP 'categories' packages/auth/src/account/provision-account.ts -B5 -A5

Length of output: 952


🏁 Script executed:

#!/bin/bash
# Look more broadly for table definitions
rg -nP -A20 'export const categories|const categories' packages/db/

Length of output: 1388


🏁 Script executed:

#!/bin/bash
# Try to find schema definitions with a simpler pattern
rg -nP 'categories' packages/db/src --type ts | head -20

Length of output: 1018


🏁 Script executed:

#!/bin/bash
# Check all files in packages/db/src
find packages/db/src -type f -name '*.ts' | xargs grep -l categories

Length of output: 31


🏁 Script executed:

#!/bin/bash
# Get file structure of packages
ls -la packages/

Length of output: 318


Code changes required with corresponding DB schema constraint.

The review correctly identifies two issues:

  1. The existence check (lines 85-87) only filters by createdBy and kind, omitting name. This allows other system categories to bypass the Inbox creation.

  2. The select-then-insert pattern is racy. Without onConflictDoNothing() or a unique constraint, concurrent logins can create duplicates.

Both code and schema changes are necessary:

  • Add name to the WHERE clause and use .onConflictDoNothing() with the composite target
  • Add the composite unique index (created_by, kind, name) to the categories table schema in packages/db/src/schema/index.ts

The DB schema currently has no such index, so the code change alone is insufficient.

🤖 Prompt for AI Agents
In packages/auth/src/account/provision-account.ts around lines 81–101 the
existence check only filters by createdBy and kind (missing name) and uses a
select-then-insert pattern that is racy; update the WHERE clause to include name
(defaultCategoryName) and change the insert to use onConflictDoNothing()
targeting the composite key (created_by, kind, name) to avoid duplicate inserts
under concurrency; additionally, add a composite unique index on (created_by,
kind, name) in packages/db/src/schema/index.ts so the onConflict target exists
at the DB level and prevents duplicates even if concurrent inserts occur.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the commit diff to verify the fix
git show 832e50049192 --stat
echo "---"
git show 832e50049192

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
# Use GitHub CLI to fetch the commit details
gh api repos/listee-dev/listee-libs/commits/832e50049192 --jq '.files[] | {filename: .filename, patch: .patch}'

Unrecoverable error: Stream setup permanently failed: Sandbox setup timed out after 180000ms


🏁 Script executed:

#!/bin/bash
# Fetch the current state of provision-account.ts from the PR branch
gh api repos/listee-dev/listee-libs/contents/packages/auth/src/account/provision-account.ts?ref=feature/add-account-provisioning --jq '.content' | base64 -d | tail -30

echo "---SEPARATOR---"

# Fetch the schema file to check for unique index
gh api repos/listee-dev/listee-libs/contents/packages/db/src/schema/index.ts?ref=feature/add-account-provisioning --jq '.content' | base64 -d | grep -A10 -B10 "uniqueIndex\|categories_system_name"

Unrecoverable error: Stream setup permanently failed: Sandbox setup timed out after 180000ms


@gentamura, thank you for fixing the race condition and idempotency issues! I appreciate you addressing both the existence check (adding name to the WHERE clause) and implementing the proper conflict handling with the composite unique index.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d9da61 and 832e500.

📒 Files selected for processing (2)
  • packages/auth/src/account/provision-account.ts (1 hunks)
  • packages/db/src/schema/index.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/auth/src/account/provision-account.ts
🧰 Additional context used
📓 Path-based instructions (1)
packages/*/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

packages/*/src/**/*.{ts,tsx}: Avoid implicit any; TypeScript is run with strict enabled
Prefer type guards or the satisfies operator over as casts where appropriate
Prefer unknown for external inputs
Use PascalCase for types and enums
Use camelCase for variables and functions
Ensure source comments are written in English

Files:

  • packages/db/src/schema/index.ts
🔇 Additional comments (1)
packages/db/src/schema/index.ts (1)

1-1: LGTM: Imports correctly added and used.

The eq and uniqueIndex imports are properly utilized in the partial unique index constraint on the categories table.

Also applies to: 9-9

@gentamura gentamura merged commit 5559b5d into main Oct 20, 2025
3 checks passed
@gentamura gentamura deleted the feature/add-account-provisioning branch October 20, 2025 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant