Files
lukasandClaude Opus 5.5 463f96ad57 Invite a friend; handle field shows the domain; system mail fixes
- Invitations: each user can invite two friends by email (sign-up works
  through the link even while registration is closed); admin-editable
  template, subject and sender.
- Invitation sender defaults to the system-mail From address, which the
  SMTP account is allowed to send as (was postmaster@APP_DOMAIN, refused
  with 553 when the two differ).
- Sign-in, sign-up and forgot-password fields show the fixed
  @domain after the typed name; the registration status reports it.
- System mail gets a Date header; the invitation's plain-text part keeps
  its paragraphs and line breaks.
- SMTP EHLO uses SMTP_HELO_NAME / APP_DOMAIN, not the container name.
- DNS recommendations for the mail domains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:34:31 +02:00

64 lines
2.0 KiB
Python

"""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
"""
)