swadhinbiswas feat: finalize platform polish - discussions, gists, webhook queue, realtime, push-mirror, auth/metrics hardening · 8/28/2026
f02976d
106 lines · 3.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
 * Webhooks Schema - Drizzle ORM
 * Defines webhooks and delivery logs
 */

import { relations } from "drizzle-orm";
import {
  boolean,
  index,
  integer,
  pgTable,
  text,
  timestamp,
} from "drizzle-orm/pg-core";
import { repositories } from "./repositories";
import { users } from "./users";

export const webhooks = pgTable(
  "webhooks",
  {
    id: text("id").primaryKey(),
    repositoryId: text("repository_id")
      .notNull()
      .references(() => repositories.id, { onDelete: "cascade" }),

    // Provider and configuration
    provider: text("provider").default("generic"), // teams, discord, slack, generic, linear, trello, clickup
    name: text("name"), // Display name for the webhook
    url: text("url").notNull(),
    secret: text("secret"), // Encrypted or plain? For now plain/masked in UI
    events: text("events").notNull(), // JSON array of events ['push', 'pull_request']
    active: boolean("active").notNull().default(true),
    enabled: boolean("enabled").notNull().default(true), // Alias for active
    contentType: text("content_type").notNull().default("json"), // json | form

    // Stats
    deliveryCount: integer("delivery_count").default(0),
    lastDeliveryStatus: text("last_delivery_status"), // success | failure
    lastDeliveryAt: timestamp("last_delivery_at"),

    createdAt: timestamp("created_at").notNull().defaultNow(),
    updatedAt: timestamp("updated_at").notNull().defaultNow(),
    createdById: text("created_by_id").references(() => users.id),
  },
  (t) => ({
    repoIdx: index("webhooks_repo_idx").on(t.repositoryId),
  }),
);

export const webhookDeliveries = pgTable(
  "webhook_deliveries",
  {
    id: text("id").primaryKey(),
    webhookId: text("webhook_id")
      .notNull()
      .references(() => webhooks.id, { onDelete: "cascade" }),
    event: text("event").notNull(),
    payload: text("payload").notNull(), // JSON

    // Queue lifecycle: pending | processing | delivered | dead.
    // Legacy rows (pre-queue) may still carry success | failure.
    status: text("status").notNull(),
    attempts: integer("attempts").notNull().default(0),
    nextAttemptAt: timestamp("next_attempt_at"),
    lockedAt: timestamp("locked_at"),
    failureReason: text("failure_reason"),

    responseCode: integer("response_code"),
    responseBody: text("response_body"),
    durationMs: integer("duration_ms"),

    error: text("error"),
    requestHeaders: text("request_headers"), // JSON
    responseHeaders: text("response_headers"), // JSON

    createdAt: timestamp("created_at").notNull().defaultNow(),
  },
  (t) => ({
    webhookIdx: index("webhook_deliveries_webhook_idx").on(t.webhookId),
    createdAtIdx: index("webhook_deliveries_created_idx").on(t.createdAt),
    queueIdx: index("webhook_deliveries_queue_idx").on(t.status, t.nextAttemptAt),
  }),
);

export const webhooksRelations = relations(webhooks, ({ one, many }) => ({
  repository: one(repositories, {
    fields: [webhooks.repositoryId],
    references: [repositories.id],
  }),
  createdBy: one(users, {
    fields: [webhooks.createdById],
    references: [users.id],
  }),
  deliveries: many(webhookDeliveries),
}));

export const webhookDeliveriesRelations = relations(
  webhookDeliveries,
  ({ one }) => ({
    webhook: one(webhooks, {
      fields: [webhookDeliveries.webhookId],
      references: [webhooks.id],
    }),
  }),
);