Files
apparel-designer/.env.example
khalid@traclabs.com 840a5dbcb3 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>
2026-09-01 23:29:16 -05:00

216 lines
10 KiB
Plaintext

# Example environment file for apparel-designer.
#
# Copy this to `.env` and adjust values as needed. Compose reads
# `.env` automatically for variable substitution in docker-compose.yml
# AND passes the values as runtime environment to the running
# container, so a single file covers both build-time and runtime.
#
# ──────────────────────────────────────────────────────────────────
# Runtime — values the server reads at startup
# ──────────────────────────────────────────────────────────────────
# HTTP port the Express server binds to. Matches the host-side port
# in docker-compose.yml's `ports:` mapping.
PORT=3001
# `production` enables prod-only behavior in server.js (production
# stickers path under dist/, production-mode logger). Use
# `development` locally to read stickers from public/ instead.
NODE_ENV=development
# Pino log level. Comment out for `info` (default). Useful values:
# trace, debug, info, warn, error, fatal
# Set to `debug` when diagnosing a problem; revert before deploy.
# LOG_LEVEL=info
# CORS — uncomment and set when the frontend is served from a
# different origin than this API. Single origin or comma-separated.
# CORS_ORIGIN=https://your-domain.com
# Upload / export TTL and sweep interval, in milliseconds.
# Defaults (24h TTL, 1h sweep) work for most cases. Lower the TTL
# if disk space is tight; lower the interval if uploads churn fast
# and you want stale files cleaned sooner.
# 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
# ──────────────────────────────────────────────────────────────────
# Only needed when BUILDING the image, not when running it. The
# image talks to git.kadil.dev to fetch the `goods-editor` git+ssh:
# npm dependency during `npm install`. See the Dockerfile docblock
# for the full mechanism.
#
# The Dockerfile expects a BASE64-ENCODED private key (not raw PEM)
# because that's the format Dokploy's Build-time Secrets textarea
# requires — it's dotenv-parsed and can't handle multi-line values.
# We use base64 for local builds too so the Dockerfile has one input
# format. Encode like this:
#
# base64 < ~/.ssh/goods-editor-deploy | tr -d '\n' > /tmp/key.b64
#
# Recommended: generate a dedicated read-only deploy key rather than
# reusing a personal key (see Dockerfile docblock for the ssh-keygen
# + Gitea deploy-key registration steps).
#
# Path to a base64-encoded key file. Uncomment one for local builds:
# SSH_KEY_FILE=/Users/khalid/.ssh/goods-editor-deploy.b64
# SSH_KEY_FILE=/home/deploy/.ssh/goods-editor-deploy.b64
# Alternative: env-var-based (CI). To use this mode, also flip
# docker-compose.yml's `secrets.ssh_key` block from `file:` to
# `environment:` (see comments there). Set the base64 contents via
# the shell or your CI secret store:
#
# SSH_KEY_B64="$(base64 < ~/.ssh/goods-editor-deploy | tr -d '\n')" \
# docker compose build
#
# Don't put the actual base64 in this .env file. The string is long
# enough that dotenv parsers may struggle, and committing real key
# material — even base64 — to a repo is a leak waiting to happen.
#
# ── Dokploy ───────────────────────────────────────────────────────
# The entries above DON'T apply to Dokploy. In Dokploy, paste the
# base64 directly into the application's Environment → Build-time
# Secrets textarea as a single line:
#
# ssh_key=<that base64 string>
#
# Dokploy parses that textarea as dotenv and passes the value into
# the build as a BuildKit secret named `ssh_key`.