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>
216 lines
10 KiB
Plaintext
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`.
|