schema.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. import {
  2. boolean,
  3. integer,
  4. jsonb,
  5. pgSchema,
  6. serial,
  7. text,
  8. timestamp,
  9. varchar,
  10. } from "drizzle-orm/pg-core";
  11. // ── Schema ───────────────────────────────────────────────────────────────────
  12. // All chatbot objects live in the "chatbot" PostgreSQL schema.
  13. // DBA setup (run once):
  14. // CREATE SCHEMA chatbot;
  15. // GRANT ALL ON SCHEMA chatbot TO chatbot_user;
  16. export const chatbotSchema = pgSchema("chatbot");
  17. // ── Enums (scoped to chatbot schema) ─────────────────────────────────────────
  18. export const roleEnum = chatbotSchema.enum("role", ["user", "agent", "admin"]);
  19. export const conversationStatusEnum = chatbotSchema.enum("conversation_status", ["active", "escalated", "resolved", "closed"]);
  20. export const senderEnum = chatbotSchema.enum("sender", ["visitor", "bot", "agent"]);
  21. export const workflowNodeTypeEnum = chatbotSchema.enum("workflow_node_type", [
  22. "greeting", "intent", "response", "condition", "escalation",
  23. "action", "end", "customer_data", "sales_order", "guardrail",
  24. ]);
  25. export const inviteStatusEnum = chatbotSchema.enum("invite_status", ["pending", "accepted", "expired", "revoked"]);
  26. export const suggestionStatusEnum = chatbotSchema.enum("suggestion_status", ["pending", "approved", "declined", "waiting"]);
  27. export const eventTypeEnum = chatbotSchema.enum("event_type", [
  28. "session_start", "message_sent", "message_received",
  29. "intent_detected", "flow_triggered", "escalated",
  30. "resolved_by_bot", "resolved_by_agent", "abandoned",
  31. "button_clicked", "feedback_positive", "feedback_negative",
  32. ]);
  33. export const sourceTypeEnum = chatbotSchema.enum("source_type", ["url", "file", "qa_pair", "api"]);
  34. export const sourceStatusEnum = chatbotSchema.enum("source_status", ["active", "inactive", "syncing", "error"]);
  35. export const httpMethodEnum = chatbotSchema.enum("http_method", ["GET", "POST", "PUT", "DELETE"]);
  36. // ── Tables (all inside chatbot schema) ───────────────────────────────────────
  37. /**
  38. * Core user table backing auth flow.
  39. */
  40. export const users = chatbotSchema.table("users", {
  41. id: serial("id").primaryKey(),
  42. openId: varchar("openId", { length: 64 }).notNull().unique(),
  43. name: text("name"),
  44. email: varchar("email", { length: 320 }),
  45. loginMethod: varchar("loginMethod", { length: 64 }),
  46. role: roleEnum("role").default("user").notNull(),
  47. createdAt: timestamp("createdAt").defaultNow().notNull(),
  48. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  49. lastSignedIn: timestamp("lastSignedIn").defaultNow().notNull(),
  50. passwordHash: varchar("passwordHash", { length: 255 }),
  51. });
  52. export type User = typeof users.$inferSelect;
  53. export type InsertUser = typeof users.$inferInsert;
  54. /**
  55. * Password reset tokens — for forgot password flow.
  56. */
  57. export const passwordResetTokens = chatbotSchema.table("password_reset_tokens", {
  58. id: serial("id").primaryKey(),
  59. userId: integer("userId").notNull(),
  60. token: varchar("token", { length: 64 }).notNull().unique(),
  61. expiresAt: timestamp("expiresAt").notNull(),
  62. usedAt: timestamp("usedAt"),
  63. createdAt: timestamp("createdAt").defaultNow().notNull(),
  64. });
  65. export type PasswordResetToken = typeof passwordResetTokens.$inferSelect;
  66. export type InsertPasswordResetToken = typeof passwordResetTokens.$inferInsert;
  67. /**
  68. * Chat conversations — each visitor session creates one conversation.
  69. */
  70. export const conversations = chatbotSchema.table("conversations", {
  71. id: serial("id").primaryKey(),
  72. sessionId: varchar("sessionId", { length: 64 }).notNull().unique(),
  73. visitorName: varchar("visitorName", { length: 255 }),
  74. visitorEmail: varchar("visitorEmail", { length: 320 }),
  75. customerId: varchar("customerId", { length: 64 }),
  76. salesRep: varchar("salesRep", { length: 255 }),
  77. status: conversationStatusEnum("status").default("active").notNull(),
  78. assignedAgentId: integer("assignedAgentId"),
  79. metadata: jsonb("metadata"),
  80. createdAt: timestamp("createdAt").defaultNow().notNull(),
  81. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  82. });
  83. export type Conversation = typeof conversations.$inferSelect;
  84. export type InsertConversation = typeof conversations.$inferInsert;
  85. /**
  86. * Chat messages — each message belongs to a conversation.
  87. */
  88. export const messages = chatbotSchema.table("messages", {
  89. id: serial("id").primaryKey(),
  90. conversationId: integer("conversationId").notNull(),
  91. sender: senderEnum("sender").notNull(),
  92. content: text("content").notNull(),
  93. metadata: jsonb("metadata"),
  94. createdAt: timestamp("createdAt").defaultNow().notNull(),
  95. });
  96. export type Message = typeof messages.$inferSelect;
  97. export type InsertMessage = typeof messages.$inferInsert;
  98. /**
  99. * Workflow nodes — each node in the chatbot conversation flow.
  100. */
  101. export const workflowNodes = chatbotSchema.table("workflow_nodes", {
  102. id: serial("id").primaryKey(),
  103. workflowId: varchar("workflowId", { length: 64 }).notNull(),
  104. nodeId: varchar("nodeId", { length: 64 }).notNull(),
  105. type: workflowNodeTypeEnum("type").notNull(),
  106. label: varchar("label", { length: 255 }).notNull(),
  107. config: jsonb("config"),
  108. positionX: integer("positionX").default(0).notNull(),
  109. positionY: integer("positionY").default(0).notNull(),
  110. createdAt: timestamp("createdAt").defaultNow().notNull(),
  111. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  112. });
  113. export type WorkflowNode = typeof workflowNodes.$inferSelect;
  114. export type InsertWorkflowNode = typeof workflowNodes.$inferInsert;
  115. /**
  116. * Workflow edges — connections between nodes.
  117. */
  118. export const workflowEdges = chatbotSchema.table("workflow_edges", {
  119. id: serial("id").primaryKey(),
  120. workflowId: varchar("workflowId", { length: 64 }).notNull(),
  121. sourceNodeId: varchar("sourceNodeId", { length: 64 }).notNull(),
  122. targetNodeId: varchar("targetNodeId", { length: 64 }).notNull(),
  123. label: varchar("label", { length: 255 }),
  124. condition: jsonb("condition"),
  125. createdAt: timestamp("createdAt").defaultNow().notNull(),
  126. });
  127. export type WorkflowEdge = typeof workflowEdges.$inferSelect;
  128. export type InsertWorkflowEdge = typeof workflowEdges.$inferInsert;
  129. /**
  130. * Invitations — track sent invitations and their acceptance status.
  131. */
  132. export const invitations = chatbotSchema.table("invitations", {
  133. id: serial("id").primaryKey(),
  134. email: varchar("email", { length: 320 }).notNull(),
  135. role: roleEnum("role").default("agent").notNull(),
  136. token: varchar("token", { length: 64 }).notNull().unique(),
  137. status: inviteStatusEnum("status").default("pending").notNull(),
  138. invitedById: integer("invitedById").notNull(),
  139. invitedByName: varchar("invitedByName", { length: 255 }),
  140. acceptedByUserId: integer("acceptedByUserId"),
  141. message: text("message"),
  142. expiresAt: timestamp("expiresAt").notNull(),
  143. acceptedAt: timestamp("acceptedAt"),
  144. createdAt: timestamp("createdAt").defaultNow().notNull(),
  145. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  146. });
  147. export type Invitation = typeof invitations.$inferSelect;
  148. export type InsertInvitation = typeof invitations.$inferInsert;
  149. /**
  150. * Audit logs — track important user management actions.
  151. */
  152. export const auditLogs = chatbotSchema.table("audit_logs", {
  153. id: serial("id").primaryKey(),
  154. action: varchar("action", { length: 64 }).notNull(),
  155. actorId: integer("actorId").notNull(),
  156. actorName: varchar("actorName", { length: 255 }),
  157. targetId: integer("targetId"),
  158. targetName: varchar("targetName", { length: 255 }),
  159. details: jsonb("details"),
  160. createdAt: timestamp("createdAt").defaultNow().notNull(),
  161. });
  162. export type AuditLog = typeof auditLogs.$inferSelect;
  163. export type InsertAuditLog = typeof auditLogs.$inferInsert;
  164. /**
  165. * AI workflow suggestions — AI-recommended nodes based on FAQ analysis.
  166. */
  167. export const workflowSuggestions = chatbotSchema.table("workflow_suggestions", {
  168. id: serial("id").primaryKey(),
  169. workflowId: varchar("workflowId", { length: 64 }).notNull(),
  170. suggestedNodeType: varchar("suggestedNodeType", { length: 64 }).notNull(),
  171. label: varchar("label", { length: 255 }).notNull(),
  172. description: text("description"),
  173. config: jsonb("config"),
  174. faqQuestion: text("faqQuestion"),
  175. frequency: integer("frequency").default(0).notNull(),
  176. status: suggestionStatusEnum("status").default("pending").notNull(),
  177. reviewedById: integer("reviewedById"),
  178. reviewedAt: timestamp("reviewedAt"),
  179. createdAt: timestamp("createdAt").defaultNow().notNull(),
  180. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  181. });
  182. export type WorkflowSuggestion = typeof workflowSuggestions.$inferSelect;
  183. export type InsertWorkflowSuggestion = typeof workflowSuggestions.$inferInsert;
  184. /**
  185. * Analytics events — track chatbot interactions for resolution rate and metrics.
  186. */
  187. export const analyticsEvents = chatbotSchema.table("analytics_events", {
  188. id: serial("id").primaryKey(),
  189. conversationId: integer("conversationId"),
  190. sessionId: varchar("sessionId", { length: 64 }),
  191. eventType: eventTypeEnum("eventType").notNull(),
  192. category: varchar("category", { length: 64 }),
  193. metadata: jsonb("metadata"),
  194. createdAt: timestamp("createdAt").defaultNow().notNull(),
  195. });
  196. export type AnalyticsEvent = typeof analyticsEvents.$inferSelect;
  197. export type InsertAnalyticsEvent = typeof analyticsEvents.$inferInsert;
  198. /**
  199. * Data sources — knowledge base for the AI agent.
  200. */
  201. export const dataSources = chatbotSchema.table("data_sources", {
  202. id: serial("id").primaryKey(),
  203. name: varchar("name", { length: 255 }).notNull(),
  204. type: sourceTypeEnum("type").notNull(),
  205. status: sourceStatusEnum("status").default("active").notNull(),
  206. config: jsonb("config"),
  207. lastSyncedAt: timestamp("lastSyncedAt"),
  208. itemCount: integer("itemCount").default(0).notNull(),
  209. createdById: integer("createdById"),
  210. createdAt: timestamp("createdAt").defaultNow().notNull(),
  211. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  212. });
  213. export type DataSource = typeof dataSources.$inferSelect;
  214. export type InsertDataSource = typeof dataSources.$inferInsert;
  215. /**
  216. * API connections — external API endpoints for Actions.
  217. */
  218. export const apiConnections = chatbotSchema.table("api_connections", {
  219. id: serial("id").primaryKey(),
  220. name: varchar("name", { length: 255 }).notNull(),
  221. description: text("description"),
  222. category: varchar("category", { length: 64 }),
  223. method: httpMethodEnum("method").default("GET").notNull(),
  224. endpoint: varchar("endpoint", { length: 1024 }).notNull(),
  225. headers: jsonb("headers"),
  226. inputVariables: jsonb("inputVariables"),
  227. outputVariables: jsonb("outputVariables"),
  228. testPayload: jsonb("testPayload"),
  229. isActive: boolean("isActive").default(true).notNull(),
  230. executionCount: integer("executionCount").default(0).notNull(),
  231. lastExecutedAt: timestamp("lastExecutedAt"),
  232. createdById: integer("createdById"),
  233. createdAt: timestamp("createdAt").defaultNow().notNull(),
  234. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  235. });
  236. export type ApiConnection = typeof apiConnections.$inferSelect;
  237. export type InsertApiConnection = typeof apiConnections.$inferInsert;
  238. /**
  239. * Knowledge base Q&A entries — first-line answer engine before the LLM.
  240. */
  241. export const knowledgeEntries = chatbotSchema.table("knowledge_entries", {
  242. id: serial("id").primaryKey(),
  243. question: text("question").notNull(),
  244. answer: text("answer").notNull(),
  245. category: varchar("category", { length: 100 }),
  246. source: varchar("source", { length: 50 }).default("manual"), // "manual"|"csv"|"url"
  247. useCount: integer("use_count").default(0).notNull(),
  248. status: varchar("status", { length: 20 }).default("active").notNull(), // "active"|"inactive"
  249. createdAt: timestamp("created_at").defaultNow().notNull(),
  250. updatedAt: timestamp("updated_at").defaultNow().notNull(),
  251. });
  252. /**
  253. * Auto-captured unanswered questions from Ellie conversations.
  254. */
  255. export const knowledgeSuggestions = chatbotSchema.table("knowledge_suggestions", {
  256. id: serial("id").primaryKey(),
  257. question: text("question").notNull(),
  258. occurrenceCount: integer("occurrence_count").default(1).notNull(),
  259. lastSeen: timestamp("last_seen").defaultNow().notNull(),
  260. status: varchar("status", { length: 20 }).default("pending").notNull(), // "pending"|"promoted"|"dismissed"
  261. promotedToId: integer("promoted_to_id"),
  262. createdAt: timestamp("created_at").defaultNow().notNull(),
  263. });
  264. /**
  265. * Product catalog imported from Excel/CSV.
  266. */
  267. export const knowledgeProducts = chatbotSchema.table("knowledge_products", {
  268. id: serial("id").primaryKey(),
  269. model: varchar("model", { length: 100 }).notNull(),
  270. description: text("description"),
  271. categories: text("categories"),
  272. collection: varchar("collection", { length: 100 }),
  273. price: varchar("price", { length: 50 }),
  274. availability: varchar("availability", { length: 50 }),
  275. features: text("features"),
  276. dimensions: text("dimensions"),
  277. imageUrl: text("image_url"),
  278. status: varchar("status", { length: 20 }).default("active").notNull(),
  279. createdAt: timestamp("created_at").defaultNow().notNull(),
  280. });
  281. export type KnowledgeEntry = typeof knowledgeEntries.$inferSelect;
  282. export type InsertKnowledgeEntry = typeof knowledgeEntries.$inferInsert;
  283. export type KnowledgeSuggestion = typeof knowledgeSuggestions.$inferSelect;
  284. export type InsertKnowledgeSuggestion = typeof knowledgeSuggestions.$inferInsert;
  285. export type KnowledgeProduct = typeof knowledgeProducts.$inferSelect;
  286. export type InsertKnowledgeProduct = typeof knowledgeProducts.$inferInsert;