From 960f78a1ade7575c953b9d0db6dd4c0f3723c38d Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Sat, 24 Jan 2026 14:50:15 -0600 Subject: [PATCH] Update initial tables --- express/database.ts | 6 ++-- express/migrations/0001_users.sql | 21 -------------- express/migrations/2026-01-01_01-users.sql | 29 +++++++++++++++++++ ...essions.sql => 2026-01-01_02-sessions.sql} | 9 +++--- .../2026-01-24_01-roles-and-groups.sql | 20 +++++++++++++ .../migrations/2026-01-24_02-capabilities.sql | 14 +++++++++ 6 files changed, 71 insertions(+), 28 deletions(-) delete mode 100644 express/migrations/0001_users.sql create mode 100644 express/migrations/2026-01-01_01-users.sql rename express/migrations/{0002_sessions.sql => 2026-01-01_02-sessions.sql} (77%) create mode 100644 express/migrations/2026-01-24_01-roles-and-groups.sql create mode 100644 express/migrations/2026-01-24_02-capabilities.sql diff --git a/express/database.ts b/express/database.ts index a932a40..49fbabd 100644 --- a/express/database.ts +++ b/express/database.ts @@ -87,8 +87,8 @@ async function raw( // ============================================================================ // Migration file naming convention: -// NNNN_description.sql -// e.g., 0001_initial.sql, 0002_add_users.sql +// yyyy-mm-dd_ss_description.sql +// e.g., 2025-01-15_01_initial.sql, 2025-01-15_02_add_users.sql // // Migrations directory: express/migrations/ @@ -128,7 +128,7 @@ function getMigrationFiles(): string[] { return fs .readdirSync(MIGRATIONS_DIR) .filter((f) => f.endsWith(".sql")) - .filter((f) => /^\d{4}_/.test(f)) + .filter((f) => /^\d{4}-\d{2}-\d{2}_\d{2}-/.test(f)) .sort(); } diff --git a/express/migrations/0001_users.sql b/express/migrations/0001_users.sql deleted file mode 100644 index 8aa8a5d..0000000 --- a/express/migrations/0001_users.sql +++ /dev/null @@ -1,21 +0,0 @@ --- 0001_users.sql --- Create users table for authentication - -CREATE TABLE users ( - id UUID PRIMARY KEY, - email TEXT UNIQUE NOT NULL, - password_hash TEXT NOT NULL, - display_name TEXT, - status TEXT NOT NULL DEFAULT 'pending', - roles TEXT[] NOT NULL DEFAULT '{}', - permissions TEXT[] NOT NULL DEFAULT '{}', - email_verified BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- Index for email lookups (login) -CREATE INDEX users_email_idx ON users (LOWER(email)); - --- Index for status filtering -CREATE INDEX users_status_idx ON users (status); diff --git a/express/migrations/2026-01-01_01-users.sql b/express/migrations/2026-01-01_01-users.sql new file mode 100644 index 0000000..83f7cb7 --- /dev/null +++ b/express/migrations/2026-01-01_01-users.sql @@ -0,0 +1,29 @@ +-- 0001_users.sql +-- Create users table for authentication + +CREATE TABLE users ( + id UUID PRIMARY KEY, + status TEXT NOT NULL DEFAULT 'active', + display_name TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE user_emails ( + id UUID PRIMARY KEY, + user_id UUID NOT NULL REFERENCES users(id), + email TEXT NOT NULL, + normalized_email TEXT NOT NULL, + is_primary BOOLEAN NOT NULL DEFAULT FALSE, + is_verified BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + verified_at TIMESTAMPTZ, + revoked_at TIMESTAMPTZ +); + +-- Enforce uniqueness only among *active* emails +CREATE UNIQUE INDEX user_emails_unique_active +ON user_emails (normalized_email) +WHERE revoked_at IS NULL; + + diff --git a/express/migrations/0002_sessions.sql b/express/migrations/2026-01-01_02-sessions.sql similarity index 77% rename from express/migrations/0002_sessions.sql rename to express/migrations/2026-01-01_02-sessions.sql index 2708f8f..2ad9b33 100644 --- a/express/migrations/0002_sessions.sql +++ b/express/migrations/2026-01-01_02-sessions.sql @@ -2,15 +2,16 @@ -- Create sessions table for auth tokens CREATE TABLE sessions ( - token_id TEXT PRIMARY KEY, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + id UUID PRIMARY KEY, + user_id UUID NOT NULL REFERENCES users(id), + user_email_id UUID REFERENCES user_emails(id), token_type TEXT NOT NULL, auth_method TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), expires_at TIMESTAMPTZ NOT NULL, - last_used_at TIMESTAMPTZ, + revoked_at TIMESTAMPTZ, + ip_address INET, user_agent TEXT, - ip_address TEXT, is_used BOOLEAN DEFAULT FALSE ); diff --git a/express/migrations/2026-01-24_01-roles-and-groups.sql b/express/migrations/2026-01-24_01-roles-and-groups.sql new file mode 100644 index 0000000..e9e7630 --- /dev/null +++ b/express/migrations/2026-01-24_01-roles-and-groups.sql @@ -0,0 +1,20 @@ +CREATE TABLE roles ( + id UUID PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + description TEXT +); + +CREATE TABLE groups ( + id UUID PRIMARY KEY, + name TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE user_group_roles ( + user_id UUID NOT NULL REFERENCES users(id), + group_id UUID NOT NULL REFERENCES groups(id), + role_id UUID NOT NULL REFERENCES roles(id), + granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + revoked_at TIMESTAMPTZ, + PRIMARY KEY (user_id, group_id, role_id) +); diff --git a/express/migrations/2026-01-24_02-capabilities.sql b/express/migrations/2026-01-24_02-capabilities.sql new file mode 100644 index 0000000..12f0329 --- /dev/null +++ b/express/migrations/2026-01-24_02-capabilities.sql @@ -0,0 +1,14 @@ +CREATE TABLE capabilities ( + id UUID PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + description TEXT +); + +CREATE TABLE role_capabilities ( + role_id UUID NOT NULL REFERENCES roles(id), + capability_id UUID NOT NULL REFERENCES capabilities(id), + granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + revoked_at TIMESTAMPTZ, + PRIMARY KEY (role_id, capability_id) +); +