chore: rebrand ClubManager -> RosterChief, add lucide icons

The clubmanager.app domain was taken, so the platform is now RosterChief
(rosterchief.app). Renames the Django project package clubmanager/ ->
rosterchief/ (git tracks it as a move, so history follows), every
`from rosterchief.base import ...`, the settings/wsgi/asgi module paths,
env vars (ROSTERCHIEF_BASE_DOMAIN / ROSTERCHIEF_RP_NAME), the MFA adapter
(RosterChiefMFAAdapter), brand text, and the docs.

Two things were deliberately NOT swept:
- club.models.ClubManager stays: it is the Django manager *for Club*, not the
  brand. A blind rename would have silently broken it.
- Migrations are untouched (history is not rewritten). The only reference was a
  cosmetic help_text, so a normal AlterField migration carries the new domain.

Note the WebAuthn RP ID is the base domain, so moving to rosterchief.app
cryptographically invalidates any passkey enrolled under the old one; they
cannot be migrated and must be re-enrolled. Nothing is in production, so the
real cost is zero.

Add django-lucide (from bsiebens/lucide) for icons: the theme toggle now swaps
sun/moon against the effective theme, and the control panel gets icons on its
tabs, actions and stat groups. Its classifiers stop at Django 5.0, but that is
stale metadata — verified rendering on Django 6 / Python 3.14.

Also add formbuilder, shop and controlpanel to ruff's known-first-party list,
which had drifted behind the apps that landed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 15:42:20 +02:00
parent 6f66df3ba4
commit eace903f05
33 changed files with 220 additions and 153 deletions

View File

@@ -1,10 +1,10 @@
# ClubManager — Model & Domain Architecture
# RosterChief — Model & Domain Architecture
Baseline reference for implementing the domain models. This describes the **intended
shape** of the data model: what exists today, what is planned, and the conventions every
app should follow. It is a living document — update it when the model changes.
> **Tenancy: multi-tenant (row-based / shared-schema).** ClubManager is designed as a
> **Tenancy: multi-tenant (row-based / shared-schema).** RosterChief is designed as a
> **multi-tenant platform** — one deployment serves many clubs, with **`Club` as the tenant
> root**. Isolation is **row-based**: a shared database and schema where every club-owned
> row carries a `club` FK (via `ClubScopedModel`), and *all* access is scoped to the
@@ -54,9 +54,9 @@ only if the app grows unwieldy. The roadmap `members` name is reserved either wa
These are already established in code — every new model follows them.
- **UUID primary keys.** Inherit `clubmanager.base.UUIDModel` (`id = UUIDField(default=uuid4)`).
- **UUID primary keys.** Inherit `rosterchief.base.UUIDModel` (`id = UUIDField(default=uuid4)`).
Never expose sequential integer PKs.
- **`ClubScopedModel`** (`clubmanager.base`) adds the tenant `club` FK
- **`ClubScopedModel`** (`rosterchief.base`) adds the tenant `club` FK
(`related_name="%(class)ss"`) and, under multi-tenancy, a tenant-aware manager + auto
club-stamping `save()` (§2.4). **Every aggregate-root model inherits it**; leaf rows
reachable only via a scoped parent (e.g. `Attendance` via `Event`) may inherit scope from
@@ -105,12 +105,12 @@ several clubs; a `Member` is that person *within one club*. So:
current club** (via the tenant context below), falling back to email.
**Tenant resolution → `request.club`.** A `ClubTenantMiddleware` resolves the active club
per request (recommended: **subdomain**, `ajax-united.clubmanager.app`; path-prefix
per request (recommended: **subdomain**, `ajax-united.rosterchief.app`; path-prefix
`/c/<slug>/` is the alternative) and stores it on `request.club` *and* in a context
variable so non-request code (services, management commands) can read it:
```
# clubmanager/tenancy.py
# rosterchief/tenancy.py
from contextvars import ContextVar
_current_club: ContextVar = ContextVar("current_club", default=None)
@@ -740,7 +740,7 @@ Legend: `───<` one-to-many, `>───<` many-to-many via a through model
rows with the current season and re-scopes `unique_together`.
3.**Full multi-tenancy** — adopt **row-based multi-tenancy**, `Club` as tenant root
(§2.4). Requires: `ClubScopedModel` on every aggregate root, `Member.user`
`ForeignKey` (+`unique(club, user)`), tenant middleware + `clubmanager/tenancy.py`
`ForeignKey` (+`unique(club, user)`), tenant middleware + `rosterchief/tenancy.py`
context, tenant-aware manager, per-club uniqueness, per-club roles (§3). **Supersedes
`CLAUDE.md`.**
4.**Jersey uniqueness** — unique **within a team** via a partial `UniqueConstraint`
@@ -822,10 +822,10 @@ Setup:
### 8.3 Tenancy runtime config (needed once `ClubTenantMiddleware` lands)
- **Hosts:** wildcard `ALLOWED_HOSTS` for the chosen base domain (e.g. `.clubmanager.app`)
- **Hosts:** wildcard `ALLOWED_HOSTS` for the chosen base domain (e.g. `.rosterchief.app`)
if using subdomain resolution; add `DJANGO_ALLOWED_HOSTS` accordingly.
- **CSRF:** `CSRF_TRUSTED_ORIGINS` must cover the wildcard scheme+host set
(`https://*.clubmanager.app`).
(`https://*.rosterchief.app`).
- **Cookies:** to share login across club subdomains, set `SESSION_COOKIE_DOMAIN` /
`CSRF_COOKIE_DOMAIN` to the base domain; otherwise keep per-subdomain sessions
(decide with the "cross-club users" question in §7).
@@ -842,8 +842,8 @@ Setup:
---
*Conventions cross-reference:* `clubmanager/base.py` (`UUIDModel`, `ClubScopedModel`),
`clubmanager/tenancy.py` (*to add* — tenant context/middleware, §2.4),
*Conventions cross-reference:* `rosterchief/base.py` (`UUIDModel`, `ClubScopedModel`),
`rosterchief/tenancy.py` (*to add* — tenant context/middleware, §2.4),
`authentication/managers.py` (`UserManager`), `authentication/services/` (service-layer
pattern).
@@ -852,10 +852,10 @@ pattern).
> ### ⚠️ Banner: supersedes `CLAUDE.md`
>
> This architecture adopts **full multi-tenancy** (§2.4), which **directly contradicts**
> the current `CLAUDE.md` ("ClubManager is a **single-club** app … deliberately *not*
> the current `CLAUDE.md` ("RosterChief is a **single-club** app … deliberately *not*
> multi-tenant — there is no `club_id` tenancy") and the project memory
> (`project_overview` — "Single-club (NOT multi-tenant)").
>
> **Action required** before/alongside implementation: update `CLAUDE.md` and the memory
> to describe ClubManager as a **multi-tenant platform (row-based, `Club` = tenant root)**.
> to describe RosterChief as a **multi-tenant platform (row-based, `Club` = tenant root)**.
> Until that is done, where the two disagree **this document is authoritative**.