import { Agent } from "app-types/agent"; import { UserPreferences } from "app-types/user"; import { MCPServerConfig, MCPToolInfo } from "app-types/mcp"; import { sql } from "drizzle-orm"; import { pgTable, text, timestamp, json, uuid, boolean, unique, varchar, index, } from "drizzle-orm/pg-core"; import { isNotNull } from "drizzle-orm"; import { DBWorkflow, DBEdge, DBNode } from "app-types/workflow"; import { UIMessage } from "ai"; import { ChatMetadata } from "app-types/chat"; import { TipTapMentionJsonContent } from "@/types/util"; export const ChatThreadTable = pgTable("chat_thread", { id: uuid("id").primaryKey().notNull().defaultRandom(), title: text("title").notNull(), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), }); export const ChatMessageTable = pgTable("chat_message", { id: text("id").primaryKey().notNull(), threadId: uuid("thread_id") .notNull() .references(() => ChatThreadTable.id, { onDelete: "cascade" }), role: text("role").notNull().$type(), parts: json("parts").notNull().array().$type(), metadata: json("metadata").$type(), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), }); export const AgentTable = pgTable("agent", { id: uuid("id").primaryKey().notNull().defaultRandom(), name: text("name").notNull(), description: text("description"), icon: json("icon").$type(), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), instructions: json("instructions").$type(), visibility: varchar("visibility", { enum: ["public", "private", "readonly"], }) .notNull() .default("private"), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`), }); export const BookmarkTable = pgTable( "bookmark", { id: uuid("id").primaryKey().notNull().defaultRandom(), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), itemId: uuid("item_id").notNull(), itemType: varchar("item_type", { enum: ["agent", "workflow", "mcp"], }).notNull(), createdAt: timestamp("created_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), }, (table) => [ unique().on(table.userId, table.itemId, table.itemType), index("bookmark_user_id_idx").on(table.userId), index("bookmark_item_idx").on(table.itemId, table.itemType), ], ); export const McpServerTable = pgTable("mcp_server", { id: uuid("id").primaryKey().notNull().defaultRandom(), name: text("name").notNull(), config: json("config").notNull().$type(), enabled: boolean("enabled").notNull().default(true), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), visibility: varchar("visibility", { enum: ["public", "private"], }) .notNull() .default("private"), toolInfo: json("tool_info").$type(), toolInfoUpdatedAt: timestamp("tool_info_updated_at"), lastConnectionStatus: varchar("last_connection_status", { enum: ["connected", "error"], }), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`), }); export const UserTable = pgTable("user", { id: uuid("id").primaryKey().notNull().defaultRandom(), name: text("name").notNull(), email: text("email").notNull().unique(), emailVerified: boolean("email_verified").default(false).notNull(), password: text("password"), image: text("image"), preferences: json("preferences").default({}).$type(), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`), banned: boolean("banned"), banReason: text("ban_reason"), banExpires: timestamp("ban_expires"), role: text("role").notNull().default("user"), }); // Role tables removed - using Better Auth's built-in role system // Roles are now managed via the 'role' field on UserTable export const SessionTable = pgTable("session", { id: uuid("id").primaryKey().notNull().defaultRandom(), expiresAt: timestamp("expires_at").notNull(), token: text("token").notNull().unique(), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`), ipAddress: text("ip_address"), userAgent: text("user_agent"), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), // Admin plugin field (from better-auth generated schema) impersonatedBy: text("impersonated_by"), }); export const AccountTable = pgTable("account", { id: uuid("id").primaryKey().notNull().defaultRandom(), accountId: text("account_id").notNull(), providerId: text("provider_id").notNull(), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), accessToken: text("access_token"), refreshToken: text("refresh_token"), idToken: text("id_token"), accessTokenExpiresAt: timestamp("access_token_expires_at"), refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), scope: text("scope"), password: text("password"), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`), }); export const VerificationTable = pgTable("verification", { id: uuid("id").primaryKey().notNull().defaultRandom(), identifier: text("identifier").notNull(), value: text("value").notNull(), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at").$defaultFn( () => /* @__PURE__ */ new Date(), ), updatedAt: timestamp("updated_at").$defaultFn( () => /* @__PURE__ */ new Date(), ), }); // Tool customization table for per-user additional instructions export const McpToolCustomizationTable = pgTable( "mcp_server_tool_custom_instructions", { id: uuid("id").primaryKey().notNull().defaultRandom(), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), toolName: text("tool_name").notNull(), mcpServerId: uuid("mcp_server_id") .notNull() .references(() => McpServerTable.id, { onDelete: "cascade" }), prompt: text("prompt"), createdAt: timestamp("created_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), }, (table) => [unique().on(table.userId, table.toolName, table.mcpServerId)], ); export const McpServerCustomizationTable = pgTable( "mcp_server_custom_instructions", { id: uuid("id").primaryKey().defaultRandom(), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), mcpServerId: uuid("mcp_server_id") .notNull() .references(() => McpServerTable.id, { onDelete: "cascade" }), prompt: text("prompt"), createdAt: timestamp("created_at") .default(sql`CURRENT_TIMESTAMP`) .notNull(), updatedAt: timestamp("updated_at") .default(sql`CURRENT_TIMESTAMP`) .notNull(), }, (table) => [unique().on(table.userId, table.mcpServerId)], ); export const WorkflowTable = pgTable("workflow", { id: uuid("id").primaryKey().notNull().defaultRandom(), version: text("version").notNull().default("0.1.0"), name: text("name").notNull(), icon: json("icon").$type(), description: text("description"), isPublished: boolean("is_published").notNull().default(false), visibility: varchar("visibility", { enum: ["public", "private", "readonly"], }) .notNull() .default("private"), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`), }); export const WorkflowNodeDataTable = pgTable( "workflow_node", { id: uuid("id").primaryKey().notNull().defaultRandom(), version: text("version").notNull().default("0.1.0"), workflowId: uuid("workflow_id") .notNull() .references(() => WorkflowTable.id, { onDelete: "cascade" }), kind: text("kind").notNull(), name: text("name").notNull(), description: text("description"), uiConfig: json("ui_config").$type().default({}), nodeConfig: json("node_config") .$type>() .default({}), createdAt: timestamp("created_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), }, (t) => [index("workflow_node_kind_idx").on(t.kind)], ); export const WorkflowEdgeTable = pgTable("workflow_edge", { id: uuid("id").primaryKey().notNull().defaultRandom(), version: text("version").notNull().default("0.1.0"), workflowId: uuid("workflow_id") .notNull() .references(() => WorkflowTable.id, { onDelete: "cascade" }), source: uuid("source") .notNull() .references(() => WorkflowNodeDataTable.id, { onDelete: "cascade" }), target: uuid("target") .notNull() .references(() => WorkflowNodeDataTable.id, { onDelete: "cascade" }), uiConfig: json("ui_config").$type().default({}), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), }); export const ArchiveTable = pgTable("archive", { id: uuid("id").primaryKey().notNull().defaultRandom(), name: text("name").notNull(), description: text("description"), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`), }); export const ArchiveItemTable = pgTable( "archive_item", { id: uuid("id").primaryKey().notNull().defaultRandom(), archiveId: uuid("archive_id") .notNull() .references(() => ArchiveTable.id, { onDelete: "cascade" }), itemId: uuid("item_id").notNull(), userId: uuid("user_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), addedAt: timestamp("added_at").notNull().default(sql`CURRENT_TIMESTAMP`), }, (t) => [index("archive_item_item_id_idx").on(t.itemId)], ); export const McpOAuthSessionTable = pgTable( "mcp_oauth_session", { id: uuid("id").primaryKey().notNull().defaultRandom(), mcpServerId: uuid("mcp_server_id") .notNull() .references(() => McpServerTable.id, { onDelete: "cascade" }), serverUrl: text("server_url").notNull(), clientInfo: json("client_info"), tokens: json("tokens"), codeVerifier: text("code_verifier"), state: text("state").unique(), // OAuth state parameter for current flow (unique for security) createdAt: timestamp("created_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), }, (t) => [ index("mcp_oauth_session_server_id_idx").on(t.mcpServerId), index("mcp_oauth_session_state_idx").on(t.state), // Partial index for sessions with tokens for better performance index("mcp_oauth_session_tokens_idx") .on(t.mcpServerId) .where(isNotNull(t.tokens)), ], ); export type McpServerEntity = typeof McpServerTable.$inferSelect; export type ChatThreadEntity = typeof ChatThreadTable.$inferSelect; export type ChatMessageEntity = typeof ChatMessageTable.$inferSelect; export type AgentEntity = typeof AgentTable.$inferSelect; export type UserEntity = typeof UserTable.$inferSelect; export type SessionEntity = typeof SessionTable.$inferSelect; export type ToolCustomizationEntity = typeof McpToolCustomizationTable.$inferSelect; export type McpServerCustomizationEntity = typeof McpServerCustomizationTable.$inferSelect; export const ChatExportTable = pgTable("chat_export", { id: uuid("id").primaryKey().notNull().defaultRandom(), title: text("title").notNull(), exporterId: uuid("exporter_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), originalThreadId: uuid("original_thread_id"), messages: json("messages").notNull().$type< Array<{ id: string; role: UIMessage["role"]; parts: UIMessage["parts"]; metadata?: ChatMetadata; }> >(), exportedAt: timestamp("exported_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), expiresAt: timestamp("expires_at"), }); export const ChatExportCommentTable = pgTable("chat_export_comment", { id: uuid("id").primaryKey().notNull().defaultRandom(), exportId: uuid("export_id") .notNull() .references(() => ChatExportTable.id, { onDelete: "cascade" }), authorId: uuid("author_id") .notNull() .references(() => UserTable.id, { onDelete: "cascade" }), parentId: uuid("parent_id").references(() => ChatExportCommentTable.id, { onDelete: "cascade", }), content: json("content").notNull().$type(), createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`), }); export type ArchiveEntity = typeof ArchiveTable.$inferSelect; export type ArchiveItemEntity = typeof ArchiveItemTable.$inferSelect; export type BookmarkEntity = typeof BookmarkTable.$inferSelect;