Aplikasi Kebugaran Cerdas: Struktur Data, Skema Database & Spesifikasi Desain Server
ArtefakIni adalah artefak yang digunakan dalam Episode 2: Merencanakan Spesifikasi Sistem Aplikasi Anda (Tanpa Kode). Anda dapat menggunakan spesifikasi ini sebagai referensi untuk menyusun koleksi Cloud Firestore, endpoint server, dan memori agen 3 tingkat.
Login untuk mengakses artefak ini
Login dengan Profil Developer Google Anda untuk melihat dan menyalin artefak lengkap yang digunakan dalam seri Build It!.
Profil Developer Diperlukan
Anda tidak memenuhi syarat untuk mendapatkan profil developer. Minta administrator Workspace Anda untuk mengaktifkan fitur ini.
Buat Profil Developer
Buat Profil Developer Google gratis untuk mengakses semua perintah dan artefak Build It!
Bergabunglah dengan Builder Central untuk Membuka Akses
Klaim badge anggota Builder gratis Anda dengan sekali klik untuk membuka semua perintah dan artefak Build It!.
Status: Sedang Berlangsung (Penyusunan Draf dengan Planning Companion)
Database Target: Google Cloud Firestore
Sasaran Arsitektur: Membangun ulang prototipe smart fitness AI Studio yang bersih, mudah dikelola, dan siap produksi.
0. Keputusan Stack Teknis & Arsitektur Sistem Inti
Untuk memastikan stabilitas produksi, performa, dan batasan yang jelas, dasar pengukuran arsitektur berikut dikunci untuk semua implementasi:
- Framework Frontend: Next.js (App Router, React 19) dengan caching aset offline Progressive Web App (PWA) (menggunakan Serwist /
@ducanh2912/next-pwa). - Komponen Gaya & UI: Tailwind CSS v4 + Tailwind Variants (untuk pengelolaan varian) + Lucide React (ikon).
- Arsitektur Status Klien:
- Zustand (dengan middleware
persist): Menangani status klien lokal, taktil, dan sementara (timer stopwatch, buffer set aktif, grafik langkah, dan input gym-floor yang belum disimpan). - TanStack Query (React Query): Menangani semua status server asinkron, caching dokumen Firestore, validasi ulang latar belakang, penghapusan duplikat permintaan, dan mutasi optimis.
- Zustand (dengan middleware
- Runtime Backend & Hosting API: Pengendali Rute Next.js (
app/api/...) yang dihosting di Google Cloud Run di region yang sama dengan Firestore dan Cache Konteks Gemini untuk latensi internal di bawah 15 md. - Autentikasi: Firebase Authentication (Login dengan Google, Email/Sandi, dan penautan orientasi Anonim). Klien melampirkan
Authorization: Bearer <Firebase_ID_Token>pada semua permintaan API; server memverifikasi identitas menggunakanfirebase-admin.auth().verifyIdToken(token). - Database Utama: Google Cloud Firestore.
1. Ringkasan Sistem & Perjalanan Pengguna Inti
1.1 Prinsip Cakupan & Arsitektur
Spesifikasi ini menentukan model data, skema Cloud Firestore, kontrak endpoint server, dan struktur memori agen AI untuk aplikasi Smart Fitness. Arsitektur ini menetapkan satu sumber tepercaya untuk semua perencanaan latihan fisik, pencatatan sesi historis, dan memori pelatihan bertingkat otonom, yang sepenuhnya dipisahkan dari framework presentasi frontend (yang dibahas dalam spesifikasi desain UI/UX khusus).
1.2 Prinsip Data Inti
- Pemisahan Kepentingan: Batas yang ketat antara kontrak data (backend/database) dan desain UI klien.
- Arsitektur Library Hybrid: Ensiklopedia latihan yang statis dan tidak dapat diubah (
exercises) dipadukan dengan komposisi AI dinamis untuk jadwal mingguan (weekly_plans). - Mutasi Bertahap dengan Human-in-the-Loop: Perubahan latihan yang dihasilkan AI ditampilkan sebagai proposal bertahap; penulisan database terjadi secara eksklusif setelah penerimaan eksplisit dari pengguna.
- Memori Hidup 3 Tingkat Berkelanjutan: AI secara mandiri menangkap, memperbarui, dan menghapus fakta pengguna di seluruh elemen permanen, status fisiologis yang akan berakhir (dengan TTL eksplisit), dan konteks situasional sementara.
- Pembacaan Dokumen Tunggal yang Terperinci: Data disusun ke dalam dokumen yang dioptimalkan (seperti buku catatan spiral rencana mingguan dan bucket chat mingguan) untuk meminimalkan latensi baca dan menghilangkan kueri yang redundan.
2. Endpoint Server & Kontrak API
Setiap endpoint harus menentukan Sumber Respons (
AGENT,DATABASE, atauHYBRID) agar endpoint yang didukung agen dapat dikonfigurasi dengan tepat selama implementasi.
POST /api/coach/chat
- Tujuan: Interaksi percakapan dengan Pelatih Pribadi AI, menangani pertanyaan pelatihan, dukungan motivasi, dan membuat usulan modifikasi latihan (baik penyesuaian harian maupun perombakan mingguan).
- Pemicu: Pengguna mengirimkan pesan di tab Pelatih atau mengetuk chip saran edit kontekstual inline (misalnya, "Ganti Arnold Press dengan DB Shoulder Press").
- Response Source:
AGENT— Memanfaatkan Gemini dengan kapabilitas streaming dan pemanggilan fungsi terstruktur. Tidak mengubah database secara langsung; menampilkan proposal bertahap untuk peninjauan human-in-the-loop. - Header:
Authorization: Bearer <Firebase_ID_Token>,Content-Type: application/json
Payload Permintaan:
{
"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)"
}
}Payload Respons:
{
"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)"
}
]
}- Dual-Write ke
weekly_chats: Di setiap giliran, server mempertahankan pesan pengguna yang masuk (text) dan respons pelatih (text) ke dalam/users/{userId}/weekly_chats/{week_start_date}menggunakanFieldValue.arrayUnion(). - Pola Mutasi Bertahap: Menampilkan payload proposal tanpa mengubah
/weekly_planshingga pengguna mengetuk [Accept Plan]. - Continuous Autonomous Memory Management: Memancarkan
memory_mutationsuntuk membuat, memperbarui, atau menghapus item dari profil 3 tingkat pengguna.
POST /api/workouts/log
- Tujuan: Mempertahankan sesi latihan yang telah selesai ke arsip pelatihan permanen pengguna, menghitung agregat volume/repetisi akhir, memeriksa Rekor Pribadi (PR), dan menandai hari yang dijadwalkan sebagai selesai.
- Pemicu: Pengguna mengetuk "Selesaikan Latihan" di layar pelacakan latihan.
- Sumber Respons:
DATABASE— Transaksi/penulisan database langsung ke Cloud Firestore.
Payload Permintaan:
{
"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)"
}Payload Respons:
{
"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. Arsitektur Database Cloud Firestore
Koleksi: exercises
Hierarki: Koleksi Root (/exercises/{exercise_id}) • Tujuan: Ensiklopedia latihan statis yang berisi multimedia, petunjuk gerakan, persyaratan peralatan, dan petunjuk.
{
"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)"]
}Koleksi: weekly_plans
Hierarki: /users/{userId}/weekly_plans/{week_start_date} • Tujuan: "Buku Catatan Spiral" yang berisi jadwal latihan mingguan lengkap, metrik ringkasan, dan latihan harian individu.
{
"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"
}
]
}
]
}
}
}Koleksi: weekly_chats
Hierarki: /users/{userId}/weekly_chats/{week_start_date} • Tujuan: "Weekly Chat Bucket" yang berisi histori percakapan lengkap untuk minggu pelatihan tertentu dalam satu pembacaan dokumen.
{
"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)"
}
}
]
}Koleksi: workout_logs
Hierarki: /users/{userId}/workout_logs/{log_id} • Tujuan: Arsip historis permanen dan tidak dapat diubah dari sesi latihan yang telah selesai.
{
"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)"
}Koleksi: user_profiles
Hierarki: /users/{userId}/profile/main • Tujuan: "Living Client Chart" yang menyimpan profil memori 3 tingkat pengguna.
{
"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"
}
]
}Koleksi: prs
Hierarki: /users/{userId}/prs/{exercise_slug} • Tujuan: Menyimpan Rekor Pribadi sepanjang waktu, perkiraan tolok ukur 1-Rep Max, dan rekor khusus repetisi.
{
"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. Siklus Proses Data & Pipeline Agen
4.1 Pipeline 1: Pelatihan, Pembuatan Latihan & Modifikasi Bertahap
- Tindakan Pengguna: Mengirimkan input teks bebas atau mengetuk chip edit kontekstual yang telah dibuat sebelumnya.
- Context Assembly: Server mengambil profil memori 3 tingkat yang aktif dan rencana minggu ini, serta memangkas item memori yang sudah tidak berlaku.
- Inferensi Agen: Gemini memproses perintah dan konteks menggunakan panggilan alat/fungsi terstruktur.
- Mutasi Memori Otonom: Fakta pengguna yang diekstrak diterapkan ke
/user_profiles/{userId}dan disinkronkan denganequipment_ids. - Staged Proposal Staging: Perubahan latihan yang diusulkan ditampilkan dengan
proposal.status = "pending"di dalam/weekly_chats/{week_start_date}. - Resolusi & Persistensi Pengguna: Mengetuk [Accept Plan] akan meng-commit data yang diusulkan ke
/weekly_plansdan menandai proposal sebagai"accepted".
4.2 Pipeline 2: Eksekusi & Pengarsipan Latihan Fisik (Kebenaran Nyata)
- Perekaman Sesi Live: Klien melacak beban aktual yang diangkat, pengulangan yang diselesaikan, dan tanda penyelesaian dalam status UI lokal.
- Pengiriman Sesi: Klien memanggil
POST /api/workouts/logdi "Selesaikan Latihan". - Penghitungan & Validasi Agregat: Server menghitung volume sesi kumulatif dan memeriksa PR menggunakan formula Brzycki.
- Persistensi yang Tidak Dapat Diubah: Menulis data yang tidak dapat diubah di
/workout_logs, menandai hari"completed"di/weekly_plans, dan meng-upsert PR baru ke/prs.
4.3 Pipeline 3: Penyerapan & Invalidasi Memori Otonom
- Penyerapan: Fakta diekstrak secara terus-menerus di semua interaksi percakapan.
- Klasifikasi: Tingkat 1 (Kebutuhan Pokok, tanpa masa berlaku), Tingkat 2 (Kondisi Fisiologis, TTL 3–4 hari), Tingkat 3 (Konteks Situasional, TTL berbasis peristiwa).
- Pemangkasan & Invalidasi: Item yang sudah tidak berlaku (
current_timestamp > expires_at) akan difilter dari konteks perintah dan dihapus secara lambat.
5. Firestore Security Rules & Spesifikasi Indeks Komposit
5.1 Aturan Keamanan (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 Indeks Kueri Komposit (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. Log Perubahan & Keputusan Terkunci
| Tanggal | Komponen/Keputusan | Jenis | Dikunci Oleh |
|---|---|---|---|
| Penyiapan Awal | Scaffold spesifikasi kosong diinisialisasi | Penyiapan Sistem | Tim |
| 2026-09-04 | Menambahkan koleksi Firestore: exercises dan weekly_plans | Skema | Tim |
| 2026-09-04 | Menambahkan endpoint POST /api/coach/chat dan koleksi chat_messages | Endpoint & Skema | Tim |
| 2026-09-04 | Refactored chat storage to weekly_chats bucket schema | Skema | Tim |
| 2026-09-04 | Menambahkan endpoint POST /api/workouts/log dan koleksi workout_logs | Endpoint & Skema | Tim |
| 2026-09-04 | Menambahkan skema memori 3 tingkat user_profiles & memperbarui POST /api/coach/chat dengan memory_mutations | Endpoint & Skema | Tim |
| 2026-09-04 | Menyelesaikan Bagian 1 (Cakupan & Prinsip Sistem) dan Bagian 4 (Siklus Proses & Pipeline Data) | Penyelesaian Spesifikasi Lengkap | Tim |
| 2026-09-22 | Spesifikasi yang diselaraskan: Stack Next.js + Tailwind + Zustand, autentikasi Bearer, subkoleksi PR, aturan Firestore, dan slug latihan kanonis | Harmonisasi Spesifikasi Lengkap | Peninjauan Arsitektur |