Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.sqlx/** linguist-generated=true

100 changes: 0 additions & 100 deletions server/migrations/0001_init.sql

This file was deleted.

9 changes: 9 additions & 0 deletions server/migrations/0001_users.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Users — authentication identities (see `domain::user`).

create table if not exists users (
id uuid primary key,
email text unique not null,
password_hash text not null,
created_at timestamptz not null default now()
);

23 changes: 23 additions & 0 deletions server/migrations/0002_teams_rbac.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- Teams & RBAC — teams and their role-scoped membership.

create table if not exists teams (
id uuid primary key,
name text not null,
-- Human-friendly join handle, e.g. `OPS-A7B9X2`.
invitation_code text unique not null,
created_at timestamptz not null default now()
);

create table if not exists team_members (
team_id uuid not null references teams (id) on delete cascade,
user_id uuid not null references users (id) on delete cascade,
-- Mirrors the `domain::team::Role` enum.
role text not null check (role in ('observer', 'responder', 'manager')),
joined_at timestamptz not null default now(),
primary key (team_id, user_id)
);

-- Storage-level guard for the single-Manager invariant.
create unique index if not exists team_members_one_manager_idx
on team_members (team_id)
where role = 'manager';
11 changes: 11 additions & 0 deletions server/migrations/0003_auth_sessions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Auth sessions — revoked bearer tokens kept until natural expiration.

create table if not exists revoked_tokens (
token_hash text primary key,
expires_at timestamptz not null,
revoked_at timestamptz not null default now()
);

create index if not exists revoked_tokens_expires_at_idx
on revoked_tokens (expires_at);

19 changes: 0 additions & 19 deletions server/migrations/0003_team_bans.sql

This file was deleted.

15 changes: 15 additions & 0 deletions server/migrations/0004_incidents.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- Incidents — lifecycle state tracked inside a team workspace.

create table if not exists incidents (
id uuid primary key,
team_id uuid not null references teams (id) on delete cascade,
title text not null,
status text not null check (status in ('open', 'acknowledged', 'escalated', 'resolved')),
severity text not null check (severity in ('low', 'medium', 'high', 'critical')),
-- Responder assigned by a Manager; nullable means unassigned.
assignee_id uuid references users (id) on delete set null,
created_at timestamptz not null
);

create index if not exists incidents_team_created_at_idx
on incidents (team_id, created_at desc);
25 changes: 0 additions & 25 deletions server/migrations/0004_private_messages.sql

This file was deleted.

39 changes: 0 additions & 39 deletions server/migrations/0005_releases.sql

This file was deleted.

12 changes: 12 additions & 0 deletions server/migrations/0005_timeline.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Incident timeline — timestamped entries authored by team members.

create table if not exists timeline_entries (
id uuid primary key,
incident_id uuid not null references incidents (id) on delete cascade,
author_id uuid not null references users (id) on delete restrict,
content text not null,
created_at timestamptz not null
);

create index if not exists timeline_entries_incident_created_at_idx
on timeline_entries (incident_id, created_at desc);
13 changes: 13 additions & 0 deletions server/migrations/0006_external_secrets.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- External secrets — encrypted third-party credentials.
--
-- AES-256-GCM: only the per-row nonce and ciphertext are stored, so a raw
-- `SELECT * FROM external_secrets` reveals nothing usable. Keyed by service
-- name ("github", later "slack", ...).

create table if not exists external_secrets (
service text primary key,
nonce bytea not null,
ciphertext bytea not null,
updated_at timestamptz not null default now()
);

Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
-- OpsWarden — RTC 2 timeline edit + reactions.
--
-- A separate versioned migration (not a new section in 0001): `sqlx::migrate!`
-- checksum-locks an applied migration, and the running dev database already has
-- 0001 applied with live data we must not drop. New incremental schema therefore
-- ships as its own file.
-- Timeline edit + reactions.

-- ============================================================================
-- Timeline editing — mark an entry as edited while preserving created_at.
-- ============================================================================
alter table timeline_entries
add column if not exists edited_at timestamptz;

-- ============================================================================
-- Timeline reactions — persistent emoji reactions per timeline entry.
-- The composite primary key makes a (user, emoji) reaction on an entry unique,
-- so a user can never duplicate the same emoji on the same entry.
-- ============================================================================
create table if not exists timeline_reactions (
entry_id uuid not null references timeline_entries (id) on delete cascade,
user_id uuid not null references users (id) on delete cascade,
Expand All @@ -24,5 +15,5 @@ create table if not exists timeline_reactions (
primary key (entry_id, user_id, emoji)
);

create index if not exists timeline_reactions_entry_idx
create index if not exists timeline_reactions_entry_id_idx
on timeline_reactions (entry_id);
19 changes: 19 additions & 0 deletions server/migrations/0008_team_bans.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- Team moderation — per-team bans that block rejoining.
--
-- One ban row per (team, user). A NULL expires_at means a permanent ban; a
-- non-NULL value is a temporary ban that stops blocking once it passes.

create table if not exists team_bans (
team_id uuid not null references teams (id) on delete cascade,
user_id uuid not null references users (id) on delete cascade,
expires_at timestamptz,
reason text,
-- Nullable + ON DELETE SET NULL so deleting the moderator's account later
-- never fails on this FK; the ban survives without attribution.
created_by uuid references users (id) on delete set null,
created_at timestamptz not null default now(),
primary key (team_id, user_id)
);

create index if not exists team_bans_team_id_idx
on team_bans (team_id);
20 changes: 20 additions & 0 deletions server/migrations/0009_private_messages.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Private messages — bilateral 1-to-1 direct messages.
--
-- Not tied to an incident, release, or team. Authorization is enforced by the
-- app layer through shared-team membership; the stored pair keeps authorship
-- unambiguous while reads fetch both directions.

create table if not exists private_messages (
id uuid primary key,
sender_id uuid not null references users (id) on delete cascade,
recipient_id uuid not null references users (id) on delete cascade,
content text not null,
created_at timestamptz not null
);

-- A conversation read fetches both directions between two users, newest first.
create index if not exists private_messages_sender_recipient_created_at_idx
on private_messages (sender_id, recipient_id, created_at desc);

create index if not exists private_messages_recipient_sender_created_at_idx
on private_messages (recipient_id, sender_id, created_at desc);
39 changes: 39 additions & 0 deletions server/migrations/0010_releases.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-- Releases — planned deployments coordinated in real time.
--
-- `base_state` is the stored lifecycle (created/in_progress/completed/cancelled).
-- The effective `blocked` state is derived at read/emit time as:
-- base_state = in_progress AND >= 1 linked incident is not resolved.

create table if not exists releases (
id uuid primary key,
team_id uuid not null references teams (id) on delete cascade,
title text not null,
base_state text not null check (base_state in ('created', 'in_progress', 'completed', 'cancelled')),
created_at timestamptz not null
);

create index if not exists releases_team_created_at_idx
on releases (team_id, created_at desc);

-- Ordered steps; a step validates only after the previous one.
-- `validated_by` survives kick/ban because moderation removes membership, not
-- the user account. ON DELETE SET NULL handles later account deletion.
create table if not exists release_steps (
release_id uuid not null references releases (id) on delete cascade,
position integer not null,
name text not null,
validated_by uuid references users (id) on delete set null,
validated_at timestamptz,
primary key (release_id, position)
);

-- Many-to-many incident links. A release is blocked while any linked incident is
-- active (status <> 'resolved'); it unblocks once all linked incidents resolve.
create table if not exists release_incidents (
release_id uuid not null references releases (id) on delete cascade,
incident_id uuid not null references incidents (id) on delete cascade,
primary key (release_id, incident_id)
);

create index if not exists release_incidents_incident_id_idx
on release_incidents (incident_id);