mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-04 03:24:09 +00:00
Add 1.18 migrations
This commit is contained in:
@@ -22,6 +22,7 @@ import m13 from "./scriptsPg/1.15.3";
|
||||
import m14 from "./scriptsPg/1.15.4";
|
||||
import m15 from "./scriptsPg/1.16.0";
|
||||
import m16 from "./scriptsPg/1.17.0";
|
||||
import m17 from "./scriptsPg/1.18.0";
|
||||
|
||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||
@@ -43,7 +44,8 @@ const migrations = [
|
||||
{ version: "1.15.3", run: m13 },
|
||||
{ version: "1.15.4", run: m14 },
|
||||
{ version: "1.16.0", run: m15 },
|
||||
{ version: "1.17.0", run: m16 }
|
||||
{ version: "1.17.0", run: m16 },
|
||||
{ version: "1.18.0", run: m17 }
|
||||
// Add new migrations here as they are created
|
||||
] as {
|
||||
version: string;
|
||||
|
||||
@@ -40,6 +40,7 @@ import m34 from "./scriptsSqlite/1.15.3";
|
||||
import m35 from "./scriptsSqlite/1.15.4";
|
||||
import m36 from "./scriptsSqlite/1.16.0";
|
||||
import m37 from "./scriptsSqlite/1.17.0";
|
||||
import m38 from "./scriptsSqlite/1.18.0";
|
||||
|
||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||
@@ -77,7 +78,8 @@ const migrations = [
|
||||
{ version: "1.15.3", run: m34 },
|
||||
{ version: "1.15.4", run: m35 },
|
||||
{ version: "1.16.0", run: m36 },
|
||||
{ version: "1.17.0", run: m37 }
|
||||
{ version: "1.17.0", run: m37 },
|
||||
{ version: "1.18.0", run: m38 }
|
||||
// Add new migrations here as they are created
|
||||
] as const;
|
||||
|
||||
|
||||
434
server/setup/scriptsPg/1.18.0.ts
Normal file
434
server/setup/scriptsPg/1.18.0.ts
Normal file
@@ -0,0 +1,434 @@
|
||||
import { db } from "@server/db/pg/driver";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
const version = "1.18.0";
|
||||
|
||||
export default async function migration() {
|
||||
console.log(`Running setup script ${version}...`);
|
||||
|
||||
// Query existing targetHealthCheck data with joined siteId and orgId before
|
||||
// the transaction adds the new columns (which start NULL for existing rows).
|
||||
// We will delete all rows and reinsert them with targetHealthCheckId = targetId
|
||||
// so the two IDs form a stable 1:1 mapping.
|
||||
const healthChecksQuery = await db.execute(
|
||||
sql`SELECT
|
||||
thc."targetHealthCheckId",
|
||||
thc."targetId",
|
||||
t."siteId",
|
||||
s."orgId",
|
||||
thc."hcEnabled",
|
||||
thc."hcPath",
|
||||
thc."hcScheme",
|
||||
thc."hcMode",
|
||||
thc."hcHostname",
|
||||
thc."hcPort",
|
||||
thc."hcInterval",
|
||||
thc."hcUnhealthyInterval",
|
||||
thc."hcTimeout",
|
||||
thc."hcHeaders",
|
||||
thc."hcFollowRedirects",
|
||||
thc."hcMethod",
|
||||
thc."hcStatus",
|
||||
thc."hcHealth",
|
||||
thc."hcTlsServerName"
|
||||
FROM "targetHealthCheck" thc
|
||||
JOIN "targets" t ON thc."targetId" = t."targetId"
|
||||
JOIN "sites" s ON t."siteId" = s."siteId"`
|
||||
);
|
||||
const existingHealthChecks = healthChecksQuery.rows as {
|
||||
targetHealthCheckId: number;
|
||||
targetId: number;
|
||||
siteId: number;
|
||||
orgId: string;
|
||||
hcEnabled: boolean;
|
||||
hcPath: string | null;
|
||||
hcScheme: string | null;
|
||||
hcMode: string | null;
|
||||
hcHostname: string | null;
|
||||
hcPort: number | null;
|
||||
hcInterval: number | null;
|
||||
hcUnhealthyInterval: number | null;
|
||||
hcTimeout: number | null;
|
||||
hcHeaders: string | null;
|
||||
hcFollowRedirects: boolean | null;
|
||||
hcMethod: string | null;
|
||||
hcStatus: number | null;
|
||||
hcHealth: string | null;
|
||||
hcTlsServerName: string | null;
|
||||
}[];
|
||||
|
||||
console.log(
|
||||
`Found ${existingHealthChecks.length} existing targetHealthCheck row(s) to migrate`
|
||||
);
|
||||
|
||||
try {
|
||||
await db.execute(sql`BEGIN`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "alertEmailActions" (
|
||||
"emailActionId" serial PRIMARY KEY NOT NULL,
|
||||
"alertRuleId" integer NOT NULL,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"lastSentAt" bigint
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "alertEmailRecipients" (
|
||||
"recipientId" serial PRIMARY KEY NOT NULL,
|
||||
"emailActionId" integer NOT NULL,
|
||||
"userId" varchar,
|
||||
"roleId" integer,
|
||||
"email" varchar(255)
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "alertHealthChecks" (
|
||||
"alertRuleId" integer NOT NULL,
|
||||
"healthCheckId" integer NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "alertResources" (
|
||||
"alertRuleId" integer NOT NULL,
|
||||
"resourceId" integer NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "alertRules" (
|
||||
"alertRuleId" serial PRIMARY KEY NOT NULL,
|
||||
"orgId" varchar(255) NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"eventType" varchar(100) NOT NULL,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"cooldownSeconds" integer DEFAULT 300 NOT NULL,
|
||||
"allSites" boolean DEFAULT false NOT NULL,
|
||||
"allHealthChecks" boolean DEFAULT false NOT NULL,
|
||||
"allResources" boolean DEFAULT false NOT NULL,
|
||||
"lastTriggeredAt" bigint,
|
||||
"createdAt" bigint NOT NULL,
|
||||
"updatedAt" bigint NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "alertSites" (
|
||||
"alertRuleId" integer NOT NULL,
|
||||
"siteId" integer NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "alertWebhookActions" (
|
||||
"webhookActionId" serial PRIMARY KEY NOT NULL,
|
||||
"alertRuleId" integer NOT NULL,
|
||||
"webhookUrl" text NOT NULL,
|
||||
"config" text,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"lastSentAt" bigint
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "networks" (
|
||||
"networkId" serial PRIMARY KEY NOT NULL,
|
||||
"niceId" text,
|
||||
"name" text,
|
||||
"scope" varchar DEFAULT 'global' NOT NULL,
|
||||
"orgId" varchar NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "siteNetworks" (
|
||||
"siteId" integer NOT NULL,
|
||||
"networkId" integer NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "statusHistory" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"entityType" varchar NOT NULL,
|
||||
"entityId" integer NOT NULL,
|
||||
"orgId" varchar NOT NULL,
|
||||
"status" varchar NOT NULL,
|
||||
"timestamp" integer NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" DROP CONSTRAINT "siteResources_siteId_sites_siteId_fk";
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "targetHealthCheck" ALTER COLUMN "targetId" DROP NOT NULL;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "subscriptions" ADD COLUMN "expiresAt" bigint;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "subscriptions" ADD COLUMN "trial" boolean DEFAULT false;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "requestAuditLog" ADD COLUMN "siteResourceId" integer;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" ADD COLUMN "networkId" integer;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" ADD COLUMN "defaultNetworkId" integer;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" ADD COLUMN "ssl" boolean DEFAULT false NOT NULL;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" ADD COLUMN "scheme" varchar;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" ADD COLUMN "domainId" varchar;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" ADD COLUMN "subdomain" varchar;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" ADD COLUMN "fullDomain" varchar;
|
||||
`);
|
||||
|
||||
// Add orgId and siteId as nullable first; NOT NULL constraints are applied
|
||||
// after the data migration below once every row has been populated.
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "targetHealthCheck" ADD COLUMN "orgId" varchar;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "targetHealthCheck" ADD COLUMN "siteId" integer;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "targetHealthCheck" ADD COLUMN "name" varchar;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "targetHealthCheck" ADD COLUMN "hcHealthyThreshold" integer DEFAULT 1;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "targetHealthCheck" ADD COLUMN "hcUnhealthyThreshold" integer DEFAULT 1;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertEmailActions" ADD CONSTRAINT "alertEmailActions_alertRuleId_alertRules_alertRuleId_fk" FOREIGN KEY ("alertRuleId") REFERENCES "public"."alertRules"("alertRuleId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertEmailRecipients" ADD CONSTRAINT "alertEmailRecipients_emailActionId_alertEmailActions_emailActionId_fk" FOREIGN KEY ("emailActionId") REFERENCES "public"."alertEmailActions"("emailActionId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertEmailRecipients" ADD CONSTRAINT "alertEmailRecipients_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertEmailRecipients" ADD CONSTRAINT "alertEmailRecipients_roleId_roles_roleId_fk" FOREIGN KEY ("roleId") REFERENCES "public"."roles"("roleId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertHealthChecks" ADD CONSTRAINT "alertHealthChecks_alertRuleId_alertRules_alertRuleId_fk" FOREIGN KEY ("alertRuleId") REFERENCES "public"."alertRules"("alertRuleId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertHealthChecks" ADD CONSTRAINT "alertHealthChecks_healthCheckId_targetHealthCheck_targetHealthCheckId_fk" FOREIGN KEY ("healthCheckId") REFERENCES "public"."targetHealthCheck"("targetHealthCheckId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertResources" ADD CONSTRAINT "alertResources_alertRuleId_alertRules_alertRuleId_fk" FOREIGN KEY ("alertRuleId") REFERENCES "public"."alertRules"("alertRuleId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertResources" ADD CONSTRAINT "alertResources_resourceId_resources_resourceId_fk" FOREIGN KEY ("resourceId") REFERENCES "public"."resources"("resourceId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertRules" ADD CONSTRAINT "alertRules_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertSites" ADD CONSTRAINT "alertSites_alertRuleId_alertRules_alertRuleId_fk" FOREIGN KEY ("alertRuleId") REFERENCES "public"."alertRules"("alertRuleId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertSites" ADD CONSTRAINT "alertSites_siteId_sites_siteId_fk" FOREIGN KEY ("siteId") REFERENCES "public"."sites"("siteId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "alertWebhookActions" ADD CONSTRAINT "alertWebhookActions_alertRuleId_alertRules_alertRuleId_fk" FOREIGN KEY ("alertRuleId") REFERENCES "public"."alertRules"("alertRuleId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "networks" ADD CONSTRAINT "networks_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteNetworks" ADD CONSTRAINT "siteNetworks_siteId_sites_siteId_fk" FOREIGN KEY ("siteId") REFERENCES "public"."sites"("siteId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteNetworks" ADD CONSTRAINT "siteNetworks_networkId_networks_networkId_fk" FOREIGN KEY ("networkId") REFERENCES "public"."networks"("networkId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "statusHistory" ADD CONSTRAINT "statusHistory_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX "idx_statusHistory_entity" ON "statusHistory" USING btree ("entityType","entityId","timestamp");
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX "idx_statusHistory_org_timestamp" ON "statusHistory" USING btree ("orgId","timestamp");
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" ADD CONSTRAINT "siteResources_networkId_networks_networkId_fk" FOREIGN KEY ("networkId") REFERENCES "public"."networks"("networkId") ON DELETE set null ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" ADD CONSTRAINT "siteResources_defaultNetworkId_networks_networkId_fk" FOREIGN KEY ("defaultNetworkId") REFERENCES "public"."networks"("networkId") ON DELETE restrict ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" ADD CONSTRAINT "siteResources_domainId_domains_domainId_fk" FOREIGN KEY ("domainId") REFERENCES "public"."domains"("domainId") ON DELETE set null ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "targetHealthCheck" ADD CONSTRAINT "targetHealthCheck_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "targetHealthCheck" ADD CONSTRAINT "targetHealthCheck_siteId_sites_siteId_fk" FOREIGN KEY ("siteId") REFERENCES "public"."sites"("siteId") ON DELETE cascade ON UPDATE no action;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" DROP COLUMN "siteId";
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "siteResources" DROP COLUMN "protocol";
|
||||
`);
|
||||
|
||||
await db.execute(sql`COMMIT`);
|
||||
console.log("Migrated database");
|
||||
} catch (e) {
|
||||
await db.execute(sql`ROLLBACK`);
|
||||
console.log("Unable to migrate database");
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Reinsert targetHealthCheck rows with corrected IDs:
|
||||
// targetHealthCheckId is set to the same integer as targetId (1:1 mapping),
|
||||
// siteId and orgId are populated from the associated target and site.
|
||||
//
|
||||
// Because targetHealthCheckId is a serial (sequence-backed) column, inserting
|
||||
// explicit values is allowed in PostgreSQL — the sequence is simply bypassed.
|
||||
// After all inserts we advance the sequence to MAX(targetHealthCheckId) via
|
||||
// setval() so future auto-inserts never collide with the explicit IDs we used.
|
||||
if (existingHealthChecks.length > 0) {
|
||||
try {
|
||||
// Remove all existing rows first. The alertHealthChecks table is brand
|
||||
// new in this migration so there are no FK references to worry about.
|
||||
await db.execute(sql`DELETE FROM "targetHealthCheck"`);
|
||||
|
||||
for (const hc of existingHealthChecks) {
|
||||
await db.execute(sql`
|
||||
INSERT INTO "targetHealthCheck" (
|
||||
"targetHealthCheckId",
|
||||
"targetId",
|
||||
"orgId",
|
||||
"siteId",
|
||||
"hcEnabled",
|
||||
"hcPath",
|
||||
"hcScheme",
|
||||
"hcMode",
|
||||
"hcHostname",
|
||||
"hcPort",
|
||||
"hcInterval",
|
||||
"hcUnhealthyInterval",
|
||||
"hcTimeout",
|
||||
"hcHeaders",
|
||||
"hcFollowRedirects",
|
||||
"hcMethod",
|
||||
"hcStatus",
|
||||
"hcHealth",
|
||||
"hcTlsServerName"
|
||||
) VALUES (
|
||||
${hc.targetId},
|
||||
${hc.targetId},
|
||||
${hc.orgId},
|
||||
${hc.siteId},
|
||||
${hc.hcEnabled},
|
||||
${hc.hcPath},
|
||||
${hc.hcScheme},
|
||||
${hc.hcMode},
|
||||
${hc.hcHostname},
|
||||
${hc.hcPort},
|
||||
${hc.hcInterval},
|
||||
${hc.hcUnhealthyInterval},
|
||||
${hc.hcTimeout},
|
||||
${hc.hcHeaders},
|
||||
${hc.hcFollowRedirects},
|
||||
${hc.hcMethod},
|
||||
${hc.hcStatus},
|
||||
${hc.hcHealth},
|
||||
${hc.hcTlsServerName}
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
// Now that every row has orgId and siteId populated, enforce NOT NULL.
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "targetHealthCheck" ALTER COLUMN "orgId" SET NOT NULL`
|
||||
);
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "targetHealthCheck" ALTER COLUMN "siteId" SET NOT NULL`
|
||||
);
|
||||
|
||||
// Advance the sequence so the next auto-insert picks up after the
|
||||
// largest ID we explicitly wrote. setval(..., max, true) means the
|
||||
// next nextval() call will return max + 1.
|
||||
await db.execute(sql`
|
||||
SELECT setval(
|
||||
pg_get_serial_sequence('"targetHealthCheck"', 'targetHealthCheckId'),
|
||||
(SELECT MAX("targetHealthCheckId") FROM "targetHealthCheck"),
|
||||
true
|
||||
)
|
||||
`);
|
||||
|
||||
console.log(
|
||||
`Migrated ${existingHealthChecks.length} targetHealthCheck row(s) with corrected IDs`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"Error while migrating targetHealthCheck rows:",
|
||||
e
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`${version} migration complete`);
|
||||
}
|
||||
403
server/setup/scriptsSqlite/1.18.0.ts
Normal file
403
server/setup/scriptsSqlite/1.18.0.ts
Normal file
@@ -0,0 +1,403 @@
|
||||
import { APP_PATH } from "@server/lib/consts";
|
||||
import Database from "better-sqlite3";
|
||||
import path from "path";
|
||||
|
||||
const version = "1.18.0";
|
||||
|
||||
export default async function migration() {
|
||||
console.log(`Running setup script ${version}...`);
|
||||
|
||||
const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||
const db = new Database(location);
|
||||
|
||||
try {
|
||||
db.pragma("foreign_keys = OFF");
|
||||
|
||||
// Query existing targetHealthCheck data with joined siteId and orgId before
|
||||
// the transaction drops and recreates the table
|
||||
const existingHealthChecks = db
|
||||
.prepare(
|
||||
`SELECT
|
||||
thc."targetHealthCheckId",
|
||||
thc."targetId",
|
||||
t."siteId",
|
||||
s."orgId",
|
||||
thc."hcEnabled",
|
||||
thc."hcPath",
|
||||
thc."hcScheme",
|
||||
thc."hcMode",
|
||||
thc."hcHostname",
|
||||
thc."hcPort",
|
||||
thc."hcInterval",
|
||||
thc."hcUnhealthyInterval",
|
||||
thc."hcTimeout",
|
||||
thc."hcHeaders",
|
||||
thc."hcFollowRedirects",
|
||||
thc."hcMethod",
|
||||
thc."hcStatus",
|
||||
thc."hcHealth",
|
||||
thc."hcTlsServerName"
|
||||
FROM 'targetHealthCheck' thc
|
||||
JOIN 'targets' t ON thc."targetId" = t."targetId"
|
||||
JOIN 'sites' s ON t."siteId" = s."siteId"`
|
||||
)
|
||||
.all() as {
|
||||
targetHealthCheckId: number;
|
||||
targetId: number;
|
||||
siteId: number;
|
||||
orgId: string;
|
||||
hcEnabled: number;
|
||||
hcPath: string | null;
|
||||
hcScheme: string | null;
|
||||
hcMode: string | null;
|
||||
hcHostname: string | null;
|
||||
hcPort: number | null;
|
||||
hcInterval: number | null;
|
||||
hcUnhealthyInterval: number | null;
|
||||
hcTimeout: number | null;
|
||||
hcHeaders: string | null;
|
||||
hcFollowRedirects: number | null;
|
||||
hcMethod: string | null;
|
||||
hcStatus: number | null;
|
||||
hcHealth: string | null;
|
||||
hcTlsServerName: string | null;
|
||||
}[];
|
||||
|
||||
console.log(
|
||||
`Found ${existingHealthChecks.length} existing targetHealthCheck row(s) to migrate`
|
||||
);
|
||||
|
||||
db.transaction(() => {
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertEmailActions' (
|
||||
'emailActionId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'alertRuleId' integer NOT NULL,
|
||||
'enabled' integer DEFAULT true NOT NULL,
|
||||
'lastSentAt' integer,
|
||||
FOREIGN KEY ('alertRuleId') REFERENCES 'alertRules'('alertRuleId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertEmailRecipients' (
|
||||
'recipientId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'emailActionId' integer NOT NULL,
|
||||
'userId' text,
|
||||
'roleId' integer,
|
||||
'email' text,
|
||||
FOREIGN KEY ('emailActionId') REFERENCES 'alertEmailActions'('emailActionId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('roleId') REFERENCES 'roles'('roleId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertHealthChecks' (
|
||||
'alertRuleId' integer NOT NULL,
|
||||
'healthCheckId' integer NOT NULL,
|
||||
FOREIGN KEY ('alertRuleId') REFERENCES 'alertRules'('alertRuleId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('healthCheckId') REFERENCES 'targetHealthCheck'('targetHealthCheckId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertResources' (
|
||||
'alertRuleId' integer NOT NULL,
|
||||
'resourceId' integer NOT NULL,
|
||||
FOREIGN KEY ('alertRuleId') REFERENCES 'alertRules'('alertRuleId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertRules' (
|
||||
'alertRuleId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'orgId' text NOT NULL,
|
||||
'name' text NOT NULL,
|
||||
'eventType' text NOT NULL,
|
||||
'enabled' integer DEFAULT true NOT NULL,
|
||||
'cooldownSeconds' integer DEFAULT 300 NOT NULL,
|
||||
'allSites' integer DEFAULT false NOT NULL,
|
||||
'allHealthChecks' integer DEFAULT false NOT NULL,
|
||||
'allResources' integer DEFAULT false NOT NULL,
|
||||
'lastTriggeredAt' integer,
|
||||
'createdAt' integer NOT NULL,
|
||||
'updatedAt' integer NOT NULL,
|
||||
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertSites' (
|
||||
'alertRuleId' integer NOT NULL,
|
||||
'siteId' integer NOT NULL,
|
||||
FOREIGN KEY ('alertRuleId') REFERENCES 'alertRules'('alertRuleId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertWebhookActions' (
|
||||
'webhookActionId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'alertRuleId' integer NOT NULL,
|
||||
'webhookUrl' text NOT NULL,
|
||||
'config' text,
|
||||
'enabled' integer DEFAULT true NOT NULL,
|
||||
'lastSentAt' integer,
|
||||
FOREIGN KEY ('alertRuleId') REFERENCES 'alertRules'('alertRuleId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'networks' (
|
||||
'networkId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'niceId' text,
|
||||
'name' text,
|
||||
'scope' text DEFAULT 'global' NOT NULL,
|
||||
'orgId' text NOT NULL,
|
||||
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'siteNetworks' (
|
||||
'siteId' integer NOT NULL,
|
||||
'networkId' integer NOT NULL,
|
||||
FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('networkId') REFERENCES 'networks'('networkId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'statusHistory' (
|
||||
'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'entityType' text NOT NULL,
|
||||
'entityId' integer NOT NULL,
|
||||
'orgId' text NOT NULL,
|
||||
'status' text NOT NULL,
|
||||
'timestamp' integer NOT NULL,
|
||||
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE INDEX 'idx_statusHistory_entity' ON 'statusHistory' ('entityType','entityId','timestamp');
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE INDEX 'idx_statusHistory_org_timestamp' ON 'statusHistory' ('orgId','timestamp');
|
||||
`
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE '__new_siteResources' (
|
||||
'siteResourceId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'orgId' text NOT NULL,
|
||||
'networkId' integer,
|
||||
'defaultNetworkId' integer,
|
||||
'niceId' text NOT NULL,
|
||||
'name' text NOT NULL,
|
||||
'ssl' integer DEFAULT false NOT NULL,
|
||||
'mode' text NOT NULL,
|
||||
'scheme' text,
|
||||
'proxyPort' integer,
|
||||
'destinationPort' integer,
|
||||
'destination' text NOT NULL,
|
||||
'enabled' integer DEFAULT true NOT NULL,
|
||||
'alias' text,
|
||||
'aliasAddress' text,
|
||||
'tcpPortRangeString' text DEFAULT '*' NOT NULL,
|
||||
'udpPortRangeString' text DEFAULT '*' NOT NULL,
|
||||
'disableIcmp' integer DEFAULT false NOT NULL,
|
||||
'authDaemonPort' integer DEFAULT 22123,
|
||||
'authDaemonMode' text DEFAULT 'site',
|
||||
'domainId' text,
|
||||
'subdomain' text,
|
||||
'fullDomain' text,
|
||||
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('networkId') REFERENCES 'networks'('networkId') ON UPDATE no action ON DELETE set null,
|
||||
FOREIGN KEY ('defaultNetworkId') REFERENCES 'networks'('networkId') ON UPDATE no action ON DELETE restrict,
|
||||
FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE set null
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO '__new_siteResources'("siteResourceId", "orgId", "networkId", "defaultNetworkId", "niceId", "name", "ssl", "mode", "scheme", "proxyPort", "destinationPort", "destination", "enabled", "alias", "aliasAddress", "tcpPortRangeString", "udpPortRangeString", "disableIcmp", "authDaemonPort", "authDaemonMode", "domainId", "subdomain", "fullDomain") SELECT "siteResourceId", "orgId", "networkId", "defaultNetworkId", "niceId", "name", "ssl", "mode", "scheme", "proxyPort", "destinationPort", "destination", "enabled", "alias", "aliasAddress", "tcpPortRangeString", "udpPortRangeString", "disableIcmp", "authDaemonPort", "authDaemonMode", "domainId", "subdomain", "fullDomain" FROM 'siteResources';
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
DROP TABLE 'siteResources';
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE '__new_siteResources' RENAME TO 'siteResources';
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE '__new_targetHealthCheck' (
|
||||
'targetHealthCheckId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'targetId' integer,
|
||||
'orgId' text NOT NULL,
|
||||
'siteId' integer NOT NULL,
|
||||
'name' text,
|
||||
'hcEnabled' integer DEFAULT false NOT NULL,
|
||||
'hcPath' text,
|
||||
'hcScheme' text,
|
||||
'hcMode' text DEFAULT 'http',
|
||||
'hcHostname' text,
|
||||
'hcPort' integer,
|
||||
'hcInterval' integer DEFAULT 30,
|
||||
'hcUnhealthyInterval' integer DEFAULT 30,
|
||||
'hcTimeout' integer DEFAULT 5,
|
||||
'hcHeaders' text,
|
||||
'hcFollowRedirects' integer DEFAULT true,
|
||||
'hcMethod' text DEFAULT 'GET',
|
||||
'hcStatus' integer,
|
||||
'hcHealth' text DEFAULT 'unknown',
|
||||
'hcTlsServerName' text,
|
||||
'hcHealthyThreshold' integer DEFAULT 1,
|
||||
'hcUnhealthyThreshold' integer DEFAULT 1,
|
||||
FOREIGN KEY ('targetId') REFERENCES 'targets'('targetId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
// INSERT INTO '__new_targetHealthCheck'("targetHealthCheckId", "targetId", "orgId", "siteId", "name", "hcEnabled", "hcPath", "hcScheme", "hcMode", "hcHostname", "hcPort", "hcInterval", "hcUnhealthyInterval", "hcTimeout", "hcHeaders", "hcFollowRedirects", "hcMethod", "hcStatus", "hcHealth", "hcTlsServerName", "hcHealthyThreshold", "hcUnhealthyThreshold") SELECT "targetHealthCheckId", "targetId", "orgId", "siteId", "name", "hcEnabled", "hcPath", "hcScheme", "hcMode", "hcHostname", "hcPort", "hcInterval", "hcUnhealthyInterval", "hcTimeout", "hcHeaders", "hcFollowRedirects", "hcMethod", "hcStatus", "hcHealth", "hcTlsServerName", "hcHealthyThreshold", "hcUnhealthyThreshold" FROM 'targetHealthCheck';
|
||||
db.prepare(
|
||||
`
|
||||
DROP TABLE 'targetHealthCheck';
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE '__new_targetHealthCheck' RENAME TO 'targetHealthCheck';
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE 'subscriptions' ADD 'expiresAt' integer;
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE 'subscriptions' ADD 'trial' integer DEFAULT false;
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE 'requestAuditLog' ADD 'siteResourceId' integer;
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE 'sites' ADD 'networkId' integer REFERENCES networks(networkId);
|
||||
`
|
||||
).run();
|
||||
})();
|
||||
|
||||
db.pragma("foreign_keys = ON");
|
||||
|
||||
// Re-insert targetHealthCheck rows with corrected IDs:
|
||||
// targetHealthCheckId is set to the same integer as targetId (1:1 mapping),
|
||||
// siteId and orgId are populated from the associated target and site.
|
||||
//
|
||||
// Because targetHealthCheckId is AUTOINCREMENT, inserting explicit values is
|
||||
// allowed, but sqlite_sequence must be updated afterwards so future
|
||||
// auto-increments don't reuse or collide with these IDs.
|
||||
if (existingHealthChecks.length > 0) {
|
||||
const insertHealthCheck = db.prepare(
|
||||
`INSERT INTO 'targetHealthCheck' (
|
||||
"targetHealthCheckId",
|
||||
"targetId",
|
||||
"orgId",
|
||||
"siteId",
|
||||
"hcEnabled",
|
||||
"hcPath",
|
||||
"hcScheme",
|
||||
"hcMode",
|
||||
"hcHostname",
|
||||
"hcPort",
|
||||
"hcInterval",
|
||||
"hcUnhealthyInterval",
|
||||
"hcTimeout",
|
||||
"hcHeaders",
|
||||
"hcFollowRedirects",
|
||||
"hcMethod",
|
||||
"hcStatus",
|
||||
"hcHealth",
|
||||
"hcTlsServerName"
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
);
|
||||
|
||||
const insertAll = db.transaction(() => {
|
||||
for (const hc of existingHealthChecks) {
|
||||
insertHealthCheck.run(
|
||||
hc.targetId, // targetHealthCheckId = targetId (explicit, non-sequential is fine)
|
||||
hc.targetId,
|
||||
hc.orgId,
|
||||
hc.siteId,
|
||||
hc.hcEnabled,
|
||||
hc.hcPath,
|
||||
hc.hcScheme,
|
||||
hc.hcMode,
|
||||
hc.hcHostname,
|
||||
hc.hcPort,
|
||||
hc.hcInterval,
|
||||
hc.hcUnhealthyInterval,
|
||||
hc.hcTimeout,
|
||||
hc.hcHeaders,
|
||||
hc.hcFollowRedirects,
|
||||
hc.hcMethod,
|
||||
hc.hcStatus,
|
||||
hc.hcHealth,
|
||||
hc.hcTlsServerName
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
insertAll();
|
||||
|
||||
// Ensure sqlite_sequence reflects the true max so that future
|
||||
// AUTOINCREMENT inserts never reuse one of the explicitly-set IDs.
|
||||
// INSERT OR IGNORE handles the case where no auto-insert has happened
|
||||
// yet and the row doesn't exist in sqlite_sequence.
|
||||
db.prepare(
|
||||
`INSERT OR IGNORE INTO sqlite_sequence (name, seq) VALUES ('targetHealthCheck', 0)`
|
||||
).run();
|
||||
db.prepare(
|
||||
`UPDATE sqlite_sequence
|
||||
SET seq = MAX(seq, (SELECT COALESCE(MAX("targetHealthCheckId"), 0) FROM 'targetHealthCheck'))
|
||||
WHERE name = 'targetHealthCheck'`
|
||||
).run();
|
||||
|
||||
console.log(
|
||||
`Migrated ${existingHealthChecks.length} targetHealthCheck row(s) with corrected IDs`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`Migrated database`);
|
||||
} catch (e) {
|
||||
console.log("Failed to migrate db:", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
console.log(`${version} migration complete`);
|
||||
}
|
||||
Reference in New Issue
Block a user