Ty
714a227e57
feat(photos): S3 multipart BLOB storage + original/processed GET + 410 after purge + attach UI
...
- backend/photos.py: POST /slabs/{id}/photos (multipart original+processed ->
slab_photos BLOBs, re-upload replaces), GET original/processed (image/png, 410
once purged), GET list. Cites CAPTURE-95/105/130, PUBLISH-40.
- frontend: create/photos/PhotoAttach.svelte (slab id, original/processed files,
calibration flag, lineup) + dev toggle.
- tests: 5 photos tests (upload+get both variants, 410 after purge, 404 slab,
reupload replaces, list).
Verified: pytest 36 passed; gates exit 0; npm build ok; live QA upload->200,
get original/processed->200, purge->410.
2026-09-19 16:47:07 -07:00
Ty
d83c9d5c21
feat(woo): S4 Woo client + taxonomy sync + test-woo + admin taxonomy
...
- backend/woo.py: stdlib urllib Woo v3 client. Basic-auth, HTTPS-only, X-WP-TotalPages
pagination, typed errors (CredentialsMissing/Auth/NotFound/Server/Connection),
injectable transport for tests, WP core media upload.
- backend/taxonomy.py: sync pulls categories/tags/attributes/terms into cache in one
commit (rollback keeps prior cache on failure); fig-*/feat-* tag scoping; stable
taxonomy_anchor hash; get_cached grouped admin view.
- backend/app.py: POST /settings/test-woo (fail-closed, resync on success),
GET /admin/taxonomy, POST /admin/taxonomy/sync (502 on Woo failure).
- frontend: Settings Test Woo button + result display.
- tests: 8 woo/taxonomy tests (auth header, non-https refusal, pagination, missing
creds, 401, scoped sync, failure-keeps-cache, anchor stability).
Verified: pytest 31 passed; check_requirements + check_coverage exit 0; npm build ok;
live QA test-woo fail-closed + sync 502.
2026-09-19 16:40:37 -07:00
Ty
38eebbc8bb
fix(db): generalize schema-drift detection to all M3 tables (slabs, slab_photos, sync_log)
...
Live host had stale M2 slabs table (no deleted_at/server_rev) -> GET /slabs 500.
Drift check now verifies sentinel (table,column) pairs across settings/slabs/
slab_photos/sync_log; on drift drops all app-owned M3 tables and rebuilds, stamps
schema_version. Drift test now seeds M2 settings + slabs and asserts both rebuilt.
pytest 23 passed; gates exit 0.
2026-09-19 16:30:43 -07:00
Ty
cc5241f7da
feat(slabs): S2 CRUD + optimistic concurrency + soft delete + slab list UI
...
- backend/slabs.py: create (calibrated, client-derived stored as sent), list (excl
soft-deleted, most recent), get, update (single compare-and-set on server_rev,
409 revision_conflict / 409 duplicate_sku / 422 stale_field, status server-derived),
delete (soft, published rejected). Explicit column allowlists, parameterized SQL,
JSON array fields, commits.
- backend/app.py: wire slabs router.
- frontend: src/api/slabs.ts client + create/slabs/SlabList.svelte + dev toggle.
- tests: 9 slabs tests (create/dup/list/get/update/rev-conflict/dup-sku/soft-delete/
published-delete) with full taxonomy seeding.
Verified: pytest 23 passed; check_requirements + check_coverage exit 0; npm build ok.
2026-09-19 16:29:06 -07:00
Ty
a0032ddd2d
fix(db): schema_version drift detection + rebuild for pre-M3 volumes
...
- Add schema_version table; init_db detects a pre-M3/unknown DB (no version
row, or settings table missing the value column) and rebuilds to the clean
M3 schema, then stamps version 1
- _has_schema_version checks for a real row (empty table != versioned)
- _reset_for_tests checkpoints WAL before close (Windows handle release)
- TDD: test_schema_drift seeds the M2 settings shape, asserts rebuild + version
Fixes live 500 'no such column: value' on the UAT host (stale M2 slab_db volume).
Gates: check_requirements + check_coverage exit 0; pytest 14 passed.
2026-09-19 16:05:40 -07:00
Ty
60bc2fb9d0
feat: add schema drift handling and rebuild
2026-09-19 16:02:04 -07:00
Ty
6aaad81583
fix(deploy): restore context: .. in compose (relative to deploy/ file); remove invalid || from backend/Dockerfile; prune backend from .dockerignore so COPY succeeds
2026-09-19 15:53:49 -07:00
Ty
b3380f3848
feat(settings): S1 AES-GCM crypto + Settings API/UI (masked write-only secrets)
...
- backend/crypto.py: AES-GCM (cryptography.AESGCM); key=base64(SLAB_AES_KEY)
must be exactly 32 bytes; value_enc=base64(nonce(12)||ct); DecryptError on tamper
- backend/settings.py: get_settings (secrets->{configured:bool}, woo_base_url env
read-only, flags default false), update_settings (rejects woo_base_url SETTINGS-85,
encrypts secrets, partial plain writes)
- backend/app.py: wire GET/PUT /api/v1/settings
- frontend/src/create/settings/Settings.svelte: masked form + partial PUT
- tests: crypto roundtrip/tamper, masked GET, encrypted PUT, reject woo_base_url,
flags default, decrypt-fail->configured:false (TDD)
Verified live: secrets round-trip configured:true with valid key; woo_base_url write
rejected 400; no secret leak in GET. Gates: check_requirements + check_coverage exit 0;
pytest 13 passed. Deploy+build on ubudual5060 (never Windows).
2026-09-19 15:48:42 -07:00
Ty
0eb2b0f697
feat(backend): S0 foundation (schema + stdlib sqlite3 db factory + health/meta + JSON logs)
...
- backend/db/schema.sql: all 12 tables from DATA-MODEL.md (taxonomy empty)
- backend/db: stdlib sqlite3 factory, WAL/FK/busy_timeout/synchronous pragmas,
single locked connection, explicit SLAB_DB_PATH/SLAB_IMAGES_DIR
- backend/app.py: /api/health (non-200 on storage not writable), /api/v1/meta,
JSON logging with request-id threading + secret redaction, startup guards
(SETTINGS-10 WOO_BASE_URL https, SETTINGS-25 SLAB_AES_KEY)
- tests: schema-loads, health happy/failure, startup guards (TDD)
- docs/contracts: add requirement-ID citations to 12 sections so
check_requirements.py passes (pre-existing gate debt)
Gates: check_requirements.py + check_coverage.py exit 0; pytest 5 passed
2026-09-19 15:34:15 -07:00