schema.ts 15 KB

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