diff --git a/app/api/clerk/webhook/route.ts b/app/api/clerk/webhook/route.ts index 5228c2fb..05092cf1 100644 --- a/app/api/clerk/webhook/route.ts +++ b/app/api/clerk/webhook/route.ts @@ -50,39 +50,40 @@ export async function POST(req: Request) { } // Do something with the payload - // For this guide, you can log the payload to your console const { id } = evt.data const eventType = evt.type if (eventType === 'user.created' || eventType === 'user.updated') { - const { id, email_addresses, first_name, last_name } = evt.data as any + const { id, email_addresses } = evt.data as any const email = email_addresses[0]?.email_address if (id) { - // Upsert user + // Upsert user using Clerk ID as the primary key 'id' const [existingUser] = await db.select() .from(users) - .where(eq(users.clerkUserId, id)) + .where(eq(users.id, id)) .limit(1); if (existingUser) { await db.update(users) .set({ email }) - .where(eq(users.clerkUserId, id)); + .where(eq(users.id, id)); } else { - // Check by email first to link + // Check by email first (in case of migration or previous Supabase user) const [existingEmailUser] = await db.select() .from(users) .where(eq(users.email, email)) .limit(1); if (existingEmailUser) { - await db.update(users) - .set({ clerkUserId: id }) - .where(eq(users.id, existingEmailUser.id)); + // If found by email but ID is different, we have a conflict. + // For a clean transition, we could update the ID, but primary keys are stable. + // In this simplified model, we'll just insert if not exact ID match. + // Or we update the email to null on the old user if we want to "take over" the email. + await db.update(users).set({ id: id }).where(eq(users.id, existingEmailUser.id)); } else { await db.insert(users).values({ - clerkUserId: id, + id: id, email, role: 'viewer' }); @@ -93,7 +94,6 @@ export async function POST(req: Request) { if (eventType === 'user.deleted') { // Optionally handle user deletion - // For safety, we might not want to delete user data entirely } return new Response('', { status: 200 }) diff --git a/drizzle/migrations/0003_complex_sir_ram.sql b/drizzle/migrations/0003_complex_sir_ram.sql new file mode 100644 index 00000000..dcdc3892 --- /dev/null +++ b/drizzle/migrations/0003_complex_sir_ram.sql @@ -0,0 +1,10 @@ +ALTER TABLE "calendar_notes" ALTER COLUMN "user_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "chat_participants" ALTER COLUMN "user_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "chats" ALTER COLUMN "user_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "locations" ALTER COLUMN "user_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "messages" ALTER COLUMN "user_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "prompt_generation_jobs" ALTER COLUMN "user_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "system_prompts" ALTER COLUMN "user_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "users" ALTER COLUMN "id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "users" ALTER COLUMN "id" DROP DEFAULT;--> statement-breakpoint +ALTER TABLE "visualizations" ALTER COLUMN "user_id" SET DATA TYPE text; \ No newline at end of file diff --git a/drizzle/migrations/meta/0003_snapshot.json b/drizzle/migrations/meta/0003_snapshot.json new file mode 100644 index 00000000..57e345d1 --- /dev/null +++ b/drizzle/migrations/meta/0003_snapshot.json @@ -0,0 +1,762 @@ +{ + "id": "51cf73db-ea21-43c4-8541-aa61eb2c428d", + "prevId": "fd89f5aa-13b0-4dd4-9f50-4e921eea0f4f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.calendar_notes": { + "name": "calendar_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location_tags": { + "name": "location_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_tags": { + "name": "user_tags", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "map_feature_id": { + "name": "map_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "calendar_notes_user_id_users_id_fk": { + "name": "calendar_notes_user_id_users_id_fk", + "tableFrom": "calendar_notes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "calendar_notes_chat_id_chats_id_fk": { + "name": "calendar_notes_chat_id_chats_id_fk", + "tableFrom": "calendar_notes", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_participants": { + "name": "chat_participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'collaborator'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_participants_chat_id_chats_id_fk": { + "name": "chat_participants_chat_id_chats_id_fk", + "tableFrom": "chat_participants", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_participants_user_id_users_id_fk": { + "name": "chat_participants_user_id_users_id_fk", + "tableFrom": "chat_participants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_participants_chat_user_unique": { + "name": "chat_participants_chat_user_unique", + "nullsNotDistinct": false, + "columns": [ + "chat_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chats": { + "name": "chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Untitled Chat'" + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'private'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "share_path": { + "name": "share_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shareable_link_id": { + "name": "shareable_link_id", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chats_user_id_users_id_fk": { + "name": "chats_user_id_users_id_fk", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chats_shareable_link_id_unique": { + "name": "chats_shareable_link_id_unique", + "nullsNotDistinct": false, + "columns": [ + "shareable_link_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.locations": { + "name": "locations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "geojson": { + "name": "geojson", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "geometry": { + "name": "geometry", + "type": "geometry(GEOMETRY, 4326)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "locations_user_id_users_id_fk": { + "name": "locations_user_id_users_id_fk", + "tableFrom": "locations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "locations_chat_id_chats_id_fk": { + "name": "locations_chat_id_chats_id_fk", + "tableFrom": "locations", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "messages_chat_id_chats_id_fk": { + "name": "messages_chat_id_chats_id_fk", + "tableFrom": "messages", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_user_id_users_id_fk": { + "name": "messages_user_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_location_id_locations_id_fk": { + "name": "messages_location_id_locations_id_fk", + "tableFrom": "messages", + "tableTo": "locations", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.prompt_generation_jobs": { + "name": "prompt_generation_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result_prompt": { + "name": "result_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "prompt_generation_jobs_user_id_users_id_fk": { + "name": "prompt_generation_jobs_user_id_users_id_fk", + "tableFrom": "prompt_generation_jobs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_prompts": { + "name": "system_prompts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "system_prompts_user_id_users_id_fk": { + "name": "system_prompts_user_id_users_id_fk", + "tableFrom": "system_prompts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'viewer'" + }, + "selected_model": { + "name": "selected_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.visualizations": { + "name": "visualizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'map_layer'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "geometry": { + "name": "geometry", + "type": "geometry(GEOMETRY, 4326)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "visualizations_user_id_users_id_fk": { + "name": "visualizations_user_id_users_id_fk", + "tableFrom": "visualizations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "visualizations_chat_id_chats_id_fk": { + "name": "visualizations_chat_id_chats_id_fk", + "tableFrom": "visualizations", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/_journal.json b/drizzle/migrations/meta/_journal.json index 259f488a..42e1bb34 100644 --- a/drizzle/migrations/meta/_journal.json +++ b/drizzle/migrations/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1782395315062, "tag": "0002_lively_black_widow", "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1783501051316, + "tag": "0003_complex_sir_ram", + "breakpoints": true } ] } \ No newline at end of file diff --git a/lib/actions/users.ts b/lib/actions/users.ts index 6fba7f8b..77790b1e 100644 --- a/lib/actions/users.ts +++ b/lib/actions/users.ts @@ -5,6 +5,7 @@ import { db } from '@/lib/db'; import { users } from '@/lib/db/schema'; import { eq, ilike } from 'drizzle-orm'; import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user'; +import { v4 as uuidv4 } from 'uuid'; export type UserRole = "admin" | "editor" | "viewer"; @@ -75,6 +76,7 @@ export async function addUser(newUser: { email: string; role: UserRole }): Promi } const [insertedUser] = await db.insert(users).values({ + id: uuidv4(), // Manual ID generation since db default is gone email: newUser.email, role: newUser.role, }).returning({ diff --git a/lib/auth/get-current-user.ts b/lib/auth/get-current-user.ts index 5ab5385f..893c12cb 100644 --- a/lib/auth/get-current-user.ts +++ b/lib/auth/get-current-user.ts @@ -22,23 +22,22 @@ export async function getClerkUserIdOnServer(): Promise { } /** - * Resolves a Clerk User ID to our internal database UUID. + * Resolves a Clerk User ID to our internal database ID. * If the user doesn't exist in our DB yet, it will create one. * * @param clerkUserId The Clerk user ID to resolve - * @returns {Promise} Our internal database UUID for the user + * @returns {Promise} Our internal database ID for the user */ export async function resolveClerkUserToDbUser(clerkUserId: string): Promise { if (AUTH_DISABLED_FLAG && clerkUserId === MOCK_USER_ID) { - // Return a consistent UUID for the mock user - return '00000000-0000-0000-0000-000000000000'; + return MOCK_USER_ID; } try { - // 1. Try to find the user by clerkUserId + // 1. Try to find the user by ID (which IS the clerkUserId) const [existingUser] = await db.select({ id: users.id }) .from(users) - .where(eq(users.clerkUserId, clerkUserId)) + .where(eq(users.id, clerkUserId)) .limit(1); if (existingUser) { @@ -51,27 +50,23 @@ export async function resolveClerkUserToDbUser(clerkUserId: string): Promise} The internal user UUID if authenticated, otherwise null. + * @returns {Promise} The internal user ID if authenticated, otherwise null. */ export async function getCurrentUserIdOnServer(): Promise { const clerkUserId = await getClerkUserIdOnServer(); diff --git a/lib/db/index.ts b/lib/db/index.ts index f0b43eb7..09a25945 100644 --- a/lib/db/index.ts +++ b/lib/db/index.ts @@ -1,5 +1,5 @@ import { drizzle } from 'drizzle-orm/node-postgres'; -import { Pool, type PoolConfig } from 'pg'; // Uses Pool from pg, import PoolConfig +import { Pool, type PoolConfig } from 'pg'; import * as dotenv from 'dotenv'; import * as schema from './schema'; @@ -7,16 +7,19 @@ dotenv.config({ path: '.env.local' }); // In production/build environments, we might not have DATABASE_URL immediately available // especially during Next.js static optimization phases. -if (!process.env.DATABASE_URL && process.env.NODE_ENV === 'production') { +const dbUrl = process.env.DATABASE_URL; +if (!dbUrl && process.env.NODE_ENV === 'production') { console.warn('DATABASE_URL environment variable is not set. Database features will be unavailable.'); } const poolConfig: PoolConfig = { - connectionString: process.env.DATABASE_URL || 'postgres://localhost:5432/postgres', + connectionString: dbUrl && dbUrl !== "postgresql://user:password@host:port/db" + ? dbUrl + : 'postgres://localhost:5432/postgres', }; // Conditionally apply SSL for Supabase URLs -if (process.env.DATABASE_URL && process.env.DATABASE_URL.includes('supabase.co')) { +if (dbUrl && dbUrl.includes('supabase.co')) { poolConfig.ssl = { rejectUnauthorized: false, }; diff --git a/lib/db/migrate.ts b/lib/db/migrate.ts index 7c696a4d..5e7338f8 100644 --- a/lib/db/migrate.ts +++ b/lib/db/migrate.ts @@ -6,30 +6,28 @@ import * as dotenv from 'dotenv'; dotenv.config({ path: '.env.local' }); async function runMigrations() { - if (!process.env.DATABASE_URL) { - throw new Error('DATABASE_URL environment variable is not set for migrations'); + const dbUrl = process.env.DATABASE_URL; + if (!dbUrl || dbUrl === "postgresql://user:password@host:port/db") { + console.warn('Skipping migrations: DATABASE_URL is not set or is placeholder.'); + return; } const pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: { - rejectUnauthorized: false, // Ensure this is appropriate for your Supabase connection - }, - // max: 1, // Optional: restrict to 1 connection for migration + connectionString: dbUrl, + ssl: dbUrl.includes('supabase.co') ? { rejectUnauthorized: false } : undefined, }); const db = drizzle(pool); console.log('Running database migrations...'); try { - // Point to the directory containing your migration files await migrate(db, { migrationsFolder: './drizzle/migrations' }); console.log('Migrations completed successfully.'); } catch (error) { console.error('Error running migrations:', error); - process.exit(1); // Exit with error code + process.exit(1); } finally { - await pool.end(); // Ensure the connection pool is closed + await pool.end(); } } @@ -37,5 +35,4 @@ if (process.env.EXECUTE_MIGRATIONS === 'true') { runMigrations(); } else { console.log('Skipping migrations. Set EXECUTE_MIGRATIONS=true to run them.'); - console.log('To run migrations, use the "npm run db:migrate" or "bun run db:migrate" script, which sets this variable.'); } diff --git a/lib/db/schema.ts b/lib/db/schema.ts index 0b41dceb..84eec0f0 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -20,17 +20,16 @@ const vector = customType<{ data: number[] }>({ }); export const users = pgTable('users', { - id: uuid('id').primaryKey().defaultRandom(), + id: text('id').primaryKey(), // Clerk user ID as primary key email: text('email').unique(), // Enforced unique for user identity role: text('role').default('viewer'), selectedModel: text('selected_model'), systemPrompt: text('system_prompt'), - clerkUserId: text('clerk_user_id').unique(), }); export const chats = pgTable('chats', { id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), title: text('title').notNull().default('Untitled Chat'), visibility: text('visibility').default('private'), path: text('path'), @@ -42,7 +41,7 @@ export const chats = pgTable('chats', { export const locations = pgTable('locations', { id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), chatId: uuid('chat_id').references(() => chats.id, { onDelete: 'cascade' }), geojson: jsonb('geojson').notNull(), geometry: geometry('geometry'), @@ -53,7 +52,7 @@ export const locations = pgTable('locations', { export const messages = pgTable('messages', { id: uuid('id').primaryKey().defaultRandom(), chatId: uuid('chat_id').notNull().references(() => chats.id, { onDelete: 'cascade' }), - userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), role: text('role').notNull(), content: text('content').notNull(), embedding: vector('embedding'), @@ -64,7 +63,7 @@ export const messages = pgTable('messages', { export const chatParticipants = pgTable('chat_participants', { id: uuid('id').primaryKey().defaultRandom(), chatId: uuid('chat_id').notNull().references(() => chats.id, { onDelete: 'cascade' }), - userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), role: text('role').notNull().default('collaborator'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), }, (t) => ({ @@ -74,7 +73,7 @@ export const chatParticipants = pgTable('chat_participants', { export const systemPrompts = pgTable('system_prompts', { id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), prompt: text('prompt').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), @@ -82,7 +81,7 @@ export const systemPrompts = pgTable('system_prompts', { export const visualizations = pgTable('visualizations', { id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), chatId: uuid('chat_id').references(() => chats.id, { onDelete: 'cascade' }), type: text('type').notNull().default('map_layer'), data: jsonb('data').notNull(), @@ -92,7 +91,7 @@ export const visualizations = pgTable('visualizations', { export const calendarNotes = pgTable('calendar_notes', { id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), chatId: uuid('chat_id').references(() => chats.id, { onDelete: 'cascade' }), date: timestamp('date', { withTimezone: true }).notNull(), content: text('content').notNull(), @@ -185,7 +184,7 @@ export const visualizationsRelations = relations(visualizations, ({ one }) => ({ export const promptGenerationJobs = pgTable('prompt_generation_jobs', { id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), domain: text('domain').notNull(), status: text('status').notNull().default('pending'), // pending | processing | complete | error resultPrompt: text('result_prompt'),