Files
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

14 KiB

Demo user (staging / local)

Local bootstrap account for testing Descrybe v2 as a standalone full-admin sandbox. Demo is not an A1 company member — staff use Admin → Users → Switch to user to act for customers.

Isolation cheat sheet: safe-test-fixtures.md

QA walkthrough: qa-local-demo.md

Smoke / tests — Platform Demo only

MUST run smoke checks, E2E, Postman, and automated tests against Platform Demo (demo@descrybe.local / dk_demo_local_descrybe_test_key_v1). Keep A1 Slovenija clean: read-only (dump-faithful billing/catalog inspection only — no process, upload, mutate, or mock-llm writes under A1). Full rules: safe-test-fixtures.md.

Credentials

Field Value
Email demo@descrybe.local
Password DemoPass123!
must_set_password false
is_platform_admin true
is_active true
Company Platform Demo (isolated sandbox)
Plan Platform Demo (is_custom=true, ~1M credits, unlimited SKUs, all feature gates ON)

seed-demo also upserts alias demo@descrybe.test / DemoPass123! (-also-email). Prefer .local.

Local logins (after demo isolation)

Role Email Password Notes
Demo admin demo@descrybe.local DemoPass123! Platform admin; Platform Demo only; UserSwitcher Demo admin under Platform Demo
Demo alias demo@descrybe.test DemoPass123! Same sandbox
Primary A1 a1-primary@descrybe.local DemoPass123! Clerk user_30AqqJ8uepxvPUzDSqy81U5w6Ll; A1 Slovenija only; limited legacy nav

Do not merge demo into A1. Switchable list groups demo under Platform Demo and A1 under A1 Slovenija.

System assistant guide personas

Empty Free-plan companies for System assistant QA (password DemoPass123!):

cd apps/api
go run ./cmd/seed-guide-personas -postgres "$env:DATABASE_URL"
Email Suggested scenario
guide-feed-url@descrybe.local Add feed from URL
guide-upload-csv@descrybe.local Upload CSV
guide-shopify@descrybe.local Connect Shopify
guide-woocommerce@descrybe.local Connect WooCommerce
guide-mapping@descrybe.local Map fields
guide-process@descrybe.local Start processing
guide-api@descrybe.local API keys + curl examples
guide-support@descrybe.local Support ticket
guide-attributes@descrybe.local List / create attributes
Surface URL
Web login http://localhost:28472/login -> /dashboard
Marketing http://localhost:28472/
API docs (RapiDoc) http://localhost:28472/docs
OpenAPI http://localhost:28471/api/v1/openapi.yaml (or same-origin via Vite proxy)
API http://localhost:28471

Browser smoke: prefer demo@descrybe.local on Platform Demo (keep A1 read-only). Supply the password via DESCRYBE_SMOKE_PASS (see root .env.example) or codehelper connections set-secret --name local-descrybe. Recipe: spa_hydrate / login on /login.

Do not use this account or API key in production.

Demo API key

Seeded for Platform Demo (sandbox company). Stored only as a SHA-256 hash (auth.HashAPIKey); plaintext is local-only.

Field Value
Key dk_demo_local_descrybe_test_key_v1
Prefix dk_demo_lo
Name Demo local API key
Authorization: Bearer dk_demo_local_descrybe_test_key_v1

or

X-API-Key: dk_demo_local_descrybe_test_key_v1
curl -s -H "Authorization: Bearer dk_demo_local_descrybe_test_key_v1" \
  http://localhost:8080/api/v1/products?limit=1

Billing / plan (A1 Slovenija — dump-faithful)

Company Settings and /api/billing/credits must show migrated A1 numbers, not a fake demo 1M / “Legacy” / “Local Demo Co” rename.

Field MySQL source Value
Company name companies.name A1 Slovenija
PG company id 604f23a8-b66e-4b21-8b45-0d72b68f4790
legacy_company_id MySQL companies.id 97e1a309-3d23-4aa2-b518-8e8d7afdfec7
Plan plans + company_plans.plan_id A1 (is_custom=true, is_legacy=false, term=yearly, yearly_credits=20000, monthly_credits=0) — pay-as-you-go (credits wallet). Open-ended contract (contract_end_date null) is intentional for this demo tenant; company_plans.notes documents PAYG.
Allocated company_plans.total_credits_allocated 2500
Wallet total credit_balances.total_credits 2500
Wallet used credit_balances.used_credits 163
Remaining computed 2337
Language / merge GTIN company_settings sl / false
Usage snapshot usage_metrics folded into company_settings.settings._legacy_usage (dump-faithful)
billing_cycles / cost_metrics in dump empty for A1; PG cycle mirrors contract window with credits_used=163

seed-demo preserves this wallet for the A1 cohort (does not AssignPlan over it — that would zero used_credits and inflate totals).

Fresh processing state (separate from seed)

After npm run seed:a1 / go run ./cmd/seed-a1 -mode reimport …, clear processing history without wiping catalog:

cd apps/api
$env:DATABASE_URL = "postgres://descrybe:descrybe@localhost:5433/descrybe?sslmode=disable"
go run ./cmd/seed-a1-reset-processing
# optional: -dry-run  |  -name "A1 Slovenija"

Retains feeds, mappings, raw/mapped products, attributes, categories. Deletes processing jobs/job-products and processed_products; sets all raw to unprocessed.

Local processing when credits run low

Remaining 2337 is enough for light local smoke. For heavy AI runs without rewriting dump usage history:

  1. Prefer platform-admin AddCredits (increases total_credits only; keeps used_credits / _legacy_usage accurate), or
  2. Process under a separate non-A1 sandbox company.

Do not re-seed a 1M “demo pack” onto A1 — that confuses Company Settings.

SELECT c.name, p.name AS plan, p.monthly_credits, p.yearly_credits, p.term, p.is_custom,
       cp.total_credits_allocated, cb.total_credits, cb.used_credits,
       (cb.total_credits - cb.used_credits) AS remaining
FROM companies c
JOIN company_plans cp ON cp.company_id = c.id AND cp.is_active
JOIN plans p ON p.id = cp.plan_id
JOIN credit_balances cb ON cb.company_id = c.id
WHERE c.legacy_company_id = '97e1a309-3d23-4aa2-b518-8e8d7afdfec7';
# A1 wallet check: login as a1-primary@ (or Admin switch), NOT demo@ — expect plan=A1 remaining≈2337
# demo@ lands on Platform Demo (custom plan / high credits), not A1
curl -s -b cookies.txt http://localhost:8080/api/billing/credits
curl -s -b cookies.txt http://localhost:8080/api/me

How it was created

cd apps/api
$env:DATABASE_URL = "postgres://descrybe:descrybe@localhost:5433/descrybe?sslmode=disable"
go run ./cmd/seed-demo -postgres $env:DATABASE_URL
# default email is demo@descrybe.local — override with -email if needed

seed-demo upserts the demo user(s) (argon2id), sets platform admin, ensures Platform Demo company (never renames A1), binds membership only to that company (removes stray A1 memberships), upserts the demo API key on Platform Demo, and assigns the custom Platform Demo plan (full features / high credits). A1 wallet and plan stay dump-faithful — see Billing section below. New self-serve signups still get Free — see free-tier.md.

Company-admin promote (migrator)

Imported cutover memberships default to role=member. Local Demo does not need -promote-company-adminsseed-demo already grants admin on Platform Demo.

cd apps/api
# Inventory (read-only). Expect a1-primary@ on A1 with a1=true; demo@ will not appear (already admin).
go run ./cmd/migrator -list-member-memberships -postgres $env:DATABASE_URL

# Promote non-A1 only (A1 always skipped). Scope with -email / -user-id / -company-id.
go run ./cmd/migrator -promote-company-admins -company-id <non-a1-uuid> -dry-run -postgres $env:DATABASE_URL
go run ./cmd/migrator -promote-company-admins -company-id <non-a1-uuid> -confirm -postgres $env:DATABASE_URL

Never promote a1=true. Full ops notes: cutover.md.

Empty local register (no migrated catalog):

pwsh -File .\scripts\seed-local.ps1

Primary company (Platform Demo)

Login lands on Platform Demo. A1 Slovenija (604f23a8-… / legacy 97e1a309-…) stays a separate customer tenant — switch via Admin impersonation, not demo membership.

Completeness vs legacy MySQL (A1 Slovenija)

Verified after migrator live load + dump-faithful billing restore. MySQL source: descrybe_new company 97e1a309-3d23-4aa2-b518-8e8d7afdfec7.

Entity MySQL (A1) Postgres (A1 Slovenija) Status
Categories 119 120 OK (+1 local CSV smoke Test Category)
Attributes 303 303 OK
Category attributes 307 307 OK
Custom variables 3 3 OK
Input feeds 12 12 OK
Feed mappings 11 11 OK (ComTrade has no field_mappings in MySQL either)
Category title/description formulas 119 / 119 119 / 119 OK — list API now returns templates + has_*_formula flags
Standard fields (A1 dump keys) 22 22 (+ ecommerce seed) After ensure: ~44 enabled (dump keys kept + full ecommerce catalog for mapping)
Export feeds 2 2 OK
Processed products 4321 4323 OK (+2 local smoke samples)
Raw products 31062 23744 OK intentional — 7320 GTIN deduped under (company_id, gtin) unique; +2 smoke
Orphan FKs (company-scoped) 0 OK (mappings/company, processed→raw/feed, raw→feed, export→source)

Global migrator validation: artifacts/validation-report.jsonmode: live, orphans 16/16 pass. Attribute collapse globally (95k → 57k) is distinct-key unique constraint, not an A1 gap.

Company ID Input feeds Products (processed) Raw products Export feeds Categories Mappings
A1 Slovenija (default) 604f23a8-b66e-4b21-8b45-0d72b68f4790 12 ~4323 ~23744 2 120 11
Descrybe 3802e3c1-b292-4d1b-9fcf-eca193f364c8 10 2742 85608 2 2334 8
Merkur d.o.o. 57921d6c-c65a-4867-b9df-e5cbe39d741b 3 151 9429 2 5442 2

Counts drift slightly after CSV smoke uploads; always key off company id 604f23a8-… / legacy_company_id 97e1a309-….

App paths (accurate)

Task Path
Products /products (use Unprocessed if Processed looks empty — UI also auto-falls back)
Feeds / mapping /feeds → feed → mappings (Elkotex: /feeds/7495b404-68d3-4f94-9568-1d2c7a81ee8a/mapping)
Category formulas Categories → Edit → Title/Description formula (templates migrated; list shows has_*_formula)
Standard fields /standard-fields — Enable recommended if mapping targets look sparse after a fresh import
CSV uploads Dashboard Upload CSV; /products, /categories, /attributes; history at /files
Process history /processing (migrated A1 jobs when domain jobs was imported; also live jobs)
Switch to Primary A1 Login a1-primary@descrybe.local / DemoPass123!, or Admin → Users → Switch to user
Brand + logo /brand (POST /api/brand/logo)
Campaigns /campaigns
Content calendar /marketing/calendar
SEO /seo
Reviews /woocommerce?tab=reviews (nav /reviews redirects here)
Email sending /integrations/email
Billing / plans /billing, /plans
Public pricing /pricing

API CSV: POST /api/products/upload, /api/categories/upload, /api/attributes/upload (and /import aliases). Product search: q or search; feed_id filters supported.

After backend route changes, rebuild/restart the Go API or smoke can show stale 404s.

Switching company

POST /api/auth/select-company with CSRF + session cookie:

{ "company_id": "3802e3c1-b292-4d1b-9fcf-eca193f364c8" }

Smoke checks

SELECT email, must_set_password, is_platform_admin, is_active
FROM users WHERE email = 'demo@descrybe.local';

SELECT c.id, c.name,
  (SELECT COUNT(*) FROM processed_products p WHERE p.company_id = c.id) AS products,
  (SELECT COUNT(*) FROM input_feeds f WHERE f.company_id = c.id) AS feeds
FROM companies c
WHERE c.name = 'Platform Demo'
ORDER BY c.created_at DESC
LIMIT 1;
-- expect Platform Demo sandbox (not A1 Slovenija / 604f23a8-…)
cd apps/api
$env:DATABASE_URL = "postgres://descrybe:descrybe@localhost:5433/descrybe?sslmode=disable"
go run ./cmd/seed-demo -postgres $env:DATABASE_URL

# Session login (CSRF double-submit cookie from any GET)
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
try { Invoke-WebRequest http://127.0.0.1:8080/api/auth/me -WebSession $session -UseBasicParsing | Out-Null } catch {}
$csrf = ($session.Cookies.GetCookies("http://127.0.0.1:8080") | Where-Object Name -eq descrybe_csrf).Value
$body = '{"email":"demo@descrybe.local","password":"DemoPass123!"}'
Invoke-WebRequest http://127.0.0.1:8080/api/auth/login -Method POST -WebSession $session `
  -Headers @{ "X-CSRF-Token" = $csrf; "Content-Type" = "application/json" } -Body $body -UseBasicParsing
Invoke-RestMethod http://127.0.0.1:8080/api/products?limit=3 -WebSession $session
Invoke-RestMethod http://127.0.0.1:8080/api/feeds -WebSession $session
Invoke-RestMethod http://127.0.0.1:8080/api/categories?limit=5 -WebSession $session

Expect: login 200, default company Platform Demo, demo API key scoped to that company. A1 catalog counts (~4323 products) belong to A1 Slovenija only — switch as a1-primary@descrybe.local, do not process under demo.

cd apps/api; go build ./...
cd ../web; npm run check