-
Notifications
You must be signed in to change notification settings - Fork 0
Tech Debt
keanu77 edited this page Jul 3, 2026
·
1 revision
本清單經兩階段對抗驗證產生:Pass 1 用 pattern matching 掃出候選,Pass 2 由對抗驗證逐條實讀程式碼給出 confidence,confidence < 0.7 者一律降級或剔除(見末尾「已檢視但不列入」小節)。所有 file:line 皆以實讀
/tmp/antidoping-src(origin/main 副本)為準,不採信 README/CLAUDE.md 的敘述(那些本身就有 drift,見docs-drift-dead-refs)。嚴重度:high / medium / low;工作量:S / M / L。可執行的收斂順序見 Plan,方向性演進見 Roadmap,指令以 Maintenance 為準。
- 嚴重度:high 工作量:L (confidence 0.97)
-
影響:
casesFixed.js多條件 regex 查詢、statsFixed.js的$addFields/$switch/$regexMatch聚合、server.jscatch-all 的路徑穿越防護全無回歸保護。任何重構或 schema 變更都靠人工點測,極易在部署後才發現破壞。 -
證據(檔案):root / backend / frontend 三個
package.json皆無 jest/vitest/mocha/playwright/cypress/supertest;root scripts 僅 build/start/dev/install:*(無 test),backend/package.jsonscripts 僅 start/dev,frontend/package.json有lint:"eslint ."但無測試框架。唯一 lint tooling =frontend/eslint.config.js。無.github、.husky、.pre-commit-config.yaml。backend/test_db.js、backend/test_route.js為無斷言的 console.log 手動連線腳本(連mongodb://localhost:27017/antidoping,非 production db 名),非測試套件。未受保護邏輯:casesFixed.js(regex)、statsFixed.js($switch聚合,532 行)、server.js:110-138(catch-all 路徑防護,".."檢查於:118、path.relativeinside-check 於:122-124)。
- 嚴重度:high 工作量:L (confidence 0.82)
-
影響:對一個以「真實禁藥案例教育」為使命的平台,資料庫混入亂數虛構案例會散布錯誤資訊、傷害可信度與教育價值;生成案例的
sourceLinks為空,無法追溯。(Pass 2 註記:無法從 repo 確認 live production DB 是否已清乾淨,但 repo 仍保留生成器、審計文件仍列待驗證項、且無自動守則。) -
證據(檔案):
backend/loadCompleteDatabase.js:124(for 迴圈起點)用 14 處Math.random()(backend 唯一使用亂數的 loader)從姓名/國家/物質池隨機拼案例,合成物件於:149-165、push 於:166;athleteName: \${firstName} ${lastName}`、eventBackground: `${year}年${event}期間藥檢呈陽性反應,違反了反禁藥規則。`。真實硬編案例僅 3 筆(famousCases:46-98:Ben Johnson、Lance Armstrong、Maria Sharapova),totalCases=166(:11)→ 約 163 筆合成。生成器為孤兒:僅自我呼叫於:171/:218,未被server.js/Dockerfile/zeabur.json/package.json引用。models/Case.js:70-77的sourceLinks為選填陣列(無 required),生成器不設此欄。審計:案例真實性確認清單.md(233 行、標生成日 2025/8/23、185 案)含 1 個 ❌(圖例)與 57 個 ❓ 待驗證項,模板產物範例 Karen Perez/Michael Perez(:22-23)。運動禁藥案例資料庫_完整清單.md` 標總案例數 171,與 185 對不上。
- 嚴重度:medium 工作量:M (confidence 0.85)
-
影響:連線邏輯重複、db 名散落硬編難改;每 handler 靠
if(!db) return 500,連線失敗時只在 console 留一行、之後永久回 500 無告警;serving 連線無 graceful shutdown。 -
證據(檔案):
casesFixed.js:12與statsFixed.js:13各自在 module load 呼叫MongoClient.connect()並db = client.db("sports-doping-db")(db 名硬編於casesFixed.js:14、statsFixed.js:15);server.js:160另獨立開一條 Mongoose 連線(query 不走它,僅 lifecycle/建 index,server.js:65載入models/Case)。等於三條連線池、兩份重複連線樣板。Pass 2 修正:專案確有約 20 處client.close()(如add_comprehensive_cases_batch1.js、verify_and_clean_cases.js),但全部位於一次性 seed/import/fix 腳本,runtime 請求路徑(casesFixed.js/statsFixed.js/server.js)從不 close,亦無 SIGTERM/SIGINT/mongoose.disconnect——故「serving 連線無 graceful shutdown」成立。education.js/tue.js不開 DB。
- 嚴重度:low 工作量:S (confidence 0.82)
-
影響:對格式錯誤輸入回 5xx(伺服器錯誤語意)而非 4xx,污染錯誤監控訊號並讓爬蟲/掃描造成噪音。屬邊界輸入驗證小缺口(列表/搜尋端點已有 page/limit clamp 與 search 長度 200 上限,ReDoS 已用
escapeRegex防護)。 -
證據(檔案):
casesFixed.js:205直接findOne({ _id: new ObjectId(req.params.id) })(ObjectIdrequire 於:202),無ObjectId.isValid前置檢查;throw 由:212-220泛用 catch 捕捉回 500。mongodb^6.18.0(package.json:25)對非法字串會 throw。server.js:97掛載/api/cases無上游 param 驗證。
- 嚴重度:medium 工作量:M (confidence 0.9)
- 影響:生產環境無錯誤聚合、無告警:DB 連線掉線或聚合查詢爆錯時無人知曉,只能事後翻 log;缺 request id/結構化欄位難以排查。
-
證據(檔案):三個
package.json皆無 Sentry/pino/winston(grep 全空);server.js12 處、statsFixed.js11 處、casesFixed.js5 處console.*。server.js:174-177全域錯誤處理只console.error(stack)回泛用 500;casesFixed.js:17/statsFixed.js:18的 DB connect 失敗只console.error,之後所有請求靜默回 500(casesFixed.js:22)。強化:/api/health(server.js:68-70)雖存在但回靜態{status:"OK"},不檢查 Mongoose/DB 連線狀態,連 health check 也偵測不到 DB 故障。
- 嚴重度:medium 工作量:M (confidence 0.9)
- 影響:案例資料的真實狀態散落數十支互相覆寫的腳本,無法重現、無法審計「目前 DB 為何長這樣」;跨裝置/重建環境時難以還原一致資料集。
-
證據(檔案):
ls backend/*.js(routes/、models/為子目錄本就排除)實得恰 50 支,扣除test_db.js/test_route.js(臨時測試)與export_cases_to_md.js(匯出),真正 seed/import/fix/remove/verify 一次性腳本約 47 支(如add_final_16_cases.js、remove_suspicious_cases.js、verify_and_clean_cases.js,皆存在)。補強:(a) grepupsert= 0,無冪等寫入;add_final_16_cases.js:10以硬編 inline 陣列insertMany。(b) 4 支破壞性全量重置:seedData.js:208、loadAllCases.js:397、loadCompleteDatabase.js:177、import_original_cases.js:57皆Case.deleteMany({}),三支「load-all」彼此競爭、無 canonical 檔。(c) 腳本 db 名不一致:runtime route 用sports-doping-db,但 9 支腳本連mongodb://localhost:27017/antidoping(如add_final_16_cases.js:5),即部分腳本寫入 app 從不讀取的 DB。(d)import-to-zeabur.js:5硬編 prod Mongo URI 含帳密並手動 seed prod(另屬 secret 外洩 子問題)。(e)DockerfileCOPY backend/會把全部 50 支腳本打包進 production image(死重)但從不執行。
- 嚴重度:medium 工作量:M (confidence 0.9)
- 影響:單檔過大降低可讀性與可測性;TUE 內容改一句要動 route 程式碼並重啟服務,無法像 education 那樣純資料更新,內容與邏輯耦合。
-
證據(檔案):
frontend/src/pages/TUE.jsx1481 行(return(於:71,476 個 className,含 inlineprohibitedDrugs物件於:34)。backend/routes/tue.js503 行,inline 資料為兩個物件:tueContent(:5-273,約 269 行)+wadaSubstances(:274-444,約 171 行,供/check用,tue.js:470),route handlers 僅:445-503。對照education.js:6-8以require('../data/*.json')讀取。補強:api.js定義tueAPI但前端無任何呼叫,TUE.jsx也不打/api/tue,故後端 inline 內容亦屬死碼/重複。
- 嚴重度:low 工作量:S (confidence 0.9)
-
影響:死方法誤導開發者以為有寫入/文章 API。屬殘留耦合(先前 196KB
mockData.js死碼已隨 commit7eab93e移除,此為剩餘小型死碼)。 -
證據(檔案):
frontend/src/services/api.js:19-21(casesAPI.create/update/delete→ POST/PUT/DELETE/cases)與:43-44(educationAPI.getArticles/getArticle→ GET/education/articles)為孤兒:casesFixed.js僅實作 GET(router.get於:20/:165/:196),education.js無/articles(route 僅:21/:29/:37/:46/:66/:74)。這 5 個方法在整個frontend/src從未被呼叫(實測僅getAll@CaseList.jsx:140、getById@CaseDetail.jsx:38、getFilterOptions@CaseList.jsx:79、educationAPI.getAll@Education.jsx:31)。且server.js:110-112catch-all 對/api未匹配回明確 JSON 404,非真正靜默。commit7eab93e亦移除 bcryptjs/jsonwebtoken 與mockData.js,佐證寫入路徑已廢棄。
- 嚴重度:medium 工作量:S (confidence 0.9)
- 影響:生產環境跑在不再收安全性修補的 runtime,且新工具鏈逐步要求 Node 20+,未來 build 可能突然失敗。屬安全與相容性雙重風險。
-
證據(檔案):
package.json:6-8"engines": {"node": "18.x"};Dockerfile三處硬釘node:18-alpine::2(frontend-build)、:19(backend-deps)、:31(production)。無.nvmrc/無 CI/無.npmrcengine-strict/backend 無 engines/zeabur.json不釘 Node → 無緩解。Node 18『Hydrogen』LTS 已於 2025-04-30 結束支援(現為 2026-07)。強化:frontend/package.json:36的vite ^7.1.2,Vite 7 現行 engines 已要求 Node^20.19.0 || >=22.12.0(現在式),該 build 就在Dockerfile:2的node:18-alpine階段執行,因無 engine-strict 才尚未硬 fail。
- 嚴重度:medium 工作量:S (confidence 0.9)
-
影響:文件 drift 誤導維護者去找/編輯不存在的檔案;
backend/package.json是失效入口,vestigial 且與根衝突。 -
證據(檔案):
CLAUDE.md:86仍把backend/routes/cases.js、stats.js、backend/server.js、mockData.js當作「存在的未掛載/死碼」,但實測四者皆 MISSING(commit7eab93e清死碼後刪除)。README.md:88-93列 POST/PUT/DELETE/api/cases、/api/cases/compare、/api/education/articles等不存在端點;README.md:117把server.js畫在backend/內(真正入口是根server.js)。backend/package.json的main與start/dev全指向不存在的backend/server.js,且依賴清單缺 helmet/compression/express-rate-limit/mongodb 與根不一致。Pass 2 修正:README.md:118的seedData.js仍存在(backend/seedData.js),該行本身非 drift;api.js已完全無mockData引用(比「fallback 永不執行」更徹底,連 fallback 碼都移除)。.env.example的JWT_SECRET全專案無 jwt 使用,宜註明為預留。
-
先立護欄(P0,不可退讓):
zero-tests-no-ci(測試 + CI)→node18-eol-runtime(安全升版,改動小)。此二者是後續所有改動的安全網,且風險最低。 -
低風險快贏:
caseid-validation-500、orphaned-api-methods、docs-drift-dead-refs(皆 S,可與測試一起清)。 -
資料存取收斂:
per-route-mongo-connections+no-error-monitoring(同批處理連線與可觀測性)。 -
信譽核心(需雙重確認/migration):
fabricated-case-data→ 配合seed-scripts-sprawl一次收斂資料治理。 -
結構優化:
oversized-files(TUE 內容外移、元件拆分)。
Pass 1 候選宣稱「無機制標示版本、使用者無從得知看到哪一年版本、內容靜默過期,對合規造成誤導」。Pass 2 實讀推翻其核心:
- 版本年份在 UI 顯著標示:
frontend/src/pages/ProhibitedList.jsx:45「2026 禁用清單」、:48「WADA International Standard Prohibited List 2026」,另 Home/News/Resources/Layout/quiz 共 6+ 檔皆標 2026。 - 資料內容以 inline 年份註記維護且已對應 2026 版變更:
prohibitedList.js:131「Tramadol(2024新增)」、:174-175CO M1.4「2026新增」,皆為真實禁用清單變更。 -
tue.js:23引用「2023 ISTUE 第4.2條」屬正確現行標準(ISTUE 2023 為現行版),非過期;tue.js另有「2026新規」註記。
因此「靜默過期、對合規誤導」的影響被明顯誇大。僅殘留一條較弱、不同層次的維護性小債:版本年份是散落 6+ 檔的 hardcoded magic string、無集中常數、資料層無結構化 version 欄位、無 CI/測試自動偵測跨年過期。此殘留不足以支撐原候選所述的債,故降級為觀察項而非正式技術債(可在 Roadmap L1「禁用清單版本化」順帶處理)。
相關頁面:Introduction · Roadmap · Plan · Maintenance · Home
乾淨運動從你我開始 · 教育用途,案例資料經真實性查核 · 回首頁