一句話結論: 「完整架構」不是一件事,是四層;其中三層可以在這台機器上做完並驗證,第四層(原生 App)做不到——需要 Xcode/Android SDK 與開發者帳號。目前已完成 L1 平台底層 與 L2 的 T2–T5(KOL 公開頁、相簿、即時協作、站內通知)。
| 交付項 | 內容 | 怎麼驗的 |
|---|---|---|
| 版本化遷移 | schema_migrations 表+遞增版本;已套用者永不修改 | 最高版本 = 遷移定義數;npm run wipe 清空資料後 schema 仍在 |
| 帳號系統 | 註冊/登入/登出//auth/me;scrypt(N=16384);session token 只存 SHA-256 雜湊 | 弱密碼 400、重複 Email 409、回應不含雜湊、資料庫內為 scrypt$… |
| 登入安全 | 連續失敗 10 次鎖定 10 分鐘 | 門檻後回 429;鎖定期間連正確密碼也被擋 |
| 權限模型 | owner/editor/viewer × private/link/public,API 層強制檢查 | viewer 新增 403、editor 201、非成員讀取/改設定 403 |
| 隱私邊界 | 公開連結頁對非成員隱藏評價;成員名單不含 Email 且僅成員可讀 | 公開頁 notes=[]、非成員讀名單 403 |
| API 版本化 | /api/v1/* 與舊路徑等價,前端全面改走 v1 | 兩個路徑回應一致 |
| 正式模式需登入 | production 預設不開放訪客(MAPSYNC_ALLOW_GUEST=1 可覆寫) | 未登入取 bootstrap 回 401;註冊後取得自己的空白地圖 |
| 交付項 | 內容 | 怎麼驗的 |
|---|---|---|
| T2 KOL 公開頁與一鍵匯入 | 公開連結頁 /api/public/maps/:slug(免登入可讀)、POST /api/maps/import 把別人的圖釘複製進自己的地圖並自動去重 | 3 個地點含 1 個重複 → 匯入 copied=2、skipped=1;再匯一次 0/3;未登入匯入 401 |
| T3 照片上傳與相簿 | POST /api/pins/:id/photos(PNG/JPEG/WebP)、靜態提供 /uploads/*、刪除照片 | 依檔案簽章驗證(偽造 content-type 被拒 400)、7MB 回 413、非成員 403、照片可取回且 MIME 正確 |
| T4 SSE 即時協作 | GET /api/maps/:id/stream 依地圖分組推播,保留最近 50 則供重連補送 | 訂閱者 2.6 秒內收到 pin.created;私人地圖非成員訂閱 403;公開連結地圖允許訂閱(與公開頁一致) |
| T5 站內通知 | 邀請加入、他人新增圖釘/評價都會產生通知;未讀數、單則已讀、全部已讀 | 邀請與新增皆產生通知;已讀後未讀數下降;通知不跨使用者外洩 |
destroy() 連線,客戶端收到 ECONNRESET 而不是 413。已改為排空後正常回應 413。/m/<slug> 會顯示該地圖的地點、標籤與 KOL 推薦語,並提供「一鍵匯入我的地圖」。非成員看不到親友實測。| 順位 | 項目 | 依賴 | 能否在本機完成並驗證 |
|---|---|---|---|
| T6 | 導購 adapter(Klook/KKday/Inline) | 需你確認金流與分潤 | ⚠️ 可先做 adapter 並用替身服務驗證 |
| T7 | 訂閱與付費 | 需金流帳號 | ⚠️ 需外部帳號才能真的收款 |
| T9 | 跨地圖搜尋、多日行程排程、行程匯出格式 | 無 | ✅ 可以 |
| T10 | 原生 App 與原生分享面板 | 需 macOS/Xcode/Android SDK/開發者帳號 | ❌ 本機無法 |
提醒:功能可以一直加,但決定產品成敗的仍然是有沒有人用。真人回饋的優先序高於功能數量。