-- v1.1.8 User Management — invitations.
--
-- Unlike verification + reset tokens, invitations don't carry a
-- user_id — the user doesn't exist yet. Instead they pre-stage the
-- email, an optional display name, and a roles array applied on
-- accept (once the per-app role table exists in migration 0031).
--
-- token_hash UNIQUE is the lookup key; the surrogate `id` UUID is
-- what the admin invitations UI references (rotation-safe; an
-- admin can list pending invites by id without leaking tokens).
--
-- accepted_at gates one-shot semantics: the consume path is an
-- atomic UPDATE WHERE accepted_at IS NULL. Stale accept attempts get
-- nothing, so a leaked / cached token can't be replayed.

CREATE TABLE app_user_invitations (
    id             UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    token_hash     TEXT NOT NULL UNIQUE,
    app_id         UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
    email          TEXT NOT NULL,
    display_name   TEXT,
    roles          TEXT[] NOT NULL DEFAULT '{}',
    created_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at     TIMESTAMPTZ NOT NULL,
    accepted_at    TIMESTAMPTZ
);

CREATE INDEX idx_app_user_invitations_app_pending
    ON app_user_invitations (app_id) WHERE accepted_at IS NULL;
