migration to postgres
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
const envPath = resolve(__dirname, '../../../.env');
|
||||
try {
|
||||
const text = readFileSync(envPath, 'utf-8');
|
||||
for (const line of text.split('\n')) {
|
||||
const match = line.match(/^(\w+)=(.*)$/);
|
||||
if (match) {
|
||||
const [, key, val] = match;
|
||||
if (!process.env[key!]) process.env[key!] = val!.replace(/^["']|["']$/g, '');
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/schema/index.ts',
|
||||
out: './migrations',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.POSTGRES_URL!,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,339 @@
|
||||
CREATE TABLE "passkey_challenges" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"origin" text NOT NULL,
|
||||
"challenge" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "passkeys" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"origin" text,
|
||||
"credential_id" text,
|
||||
"public_key" text,
|
||||
"counter" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "token_blacklist" (
|
||||
"jti" text PRIMARY KEY NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"password" text,
|
||||
"role" text DEFAULT 'Member' NOT NULL,
|
||||
"status" text DEFAULT 'Unverified' NOT NULL,
|
||||
"name" text,
|
||||
"username" text,
|
||||
"avatar" text,
|
||||
"password_changed_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "users_email_unique" UNIQUE("email"),
|
||||
CONSTRAINT "users_username_unique" UNIQUE("username")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "dock_configs" (
|
||||
"user_id" integer PRIMARY KEY NOT NULL,
|
||||
"paths" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_integrations" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"provider" text NOT NULL,
|
||||
"access_token" text,
|
||||
"refresh_token" text,
|
||||
"expires_at" timestamp with time zone,
|
||||
"profile" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_user_integrations_user_provider" UNIQUE("user_id","provider")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_settings" (
|
||||
"user_id" integer PRIMARY KEY NOT NULL,
|
||||
"settings" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_state" (
|
||||
"user_id" integer PRIMARY KEY NOT NULL,
|
||||
"state" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "chat_groups" (
|
||||
"slug" text PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "chat_messages" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"session_id" text NOT NULL,
|
||||
"role" text NOT NULL,
|
||||
"text" text,
|
||||
"model" text,
|
||||
"tool_name" text,
|
||||
"tool_input" jsonb,
|
||||
"tool_call_id" text,
|
||||
"output" text,
|
||||
"is_error" boolean DEFAULT false,
|
||||
"cost_input_tokens" integer,
|
||||
"cost_output_tokens" integer,
|
||||
"cost_total_usd" numeric(12, 6),
|
||||
"sort_order" integer NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "chat_sessions" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"context" text DEFAULT 'chat' NOT NULL,
|
||||
"context_id" text,
|
||||
"title" text DEFAULT 'New Chat' NOT NULL,
|
||||
"model" text,
|
||||
"cwd" text,
|
||||
"thinking" text,
|
||||
"archived" boolean DEFAULT false NOT NULL,
|
||||
"group_slug" text,
|
||||
"message_count" integer DEFAULT 0 NOT NULL,
|
||||
"cost_input_tokens" integer DEFAULT 0 NOT NULL,
|
||||
"cost_output_tokens" integer DEFAULT 0 NOT NULL,
|
||||
"cost_total_usd" numeric(12, 6) DEFAULT '0' NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "projects" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"slug" text NOT NULL,
|
||||
"meta" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"layout" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"terminals" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_projects_user_slug" UNIQUE("user_id","slug")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "screens" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"layout" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_screens_user_name" UNIQUE("user_id","name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "workspaces" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"config" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"layout" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"terminals" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"host_terminals" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"sort_order" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_workspaces_user_id" UNIQUE("user_id","id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "extensions" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"scope" text NOT NULL,
|
||||
"user_id" integer,
|
||||
"dir_name" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"implementation" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_extensions_scope_user_dir" UNIQUE("scope","user_id","dir_name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "item_chats" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"item_type" text NOT NULL,
|
||||
"item_id" integer NOT NULL,
|
||||
"messages" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_item_chats_type_item" UNIQUE("item_type","item_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "processes" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"scope" text NOT NULL,
|
||||
"user_id" integer,
|
||||
"dir_name" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"body" text,
|
||||
"version" integer DEFAULT 1 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_processes_scope_user_dir" UNIQUE("scope","user_id","dir_name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "resources" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"scope" text NOT NULL,
|
||||
"dir_name" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"body" text,
|
||||
"version" integer DEFAULT 1 NOT NULL,
|
||||
"config" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_resources_scope_dir" UNIQUE("scope","dir_name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "skills" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"scope" text NOT NULL,
|
||||
"user_id" integer,
|
||||
"dir_name" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"body" text,
|
||||
"version" integer DEFAULT 1 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_skills_scope_user_dir" UNIQUE("scope","user_id","dir_name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "tasks" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"scope" text NOT NULL,
|
||||
"user_id" integer,
|
||||
"dir_name" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"body" text,
|
||||
"version" integer DEFAULT 1 NOT NULL,
|
||||
"tags" jsonb,
|
||||
"tools" jsonb,
|
||||
"skills" jsonb,
|
||||
"inputs" jsonb,
|
||||
"outputs" jsonb,
|
||||
"dependencies" jsonb,
|
||||
"config" jsonb,
|
||||
"trigger" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_tasks_scope_user_dir" UNIQUE("scope","user_id","dir_name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "tools" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"scope" text NOT NULL,
|
||||
"user_id" integer,
|
||||
"dir_name" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"label" text,
|
||||
"description" text,
|
||||
"body" text,
|
||||
"version" integer DEFAULT 1 NOT NULL,
|
||||
"language" text,
|
||||
"inputs" jsonb,
|
||||
"implementation" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_tools_scope_user_dir" UNIQUE("scope","user_id","dir_name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "queue_jobs" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"lane" text NOT NULL,
|
||||
"type" text NOT NULL,
|
||||
"status" text DEFAULT 'queued' NOT NULL,
|
||||
"current_step" integer DEFAULT 0 NOT NULL,
|
||||
"steps" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"meta" jsonb,
|
||||
"error" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"started_at" timestamp with time zone,
|
||||
"completed_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "task_logs" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"task_name" text NOT NULL,
|
||||
"task_dir_name" text NOT NULL,
|
||||
"entry_name" text NOT NULL,
|
||||
"entry_type" text NOT NULL,
|
||||
"provider" text NOT NULL,
|
||||
"model" text NOT NULL,
|
||||
"is_error" boolean DEFAULT false NOT NULL,
|
||||
"messages" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"started_at" timestamp with time zone NOT NULL,
|
||||
"completed_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "terminal_containers" (
|
||||
"user_id" integer PRIMARY KEY NOT NULL,
|
||||
"docker_id" text NOT NULL,
|
||||
"port" integer NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "server_config" (
|
||||
"key" text PRIMARY KEY NOT NULL,
|
||||
"value" jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "passkey_challenges" ADD CONSTRAINT "passkey_challenges_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "passkeys" ADD CONSTRAINT "passkeys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "dock_configs" ADD CONSTRAINT "dock_configs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_integrations" ADD CONSTRAINT "user_integrations_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_settings" ADD CONSTRAINT "user_settings_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_state" ADD CONSTRAINT "user_state_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "chat_groups" ADD CONSTRAINT "chat_groups_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "chat_messages" ADD CONSTRAINT "chat_messages_session_id_chat_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."chat_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "chat_sessions" ADD CONSTRAINT "chat_sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "screens" ADD CONSTRAINT "screens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workspaces" ADD CONSTRAINT "workspaces_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "extensions" ADD CONSTRAINT "extensions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "processes" ADD CONSTRAINT "processes_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "skills" ADD CONSTRAINT "skills_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "tools" ADD CONSTRAINT "tools_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "queue_jobs" ADD CONSTRAINT "queue_jobs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "task_logs" ADD CONSTRAINT "task_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "terminal_containers" ADD CONSTRAINT "terminal_containers_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "idx_token_blacklist_expires" ON "token_blacklist" USING btree ("expires_at");--> statement-breakpoint
|
||||
CREATE INDEX "idx_chat_groups_user" ON "chat_groups" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "idx_messages_session_order" ON "chat_messages" USING btree ("session_id","sort_order");--> statement-breakpoint
|
||||
CREATE INDEX "idx_sessions_user_context" ON "chat_sessions" USING btree ("user_id","context");--> statement-breakpoint
|
||||
CREATE INDEX "idx_sessions_user_created" ON "chat_sessions" USING btree ("user_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX "idx_sessions_user_group" ON "chat_sessions" USING btree ("user_id","group_slug");--> statement-breakpoint
|
||||
CREATE INDEX "idx_extensions_scope" ON "extensions" USING btree ("scope");--> statement-breakpoint
|
||||
CREATE INDEX "idx_extensions_user" ON "extensions" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "idx_item_chats_type_item" ON "item_chats" USING btree ("item_type","item_id");--> statement-breakpoint
|
||||
CREATE INDEX "idx_processes_scope" ON "processes" USING btree ("scope");--> statement-breakpoint
|
||||
CREATE INDEX "idx_processes_user" ON "processes" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "idx_resources_scope" ON "resources" USING btree ("scope");--> statement-breakpoint
|
||||
CREATE INDEX "idx_skills_scope" ON "skills" USING btree ("scope");--> statement-breakpoint
|
||||
CREATE INDEX "idx_skills_user" ON "skills" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "idx_tasks_scope" ON "tasks" USING btree ("scope");--> statement-breakpoint
|
||||
CREATE INDEX "idx_tasks_user" ON "tasks" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "idx_tools_scope" ON "tools" USING btree ("scope");--> statement-breakpoint
|
||||
CREATE INDEX "idx_tools_user" ON "tools" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "idx_queue_jobs_status_lane" ON "queue_jobs" USING btree ("status","lane");--> statement-breakpoint
|
||||
CREATE INDEX "idx_queue_jobs_user" ON "queue_jobs" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "idx_task_logs_user_started" ON "task_logs" USING btree ("user_id","started_at");
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1772121115932,
|
||||
"tag": "0000_clammy_meteorite",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,7 +5,15 @@
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./types": "./src/types.ts"
|
||||
"./types": "./src/types.ts",
|
||||
"./db": "./src/db.ts",
|
||||
"./schema": "./src/schema/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"generate": "drizzle-kit generate --config=drizzle.config.ts",
|
||||
"push": "drizzle-kit push --config=drizzle.config.ts",
|
||||
"migrate": "drizzle-kit migrate --config=drizzle.config.ts",
|
||||
"studio": "drizzle-kit studio --config=drizzle.config.ts"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
import * as schema from './schema';
|
||||
|
||||
const { POSTGRES_URL } = process.env;
|
||||
|
||||
if (!POSTGRES_URL) {
|
||||
throw new Error('POSTGRES_URL environment variable is required');
|
||||
}
|
||||
|
||||
const client = postgres(POSTGRES_URL);
|
||||
export const db = drizzle(client, { schema });
|
||||
@@ -0,0 +1,112 @@
|
||||
import { eq, and, lt, sql } from 'drizzle-orm';
|
||||
import { db } from '../db';
|
||||
import { users, passkeys, passkeyChallenges, tokenBlacklist } from '../schema';
|
||||
import type { UserSelect, UserInsert, PasskeySelect, PasskeyInsert } from '../types';
|
||||
|
||||
// ── Users ──
|
||||
|
||||
export async function getUsers(): Promise<UserSelect[]> {
|
||||
return db.select().from(users);
|
||||
}
|
||||
|
||||
export async function getUserById(id: number): Promise<UserSelect | undefined> {
|
||||
const [user] = await db.select().from(users).where(eq(users.id, id));
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function getUserByEmail(email: string): Promise<UserSelect | undefined> {
|
||||
const [user] = await db.select().from(users).where(eq(users.email, email));
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function getUserCount(): Promise<number> {
|
||||
const [result] = await db.select({ count: sql<number>`count(*)::int` }).from(users);
|
||||
return result?.count ?? 0;
|
||||
}
|
||||
|
||||
export async function createUser(data: UserInsert): Promise<UserSelect> {
|
||||
const [user] = await db.insert(users).values(data).returning();
|
||||
return user!;
|
||||
}
|
||||
|
||||
export async function updateUser(id: number, data: Partial<Omit<UserSelect, 'id'>>): Promise<UserSelect | undefined> {
|
||||
const [user] = await db.update(users).set({ ...data, updatedAt: new Date() }).where(eq(users.id, id)).returning();
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function deleteUser(id: number): Promise<boolean> {
|
||||
const result = await db.delete(users).where(eq(users.id, id)).returning({ id: users.id });
|
||||
return result.length > 0;
|
||||
}
|
||||
|
||||
// ── Passkeys ──
|
||||
|
||||
export async function getPasskeysByUserId(userId: number): Promise<PasskeySelect[]> {
|
||||
return db.select().from(passkeys).where(eq(passkeys.userId, userId));
|
||||
}
|
||||
|
||||
export async function getPasskeysByUserIdAndOrigin(userId: number, origin: string): Promise<PasskeySelect[]> {
|
||||
return db.select().from(passkeys).where(and(eq(passkeys.userId, userId), eq(passkeys.origin, origin)));
|
||||
}
|
||||
|
||||
export async function getPasskeyByCredentialId(userId: number, credentialId: string): Promise<PasskeySelect | undefined> {
|
||||
const [passkey] = await db
|
||||
.select()
|
||||
.from(passkeys)
|
||||
.where(and(eq(passkeys.userId, userId), eq(passkeys.credentialId, credentialId)));
|
||||
return passkey;
|
||||
}
|
||||
|
||||
export async function createPasskey(data: PasskeyInsert): Promise<PasskeySelect> {
|
||||
const [passkey] = await db.insert(passkeys).values(data).returning();
|
||||
return passkey!;
|
||||
}
|
||||
|
||||
export async function updatePasskey(id: number, data: Partial<Omit<PasskeySelect, 'id'>>): Promise<PasskeySelect | undefined> {
|
||||
const [passkey] = await db.update(passkeys).set(data).where(eq(passkeys.id, id)).returning();
|
||||
return passkey;
|
||||
}
|
||||
|
||||
// ── Passkey Challenges ──
|
||||
|
||||
export async function storeChallenge(userId: number, origin: string, challenge: string, ttlMs: number) {
|
||||
const expiresAt = new Date(Date.now() + ttlMs);
|
||||
|
||||
// Upsert: delete existing challenge for this user+origin, then insert
|
||||
await db.delete(passkeyChallenges).where(and(eq(passkeyChallenges.userId, userId), eq(passkeyChallenges.origin, origin)));
|
||||
await db.insert(passkeyChallenges).values({ userId, origin, challenge, expiresAt });
|
||||
}
|
||||
|
||||
export async function consumeChallenge(userId: number, origin: string): Promise<string | null> {
|
||||
const now = new Date();
|
||||
|
||||
// Clean up expired challenges
|
||||
await db.delete(passkeyChallenges).where(lt(passkeyChallenges.expiresAt, now));
|
||||
|
||||
// Find and delete the matching challenge
|
||||
const [entry] = await db
|
||||
.delete(passkeyChallenges)
|
||||
.where(and(eq(passkeyChallenges.userId, userId), eq(passkeyChallenges.origin, origin)))
|
||||
.returning();
|
||||
|
||||
if (!entry) return null;
|
||||
if (entry.expiresAt < now) return null;
|
||||
return entry.challenge;
|
||||
}
|
||||
|
||||
// ── Token Blacklist ──
|
||||
|
||||
export async function blacklistToken(jti: string, expiresAt: number) {
|
||||
// expiresAt comes as Unix seconds from JWT exp claim
|
||||
const expiresDate = new Date(expiresAt * 1000);
|
||||
await db.insert(tokenBlacklist).values({ jti, expiresAt: expiresDate }).onConflictDoNothing();
|
||||
}
|
||||
|
||||
export async function isTokenBlacklisted(jti: string): Promise<boolean> {
|
||||
const [entry] = await db.select({ jti: tokenBlacklist.jti }).from(tokenBlacklist).where(eq(tokenBlacklist.jti, jti));
|
||||
return !!entry;
|
||||
}
|
||||
|
||||
export async function cleanupExpiredTokens() {
|
||||
await db.delete(tokenBlacklist).where(lt(tokenBlacklist.expiresAt, new Date()));
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { db } from '../db';
|
||||
import { serverIntegrations, userIntegrations } from '../schema';
|
||||
import type { ServerIntegrationSelect, UserIntegrationSelect } from '../types';
|
||||
|
||||
// ── Server Integrations ──
|
||||
|
||||
export async function getServerIntegrations(): Promise<ServerIntegrationSelect[]> {
|
||||
return db.select().from(serverIntegrations);
|
||||
}
|
||||
|
||||
export async function getServerIntegration(provider: string): Promise<ServerIntegrationSelect | undefined> {
|
||||
const [row] = await db.select().from(serverIntegrations).where(eq(serverIntegrations.provider, provider));
|
||||
return row;
|
||||
}
|
||||
|
||||
export async function upsertServerIntegration(provider: string, config: Record<string, unknown>, enabled = true): Promise<ServerIntegrationSelect> {
|
||||
const [row] = await db
|
||||
.insert(serverIntegrations)
|
||||
.values({ provider, config, enabled, updatedAt: new Date() })
|
||||
.onConflictDoUpdate({
|
||||
target: serverIntegrations.provider,
|
||||
set: { config, enabled, updatedAt: new Date() },
|
||||
})
|
||||
.returning();
|
||||
return row!;
|
||||
}
|
||||
|
||||
export async function deleteServerIntegration(provider: string): Promise<boolean> {
|
||||
const result = await db.delete(serverIntegrations).where(eq(serverIntegrations.provider, provider)).returning({ id: serverIntegrations.id });
|
||||
return result.length > 0;
|
||||
}
|
||||
|
||||
// ── User Integrations ──
|
||||
|
||||
export async function getUserIntegrations(userId: number): Promise<UserIntegrationSelect[]> {
|
||||
return db.select().from(userIntegrations).where(eq(userIntegrations.userId, userId));
|
||||
}
|
||||
|
||||
export async function getUserIntegration(userId: number, provider: string): Promise<UserIntegrationSelect | undefined> {
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(userIntegrations)
|
||||
.where(and(eq(userIntegrations.userId, userId), eq(userIntegrations.provider, provider)));
|
||||
return row;
|
||||
}
|
||||
|
||||
type UpsertUserIntegrationParams = {
|
||||
userId: number;
|
||||
provider: string;
|
||||
serverIntegrationId?: number | null;
|
||||
config: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export async function upsertUserIntegration({ userId, provider, serverIntegrationId, config }: UpsertUserIntegrationParams): Promise<UserIntegrationSelect> {
|
||||
const [row] = await db
|
||||
.insert(userIntegrations)
|
||||
.values({ userId, provider, serverIntegrationId: serverIntegrationId ?? null, config, updatedAt: new Date() })
|
||||
.onConflictDoUpdate({
|
||||
target: [userIntegrations.userId, userIntegrations.provider],
|
||||
set: { config, serverIntegrationId: serverIntegrationId ?? null, updatedAt: new Date() },
|
||||
})
|
||||
.returning();
|
||||
return row!;
|
||||
}
|
||||
|
||||
export async function deleteUserIntegration(userId: number, provider: string): Promise<boolean> {
|
||||
const result = await db
|
||||
.delete(userIntegrations)
|
||||
.where(and(eq(userIntegrations.userId, userId), eq(userIntegrations.provider, provider)))
|
||||
.returning({ id: userIntegrations.id });
|
||||
return result.length > 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { db } from '../db';
|
||||
import { serverConfig } from '../schema';
|
||||
|
||||
const SETTINGS_KEY = 'server-settings';
|
||||
|
||||
export async function readServerSettings(): Promise<Record<string, unknown>> {
|
||||
const [row] = await db.select().from(serverConfig).where(eq(serverConfig.key, SETTINGS_KEY));
|
||||
return (row?.value as Record<string, unknown>) ?? {};
|
||||
}
|
||||
|
||||
export async function writeServerSettings(settings: Record<string, unknown>): Promise<void> {
|
||||
await db
|
||||
.insert(serverConfig)
|
||||
.values({ key: SETTINGS_KEY, value: settings, updatedAt: new Date() })
|
||||
.onConflictDoUpdate({
|
||||
target: serverConfig.key,
|
||||
set: { value: settings, updatedAt: new Date() },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { pgTable, serial, text, integer, timestamp, jsonb, index, unique } from 'drizzle-orm/pg-core';
|
||||
import { users } from './auth';
|
||||
|
||||
// ── Shared columns pattern ──
|
||||
// Each table has: id, scope, userId, dirName, name, description, body, version, timestamps
|
||||
// Type-specific columns are added per table
|
||||
|
||||
// ── Tasks ──
|
||||
// Complex frontmatter: inputs, outputs, dependencies, triggers, config, tags, tools, skills
|
||||
|
||||
export const tasks = pgTable('tasks', {
|
||||
id: serial('id').primaryKey(),
|
||||
scope: text('scope').notNull(),
|
||||
userId: integer('user_id').references(() => users.id, { onDelete: 'cascade' }),
|
||||
dirName: text('dir_name').notNull(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
body: text('body'),
|
||||
version: integer('version').notNull().default(1),
|
||||
tags: jsonb('tags').$type<string[]>(),
|
||||
tools: jsonb('tools').$type<string[]>(),
|
||||
skills: jsonb('skills').$type<string[]>(),
|
||||
inputs: jsonb('inputs'),
|
||||
outputs: jsonb('outputs'),
|
||||
dependencies: jsonb('dependencies'),
|
||||
config: jsonb('config'),
|
||||
trigger: jsonb('trigger'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_tasks_scope_user_dir').on(table.scope, table.userId, table.dirName),
|
||||
index('idx_tasks_scope').on(table.scope),
|
||||
index('idx_tasks_user').on(table.userId),
|
||||
]);
|
||||
|
||||
// ── Skills ──
|
||||
// Minimal frontmatter: name, description only. Rich markdown body.
|
||||
|
||||
export const skills = pgTable('skills', {
|
||||
id: serial('id').primaryKey(),
|
||||
scope: text('scope').notNull(),
|
||||
userId: integer('user_id').references(() => users.id, { onDelete: 'cascade' }),
|
||||
dirName: text('dir_name').notNull(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
body: text('body'),
|
||||
version: integer('version').notNull().default(1),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_skills_scope_user_dir').on(table.scope, table.userId, table.dirName),
|
||||
index('idx_skills_scope').on(table.scope),
|
||||
index('idx_skills_user').on(table.userId),
|
||||
]);
|
||||
|
||||
// ── Processes ──
|
||||
// Same shape as skills. Represents documented workflows.
|
||||
|
||||
export const processes = pgTable('processes', {
|
||||
id: serial('id').primaryKey(),
|
||||
scope: text('scope').notNull(),
|
||||
userId: integer('user_id').references(() => users.id, { onDelete: 'cascade' }),
|
||||
dirName: text('dir_name').notNull(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
body: text('body'),
|
||||
version: integer('version').notNull().default(1),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_processes_scope_user_dir').on(table.scope, table.userId, table.dirName),
|
||||
index('idx_processes_scope').on(table.scope),
|
||||
index('idx_processes_user').on(table.userId),
|
||||
]);
|
||||
|
||||
// ── Resources ──
|
||||
// Has separate config (key-value for external service settings). No user scope.
|
||||
|
||||
export const resources = pgTable('resources', {
|
||||
id: serial('id').primaryKey(),
|
||||
scope: text('scope').notNull(),
|
||||
dirName: text('dir_name').notNull(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
body: text('body'),
|
||||
version: integer('version').notNull().default(1),
|
||||
config: jsonb('config').$type<Record<string, string>>().notNull().default({}),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_resources_scope_dir').on(table.scope, table.dirName),
|
||||
index('idx_resources_scope').on(table.scope),
|
||||
]);
|
||||
|
||||
// ── Tools ──
|
||||
// Has implementation code, language, structured input params, label.
|
||||
|
||||
export const tools = pgTable('tools', {
|
||||
id: serial('id').primaryKey(),
|
||||
scope: text('scope').notNull(),
|
||||
userId: integer('user_id').references(() => users.id, { onDelete: 'cascade' }),
|
||||
dirName: text('dir_name').notNull(),
|
||||
name: text('name').notNull(),
|
||||
label: text('label'),
|
||||
description: text('description'),
|
||||
body: text('body'),
|
||||
version: integer('version').notNull().default(1),
|
||||
language: text('language'),
|
||||
inputs: jsonb('inputs'),
|
||||
implementation: text('implementation'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_tools_scope_user_dir').on(table.scope, table.userId, table.dirName),
|
||||
index('idx_tools_scope').on(table.scope),
|
||||
index('idx_tools_user').on(table.userId),
|
||||
]);
|
||||
|
||||
// ── Extensions ──
|
||||
// Code-only, no markdown, no chat. Just implementation.
|
||||
|
||||
export const extensions = pgTable('extensions', {
|
||||
id: serial('id').primaryKey(),
|
||||
scope: text('scope').notNull(),
|
||||
userId: integer('user_id').references(() => users.id, { onDelete: 'cascade' }),
|
||||
dirName: text('dir_name').notNull(),
|
||||
name: text('name').notNull(),
|
||||
implementation: text('implementation'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_extensions_scope_user_dir').on(table.scope, table.userId, table.dirName),
|
||||
index('idx_extensions_scope').on(table.scope),
|
||||
index('idx_extensions_user').on(table.userId),
|
||||
]);
|
||||
|
||||
// ── Item Chats ──
|
||||
// Chat history for tasks, skills, processes, resources, tools.
|
||||
// Uses polymorphic reference (item_type + item_id) instead of per-table FKs.
|
||||
|
||||
export const itemChats = pgTable('item_chats', {
|
||||
id: text('id').primaryKey(),
|
||||
itemType: text('item_type').notNull(),
|
||||
itemId: integer('item_id').notNull(),
|
||||
messages: jsonb('messages').notNull().default([]),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_item_chats_type_item').on(table.itemType, table.itemId),
|
||||
index('idx_item_chats_type_item').on(table.itemType, table.itemId),
|
||||
]);
|
||||
@@ -0,0 +1,41 @@
|
||||
import { pgTable, serial, text, integer, timestamp, index } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
email: text('email').notNull().unique(),
|
||||
password: text('password'),
|
||||
role: text('role', { enum: ['Member', 'Admin', 'Owner', 'Super Admin'] }).notNull().default('Member'),
|
||||
status: text('status', { enum: ['Unverified', 'Active', 'Prospect', 'Invited', 'Blocked', 'Banned', 'Deleted'] }).notNull().default('Unverified'),
|
||||
name: text('name'),
|
||||
username: text('username').unique(),
|
||||
avatar: text('avatar'),
|
||||
passwordChangedAt: timestamp('password_changed_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const passkeys = pgTable('passkeys', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
origin: text('origin'),
|
||||
credentialId: text('credential_id'),
|
||||
publicKey: text('public_key'),
|
||||
counter: integer('counter').notNull().default(0),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const passkeyChallenges = pgTable('passkey_challenges', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
origin: text('origin').notNull(),
|
||||
challenge: text('challenge').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||
});
|
||||
|
||||
export const tokenBlacklist = pgTable('token_blacklist', {
|
||||
jti: text('jti').primaryKey(),
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||
}, (table) => [
|
||||
index('idx_token_blacklist_expires').on(table.expiresAt),
|
||||
]);
|
||||
@@ -0,0 +1,56 @@
|
||||
import { pgTable, text, integer, boolean, timestamp, jsonb, index, numeric } from 'drizzle-orm/pg-core';
|
||||
import { users } from './auth';
|
||||
|
||||
export const chatSessions = pgTable('chat_sessions', {
|
||||
id: text('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
context: text('context').notNull().default('chat'),
|
||||
contextId: text('context_id'),
|
||||
title: text('title').notNull().default('New Chat'),
|
||||
model: text('model'),
|
||||
cwd: text('cwd'),
|
||||
thinking: text('thinking'),
|
||||
archived: boolean('archived').notNull().default(false),
|
||||
groupSlug: text('group_slug'),
|
||||
messageCount: integer('message_count').notNull().default(0),
|
||||
costInputTokens: integer('cost_input_tokens').notNull().default(0),
|
||||
costOutputTokens: integer('cost_output_tokens').notNull().default(0),
|
||||
costTotalUsd: numeric('cost_total_usd', { precision: 12, scale: 6 }).notNull().default('0'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
index('idx_sessions_user_context').on(table.userId, table.context),
|
||||
index('idx_sessions_user_created').on(table.userId, table.createdAt),
|
||||
index('idx_sessions_user_group').on(table.userId, table.groupSlug),
|
||||
]);
|
||||
|
||||
export const chatMessages = pgTable('chat_messages', {
|
||||
id: text('id').primaryKey(),
|
||||
sessionId: text('session_id').notNull().references(() => chatSessions.id, { onDelete: 'cascade' }),
|
||||
role: text('role').notNull(),
|
||||
text: text('text'),
|
||||
model: text('model'),
|
||||
toolName: text('tool_name'),
|
||||
toolInput: jsonb('tool_input'),
|
||||
toolCallId: text('tool_call_id'),
|
||||
output: text('output'),
|
||||
isError: boolean('is_error').default(false),
|
||||
costInputTokens: integer('cost_input_tokens'),
|
||||
costOutputTokens: integer('cost_output_tokens'),
|
||||
costTotalUsd: numeric('cost_total_usd', { precision: 12, scale: 6 }),
|
||||
sortOrder: integer('sort_order').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
index('idx_messages_session_order').on(table.sessionId, table.sortOrder),
|
||||
]);
|
||||
|
||||
export const chatGroups = pgTable('chat_groups', {
|
||||
slug: text('slug').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
index('idx_chat_groups_user').on(table.userId),
|
||||
]);
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './auth';
|
||||
export * from './user-data';
|
||||
export * from './chat';
|
||||
export * from './workspaces';
|
||||
export * from './agent-items';
|
||||
export * from './operations';
|
||||
export * from './server';
|
||||
@@ -0,0 +1,44 @@
|
||||
import { pgTable, serial, text, integer, boolean, timestamp, jsonb, index } from 'drizzle-orm/pg-core';
|
||||
import { users } from './auth';
|
||||
|
||||
export const taskLogs = pgTable('task_logs', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
taskName: text('task_name').notNull(),
|
||||
taskDirName: text('task_dir_name').notNull(),
|
||||
entryName: text('entry_name').notNull(),
|
||||
entryType: text('entry_type').notNull(),
|
||||
provider: text('provider').notNull(),
|
||||
model: text('model').notNull(),
|
||||
isError: boolean('is_error').notNull().default(false),
|
||||
messages: jsonb('messages').notNull().default([]),
|
||||
startedAt: timestamp('started_at', { withTimezone: true }).notNull(),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
}, (table) => [
|
||||
index('idx_task_logs_user_started').on(table.userId, table.startedAt),
|
||||
]);
|
||||
|
||||
export const queueJobs = pgTable('queue_jobs', {
|
||||
id: text('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
lane: text('lane').notNull(),
|
||||
type: text('type').notNull(),
|
||||
status: text('status', { enum: ['queued', 'running', 'completed', 'failed', 'cancelled'] }).notNull().default('queued'),
|
||||
currentStep: integer('current_step').notNull().default(0),
|
||||
steps: jsonb('steps').notNull().default([]),
|
||||
meta: jsonb('meta'),
|
||||
error: text('error'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
startedAt: timestamp('started_at', { withTimezone: true }),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
}, (table) => [
|
||||
index('idx_queue_jobs_status_lane').on(table.status, table.lane),
|
||||
index('idx_queue_jobs_user').on(table.userId),
|
||||
]);
|
||||
|
||||
export const terminalContainers = pgTable('terminal_containers', {
|
||||
userId: integer('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }),
|
||||
dockerId: text('docker_id').notNull(),
|
||||
port: integer('port').notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { pgTable, serial, boolean, text, timestamp, jsonb } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const serverConfig = pgTable('server_config', {
|
||||
key: text('key').primaryKey(),
|
||||
value: jsonb('value').notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const serverIntegrations = pgTable('server_integrations', {
|
||||
id: serial('id').primaryKey(),
|
||||
provider: text('provider').notNull().unique(),
|
||||
enabled: boolean('enabled').notNull().default(true),
|
||||
config: jsonb('config').notNull().default({}),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { pgTable, serial, integer, text, timestamp, jsonb, unique } from 'drizzle-orm/pg-core';
|
||||
import { users } from './auth';
|
||||
import { serverIntegrations } from './server';
|
||||
|
||||
export const userSettings = pgTable('user_settings', {
|
||||
userId: integer('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }),
|
||||
settings: jsonb('settings').notNull().default({}),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const userState = pgTable('user_state', {
|
||||
userId: integer('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }),
|
||||
state: jsonb('state').notNull().default({}),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const userIntegrations = pgTable('user_integrations', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
provider: text('provider').notNull(),
|
||||
serverIntegrationId: integer('server_integration_id').references(() => serverIntegrations.id, { onDelete: 'set null' }),
|
||||
config: jsonb('config').notNull().default({}),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_user_integrations_user_provider').on(table.userId, table.provider),
|
||||
]);
|
||||
|
||||
export const dockConfigs = pgTable('dock_configs', {
|
||||
userId: integer('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }),
|
||||
paths: jsonb('paths').notNull().default([]),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { pgTable, serial, text, integer, timestamp, jsonb, unique } from 'drizzle-orm/pg-core';
|
||||
import { users } from './auth';
|
||||
|
||||
export const workspaces = pgTable('workspaces', {
|
||||
id: text('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
config: jsonb('config').notNull().default({}),
|
||||
layout: jsonb('layout').notNull().default([]),
|
||||
terminals: jsonb('terminals').notNull().default([]),
|
||||
hostTerminals: jsonb('host_terminals').notNull().default([]),
|
||||
sortOrder: integer('sort_order').notNull().default(0),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_workspaces_user_id').on(table.userId, table.id),
|
||||
]);
|
||||
|
||||
export const screens = pgTable('screens', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
layout: jsonb('layout').notNull().default([]),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_screens_user_name').on(table.userId, table.name),
|
||||
]);
|
||||
|
||||
export const projects = pgTable('projects', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
slug: text('slug').notNull(),
|
||||
meta: jsonb('meta').notNull().default({}),
|
||||
layout: jsonb('layout').notNull().default([]),
|
||||
terminals: jsonb('terminals').notNull().default([]),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique('uq_projects_user_slug').on(table.userId, table.slug),
|
||||
]);
|
||||
@@ -1,50 +1,114 @@
|
||||
import type { USER_ROLES, USER_STATUSES } from 'definitions';
|
||||
import type * as Schema from './schema';
|
||||
|
||||
// Auth
|
||||
export type UserSelect = {
|
||||
id: number;
|
||||
email: string;
|
||||
password: string | null;
|
||||
role: (typeof USER_ROLES)[number] | null;
|
||||
status: (typeof USER_STATUSES)[number] | null;
|
||||
name: string | null;
|
||||
username: string | null;
|
||||
avatar: string | null;
|
||||
passwordChangedAt: number | null;
|
||||
};
|
||||
|
||||
export type UserInsert = {
|
||||
email: string;
|
||||
password?: string | null;
|
||||
role?: (typeof USER_ROLES)[number] | null;
|
||||
status?: (typeof USER_STATUSES)[number] | null;
|
||||
name?: string | null;
|
||||
username?: string | null;
|
||||
avatar?: string | null;
|
||||
passwordChangedAt?: number | null;
|
||||
};
|
||||
// ── Auth ──
|
||||
|
||||
export type UserSelect = typeof Schema.users.$inferSelect;
|
||||
export type UserInsert = typeof Schema.users.$inferInsert;
|
||||
export type User = UserSelect & {
|
||||
passkeys: Passkey[];
|
||||
};
|
||||
|
||||
export type PasskeySelect = {
|
||||
id: number;
|
||||
email: string;
|
||||
origin: string | null;
|
||||
credentialId: string | null;
|
||||
publicKey: string | null;
|
||||
counter: number;
|
||||
};
|
||||
|
||||
export type PasskeyInsert = {
|
||||
email: string;
|
||||
origin?: string | null;
|
||||
credentialId?: string | null;
|
||||
publicKey?: string | null;
|
||||
counter?: number;
|
||||
passkeys: PasskeySelect[];
|
||||
};
|
||||
|
||||
export type PasskeySelect = typeof Schema.passkeys.$inferSelect;
|
||||
export type PasskeyInsert = typeof Schema.passkeys.$inferInsert;
|
||||
export type Passkey = PasskeySelect & {
|
||||
user: User;
|
||||
user: UserSelect;
|
||||
};
|
||||
|
||||
export type PasskeyChallengeSelect = typeof Schema.passkeyChallenges.$inferSelect;
|
||||
export type PasskeyChallengeInsert = typeof Schema.passkeyChallenges.$inferInsert;
|
||||
|
||||
export type TokenBlacklistSelect = typeof Schema.tokenBlacklist.$inferSelect;
|
||||
export type TokenBlacklistInsert = typeof Schema.tokenBlacklist.$inferInsert;
|
||||
|
||||
// ── User Data ──
|
||||
|
||||
export type UserSettingsSelect = typeof Schema.userSettings.$inferSelect;
|
||||
export type UserSettingsInsert = typeof Schema.userSettings.$inferInsert;
|
||||
|
||||
export type UserStateSelect = typeof Schema.userState.$inferSelect;
|
||||
export type UserStateInsert = typeof Schema.userState.$inferInsert;
|
||||
|
||||
export type UserIntegrationSelect = typeof Schema.userIntegrations.$inferSelect;
|
||||
export type UserIntegrationInsert = typeof Schema.userIntegrations.$inferInsert;
|
||||
|
||||
export type DockConfigSelect = typeof Schema.dockConfigs.$inferSelect;
|
||||
export type DockConfigInsert = typeof Schema.dockConfigs.$inferInsert;
|
||||
|
||||
// ── Chat ──
|
||||
|
||||
export type ChatSessionSelect = typeof Schema.chatSessions.$inferSelect;
|
||||
export type ChatSessionInsert = typeof Schema.chatSessions.$inferInsert;
|
||||
export type ChatSession = ChatSessionSelect & {
|
||||
messages?: ChatMessageSelect[];
|
||||
};
|
||||
|
||||
export type ChatMessageSelect = typeof Schema.chatMessages.$inferSelect;
|
||||
export type ChatMessageInsert = typeof Schema.chatMessages.$inferInsert;
|
||||
|
||||
export type ChatGroupSelect = typeof Schema.chatGroups.$inferSelect;
|
||||
export type ChatGroupInsert = typeof Schema.chatGroups.$inferInsert;
|
||||
|
||||
// ── Workspaces ──
|
||||
|
||||
export type WorkspaceSelect = typeof Schema.workspaces.$inferSelect;
|
||||
export type WorkspaceInsert = typeof Schema.workspaces.$inferInsert;
|
||||
|
||||
export type ScreenSelect = typeof Schema.screens.$inferSelect;
|
||||
export type ScreenInsert = typeof Schema.screens.$inferInsert;
|
||||
|
||||
export type ProjectSelect = typeof Schema.projects.$inferSelect;
|
||||
export type ProjectInsert = typeof Schema.projects.$inferInsert;
|
||||
|
||||
// ── Tasks ──
|
||||
|
||||
export type TaskSelect = typeof Schema.tasks.$inferSelect;
|
||||
export type TaskInsert = typeof Schema.tasks.$inferInsert;
|
||||
|
||||
// ── Skills ──
|
||||
|
||||
export type SkillSelect = typeof Schema.skills.$inferSelect;
|
||||
export type SkillInsert = typeof Schema.skills.$inferInsert;
|
||||
|
||||
// ── Processes ──
|
||||
|
||||
export type ProcessSelect = typeof Schema.processes.$inferSelect;
|
||||
export type ProcessInsert = typeof Schema.processes.$inferInsert;
|
||||
|
||||
// ── Resources ──
|
||||
|
||||
export type ResourceSelect = typeof Schema.resources.$inferSelect;
|
||||
export type ResourceInsert = typeof Schema.resources.$inferInsert;
|
||||
|
||||
// ── Tools ──
|
||||
|
||||
export type ToolSelect = typeof Schema.tools.$inferSelect;
|
||||
export type ToolInsert = typeof Schema.tools.$inferInsert;
|
||||
|
||||
// ── Extensions ──
|
||||
|
||||
export type ExtensionSelect = typeof Schema.extensions.$inferSelect;
|
||||
export type ExtensionInsert = typeof Schema.extensions.$inferInsert;
|
||||
|
||||
// ── Item Chats ──
|
||||
|
||||
export type ItemChatSelect = typeof Schema.itemChats.$inferSelect;
|
||||
export type ItemChatInsert = typeof Schema.itemChats.$inferInsert;
|
||||
|
||||
// ── Operations ──
|
||||
|
||||
export type TaskLogSelect = typeof Schema.taskLogs.$inferSelect;
|
||||
export type TaskLogInsert = typeof Schema.taskLogs.$inferInsert;
|
||||
|
||||
export type QueueJobSelect = typeof Schema.queueJobs.$inferSelect;
|
||||
export type QueueJobInsert = typeof Schema.queueJobs.$inferInsert;
|
||||
|
||||
export type TerminalContainerSelect = typeof Schema.terminalContainers.$inferSelect;
|
||||
export type TerminalContainerInsert = typeof Schema.terminalContainers.$inferInsert;
|
||||
|
||||
// ── Server ──
|
||||
|
||||
export type ServerConfigSelect = typeof Schema.serverConfig.$inferSelect;
|
||||
export type ServerConfigInsert = typeof Schema.serverConfig.$inferInsert;
|
||||
|
||||
export type ServerIntegrationSelect = typeof Schema.serverIntegrations.$inferSelect;
|
||||
export type ServerIntegrationInsert = typeof Schema.serverIntegrations.$inferInsert;
|
||||
|
||||
Reference in New Issue
Block a user