mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-12 12:20:23 +01:00
.
This commit is contained in:
70
packages/web/scripts/db-connect.ts
Normal file
70
packages/web/scripts/db-connect.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Test PlanetScale Postgres connection
|
||||
*
|
||||
* Run: pnpm tsx scripts/db-connect.ts
|
||||
*/
|
||||
|
||||
import "dotenv/config"
|
||||
import postgres from "postgres"
|
||||
|
||||
const CONNECTION_STRING = process.env.DATABASE_URL
|
||||
|
||||
if (!CONNECTION_STRING) {
|
||||
console.error("❌ DATABASE_URL is required in .env")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const sql = postgres(CONNECTION_STRING, {
|
||||
ssl: "require",
|
||||
max: 1,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 10,
|
||||
})
|
||||
|
||||
async function testConnection() {
|
||||
console.log("🔌 Connecting to PlanetScale Postgres...")
|
||||
|
||||
try {
|
||||
// Test basic connection
|
||||
const [result] = await sql`SELECT NOW() as time, current_database() as db`
|
||||
console.log("✅ Connected!")
|
||||
console.log(` Database: ${result.db}`)
|
||||
console.log(` Server time: ${result.time}`)
|
||||
|
||||
// List all databases
|
||||
const databases = await sql`
|
||||
SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname
|
||||
`
|
||||
console.log(`\n📁 Databases:`)
|
||||
for (const d of databases) {
|
||||
console.log(` - ${d.datname}`)
|
||||
}
|
||||
|
||||
// List tables in current db
|
||||
const tables = await sql`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name
|
||||
`
|
||||
|
||||
if (tables.length > 0) {
|
||||
console.log(`\n📋 Tables (${tables.length}):`)
|
||||
for (const t of tables) {
|
||||
console.log(` - ${t.table_name}`)
|
||||
}
|
||||
} else {
|
||||
console.log("\n📋 No tables found in public schema")
|
||||
}
|
||||
|
||||
// Show version
|
||||
const [version] = await sql`SELECT version()`
|
||||
console.log(`\n🐘 ${version.version}`)
|
||||
} catch (err) {
|
||||
console.error("❌ Connection failed:", err)
|
||||
} finally {
|
||||
await sql.end()
|
||||
}
|
||||
}
|
||||
|
||||
testConnection()
|
||||
250
packages/web/scripts/db-query.ts
Normal file
250
packages/web/scripts/db-query.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* Production Database Query Tool
|
||||
* Allows CRUD operations on the production database
|
||||
*
|
||||
* Usage:
|
||||
* DATABASE_URL="..." pnpm tsx scripts/db-query.ts
|
||||
*
|
||||
* Commands (interactive):
|
||||
* tables - List all tables
|
||||
* users - List all users
|
||||
* threads - List chat threads
|
||||
* sql <query> - Run raw SQL
|
||||
* insert-user <email> <name> - Create a user
|
||||
* delete-user <id> - Delete a user
|
||||
* help - Show commands
|
||||
* exit - Exit
|
||||
*/
|
||||
|
||||
import "dotenv/config"
|
||||
import postgres from "postgres"
|
||||
import * as readline from "readline"
|
||||
|
||||
const CONNECTION_STRING = process.env.DATABASE_URL
|
||||
|
||||
if (!CONNECTION_STRING) {
|
||||
console.error("❌ DATABASE_URL is required")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const sql = postgres(CONNECTION_STRING, {
|
||||
ssl: "require",
|
||||
max: 1,
|
||||
})
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
})
|
||||
|
||||
function prompt(question: string): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(question, resolve)
|
||||
})
|
||||
}
|
||||
|
||||
async function listTables() {
|
||||
const tables = await sql`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name
|
||||
`
|
||||
console.log("\n📋 Tables:")
|
||||
if (tables.length === 0) {
|
||||
console.log(" (no tables found)")
|
||||
} else {
|
||||
for (const t of tables) {
|
||||
const count = await sql`
|
||||
SELECT COUNT(*) as count FROM ${sql(t.table_name)}
|
||||
`
|
||||
console.log(` - ${t.table_name} (${count[0].count} rows)`)
|
||||
}
|
||||
}
|
||||
console.log()
|
||||
}
|
||||
|
||||
async function listUsers() {
|
||||
try {
|
||||
const users = await sql`SELECT id, name, email, "createdAt" FROM users ORDER BY "createdAt" DESC LIMIT 20`
|
||||
console.log("\n👥 Users:")
|
||||
if (users.length === 0) {
|
||||
console.log(" (no users)")
|
||||
} else {
|
||||
for (const u of users) {
|
||||
console.log(` - ${u.id}: ${u.name} <${u.email}> (${u.createdAt})`)
|
||||
}
|
||||
}
|
||||
console.log()
|
||||
} catch (e) {
|
||||
console.log(" ❌ users table not found or error:", (e as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
async function listThreads() {
|
||||
try {
|
||||
const threads = await sql`
|
||||
SELECT id, title, user_id, created_at
|
||||
FROM chat_threads
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 20
|
||||
`
|
||||
console.log("\n💬 Chat Threads:")
|
||||
if (threads.length === 0) {
|
||||
console.log(" (no threads)")
|
||||
} else {
|
||||
for (const t of threads) {
|
||||
console.log(` - #${t.id}: "${t.title}" (user: ${t.user_id})`)
|
||||
}
|
||||
}
|
||||
console.log()
|
||||
} catch (e) {
|
||||
console.log(" ❌ chat_threads table not found or error:", (e as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
async function runSQL(query: string) {
|
||||
try {
|
||||
const result = await sql.unsafe(query)
|
||||
console.log("\n✅ Result:")
|
||||
console.log(result)
|
||||
console.log()
|
||||
} catch (e) {
|
||||
console.log("❌ Error:", (e as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
async function insertUser(email: string, name: string) {
|
||||
try {
|
||||
const id = `user_${Date.now()}`
|
||||
await sql`
|
||||
INSERT INTO users (id, name, email, "emailVerified", "createdAt", "updatedAt")
|
||||
VALUES (${id}, ${name}, ${email}, false, NOW(), NOW())
|
||||
`
|
||||
console.log(`✅ Created user: ${id}`)
|
||||
} catch (e) {
|
||||
console.log("❌ Error:", (e as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteUser(id: string) {
|
||||
try {
|
||||
const result = await sql`DELETE FROM users WHERE id = ${id}`
|
||||
console.log(`✅ Deleted ${result.count} user(s)`)
|
||||
} catch (e) {
|
||||
console.log("❌ Error:", (e as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp() {
|
||||
console.log(`
|
||||
📖 Commands:
|
||||
tables - List all tables with row counts
|
||||
users - List users (max 20)
|
||||
threads - List chat threads (max 20)
|
||||
sql <query> - Run raw SQL query
|
||||
insert-user <email> <name> - Create a new user
|
||||
delete-user <id> - Delete a user by ID
|
||||
drop-all - Drop all tables (dangerous!)
|
||||
help - Show this help
|
||||
exit - Exit the tool
|
||||
`)
|
||||
}
|
||||
|
||||
async function dropAll() {
|
||||
const confirm = await prompt("⚠️ This will DROP ALL TABLES. Type 'DROP' to confirm: ")
|
||||
if (confirm !== "DROP") {
|
||||
console.log("Aborted.")
|
||||
return
|
||||
}
|
||||
|
||||
const tables = [
|
||||
"thread_context_items",
|
||||
"context_items",
|
||||
"canvas_images",
|
||||
"canvas",
|
||||
"chat_messages",
|
||||
"chat_threads",
|
||||
"verifications",
|
||||
"accounts",
|
||||
"sessions",
|
||||
"users",
|
||||
]
|
||||
|
||||
for (const table of tables) {
|
||||
try {
|
||||
await sql`DROP TABLE IF EXISTS ${sql(table)} CASCADE`
|
||||
console.log(` ✓ Dropped ${table}`)
|
||||
} catch (e) {
|
||||
console.log(` ✗ ${table}: ${(e as Error).message}`)
|
||||
}
|
||||
}
|
||||
console.log("\n✓ All tables dropped")
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("🔌 Connected to production database")
|
||||
console.log('Type "help" for commands, "exit" to quit.\n')
|
||||
|
||||
// Check initial connection
|
||||
try {
|
||||
const [result] = await sql`SELECT current_database() as db`
|
||||
console.log(`Database: ${result.db}\n`)
|
||||
} catch (e) {
|
||||
console.error("❌ Connection failed:", (e as Error).message)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const input = await prompt("db> ")
|
||||
const [cmd, ...args] = input.trim().split(/\s+/)
|
||||
|
||||
switch (cmd.toLowerCase()) {
|
||||
case "tables":
|
||||
await listTables()
|
||||
break
|
||||
case "users":
|
||||
await listUsers()
|
||||
break
|
||||
case "threads":
|
||||
await listThreads()
|
||||
break
|
||||
case "sql":
|
||||
await runSQL(args.join(" "))
|
||||
break
|
||||
case "insert-user":
|
||||
if (args.length < 2) {
|
||||
console.log("Usage: insert-user <email> <name>")
|
||||
} else {
|
||||
await insertUser(args[0], args.slice(1).join(" "))
|
||||
}
|
||||
break
|
||||
case "delete-user":
|
||||
if (args.length < 1) {
|
||||
console.log("Usage: delete-user <id>")
|
||||
} else {
|
||||
await deleteUser(args[0])
|
||||
}
|
||||
break
|
||||
case "drop-all":
|
||||
await dropAll()
|
||||
break
|
||||
case "help":
|
||||
showHelp()
|
||||
break
|
||||
case "exit":
|
||||
case "quit":
|
||||
case "q":
|
||||
console.log("Bye!")
|
||||
await sql.end()
|
||||
rl.close()
|
||||
process.exit(0)
|
||||
case "":
|
||||
break
|
||||
default:
|
||||
console.log(`Unknown command: ${cmd}. Type "help" for commands.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
149
packages/web/scripts/migrate-safe.ts
Normal file
149
packages/web/scripts/migrate-safe.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Safe production migration script
|
||||
* Usage: DATABASE_URL="..." pnpm tsx scripts/migrate-safe.ts [option]
|
||||
* Options: check | auth | drizzle | both
|
||||
*/
|
||||
import postgres from "postgres"
|
||||
|
||||
const DATABASE_URL = process.env.DATABASE_URL
|
||||
if (!DATABASE_URL) {
|
||||
console.error("❌ DATABASE_URL is required")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const sql = postgres(DATABASE_URL)
|
||||
|
||||
async function checkConnection() {
|
||||
try {
|
||||
await sql`SELECT 1`
|
||||
console.log("✓ Connected to database")
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error("✗ Connection failed:", (e as Error).message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function listTables() {
|
||||
const tables = await sql`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name
|
||||
`
|
||||
if (tables.length === 0) {
|
||||
console.log(" (no tables)")
|
||||
} else {
|
||||
tables.forEach((t) => console.log(" -", t.table_name))
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAuthTables() {
|
||||
const cols = await sql`
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'verifications'
|
||||
ORDER BY ordinal_position
|
||||
`
|
||||
if (cols.length === 0) {
|
||||
console.log(" verifications: NOT EXISTS (will be created)")
|
||||
return false
|
||||
}
|
||||
const colNames = cols.map((c) => c.column_name)
|
||||
if (colNames.includes("expiresAt")) {
|
||||
console.log(" verifications: ✓ Correct (camelCase)")
|
||||
return true
|
||||
} else {
|
||||
console.log(" verifications: ⚠ Wrong columns:", colNames.join(", "))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function createAuthTables() {
|
||||
console.log("Dropping existing auth tables...")
|
||||
await sql`DROP TABLE IF EXISTS verifications CASCADE`
|
||||
await sql`DROP TABLE IF EXISTS accounts CASCADE`
|
||||
await sql`DROP TABLE IF EXISTS sessions CASCADE`
|
||||
await sql`DROP TABLE IF EXISTS users CASCADE`
|
||||
|
||||
console.log("Creating auth tables with camelCase columns...")
|
||||
await sql.unsafe(`
|
||||
CREATE TABLE users (
|
||||
id text PRIMARY KEY,
|
||||
name text NOT NULL,
|
||||
email text NOT NULL UNIQUE,
|
||||
"emailVerified" boolean NOT NULL DEFAULT false,
|
||||
image text,
|
||||
"createdAt" timestamp NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamp NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE TABLE sessions (
|
||||
id text PRIMARY KEY,
|
||||
"expiresAt" timestamp NOT NULL,
|
||||
token text NOT NULL UNIQUE,
|
||||
"createdAt" timestamp NOT NULL,
|
||||
"updatedAt" timestamp NOT NULL,
|
||||
"ipAddress" text,
|
||||
"userAgent" text,
|
||||
"userId" text NOT NULL REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE accounts (
|
||||
id text PRIMARY KEY,
|
||||
"accountId" text NOT NULL,
|
||||
"providerId" text NOT NULL,
|
||||
"userId" text NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
"accessToken" text,
|
||||
"refreshToken" text,
|
||||
"idToken" text,
|
||||
"accessTokenExpiresAt" timestamp,
|
||||
"refreshTokenExpiresAt" timestamp,
|
||||
scope text,
|
||||
password text,
|
||||
"createdAt" timestamp NOT NULL,
|
||||
"updatedAt" timestamp NOT NULL
|
||||
);
|
||||
CREATE TABLE verifications (
|
||||
id text PRIMARY KEY,
|
||||
identifier text NOT NULL,
|
||||
value text NOT NULL,
|
||||
"expiresAt" timestamp NOT NULL,
|
||||
"createdAt" timestamp NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamp NOT NULL DEFAULT now()
|
||||
);
|
||||
`)
|
||||
console.log("✓ Auth tables created")
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const option = process.argv[2] || "check"
|
||||
|
||||
if (!(await checkConnection())) {
|
||||
await sql.end()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (option === "check") {
|
||||
console.log("\nCurrent tables:")
|
||||
await listTables()
|
||||
console.log("\nAuth tables status:")
|
||||
await checkAuthTables()
|
||||
} else if (option === "auth") {
|
||||
await createAuthTables()
|
||||
} else if (option === "drizzle") {
|
||||
console.log("Run: DATABASE_URL=\"...\" pnpm drizzle-kit push --force")
|
||||
} else if (option === "both") {
|
||||
await createAuthTables()
|
||||
console.log("\nNow run: DATABASE_URL=\"...\" pnpm drizzle-kit push --force")
|
||||
} else {
|
||||
console.log("Unknown option:", option)
|
||||
console.log("Options: check | auth | drizzle | both")
|
||||
}
|
||||
|
||||
await sql.end()
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e)
|
||||
sql.end()
|
||||
process.exit(1)
|
||||
})
|
||||
222
packages/web/scripts/push-schema.ts
Normal file
222
packages/web/scripts/push-schema.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Push schema directly to PlanetScale Postgres
|
||||
* Bypasses drizzle-kit permission issues
|
||||
*
|
||||
* Run: DATABASE_URL="..." pnpm tsx scripts/push-schema.ts
|
||||
*
|
||||
* NOTE: PlanetScale API tokens may not have CREATE permissions.
|
||||
* If you get "permission denied for schema public", you need to:
|
||||
* 1. Go to PlanetScale dashboard
|
||||
* 2. Create a new password with "Admin" role
|
||||
* 3. Use that connection string instead
|
||||
* OR run the SQL manually in PlanetScale's web console
|
||||
*/
|
||||
|
||||
import "dotenv/config"
|
||||
import postgres from "postgres"
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL
|
||||
|
||||
if (!databaseUrl) {
|
||||
throw new Error("DATABASE_URL is required")
|
||||
}
|
||||
|
||||
// Allow disabling SSL for local/dev databases while keeping require for prod.
|
||||
const parsed = new URL(databaseUrl)
|
||||
const hostname = parsed.hostname
|
||||
const explicitSsl = process.env.DATABASE_SSL?.toLowerCase()
|
||||
const isLocalHost =
|
||||
hostname === "localhost" ||
|
||||
hostname === "127.0.0.1" ||
|
||||
hostname.endsWith(".local") ||
|
||||
hostname.endsWith(".localtest.me")
|
||||
|
||||
const ssl =
|
||||
explicitSsl === "disable"
|
||||
? false
|
||||
: explicitSsl === "require"
|
||||
? "require"
|
||||
: isLocalHost
|
||||
? false
|
||||
: "require"
|
||||
|
||||
const sql = postgres(databaseUrl, {
|
||||
ssl,
|
||||
max: 1,
|
||||
})
|
||||
|
||||
async function pushSchema() {
|
||||
console.log("🚀 Pushing schema to PlanetScale Postgres...")
|
||||
|
||||
// Check if we have CREATE permissions
|
||||
const [user] = await sql`SELECT current_user`
|
||||
console.log(` Connected as: ${user.current_user}`)
|
||||
|
||||
try {
|
||||
// Better-auth tables (camelCase columns)
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" text PRIMARY KEY,
|
||||
"name" text NOT NULL,
|
||||
"email" text NOT NULL UNIQUE,
|
||||
"emailVerified" boolean NOT NULL DEFAULT false,
|
||||
"image" text,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
)
|
||||
`
|
||||
console.log("✅ Created users table")
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "sessions" (
|
||||
"id" text PRIMARY KEY,
|
||||
"expiresAt" timestamptz NOT NULL,
|
||||
"token" text NOT NULL UNIQUE,
|
||||
"createdAt" timestamptz NOT NULL,
|
||||
"updatedAt" timestamptz NOT NULL,
|
||||
"ipAddress" text,
|
||||
"userAgent" text,
|
||||
"userId" text NOT NULL REFERENCES "users"("id") ON DELETE cascade
|
||||
)
|
||||
`
|
||||
console.log("✅ Created sessions table")
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "accounts" (
|
||||
"id" text PRIMARY KEY,
|
||||
"accountId" text NOT NULL,
|
||||
"providerId" text NOT NULL,
|
||||
"userId" text NOT NULL REFERENCES "users"("id") ON DELETE cascade,
|
||||
"accessToken" text,
|
||||
"refreshToken" text,
|
||||
"idToken" text,
|
||||
"accessTokenExpiresAt" timestamptz,
|
||||
"refreshTokenExpiresAt" timestamptz,
|
||||
"scope" text,
|
||||
"password" text,
|
||||
"createdAt" timestamptz NOT NULL,
|
||||
"updatedAt" timestamptz NOT NULL
|
||||
)
|
||||
`
|
||||
console.log("✅ Created accounts table")
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "verifications" (
|
||||
"id" text PRIMARY KEY,
|
||||
"identifier" text NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
"expiresAt" timestamptz NOT NULL,
|
||||
"createdAt" timestamptz DEFAULT now(),
|
||||
"updatedAt" timestamptz DEFAULT now()
|
||||
)
|
||||
`
|
||||
console.log("✅ Created verifications table")
|
||||
|
||||
// App tables (snake_case for Electric sync)
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "chat_threads" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"title" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now()
|
||||
)
|
||||
`
|
||||
console.log("✅ Created chat_threads table")
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "chat_messages" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"thread_id" integer NOT NULL REFERENCES "chat_threads"("id") ON DELETE cascade,
|
||||
"role" varchar(32) NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now()
|
||||
)
|
||||
`
|
||||
console.log("✅ Created chat_messages table")
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "canvas" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"owner_id" text NOT NULL REFERENCES "users"("id") ON DELETE cascade,
|
||||
"name" text NOT NULL DEFAULT 'Untitled Canvas',
|
||||
"width" integer NOT NULL DEFAULT 1024,
|
||||
"height" integer NOT NULL DEFAULT 1024,
|
||||
"default_model" text NOT NULL DEFAULT 'gemini-2.0-flash-exp-image-generation',
|
||||
"default_style" text NOT NULL DEFAULT 'default',
|
||||
"background_prompt" text,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"updated_at" timestamptz NOT NULL DEFAULT now()
|
||||
)
|
||||
`
|
||||
console.log("✅ Created canvas table")
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "canvas_images" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"canvas_id" uuid NOT NULL REFERENCES "canvas"("id") ON DELETE cascade,
|
||||
"name" text NOT NULL DEFAULT 'Untitled Image',
|
||||
"prompt" text NOT NULL DEFAULT '',
|
||||
"model_id" text NOT NULL DEFAULT 'gemini-2.0-flash-exp-image-generation',
|
||||
"model_used" text,
|
||||
"style_id" text NOT NULL DEFAULT 'default',
|
||||
"width" integer NOT NULL DEFAULT 512,
|
||||
"height" integer NOT NULL DEFAULT 512,
|
||||
"position" jsonb NOT NULL DEFAULT '{"x": 0, "y": 0}',
|
||||
"rotation" double precision NOT NULL DEFAULT 0,
|
||||
"content_base64" text,
|
||||
"image_url" text,
|
||||
"metadata" jsonb,
|
||||
"branch_parent_id" uuid,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"updated_at" timestamptz NOT NULL DEFAULT now()
|
||||
)
|
||||
`
|
||||
console.log("✅ Created canvas_images table")
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "context_items" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"user_id" text NOT NULL REFERENCES "users"("id") ON DELETE cascade,
|
||||
"type" varchar(32) NOT NULL,
|
||||
"url" text,
|
||||
"name" text NOT NULL,
|
||||
"content" text,
|
||||
"refreshing" boolean NOT NULL DEFAULT false,
|
||||
"parent_id" integer,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"updated_at" timestamptz NOT NULL DEFAULT now()
|
||||
)
|
||||
`
|
||||
console.log("✅ Created context_items table")
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "thread_context_items" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"thread_id" integer NOT NULL REFERENCES "chat_threads"("id") ON DELETE cascade,
|
||||
"context_item_id" integer NOT NULL REFERENCES "context_items"("id") ON DELETE cascade,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now()
|
||||
)
|
||||
`
|
||||
console.log("✅ Created thread_context_items table")
|
||||
|
||||
console.log("\n🎉 All tables created successfully!")
|
||||
|
||||
// List tables
|
||||
const tables = await sql`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name
|
||||
`
|
||||
console.log("\n📋 Tables in database:")
|
||||
for (const t of tables) {
|
||||
console.log(` - ${t.table_name}`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("❌ Error:", err)
|
||||
} finally {
|
||||
await sql.end()
|
||||
}
|
||||
}
|
||||
|
||||
pushSchema()
|
||||
118
packages/web/scripts/schema.sql
Normal file
118
packages/web/scripts/schema.sql
Normal file
@@ -0,0 +1,118 @@
|
||||
-- PlanetScale Postgres Schema
|
||||
-- Run this in PlanetScale's web console if API token doesn't have CREATE permissions
|
||||
|
||||
-- Better-auth tables (camelCase columns)
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" text PRIMARY KEY,
|
||||
"name" text NOT NULL,
|
||||
"email" text NOT NULL UNIQUE,
|
||||
"emailVerified" boolean NOT NULL DEFAULT false,
|
||||
"image" text,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "sessions" (
|
||||
"id" text PRIMARY KEY,
|
||||
"expiresAt" timestamptz NOT NULL,
|
||||
"token" text NOT NULL UNIQUE,
|
||||
"createdAt" timestamptz NOT NULL,
|
||||
"updatedAt" timestamptz NOT NULL,
|
||||
"ipAddress" text,
|
||||
"userAgent" text,
|
||||
"userId" text NOT NULL REFERENCES "users"("id") ON DELETE cascade
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "accounts" (
|
||||
"id" text PRIMARY KEY,
|
||||
"accountId" text NOT NULL,
|
||||
"providerId" text NOT NULL,
|
||||
"userId" text NOT NULL REFERENCES "users"("id") ON DELETE cascade,
|
||||
"accessToken" text,
|
||||
"refreshToken" text,
|
||||
"idToken" text,
|
||||
"accessTokenExpiresAt" timestamptz,
|
||||
"refreshTokenExpiresAt" timestamptz,
|
||||
"scope" text,
|
||||
"password" text,
|
||||
"createdAt" timestamptz NOT NULL,
|
||||
"updatedAt" timestamptz NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "verifications" (
|
||||
"id" text PRIMARY KEY,
|
||||
"identifier" text NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
"expiresAt" timestamptz NOT NULL,
|
||||
"createdAt" timestamptz DEFAULT now(),
|
||||
"updatedAt" timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
-- App tables (snake_case for Electric sync)
|
||||
CREATE TABLE IF NOT EXISTS "chat_threads" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"title" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "chat_messages" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"thread_id" integer NOT NULL REFERENCES "chat_threads"("id") ON DELETE cascade,
|
||||
"role" varchar(32) NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "canvas" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"owner_id" text NOT NULL REFERENCES "users"("id") ON DELETE cascade,
|
||||
"name" text NOT NULL DEFAULT 'Untitled Canvas',
|
||||
"width" integer NOT NULL DEFAULT 1024,
|
||||
"height" integer NOT NULL DEFAULT 1024,
|
||||
"default_model" text NOT NULL DEFAULT 'gemini-2.0-flash-exp-image-generation',
|
||||
"default_style" text NOT NULL DEFAULT 'default',
|
||||
"background_prompt" text,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"updated_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "canvas_images" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"canvas_id" uuid NOT NULL REFERENCES "canvas"("id") ON DELETE cascade,
|
||||
"name" text NOT NULL DEFAULT 'Untitled Image',
|
||||
"prompt" text NOT NULL DEFAULT '',
|
||||
"model_id" text NOT NULL DEFAULT 'gemini-2.0-flash-exp-image-generation',
|
||||
"model_used" text,
|
||||
"style_id" text NOT NULL DEFAULT 'default',
|
||||
"width" integer NOT NULL DEFAULT 512,
|
||||
"height" integer NOT NULL DEFAULT 512,
|
||||
"position" jsonb NOT NULL DEFAULT '{"x": 0, "y": 0}',
|
||||
"rotation" double precision NOT NULL DEFAULT 0,
|
||||
"content_base64" text,
|
||||
"image_url" text,
|
||||
"metadata" jsonb,
|
||||
"branch_parent_id" uuid,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"updated_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "context_items" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"user_id" text NOT NULL REFERENCES "users"("id") ON DELETE cascade,
|
||||
"type" varchar(32) NOT NULL,
|
||||
"url" text,
|
||||
"name" text NOT NULL,
|
||||
"content" text,
|
||||
"refreshing" boolean NOT NULL DEFAULT false,
|
||||
"parent_id" integer,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"updated_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "thread_context_items" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"thread_id" integer NOT NULL REFERENCES "chat_threads"("id") ON DELETE cascade,
|
||||
"context_item_id" integer NOT NULL REFERENCES "context_items"("id") ON DELETE cascade,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
350
packages/web/scripts/seed.ts
Normal file
350
packages/web/scripts/seed.ts
Normal file
@@ -0,0 +1,350 @@
|
||||
import "dotenv/config"
|
||||
import crypto from "node:crypto"
|
||||
import { sql, eq } from "drizzle-orm"
|
||||
import { getDb, getAuthDb } from "../src/db/connection"
|
||||
import {
|
||||
accounts,
|
||||
chat_messages,
|
||||
chat_threads,
|
||||
sessions,
|
||||
users,
|
||||
verifications,
|
||||
} from "../src/db/schema"
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL
|
||||
|
||||
if (!databaseUrl) {
|
||||
throw new Error("DATABASE_URL is required in packages/web/.env")
|
||||
}
|
||||
|
||||
const appDb = getDb(databaseUrl)
|
||||
const authDb = getAuthDb(databaseUrl)
|
||||
|
||||
async function ensureTables() {
|
||||
await authDb.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" text PRIMARY KEY,
|
||||
"name" text NOT NULL,
|
||||
"email" text NOT NULL UNIQUE,
|
||||
"emailVerified" boolean NOT NULL DEFAULT false,
|
||||
"image" text,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
`)
|
||||
|
||||
await authDb.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "sessions" (
|
||||
"id" text PRIMARY KEY,
|
||||
"expiresAt" timestamptz NOT NULL,
|
||||
"token" text NOT NULL UNIQUE,
|
||||
"createdAt" timestamptz NOT NULL,
|
||||
"updatedAt" timestamptz NOT NULL,
|
||||
"ipAddress" text,
|
||||
"userAgent" text,
|
||||
"userId" text NOT NULL REFERENCES "users"("id") ON DELETE cascade
|
||||
);
|
||||
`)
|
||||
|
||||
await authDb.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "accounts" (
|
||||
"id" text PRIMARY KEY,
|
||||
"accountId" text NOT NULL,
|
||||
"providerId" text NOT NULL,
|
||||
"userId" text NOT NULL REFERENCES "users"("id") ON DELETE cascade,
|
||||
"accessToken" text,
|
||||
"refreshToken" text,
|
||||
"idToken" text,
|
||||
"accessTokenExpiresAt" timestamptz,
|
||||
"refreshTokenExpiresAt" timestamptz,
|
||||
"scope" text,
|
||||
"password" text,
|
||||
"createdAt" timestamptz NOT NULL,
|
||||
"updatedAt" timestamptz NOT NULL
|
||||
);
|
||||
`)
|
||||
|
||||
await authDb.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "verifications" (
|
||||
"id" text PRIMARY KEY,
|
||||
"identifier" text NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
"expiresAt" timestamptz NOT NULL,
|
||||
"createdAt" timestamptz DEFAULT now(),
|
||||
"updatedAt" timestamptz DEFAULT now()
|
||||
);
|
||||
`)
|
||||
|
||||
// Backfill camelCase columns when an older snake_case seed created the tables.
|
||||
// Add missing legacy snake_case columns first so COALESCE references are safe.
|
||||
await authDb.execute(sql`
|
||||
ALTER TABLE "users"
|
||||
ADD COLUMN IF NOT EXISTS "email_verified" boolean,
|
||||
ADD COLUMN IF NOT EXISTS "created_at" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "updated_at" timestamptz
|
||||
`)
|
||||
await authDb.execute(sql`
|
||||
ALTER TABLE "sessions"
|
||||
ADD COLUMN IF NOT EXISTS "expires_at" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "created_at" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "updated_at" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "ip_address" text,
|
||||
ADD COLUMN IF NOT EXISTS "user_agent" text,
|
||||
ADD COLUMN IF NOT EXISTS "user_id" text
|
||||
`)
|
||||
await authDb.execute(sql`
|
||||
ALTER TABLE "accounts"
|
||||
ADD COLUMN IF NOT EXISTS "account_id" text,
|
||||
ADD COLUMN IF NOT EXISTS "provider_id" text,
|
||||
ADD COLUMN IF NOT EXISTS "user_id" text,
|
||||
ADD COLUMN IF NOT EXISTS "access_token" text,
|
||||
ADD COLUMN IF NOT EXISTS "refresh_token" text,
|
||||
ADD COLUMN IF NOT EXISTS "id_token" text,
|
||||
ADD COLUMN IF NOT EXISTS "access_token_expires_at" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "refresh_token_expires_at" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "created_at" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "updated_at" timestamptz
|
||||
`)
|
||||
await authDb.execute(sql`
|
||||
ALTER TABLE "verifications"
|
||||
ADD COLUMN IF NOT EXISTS "expires_at" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "created_at" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "updated_at" timestamptz
|
||||
`)
|
||||
|
||||
await authDb.execute(sql`
|
||||
ALTER TABLE "users"
|
||||
ADD COLUMN IF NOT EXISTS "emailVerified" boolean DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS "createdAt" timestamptz DEFAULT now(),
|
||||
ADD COLUMN IF NOT EXISTS "updatedAt" timestamptz DEFAULT now()
|
||||
`)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "users" SET "emailVerified" = COALESCE("emailVerified", "email_verified")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "users" SET "createdAt" = COALESCE("createdAt", "created_at")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "users" SET "updatedAt" = COALESCE("updatedAt", "updated_at")`,
|
||||
)
|
||||
|
||||
await authDb.execute(sql`
|
||||
ALTER TABLE "sessions"
|
||||
ADD COLUMN IF NOT EXISTS "expiresAt" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "token" text,
|
||||
ADD COLUMN IF NOT EXISTS "createdAt" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "updatedAt" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "ipAddress" text,
|
||||
ADD COLUMN IF NOT EXISTS "userAgent" text,
|
||||
ADD COLUMN IF NOT EXISTS "userId" text
|
||||
`)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "sessions" SET "expiresAt" = COALESCE("expiresAt", "expires_at")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "sessions" SET "createdAt" = COALESCE("createdAt", "created_at")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "sessions" SET "updatedAt" = COALESCE("updatedAt", "updated_at")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "sessions" SET "ipAddress" = COALESCE("ipAddress", "ip_address")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "sessions" SET "userAgent" = COALESCE("userAgent", "user_agent")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "sessions" SET "userId" = COALESCE("userId", "user_id")`,
|
||||
)
|
||||
|
||||
await authDb.execute(sql`
|
||||
ALTER TABLE "accounts"
|
||||
ADD COLUMN IF NOT EXISTS "accountId" text,
|
||||
ADD COLUMN IF NOT EXISTS "providerId" text,
|
||||
ADD COLUMN IF NOT EXISTS "userId" text,
|
||||
ADD COLUMN IF NOT EXISTS "accessToken" text,
|
||||
ADD COLUMN IF NOT EXISTS "refreshToken" text,
|
||||
ADD COLUMN IF NOT EXISTS "idToken" text,
|
||||
ADD COLUMN IF NOT EXISTS "accessTokenExpiresAt" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "refreshTokenExpiresAt" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "createdAt" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "updatedAt" timestamptz
|
||||
`)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "accounts" SET "accountId" = COALESCE("accountId", "account_id")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "accounts" SET "providerId" = COALESCE("providerId", "provider_id")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "accounts" SET "userId" = COALESCE("userId", "user_id")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "accounts" SET "accessToken" = COALESCE("accessToken", "access_token")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "accounts" SET "refreshToken" = COALESCE("refreshToken", "refresh_token")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "accounts" SET "idToken" = COALESCE("idToken", "id_token")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "accounts" SET "accessTokenExpiresAt" = COALESCE("accessTokenExpiresAt", "access_token_expires_at")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "accounts" SET "refreshTokenExpiresAt" = COALESCE("refreshTokenExpiresAt", "refresh_token_expires_at")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "accounts" SET "createdAt" = COALESCE("createdAt", "created_at")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "accounts" SET "updatedAt" = COALESCE("updatedAt", "updated_at")`,
|
||||
)
|
||||
|
||||
await authDb.execute(sql`
|
||||
ALTER TABLE "verifications"
|
||||
ADD COLUMN IF NOT EXISTS "expiresAt" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "createdAt" timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS "updatedAt" timestamptz
|
||||
`)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "verifications" SET "expiresAt" = COALESCE("expiresAt", "expires_at")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "verifications" SET "createdAt" = COALESCE("createdAt", "created_at")`,
|
||||
)
|
||||
await authDb.execute(
|
||||
sql`UPDATE "verifications" SET "updatedAt" = COALESCE("updatedAt", "updated_at")`,
|
||||
)
|
||||
|
||||
await appDb.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "chat_threads" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"title" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
`)
|
||||
|
||||
await appDb.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "chat_messages" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"thread_id" integer NOT NULL REFERENCES "chat_threads"("id") ON DELETE cascade,
|
||||
"role" varchar(32) NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
`)
|
||||
|
||||
await appDb.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "context_items" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"user_id" text NOT NULL REFERENCES "users"("id") ON DELETE cascade,
|
||||
"type" varchar(32) NOT NULL,
|
||||
"url" text,
|
||||
"name" text NOT NULL,
|
||||
"content" text,
|
||||
"refreshing" boolean NOT NULL DEFAULT false,
|
||||
"parent_id" integer,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"updated_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
`)
|
||||
|
||||
await appDb.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "thread_context_items" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"thread_id" integer NOT NULL REFERENCES "chat_threads"("id") ON DELETE cascade,
|
||||
"context_item_id" integer NOT NULL REFERENCES "context_items"("id") ON DELETE cascade,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
`)
|
||||
}
|
||||
|
||||
async function seed() {
|
||||
await ensureTables()
|
||||
|
||||
const demoUserId = "demo-user"
|
||||
const demoEmail = "demo@ai.chat"
|
||||
|
||||
await authDb
|
||||
.insert(users)
|
||||
.values({
|
||||
id: demoUserId,
|
||||
name: "Demo User",
|
||||
email: demoEmail,
|
||||
emailVerified: true,
|
||||
image: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.onConflictDoNothing({ target: users.id })
|
||||
|
||||
// Clear any orphaned auth rows for the demo user to keep data tidy
|
||||
await authDb.delete(sessions).where(eq(sessions.userId, demoUserId))
|
||||
await authDb.delete(accounts).where(eq(accounts.userId, demoUserId))
|
||||
await authDb.delete(verifications).where(eq(verifications.identifier, demoEmail))
|
||||
|
||||
// Find or create a chat thread for the demo user
|
||||
const [existingThread] = await appDb
|
||||
.select()
|
||||
.from(chat_threads)
|
||||
.where(eq(chat_threads.user_id, demoUserId))
|
||||
.limit(1)
|
||||
|
||||
const [thread] =
|
||||
existingThread && existingThread.id
|
||||
? [existingThread]
|
||||
: await appDb
|
||||
.insert(chat_threads)
|
||||
.values({
|
||||
title: "Getting started with AI chat",
|
||||
user_id: demoUserId,
|
||||
})
|
||||
.returning()
|
||||
|
||||
const threadId = thread.id
|
||||
|
||||
await appDb
|
||||
.delete(chat_messages)
|
||||
.where(eq(chat_messages.thread_id, threadId))
|
||||
|
||||
const starterMessages = [
|
||||
{
|
||||
role: "user",
|
||||
content: "How do I get reliable AI chat responses from this app?",
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content:
|
||||
"Each thread keeps your message history. You can seed demos like this one, or stream responses from your AI provider. Try adding more messages to this thread.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: "Can I hook this up to my own model API?",
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content:
|
||||
"Yes. Point your server-side handler at your model endpoint and persist messages into the database. Electric can sync them live to the client.",
|
||||
},
|
||||
]
|
||||
|
||||
await appDb.insert(chat_messages).values(
|
||||
starterMessages.map((msg) => ({
|
||||
thread_id: threadId,
|
||||
role: msg.role,
|
||||
content: msg.content,
|
||||
created_at: new Date(),
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
seed()
|
||||
.then(() => {
|
||||
console.log("Seed complete: demo user and chat thread ready.")
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user