Let an operator authenticate with a seeded key, and reset the schema
- TERDUT_OPERATOR_KEY creates or re-keys the instance-scoped service account
"terdut-operator" at every start, so terdut-operator needs no bootstrap
handshake. An instance-scoped account now acts as owner of every team's
configuration, but is not a member of any team.
- POST /api/teams takes an external_id (instance service accounts only) and
is idempotent on it, so automation finds its own team again after a crash
instead of adopting by display name. GET /api/teams?name= is removed.
- Integration and dead man's switch names are unique per team (409). The
escalation PUT accepts usernames and resolves them itself.
- The 18 migrations are squashed into 001_schema.sql, with no Default team.
TERDUT_DEADMAN_* and the env seeding of switches are removed: teams carry
their own. Existing development databases must be recreated.
Security and robustness:
- GET /api/users no longer returns other people's email or ntfy topic to
non-admins.
- The access log records the route pattern, so integration keys and ack
tokens in the path are not written to the log. Server errors are logged.
- Rate limits take the client address TERDUT_TRUSTED_PROXIES hops from the
right of X-Forwarded-For instead of trusting the first, forgeable entry.
- /api/bootstrap runs in a transaction under an advisory lock, so two
concurrent calls cannot both create an administrator.
- API key last_used_at is written at most every five minutes.
Cleanup: remove GET /api/incidents/{id}/alerts, unused exports, SQLite
remnants in comments and config.
Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
This commit is contained in:
+3
-5
@@ -35,8 +35,7 @@ const (
|
||||
//
|
||||
// The pool is modest on purpose: this server's concurrency comes from a handful
|
||||
// of HTTP handlers plus two background loops, and a cloud-native-pg instance
|
||||
// sized for it has a low max_connections. It is still a pool, unlike the single
|
||||
// connection SQLite forced, so the notifier no longer blocks a webhook.
|
||||
// sized for it has a low max_connections.
|
||||
func Open(dsn string) (*sql.DB, error) {
|
||||
if dsn == "" {
|
||||
return nil, fmt.Errorf("empty DSN: set TERDUT_DB_DSN")
|
||||
@@ -76,9 +75,8 @@ const migrationLockKey int64 = 7265_0003
|
||||
// Migrate applies every embedded migration that has not been applied yet, in
|
||||
// filename order, recording each in schema_migrations.
|
||||
//
|
||||
// Each file runs inside a transaction, which SQLite's version did not do: a
|
||||
// migration that failed half way used to leave the schema in whatever state it
|
||||
// had reached. Postgres has transactional DDL, so the rollback is real.
|
||||
// Each file runs inside a transaction, so a migration that fails half way
|
||||
// leaves the schema as it was: Postgres has transactional DDL.
|
||||
func Migrate(db *sql.DB) error {
|
||||
ctx := context.Background()
|
||||
conn, err := db.Conn(ctx)
|
||||
|
||||
@@ -0,0 +1,587 @@
|
||||
-- Terdut Server schema. One baseline: the project has not shipped, so the
|
||||
-- migration history that led here (SQLite import, a Default team, per-team
|
||||
-- deadman configs later replaced by switches) is not carried. Later changes are
|
||||
-- new numbered files after this one.
|
||||
--
|
||||
-- Timestamps are Unix epoch seconds in BIGINT columns throughout.
|
||||
|
||||
CREATE TABLE alerts (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
fingerprint text NOT NULL,
|
||||
name text NOT NULL,
|
||||
status text NOT NULL,
|
||||
labels jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
annotations jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
starts_at bigint NOT NULL,
|
||||
ends_at bigint,
|
||||
generator_url text DEFAULT ''::text NOT NULL,
|
||||
received_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
archived_at bigint,
|
||||
resolution_source text,
|
||||
team_id bigint NOT NULL,
|
||||
integration_id bigint,
|
||||
CONSTRAINT alerts_status_check CHECK ((status = ANY (ARRAY['firing'::text, 'resolved'::text])))
|
||||
);
|
||||
|
||||
CREATE TABLE api_keys (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
key_hash text NOT NULL,
|
||||
name text NOT NULL,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
last_used_at bigint,
|
||||
expires_at bigint
|
||||
);
|
||||
|
||||
CREATE TABLE deadman_switches (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
team_id bigint NOT NULL,
|
||||
name text NOT NULL,
|
||||
matcher text NOT NULL,
|
||||
timeout_seconds bigint NOT NULL,
|
||||
severity text DEFAULT 'critical'::text NOT NULL,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
CONSTRAINT deadman_switches_timeout_seconds_check CHECK ((timeout_seconds > 0))
|
||||
);
|
||||
|
||||
CREATE TABLE device_logins (
|
||||
device_hash text NOT NULL,
|
||||
user_code text NOT NULL,
|
||||
status text DEFAULT 'pending'::text NOT NULL,
|
||||
user_id bigint,
|
||||
expires_at bigint NOT NULL,
|
||||
last_polled_at bigint DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT device_logins_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'approved'::text, 'denied'::text])))
|
||||
);
|
||||
|
||||
CREATE TABLE escalation_levels (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
team_id bigint NOT NULL,
|
||||
"position" bigint NOT NULL,
|
||||
timeout_seconds bigint NOT NULL,
|
||||
CONSTRAINT escalation_levels_timeout_seconds_check CHECK ((timeout_seconds > 0))
|
||||
);
|
||||
|
||||
CREATE TABLE escalation_policies (
|
||||
team_id bigint NOT NULL,
|
||||
repeat_count bigint DEFAULT 0 NOT NULL,
|
||||
fallback_topic text DEFAULT ''::text NOT NULL,
|
||||
updated_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
CONSTRAINT escalation_policies_repeat_count_check CHECK (((repeat_count >= 0) AND (repeat_count <= 10)))
|
||||
);
|
||||
|
||||
CREATE TABLE escalation_targets (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
level_id bigint NOT NULL,
|
||||
kind text NOT NULL,
|
||||
user_id bigint,
|
||||
CONSTRAINT escalation_targets_check CHECK ((((kind = 'user'::text) AND (user_id IS NOT NULL)) OR ((kind = 'oncall'::text) AND (user_id IS NULL)))),
|
||||
CONSTRAINT escalation_targets_kind_check CHECK ((kind = ANY (ARRAY['user'::text, 'oncall'::text])))
|
||||
);
|
||||
|
||||
CREATE TABLE incident_ack_tokens (
|
||||
token_hash text NOT NULL,
|
||||
incident_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
created_at bigint NOT NULL,
|
||||
expires_at bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE incident_alerts (
|
||||
incident_id bigint NOT NULL,
|
||||
alert_id bigint NOT NULL,
|
||||
added_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE incident_events (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
incident_id bigint NOT NULL,
|
||||
type text NOT NULL,
|
||||
user_id bigint,
|
||||
alert_id bigint,
|
||||
detail text,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
service_account_id bigint,
|
||||
actor_user_id bigint,
|
||||
actor_service_account_id bigint,
|
||||
CONSTRAINT incident_events_actor_xor_chk CHECK (((user_id IS NULL) OR (service_account_id IS NULL))),
|
||||
CONSTRAINT incident_events_assign_actor_xor_chk CHECK (((actor_user_id IS NULL) OR (actor_service_account_id IS NULL)))
|
||||
);
|
||||
|
||||
CREATE TABLE incidents (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
group_key text NOT NULL,
|
||||
title text NOT NULL,
|
||||
group_labels jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
status text NOT NULL,
|
||||
severity text,
|
||||
triggered_at bigint NOT NULL,
|
||||
acknowledged_by bigint,
|
||||
acknowledged_at bigint,
|
||||
assigned_to bigint,
|
||||
snoozed_until bigint,
|
||||
resolved_at bigint,
|
||||
resolution_source text,
|
||||
archived_at bigint,
|
||||
team_id bigint NOT NULL,
|
||||
escalation_level bigint DEFAULT 0 NOT NULL,
|
||||
escalation_level_at bigint,
|
||||
escalation_round bigint DEFAULT 0 NOT NULL,
|
||||
signature text DEFAULT ''::text NOT NULL,
|
||||
acknowledged_by_service_account_id bigint,
|
||||
CONSTRAINT incidents_ack_actor_xor_chk CHECK (((acknowledged_by IS NULL) OR (acknowledged_by_service_account_id IS NULL))),
|
||||
CONSTRAINT incidents_status_check CHECK ((status = ANY (ARRAY['triggered'::text, 'acknowledged'::text, 'resolved'::text])))
|
||||
);
|
||||
|
||||
CREATE TABLE integrations (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
team_id bigint NOT NULL,
|
||||
kind text NOT NULL,
|
||||
name text NOT NULL,
|
||||
key_hash text NOT NULL,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
last_used_at bigint,
|
||||
CONSTRAINT integrations_kind_check CHECK ((kind = 'alertmanager'::text))
|
||||
);
|
||||
|
||||
CREATE TABLE invites (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
token_hash text NOT NULL,
|
||||
team_id bigint NOT NULL,
|
||||
role text NOT NULL,
|
||||
created_by bigint,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
expires_at bigint NOT NULL,
|
||||
max_uses bigint DEFAULT 1 NOT NULL,
|
||||
uses bigint DEFAULT 0 NOT NULL,
|
||||
revoked_at bigint,
|
||||
CONSTRAINT invites_max_uses_check CHECK (((max_uses > 0) AND (max_uses <= 100))),
|
||||
CONSTRAINT invites_role_check CHECK ((role = ANY (ARRAY['owner'::text, 'member'::text])))
|
||||
);
|
||||
|
||||
CREATE TABLE notifications (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
incident_id bigint NOT NULL,
|
||||
user_id bigint,
|
||||
topic text NOT NULL,
|
||||
kind text NOT NULL,
|
||||
created_at bigint NOT NULL,
|
||||
send_after bigint NOT NULL,
|
||||
attempts bigint DEFAULT 0 NOT NULL,
|
||||
sent_at bigint,
|
||||
last_error text,
|
||||
CONSTRAINT notifications_kind_check CHECK ((kind = ANY (ARRAY['triggered'::text, 'reminder'::text, 'resolved'::text, 'escalated'::text])))
|
||||
);
|
||||
|
||||
CREATE TABLE oidc_logins (
|
||||
state_hash text NOT NULL,
|
||||
nonce text NOT NULL,
|
||||
pkce_verifier text NOT NULL,
|
||||
expires_at bigint NOT NULL,
|
||||
next text DEFAULT '/'::text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE rate_limit_counters (
|
||||
key text NOT NULL,
|
||||
window_start bigint NOT NULL,
|
||||
count integer NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE schedule_entries (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
date text NOT NULL,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
team_id bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE service_account_keys (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
service_account_id bigint NOT NULL,
|
||||
key_hash text NOT NULL,
|
||||
name text NOT NULL,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
last_used_at bigint
|
||||
);
|
||||
|
||||
CREATE TABLE service_accounts (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
name text NOT NULL,
|
||||
scope text NOT NULL,
|
||||
team_id bigint,
|
||||
created_by bigint,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
CONSTRAINT service_accounts_scope_check CHECK ((scope = ANY (ARRAY['instance'::text, 'team'::text]))),
|
||||
CONSTRAINT service_accounts_scope_team_id_chk CHECK ((((scope = 'team'::text) AND (team_id IS NOT NULL)) OR ((scope = 'instance'::text) AND (team_id IS NULL))))
|
||||
);
|
||||
|
||||
CREATE TABLE sessions (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
token_hash text NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
created_at bigint NOT NULL,
|
||||
last_seen_at bigint NOT NULL,
|
||||
expires_at bigint NOT NULL,
|
||||
user_agent text,
|
||||
max_expires_at bigint
|
||||
);
|
||||
|
||||
CREATE TABLE settings (
|
||||
key text NOT NULL,
|
||||
value text NOT NULL,
|
||||
updated_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE team_members (
|
||||
team_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
role text NOT NULL,
|
||||
joined_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
source text DEFAULT 'manual'::text NOT NULL,
|
||||
CONSTRAINT team_members_role_check CHECK ((role = ANY (ARRAY['owner'::text, 'member'::text]))),
|
||||
CONSTRAINT team_members_source_check CHECK ((source = ANY (ARRAY['manual'::text, 'oidc'::text])))
|
||||
);
|
||||
|
||||
CREATE TABLE teams (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
name text NOT NULL,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
oidc_member_group text,
|
||||
oidc_owner_group text,
|
||||
-- A stable identity for a team managed by automation (terdut-operator:
|
||||
-- "<namespace>/<name>" of its TerdutTeam), so it can find or recreate its own
|
||||
-- team without trusting a display name. NULL for a team a person made.
|
||||
external_id text
|
||||
);
|
||||
|
||||
CREATE TABLE user_identities (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
issuer text NOT NULL,
|
||||
subject text NOT NULL,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
last_login_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE users (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
username text NOT NULL,
|
||||
email text NOT NULL,
|
||||
created_at bigint DEFAULT (floor(EXTRACT(epoch FROM now())))::bigint NOT NULL,
|
||||
ntfy_topic text,
|
||||
password_hash text,
|
||||
is_admin boolean DEFAULT false NOT NULL,
|
||||
disabled_at bigint,
|
||||
invited_via bigint,
|
||||
onboarding_dismissed_at bigint,
|
||||
admin_source text DEFAULT 'manual'::text NOT NULL,
|
||||
CONSTRAINT users_admin_source_check CHECK ((admin_source = ANY (ARRAY['manual'::text, 'oidc'::text])))
|
||||
);
|
||||
|
||||
ALTER TABLE alerts
|
||||
ADD CONSTRAINT alerts_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE api_keys
|
||||
ADD CONSTRAINT api_keys_key_hash_key UNIQUE (key_hash);
|
||||
|
||||
ALTER TABLE api_keys
|
||||
ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE deadman_switches
|
||||
ADD CONSTRAINT deadman_switches_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE device_logins
|
||||
ADD CONSTRAINT device_logins_pkey PRIMARY KEY (device_hash);
|
||||
|
||||
ALTER TABLE device_logins
|
||||
ADD CONSTRAINT device_logins_user_code_key UNIQUE (user_code);
|
||||
|
||||
ALTER TABLE escalation_levels
|
||||
ADD CONSTRAINT escalation_levels_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE escalation_levels
|
||||
ADD CONSTRAINT escalation_levels_team_id_position_key UNIQUE (team_id, "position");
|
||||
|
||||
ALTER TABLE escalation_policies
|
||||
ADD CONSTRAINT escalation_policies_pkey PRIMARY KEY (team_id);
|
||||
|
||||
ALTER TABLE escalation_targets
|
||||
ADD CONSTRAINT escalation_targets_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE incident_ack_tokens
|
||||
ADD CONSTRAINT incident_ack_tokens_pkey PRIMARY KEY (token_hash);
|
||||
|
||||
ALTER TABLE incident_alerts
|
||||
ADD CONSTRAINT incident_alerts_pkey PRIMARY KEY (incident_id, alert_id);
|
||||
|
||||
ALTER TABLE incident_events
|
||||
ADD CONSTRAINT incident_events_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE incidents
|
||||
ADD CONSTRAINT incidents_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE integrations
|
||||
ADD CONSTRAINT integrations_key_hash_key UNIQUE (key_hash);
|
||||
|
||||
ALTER TABLE integrations
|
||||
ADD CONSTRAINT integrations_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE invites
|
||||
ADD CONSTRAINT invites_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE invites
|
||||
ADD CONSTRAINT invites_token_hash_key UNIQUE (token_hash);
|
||||
|
||||
ALTER TABLE notifications
|
||||
ADD CONSTRAINT notifications_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE oidc_logins
|
||||
ADD CONSTRAINT oidc_logins_pkey PRIMARY KEY (state_hash);
|
||||
|
||||
ALTER TABLE rate_limit_counters
|
||||
ADD CONSTRAINT rate_limit_counters_pkey PRIMARY KEY (key);
|
||||
|
||||
ALTER TABLE schedule_entries
|
||||
ADD CONSTRAINT schedule_entries_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE service_account_keys
|
||||
ADD CONSTRAINT service_account_keys_key_hash_key UNIQUE (key_hash);
|
||||
|
||||
ALTER TABLE service_account_keys
|
||||
ADD CONSTRAINT service_account_keys_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE service_accounts
|
||||
ADD CONSTRAINT service_accounts_name_key UNIQUE (name);
|
||||
|
||||
ALTER TABLE service_accounts
|
||||
ADD CONSTRAINT service_accounts_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE sessions
|
||||
ADD CONSTRAINT sessions_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE sessions
|
||||
ADD CONSTRAINT sessions_token_hash_key UNIQUE (token_hash);
|
||||
|
||||
ALTER TABLE settings
|
||||
ADD CONSTRAINT settings_pkey PRIMARY KEY (key);
|
||||
|
||||
ALTER TABLE team_members
|
||||
ADD CONSTRAINT team_members_pkey PRIMARY KEY (team_id, user_id);
|
||||
|
||||
ALTER TABLE teams
|
||||
ADD CONSTRAINT teams_name_key UNIQUE (name);
|
||||
|
||||
ALTER TABLE teams
|
||||
ADD CONSTRAINT teams_external_id_key UNIQUE (external_id);
|
||||
|
||||
ALTER TABLE teams
|
||||
ADD CONSTRAINT teams_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE user_identities
|
||||
ADD CONSTRAINT user_identities_issuer_subject_key UNIQUE (issuer, subject);
|
||||
|
||||
ALTER TABLE user_identities
|
||||
ADD CONSTRAINT user_identities_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT users_email_key UNIQUE (email);
|
||||
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT users_username_key UNIQUE (username);
|
||||
|
||||
CREATE INDEX alerts_archived_at_idx ON alerts USING btree (archived_at);
|
||||
|
||||
CREATE INDEX alerts_integration_idx ON alerts USING btree (integration_id, received_at) WHERE (integration_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX alerts_name_idx ON alerts USING btree (name);
|
||||
|
||||
CREATE INDEX alerts_received_at_idx ON alerts USING btree (received_at DESC);
|
||||
|
||||
CREATE INDEX alerts_status_idx ON alerts USING btree (status);
|
||||
|
||||
CREATE UNIQUE INDEX alerts_team_fingerprint_idx ON alerts USING btree (team_id, fingerprint);
|
||||
|
||||
CREATE INDEX alerts_team_received_idx ON alerts USING btree (team_id, received_at DESC);
|
||||
|
||||
CREATE INDEX deadman_switches_team_idx ON deadman_switches USING btree (team_id);
|
||||
|
||||
CREATE INDEX device_logins_expires_idx ON device_logins USING btree (expires_at);
|
||||
|
||||
CREATE INDEX escalation_targets_level_idx ON escalation_targets USING btree (level_id);
|
||||
|
||||
CREATE INDEX idx_sessions_user ON sessions USING btree (user_id);
|
||||
|
||||
CREATE INDEX incident_ack_tokens_expires_idx ON incident_ack_tokens USING btree (expires_at);
|
||||
|
||||
CREATE INDEX incident_alerts_alert_id_idx ON incident_alerts USING btree (alert_id);
|
||||
|
||||
CREATE INDEX incident_events_actor_service_account_id_idx ON incident_events USING btree (actor_service_account_id);
|
||||
|
||||
CREATE INDEX incident_events_actor_user_id_idx ON incident_events USING btree (actor_user_id);
|
||||
|
||||
CREATE INDEX incident_events_incident_idx ON incident_events USING btree (incident_id, created_at);
|
||||
|
||||
CREATE INDEX incident_events_service_account_id_idx ON incident_events USING btree (service_account_id);
|
||||
|
||||
CREATE INDEX incidents_acknowledged_by_service_account_id_idx ON incidents USING btree (acknowledged_by_service_account_id);
|
||||
|
||||
CREATE INDEX incidents_archived_at_idx ON incidents USING btree (archived_at);
|
||||
|
||||
CREATE INDEX incidents_escalation_idx ON incidents USING btree (escalation_level_at) WHERE ((resolved_at IS NULL) AND (status = 'triggered'::text));
|
||||
|
||||
CREATE UNIQUE INDEX incidents_open_group_key_idx ON incidents USING btree (team_id, group_key) WHERE (resolved_at IS NULL);
|
||||
|
||||
CREATE INDEX incidents_signature_idx ON incidents USING btree (team_id, signature, triggered_at DESC);
|
||||
|
||||
CREATE INDEX incidents_status_idx ON incidents USING btree (status);
|
||||
|
||||
CREATE INDEX incidents_team_triggered_idx ON incidents USING btree (team_id, triggered_at DESC);
|
||||
|
||||
CREATE INDEX incidents_triggered_at_idx ON incidents USING btree (triggered_at DESC);
|
||||
|
||||
CREATE INDEX integrations_team_idx ON integrations USING btree (team_id);
|
||||
|
||||
-- A name identifies an integration (and a switch) within its team, so a client
|
||||
-- that manages them declaratively can look one up by name instead of listing
|
||||
-- and matching.
|
||||
CREATE UNIQUE INDEX integrations_team_name_key ON integrations (team_id, name);
|
||||
CREATE UNIQUE INDEX deadman_switches_team_name_key ON deadman_switches (team_id, name);
|
||||
|
||||
CREATE INDEX invites_team_idx ON invites USING btree (team_id);
|
||||
|
||||
CREATE INDEX notifications_incident_idx ON notifications USING btree (incident_id, id DESC);
|
||||
|
||||
CREATE INDEX notifications_pending_idx ON notifications USING btree (send_after) WHERE (sent_at IS NULL);
|
||||
|
||||
CREATE INDEX oidc_logins_expires_idx ON oidc_logins USING btree (expires_at);
|
||||
|
||||
CREATE INDEX schedule_entries_date_idx ON schedule_entries USING btree (date);
|
||||
|
||||
CREATE UNIQUE INDEX schedule_entries_team_date_idx ON schedule_entries USING btree (team_id, date);
|
||||
|
||||
CREATE INDEX service_account_keys_service_account_id_idx ON service_account_keys USING btree (service_account_id);
|
||||
|
||||
CREATE INDEX service_accounts_team_id_idx ON service_accounts USING btree (team_id);
|
||||
|
||||
CREATE INDEX team_members_user_idx ON team_members USING btree (user_id);
|
||||
|
||||
CREATE INDEX user_identities_user_idx ON user_identities USING btree (user_id);
|
||||
|
||||
CREATE INDEX users_is_admin_idx ON users USING btree (is_admin) WHERE is_admin;
|
||||
|
||||
ALTER TABLE alerts
|
||||
ADD CONSTRAINT alerts_integration_id_fkey FOREIGN KEY (integration_id) REFERENCES integrations(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE alerts
|
||||
ADD CONSTRAINT alerts_team_id_fkey FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE api_keys
|
||||
ADD CONSTRAINT api_keys_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE deadman_switches
|
||||
ADD CONSTRAINT deadman_switches_team_id_fkey FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE device_logins
|
||||
ADD CONSTRAINT device_logins_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE escalation_levels
|
||||
ADD CONSTRAINT escalation_levels_team_id_fkey FOREIGN KEY (team_id) REFERENCES escalation_policies(team_id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE escalation_policies
|
||||
ADD CONSTRAINT escalation_policies_team_id_fkey FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE escalation_targets
|
||||
ADD CONSTRAINT escalation_targets_level_id_fkey FOREIGN KEY (level_id) REFERENCES escalation_levels(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE escalation_targets
|
||||
ADD CONSTRAINT escalation_targets_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE incident_ack_tokens
|
||||
ADD CONSTRAINT incident_ack_tokens_incident_id_fkey FOREIGN KEY (incident_id) REFERENCES incidents(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE incident_ack_tokens
|
||||
ADD CONSTRAINT incident_ack_tokens_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE incident_alerts
|
||||
ADD CONSTRAINT incident_alerts_alert_id_fkey FOREIGN KEY (alert_id) REFERENCES alerts(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE incident_alerts
|
||||
ADD CONSTRAINT incident_alerts_incident_id_fkey FOREIGN KEY (incident_id) REFERENCES incidents(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE incident_events
|
||||
ADD CONSTRAINT incident_events_actor_service_account_id_fkey FOREIGN KEY (actor_service_account_id) REFERENCES service_accounts(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE incident_events
|
||||
ADD CONSTRAINT incident_events_actor_user_id_fkey FOREIGN KEY (actor_user_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE incident_events
|
||||
ADD CONSTRAINT incident_events_alert_id_fkey FOREIGN KEY (alert_id) REFERENCES alerts(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE incident_events
|
||||
ADD CONSTRAINT incident_events_incident_id_fkey FOREIGN KEY (incident_id) REFERENCES incidents(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE incident_events
|
||||
ADD CONSTRAINT incident_events_service_account_id_fkey FOREIGN KEY (service_account_id) REFERENCES service_accounts(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE incident_events
|
||||
ADD CONSTRAINT incident_events_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE incidents
|
||||
ADD CONSTRAINT incidents_acknowledged_by_fkey FOREIGN KEY (acknowledged_by) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE incidents
|
||||
ADD CONSTRAINT incidents_acknowledged_by_service_account_id_fkey FOREIGN KEY (acknowledged_by_service_account_id) REFERENCES service_accounts(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE incidents
|
||||
ADD CONSTRAINT incidents_assigned_to_fkey FOREIGN KEY (assigned_to) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE incidents
|
||||
ADD CONSTRAINT incidents_team_id_fkey FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE integrations
|
||||
ADD CONSTRAINT integrations_team_id_fkey FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE invites
|
||||
ADD CONSTRAINT invites_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE invites
|
||||
ADD CONSTRAINT invites_team_id_fkey FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE notifications
|
||||
ADD CONSTRAINT notifications_incident_id_fkey FOREIGN KEY (incident_id) REFERENCES incidents(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE notifications
|
||||
ADD CONSTRAINT notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE schedule_entries
|
||||
ADD CONSTRAINT schedule_entries_team_id_fkey FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE schedule_entries
|
||||
ADD CONSTRAINT schedule_entries_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE service_account_keys
|
||||
ADD CONSTRAINT service_account_keys_service_account_id_fkey FOREIGN KEY (service_account_id) REFERENCES service_accounts(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE service_accounts
|
||||
ADD CONSTRAINT service_accounts_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE service_accounts
|
||||
ADD CONSTRAINT service_accounts_team_id_fkey FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE sessions
|
||||
ADD CONSTRAINT sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE team_members
|
||||
ADD CONSTRAINT team_members_team_id_fkey FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE team_members
|
||||
ADD CONSTRAINT team_members_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE user_identities
|
||||
ADD CONSTRAINT user_identities_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT users_invited_via_fkey FOREIGN KEY (invited_via) REFERENCES invites(id) ON DELETE SET NULL;
|
||||
Reference in New Issue
Block a user