Files

221 lines
9.6 KiB
Markdown
Raw Permalink Normal View History

# Migrate `descrybe_new.sql` → v2 Postgres (A1 only)
**Date:** 2026-08-08 (local reseed from `descrybe_new (1).sql`)
**Status:** **PASS**
**Scope:** Single tenant **A1 Slovenija** only (all other dump tenants skipped / cleaned)
Secrets are masked below. Do not commit `.env`, MySQL passwords, `artifacts/`, or invite tokens.
## What “a1” resolved to
| Field | Value |
|-------|--------|
| MySQL company name | `A1 Slovenija` |
| MySQL / legacy `company_id` | `97e1a309-3d23-4aa2-b518-8e8d7afdfec7` |
| Match used | `companies.name` LIKE `%A1%` → exact row above (only A1 hit) |
| Postgres after migrate + demo | **A1 Slovenija** (dump name kept) |
| Postgres `companies.id` | `604f23a8-b66e-4b21-8b45-0d72b68f4790` (stable via `artifacts/descrybe-new-a1/id-map.json`) |
| `legacy_company_id` | `97e1a309-3d23-4aa2-b518-8e8d7afdfec7` |
**Company naming:** Keep dump name **A1 Slovenija**. Do not rename to “Local Demo Co”. Wallet/plan come from MySQL `credit_balances` / plan **A1** (2500 total / 163 used / 2337 remaining on 2026-08-08 dump) — not a fake 1M demo pack.
## Inputs
| Item | Value |
|------|--------|
| Dump | `C:\Users\Green Eclipse\Downloads\descrybe_new (1).sql` (~549 MB Adminer MySQL 8 dump) |
| Temp MySQL DB | Laragon `descrybe_new` (left intact; did **not** overwrite `descrybe_v2`) |
| Postgres | Docker `descrybe-v2-postgres` host port **5433** |
| Postgres DSN (local default) | `postgres://descrybe:descrybe@localhost:5433/descrybe?sslmode=disable` |
| Migrator | `apps/api/cmd/migrator` |
| Maps / report | `artifacts/descrybe-new-a1/` (gitignored); copy `docs/migration-reports/migration-report-a1-latest.json` |
## Commands run
### 1. Wipe v2 Postgres only (keep Docker volume)
```powershell
docker exec descrybe-v2-postgres psql -U descrybe -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'descrybe' AND pid <> pg_backend_pid();"
docker exec descrybe-v2-postgres psql -U descrybe -d postgres -c "DROP DATABASE IF EXISTS descrybe;"
docker exec descrybe-v2-postgres psql -U descrybe -d postgres -c "CREATE DATABASE descrybe OWNER descrybe;"
```
### 2. Goose migrations (001024)
```powershell
cd f:\laragon\www\_MY\descrybe-v2\apps\api
$env:DATABASE_URL = "postgres://descrybe:descrybe@localhost:5433/descrybe?sslmode=disable"
go run github.com/pressly/goose/v3/cmd/goose@v3.24.3 -dir sql/schema postgres $env:DATABASE_URL up
```
Note: `024_ai_prompts.sql` had a UTF-8 BOM (`\ufeff`); stripped so goose could parse (same class of fix as earlier `006_feed_sync.sql`).
### 3. Import dump into temp MySQL (streamed; ~2 min)
```powershell
# create empty DB, then stream file into mysql client (do not load 862MB into PowerShell)
mysql -uroot -p**** -e "DROP DATABASE IF EXISTS descrybe_new; CREATE DATABASE descrybe_new CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# stdin stream of descrybe_new.sql → descrybe_new
```
Dump-wide MySQL counts (before filter): companies 31, categories 8465, attributes 198319, xml_feeds 33, raw_products 159070, processed_products 7953, export_feeds 11.
### 4. A1-only migrator
```powershell
cd f:\laragon\www\_MY\descrybe-v2\apps\api
$env:DATABASE_URL = "postgres://descrybe:descrybe@localhost:5433/descrybe?sslmode=disable"
$env:MIGRATE_MYSQL_DSN = "root:****@tcp(localhost:3306)/descrybe_new?parseTime=true"
$maps = "f:\laragon\www\_MY\descrybe-v2\artifacts\descrybe-new-a1"
$a1 = "97e1a309-3d23-4aa2-b518-8e8d7afdfec7"
go build -o $env:TEMP\descrybe-migrator.exe ./cmd/migrator
# dry-run then live (filter at migrate time)
& $env:TEMP\descrybe-migrator.exe -mysql $env:MIGRATE_MYSQL_DSN -postgres $env:DATABASE_URL `
-company $a1 -dry-run -maps-dir $maps -report-dir $maps -resume=false
& $env:TEMP\descrybe-migrator.exe -mysql $env:MIGRATE_MYSQL_DSN -postgres $env:DATABASE_URL `
-company $a1 -maps-dir $maps -report-dir $maps -resume `
-ensure-demo -demo-email demo@descrybe.local -demo-password 'DemoPass123!' `
-skip-post-import
```
Live elapsed ≈ **32s**. Validation: `mode: live ok=true`, orphan checks **17/17 PASS**.
### 5. Seed demo (Enterprise + API key + `.test` alias)
```powershell
go run ./cmd/seed-demo -postgres $env:DATABASE_URL `
-email demo@descrybe.local -password 'DemoPass123!'
```
### 6. Post-cleanup (junk from global user import)
Migrator still upserts **all** MySQL profile→user rows even with `-company` (memberships are filtered). Removed users with **no** membership (kept demo emails):
```sql
DELETE FROM users u
WHERE NOT EXISTS (SELECT 1 FROM memberships m WHERE m.user_id = u.id)
AND lower(u.email) NOT IN ('demo@descrybe.local','demo@descrybe.test');
-- deleted 22 orphan users
```
### 6b. Import A1 job history (optional follow-up)
Job history was skipped in the initial cutover. Re-run with domain `jobs` (rows tagged `ai_provider_mode=migrated` so 30-day retention does not purge them):
```powershell
& $env:TEMP\descrybe-migrator.exe -mysql $env:MIGRATE_MYSQL_DSN -postgres $env:DATABASE_URL `
-company $a1 -domains jobs -maps-dir $maps -report-dir $maps -resume -skip-post-import
```
Local/dev (**outdated — do not follow**): demo no longer owns A1; use **Platform Demo** + `a1-primary@` per [safe-test-fixtures.md](safe-test-fixtures.md). Historical logins:
| Role | Email | Password |
|------|-------|----------|
| Demo admin | `demo@descrybe.local` (alias `.test`) | `DemoPass123!` |
| Primary A1 | `a1-primary@descrybe.local` | `DemoPass123!` |
Primary maps to Clerk `user_30AqqJ8uepxvPUzDSqy81U5w6Ll`. Other A1 MySQL profiles were removed after migrate (jobs reassigned to primary). To reset the primary password:
```powershell
go run ./cmd/migrator -postgres $env:DATABASE_URL `
-set-password 'a1-primary@descrybe.local:DemoPass123!'
```
### 6c. Local Demo Co user cleanup (2026-08-05)
MySQL A1 (`97e1a309-…`) had **5** `profiles.user_id` values. Preference: keep **demo + primary only**.
| Action | Detail |
|--------|--------|
| Kept | `demo@descrybe.local`, `demo@descrybe.test`, `a1-primary@descrybe.local` (`user_30AqqJ…`) |
| Deleted (4) | `user_2tJxu…`, `user_2yaUP…`, `user_2zzh…`, `user_3005r…` (`@legacy.local`) |
| Reassigned | 3 `processing_jobs.user_id` → primary (280 jobs on primary; 51 still null) |
| Catalog | Unchanged — **4378** processed products, 331 jobs |
**Do not restore** those four `@legacy.local` members. A later agent re-inserted them for switcher labeling; they were deleted again. Local Demo Co stays at **3 users** / **3 memberships**. UserSwitcher labels: **Demo admin** (`.local` + `.test`) and **A1 · …81U5w6Ll** (`is_primary_a1`).
## Row counts — kept vs skipped
### Migrator written (A1 filter)
| Entity | Written / kept | Notes |
|--------|---------------:|-------|
| companies | 1 | Only A1; renamed → Local Demo Co |
| memberships | 5 → **3** after cleanup | A1 primary + 2 demo admins |
| users (after cleanup) | **3** | demo `.local` + demo `.test` + `a1-primary@descrybe.local` |
| plans | seed + migrated catalog | Global plan rows OK for local billing |
| company_plans | 1 → Enterprise via seed-demo | Other tenants plans skipped |
| credit_balances | 1 | 1M Enterprise credits after seed |
| categories | 119 | |
| attributes | 303 | |
| category_attributes | 307 | |
| custom_variables | 3 | |
| input_feeds (xml) | 12 | |
| feed_mappings | 11 | ComTrade has 0 mappings in source too |
| raw_products | 23748 | MySQL A1 had 56132; GTIN-deduped under `(company_id, gtin)` |
| processed_products | 4330 | |
| export_feeds | 2 | |
| files | 0 | Dump had 1 file metadata row for another context / skipped |
| processing_jobs | 179 | via `-domains jobs` (`ai_provider_mode=migrated`) |
### Skipped (other tenants / noise)
| Item | Approx. skipped |
|------|----------------:|
| Other companies | 30 of 31 |
| Non-A1 categories / attributes / feeds / products / exports | Rest of dump (see MySQL totals above) |
| Orphan users after cleanup | 22 |
| `company_plans` for non-A1 | 4 skipped in migrator log |
| `credit_balances` for non-A1 | 7 skipped |
| API keys / Clerk passwords / job history | Never imported |
### Final Postgres snapshot (A1 Slovenija — 2026-08-08)
| Metric | Count |
|--------|------:|
| companies | 1 (+ `__platform_settings__`) |
| users | **3** (demo.local, demo.test, a1-primary) |
| memberships | **3** |
| categories | 119 (108 with AI prompts overlay) |
| attributes | 303 |
| category_attributes | 307 |
| input_feeds | 12 |
| feed_mappings | 11 |
| raw_products | 23748 |
| processed_products | 4330 |
| export_feeds | 2 |
| processing_jobs | 179 |
| custom_variables | 3 |
| wallet | 2500 / 163 used / 2337 remaining |
## Demo admin (local only)
| Field | Value |
|-------|--------|
| Email | `demo@descrybe.local` (alias `demo@descrybe.test`) |
| Password | `DemoPass123!` (already documented in `docs/demo-user.md`) |
| Role | Platform admin + company **admin** on Local Demo Co |
| Plan | Enterprise — 1_000_000 AI credits, unlimited SKUs |
| API key | `dk_demo_local_descrybe_test_key_v1` (hash only in DB) |
Web: http://localhost:5174/login · API: http://localhost:8080
## Verification (this run)
| Check | Result |
|-------|--------|
| `GET /healthz` | ok |
| `GET /readyz` | database ok |
| `POST /api/auth/login` (CSRF cookie + Origin) | **200** → company Local Demo Co |
| `GET /api/v1/products?limit=1` (demo API key) | **200**, `meta.total=4378` |
| `GET /api/v1/feeds` | **200**, 12 feeds |
## Related docs
- [portable-mysql-pg-migration.md](portable-mysql-pg-migration.md) — migrator flags / domains
- [demo-user.md](demo-user.md) — demo credentials & Enterprise wallet
- [migration-run-log.md](migration-run-log.md) — prior full-tenant rehearsal
- Report: [migration-reports/migration-report-a1-latest.json](migration-reports/migration-report-a1-latest.json)