Spaces:
Sleeping
Sleeping
| 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<UIMessage["role"]>(), | |
| parts: json("parts").notNull().array().$type<UIMessage["parts"]>(), | |
| metadata: json("metadata").$type<ChatMetadata>(), | |
| 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<Agent["icon"]>(), | |
| userId: uuid("user_id") | |
| .notNull() | |
| .references(() => UserTable.id, { onDelete: "cascade" }), | |
| instructions: json("instructions").$type<Agent["instructions"]>(), | |
| 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<MCPServerConfig>(), | |
| 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<MCPToolInfo[]>(), | |
| 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<UserPreferences>(), | |
| 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<DBWorkflow["icon"]>(), | |
| 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<DBNode["uiConfig"]>().default({}), | |
| nodeConfig: json("node_config") | |
| .$type<Partial<DBNode["nodeConfig"]>>() | |
| .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<DBEdge["uiConfig"]>().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<TipTapMentionJsonContent>(), | |
| 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; | |