* feat(db): add webhook schema, migration, and repository Add the database foundation for workspace webhooks: - Add workspace_webhooks table with migration (webhook_event enum, URL, secret, event subscriptions, active flag) - Add webhook repository with CRUD operations - Add webhooks schema definition with relations - Extend card and list repos to return board/list names for webhook payload context Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): remove unused webhook_event enum and fix migration timestamp - Remove dead webhook_event pgEnum from schema (events column uses text) - Remove CREATE TYPE statement from migration SQL - Fix migration journal timestamp to be chronologically after the notifications migration (idx 25) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): return boardPublicId from card and list repo queries Add board publicId to getWorkspaceAndCardIdByCardPublicId and getWorkspaceAndListIdByListPublicId return values, needed for correct boardId in webhook payloads. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): add workspaceId index and document secret exposure - Add index on workspaceId for efficient webhook lookups per workspace - Add JSDoc comment on getActiveByWorkspaceId explaining that it returns secrets for server-side HMAC signing only and must never be exposed via client-facing endpoints Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(db): extract parseEvents helper in webhook repo DRY up 6 repeated JSON.parse-and-cast calls into a single helper function, addressing reviewer feedback on PR #391. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
60 lines
1.7 KiB
TypeScript
60 lines
1.7 KiB
TypeScript
import { relations } from "drizzle-orm";
|
|
import {
|
|
bigint,
|
|
bigserial,
|
|
boolean,
|
|
index,
|
|
pgTable,
|
|
text,
|
|
timestamp,
|
|
uuid,
|
|
varchar,
|
|
} from "drizzle-orm/pg-core";
|
|
|
|
import { users } from "./users";
|
|
import { workspaces } from "./workspaces";
|
|
|
|
export const webhookEvents = [
|
|
"card.created",
|
|
"card.updated",
|
|
"card.moved",
|
|
"card.deleted",
|
|
] as const;
|
|
export type WebhookEvent = (typeof webhookEvents)[number];
|
|
|
|
export const workspaceWebhooks = pgTable("workspace_webhooks", {
|
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
|
workspaceId: bigint("workspaceId", { mode: "number" })
|
|
.notNull()
|
|
.references(() => workspaces.id, { onDelete: "cascade" }),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
url: varchar("url", { length: 2048 }).notNull(),
|
|
secret: text("secret"),
|
|
events: text("events").notNull(), // JSON array of webhook events
|
|
active: boolean("active").notNull().default(true),
|
|
createdBy: uuid("createdBy")
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
|
updatedAt: timestamp("updatedAt"),
|
|
}, (table) => [
|
|
index("workspace_webhooks_workspace_idx").on(table.workspaceId),
|
|
]).enableRLS();
|
|
|
|
export const workspaceWebhooksRelations = relations(
|
|
workspaceWebhooks,
|
|
({ one }) => ({
|
|
workspace: one(workspaces, {
|
|
fields: [workspaceWebhooks.workspaceId],
|
|
references: [workspaces.id],
|
|
relationName: "workspaceWebhooksWorkspace",
|
|
}),
|
|
createdByUser: one(users, {
|
|
fields: [workspaceWebhooks.createdBy],
|
|
references: [users.id],
|
|
relationName: "workspaceWebhooksCreatedByUser",
|
|
}),
|
|
}),
|
|
);
|