feat(24-00): DB migrations and Drizzle schema updates for search/history/branching

- Add 0050_add_branch_columns.sql: parent_conversation_id + branch_from_message_id on chat_conversations
- Add 0051_add_message_search_vector.sql: content_search tsvector + GIN index on chat_messages
- Add 0052_create_chat_message_bookmarks.sql: new bookmarks table with company/message/conversation FK
- Update chat_conversations.ts: parentConversationId + branchFromMessageId columns + parentIdx
- Update chat_messages.ts: add comment for generated tsvector column
- Create chat_message_bookmarks.ts: Drizzle schema with indexes
- Update schema/index.ts: export chatMessageBookmarks
- Update _journal.json: entries for idx 50, 51, 52
This commit is contained in:
Nexus Dev 2026-04-01 22:25:52 +00:00
parent 1e4d2be3dd
commit 988514db41
8 changed files with 64 additions and 1 deletions

View file

@ -0,0 +1,4 @@
ALTER TABLE "chat_conversations"
ADD COLUMN "parent_conversation_id" uuid REFERENCES "chat_conversations"("id") ON DELETE SET NULL,
ADD COLUMN "branch_from_message_id" uuid;
CREATE INDEX "chat_conversations_parent_idx" ON "chat_conversations" ("parent_conversation_id");

View file

@ -0,0 +1,5 @@
ALTER TABLE "chat_messages"
ADD COLUMN "content_search" tsvector
GENERATED ALWAYS AS (to_tsvector('english', "content")) STORED;
CREATE INDEX "chat_messages_content_search_idx"
ON "chat_messages" USING GIN ("content_search");

View file

@ -0,0 +1,9 @@
CREATE TABLE "chat_message_bookmarks" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
"company_id" uuid NOT NULL REFERENCES "companies"("id"),
"message_id" uuid NOT NULL REFERENCES "chat_messages"("id") ON DELETE CASCADE,
"conversation_id" uuid NOT NULL REFERENCES "chat_conversations"("id") ON DELETE CASCADE,
"created_at" timestamp with time zone NOT NULL DEFAULT now()
);
CREATE INDEX "chat_bookmarks_company_message_idx" ON "chat_message_bookmarks" ("company_id", "message_id");
CREATE INDEX "chat_bookmarks_company_conv_idx" ON "chat_message_bookmarks" ("company_id", "conversation_id");

View file

@ -344,6 +344,27 @@
"when": 1775145655557,
"tag": "0048_flashy_marrow",
"breakpoints": true
},
{
"idx": 50,
"version": "7",
"when": 1775200000000,
"tag": "0050_add_branch_columns",
"breakpoints": true
},
{
"idx": 51,
"version": "7",
"when": 1775200001000,
"tag": "0051_add_message_search_vector",
"breakpoints": true
},
{
"idx": 52,
"version": "7",
"when": 1775200002000,
"tag": "0052_create_chat_message_bookmarks",
"breakpoints": true
}
]
}

View file

@ -1,4 +1,4 @@
import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core";
import { type AnyPgColumn, pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core";
import { companies } from "./companies.js";
import { agents } from "./agents.js";
@ -14,9 +14,12 @@ export const chatConversations = pgTable(
deletedAt: timestamp("deleted_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
parentConversationId: uuid("parent_conversation_id").references((): AnyPgColumn => chatConversations.id, { onDelete: "set null" }),
branchFromMessageId: uuid("branch_from_message_id"),
},
(table) => ({
companyUpdatedIdx: index("chat_conversations_company_updated_idx").on(table.companyId, table.updatedAt),
companyDeletedIdx: index("chat_conversations_company_deleted_idx").on(table.companyId, table.deletedAt),
parentIdx: index("chat_conversations_parent_idx").on(table.parentConversationId),
}),
);

View file

@ -0,0 +1,19 @@
import { pgTable, uuid, timestamp, index } from "drizzle-orm/pg-core";
import { companies } from "./companies.js";
import { chatMessages } from "./chat_messages.js";
import { chatConversations } from "./chat_conversations.js";
export const chatMessageBookmarks = pgTable(
"chat_message_bookmarks",
{
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
messageId: uuid("message_id").notNull().references(() => chatMessages.id, { onDelete: "cascade" }),
conversationId: uuid("conversation_id").notNull().references(() => chatConversations.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
companyMessageIdx: index("chat_bookmarks_company_message_idx").on(table.companyId, table.messageId),
companyConvIdx: index("chat_bookmarks_company_conv_idx").on(table.companyId, table.conversationId),
}),
);

View file

@ -13,6 +13,7 @@ export const chatMessages = pgTable(
messageType: text("message_type"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
// content_search tsvector column exists in Postgres (generated stored) — queried via sql`` only
},
(table) => ({
conversationCreatedIdx: index("chat_messages_conversation_created_idx").on(table.conversationId, table.createdAt),

View file

@ -60,3 +60,4 @@ export { pluginWebhookDeliveries } from "./plugin_webhooks.js";
export { pluginLogs } from "./plugin_logs.js";
export { chatConversations } from "./chat_conversations.js";
export { chatMessages } from "./chat_messages.js";
export { chatMessageBookmarks } from "./chat_message_bookmarks.js";