"""invitations: users invite friends, even while sign-up is closed An administrator switch lets every user send a few single-use invitation links by email; a link opens sign-up for the person it was sent to even when public registration is off. The email is built from a template the administrator uploads (or the built-in one) and sent from the instance's own address. Revision ID: 0027 Revises: 0026 Create Date: 2026-10-14 """ from __future__ import annotations from collections.abc import Sequence from alembic import op revision: str = "0027" down_revision: str | None = "0026" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.execute( """ ALTER TABLE app_settings ADD COLUMN invitations_enabled boolean NOT NULL DEFAULT false, ADD COLUMN invite_from text NOT NULL DEFAULT '', ADD COLUMN invite_subject text NOT NULL DEFAULT '', ADD COLUMN invite_template text NOT NULL DEFAULT '' """ ) op.execute( """ CREATE TABLE invitations ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), inviter_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash text NOT NULL UNIQUE, invitee_name text NOT NULL, invitee_email text NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), expires_at timestamptz NOT NULL, used_at timestamptz, used_by uuid REFERENCES users(id) ON DELETE SET NULL ) """ ) op.execute("CREATE INDEX ix_invitations_inviter ON invitations (inviter_id)") def downgrade() -> None: op.execute("DROP TABLE IF EXISTS invitations") op.execute( """ ALTER TABLE app_settings DROP COLUMN IF EXISTS invitations_enabled, DROP COLUMN IF EXISTS invite_from, DROP COLUMN IF EXISTS invite_subject, DROP COLUMN IF EXISTS invite_template """ )