285 lines
14 KiB
TypeScript
285 lines
14 KiB
TypeScript
export async function migrate(sql: any) {
|
|
await sql`CREATE EXTENSION IF NOT EXISTS pgcrypto`;
|
|
|
|
// ── accounts ──────────────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS accounts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
key TEXT NOT NULL UNIQUE,
|
|
email_hash TEXT,
|
|
plan TEXT NOT NULL DEFAULT 'free',
|
|
plan_expires_at TIMESTAMPTZ,
|
|
plan_stack JSONB NOT NULL DEFAULT '[]',
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
)
|
|
`;
|
|
|
|
// ── monitors ──────────────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS monitors (
|
|
id TEXT PRIMARY KEY DEFAULT encode(gen_random_bytes(8), 'hex'),
|
|
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
url TEXT NOT NULL,
|
|
method TEXT NOT NULL DEFAULT 'GET',
|
|
request_headers JSONB,
|
|
request_body TEXT,
|
|
timeout_ms INTEGER NOT NULL DEFAULT 30000,
|
|
interval_s INTEGER NOT NULL DEFAULT 60,
|
|
query JSONB,
|
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
regions TEXT[] NOT NULL DEFAULT '{}',
|
|
max_retries INTEGER NOT NULL DEFAULT 0,
|
|
retry_interval_s INTEGER NOT NULL DEFAULT 30,
|
|
resend_interval INTEGER NOT NULL DEFAULT 0,
|
|
cert_alert_days INTEGER NOT NULL DEFAULT 0,
|
|
max_redirects INTEGER NOT NULL DEFAULT 1,
|
|
tags TEXT[] NOT NULL DEFAULT '{}',
|
|
channel_ids UUID[] NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_monitors_account ON monitors(account_id)`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_monitors_tags ON monitors USING GIN(tags)`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_monitors_channel_ids ON monitors USING GIN(channel_ids)`;
|
|
|
|
// ── pings ─────────────────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS pings (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
monitor_id TEXT NOT NULL REFERENCES monitors(id) ON DELETE CASCADE,
|
|
checked_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
scheduled_at TIMESTAMPTZ,
|
|
jitter_ms INTEGER,
|
|
status_code INTEGER,
|
|
latency_ms INTEGER,
|
|
up BOOLEAN NOT NULL,
|
|
important BOOLEAN NOT NULL DEFAULT false,
|
|
error TEXT,
|
|
meta JSONB,
|
|
region TEXT NOT NULL DEFAULT 'default',
|
|
run_id TEXT,
|
|
cert_expiry_days INTEGER
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_pings_monitor ON pings(monitor_id, checked_at DESC)`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_pings_checked_at ON pings(checked_at)`;
|
|
|
|
// ── ping_bodies ───────────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS ping_bodies (
|
|
ping_id BIGINT PRIMARY KEY REFERENCES pings(id) ON DELETE CASCADE,
|
|
body TEXT
|
|
)
|
|
`;
|
|
|
|
// ── api_keys ──────────────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS api_keys (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
key TEXT NOT NULL UNIQUE,
|
|
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
label TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
last_used_at TIMESTAMPTZ
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_api_keys_account ON api_keys(account_id)`;
|
|
|
|
// ── monitor_region_state ──────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS monitor_region_state (
|
|
monitor_id TEXT NOT NULL REFERENCES monitors(id) ON DELETE CASCADE,
|
|
region TEXT NOT NULL,
|
|
last_state TEXT,
|
|
consecutive_down INTEGER NOT NULL DEFAULT 0,
|
|
cert_alert_sent BOOLEAN NOT NULL DEFAULT false,
|
|
updated_at TIMESTAMPTZ DEFAULT now(),
|
|
PRIMARY KEY (monitor_id, region)
|
|
)
|
|
`;
|
|
|
|
// ── notification_channels ─────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS notification_channels (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
kind TEXT NOT NULL,
|
|
config JSONB NOT NULL,
|
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_notification_channels_account ON notification_channels(account_id)`;
|
|
|
|
// ── status_pages ──────────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS status_pages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
slug TEXT NOT NULL UNIQUE,
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
theme TEXT NOT NULL DEFAULT 'auto',
|
|
password_hash TEXT,
|
|
index_search BOOLEAN NOT NULL DEFAULT true,
|
|
show_powered_by BOOLEAN NOT NULL DEFAULT true,
|
|
show_response_time BOOLEAN NOT NULL DEFAULT true,
|
|
show_cert_expiry BOOLEAN NOT NULL DEFAULT false,
|
|
default_window TEXT NOT NULL DEFAULT '24h',
|
|
display_mode TEXT NOT NULL DEFAULT 'expanded',
|
|
bar_frequency TEXT NOT NULL DEFAULT 'daily',
|
|
bar_count INTEGER NOT NULL DEFAULT 90,
|
|
custom_css TEXT,
|
|
footer_text TEXT,
|
|
og_image_url TEXT,
|
|
analytics_html TEXT,
|
|
auto_refresh_s INTEGER NOT NULL DEFAULT 60,
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_status_pages_account ON status_pages(account_id)`;
|
|
|
|
// ── status_page_groups ────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS status_page_groups (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
status_page_id UUID NOT NULL REFERENCES status_pages(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
position INTEGER NOT NULL DEFAULT 0
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_status_page_groups_page ON status_page_groups(status_page_id)`;
|
|
|
|
// ── status_page_monitors ──────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS status_page_monitors (
|
|
status_page_id UUID NOT NULL REFERENCES status_pages(id) ON DELETE CASCADE,
|
|
monitor_id TEXT NOT NULL REFERENCES monitors(id) ON DELETE CASCADE,
|
|
group_id UUID REFERENCES status_page_groups(id) ON DELETE SET NULL,
|
|
display_name TEXT,
|
|
display_mode TEXT,
|
|
position INTEGER NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (status_page_id, monitor_id)
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_status_page_monitors_monitor ON status_page_monitors(monitor_id)`;
|
|
|
|
// ── incidents ─────────────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS incidents (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
severity TEXT NOT NULL DEFAULT 'minor',
|
|
pinned BOOLEAN NOT NULL DEFAULT true,
|
|
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
resolved_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_incidents_account ON incidents(account_id, started_at DESC)`;
|
|
|
|
// ── incident_updates ──────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS incident_updates (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
incident_id UUID NOT NULL REFERENCES incidents(id) ON DELETE CASCADE,
|
|
status TEXT NOT NULL,
|
|
body TEXT NOT NULL,
|
|
body_html TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_incident_updates_incident ON incident_updates(incident_id, created_at)`;
|
|
|
|
// ── incident_monitors ─────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS incident_monitors (
|
|
incident_id UUID NOT NULL REFERENCES incidents(id) ON DELETE CASCADE,
|
|
monitor_id TEXT NOT NULL REFERENCES monitors(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (incident_id, monitor_id)
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_incident_monitors_monitor ON incident_monitors(monitor_id)`;
|
|
|
|
// ── incident_status_pages ─────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS incident_status_pages (
|
|
incident_id UUID NOT NULL REFERENCES incidents(id) ON DELETE CASCADE,
|
|
status_page_id UUID NOT NULL REFERENCES status_pages(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (incident_id, status_page_id)
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_incident_status_pages_page ON incident_status_pages(status_page_id)`;
|
|
|
|
// ── monitor_uptime_rollup ─────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS monitor_uptime_rollup (
|
|
monitor_id TEXT NOT NULL REFERENCES monitors(id) ON DELETE CASCADE,
|
|
region TEXT NOT NULL,
|
|
bucket_type TEXT NOT NULL,
|
|
bucket_start TIMESTAMPTZ NOT NULL,
|
|
total INTEGER NOT NULL,
|
|
up_count INTEGER NOT NULL,
|
|
avg_latency REAL,
|
|
PRIMARY KEY (monitor_id, region, bucket_type, bucket_start)
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_uptime_rollup_lookup ON monitor_uptime_rollup(monitor_id, bucket_type, bucket_start DESC)`;
|
|
|
|
// ── rollup_watermarks ─────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS rollup_watermarks (
|
|
bucket_type TEXT PRIMARY KEY,
|
|
last_aggregated_at TIMESTAMPTZ NOT NULL DEFAULT to_timestamp(0)
|
|
)
|
|
`;
|
|
|
|
// ── payments ──────────────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS payments (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
plan TEXT NOT NULL,
|
|
months INTEGER,
|
|
amount_usd NUMERIC(10,2) NOT NULL,
|
|
coin TEXT NOT NULL,
|
|
amount_crypto TEXT NOT NULL,
|
|
address TEXT NOT NULL,
|
|
derivation_index INTEGER NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
amount_received TEXT NOT NULL DEFAULT '0',
|
|
receipt_html TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
paid_at TIMESTAMPTZ,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
txid TEXT
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_payments_status ON payments(status)`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_payments_account ON payments(account_id)`;
|
|
await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_payments_coin_derivation ON payments(coin, derivation_index)`;
|
|
|
|
// ── payment_txs ───────────────────────────────────────────────────────
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS payment_txs (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
payment_id BIGINT NOT NULL REFERENCES payments(id) ON DELETE CASCADE,
|
|
txid TEXT NOT NULL,
|
|
amount TEXT NOT NULL,
|
|
confirmed BOOLEAN NOT NULL DEFAULT false,
|
|
detected_at TIMESTAMPTZ DEFAULT now(),
|
|
UNIQUE(payment_id, txid)
|
|
)
|
|
`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_payment_txs_payment ON payment_txs(payment_id)`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_payment_txs_txid ON payment_txs(txid)`;
|
|
|
|
console.log("DB ready");
|
|
}
|