- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
4.6 KiB
SDS Sync Guard Fix Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Prevent the SDS (mapi.sdspod.com) sync from deleting the category/product library when the upstream API returns a degenerate/partial response, and recover the data lost in the 2026-08-20 incident.
Background / Root cause:
- On 2026-08-20 06:31 a manual sync ran while
category/tree/3returned only 2 nodes (normal: 226). syncCategoriestreats SDS as the single source of truth:staleDeleted=225deleted all previously synced SDS categories.- With SDS categories gone,
syncProductshadleafCategories=0/2→delisted=523marked 523/536 origin goods as delisted. - Result: all 536 origin goods
delisted=true; all 240 websiteGoodrows reference delisted origin goods →/public/goodsreturnstotal: 0→ product-center page appears empty.
Architecture: Add safety guards to SyncService and SdsClientService so a partial/failed upstream response never triggers destructive operations (staleDeleted / delist marking). Recovery is a data fix (re-enable delisted origin goods) followed by normal re-sync.
Tech Stack: NestJS, TypeScript, Prisma, Jest.
Task 1: Data recovery — re-enable all origin goods
Files:
-
Run ad-hoc prisma script (read+write to DB via apps/api @prisma/client)
-
Step 1: Reset
delisted=falsefor alloriginGoodrows All 536 rows were mis-flagged by the 08-20 partial sync. Runprisma.originGood.updateMany({ where: {}, data: { delisted: false } }). -
Step 2: Trigger category sync then product sync Verify
/sync/statuslogs show SUCCESS with healthy numbers (categories ≈ 225+, products ≈ 519+). -
Step 3: Verify
/public/goodsnon-empty and product-center page renders products.
Task 2: Hardening — category sync stale-deletion guard
Files:
-
Modify:
apps/api/src/sync/sync.service.ts -
Modify:
apps/api/src/sync/sync.service.spec.ts -
Step 1: Write failing test Given existing SDS-linked categories in DB, when
fetchCategoryTreereturns a degenerate small tree (< MIN_CATEGORY_COUNT),syncCategoriesmust NOT delete existing categories (no stale deletion) and must log a warning. -
Step 2: Run test, verify RED.
-
Step 3: Implement guard Before the stale-deletion block, compute
existingSdsCount = categories with sdsCategoryId. Skip stale deletion whenflat.length < MIN_CATEGORY_COUNT (10)ORflat.length < MIN_CATEGORY_RATIO (0.5) * existingSdsCount. Log warning with counts. -
Step 4: Verify GREEN + no regressions.
Task 3: Hardening — product sync delist guard
Files:
-
Modify:
apps/api/src/sync/sync.service.ts -
Modify:
apps/api/src/sync/sync.service.spec.ts -
Step 1: Write failing test When there are very few leaf categories (< MIN_LEAF_CATEGORIES) or very few seen products (< MIN_SEEN_GOODS),
syncProductsmust skip the delist/reactivate marking and log a warning instead of mass-delisting. -
Step 2: Run test, verify RED.
-
Step 3: Implement guard Only run delist detection when
leafRows.length >= MIN_LEAF_CATEGORIES (10)ANDseenSdsGoodIds.size >= MIN_SEEN_GOODS (50). Otherwise skip and log warning. -
Step 4: Verify GREEN + no regressions.
Task 4: Hardening — SDS client response sanity validation
Files:
-
Modify:
apps/api/src/sync/sds-client.service.ts -
Modify:
apps/api/src/sync/sds-client.service.spec.ts(create if absent) -
Step 1: Write failing test
fetchCategoryTreethrows when the returned array is degenerate (e.g. fewer thanMIN_SDS_CATEGORY_NODES), so callers never operate on a bad tree. -
Step 2: Run test, verify RED.
-
Step 3: Implement If the fetched category array length < MIN_SDS_CATEGORY_NODES (10), throw an Error with the received count so sync logs FAILED (non-destructive) instead of deleting data.
-
Step 4: Verify GREEN + no regressions.
Task 5: Full verification
- Step 1: Run
apps/apifull jest suite — all green. - Step 2: Typecheck
apps/api(tsc -p tsconfig.json --noEmitor nest build). - Step 3: Trigger a real sync via the admin UI / HTTP; confirm sync logs show
healthy category/product counts and
/public/goodsreturns data. - Step 4: Confirm product-center page at
http://192.168.124.137:3000/product-centerrenders products.