diff --git a/.claude/guidelines/authentication.md b/.claude/guidelines/authentication.md index fe885da13..d0f9a5dae 100644 --- a/.claude/guidelines/authentication.md +++ b/.claude/guidelines/authentication.md @@ -32,11 +32,37 @@ All authentication is handled by **Mana Core Auth**, a centralized authenticatio │<──────────────────────│ │ ``` +## User ID Format + +**CRITICAL**: Mana Core Auth uses Better Auth, which generates **non-UUID user IDs**. + +``` +Example user ID: otUe1YrfENPdHnrF3g1vSBfpkQfambCZ +``` + +**Format details:** +- 32 characters +- Base62 alphabet (a-z, A-Z, 0-9) +- ~190 bits of entropy (more than UUID's 122 bits) +- NOT a valid UUID format + +**Database schema implications:** + +```typescript +// CORRECT - use text for user_id +userId: text('user_id').notNull(), + +// WRONG - will cause "invalid input syntax for type uuid" errors +userId: uuid('user_id').notNull(), +``` + +Always use `text` type for `user_id` columns in all database schemas. + ## Token Structure (EdDSA JWT) ```json { - "sub": "user-uuid-123", + "sub": "otUe1YrfENPdHnrF3g1vSBfpkQfambCZ", "email": "user@example.com", "role": "user", "sid": "session-id-456", @@ -47,6 +73,8 @@ All authentication is handled by **Mana Core Auth**, a centralized authenticatio } ``` +**Note**: The `sub` claim contains the Better Auth user ID (not a UUID). + **Important**: Keep claims minimal. Do NOT include: - Credit balance (changes frequently) - Organization data (use API instead) diff --git a/.claude/guidelines/database.md b/.claude/guidelines/database.md index a503f86cd..285803ff3 100644 --- a/.claude/guidelines/database.md +++ b/.claude/guidelines/database.md @@ -101,6 +101,26 @@ src/db/ └── migrations/ # Generated migrations ``` +### User ID Column Type + +**CRITICAL**: Always use `text` for `user_id` columns, NOT `uuid`. + +Mana Core Auth (Better Auth) generates non-UUID user IDs: + +``` +Example: otUe1YrfENPdHnrF3g1vSBfpkQfambCZ +``` + +```typescript +// CORRECT +userId: text('user_id').notNull(), + +// WRONG - causes "invalid input syntax for type uuid" errors +userId: uuid('user_id').notNull(), +``` + +See [Authentication Guidelines](./authentication.md#user-id-format) for details. + ### Table Definition Pattern ```typescript @@ -123,8 +143,10 @@ export const files = pgTable( // Primary key - always UUID with auto-generation id: uuid('id').primaryKey().defaultRandom(), - // Foreign keys - userId: varchar('user_id', { length: 255 }).notNull(), + // User ID - always TEXT (Better Auth uses non-UUID format) + userId: text('user_id').notNull(), + + // Foreign keys to other tables (UUIDs are fine) parentFolderId: uuid('parent_folder_id').references(() => folders.id, { onDelete: 'set null' }), // Required fields diff --git a/apps/todo/CLAUDE.md b/apps/todo/CLAUDE.md index c12462a3e..affb10e98 100644 --- a/apps/todo/CLAUDE.md +++ b/apps/todo/CLAUDE.md @@ -156,9 +156,11 @@ pnpm preview # Preview build ## Database Schema +> **Note**: `user_id` columns use TEXT type (not UUID) because Mana Core Auth generates non-UUID user IDs. + ### projects - `id` (UUID) - Primary key -- `user_id` (UUID) - Owner +- `user_id` (TEXT) - Owner (Better Auth format) - `name` (VARCHAR) - Project name - `color` (VARCHAR) - Hex color - `icon` (VARCHAR) - Icon name @@ -169,7 +171,7 @@ pnpm preview # Preview build ### tasks - `id` (UUID) - Primary key - `project_id` (UUID) - FK to projects (nullable = Inbox) -- `user_id` (UUID) - Owner +- `user_id` (TEXT) - Owner (Better Auth format) - `title` (VARCHAR) - Task title - `description` (TEXT) - Description - `due_date` (TIMESTAMP) - Due date @@ -182,7 +184,7 @@ pnpm preview # Preview build ### labels - `id` (UUID) - Primary key -- `user_id` (UUID) - Owner +- `user_id` (TEXT) - Owner (Better Auth format) - `name` (VARCHAR) - Label name - `color` (VARCHAR) - Hex color @@ -193,6 +195,7 @@ pnpm preview # Preview build ### reminders - `id` (UUID) - Primary key - `task_id` (UUID) - FK to tasks +- `user_id` (TEXT) - Owner (Better Auth format) - `minutes_before` (INTEGER) - Offset - `type` (VARCHAR) - push/email/both - `status` (VARCHAR) - pending/sent/failed diff --git a/apps/todo/apps/backend/src/db/schema/kanban-boards.schema.ts b/apps/todo/apps/backend/src/db/schema/kanban-boards.schema.ts index 1245668e8..1d074c7cc 100644 --- a/apps/todo/apps/backend/src/db/schema/kanban-boards.schema.ts +++ b/apps/todo/apps/backend/src/db/schema/kanban-boards.schema.ts @@ -1,11 +1,20 @@ -import { pgTable, uuid, timestamp, varchar, boolean, integer, index } from 'drizzle-orm/pg-core'; +import { + pgTable, + uuid, + text, + timestamp, + varchar, + boolean, + integer, + index, +} from 'drizzle-orm/pg-core'; import { projects } from './projects.schema'; export const kanbanBoards = pgTable( 'kanban_boards', { id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull(), + userId: text('user_id').notNull(), projectId: uuid('project_id').references(() => projects.id, { onDelete: 'cascade' }), // Board properties diff --git a/apps/todo/apps/backend/src/db/schema/kanban-columns.schema.ts b/apps/todo/apps/backend/src/db/schema/kanban-columns.schema.ts index 3764e4872..98a8faddd 100644 --- a/apps/todo/apps/backend/src/db/schema/kanban-columns.schema.ts +++ b/apps/todo/apps/backend/src/db/schema/kanban-columns.schema.ts @@ -1,4 +1,13 @@ -import { pgTable, uuid, timestamp, varchar, boolean, integer, index } from 'drizzle-orm/pg-core'; +import { + pgTable, + uuid, + text, + timestamp, + varchar, + boolean, + integer, + index, +} from 'drizzle-orm/pg-core'; import { kanbanBoards } from './kanban-boards.schema'; // Define locally to avoid circular dependency with tasks.schema @@ -8,7 +17,7 @@ export const kanbanColumns = pgTable( 'kanban_columns', { id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull(), + userId: text('user_id').notNull(), boardId: uuid('board_id') .references(() => kanbanBoards.id, { onDelete: 'cascade' }) .notNull(), diff --git a/apps/todo/apps/backend/src/db/schema/labels.schema.ts b/apps/todo/apps/backend/src/db/schema/labels.schema.ts index 64e1f0bf0..55add9f7f 100644 --- a/apps/todo/apps/backend/src/db/schema/labels.schema.ts +++ b/apps/todo/apps/backend/src/db/schema/labels.schema.ts @@ -1,10 +1,10 @@ -import { pgTable, uuid, timestamp, varchar, index } from 'drizzle-orm/pg-core'; +import { pgTable, uuid, text, timestamp, varchar, index } from 'drizzle-orm/pg-core'; export const labels = pgTable( 'labels', { id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull(), + userId: text('user_id').notNull(), name: varchar('name', { length: 100 }).notNull(), color: varchar('color', { length: 7 }).default('#6B7280'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), diff --git a/apps/todo/apps/backend/src/db/schema/projects.schema.ts b/apps/todo/apps/backend/src/db/schema/projects.schema.ts index f1b507042..b45b7e92f 100644 --- a/apps/todo/apps/backend/src/db/schema/projects.schema.ts +++ b/apps/todo/apps/backend/src/db/schema/projects.schema.ts @@ -21,7 +21,7 @@ export const projects = pgTable( 'projects', { id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull(), + userId: text('user_id').notNull(), name: varchar('name', { length: 255 }).notNull(), description: text('description'), color: varchar('color', { length: 7 }).default('#3B82F6'), diff --git a/apps/todo/apps/backend/src/db/schema/reminders.schema.ts b/apps/todo/apps/backend/src/db/schema/reminders.schema.ts index fdb3f6e7f..63a1e9ce5 100644 --- a/apps/todo/apps/backend/src/db/schema/reminders.schema.ts +++ b/apps/todo/apps/backend/src/db/schema/reminders.schema.ts @@ -1,4 +1,4 @@ -import { pgTable, uuid, timestamp, varchar, integer, index } from 'drizzle-orm/pg-core'; +import { pgTable, uuid, text, timestamp, varchar, integer, index } from 'drizzle-orm/pg-core'; import { tasks } from './tasks.schema'; export type ReminderType = 'push' | 'email' | 'both'; @@ -11,7 +11,7 @@ export const reminders = pgTable( taskId: uuid('task_id') .notNull() .references(() => tasks.id, { onDelete: 'cascade' }), - userId: uuid('user_id').notNull(), + userId: text('user_id').notNull(), // Timing minutesBefore: integer('minutes_before').notNull(), diff --git a/apps/todo/apps/backend/src/db/schema/tasks.schema.ts b/apps/todo/apps/backend/src/db/schema/tasks.schema.ts index 573937582..8d3596fa5 100644 --- a/apps/todo/apps/backend/src/db/schema/tasks.schema.ts +++ b/apps/todo/apps/backend/src/db/schema/tasks.schema.ts @@ -45,7 +45,7 @@ export const tasks = pgTable( { id: uuid('id').primaryKey().defaultRandom(), projectId: uuid('project_id').references(() => projects.id, { onDelete: 'set null' }), - userId: uuid('user_id').notNull(), + userId: text('user_id').notNull(), parentTaskId: uuid('parent_task_id'), // Content