v0.8: auth integration, design persistence (#23), protected sweep (#24)

Server (spans the v0.6→v0.8 arc in one file):
- template-manager mounted with the productsStore catalog, PNG-only
  mockup multer, facet personalization config (§R10, FACET_KEY-gated,
  inert when unset), and the marketing-preview drainer (2s tick, one
  render per tick, .unref()).
- auth-manager bootstrap: DATABASE_URL gates the pg Pool, migrations,
  and mountAuth (magic-link customer + Google SSO admin + facet);
  absent DATABASE_URL logs a warning and the host boots without auth.
- goods-editor design persistence (#23): idempotent 001_designs.sql
  applied at boot, db handle passed to mountEditorApi so /api/designs
  routes mount; without Postgres the client falls back to
  cart-without-design_id.
- reference-protected TTL sweep (#24): before each pass, every
  /uploads/ basename referenced by template-manager JSON or persisted
  designs is collected and protected, and exports/designs (db-driven
  lifecycle, unrecoverable sources) is skipped entirely. Sweep kickoff
  moves below the bootstrap (pgPool TDZ). sweep-worker.mjs is the
  standalone process variant for production.

Client: main.jsx wraps the tree in AuthProvider (single /api/auth/me
fetch shared by useCustomer/useAdmin, degrades on 404) and mounts
AppRouter. eslint bumps server.js to ecmaVersion 2022 (top-level
await); .env.example documents the full v0.8 env surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
khalid@traclabs.com
2026-09-01 23:29:16 -05:00
parent dfa1f90a16
commit 840a5dbcb3
5 changed files with 614 additions and 27 deletions

View File

@@ -34,6 +34,140 @@ NODE_ENV=development
# FILE_TTL_MS=86400000 # 24 hours
# CLEANUP_INTERVAL_MS=3600000 # 1 hour
# ==================================================================
# auth-manager (v0.8) -- runtime
# ==================================================================
#
# auth-manager mounts customer magic-link + admin Google SSO + the
# account shell. If DATABASE_URL is NOT set, auth-manager is disabled
# gracefully and the rest of the host still boots -- useful when
# working on goods-editor / template-manager features without
# provisioning Postgres locally.
# Postgres connection string. Required to enable auth-manager.
# Examples:
# Local Docker: postgres://postgres:postgres@localhost:5432/apparel
# Dokploy: postgres://<user>:<pass>@<service>:5432/<db>
# DATABASE_URL=postgres://postgres:postgres@localhost:5432/apparel
# Fully-qualified base used to build:
# - the magic-link URL we email to customers (and print to console)
# - the Google OAuth redirect URI for admin SSO
# Must match the redirect URI registered in Google Cloud Console.
# In dev, set to http://localhost:3001. In prod, your storefront URL.
# Falls back to http://localhost:${PORT} if unset (dev only -- won't
# work behind a reverse proxy).
# PUBLIC_BASE_URL=http://localhost:3001
# Comma-separated allowlist of Google accounts that can sign in via
# admin SSO. Two or more entries strongly recommended for break-glass
# recovery (locked decision #4 in v08_Auth_v1.md).
# ADMIN_EMAILS=you@gmail.com,backup@gmail.com
# Google OAuth 2.0 Web client credentials. Required for admin SSO.
# Create in Google Cloud Console -> APIs & Services -> Credentials ->
# Create credentials -> OAuth client ID -> Web application. Authorized
# redirect URI must be ${PUBLIC_BASE_URL}/auth/admin/google/callback.
# GOOGLE_CLIENT_ID=...apps.googleusercontent.com
# GOOGLE_CLIENT_SECRET=...
# Optional: SMTP relay for magic-link emails. If unset, magic links
# ONLY print to the server's stdout (still fully functional for dev
# -- just copy/paste the link from your terminal).
#
# Recommended dev setup: Mailpit (https://mailpit.axllent.org).
# docker run -d -p 1025:1025 -p 8025:8025 axllent/mailpit
# then visit http://localhost:8025 to read sent mail.
#
# Resend integration is deferred to v2 -- see v08_Auth_v1.md
# "Email delivery in v1" section for rationale.
# SMTP_HOST=localhost
# SMTP_PORT=1025
# SMTP_USER=
# SMTP_PASS=
# SMTP_FROM=no-reply@localhost
# Optional: cookie domain for cross-subdomain session sharing
# (e.g. '.yourdomain.com' so checkout.yourdomain.com sees the same
# session as yourdomain.com). Usually leave unset -- SureCart's WP
# sidecar uses its own customer identity (passed via metadata at
# checkout creation), not auth-manager's session.
# COOKIE_DOMAIN=
# ==================================================================
# Personalization facet (Project doc §R10) -- runtime
# ==================================================================
#
# Generic "customers filter the shop by a facet" feature. For THIS
# deployment the facet is "Pet Type" (dog/cat/reptile/…); other shops
# configure a different facet, or none. The facet VALUES are managed by
# an admin inside template-manager; only the DEFINITION is set here.
#
# FACET_KEY is the master switch. Leave it UNSET to run a plain,
# unfiltered shop: no sign-in prompt, no template filtering, the editor
# shows all stickers, and the account/admin facet UIs stay hidden.
# The facet's stable key. Setting it turns the feature ON. Becomes the
# facet_key on customer_facet_values rows + the audienceTags scope.
# FACET_KEY=pet_type
# Display label shown in the UI ("Choose your {LABEL}", account heading,
# admin section title).
# FACET_LABEL=Pet Type
# Selection mode: `multi` (default) lets a customer pick several values
# (e.g. dog + cat); `single` restricts to one.
# FACET_SELECTION_MODE=multi
# Require an admin to categorize every template before saving it
# (>=1 facet value, or marked General/universal). Default false. Turn
# this ON whenever the shop filters strictly, so an untagged template
# is never hidden silently. THIS shop: true.
# REQUIRE_TEMPLATE_CATEGORY=true
# Require a signed-in customer to choose at least one value before
# browsing. Default false. THIS shop: false (the prompt nudges but is
# skippable; choosing nothing shows everything).
# REQUIRE_FACET_SELECTION=false
# ==================================================================
# Image upscale service (goods-editor, 2026-06-10) -- runtime
# ==================================================================
#
# Optional REST service that upscales low-resolution customer photos
# before they hit the print render (goods-editor's /api/export
# pipeline). goods-editor reads these variables itself
# (imageUpscalerFromEnv inside mountEditorApi) -- no server.js wiring
# needed; setting UPSCALE_SERVICE_URL is the master switch. Unset =
# feature fully dormant, renders byte-identical to before.
#
# The editor independently FLAGS low-res images to customers/admins
# in the UI regardless of this setting -- the service only improves
# what actually prints. Upscaling is best-effort: if the service is
# down or slow, the render proceeds with the original pixels.
#
# Expected contract (adapt goods-editor's src/server/lib/
# imageUpscaler.js if your service differs -- it's the single point
# of adaptation):
# POST $UPSCALE_SERVICE_URL with JSON
# { "image": "<base64>", "mime": "image/png", "scale": 2 }
# -> 200 with an image/* binary body, OR JSON { "image": "<base64>" }
# Master switch. e.g. a Real-ESRGAN container in the same Dokploy
# project: http://upscaler:5000/upscale
# UPSCALE_SERVICE_URL=
# Optional credential. Sent as 'Authorization: Bearer <key>' unless
# UPSCALE_SERVICE_AUTH_HEADER names a custom header (e.g. X-Api-Key),
# in which case the bare key is sent in that header.
# UPSCALE_SERVICE_API_KEY=
# UPSCALE_SERVICE_AUTH_HEADER=Authorization
# Per-request abort + the service's integer scale ceiling.
# UPSCALE_SERVICE_TIMEOUT_MS=30000
# UPSCALE_SERVICE_MAX_SCALE=4
# ──────────────────────────────────────────────────────────────────
# Build-time — values the `docker compose build` step uses
# ──────────────────────────────────────────────────────────────────