Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
307 lines
14 KiB
Markdown
307 lines
14 KiB
Markdown
# 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](safe-test-fixtures.md)
|
|
|
|
**QA walkthrough:** [qa-local-demo.md](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](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!`):
|
|
|
|
```bash
|
|
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 |
|
|
|
|
```http
|
|
Authorization: Bearer dk_demo_local_descrybe_test_key_v1
|
|
```
|
|
|
|
or
|
|
|
|
```http
|
|
X-API-Key: dk_demo_local_descrybe_test_key_v1
|
|
```
|
|
|
|
```bash
|
|
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:
|
|
|
|
```powershell
|
|
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.
|
|
|
|
```sql
|
|
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';
|
|
```
|
|
|
|
```powershell
|
|
# 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
|
|
|
|
```powershell
|
|
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](free-tier.md).
|
|
|
|
### Company-admin promote (migrator)
|
|
|
|
Imported cutover memberships default to `role=member`. Local Demo does **not** need `-promote-company-admins` — `seed-demo` already grants **admin** on Platform Demo.
|
|
|
|
```powershell
|
|
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](cutover.md#promote-company-admins-rolemember--admin).
|
|
|
|
Empty local register (no migrated catalog):
|
|
|
|
```powershell
|
|
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.json` — `mode: 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:
|
|
|
|
```json
|
|
{ "company_id": "3802e3c1-b292-4d1b-9fcf-eca193f364c8" }
|
|
```
|
|
|
|
## Smoke checks
|
|
|
|
```sql
|
|
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-…)
|
|
```
|
|
|
|
```powershell
|
|
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.
|
|
|
|
```powershell
|
|
cd apps/api; go build ./...
|
|
cd ../web; npm run check
|
|
```
|
|
|
|
## Related
|
|
- [e2e-feeds-process-export.md](e2e-feeds-process-export.md)
|
|
|
|
- [qa-local-demo.md](qa-local-demo.md) — login steps, nav status, uploads, known limits
|
|
- [migration-run-log.md](migration-run-log.md) — full MySQL→PG migrator report
|
|
- [marketing-suite-user-guide.md](marketing-suite-user-guide.md)
|
|
- [free-tier.md](free-tier.md)
|
|
- [go-live-checklist.md](go-live-checklist.md)
|
|
- Repo root `README.md`
|
|
|
|
## Related
|
|
|
|
- [api-surface-smoke.md](api-surface-smoke.md) — admin / healthz / docs / public v1 smoke
|
|
- [qa-local-demo.md](qa-local-demo.md)
|