Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
12 KiB
Descrybe v2
Go API + SvelteKit frontend + PostgreSQL rewrite of Descrybe (no Clerk).
Stack
- API: Go, chi, pgx, sqlc, goose, River jobs
- Web: SvelteKit 2, Svelte 5, Tailwind
- DB: PostgreSQL 16
- Migrator: MySQL → Postgres ETL with Clerk ID remapping
Local setup (any OS)
Docker Compose runs Postgres only (host :5433). API, web, and worker run on the host via npm run setup + npm run dev (fast reload; no Laragon/XAMPP required). OS-specific Docker notes: docs/docker.md. Ports match docker-compose.yml + scripts/dev-ports.mjs.
After the stack is up: operator path (login → feed → process → export) → docs/getting-started.md.
Two-step path (recommended)
npm run setup # .env (if missing) + docker Postgres :5433 + goose migrate
npm install && npm run dev # API :28471 + web :28472 + worker (needed for /readyz + jobs)
Same on Windows PowerShell, macOS, and Linux (Node scripts; Docker Desktop or Engine + Compose v2). Equivalents: make setup then make targets / npm run ….
Optional: npm run seed (demo login) · npm run health (/healthz + /readyz).
Prerequisites
| Tool | Version / notes |
|---|---|
| Docker | Docker Engine + Compose v2 (docker compose) — Postgres only on :5433 |
| Node.js | ≥ 20 (package.json engines.node) |
| Go | 1.25.0 (apps/api/go.mod) |
| sqlc (optional) | On PATH for migrate regen; scripts fall back to go run …/sqlc if missing |
| Make (optional) | make setup / make up / make migrate — macOS/Linux/WSL; Windows can use npm run … |
Local ports (canonical)
| Service | Host |
|---|---|
| Postgres | localhost:5433 → container 5432 (postgres:16-alpine) |
| API | http://localhost:28471 (HTTP_ADDR=:28471) |
| Web | http://localhost:28472 (Vite strictPort; proxies /api, /healthz, /readyz) |
Do not use older docs that mention :8080 / :5174 for day-to-day npm run dev.
Environment (one file)
Use a single root .env (copy from .env.example). Do not create apps/api/.env. npm run setup creates .env from the example and fills empty TOKEN_SIGNING_SECRET / APP_ENCRYPTION_KEY with local random hex (never commit .env).
| Variable | Local default / note |
|---|---|
DATABASE_URL |
postgres://descrybe:descrybe@localhost:5433/descrybe?sslmode=disable (matches docker-compose.yml) |
HTTP_ADDR |
:28471 |
WEB_ORIGIN |
http://localhost:28472 |
PUBLIC_API_URL |
http://localhost:28471 |
APP_ENV |
development |
SESSION_SECURE |
false locally; true in production |
TOKEN_SIGNING_SECRET |
openssl rand -hex 32 (or let npm run setup generate) |
APP_ENCRYPTION_KEY |
Prefer this for at-rest secrets (CREDENTIALS_ENCRYPTION_KEY is legacy). Same generate rule |
Leave OpenAI, Stripe, EPREL, marketing mail, Woo, and Shopify out of .env for day-to-day setup. Configure them in the app after login:
| Integration | Where |
|---|---|
| AI (OpenAI-compatible / BYOK) | /integrations/ai (tenant) and/or /admin/settings (platform) |
| Stripe, EPREL, feed private-URL allowlist | /admin/settings (GET/PUT /api/admin/settings → values.*; env optional fallback) |
| Marketing email (Resend / SMTP) | /integrations/email |
| Stores (Woo / Shopify / feeds) | /stores |
Platform set-password / invite SMTP may still use process SMTP_* when you enable live invite mail (see docs/ops-runtime.md). Session/encryption fail-closed rules are in .env.example and docs/production-checklist.md. Production does not require Stripe secrets at boot.
The API/worker load the monorepo-root .env automatically (apps/api/internal/config loadDotEnv).
Manual steps (if you skip npm run setup)
docker-compose.yml defines only Postgres (descrybe-v2-postgres, user/db/password descrybe, volume descrybe_v2_pg, pg_isready healthcheck). OS-specific Docker Desktop / Engine notes: docs/docker.md.
cp .env.example .env # PowerShell: Copy-Item .env.example .env
docker compose up -d # or: npm run db:up / make up
npm run migrate # goose up + sqlc (loads root .env)
npm install && npm run dev
Migrate wrappers: bash scripts/migrate.sh · .\scripts\migrate.ps1 · make migrate (all call scripts/migrate.mjs).
Worker and /readyz
npm run dev starts API + web + worker. Processing jobs and GET /readyz need that worker heartbeat (stale after 60s).
| Script | What runs |
|---|---|
npm run dev / npm run dev:app |
api + web + worker |
npm run dev:backend / make backend |
api + worker (no web; enough for /readyz) |
npm run dev:api |
api only → /readyz 503 until a worker is started |
npm run dev:worker |
worker only |
make worker / .\scripts\run-api.ps1 worker |
worker only |
Verify
npm run health
# or:
curl -sS http://127.0.0.1:28471/healthz
curl -sS http://127.0.0.1:28471/readyz
Open the app: http://localhost:28472. Demo seed: npm run seed.
Split processes (optional)
npm run dev:web # web only
npm run dev:api # API only (readyz 503 without worker)
npm run dev:worker # worker only
npm run dev:backend # api + worker
npm run dev:ps1 # PowerShell: api + web + worker
OS notes
- Windows / macOS / Linux Docker details (Desktop vs Engine, WSL2, ports): docs/docker.md.
- Windows: Docker Desktop running; use
npm run setup/npm run migrate/npm run seed(Node) — no Git Bash required. Optional:.\scripts\migrate.ps1,.\scripts\dev.ps1. - macOS / Linux: same npm commands;
make setup/make migratework if Make is installed. - WSL2: Docker Desktop WSL integration or Linux engine; keep
DATABASE_URLonlocalhost:5433from the same environment as the API.
Troubleshooting: /readyz returns 503
GET /healthz = process liveness. GET /readyz = Postgres ping and a fresh worker heartbeat (worker_id=processing, stale after 60s). On worker failure the JSON keeps a short error and adds operator reason (how to start the host worker). See also docs/ops-runtime.md and docs/production-checklist.md.
| Symptom | Meaning | Fix |
|---|---|---|
/healthz 200, /readyz 503, checks.worker = missing, error worker heartbeat missing |
API up, worker never started (or migrations before 039_worker_heartbeats) |
Use npm run dev (includes worker), or npm run dev:worker / make worker; ensure goose is up through 039. Read JSON reason. |
/readyz 503, checks.worker = stale, error worker heartbeat stale |
Worker was up but heartbeat older than 60s | Restart the worker; confirm it stays running. Read JSON reason. |
/readyz 503, checks.database = fail / unavailable |
DB down or bad DATABASE_URL |
docker compose ps, fix URL (host 5433), re-run migrate |
/readyz still 503 after starting worker |
Heartbeat table missing or wrong DB | Re-run migrate; confirm worker and API share the same DATABASE_URL |
Expected: API-only local (cmd/api without cmd/worker, or npm run dev:api) → /readyz 503. That is not a broken API binary — start the worker (npm run dev, npm run dev:backend, or npm run dev:worker) before treating readiness as green. Jobs on /processing also need the worker. Compose does not start the worker (Postgres only).
Routes
| Path | Purpose |
|---|---|
/ |
Public marketing homepage (sell copy, FAQ, pricing teaser; no app shell) |
/pricing |
Public marketing pricing (Free→Enterprise; no client deals) |
/features |
Optional features deep-dive (not primary nav) |
/privacy, /terms |
Legal pages |
/login, /register |
Auth (login success → /dashboard) |
/dashboard |
App home (sidebar shell) |
/plans |
In-app plans (authenticated) |
/standard-fields, /feeds, /feeds/{id}/mapping |
Catalog ingest + mapping |
/products, /processing |
Catalog + background process jobs |
/export-feeds |
Outbound CSV/XML templates + generate |
/stores, /stores/shopify |
Store connectors hub (Woo, Shopify sibling, feed URL, CSV) |
/campaigns, /seo, /brand, /marketing/calendar |
Marketing suite |
/reviews |
Alias to WooCommerce Reviews tab |
/admin/* |
Platform admin |
Demo login (migrated staging data)
Alias demo@descrybe.test is also seeded. Platform admin + admin of Platform Demo only (not A1). Default session company: Platform Demo. Act for A1 via Admin → Users → Switch to user (a1-primary@descrybe.local). Details: docs/demo-user.md, docs/safe-test-fixtures.md.
npm run seed
# or: node scripts/seed-local.mjs
# or: pwsh -File .\scripts\seed-local.ps1
Migrations
Prefer the cross-platform script (loads root .env):
npm run migrate
# equivalents: node scripts/migrate.mjs | make migrate | .\scripts\migrate.ps1
Manual:
cd apps/api
go run github.com/pressly/goose/v3/cmd/goose@v3.24.3 -dir sql/schema postgres "$DATABASE_URL" up
sqlc generate
Migrator
Live MySQL → staging Postgres load succeeded (2026-08-03); production cutover is still NO-GO until emails/roles/SMTP login are proven. Evidence: docs/migration-run-log.md.
cd apps/api
go run ./cmd/migrator -mysql "$MIGRATE_MYSQL_DSN" -postgres "$DATABASE_URL" -dry-run
Docs
-
Local setup (README) — any-OS Docker/Node/Go/migrate;
/readyz503 troubleshooting -
docs/getting-started.md — operator checklist: login → feed → process → export
-
docs/store-connectors.md — Woo / Shopify / feed URL / CSV / export REST
-
docs/marketing-suite-user-guide.md — seasons, Black Friday in 5 clicks, tutorial start
-
docs/free-tier.md — Free plan 0 AI credits + marketing gates
-
docs/green-chat-ai.md — Green Chat / OPENAI_BASE_URL for processing + campaign AI
-
docs/mock-llm.md — Tiny OpenAI-compatible stub (
cmd/mock-llm) for CI/dev processing without production keys -
Email sending — Resend/SMTP, encryption, dry-run
-
docs/process-and-sell-summary.md — process & sell E2E test path (standard fields, sync/process, EPREL, export, public API)
-
docs/demo-user.md — demo login + company data counts for staging testing
-
docs/woocommerce-demo.md — Woo store setup, WOO_* env, seed-woo-demo, audience/campaigns
-
docs/go-live-checklist.md — go/no-go cutover checklist (design, stubs, migrate, set-password)
-
docs/migration-run-log.md — live dry-run + staging load report
-
docs/status-and-gaps.md — what works, what's stubbed, what's missing, priority order
-
docs/design-gaps.md — UI/UX parity vs legacy
-
docs/migration-readiness.md — ETL status + post-import set-password
-
docs/features.md — phase checklist
-
docs/schema-map.md — MySQL → Postgres ID maps
-
docs/cutover.md — production cutover runbook
-
docs/ops-runtime.md — SMTP, sessions, Woo encryption