-- Session tokens authenticate /api/* requests. We store sha256(token) so a -- read-only DB leak doesn't grant active sessions; the raw token lives only -- in the client's cookie (web) or bearer header (Flutter). -- -- last_seen_at enables an "active sessions" UI later (not wired in this plan) -- without schema churn. user_agent is captured at issue time for the same -- reason — free metadata now, no migration later. CREATE TABLE sessions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash bytea NOT NULL UNIQUE, user_agent text NOT NULL DEFAULT '', created_at timestamptz NOT NULL DEFAULT now(), last_seen_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX sessions_user_id_idx ON sessions (user_id);