schema.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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. erpContactCid: varchar("erpContactCid", { length: 64 }), // links user to ERP ContactID for permission filtering
  48. createdAt: timestamp("createdAt").defaultNow().notNull(),
  49. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  50. lastSignedIn: timestamp("lastSignedIn").defaultNow().notNull(),
  51. passwordHash: varchar("passwordHash", { length: 255 }),
  52. });
  53. export type User = typeof users.$inferSelect;
  54. export type InsertUser = typeof users.$inferInsert;
  55. /**
  56. * Password reset tokens — for forgot password flow.
  57. */
  58. export const passwordResetTokens = chatbotSchema.table("password_reset_tokens", {
  59. id: serial("id").primaryKey(),
  60. userId: integer("userId").notNull(),
  61. token: varchar("token", { length: 64 }).notNull().unique(),
  62. expiresAt: timestamp("expiresAt").notNull(),
  63. usedAt: timestamp("usedAt"),
  64. createdAt: timestamp("createdAt").defaultNow().notNull(),
  65. });
  66. export type PasswordResetToken = typeof passwordResetTokens.$inferSelect;
  67. export type InsertPasswordResetToken = typeof passwordResetTokens.$inferInsert;
  68. /**
  69. * Chat conversations — each visitor session creates one conversation.
  70. */
  71. export const conversations = chatbotSchema.table("conversations", {
  72. id: serial("id").primaryKey(),
  73. sessionId: varchar("sessionId", { length: 64 }).notNull().unique(),
  74. visitorName: varchar("visitorName", { length: 255 }),
  75. visitorEmail: varchar("visitorEmail", { length: 320 }),
  76. customerId: varchar("customerId", { length: 64 }),
  77. salesRep: varchar("salesRep", { length: 255 }),
  78. status: conversationStatusEnum("status").default("active").notNull(),
  79. assignedAgentId: integer("assignedAgentId"),
  80. metadata: jsonb("metadata"),
  81. createdAt: timestamp("createdAt").defaultNow().notNull(),
  82. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  83. });
  84. export type Conversation = typeof conversations.$inferSelect;
  85. export type InsertConversation = typeof conversations.$inferInsert;
  86. /**
  87. * Chat messages — each message belongs to a conversation.
  88. */
  89. export const messages = chatbotSchema.table("messages", {
  90. id: serial("id").primaryKey(),
  91. conversationId: integer("conversationId").notNull(),
  92. sender: senderEnum("sender").notNull(),
  93. content: text("content").notNull(),
  94. metadata: jsonb("metadata"),
  95. createdAt: timestamp("createdAt").defaultNow().notNull(),
  96. });
  97. export type Message = typeof messages.$inferSelect;
  98. export type InsertMessage = typeof messages.$inferInsert;
  99. /**
  100. * Workflow nodes — each node in the chatbot conversation flow.
  101. */
  102. export const workflowNodes = chatbotSchema.table("workflow_nodes", {
  103. id: serial("id").primaryKey(),
  104. workflowId: varchar("workflowId", { length: 64 }).notNull(),
  105. nodeId: varchar("nodeId", { length: 64 }).notNull(),
  106. type: workflowNodeTypeEnum("type").notNull(),
  107. label: varchar("label", { length: 255 }).notNull(),
  108. config: jsonb("config"),
  109. positionX: integer("positionX").default(0).notNull(),
  110. positionY: integer("positionY").default(0).notNull(),
  111. createdAt: timestamp("createdAt").defaultNow().notNull(),
  112. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  113. });
  114. export type WorkflowNode = typeof workflowNodes.$inferSelect;
  115. export type InsertWorkflowNode = typeof workflowNodes.$inferInsert;
  116. /**
  117. * Workflow edges — connections between nodes.
  118. */
  119. export const workflowEdges = chatbotSchema.table("workflow_edges", {
  120. id: serial("id").primaryKey(),
  121. workflowId: varchar("workflowId", { length: 64 }).notNull(),
  122. sourceNodeId: varchar("sourceNodeId", { length: 64 }).notNull(),
  123. targetNodeId: varchar("targetNodeId", { length: 64 }).notNull(),
  124. label: varchar("label", { length: 255 }),
  125. condition: jsonb("condition"),
  126. createdAt: timestamp("createdAt").defaultNow().notNull(),
  127. });
  128. export type WorkflowEdge = typeof workflowEdges.$inferSelect;
  129. export type InsertWorkflowEdge = typeof workflowEdges.$inferInsert;
  130. /**
  131. * Invitations — track sent invitations and their acceptance status.
  132. */
  133. export const invitations = chatbotSchema.table("invitations", {
  134. id: serial("id").primaryKey(),
  135. email: varchar("email", { length: 320 }).notNull(),
  136. role: roleEnum("role").default("agent").notNull(),
  137. token: varchar("token", { length: 64 }).notNull().unique(),
  138. status: inviteStatusEnum("status").default("pending").notNull(),
  139. invitedById: integer("invitedById").notNull(),
  140. invitedByName: varchar("invitedByName", { length: 255 }),
  141. acceptedByUserId: integer("acceptedByUserId"),
  142. message: text("message"),
  143. expiresAt: timestamp("expiresAt").notNull(),
  144. acceptedAt: timestamp("acceptedAt"),
  145. createdAt: timestamp("createdAt").defaultNow().notNull(),
  146. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  147. });
  148. export type Invitation = typeof invitations.$inferSelect;
  149. export type InsertInvitation = typeof invitations.$inferInsert;
  150. /**
  151. * Audit logs — track important user management actions.
  152. */
  153. export const auditLogs = chatbotSchema.table("audit_logs", {
  154. id: serial("id").primaryKey(),
  155. action: varchar("action", { length: 64 }).notNull(),
  156. actorId: integer("actorId").notNull(),
  157. actorName: varchar("actorName", { length: 255 }),
  158. targetId: integer("targetId"),
  159. targetName: varchar("targetName", { length: 255 }),
  160. details: jsonb("details"),
  161. createdAt: timestamp("createdAt").defaultNow().notNull(),
  162. });
  163. export type AuditLog = typeof auditLogs.$inferSelect;
  164. export type InsertAuditLog = typeof auditLogs.$inferInsert;
  165. /**
  166. * AI workflow suggestions — AI-recommended nodes based on FAQ analysis.
  167. */
  168. export const workflowSuggestions = chatbotSchema.table("workflow_suggestions", {
  169. id: serial("id").primaryKey(),
  170. workflowId: varchar("workflowId", { length: 64 }).notNull(),
  171. suggestedNodeType: varchar("suggestedNodeType", { length: 64 }).notNull(),
  172. label: varchar("label", { length: 255 }).notNull(),
  173. description: text("description"),
  174. config: jsonb("config"),
  175. faqQuestion: text("faqQuestion"),
  176. frequency: integer("frequency").default(0).notNull(),
  177. status: suggestionStatusEnum("status").default("pending").notNull(),
  178. reviewedById: integer("reviewedById"),
  179. reviewedAt: timestamp("reviewedAt"),
  180. createdAt: timestamp("createdAt").defaultNow().notNull(),
  181. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  182. });
  183. export type WorkflowSuggestion = typeof workflowSuggestions.$inferSelect;
  184. export type InsertWorkflowSuggestion = typeof workflowSuggestions.$inferInsert;
  185. /**
  186. * Analytics events — track chatbot interactions for resolution rate and metrics.
  187. */
  188. export const analyticsEvents = chatbotSchema.table("analytics_events", {
  189. id: serial("id").primaryKey(),
  190. conversationId: integer("conversationId"),
  191. sessionId: varchar("sessionId", { length: 64 }),
  192. eventType: eventTypeEnum("eventType").notNull(),
  193. category: varchar("category", { length: 64 }),
  194. metadata: jsonb("metadata"),
  195. createdAt: timestamp("createdAt").defaultNow().notNull(),
  196. });
  197. export type AnalyticsEvent = typeof analyticsEvents.$inferSelect;
  198. export type InsertAnalyticsEvent = typeof analyticsEvents.$inferInsert;
  199. /**
  200. * Data sources — knowledge base for the AI agent.
  201. */
  202. export const dataSources = chatbotSchema.table("data_sources", {
  203. id: serial("id").primaryKey(),
  204. name: varchar("name", { length: 255 }).notNull(),
  205. type: sourceTypeEnum("type").notNull(),
  206. status: sourceStatusEnum("status").default("active").notNull(),
  207. config: jsonb("config"),
  208. lastSyncedAt: timestamp("lastSyncedAt"),
  209. itemCount: integer("itemCount").default(0).notNull(),
  210. createdById: integer("createdById"),
  211. createdAt: timestamp("createdAt").defaultNow().notNull(),
  212. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  213. });
  214. export type DataSource = typeof dataSources.$inferSelect;
  215. export type InsertDataSource = typeof dataSources.$inferInsert;
  216. /**
  217. * API connections — external API endpoints for Actions.
  218. */
  219. export const apiConnections = chatbotSchema.table("api_connections", {
  220. id: serial("id").primaryKey(),
  221. name: varchar("name", { length: 255 }).notNull(),
  222. description: text("description"),
  223. category: varchar("category", { length: 64 }),
  224. method: httpMethodEnum("method").default("GET").notNull(),
  225. endpoint: varchar("endpoint", { length: 1024 }).notNull(),
  226. headers: jsonb("headers"),
  227. inputVariables: jsonb("inputVariables"),
  228. outputVariables: jsonb("outputVariables"),
  229. testPayload: jsonb("testPayload"),
  230. isActive: boolean("isActive").default(true).notNull(),
  231. executionCount: integer("executionCount").default(0).notNull(),
  232. lastExecutedAt: timestamp("lastExecutedAt"),
  233. createdById: integer("createdById"),
  234. createdAt: timestamp("createdAt").defaultNow().notNull(),
  235. updatedAt: timestamp("updatedAt").defaultNow().notNull(),
  236. });
  237. export type ApiConnection = typeof apiConnections.$inferSelect;
  238. export type InsertApiConnection = typeof apiConnections.$inferInsert;
  239. /**
  240. * Knowledge base Q&A entries — first-line answer engine before the LLM.
  241. */
  242. export const knowledgeEntries = chatbotSchema.table("knowledge_entries", {
  243. id: serial("id").primaryKey(),
  244. question: text("question").notNull(),
  245. answer: text("answer").notNull(),
  246. category: varchar("category", { length: 100 }),
  247. source: varchar("source", { length: 50 }).default("manual"), // "manual"|"csv"|"url"
  248. useCount: integer("use_count").default(0).notNull(),
  249. status: varchar("status", { length: 20 }).default("active").notNull(), // "active"|"inactive"
  250. createdAt: timestamp("created_at").defaultNow().notNull(),
  251. updatedAt: timestamp("updated_at").defaultNow().notNull(),
  252. });
  253. /**
  254. * Auto-captured unanswered questions from Ellie conversations.
  255. */
  256. export const knowledgeSuggestions = chatbotSchema.table("knowledge_suggestions", {
  257. id: serial("id").primaryKey(),
  258. question: text("question").notNull(),
  259. occurrenceCount: integer("occurrence_count").default(1).notNull(),
  260. lastSeen: timestamp("last_seen").defaultNow().notNull(),
  261. status: varchar("status", { length: 20 }).default("pending").notNull(), // "pending"|"promoted"|"dismissed"
  262. promotedToId: integer("promoted_to_id"),
  263. createdAt: timestamp("created_at").defaultNow().notNull(),
  264. });
  265. /**
  266. * Product catalog imported from Excel/CSV.
  267. */
  268. export const knowledgeProducts = chatbotSchema.table("knowledge_products", {
  269. id: serial("id").primaryKey(),
  270. model: varchar("model", { length: 100 }).notNull(),
  271. description: text("description"),
  272. categories: text("categories"),
  273. collection: varchar("collection", { length: 100 }),
  274. price: varchar("price", { length: 50 }),
  275. availability: varchar("availability", { length: 50 }),
  276. features: text("features"),
  277. dimensions: text("dimensions"),
  278. imageUrl: text("image_url"),
  279. status: varchar("status", { length: 20 }).default("active").notNull(),
  280. createdAt: timestamp("created_at").defaultNow().notNull(),
  281. });
  282. export type KnowledgeEntry = typeof knowledgeEntries.$inferSelect;
  283. export type InsertKnowledgeEntry = typeof knowledgeEntries.$inferInsert;
  284. export type KnowledgeSuggestion = typeof knowledgeSuggestions.$inferSelect;
  285. export type InsertKnowledgeSuggestion = typeof knowledgeSuggestions.$inferInsert;
  286. export type KnowledgeProduct = typeof knowledgeProducts.$inferSelect;
  287. export type InsertKnowledgeProduct = typeof knowledgeProducts.$inferInsert;