# E2E: Feeds → Map → Process → Export (Local Demo Co) **When:** 2026-08-04 **Tenant:** Local Demo Co (`ee246275-dec0-4446-9e83-58d0c16c258a`) **Web:** http://127.0.0.1:5174 (not 5173 — that port may be another Vite app) **API:** http://127.0.0.1:8080 **DB:** `postgres://descrybe:***@localhost:5433/descrybe` ## Credentials | | | |---|---| | Email | `demo@descrybe.local` | | Password | `DemoPass123!` | | API key | `dk_demo_local_descrybe_test_key_v1` | Session login needs CSRF: GET any `/api/*` route to set `descrybe_csrf`, then POST `/api/auth/login` with header `X-CSRF-Token`. ```http Authorization: Bearer dk_demo_local_descrybe_test_key_v1 ``` ## Pipeline (what “done” looks like) ``` input feeds → PUT mappings → (sync*) → POST process → export generate → public XML/CSV ``` \* Live FTP/FTPS supplier URLs return `400 ftp/ftps feed sync is not supported yet`. Local Demo Co already has migrated raw/processed rows, so process + export work without re-sync. ## Verified counts (after this E2E) | Surface | Result | |---------|--------| | Input feeds | **12** (`active` / `mapped`) | | Feed mappings | Present (e.g. Vama Trade **13** fields) | | Processed products | **~4330** (`GET /api/v1/products`) | | Raw / unprocessed | **23744** (`kind=raw` or `kind=unprocessed`) | | Export generate XML | **4322** products, `status=completed` | | Export generate CSV | **4322** products, `status=completed` | | Process job | **5/5** completed (`normalize` → specs → fill → eprel skipped → ai) | ## API path (copy/paste) ### 1. List feeds + mappings ```powershell $H = @{ Authorization = "Bearer dk_demo_local_descrybe_test_key_v1"; "Content-Type" = "application/json" } Invoke-RestMethod http://127.0.0.1:8080/api/v1/feeds -Headers $H Invoke-RestMethod http://127.0.0.1:8080/api/v1/feeds/b5fc7c4c-830b-4d6e-a90c-9b72800a89a1/mappings -Headers $H ``` ### 2. Save mappings (flips `unmapped` → `mapped`, sets `options.item_path`) Body uses the dashboard shape `{ key, mapping: { fieldName, xpath } }`: ```powershell $body = @{ mappings = @( @{ key = "Export/Item/name"; mapping = @{ fieldName = "name"; xpath = "Export/Item/name" } } @{ key = "Export/Item/EAN"; mapping = @{ fieldName = "gtin"; xpath = "Export/Item/EAN" } } ) } | ConvertTo-Json -Depth 6 Invoke-RestMethod -Method PUT -Uri http://127.0.0.1:8080/api/v1/feeds//mappings -Headers $H -Body $body ``` ### 3. Process sample raw products ```powershell $raw = Invoke-RestMethod "http://127.0.0.1:8080/api/v1/products?kind=raw&limit=5" -Headers $H $ids = @($raw.products | ForEach-Object { $_.id }) $jobBody = @{ raw_product_ids = $ids; processing_type = "normalize" } | ConvertTo-Json $job = Invoke-RestMethod -Method POST -Uri http://127.0.0.1:8080/api/v1/process -Headers $H -Body $jobBody # poll GET /api/v1/process/{id} until status=completed (worker must be running) ``` Dashboard alias: `POST /api/processing/jobs` (same body). ### 4. Export XML + CSV ```powershell # Example feed IDs on Local Demo Co $xmlId = "d52bc02c-77ee-4cd5-bcd9-fde652133f38" # XML - Example $csvId = "d6ccb83e-7c76-4328-8b60-820503bd24d0" # CSV - Example Invoke-RestMethod -Method POST -Uri "http://127.0.0.1:8080/api/v1/export-feeds/$xmlId/generate" -Headers $H -Body "{}" Invoke-RestMethod -Method POST -Uri "http://127.0.0.1:8080/api/v1/export-feeds/$csvId/generate" -Headers $H -Body "{}" # Public downloads (token from export feed) # GET /api/public/export-feeds/{public_token}.xml # GET /api/public/export-feeds/{public_token}.csv ``` Example tokens after seed/migrate: XML `f2321bf2d18609b7abf5ea0f0f9fbb86`, CSV `7fdea27d7bc9e21aec6403e24ccb3c8a`. ## UI path | Step | URL | |------|-----| | Login | http://127.0.0.1:5174/login | | Feeds | http://127.0.0.1:5174/feeds | | Mapping | http://127.0.0.1:5174/feeds/`{feedId}`/mapping | | Products | http://127.0.0.1:5174/products (Processed / Unprocessed tabs) | | Processing | http://127.0.0.1:5174/processing | | Export feeds | http://127.0.0.1:5174/export-feeds | Session-cookie calls go through the Vite proxy (`/api/...` → `:8080`). Same CSRF rules as direct API. SSR smoke (logged in): `/dashboard`, `/feeds`, `/products`, `/export-feeds`, `/processing`, `/feeds/.../mapping` → **200**. ## Blockers fixed in this pass | Issue | Fix | |-------|-----| | `parseMappings` ignored UI `{key,mapping}` rows → sync would apply **zero** fields | Unwrap nested `mapping` + `key` as xpath/source in `internal/feeds/mapping.go` | | PUT mappings left feed `status=unmapped` | Derive `item_path` from xpath parents; always set `unmapped`→`mapped` when fields saved (`service.go` / `extract_schema.go`) | | `kind=unprocessed` ignored (returned processed) | Alias to raw list in `catalog_handlers.go` | | Empty-looking processed tab | Data was present (~4.3k); use Unprocessed/`kind=raw` for inventory; API totals verified | ## Known limits - **FTP/FTPS sync** not supported: `POST .../sync` and `.../sync-process-sample` return 400 for supplier FTP URLs (Vama, ComTrade, …). - **Worker required** for process jobs (`scripts/run-api.ps1 worker` or `go run ./cmd/worker`). - Public export **HEAD** may 405; use **GET**. - Web port is **5174** in local docs/smoke; confirm with the Descrybe title before testing. ## Verification commands ```powershell cd apps/api go test ./internal/feeds ./internal/httpapi -count=1 go build -o bin/api.exe ./cmd/api cd ../web npm run check ``` ## Related - [demo-user.md](demo-user.md) — credentials / API key - [qa-local-demo.md](qa-local-demo.md) — broader Local Demo Co checklist - [local-smoke-results.md](local-smoke-results.md) — prior smoke matrix