Skip to content

AlpineClubBookingsNZ v0.12.1

Choose a tag to compare

@thatskiff33 thatskiff33 released this 19 Jul 07:44
873f9a9

AlpineClubBookingsNZ v0.12.1

Release target: v0.12.1 on 2026-07-19. This patch public reference release
brings all changes landed after v0.12.0 into one supported tag. The version
is a deliberate patch bump chosen by the owner even though the range carries
feature work, because every addition is additive and flagged off by default —
nothing changes for an existing club until an admin opts in. The canonical
detailed entries remain in CHANGELOG.md; this page is the release/operator
overview.

Highlights

  • Optional sign-in methods behind a new admin Login & Security page
    (/admin/security): a per-club password-complexity policy (minimum length
    8–64, default 12; four character-class toggles, default off; fixed 128
    maximum) enforced only at password-set time, plus two module-flagged
    sign-in additions that never replace password login — email magic-link
    (single-use hashed token, club-set TTL) and Google OAuth (profile-initiated
    account linking only, resolving by pinned Google subject id, never by email
    match or auto-provisioning). Both new modules default off; an
    un-configured club is byte-identical to today.
  • Per-age-tier membership billing: membership types gain a Required based on
    age tier
    subscription behaviour that defers to the existing per-tier
    subscriptionRequiredForBooking flag (billing liability fixed by age at the
    start of the financial year), and annual membership fees gain the same
    flat vs per-tier shape the joining fee already carried (existing rows are
    the flat fallback; per-family fees stay flat-only). The membership-type
    editor adds an explicit "N/A (no age)" allowed-tier option.
  • Fees editor UX: the annual-fee editor replaces free-text Xero Account/Item
    inputs with searchable pickers (ACTIVE revenue accounts / sales-capable
    items) fed by the existing admin-gated proxy endpoints, falling back to
    manual entry on Xero disconnection, surfacing the resolved default account,
    and showing the fee-level proration rule — with no billing-math change.
  • Lobby Display polish: a six-board template pack (four built-ins + a
    two-board import bundle) so every display module is exercised, a guided
    visual builder at /admin/display/builder (ADR-004) over the unchanged
    save contract with the textarea editors retained as Advanced mode and no
    schema change, and night-columns rescoped as an honest permanent 3-night
    board. The Lobby Display module remains off by default.
  • A full documentation library: a docs foundation (style guide, audience-first
    hub, curated architecture diagrams, a coverage matrix, an advisory link-check
    CI workflow, and a screenshot harness), 65 operator guides in
    docs/guides/ across four batches, and a fifth batch of member-facing
    journey guides
    in docs/user-guide/, each written against the running
    seeded app — closing the coverage matrix to zero gaps.
  • A screenshot-forward README.md marketing front page with reproducible hero
    art and a booking-demo GIF harness, its former deep operational content
    relocated into the docs it duplicated. No runtime app code changes.
  • Fixes and hardening: membership-types editor close-on-save + dirty-guarded
    header X, /admin/display drill-down back-links (with a repo-wide
    normalisation), a dollar-quote-aware fix to the blue/green migration
    validator's session-clock gate, and a github/codeql-action bump to 4.37.0.

Compatibility and operator impact

  • Runtime requirements remain Node.js 24 LTS, npm 11+, and PostgreSQL 16.
  • The release contains five migrations: all expand/additive, no contract
    migration
    . Four have explicit safety-ledger rows in
    docs/BLUE_GREEN_MIGRATION_SAFETY.tsv, all old_code_compatible=yes:
    • 20260718130000_add_magic_link adds ClubModuleSettings.magicLink
      (flagged off, constant default) and a new empty MagicLinkToken table
      with an ON DELETE CASCADE FK to Member. The ADD COLUMN is catalog-only
      on the cold single-row settings table; the FK on a brand-new empty child
      table scans nothing. Additive; safe at any traffic level.
    • 20260719120000_add_google_oauth adds ClubModuleSettings.googleLogin
      (flagged off, constant default) and a nullable Member.googleSub with a
      unique index. Both ADD COLUMNs are catalog-only (the googleSub column
      takes no default, so even on the hot Member table it is metadata-only);
      the unique index builds over an all-NULL column (NULLs never collide) and
      briefly blocks Member writes — switch to CREATE UNIQUE INDEX CONCURRENTLY if Member is very large. No backfill, no provider call.
    • 20260719130000_add_based_on_age_tier_subscription_behavior registers the
      BASED_ON_AGE_TIER value on the MembershipTypeSubscriptionBehavior enum
      (ALTER TYPE ... ADD VALUE IF NOT EXISTS). Purely additive; no existing
      row changes and no old colour reads the new label before cutover.
    • 20260719140000_annual_fee_age_tier adds a nullable MembershipAnnualFee.ageTier
      (no default, no backfill — every existing row stays the flat NULL-tier
      row and resolves identically) plus index/constraint reshaping on the cold
      MembershipAnnualFee config table: the uniqueness re-keys from
      (membershipTypeId, effectiveFrom) to (membershipTypeId, ageTier, effectiveFrom) with a partial unique flat index, the effective-lookup
      index widens to include ageTier, and the single per-type GiST no-overlap
      EXCLUDE is split into two partial constraints (flat-vs-flat and same-tier
      overlaps still conflict; flat-vs-tier and cross-tier coexist). A raw-SQL
      CHECK forbids per-tier PER_FAMILY rows. Catalog-only ADD COLUMN plus
      brief-lock index/EXCLUDE swaps on a cold table; no DML.
  • The fifth migration, 20260718120000_add_login_security_setting, creates the
    empty single-row LoginSecuritySetting config table (password policy plus the
    magic-link TTL) with no FK and no hot-table or breaking SQL, so it needs no
    ledger row — the same policy under which several v0.12.0 additive migrations
    carried no row. An absent row falls through to the code defaults (minimum
    length 12, classes off, TTL 15), so an un-configured club behaves exactly as
    before.
  • One old-colour operator caveat. Because the old colour's annual-fee
    resolver does not filter by age tier, a per-age-tier annual-fee row is not
    invisible to it — once such a row falls in an active window the old colour can
    select it for a member of any tier (first match by effectiveFrom desc) and
    mis-price them at the wrong tier's amount. Do not create per-age-tier
    annual-fee rows until the cutover completes.
    Flat annual fees and existing
    rows are unaffected; per-tier joining fees already shipped in v0.12.0.
  • The two new sign-in modules default off, so magic-link and Google sign-in
    stay disabled through the migrate→cutover window until an admin enables the
    module (and, for Google, a member links their account). The
    password-complexity policy applies only at password-set time and never
    re-validates an existing password.
  • No migration makes a Xero, Stripe, or SES call, and no member is re-grouped
    in Xero. The annual-fee Xero pickers read only the existing admin-gated
    proxy endpoints server-side; no Xero secret reaches the browser bundle.

Upgrade and verification

Follow docs/UPGRADING.md from v0.12.0 to v0.12.1. In summary:
restore-test a fresh backup, review the five pending migrations against the
safety ledger, deploy in a normal window (no contract window is required this
release), and — critically — do not author any per-age-tier annual-fee rows
until cutover completes.

After cutover, verify at minimum:

  1. Login & Security — the admin security page shows the intended password
    policy (an un-configured club still accepts and rejects passwords exactly as
    before), and existing members can still sign in with their password.
  2. Optional sign-in modules — magic-link and Google OAuth remain disabled
    under Admin > Modules unless the club intends to use them; if enabling
    Google, per-club GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET are configured
    per CONFIGURATION.md and a member can link and then sign in.
  3. Per-age-tier billing — for any membership type set to Required based on
    age tier
    , the age-tier settings drive which tiers are billed, and exempt
    tiers get no subscription invoice; REQUIRED/NOT_REQUIRED types are
    unchanged.
  4. Annual fees — admin fee configuration and the public annual-fees embed
    show the same amounts as before; per-tier annual-fee rows are added only
    after cutover.
  5. Fees editor — the annual-fee Account/Item pickers list the expected Xero
    codes (or fall back to manual entry with the amber notice if Xero is
    disconnected), and the surfaced default account is correct.
  6. Lobby Display — the module stays off unless intended; if enabled, the
    template pack and the guided builder at /admin/display/builder work, and
    guest phones stay hidden unless both the member and the lodge opt in.
  7. Docs and README — the operator/member guides and the new README render
    correctly; these are documentation-only and carry no runtime risk.

This release does not publish an npm package (package.json remains private).
The supported artefacts are the Git tag/GitHub release and the repository's
container-image release workflow.