mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
fix(todo): use TEXT for user_id columns (Better Auth compatibility)
Better Auth generates non-UUID user IDs (e.g., otUe1YrfENPdHnrF3g1vSBfpkQfambCZ). Changed all user_id columns from uuid to text type to prevent "invalid input syntax for type uuid" errors. Also documented this requirement in: - .claude/guidelines/authentication.md (new User ID Format section) - .claude/guidelines/database.md (User ID Column Type section) - apps/todo/CLAUDE.md (Database Schema section) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
582c6f58a4
commit
4e63f3f74b
9 changed files with 87 additions and 16 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue