-- User management foundation (U1 of #376): -- -- - users.display_name: optional human-readable name distinct from username. -- - user_invites: admin-generated tokens; user redeems during signup. -- - registration_settings (singleton): controls whether registration is -- 'invite_only' (default after first admin) or 'open' (admin can -- re-enable). The handler in U1-T2 also has a special-case for the -- "no users yet" state where it ignores invites and lets the first -- register as admin. -- - audit_log: append-only record of admin-driven user mutations. -- No retention policy in v1. ALTER TABLE users ADD COLUMN IF NOT EXISTS display_name text; CREATE TABLE user_invites ( token text PRIMARY KEY, invited_by uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, note text, created_at timestamptz NOT NULL DEFAULT now(), expires_at timestamptz NOT NULL, redeemed_at timestamptz, redeemed_by uuid REFERENCES users(id) ON DELETE SET NULL ); CREATE INDEX user_invites_active_idx ON user_invites (expires_at) WHERE redeemed_at IS NULL; CREATE TABLE registration_settings ( id boolean PRIMARY KEY DEFAULT true, mode text NOT NULL DEFAULT 'invite_only', CONSTRAINT registration_settings_singleton CHECK (id = true), CONSTRAINT registration_settings_mode_check CHECK (mode IN ('invite_only', 'open')) ); INSERT INTO registration_settings (id) VALUES (true) ON CONFLICT (id) DO NOTHING; CREATE TABLE audit_log ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), actor_id uuid REFERENCES users(id) ON DELETE SET NULL, target_id uuid REFERENCES users(id) ON DELETE SET NULL, action text NOT NULL, metadata jsonb, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX audit_log_created_at_idx ON audit_log (created_at DESC); CREATE INDEX audit_log_actor_id_idx ON audit_log (actor_id); CREATE INDEX audit_log_target_id_idx ON audit_log (target_id);