Files
NextChair/.env.business.example
T
Stefan Lazic 3b07124d7e
CI / ci (push) Canceled after 0s
Edit Business flow
New files:

manager/src/lib/env-file.ts — a shared, surgical .env reader/updater. It only ever touches the specific keys you tell it to (never regenerates the file), so anything hand-edited into a business's .env since provisioning is preserved exactly.
manager/src/app/dashboard/[slug]/edit/ — the edit page, its form component, and its server action.

Modified:

provisioning.ts gained updateBusiness() alongside the existing provisionBusiness(), reusing the same Caddy-site-writing and container-recreation logic (refactored into a shared writeCaddySiteAndReload helper).
stacks.ts/compose-template.ts — extended to track a BUSINESS_NAME field (previously collected on the Add Business form but silently discarded — that gap is now fixed too, so newly created businesses get their name persisted from the start).
The detail page and dashboard list now show the business name, and the detail page has an Edit button.

Per the requirements:

Slug is read-only in the edit form (disabled input, never accepted by the server action — it's bound in from the route, not form data).
Domain uniqueness is validated against every other business (and correctly self-excludes the business being edited).
A domain change rewrites the Caddy site file, reloads Caddy, and force-recreates the container; a name-only change does neither.
No secrets (DATABASE_PASSWORD, AUTH_SECRET, etc.) are ever read into the edit UI — only name/domain/databaseName/status are exposed.
Clear success banners ("Changes saved" vs. "...the container was restarted...") and inline error display, matching the existing Add Business pattern.
2026-09-05 17:05:33 +01:00

104 lines
5.0 KiB
Bash

# One business's config on the shared multi-business VPS setup — see
# stacks/infra/compose.yaml for the one-time setup this depends on
# (creating this business's database on the shared MySQL server) and
# stacks/<slug>/compose.yaml for how this file is used. Copy this to
# `stacks/<slug>/.env` (e.g. `stacks/blake-heritage/.env` — kept out of
# git, see .gitignore) per business and fill in real values. Nothing here
# is a real credential. Normally the management UI (manager/) generates
# this file for you — this template is for the by-hand setup path.
#
# The app starts and runs a complete first-install flow with ONLY the
# "Required for basic startup" section below filled in — every other
# integration (email, push, Google sign-in, Google Calendar) shows a
# disabled state rather than crashing when its variables are left blank.
# ── Required for basic startup ─────────────────────────────────────────
# Manager-only bookkeeping (shown on the dashboard/edit screen, editable
# there) — the app itself never reads this; its own business name lives
# in the database, set via /install.
BUSINESS_NAME="Blake Heritage Barbershop"
# The public domain this business is served on — must match the .caddy
# site file's own domain (see stacks/infra/sites/.gitkeep) and that
# domain's real DNS (pointed at the VPS) before Caddy can issue a
# certificate for it.
DOMAIN="blake-heritage.example.com"
# The app reaches MySQL via the shared infra's service hostname
# `next-chair-mysql`, never `localhost` — see stacks/infra/compose.yaml.
# DATABASE_NAME/USER/PASSWORD must match exactly what you created for
# this business on the shared server (see stacks/infra/compose.yaml's
# "CREATE DATABASE ..." step) — this file doesn't create them, it only
# tells the app what to connect as.
DATABASE_HOST="next-chair-mysql"
DATABASE_PORT="3306"
DATABASE_NAME="blake_heritage"
DATABASE_USER="blake_heritage"
DATABASE_PASSWORD="replace-me"
# Generate with: npx auth secret
AUTH_SECRET="replace-me-with-a-32-byte-random-string"
# Auth.js needs to trust the host it's served from when not on localhost:3000.
AUTH_TRUST_HOST="true"
NEXT_PUBLIC_APP_URL="https://blake-heritage.example.com"
# One-time setup token the installation wizard (/install) requires before
# it will create the first business/administrator — generate with:
# openssl rand -hex 32
INSTALLATION_TOKEN="replace-me"
# Shared secret an external scheduler presents to
# /api/cron/process-notifications — generate with: openssl rand -hex 32
CRON_SECRET="replace-me"
# ── Super Administrator (optional — see scripts/ensure-super-admin.ts) ──
# Platform-level account with every Owner permission for THIS business.
# Not run automatically by this compose file (that's a Docker-entrypoint-
# only behaviour, see docker/entrypoint.sh) — run it manually, once:
# docker exec -it next-chair-blake-heritage npx tsx scripts/ensure-super-admin.ts
# Leave SUPER_ADMIN_EMAIL blank to skip entirely and use the /install
# wizard's own administrator account instead.
SUPER_ADMIN_EMAIL=""
SUPER_ADMIN_PASSWORD=""
SUPER_ADMIN_FIRST_NAME=""
SUPER_ADMIN_LAST_NAME=""
# ── Google sign-in (optional — "Continue with Google" on Sign In) ─────
AUTH_GOOGLE_ID=""
AUTH_GOOGLE_SECRET=""
# ── Google Calendar sync (optional — staff-level, see /staff/settings/calendar) ─
# From https://console.cloud.google.com/apis/credentials — a separate
# OAuth client from AUTH_GOOGLE_ID/SECRET above (different scopes).
GOOGLE_CALENDAR_CLIENT_ID=""
GOOGLE_CALENDAR_CLIENT_SECRET=""
GOOGLE_CALENDAR_REDIRECT_URI="https://blake-heritage.example.com/api/staff/calendar/google/callback"
# Encrypts stored refresh tokens at rest — generate with: openssl rand -hex 32
CALENDAR_TOKEN_ENCRYPTION_KEY=""
# ── Media Manager (optional — see src/lib/media/media-storage.ts) ──────
# Max upload size in bytes for the Media Manager. Defaults to 5,000,000
# (5MB) if unset.
MEDIA_MAX_UPLOAD_BYTES="5000000"
# ── Google Reviews (optional — see /owner/settings/reviews) ────────────
# From https://console.cloud.google.com/apis/credentials — needs "Places
# API (New)" enabled on that project (a different product from the older
# "Places API" — enabling the wrong one returns REQUEST_DENIED). Leave
# blank to keep review sync disabled entirely (Home shows clearly-labelled
# demo reviews instead).
GOOGLE_PLACES_API_KEY=""
# ── Email (optional — defaults to logging to the console instead of sending) ─
EMAIL_PROVIDER="console"
EMAIL_SMTP_HOST="smtp-relay.brevo.com"
EMAIL_SMTP_PORT="587"
EMAIL_SMTP_USERNAME=""
EMAIL_SMTP_PASSWORD=""
EMAIL_FROM_ADDRESS=""
EMAIL_FROM_NAME=""
# ── Web Push (optional — defaults to logging to the console instead of sending) ─
# Generate with: npx web-push generate-vapid-keys
PUSH_PROVIDER="console"
VAPID_PUBLIC_KEY=""
NEXT_PUBLIC_VAPID_PUBLIC_KEY=""
VAPID_PRIVATE_KEY=""
VAPID_SUBJECT="mailto:owner@example.com"