mirror of
https://github.com/fosrl/pangolin.git
synced 2026-04-16 06:46:37 +00:00
Add init alert schema
This commit is contained in:
@@ -16,11 +16,13 @@ import {
|
|||||||
domains,
|
domains,
|
||||||
orgs,
|
orgs,
|
||||||
targets,
|
targets,
|
||||||
|
roles,
|
||||||
users,
|
users,
|
||||||
exitNodes,
|
exitNodes,
|
||||||
sessions,
|
sessions,
|
||||||
clients,
|
clients,
|
||||||
siteResources,
|
siteResources,
|
||||||
|
targetHealthCheck,
|
||||||
sites
|
sites
|
||||||
} from "./schema";
|
} from "./schema";
|
||||||
|
|
||||||
@@ -425,7 +427,9 @@ export const eventStreamingDestinations = pgTable(
|
|||||||
orgId: varchar("orgId", { length: 255 })
|
orgId: varchar("orgId", { length: 255 })
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
sendConnectionLogs: boolean("sendConnectionLogs").notNull().default(false),
|
sendConnectionLogs: boolean("sendConnectionLogs")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
sendRequestLogs: boolean("sendRequestLogs").notNull().default(false),
|
sendRequestLogs: boolean("sendRequestLogs").notNull().default(false),
|
||||||
sendActionLogs: boolean("sendActionLogs").notNull().default(false),
|
sendActionLogs: boolean("sendActionLogs").notNull().default(false),
|
||||||
sendAccessLogs: boolean("sendAccessLogs").notNull().default(false),
|
sendAccessLogs: boolean("sendAccessLogs").notNull().default(false),
|
||||||
@@ -447,7 +451,9 @@ export const eventStreamingCursors = pgTable(
|
|||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
}),
|
}),
|
||||||
logType: varchar("logType", { length: 50 }).notNull(), // "request" | "action" | "access" | "connection"
|
logType: varchar("logType", { length: 50 }).notNull(), // "request" | "action" | "access" | "connection"
|
||||||
lastSentId: bigint("lastSentId", { mode: "number" }).notNull().default(0),
|
lastSentId: bigint("lastSentId", { mode: "number" })
|
||||||
|
.notNull()
|
||||||
|
.default(0),
|
||||||
lastSentAt: bigint("lastSentAt", { mode: "number" }) // epoch milliseconds, null if never sent
|
lastSentAt: bigint("lastSentAt", { mode: "number" }) // epoch milliseconds, null if never sent
|
||||||
},
|
},
|
||||||
(table) => [
|
(table) => [
|
||||||
@@ -458,6 +464,66 @@ export const eventStreamingCursors = pgTable(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const alertRules = pgTable("alertRules", {
|
||||||
|
alertRuleId: serial("alertRuleId").primaryKey(),
|
||||||
|
orgId: varchar("orgId", { length: 255 })
|
||||||
|
.notNull()
|
||||||
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
|
// Single field encodes both source and trigger - no redundancy
|
||||||
|
eventType: varchar("eventType", { length: 100 })
|
||||||
|
.$type<
|
||||||
|
| "site_online"
|
||||||
|
| "site_offline"
|
||||||
|
| "health_check_healthy"
|
||||||
|
| "health_check_not_healthy"
|
||||||
|
>()
|
||||||
|
.notNull(),
|
||||||
|
// Nullable depending on eventType
|
||||||
|
siteId: integer("siteId").references(() => sites.siteId, { onDelete: "cascade" }),
|
||||||
|
healthCheckId: integer("healthCheckId").references(
|
||||||
|
() => targetHealthCheck.targetHealthCheckId,
|
||||||
|
{ onDelete: "cascade" }
|
||||||
|
),
|
||||||
|
enabled: boolean("enabled").notNull().default(true),
|
||||||
|
cooldownSeconds: integer("cooldownSeconds").notNull().default(300),
|
||||||
|
lastTriggeredAt: bigint("lastTriggeredAt", { mode: "number" }), // nullable
|
||||||
|
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
|
||||||
|
updatedAt: bigint("updatedAt", { mode: "number" }).notNull()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Separating channels by type avoids the mixed-shape problem entirely
|
||||||
|
export const alertEmailActions = pgTable("alertEmailActions", {
|
||||||
|
emailActionId: serial("emailActionId").primaryKey(),
|
||||||
|
alertRuleId: integer("alertRuleId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => alertRules.alertRuleId, { onDelete: "cascade" }),
|
||||||
|
enabled: boolean("enabled").notNull().default(true),
|
||||||
|
lastSentAt: bigint("lastSentAt", { mode: "number" }), // nullable
|
||||||
|
});
|
||||||
|
|
||||||
|
export const alertEmailRecipients = pgTable("alertEmailRecipients", {
|
||||||
|
recipientId: serial("recipientId").primaryKey(),
|
||||||
|
emailActionId: integer("emailActionId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => alertEmailActions.emailActionId, { onDelete: "cascade" }),
|
||||||
|
// At least one of these should be set - enforced at app level
|
||||||
|
userId: varchar("userId").references(() => users.userId, { onDelete: "cascade" }),
|
||||||
|
roleId: varchar("roleId").references(() => roles.roleId, { onDelete: "cascade" }),
|
||||||
|
email: varchar("email", { length: 255 }) // external emails not tied to a user
|
||||||
|
});
|
||||||
|
|
||||||
|
export const alertWebhookActions = pgTable("alertWebhookActions", {
|
||||||
|
webhookActionId: serial("webhookActionId").primaryKey(),
|
||||||
|
alertRuleId: integer("alertRuleId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => alertRules.alertRuleId, { onDelete: "cascade" }),
|
||||||
|
webhookUrl: text("webhookUrl").notNull(),
|
||||||
|
secret: varchar("secret", { length: 255 }), // for HMAC signature validation
|
||||||
|
enabled: boolean("enabled").notNull().default(true),
|
||||||
|
lastSentAt: bigint("lastSentAt", { mode: "number" }) // nullable
|
||||||
|
});
|
||||||
|
|
||||||
export type Approval = InferSelectModel<typeof approvals>;
|
export type Approval = InferSelectModel<typeof approvals>;
|
||||||
export type Limit = InferSelectModel<typeof limits>;
|
export type Limit = InferSelectModel<typeof limits>;
|
||||||
export type Account = InferSelectModel<typeof account>;
|
export type Account = InferSelectModel<typeof account>;
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import {
|
|||||||
domains,
|
domains,
|
||||||
exitNodes,
|
exitNodes,
|
||||||
orgs,
|
orgs,
|
||||||
|
roles,
|
||||||
sessions,
|
sessions,
|
||||||
siteResources,
|
siteResources,
|
||||||
sites,
|
sites,
|
||||||
|
targetHealthCheck,
|
||||||
users
|
users
|
||||||
} from "./schema";
|
} from "./schema";
|
||||||
|
|
||||||
@@ -455,6 +457,62 @@ export const eventStreamingCursors = sqliteTable(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const alertRules = sqliteTable("alertRules", {
|
||||||
|
alertRuleId: integer("alertRuleId").primaryKey({ autoIncrement: true }),
|
||||||
|
orgId: text("orgId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
eventType: text("eventType")
|
||||||
|
.$type<
|
||||||
|
| "site_online"
|
||||||
|
| "site_offline"
|
||||||
|
| "health_check_healthy"
|
||||||
|
| "health_check_not_healthy"
|
||||||
|
>()
|
||||||
|
.notNull(),
|
||||||
|
siteId: integer("siteId").references(() => sites.siteId, { onDelete: "cascade" }),
|
||||||
|
healthCheckId: integer("healthCheckId").references(
|
||||||
|
() => targetHealthCheck.targetHealthCheckId,
|
||||||
|
{ onDelete: "cascade" }
|
||||||
|
),
|
||||||
|
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||||
|
cooldownSeconds: integer("cooldownSeconds").notNull().default(300),
|
||||||
|
lastTriggeredAt: integer("lastTriggeredAt"),
|
||||||
|
createdAt: integer("createdAt").notNull(),
|
||||||
|
updatedAt: integer("updatedAt").notNull()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const alertEmailActions = sqliteTable("alertEmailActions", {
|
||||||
|
emailActionId: integer("emailActionId").primaryKey({ autoIncrement: true }),
|
||||||
|
alertRuleId: integer("alertRuleId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => alertRules.alertRuleId, { onDelete: "cascade" }),
|
||||||
|
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||||
|
lastSentAt: integer("lastSentAt")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const alertEmailRecipients = sqliteTable("alertEmailRecipients", {
|
||||||
|
recipientId: integer("recipientId").primaryKey({ autoIncrement: true }),
|
||||||
|
emailActionId: integer("emailActionId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => alertEmailActions.emailActionId, { onDelete: "cascade" }),
|
||||||
|
userId: text("userId").references(() => users.userId, { onDelete: "cascade" }),
|
||||||
|
roleId: text("roleId").references(() => roles.roleId, { onDelete: "cascade" }),
|
||||||
|
email: text("email")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const alertWebhookActions = sqliteTable("alertWebhookActions", {
|
||||||
|
webhookActionId: integer("webhookActionId").primaryKey({ autoIncrement: true }),
|
||||||
|
alertRuleId: integer("alertRuleId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => alertRules.alertRuleId, { onDelete: "cascade" }),
|
||||||
|
webhookUrl: text("webhookUrl").notNull(),
|
||||||
|
secret: text("secret"),
|
||||||
|
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||||
|
lastSentAt: integer("lastSentAt")
|
||||||
|
});
|
||||||
|
|
||||||
export type Approval = InferSelectModel<typeof approvals>;
|
export type Approval = InferSelectModel<typeof approvals>;
|
||||||
export type Limit = InferSelectModel<typeof limits>;
|
export type Limit = InferSelectModel<typeof limits>;
|
||||||
export type Account = InferSelectModel<typeof account>;
|
export type Account = InferSelectModel<typeof account>;
|
||||||
|
|||||||
Reference in New Issue
Block a user