運動應用程式:資料結構、資料庫結構定義和伺服器設計規格
Artifact這是第 2 集「規劃應用程式的系統規格 (無程式碼)」使用的構件。您可以參考這份規格,建構 Cloud Firestore 集合、伺服器端點和 3 層代理記憶體。
登入即可存取這個構件
登入 Google 開發人員個人檔案,即可查看及複製「Build It!」系列中使用的完整構件。
須具備開發人員設定檔
您不符合建立開發人員設定檔的資格。請通知 Workspace 管理員啟用這項功能。
建立開發人員設定檔
免費建立 Google 開發人員個人檔案,即可解鎖所有 Build It! 提示和構件。
加入 Builder Central 即可解鎖
一鍵領取免費的 Builder 會員徽章,即可解鎖所有 Build It! 提示和構件。
狀態:進行中 (使用 Planning Companion 撰寫草稿)
目標資料庫:Google Cloud Firestore
架構目標:以乾淨、易於維護且可部署於正式環境的方式,重建 AI Studio 智慧健身原型。
0. 核心技術堆疊和系統架構決策
為確保正式環境穩定、效能良好且界線明確,所有實作項目都會鎖定下列架構底限:
- 前端框架:Next.js (App Router、React 19),搭配漸進式網頁應用程式 (PWA) 離線資產快取 (使用 Serwist/
@ducanh2912/next-pwa)。 - 樣式和 UI 元件:Tailwind CSS v4 + Tailwind Variants (用於管理變化版本) + Lucide React (圖示)。
- 用戶端狀態架構:
- Zustand (搭配
persist中介軟體):處理本機、觸覺和暫時用戶端狀態 (碼錶計時器、有效組合緩衝區、步數圖表和未儲存的健身房輸入內容)。 - TanStack Query (React Query):處理所有非同步伺服器狀態、Firestore 文件快取、背景重新驗證、要求簡化和樂觀變動。
- Zustand (搭配
- 後端執行階段和 API 託管:Next.js 路由處理常式 (
app/api/...) 託管於 Google Cloud Run,與 Firestore 和 Gemini 脈絡快取位於相同區域,內部延遲時間低於 15 毫秒。 - 驗證:Firebase 驗證 (Google 登入、電子郵件/密碼,以及匿名新手上路連結)。用戶端會在所有 API 要求中附加
Authorization: Bearer <Firebase_ID_Token>;伺服器會使用firebase-admin.auth().verifyIdToken(token)驗證身分。 - 主要資料庫:Google Cloud Firestore。
1. 系統總覽和核心使用者歷程
1.1 範圍和架構原則
這份規格定義了 Smart Fitness 應用程式的資料模型、Cloud Firestore 結構定義、伺服器端點合約,以及 AI 代理記憶體結構。這個資料庫是所有訓練計畫、歷來課程記錄和自主分級指導記憶的單一事實來源,與前端呈現架構完全分離 (前端呈現架構會在專屬的 UI/UX 設計規格中處理)。
1.2 核心資料原則
- 關注點分離:資料合約 (後端/資料庫) 和用戶端 UI 設計之間有嚴格的界線。
- 混合式程式庫架構:靜態、不可變更的運動百科全書 (
exercises),搭配動態 AI 組合的每週時間表 (weekly_plans)。 - 人工介入迴路分階段變更:AI 生成的訓練變更會以分階段提案的形式傳回,只有在使用者明確接受後,才會寫入資料庫。
- 持續更新的三層式生活記憶:AI 會自主擷取、更新及清除使用者資訊,包括永久資訊、會過期的生理狀態 (有明確的存留時間),以及暫時性的情境資訊。
- 精細的單一文件讀取:資料會結構化為最佳化文件 (例如每週計畫螺旋筆記本和每週聊天值區),盡可能縮短讀取延遲時間,並消除多餘的查詢。
2. 伺服器端點和 API 合約
每個端點都必須指定回應來源 (
AGENT、DATABASE或HYBRID),以便在實作期間適當設定代理支援的端點。
POST /api/coach/chat
- 目的:與 AI 私人教練進行對話互動、處理訓練問題、提供激勵支持,以及生成建議的訓練調整 (包括每日調整和每週全面調整)。
- 觸發條件:使用者在「教練」分頁中提交訊息,或輕觸內嵌情境建議編輯方塊 (例如「將阿諾肩推換成啞鈴肩推」)。
- 回覆來源:
AGENT- 運用 Gemini 的結構化工具/函式呼叫和串流功能。不會直接變更資料庫,而是傳回暫存提案,供人工介入迴路審查。 - 標頭:
Authorization: Bearer <Firebase_ID_Token>、Content-Type: application/json
要求酬載:
{
"message": "string (user input or selected prompt chip text)",
"context": {
"active_tab": "string (enum: today | week | coach)",
"week_start_date": "string (ISO date YYYY-MM-DD)",
"selected_day": "string (e.g. monday, optional)"
}
}回應酬載:
{
"message": "string (conversational coaching response displayed in chat bubble)",
"proposal": {
"id": "string (unique proposal ID, nullable)",
"type": "string (enum: NONE | UPDATE_DAY | UPDATE_WEEK)",
"target_day": "string (e.g. monday, nullable)",
"summary_diff": "string (concise summary of changes, e.g. 'Swapped Arnold Press ➔ DB Shoulder Press', nullable)",
"data": "map (full proposed workout day object or weekly plan object, nullable)"
},
"memory_mutations": [
{
"operation": "string (enum: CREATE | UPDATE | DELETE)",
"target_tier": "string (enum: tier_1_staples | tier_2_physiological | tier_3_situational)",
"memory_id": "string (required for UPDATE and DELETE)",
"entry": "map (memory payload with fact, category, expires_at, etc., required for CREATE and UPDATE)",
"reason": "string (concise rationale for why memory was mutated)"
}
]
}- 雙重寫入
weekly_chats:在每個回合中,伺服器會使用FieldValue.arrayUnion()將傳入的使用者訊息 (text) 和教練的回覆 (text) 永久儲存至/users/{userId}/weekly_chats/{week_start_date}。 - 分階段異動模式:傳回提案酬載,但不會修改
/weekly_plans,直到使用者輕觸「接受方案」為止。 - 持續自主記憶體管理:發出
memory_mutations,在使用者 3 層設定檔中建立、更新或清除項目。
POST /api/workouts/log
- 用途:將完成的訓練課程保存到使用者的永久訓練封存檔、計算最終訓練量/重複次數匯總資料、檢查個人記錄 (PR),並將排定的日期標示為已完成。
- 觸發條件:使用者在健身追蹤畫面上輕觸「完成健身」。
- 回覆來源:
DATABASE- 直接將資料庫交易/寫入 Cloud Firestore。
要求酬載:
{
"plan_reference": {
"week_start_date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. monday)"
},
"title": "string (e.g. Upper Body Hypertrophy)",
"started_at": "string (ISO timestamp)",
"completed_at": "string (ISO timestamp)",
"duration_seconds": "number (e.g. 3120)",
"exercises_logged": [
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"exercise_name": "string (e.g. Incline DB Bench Press)",
"sets": [
{
"set_number": "number (e.g. 1)",
"weight_lbs": "number (e.g. 60)",
"reps_completed": "number (e.g. 12)",
"completed": "boolean"
}
]
}
],
"user_notes": "string (optional subjective feedback)",
"perceived_exertion": "number (optional 1-10 RPE rating)"
}回應酬載:
{
"success": "boolean",
"log_id": "string (newly created workout log document ID)",
"summary": {
"total_volume_lbs": "number",
"total_sets_completed": "number",
"total_reps_completed": "number"
},
"new_prs": [
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"metric": "string (e.g. '10 Rep Max' or 'Estimated 1RM')",
"value": "string (e.g. '65 lbs' or '105 lbs')"
}
]
}3. Cloud Firestore 資料庫架構
集合:exercises
階層:根集合 (/exercises/{exercise_id}) • 用途:靜態運動百科全書,包含多媒體、姿勢提示、器材需求和操作說明。
{
"id": "string (canonical slug matching document ID, e.g. incline-db-bench-press)",
"name": "string (e.g. Incline DB Bench Press)",
"category": "string (e.g. Strength, Hypertrophy, Cardio, Mobility)",
"target_muscles": {
"primary": ["string (e.g. Upper Chest)"],
"secondary": ["string (e.g. Front Deltoids, Triceps)"]
},
"media": {
"video_url": "string (URL to demonstration video)",
"poster_url": "string (URL to video poster / thumbnail image)"
},
"instructions": ["string (step-by-step execution guidance)"],
"form_tips": ["string (critical cues and common pitfalls)"],
"equipment_required": ["string (e.g. Dumbbells, Incline Bench)"]
}集合:weekly_plans
階層:/users/{userId}/weekly_plans/{week_start_date} • 用途:「Spiral Notebook」包含完整的每週訓練時間表、摘要指標和個別日期的訓練。
{
"week_start_date": "string (ISO date YYYY-MM-DD, e.g. 2026-09-07)",
"theme": "string (e.g. Hypertrophy Focus & Upper Body Overload)",
"summary": {
"total_time_minutes": "number (raw minutes, e.g. 180)",
"total_volume_lbs": "number (raw tonnage in lbs, e.g. 38400)",
"frequency": "number (active workout days, e.g. 4)"
},
"suggested_edits": ["string (pre-generated AI prompt suggestion)"],
"days": {
"monday": {
"date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. Monday)",
"is_rest_day": "boolean",
"status": "string (enum: scheduled | completed | skipped)",
"completed_log_id": "string (nullable)",
"completed_at": "string (ISO timestamp, nullable)",
"title": "string (e.g. Upper Body Hypertrophy)",
"focus_tag": "string (e.g. Push & Chest Dominant)",
"duration_minutes": "number",
"estimated_volume_lbs": "number",
"coach_note": "string",
"muscle_focus": [{ "muscle": "string", "intensity": "number (0-100)" }],
"suggested_edits": ["string"],
"blocks": [
{
"label": "string (e.g. Main Compound Block)",
"exercises": [
{
"exercise_id": "string",
"name": "string",
"target_sets": "number",
"target_reps": "string or number",
"rest_seconds": "number"
}
]
}
]
}
}
}集合:weekly_chats
階層:/users/{userId}/weekly_chats/{week_start_date} • 用途:「每週對話區塊」包含特定訓練週的完整對話記錄,可透過單一文件讀取。
{
"week_start_date": "string (ISO date YYYY-MM-DD, e.g. 2026-09-07)",
"messages": [
{
"id": "string (unique message ID)",
"sender": "string (enum: user | coach)",
"text": "string (conversational message body)",
"created_at": "timestamp (server timestamp)",
"proposal": {
"id": "string (proposal UID, nullable)",
"type": "string (enum: UPDATE_DAY | UPDATE_WEEK, nullable)",
"target_day": "string (e.g. monday, nullable)",
"summary_diff": "string (nullable)",
"status": "string (enum: pending | accepted | dismissed)",
"data": "map (workout or week snapshot payload, nullable)"
}
}
]
}集合:workout_logs
階層:/users/{userId}/workout_logs/{log_id} • 用途:永久、不可變更的已完成訓練課程歷來封存記錄。
{
"id": "string (document ID)",
"plan_reference": {
"week_start_date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. monday)"
},
"title": "string (e.g. Upper Body Hypertrophy)",
"started_at": "timestamp",
"completed_at": "timestamp",
"duration_seconds": "number",
"summary": {
"total_volume_lbs": "number",
"total_sets_completed": "number",
"total_reps_completed": "number"
},
"exercises_logged": [
{
"exercise_id": "string",
"exercise_name": "string",
"sets": [
{
"set_number": "number",
"weight_lbs": "number",
"reps_completed": "number",
"completed": "boolean"
}
]
}
],
"user_notes": "string (nullable)",
"perceived_exertion": "number (1-10 RPE, nullable)"
}集合:user_profiles
階層:/users/{userId}/profile/main • 用途:「Living Client Chart」會儲存使用者的 3 層記憶體設定檔。
{
"user_id": "string (document owner UID)",
"display_name": "string (e.g. Alex)",
"email": "string (e.g. alex@example.com)",
"preferences": {
"units": "string (enum: lbs | kg)",
"haptics": "boolean",
"sound": "boolean"
},
"equipment_ids": ["string (e.g. 'dumbbells', 'bench_adjustable', 'pull_up_bar')"],
"onboarding_status": "string (enum: not_started | in_progress | completed)",
"created_at": "timestamp",
"tier_1_staples": [
{
"id": "string (e.g. mem_01)",
"category": "string (enum: equipment | preference | goal | chronic_condition)",
"fact": "string (e.g. Home gym with dumbbells up to 50 lbs and adjustable bench)",
"source": "string (e.g. conversational_onboarding | manual_entry)"
}
],
"tier_2_physiological": [
{
"id": "string (e.g. mem_02)",
"body_part": "string (e.g. hamstrings, shoulder)",
"condition": "string (e.g. soreness, acute_fatigue, minor_strain)",
"fact": "string (e.g. Hamstrings are moderately sore from hill sprints)",
"effective_date": "string (ISO date YYYY-MM-DD)",
"expires_at": "timestamp"
}
],
"tier_3_situational": [
{
"id": "string (e.g. mem_03)",
"event": "string (e.g. golf_competition, travel)",
"fact": "string (e.g. Major golf tournament on Saturday; avoid excessive arm/lat fatigue on Friday)",
"week_start_date": "string (ISO date YYYY-MM-DD)",
"expires_at": "timestamp"
}
]
}集合:prs
階層:/users/{userId}/prs/{exercise_slug} • 用途:儲存歷來個人記錄、預估 1-Rep Max 基準和特定次數記錄。
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"exercise_name": "string (e.g. Incline DB Bench Press)",
"estimated_1rm": "number (calculated using Brzycki formula, e.g. 105)",
"max_weight_set": {
"weight_lbs": "number (e.g. 80)",
"reps": "number (e.g. 10)",
"achieved_at": "timestamp"
},
"rep_records": {
"10": 80,
"8": 85,
"5": 95
},
"last_updated": "timestamp"
}4. 資料生命週期和代理管道
4.1 管道 1:指導、生成健身計畫和分階段修改
- 使用者動作:提交任意形式的輸入內容,或輕觸預先生成的內容相關編輯方塊。
- 情境組合:伺服器擷取有效的 3 層記憶體設定檔和本週計畫,並刪除過期的記憶體項目。
- 代理推論:Gemini 使用結構化工具/函式呼叫處理提示和情境。
- 自主記憶體變動:擷取的使用者事實會套用至
/user_profiles/{userId},並與equipment_ids同步。 - Staged Proposal Staging:建議的訓練修改內容會與
/weekly_chats/{week_start_date}中的proposal.status = "pending"一併傳回。 - 使用者解決和持續性:輕觸「[Accept Plan]」會將建議資料提交至
/weekly_plans,並將建議標示為"accepted"。
4.2 管道 2:執行及封存健身計畫 (基準真相)
- 即時記錄課程:用戶端會追蹤實際舉起的重量、完成的重複次數,以及本機 UI 狀態中的完成標記。
- 提交工作階段:用戶端在「完成健身」時呼叫
POST /api/workouts/log。 - 匯總計算和驗證:伺服器會計算累積訓練量,並使用 Brzycki 公式檢查個人記錄。
- Immutable Persistence:在
/workout_logs中寫入不可變更的記錄、在/weekly_plans中標記"completed"日期,並將新的 PR upsert 至/prs。
4.3 管道 3:自主記憶體擷取與撤銷
- 擷取:系統會持續從所有對話互動中擷取事實。
- 分類:第 1 級 (基本,無效期)、第 2 級 (生理狀態,存留時間 3 到 4 天)、第 3 級 (情境,存留時間依事件而定)。
- 修剪和失效:過期項目 (
current_timestamp > expires_at) 會從提示內容中篩除,並延遲清除。
5. Firestore 安全規則和複合式索引規格
5.1 安全性規則 (firestore.rules)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
match /profile/{document=**} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /weekly_plans/{planId} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /weekly_chats/{chatId} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /prs/{exerciseSlug} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /workout_logs/{logId} {
allow read, create: if request.auth != null && request.auth.uid == userId;
allow update, delete: if false;
}
}
match /exercises/{exerciseId} {
allow read: if request.auth != null;
allow write: if false;
}
}
}5.2 複合查詢索引 (firestore.indexes.json)
{
"indexes": [
{
"collectionGroup": "workout_logs",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "completed_at", "order": "DESCENDING" }]
},
{
"collectionGroup": "weekly_plans",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "week_start_date", "order": "DESCENDING" }]
},
{
"collectionGroup": "prs",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "last_updated", "order": "DESCENDING" }]
}
]
}6. 變更記錄和鎖定的決策
| 日期 | 元件/決策 | 類型 | 鎖定者 |
|---|---|---|---|
| 初始設定 | 已初始化空白規格鷹架 | 系統設定 | 團隊 |
| 2026-09-04 | 新增 Firestore 集合:exercises 和 weekly_plans | 結構定義 | 團隊 |
| 2026-09-04 | 新增 POST /api/coach/chat 端點和 chat_messages 集合 | 端點和結構定義 | 團隊 |
| 2026-09-04 | 將對話儲存空間重構為 weekly_chats bucket 結構定義 | 結構定義 | 團隊 |
| 2026-09-04 | 新增 POST /api/workouts/log 端點和 workout_logs 集合 | 端點和結構定義 | 團隊 |
| 2026-09-04 | 新增 user_profiles 3 層記憶體結構定義,並更新 POST /api/coach/chat,加入 memory_mutations | 端點和結構定義 | 團隊 |
| 2026-09-04 | 完成第 1 節 (系統範圍和原則) 和第 4 節 (資料生命週期和管道) | 完整規格 | 團隊 |
| 2026-09-22 | 統一規格:Next.js + Tailwind + Zustand 堆疊、Bearer 驗證、PR 子集合、Firestore 規則和標準練習網址 | 完整規格統合 | 架構審查 |