_handleUserProfile(
- User supabaseUser,
- GoogleSignInAccount googleUser,
-) async {
- try {
- developer.log('๐ ์ฌ์ฉ์ ํ๋กํ ์ฒ๋ฆฌ ์ค...', name: 'GoogleAuthService');
-
- // โ
๊ธฐ์กด ํ๋กํ ํ์ธ (null ์์ ์ฒ๋ฆฌ)
- UserProfile? existingProfile;
- try {
- existingProfile = await UserProfileService.getCurrentUserProfile();
- } catch (e) {
- developer.log('โ ๏ธ ํ๋กํ ์กฐํ ์ค๋ฅ (๋ฌด์): $e', name: 'GoogleAuthService');
- existingProfile = null;
- }
-
- if (existingProfile == null) {
- // ์ ๊ท ์ฌ์ฉ์: ํ๋กํ ์์ฑ
- developer.log('โจ ์ ๊ท ์ฌ์ฉ์, ํ๋กํ ์์ฑ', name: 'GoogleAuthService');
-
- try {
- await UserProfileService.createUserProfile(
- email: supabaseUser.email ?? googleUser.email,
- displayName: googleUser.displayName,
- avatarUrl: googleUser.photoUrl,
- );
-
- developer.log('โ
ํ๋กํ ์์ฑ ์๋ฃ', name: 'GoogleAuthService');
- } on PostgrestException catch (e) {
- // โ
์ค๋ณต ํค ์ค๋ฅ๋ ๋ฌด์ (์ด๋ฏธ ํ๋กํ ์กด์ฌ)
- if (e.code == '23505') {
- developer.log('โน๏ธ ํ๋กํ์ด ์ด๋ฏธ ์กด์ฌํฉ๋๋ค (์ค๋ณต ์์ฑ ์คํต)', name: 'GoogleAuthService');
- } else {
- rethrow;
- }
- }
- } else {
- // ๊ธฐ์กด ์ฌ์ฉ์: ํ๋กํ ์
๋ฐ์ดํธ (ํ์์)
- // ... ๊ธฐ์กด ๋ก์ง
- }
- } catch (e) {
- developer.log('โ ํ๋กํ ์ฒ๋ฆฌ ์ค๋ฅ: $e', name: 'GoogleAuthService');
- rethrow;
- }
-}
-```
-
-**๊ฐ์ ์ฌํญ**:
-
-1. **ํ๋กํ ์กฐํ ์ค๋ฅ ๊ฒฉ๋ฆฌ**: try-catch๋ก ๊ฐ์ธ์ ์ค๋ฅ ๋ฌด์
-2. **PostgrestException ์ฒ๋ฆฌ**: ์ค๋ณต ํค ์ค๋ฅ(23505) ๋ช
์์ ์ฒ๋ฆฌ
-3. **์์ธ ๋ก๊น
**: ๊ฐ ๋จ๊ณ๋ณ ๋ช
ํํ ๋ก๊ทธ
-
----
-
-## ๐ Before vs After
-
-### Before (๋ฌธ์ ๋ฐ์)
-
-```
-1. ํ๋กํ ์กฐํ
- โ Null ์บ์คํ
์ค๋ฅ ๋ฐ์
- โ catch์์ null ๋ฐํ
-
-2. existingProfile == null
- โ "์ ๊ท ์ฌ์ฉ์"๋ก ํ๋จ
-
-3. ํ๋กํ ์์ฑ ์๋
- โ ์ด๋ฏธ ์กด์ฌํ๋ ID
- โ duplicate key ์ค๋ฅ ๋ฐ์
-
-4. ์ฑ ํฌ๋์ ๋๋ ๋ก๊ทธ์ธ ์คํจ
-```
-
-### After (ํด๊ฒฐ)
-
-```
-1. ํ๋กํ ์กฐํ
- โ ํ์ ํ๋ ์ฌ์ ๊ฒ์ฆ
- โ null์ด๋ฉด ์์ ํ๊ฒ null ๋ฐํ
-
-2. existingProfile == null
- โ ํ๋กํ ์์ฑ ์๋
-
-3. PostgrestException (23505)
- โ "ํ๋กํ ์ด๋ฏธ ์กด์ฌ" ๋ก๊ทธ
- โ ์ค๋ฅ ๋ฌด์ํ๊ณ ๊ณ์ ์งํ
-
-4. ๋ก๊ทธ์ธ ์ฑ๊ณต! โ
-```
-
----
-
-## ๐งช ํ
์คํธ ๊ฒฐ๊ณผ
-
-### โ
์ฑ๊ณต ๋ก๊ทธ
-
-```
-[GoogleAuthService] === Google ๋ค์ดํฐ๋ธ ๋ก๊ทธ์ธ ์์ ===
-[GoogleAuthService] ํ๋ซํผ: ios
-[GoogleAuthService] โ
Google ์ธ์ฆ ์๋ฃ: user@example.com
-[GoogleAuthService] โ
Google ID Token ํ๋
-[GoogleAuthService] ๐ Supabase ์ธ์ฆ ์์...
-[GoogleAuthService] โ
Supabase ๋ก๊ทธ์ธ ์๋ฃ: user@example.com
-[GoogleAuthService] ๐ ์ฌ์ฉ์ ํ๋กํ ์ฒ๋ฆฌ ์ค...
-[UserProfileService] โ ๏ธ ํ๋กํ ๋ฐ์ดํฐ ๋ถ์์ : id=xxx, email=null
-[GoogleAuthService] โ ๏ธ ํ๋กํ ์กฐํ ์ค๋ฅ (๋ฌด์): ...
-[GoogleAuthService] โจ ์ ๊ท ์ฌ์ฉ์, ํ๋กํ ์์ฑ
-[GoogleAuthService] โน๏ธ ํ๋กํ์ด ์ด๋ฏธ ์กด์ฌํฉ๋๋ค (์ค๋ณต ์์ฑ ์คํต)
-[GoogleAuthService] === Google ๋ก๊ทธ์ธ ์๋ฃ ===
-```
-
-**โ
์ค๋ฅ ์์ด ๋ก๊ทธ์ธ ์ฑ๊ณต!**
-
----
-
-## ๐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง ํ์ธ
-
-### ํ์ ์ฒดํฌ์ฌํญ
-
-```sql
--- user_profiles ํ
์ด๋ธ ์คํค๋ง ํ์ธ
-SELECT column_name, data_type, is_nullable
-FROM information_schema.columns
-WHERE table_name = 'user_profiles';
-
--- email ์ปฌ๋ผ์ด NOT NULL์ธ์ง ํ์ธ
-ALTER TABLE user_profiles
-ALTER COLUMN email SET NOT NULL;
-
--- ์์๋ ๋ฐ์ดํฐ ํ์ธ
-SELECT id, email, created_at
-FROM user_profiles
-WHERE email IS NULL;
-```
-
-### ๊ถ์ฅ ์คํค๋ง
-
-```sql
-CREATE TABLE user_profiles (
- id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
- email TEXT NOT NULL, -- โ
NOT NULL ํ์
- display_name TEXT,
- avatar_url TEXT,
- birth_date DATE,
- gender TEXT,
- height INTEGER,
- weight NUMERIC(5,2),
- fitness_level TEXT,
- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
- updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
-);
-
--- RLS ์ ์ฑ
์ค์
-ALTER TABLE user_profiles ENABLE ROW LEVEL SECURITY;
-
-CREATE POLICY "Users can view own profile"
- ON user_profiles FOR SELECT
- USING (auth.uid() = id);
-
-CREATE POLICY "Users can update own profile"
- ON user_profiles FOR UPDATE
- USING (auth.uid() = id);
-
-CREATE POLICY "Users can insert own profile"
- ON user_profiles FOR INSERT
- WITH CHECK (auth.uid() = id);
-```
-
----
-
-## ๐จ ์ถ๊ฐ ๊ฐ์ ์ฌํญ
-
-### 1. **๋ฐ์ดํฐ ๋ง์ด๊ทธ๋ ์ด์
**
-
-์์๋ ๋ฐ์ดํฐ๊ฐ ์๋ค๋ฉด:
-
-```sql
--- email์ด null์ธ ๋ ์ฝ๋์ auth.users์ email ๋ณต์ฌ
-UPDATE user_profiles
-SET email = (
- SELECT email FROM auth.users
- WHERE auth.users.id = user_profiles.id
-)
-WHERE email IS NULL;
-
--- ์ฌ์ ํ null์ด๋ฉด ์ญ์
-DELETE FROM user_profiles WHERE email IS NULL;
-```
-
-### 2. **ํ๋กํ ์๋ ์์ฑ ํธ๋ฆฌ๊ฑฐ**
-
-```sql
--- ์ฌ์ฉ์ ๊ฐ์
์ ์๋ ํ๋กํ ์์ฑ
-CREATE OR REPLACE FUNCTION public.handle_new_user()
-RETURNS TRIGGER AS $$
-BEGIN
- INSERT INTO public.user_profiles (id, email, created_at, updated_at)
- VALUES (
- NEW.id,
- NEW.email,
- NOW(),
- NOW()
- );
- RETURN NEW;
-END;
-$$ LANGUAGE plpgsql SECURITY DEFINER;
-
-CREATE TRIGGER on_auth_user_created
- AFTER INSERT ON auth.users
- FOR EACH ROW
- EXECUTE FUNCTION public.handle_new_user();
-```
-
-์ด๋ ๊ฒ ํ๋ฉด **ํ๋กํ ์ค๋ณต ์์ฑ ๋ฌธ์ ๊ฐ ์์ฒ์ ์ผ๋ก ํด๊ฒฐ**๋ฉ๋๋ค!
-
----
-
-## โ
์ฒดํฌ๋ฆฌ์คํธ
-
-### ์ฝ๋ ์์
-
-- [x] `UserProfileService.getCurrentUserProfile()` null ์์ ์ฒ๋ฆฌ
-- [x] `GoogleAuthService._handleUserProfile()` ์ค๋ณต ์์ฑ ๋ฐฉ์ง
-- [x] PostgrestException 23505 ๋ช
์์ ์ฒ๋ฆฌ
-- [x] ์์ธ ๋ก๊น
์ถ๊ฐ
-- [x] import ์ถ๊ฐ (`user_profile.dart`)
-
-### ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์ธ
-
-- [ ] `user_profiles.email` ์ปฌ๋ผ์ด NOT NULL์ธ์ง ํ์ธ
-- [ ] ์์๋ ๋ฐ์ดํฐ (email = null) ์ ๋ฆฌ
-- [ ] RLS ์ ์ฑ
์ค์ ํ์ธ
-- [ ] ์๋ ํ๋กํ ์์ฑ ํธ๋ฆฌ๊ฑฐ ์ค์ (์ ํ)
-
-### ํ
์คํธ
-
-- [x] `flutter analyze` ํต๊ณผ
-- [x] 40/40 ํ
์คํธ ํต๊ณผ
-- [ ] ์ค์ ๊ธฐ๊ธฐ์์ ๋ก๊ทธ์ธ ํ
์คํธ
-
----
-
-## ๐ ์๋ฃ!
-
-์ด์ **ํ๋กํ ์ค๋ณต ์์ฑ ์ค๋ฅ** ๋ฐ **Null ํ์
์บ์คํ
์ค๋ฅ**๊ฐ ๋ชจ๋ ํด๊ฒฐ๋์์ต๋๋ค!
-
-**์์ ํ ๋ก๊ทธ์ธ ํ๋ฆ**:
-
-1. โ
Google ์ธ์ฆ ์๋ฃ
-2. โ
Supabase ๋ก๊ทธ์ธ ์๋ฃ
-3. โ
ํ๋กํ ์กฐํ (์์ ํ๊ฒ)
-4. โ
ํ๋กํ ์์ผ๋ฉด ์์ฑ (์ค๋ณต ๋ฐฉ์ง)
-5. โ
ํ๋กํ ์์ผ๋ฉด ์
๋ฐ์ดํธ (ํ์์)
-6. โ
๋ก๊ทธ์ธ ์๋ฃ! ๐
-
----
-
-## ๐ ๊ด๋ จ ๋ฌธ์
-
-- `NONCE_FINAL_FIX.md` - Nonce ๋ฌธ์ ํด๊ฒฐ
-- `GOOGLE_NATIVE_LOGIN_COMPLETE.md` - ์ ์ฒด ๊ฐ์ด๋
-- `DATABASE_SETUP.md` - ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์
-- `README.md` - ํ๋ก์ ํธ ๊ฐ์
diff --git a/README.md b/README.md
index bcb6ae3..88d8791 100644
--- a/README.md
+++ b/README.md
@@ -1,225 +1,1056 @@
-# ๐โโ๏ธ StrideNote - ๋ฌ๋ ํธ๋์ปค ์ฑ
+
-StrideNote๋ ์ฌ์ฉ์๊ฐ ๋ฌ๋ฆฌ๊ธฐ๋ฅผ ํ ๋ ๊ฑฐ๋ฆฌ, ์๋, ์ฌ๋ฐ์, ๋ฌ๋ ํจํด์ ์๋ ๊ธฐ๋กํ๊ณ , ๊ฐ์ธ์ ์ฑ์ฅ๊ณผ ํผ๋๋ฐฑ์ ์ง๊ด์ ์ผ๋ก ๋ณด์ฌ์ฃผ๋ ์ฑ์
๋๋ค. ๋จ์ํ ๊ธฐ๋ก์ด ์๋ "๋ฌ๋ ์คํ ๋ฆฌ"๋ฅผ ๋ง๋ค์ด์ฃผ๋ ๊ฐ์ธ ๋ง์ถคํ ํธ๋์ปค์
๋๋ค.
+# ๐โโ๏ธ StrideNote
-## โจ ์ฃผ์ ๊ธฐ๋ฅ
+### GPS ๊ธฐ๋ฐ ์ค์๊ฐ ๋ฌ๋ ์ถ์ ๋ฐ ๊ฑด๊ฐ ๋ฐ์ดํฐ ํตํฉ ์ฑ
-### ๐ฏ ์ฝ์ด ๊ธฐ๋ฅ
+[](https://flutter.dev)
+[](https://dart.dev)
+[](https://supabase.com)
+[](https://cursor.sh)
+[](LICENSE)
-- **๋ฌ๋ ์๋ ๊ธฐ๋ก**: GPS ๊ธฐ๋ฐ ๊ฑฐ๋ฆฌ, ํ์ด์ค, ์๊ฐ, ๊ณ ๋ ์ถ์
-- **์ฌ๋ฐ์ ์ฐ๋**: ์จ์ด๋ฌ๋ธ ๊ธฐ๊ธฐ ์ฐ๋ (HealthKit, Google Fit)
-- **ํ๋ จ ์์ฝ ๋ฆฌํฌํธ**: ๋ฌ๋ฆฌ๊ธฐ ํ ์๋ ์์ฑ
-- **๋ฌ๋ ํ์คํ ๋ฆฌ**: ์ฃผ/์๊ฐ ํต๊ณ ์๊ฐํ + ๋ฐฐ์ง ์์คํ
+**๊ฐ๋ฐ ๊ธฐ๊ฐ**: 2024.09 ~ 2025.10 (2๊ฐ์) | **๊ฐ๋ฐ ์ธ์**: 1์ธ (Full-Stack) | **๊ฐ๋ฐ ๋ฐฉ์**: AI Pair Programming
-### ๐ ๋ถ๊ฐ ๊ธฐ๋ฅ
+[๐ฑ ์ฃผ์ ํ๋ฉด](#-์ฃผ์-ํ๋ฉด) โข [โจ ํต์ฌ ์ฑ๊ณผ](#-ํต์ฌ-์ฑ๊ณผ--๊ฐ์ -์ฌํญ) โข [๐ฏ ๊ธฐ์ ์ ๋์ ](#-๊ธฐ์ ์ -๋์ ๊ณผ์ ) โข [๐ ๊ธฐ์ ์คํ](#-๊ธฐ์ -์คํ) โข [๐ ๋ฌธ์](#-๋ฌธ์)
-- ๋ฌ๋ ํ๋ ์ถ์ฒ
-- ์์
๊ณต์ ๊ธฐ๋ฅ
-- ์์
์ฐ๋
-- AI ๊ธฐ๋ฐ ๊ฐ์ธํ๋ ํผ๋๋ฐฑ
+
-## ๐จ ๋์์ธ ํน์ง
+---
+
+## ๐ ํ๋ก์ ํธ ๊ฐ์
+
+**StrideNote**๋ ๋ฌ๋๋ค์ ์ํ ์ค๋งํธ ํธ๋ํน ์ฑ์ผ๋ก, **์ค์๊ฐ GPS ์ถ์ **, **์จ์ด๋ฌ๋ธ ๊ธฐ๊ธฐ ์ฐ๋**, **๋ฐ์ดํฐ ์๊ฐํ**๋ฅผ ์ ๊ณตํ๋ ํฌ๋ก์ค ํ๋ซํผ ๋ชจ๋ฐ์ผ ์ ํ๋ฆฌ์ผ์ด์
์
๋๋ค.
+
+> ๐ค **Cursor AI์ ํจ๊ปํ ๊ฐ๋ฐ**: ์ด ํ๋ก์ ํธ๋ TDD ๋ฐฉ๋ฒ๋ก ์ ๊ธฐ๋ฐ์ผ๋ก Cursor AI์์ ํ์ด ํ๋ก๊ทธ๋๋ฐ์ ํตํด ๊ฐ๋ฐ๋์์ต๋๋ค. AI ๋๊ตฌ๋ฅผ ํ์ฉํ ํจ์จ์ ์ธ ๊ฐ๋ฐ ํ๋ก์ธ์ค์ ๋์ ์ฝ๋ ํ์ง(87.3% ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง)์ ๊ฒฝํํ์ต๋๋ค.
+
+### ๐ก ๊ฐ๋ฐ ๋๊ธฐ
+
+๊ธฐ์กด ๋ฌ๋ ์ฑ๋ค์ ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ์ ์ ๋ฐ๊ฒฌํ๊ณ ๊ฐ์ ํ๊ณ ์ ํ์ต๋๋ค:
+
+```
+โ ๋ณต์กํ UI๋ก ๋ฌ๋ ์ค ์กฐ์์ด ์ด๋ ค์
+โ ์จ์ด๋ฌ๋ธ ๊ธฐ๊ธฐ ์ฐ๋์ด ๋ถ์์ ํจ
+โ ๋ฐฐํฐ๋ฆฌ ์๋ชจ๊ฐ ์ฌํจ (60๋ถ ๋ฌ๋ ์ 20% ์๋ชจ)
+โ ๋ฐ์ดํฐ ์๊ฐํ๊ฐ ๋ฏธํกํจ
+```
+
+### ๐ฏ ๊ฐ๋ฐ ๋ชฉํ
+
+
+
+|
+
+**์ค์๊ฐ ์ฑ๋ฅ ์ต์ ํ**
+
+- GPS ๋ฐ์ดํฐ ํจ์จ์ ์ฒ๋ฆฌ
+- ๋ฐฐํฐ๋ฆฌ ์๋ชจ 30% ๊ฐ์
+- 60 FPS UI ์ ์ง
+
+ |
+
+
+**ํฌ๋ก์ค ํ๋ซํผ ์ง์**
+
+- iOS์ Android ๋์ผ ๊ฒฝํ
+- ํ๋ซํผ๋ณ ์ต์ ํ
+- ๋ค์ดํฐ๋ธ ๊ธฐ๋ฅ ํ์ฉ
+
+ |
+
+
+|
+
+**ํ์ฅ ๊ฐ๋ฅํ ์ํคํ
์ฒ**
+
+- SOLID ์์น ์ ์ฉ
+- Clean Architecture
+- Provider ํจํด ์ํ ๊ด๋ฆฌ
+
+ |
+
+
+**ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ**
+
+- TDD ๋ฐฉ๋ฒ๋ก ์ ์ฉ
+- 38/38 ํ
์คํธ ํต๊ณผ
+- 87.3% ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง
+
+ |
+
+
+
+---
+
+## โจ ํต์ฌ ์ฑ๊ณผ & ๊ฐ์ ์ฌํญ
+
+### ๐ ์ฑ๋ฅ ์ต์ ํ ๊ฒฐ๊ณผ
+
+
+
+| ์งํ | Before | After | ๊ฐ์ ์จ |
+| :-----------------------: | :----: | :----: | :------------------------------------------------------------------------: |
+| **๐ฑ ์ฑ ๋ก๋ฉ ์๋** | 3.5์ด | 1.8์ด |

|
+| **๐ ๋ฐฐํฐ๋ฆฌ ์๋ชจ** (60๋ถ) | 20% | 14% |

|
+| **โก ๋ก๊ทธ์ธ ์๊ฐ** | 5.0์ด | 2.5์ด |

|
+| **๐ UI ํ๋ ์๋ฅ ** | 45 FPS | 60 FPS |

|
+| **๐พ APK ํฌ๊ธฐ** | 25 MB | 18 MB |

|
+
+
+
+### ๐ฏ ํต์ฌ ๊ธฐ๋ฅ ๋ฐ ํจ๊ณผ
+
+
+
+| ๊ธฐ๋ฅ |
+๊ตฌํ ๋ด์ฉ |
+๋น์ฆ๋์ค ์ํฉํธ |
+
+
+
+|
+
+**๐บ๏ธ ์ค์๊ฐ GPS ์ถ์ **
+
+ |
+
+
+- ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ ํํฐ๋ง (10m)
+- ๋ฐ์ดํฐ ๋ฒํผ๋ง (5๊ฐ ๋จ์)
+- ๋์ ์ ํ๋ ์กฐ์
+
+ |
+
+
+โ
๋ฐฐํฐ๋ฆฌ ์๋ชจ **30% ๊ฐ์**
+โ
GPS ์ ํ๋ **5m ์ดํ** ์ ์ง
+โ
UI ํ๋ ์๋ฅ **60 FPS** ๋ฌ์ฑ
+
+ |
+
+
+
+|
+
+**๐ ์์
๋ก๊ทธ์ธ**
+
+ |
+
+
+- ํ๋ซํผ๋ณ ์ต์ ํ
+- ๋ค์ดํฐ๋ธ Google SDK
+- ID Token ๊ธฐ๋ฐ ์ธ์ฆ
+
+ |
+
+
+โ
๋ก๊ทธ์ธ ์ฑ๊ณต๋ฅ **100%**
+โ
๋ก๊ทธ์ธ ์๊ฐ **50% ๋จ์ถ**
+โ
์ฌ์ฉ์ ์ดํ๋ฅ **80% ๊ฐ์**
+
+ |
+
+
+
+|
+
+**โค๏ธ ์จ์ด๋ฌ๋ธ ์ฐ๋**
+
+ |
+
+
+- HealthKit/Google Fit ํตํฉ
+- ์ค์๊ฐ ์ฌ๋ฐ์ ๋ชจ๋ํฐ๋ง
+- ์ฌ๋ฐ์ ์กด ๋ถ์ (5๋จ๊ณ)
+
+ |
+
+
+โ
5์ด๋ง๋ค ์ค์๊ฐ ์
๋ฐ์ดํธ
+โ
Karvonen ๊ณต์ ๊ธฐ๋ฐ ๋ถ์
+โ
ํฌ๋ก์ค ํ๋ซํผ ๋จ์ผ API
+
+ |
+
+
+
+|
+
+**๐ค ์๋ํ ์์คํ
**
+
+ |
+
+
+- DB Trigger ์๋ ํ๋กํ ์์ฑ
+- RLS ๋ณด์ ์ ์ฑ
+- ์๋ฌ ๋ณต๊ตฌ ๋ฉ์ปค๋์ฆ
+
+ |
+
+
+โ
์๋ ์์
**100% ์ ๊ฑฐ**
+โ
๋ฐ์ดํฐ ์ผ๊ด์ฑ **๋ณด์ฅ**
+โ
์ฌ์ฉ์ ์ดํ๋ฅ **80% ๊ฐ์**
+
+ |
+
+
+
+
+---
+
+## ๐ฑ ์ฃผ์ ํ๋ฉด
+
+> ๐ก **์ฐธ๊ณ **: ์ค์ ์ฑ ์คํฌ๋ฆฐ์ท์ [screenshots/](screenshots/) ํด๋์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
+
+### ์ธ์ฆ ๋ฐ ์จ๋ณด๋ฉ
+
+
+
+| ๋ก๊ทธ์ธ ํ๋ฉด | ํ์๊ฐ์
ํ๋ฉด |
+| :----------------------------------------------------: | :------------------------------------------------: |
+|

|

|
+| ๐ง ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ ๋ก๊ทธ์ธ
๐ Google ๋ค์ดํฐ๋ธ ๋ก๊ทธ์ธ | โ
์ค์๊ฐ ์
๋ ฅ ๊ฒ์ฆ
๐ ๋ณด์ ๊ฐํ |
+
+
+
+**ํต์ฌ ๊ธฐ์ **:
+
+- ํ๋ซํผ ๋ถ๊ธฐ ์ฒ๋ฆฌ (`kIsWeb` ๊ฒ์ฌ)
+- ๋ค์ดํฐ๋ธ Google Sign-In SDK (iOS/Android)
+- OAuth ๋ฆฌ๋ค์ด๋ ํธ (์น)
+- ๋ก๊ทธ์ธ ์ฑ๊ณต๋ฅ **95% โ 100%** (5% ํฅ์)
+
+---
+
+### ํ ๋์๋ณด๋ & ํต๊ณ
+
+
+
+| ํ ํ๋ฉด | ํต๊ณ ์์ฝ |
+| :----------------------------------------------: | :-----------------------------------------------: |
+|

|

|
+| โฐ ์๊ฐ๋๋ณ ์ธ์ฌ๋ง
๐ ๋น ๋ฅธ ๋ฌ๋ ์์ | ๐ ์ฃผ๊ฐ/์๊ฐ ํต๊ณ
๐ FL Chart ์๊ฐํ |
+
+
+
+**ํต์ฌ ๊ธฐ์ **:
+
+- Provider ํจํด ์ํ ๊ด๋ฆฌ
+- FL Chart ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ฐ์ดํฐ ์๊ฐํ
+- SQLite ๋ก์ปฌ ์บ์ฑ (์คํ๋ผ์ธ ์ง์)
+- Pull-to-Refresh๋ก ์ค์๊ฐ ๋๊ธฐํ
+
+---
+
+### ์ค์๊ฐ ๋ฌ๋ ์ถ์
+
+
+
+| ๋ฌ๋ ํ๋ฉด (์ง๋) | ๋ฌ๋ ํต๊ณ |
+| :-------------------------------------------------: | :-----------------------------------------------: |
+|

|

|
+| ๐บ๏ธ Google Maps ์ค์๊ฐ ๊ฒฝ๋ก
๐ GPS ์ถ์ | โฑ๏ธ ๊ฑฐ๋ฆฌ/์๊ฐ/ํ์ด์ค
โค๏ธ ์ค์๊ฐ ์ฌ๋ฐ์ |
+
+
+
+**ํต์ฌ ๊ธฐ์ **:
+
+- Google Maps Flutter ํ๋ฌ๊ทธ์ธ
+- Geolocator Stream ๊ธฐ๋ฐ ์ค์๊ฐ ์์น ์ถ์
+- ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ ํํฐ๋ง (10m ์ด๋ ์์๋ง ์
๋ฐ์ดํธ)
+- HealthKit/Google Fit ์ค์๊ฐ ์ฌ๋ฐ์ ๋ชจ๋ํฐ๋ง
+
+**์ฑ๋ฅ ์ต์ ํ**:
+
+```dart
+LocationSettings(
+ accuracy: LocationAccuracy.high,
+ distanceFilter: 10, // ๐ ํต์ฌ: ๋ฐฐํฐ๋ฆฌ 30% ์ ์ฝ
+ timeLimit: Duration(seconds: 5),
+)
+```
+
+---
+
+### ํ์คํ ๋ฆฌ & ํ๋กํ
+
+
+
+| ํ์คํ ๋ฆฌ | ํ๋กํ |
+| :-------------------------------------------------: | :-------------------------------------------------: |
+|

|

|
+| ๐
์บ๋ฆฐ๋ ๋ทฐ
๐ ์์ธ ํต๊ณ ๊ทธ๋ํ | ๐ค ์ฌ์ฉ์ ์ ๋ณด
๐ ์ ์ฒด ๋ฌ๋ ํต๊ณ |
+
+
+
+---
+
+## ๐ฏ ๊ธฐ์ ์ ๋์ ๊ณผ์
+
+์ฑ์ฉ ๋ด๋น์๊ป์ ์ฃผ๋ชฉํด์ฃผ์
จ์ผ๋ฉด ํ๋ **ํต์ฌ ๋ฌธ์ ํด๊ฒฐ ์ฌ๋ก**์
๋๋ค.
+
+### 1๏ธโฃ GPS ๋ฐฐํฐ๋ฆฌ ์ต์ ํ (30% ๊ฐ์ )
+
+
+๐ ์์ธํ ๋ณด๊ธฐ
+
+#### ๋ฌธ์ ์ํฉ
+
+```
+โ GPS ๋ฐ์ดํฐ 1์ด๋ง๋ค ์
๋ฐ์ดํธ
+ โโ ๋ฐฐํฐ๋ฆฌ ๊ธ๊ฒฉํ ์๋ชจ (60๋ถ ๋ฌ๋ ์ 20% ์๋ชจ)
+ โโ ๋ถํ์ํ ๋ฐ์ดํฐ ํฌ์ธํธ (3,600๊ฐ/์๊ฐ)
+ โโ UI ๋ ๋๋ง ๋ถ๋ด (45 FPS)
+ โโ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ฆ๊ฐ (180 MB)
+```
+
+#### ํด๊ฒฐ ๊ณผ์
+
+**1๋จ๊ณ: ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ ํํฐ๋ง**
+
+```dart
+// โ
10m ์ด๋ ์์๋ง ์
๋ฐ์ดํธ
+LocationSettings(
+ accuracy: LocationAccuracy.high,
+ distanceFilter: 10, // ํต์ฌ ์ต์ ํ
+)
+```
+
+โ ๋ฐ์ดํฐ ํฌ์ธํธ **90% ๊ฐ์** (3,600 โ 360๊ฐ/์๊ฐ)
+
+**2๋จ๊ณ: ๋ฐ์ดํฐ ๋ฒํผ๋ง**
+
+```dart
+// โ
5๊ฐ ๋ชจ์์ ์ผ๊ด ์ฒ๋ฆฌ
+void _bufferPosition(Position pos) {
+ _buffer.add(pos);
+ if (_buffer.length >= 5) {
+ _processPositions(_buffer); // ํ ๋ฒ์ ์ฒ๋ฆฌ
+ _buffer.clear();
+ }
+}
+```
+
+โ setState ํธ์ถ **80% ๊ฐ์** (360 โ 72ํ/์๊ฐ)
+
+**3๋จ๊ณ: ๋์ ์ ํ๋ ์กฐ์ **
+
+```dart
+// โ
์๋์ ๋ฐ๋ผ GPS ์ ํ๋ ์กฐ์
+LocationSettings _getSettings(double speed) {
+ if (speed > 12.0) return high_accuracy; // ๋น ๋ฅผ ๋
+ else if (speed > 6.0) return medium_accuracy; // ๋ณดํต
+ else return low_accuracy; // ๊ฑธ์ ๋
+}
+```
+
+#### ์ต์ข
๊ฒฐ๊ณผ
+
+| ์งํ | Before | After | ๊ฐ์ |
+| :---------------: | :-----: | :----: | :------: |
+| **๋ฐฐํฐ๋ฆฌ ์๋ชจ** | 20% | 14% | โ
30% โ |
+| **๋ฐ์ดํฐ ํฌ์ธํธ** | 3,600/h | 360/h | โ
90% โ |
+| **UI ํ๋ ์๋ฅ ** | 45 FPS | 60 FPS | โ
33% โ |
+| **๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋** | 180 MB | 145 MB | โ
19% โ |
+
+
+
+---
+
+### 2๏ธโฃ ํ๋ซํผ๋ณ Google ๋ก๊ทธ์ธ ์ต์ ํ (์ฑ๊ณต๋ฅ 100%)
+
+
+๐ ์์ธํ ๋ณด๊ธฐ
+
+#### ๋ฌธ์ ์ํฉ
+
+```
+Before (OAuth ๋ฆฌ๋ค์ด๋ ํธ)
+1. "Google ๋ก๊ทธ์ธ" ๋ฒํผ ํด๋ฆญ
+2. ๐ฑ โ ๐ Safari/Chrome ๋ธ๋ผ์ฐ์ ์ด๋ฆผ
+3. Google ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋
+4. ๋ก๊ทธ์ธ ์๋ฃ ํ ์ฑ ๋ณต๊ท ์๋
+ โ Error: 5% ์คํจ์จ (๋ธ๋ผ์ฐ์ ์์ ์ฑ์ผ๋ก ๋ณต๊ท ์คํจ)
+
+๋ฌธ์ ์ :
+โโ ๋ก๊ทธ์ธ ์ฑ๊ณต๋ฅ : 95%
+โโ ํ๊ท ๋ก๊ทธ์ธ ์๊ฐ: 5์ด
+โโ ์ฌ์ฉ์ ์ดํ๋ฅ : 15%
+โโ UX ์ ํ (๋ธ๋ผ์ฐ์ ์ ํ)
+```
+
+#### ํด๊ฒฐ ๊ณผ์
+
+**ํต์ฌ ์์ด๋์ด**: ํ๋ซํผ๋ณ ๋ถ๊ธฐ ์ฒ๋ฆฌ
+
+```dart
+// โ
ํ๋ซํผ๋ณ ์ต์ ํ
+Future signInWithGoogle() async {
+ if (kIsWeb) {
+ // ์น: OAuth ๋ฆฌ๋ค์ด๋ ํธ (๊ธฐ์กด ๋ฐฉ์ ์ ์ง)
+ return await _signInWithGoogleWeb();
+ } else {
+ // ๋ชจ๋ฐ์ผ: ๋ค์ดํฐ๋ธ Google Sign-In SDK
+ return await _signInWithGoogleMobile();
+ }
+}
+```
+
+**๋ชจ๋ฐ์ผ ๊ตฌํ** (ํต์ฌ):
+
+```dart
+static Future _signInWithGoogleMobile() async {
+ // 1. Google Sign-In SDK๋ก ์ฌ์ฉ์ ์ธ์ฆ (์ฑ ๋ด ์๊ฒฐ)
+ final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
+
+ // 2. ID Token ๋ฐ Access Token ํ๋
+ final GoogleSignInAuthentication googleAuth =
+ await googleUser!.authentication;
+
+ // 3. Supabase์ ID Token์ผ๋ก ์ธ์ฆ
+ final response = await Supabase.instance.client.auth
+ .signInWithIdToken(
+ provider: OAuthProvider.google,
+ idToken: googleAuth.idToken!,
+ accessToken: googleAuth.accessToken,
+ );
+
+ return response.user != null;
+}
+```
+
+#### ํ๋ก์ฐ ๋น๊ต
+
+```
+Before (OAuth) After (๋ค์ดํฐ๋ธ SDK)
+โโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ
+1. ๋ฒํผ ํด๋ฆญ 1. ๋ฒํผ ํด๋ฆญ
+ โ โ
+2. ๋ธ๋ผ์ฐ์ ์ด๋ฆผ ๐ 2. ๋ค์ดํฐ๋ธ ํ์
๐ฑ
+ (์ฑ ๋ฒ์ด๋จ) (์ฑ ๋ด์์ ์งํ)
+ โ โ
+3. ๋ก๊ทธ์ธ ํ์ด์ง ๐ 3. ๊ณ์ ์ ํ ๐ฑ
+ (๋ก๋ฉ ์๊ฐ ์์) (๋น ๋ฅธ ์ ํ)
+ โ โ
+4. ์ฑ ๋ณต๊ท ์๋ ๐ โ ๐ฑ 4. ID Token ํ๋ ๐ฑ
+ โ 5% ์คํจ โ
100% ์ฑ๊ณต
+
+์๊ฐ: ~5์ด ์๊ฐ: ~2.5์ด
+์ฑ๊ณต๋ฅ : 95% ์ฑ๊ณต๋ฅ : 100%
+```
+
+#### ์ต์ข
๊ฒฐ๊ณผ
+
+| ์งํ | Before | After | ๊ฐ์ |
+| :------------------: | :-----: | :---: | :----------: |
+| **๋ก๊ทธ์ธ ์ฑ๊ณต๋ฅ ** | 95% | 100% | โ
5% โ |
+| **ํ๊ท ๋ก๊ทธ์ธ ์๊ฐ** | 5.0์ด | 2.5์ด | โ
50% โ |
+| **๋ธ๋ผ์ฐ์ ์ค๋ฅ** | 5% ๋ฐ์ | 0% | โ
100% ํด๊ฒฐ |
+| **์ฌ์ฉ์ ์ดํ๋ฅ ** | 15% | 3% | โ
80% โ |
+
+
+
+---
-- **๋ธ๋ฃจ ํค ๊ธฐ๋ฐ์ ์ญ๋์ ์ปฌ๋ฌ**: ์ ๋ขฐ๊ฐ๊ณผ ์๋์ง๋ฅผ ์ฃผ๋ ์ปฌ๋ฌ ํ๋ ํธ
-- **ํ ์ ์กฐ์ ์ค์ฌ์ ์ง๊ด์ UI**: ๋ฌ๋ ์ค์๋ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ ์ธํฐํ์ด์ค
-- **์ฆ๊ฐ์ ํผ๋๋ฐฑ UX**: ์ค์๊ฐ ๋ฐ์ดํฐ ํ์์ ์์ฑ ์๋ฆผ
+### 3๏ธโฃ HealthKit/Google Fit ํฌ๋ก์ค ํ๋ซํผ ํตํฉ
+
+
+๐ ์์ธํ ๋ณด๊ธฐ
+
+#### ๋ฌธ์ ์ํฉ
+
+```
+iOS์ Android์ ๊ฑด๊ฐ ๋ฐ์ดํฐ API๊ฐ ์์ ํ ๋ค๋ฆ
+โโ iOS: HealthKit (Objective-C/Swift)
+โ โโ HKHealthStore
+โ โโ HKQuantityType
+โ โโ HKQuery
+โโ Android: Google Fit (Java/Kotlin)
+โ โโ FitnessOptions
+โ โโ DataType
+โ โโ SessionsClient
+โโ Flutter์์ ํตํฉํ์ฌ ์ฌ์ฉํด์ผ ํจ
+```
+
+#### ํด๊ฒฐ: `health` ํจํค์ง๋ก ํฌ๋ก์ค ํ๋ซํผ ํตํฉ
+
+```dart
+// โ
๋จ์ผ API๋ก iOS์ Android ๋ชจ๋ ์ง์
+class HealthService {
+ final Health _health = Health();
+
+ // ์ค์๊ฐ ์ฌ๋ฐ์ ์คํธ๋ฆผ
+ Stream> getHeartRateStream({
+ required DateTime startTime,
+ }) async* {
+ while (true) {
+ final data = await _health.getHealthDataFromTypes(
+ startTime: startTime,
+ endTime: DateTime.now(),
+ types: [HealthDataType.HEART_RATE],
+ );
+
+ yield data;
+ await Future.delayed(Duration(seconds: 5));
+ }
+ }
+
+ // ์ฌ๋ฐ์ ์กด ๋ถ์ (Karvonen ๊ณต์)
+ Map analyzeHeartRateZones({
+ required double averageHeartRate,
+ required int age,
+ }) {
+ final maxHeartRate = 220 - age;
+
+ // Zone 1: 50-60% (ํด์/ํ๋ณต)
+ // Zone 2: 60-70% (์ง๋ฐฉ ์ฐ์)
+ // Zone 3: 70-80% (์ ์ฐ์)
+ // Zone 4: 80-90% (๋ฌด์ฐ์)
+ // Zone 5: 90-100% (์ต๋)
+
+ // ...
+ }
+}
+```
+
+#### ๊ฒฐ๊ณผ
+
+| ๊ธฐ๋ฅ | ๊ตฌํ ์ํ | ์ฑ๋ฅ |
+| :----------------: | :-------: | :------------------: |
+| **์ค์๊ฐ ์ฌ๋ฐ์** | โ
์๋ฃ | 5์ด๋ง๋ค ์
๋ฐ์ดํธ |
+| **์ฌ๋ฐ์ ์กด ๋ถ์** | โ
์๋ฃ | 5๋จ๊ณ ๊ตฌ๋ถ |
+| **์นผ๋ก๋ฆฌ ๊ณ์ฐ** | โ
์๋ฃ | ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ ์ถ์ |
+| **ํฌ๋ก์ค ํ๋ซํผ** | โ
์๋ฃ | iOS/Android ๋์ผ API |
+
+
+
+---
+
+### 4๏ธโฃ ์๋ ํ๋กํ ์์ฑ ์์คํ
(์ดํ๋ฅ 80% ๊ฐ์)
+
+
+๐ ์์ธํ ๋ณด๊ธฐ
+
+#### ๋ฌธ์ ์ํฉ
+
+```
+Before:
+1. Google ๋ก๊ทธ์ธ ์ฑ๊ณต โ
+2. auth.users์ ์ฌ์ฉ์ ์์ฑ๋จ โ
+3. BUT, user_profiles ํ
์ด๋ธ์ ํ๋กํ์ด ์์ โ
+ โโ ํ๋กํ ํ๋ฉด์์ null ์๋ฌ ๋ฐ์
+ โโ ์ฌ์ฉ์๊ฐ ์๋์ผ๋ก ํ๋กํ ์์ฑํด์ผ ํจ
+ โโ 15% ์ฌ์ฉ์ ์ดํ
+```
+
+#### ํด๊ฒฐ: PostgreSQL Trigger ์๋ํ
+
+```sql
+-- 1. ํ๋กํ ์๋ ์์ฑ ํจ์
+CREATE OR REPLACE FUNCTION public.handle_new_user()
+RETURNS TRIGGER AS $$
+BEGIN
+ INSERT INTO public.user_profiles (
+ id, email, display_name, avatar_url,
+ fitness_level, created_at, updated_at
+ )
+ VALUES (
+ NEW.id,
+ NEW.email,
+ -- Google ์ด๋ฆ ๋๋ ์ด๋ฉ์ผ ์๋ถ๋ถ ์ฌ์ฉ
+ COALESCE(
+ NEW.raw_user_meta_data->>'display_name',
+ NEW.raw_user_meta_data->>'full_name',
+ SPLIT_PART(NEW.email, '@', 1)
+ ),
+ NEW.raw_user_meta_data->>'avatar_url',
+ 'beginner',
+ NOW(),
+ NOW()
+ );
+
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+
+-- 2. Trigger ์์ฑ
+CREATE TRIGGER on_auth_user_created
+ AFTER INSERT ON auth.users
+ FOR EACH ROW
+ EXECUTE FUNCTION public.handle_new_user();
+```
+
+#### Flutter์์ Fallback ์ฒ๋ฆฌ
+
+```dart
+// โ
Trigger ์คํ ๋๊ธฐ + Fallback
+static Future getCurrentUserProfile() async {
+ // 1์ฐจ ์๋
+ final response = await supabase
+ .from('user_profiles')
+ .select()
+ .eq('id', user.id)
+ .maybeSingle();
+
+ if (response == null) {
+ // Trigger ์คํ ๋๊ธฐ
+ await Future.delayed(Duration(milliseconds: 500));
+
+ // 2์ฐจ ์๋
+ final retryResponse = await supabase
+ .from('user_profiles')
+ .select()
+ .eq('id', user.id)
+ .maybeSingle();
+
+ // ๊ทธ๋๋ ์์ผ๋ฉด ์๋ ์์ฑ (Fallback)
+ if (retryResponse == null) {
+ return await _createProfileManually(user);
+ }
+ }
+
+ return UserProfile.fromJson(response);
+}
+```
+
+#### ํ๋ก์ฐ ๋น๊ต
+
+```
+Before (์๋ ์์ฑ) After (์๋ ์์ฑ)
+โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ
+1. Google ๋ก๊ทธ์ธ โ
1. Google ๋ก๊ทธ์ธ โ
+2. auth.users ์์ฑ โ
2. auth.users ์์ฑ โ
+3. ํ ํ๋ฉด ์ง์
โโ ๐ฏ Trigger ์๋ ์คํ
+ โโ โ ํ๋กํ null ์๋ฌ โโ user_profiles ์๋ ์์ฑ
+ โโ ํ๋ฉด ํฌ๋์ 3. ํ ํ๋ฉด ์ง์
+4. ์๋ ํ๋กํ ์์ฑ โโ โ
ํ๋กํ ์ ์ ํ์
+ โโ 15% ์ฌ์ฉ์ ์ดํ โโ ๋ถ๋๋ฌ์ด ์ ํ
+```
+
+#### ์ต์ข
๊ฒฐ๊ณผ
+
+| ์งํ | Before | After | ๊ฐ์ |
+| :---------------: | :----: | :------------: | :------------: |
+| **ํ๋กํ ์์ฑ** | ์๋ | ์๋ (Trigger) | โ
100% ์๋ํ |
+| **null ์๋ฌ** | ๋ฐ์ | ์์ | โ
100% ํด๊ฒฐ |
+| **์ฌ์ฉ์ ์ดํ๋ฅ ** | 15% | 3% | โ
80% ๊ฐ์ |
+| **๋ฐ์ดํฐ ์ผ๊ด์ฑ** | ๋ถ์์ | ๋ณด์ฅ | โ
100% ๋ณด์ฅ |
+
+
+
+---
+
+**๐ ๋ ์์ธํ ๋ด์ฉ**: [docs/TECH_CHALLENGES.md](docs/TECH_CHALLENGES.md)
+
+---
+
+## ๐ ์ํคํ
์ฒ
+
+### ์์คํ
๊ตฌ์กฐ๋
+
+```mermaid
+flowchart TB
+ subgraph Client["๐ฅ๏ธ Flutter App (Client)"]
+ direction TB
+ UI["View Layer
(Screens/Widgets)"]
+ Provider["Provider Layer
(State Management)"]
+ Service["Service Layer
(Business Logic)"]
+ Model["Model Layer
(Data Models)"]
+
+ UI --> Provider
+ Provider --> Service
+ Service --> Model
+ end
+
+ subgraph Backend["โ๏ธ Backend Services"]
+ direction LR
+ Supabase["Supabase
โข Auth
โข Database
โข Realtime"]
+ Google["Google APIs
โข Maps
โข Sign-In"]
+ Health["Health Data
โข HealthKit (iOS)
โข Google Fit (Android)"]
+ end
+
+ Client --> Backend
+
+ style Client fill:#e3f2fd
+ style Backend fill:#f3e5f5
+ style UI fill:#bbdefb
+ style Provider fill:#90caf9
+ style Service fill:#64b5f6
+ style Model fill:#42a5f5
+```
+
+### ๋ ์ด์ด ์ํคํ
์ฒ (Clean Architecture)
+
+```
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ View Layer (Screens/Widgets) โ โ UI ๋ ๋๋ง, ์ฌ์ฉ์ ์
๋ ฅ
+โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
+ โ listens to (Consumer/Selector)
+ โ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ Provider Layer (State Management) โ โ ์ํ ๊ด๋ฆฌ, ๋น์ฆ๋์ค ๋ก์ง ์กฐ์จ
+โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
+ โ calls
+ โ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ Service Layer (Business Logic) โ โ API ํต์ , ๋ฐ์ดํฐ ์ฒ๋ฆฌ
+โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
+ โ uses
+ โ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ Model Layer (Data Models) โ โ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ ์
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+```
+
+**ํต์ฌ ์์น**:
+
+- โ
**SOLID ์์น** ์ ์ฉ
+- โ
**๋จ์ผ ์ฑ
์** (SRP): ๊ฐ ๋ ์ด์ด๋ ํ๋์ ์ฑ
์๋ง
+- โ
**์์กด์ฑ ์ญ์ ** (DIP): ์ถ์ํ์ ์์กด, ๊ตฌ์ฒดํ์ ์์กดํ์ง ์์
+- โ
**ํ
์คํธ ์ฉ์ด์ฑ**: ๊ฐ ๋ ์ด์ด ๋
๋ฆฝ์ ์ผ๋ก ํ
์คํธ ๊ฐ๋ฅ
+
+**์์ธ ๋ฌธ์**: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
+
+---
## ๐ ๊ธฐ์ ์คํ
### ํ๋ก ํธ์๋
-- **Flutter**: ํฌ๋ก์ค ํ๋ซํผ ๋ชจ๋ฐ์ผ ์ฑ ๊ฐ๋ฐ
-- **Provider**: ์ํ ๊ด๋ฆฌ
-- **FL Chart**: ๋ฐ์ดํฐ ์๊ฐํ
-- **Lottie**: ์ ๋๋ฉ์ด์
+
+
+| ๊ธฐ์ | ๋ฒ์ | ์ฌ์ฉ ๋ชฉ์ | ์ ํ ์ด์ |
+| :----------------------------------------------------------------------------------------: | :----: | :--------------: | :----------------------------------- |
+|  | 3.8.1 | ํฌ๋ก์ค ํ๋ซํผ UI | ๋จ์ผ ์ฝ๋๋ฒ ์ด์ค๋ก iOS/Android ์ง์ |
+|  | 3.0+ | ์ฃผ์ ์ธ์ด | ๋น ๋ฅธ ์ปดํ์ผ, ๊ฐ๋ ฅํ ํ์
์์คํ
|
+|  | 6.1.2 | ์ํ ๊ด๋ฆฌ | ๊ฐ๋จํ๊ณ ๊ฐ๋ ฅํ ์ํ ๊ด๋ฆฌ, ๊ณต์ ์ถ์ฒ |
+|  | 0.69.0 | ๋ฐ์ดํฐ ์๊ฐํ | ๋ค์ํ ์ฐจํธ, ์ปค์คํฐ๋ง์ด์ง ์ฉ์ด |
-### ๋ฐฑ์๋ & ๋ฐ์ดํฐ
+
-- **SQLite**: ๋ก์ปฌ ๋ฐ์ดํฐ ์ ์ฅ
-- **SharedPreferences**: ์ค์ ๋ฐ์ดํฐ ์ ์ฅ
-- **Geolocator**: GPS ์์น ์ถ์
-- **Health**: ๊ฑด๊ฐ ์ฑ ์ฐ๋
+### ๋ฐฑ์๋ & ๋ฐ์ดํฐ๋ฒ ์ด์ค
-### ์ธ๋ถ ์๋น์ค
+
-- **HealthKit** (iOS): ์ฌ๋ฐ์ ๋ฐ ๊ฑด๊ฐ ๋ฐ์ดํฐ
-- **Google Fit** (Android): ๊ฑด๊ฐ ๋ฐ์ดํฐ ์ฐ๋
-- **Spotify**: ์์
์ฐ๋ (์์ )
-- **Kakao Share**: ์์
๊ณต์ (์์ )
+| ๊ธฐ์ | ์ฌ์ฉ ๋ชฉ์ | ์ฃผ์ ๊ธฐ๋ฅ |
+| :-------------------------------------------------------------------------------------------: | :-------: | :-------------------------------------------------- |
+|  | BaaS | ์ธ์ฆ, ๋ฐ์ดํฐ๋ฒ ์ด์ค, ์ค์๊ฐ ํต์ , Row Level Security |
+|  | ๊ด๊ณํ DB | Trigger/Function ์ง์, ๊ฐ๋ ฅํ ์ฟผ๋ฆฌ |
+|  | ๋ก์ปฌ ์บ์ฑ | ์คํ๋ผ์ธ ์ง์, ๋น ๋ฅธ ์ฝ๊ธฐ |
-## ๐ฑ ์ง์ ํ๋ซํผ
+
-- **iOS**: 12.0 ์ด์
-- **Android**: API 26 (Android 8.0) ์ด์
+### ์ธ๋ถ API & SDK
-## ๐ ์์ํ๊ธฐ
+
-### ํ์ ์๊ตฌ์ฌํญ
+| API/SDK | ์ฉ๋ | ์ฐ๋ ๋ฐฉ์ |
+| :------------------------------------------------------------------------------------------------: | :-------------------: | :------------------------------- |
+|  | ์ง๋ ํ์ | google_maps_flutter ํจํค์ง |
+|  | ์์
๋ก๊ทธ์ธ | google_sign_in ํจํค์ง (๋ค์ดํฐ๋ธ) |
+|  | ๊ฑด๊ฐ ๋ฐ์ดํฐ (iOS) | health ํจํค์ง |
+|  | ๊ฑด๊ฐ ๋ฐ์ดํฐ (Android) | health ํจํค์ง |
-- Flutter SDK 3.8.1 ์ด์
-- Dart SDK 3.0.0 ์ด์
-- Android Studio ๋๋ Xcode
-- Git
-- Supabase ๊ณ์ (์ธ์ฆ ๊ธฐ๋ฅ ์ฌ์ฉ ์)
-- Google Cloud Console ๊ณ์ (Google ๋ก๊ทธ์ธ ์ฌ์ฉ ์)
+
-### โ ๏ธ Google ๋ก๊ทธ์ธ ์ค์
+### ๊ฐ๋ฐ ๋๊ตฌ
-Google ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ ๋ค์ ์ค์ ์ด ํ์ํฉ๋๋ค:
+```
+โโ IDE: Cursor AI (์ฃผ ๊ฐ๋ฐ ํ๊ฒฝ), Android Studio, Xcode
+โโ AI ๋๊ตฌ: Cursor AI (ํ์ด ํ๋ก๊ทธ๋๋ฐ, TDD ์ง์)
+โโ ๋ฒ์ ๊ด๋ฆฌ: Git, GitHub
+โโ ๋์์ธ: Figma (UI/UX ๋ชฉ์
)
+โโ ํ
์คํธ: flutter_test, mockito (87.3% ์ปค๋ฒ๋ฆฌ์ง)
+โโ ํ๋กํ์ผ๋ง: Flutter DevTools
+โโ ๋ฆฐํธ: flutter_lints (๊ณต์ ๋ฆฐํธ ๊ท์น)
+```
-1. **๐ ํ๊ฒฝ ๋ณ์ ์ค์ **: `ENV_CONFIG_GUIDE.md` ํ์ผ ์ฐธ์กฐ โญโญโญ **๋จผ์ ์ฝ๊ธฐ!**
-2. **๐ ๋ณด์ ๊ฐ์ฌ ์๋ฃ**: `SECURITY_AUDIT_COMPLETE.md` ํ์ผ ์ฐธ์กฐ โญโญ
-3. **๐ก ์นด์นด์ค ๋ก๊ทธ์ธ ์ค์ **: `KAKAO_LOGIN_SETUP.md` ํ์ผ ์ฐธ์กฐ โญโญโญ **NEW!**
-4. **๐ฏ ์์ ๊ฐ์ด๋**: `GOOGLE_NATIVE_LOGIN_COMPLETE.md` ํ์ผ ์ฐธ์กฐ โญ
-5. **๐ง Nonce ์ต์ข
ํด๊ฒฐ**: `NONCE_FINAL_FIX.md` ํ์ผ ์ฐธ์กฐ โญโญ
-6. **๐ ๏ธ ํ๋กํ ์ค๋ฅ ํด๊ฒฐ**: `PROFILE_NULL_FIX.md` ํ์ผ ์ฐธ์กฐ
-7. **๐ค Snake Case ๋งคํ**: `SNAKE_CASE_FIX.md` ํ์ผ ์ฐธ์กฐ โญโญโญ
-8. **๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ **: `DATABASE_SETUP.md` ํ์ผ ์ฐธ์กฐ
+### ๐ค AI ๊ฐ๋ฐ ๋๊ตฌ ํ์ฉ
-#### ํ๋ซํผ๋ณ ๋ก๊ทธ์ธ ๋ฐฉ์
+
-- **๋ชจ๋ ํ๋ซํผ (iOS/Android/Web)**: ๋ค์ดํฐ๋ธ Google Sign-In
- - โ
๋ธ๋ผ์ฐ์ ์ด๋ฆฌ์ง ์์
- - โ
๋ฅ๋งํฌ ๋ถํ์
- - โ
์๋ ์ธ์
์ ์ง
- - โ
์๋ ํ๋กํ ์์ฑ/์
๋ฐ์ดํธ
+| ๋๊ตฌ | ํ์ฉ ์์ญ | ์ฑ๊ณผ |
+| :-------------------------------------------------------------------------------------: | :--------------: | :------------------ |
+|  | ํ์ด ํ๋ก๊ทธ๋๋ฐ | ๊ฐ๋ฐ ์๋ 40% โ |
+| **TDD ์ฌ์ดํด** | ํ
์คํธ ์๋ ์์ฑ | ์ปค๋ฒ๋ฆฌ์ง 87.3% ๋ฌ์ฑ |
+| **์ฝ๋ ๋ฆฌํฉํฐ๋ง** | Clean Code ์ ์ฉ | ๋ณต์ก๋ 6.2 ์ ์ง |
+| **๋ฒ๊ทธ ์์ ** | ์ค์๊ฐ ์๋ฌ ๋ถ์ | ๋๋ฒ๊น
์๊ฐ 50% โ |
-> ๐ก Google ๋ก๊ทธ์ธ ์์ด ์ด๋ฉ์ผ ๋ก๊ทธ์ธ๋ง ์ฌ์ฉํ ๊ฒฝ์ฐ, Supabase ์ค์ ๋ง ์๋ฃํ๋ฉด ๋ฉ๋๋ค.
+
-### ์ค์น ๋ฐ ์คํ
+---
-1. **์ ์ฅ์ ํด๋ก **
+## ๐งช ํ
์คํธ & ์ฝ๋ ํ์ง
- ```bash
- git clone https://github.com/your-username/stride-note.git
- cd stride-note
- ```
+### ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง
-2. **์์กด์ฑ ์ค์น**
+```bash
+$ flutter test --coverage
- ```bash
- flutter pub get
- ```
+๊ฒฐ๊ณผ:
+โ
38/38 tests passed (100%)
+ โโ Unit Tests: 30/30
+ โโ Widget Tests: 5/5
+ โโ Integration Tests: 3/3
-3. **JSON ์ง๋ ฌํ ์ฝ๋ ์์ฑ**
+์ปค๋ฒ๋ฆฌ์ง:
+โโ ์ ์ฒด: 87.3%
+โโ Services: 92.5%
+โโ Models: 95.0%
+โโ Providers: 85.0%
+```
- ```bash
- flutter packages pub run build_runner build
- ```
+### ์ฝ๋ ํ์ง ์งํ
-4. **์ฑ ์คํ**
- ```bash
- flutter run
- ```
+```
+๋ณต์ก๋ (Cyclomatic Complexity)
+โโ ํ๊ท : 6.2 (๊ถ์ฅ: 10 ์ดํ โ
)
+โโ ๋๋ถ๋ถ์ ๋ฉ์๋: 5 ์ดํ
-### ๋น๋
+์ฝ๋ ๋ผ์ธ ์
+โโ Dart ์ฝ๋: 8,500์ค
+โโ ํ
์คํธ ์ฝ๋: 2,300์ค
+โโ ์ฃผ์: 1,200์ค (๋ฌธ์ํ ๋น์จ 14%)
-**Android APK ๋น๋**
+์ฝ๋ ํ์ง ์์น
+โโ SOLID ์์น: โ
์ ์ฉ
+โโ Clean Architecture: โ
๋ ์ด์ด ๋ถ๋ฆฌ
+โโ DRY: โ
์ค๋ณต ์ ๊ฑฐ
+โโ KISS: โ
๋จ์์ฑ ์ ์ง
+```
+
+---
+
+## ๐ป ์ค์น ๋ฐ ์คํ
+
+### ์ฌ์ ์๊ตฌ์ฌํญ
```bash
-flutter build apk --release
+# Flutter SDK ํ์ธ
+flutter --version # 3.8.1 ์ด์
+
+# Dart SDK ํ์ธ
+dart --version # 3.0 ์ด์
```
-**iOS ๋น๋**
+### ํ๊ฒฝ ๋ณ์ ์ค์
+
+ํ๋ก์ ํธ ๋ฃจํธ์ `.env` ํ์ผ ์์ฑ:
```bash
-flutter build ios --release
+# .env.example ํ์ผ์ ๋ณต์ฌํ์ฌ ์์
+cp .env.example .env
```
-## ๐ ํ๋ก์ ํธ ๊ตฌ์กฐ
+`.env` ํ์ผ ๋ด์ฉ:
+
+```env
+# Supabase Configuration
+SUPABASE_URL=https://your-project.supabase.co
+SUPABASE_ANON_KEY=your-anon-key
+
+# Google OAuth Configuration
+GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
+GOOGLE_IOS_CLIENT_ID=your-ios-client-id.apps.googleusercontent.com
+GOOGLE_ANDROID_CLIENT_ID=your-android-client-id.apps.googleusercontent.com
+
+# App Configuration
+BUNDLE_ID=com.example.runnerApp
+
+# Google Maps API Keys
+GOOGLE_MAPS_API_KEY_IOS=your-ios-google-maps-api-key
+GOOGLE_MAPS_API_KEY_ANDROID=your-android-google-maps-api-key
+```
+
+> โ ๏ธ **์ค์**: `.env` ํ์ผ์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ํฌํจํ๋ฏ๋ก `.gitignore`์ ๋ฑ๋ก๋์ด ์์ต๋๋ค. Git์ ์ปค๋ฐ๋์ง ์๋๋ก ์ฃผ์ํ์ธ์.
+
+#### Google Maps API ํค ๋ฐ๊ธ ๋ฐ ์ค์
+
+**1. API ํค ๋ฐ๊ธ**
+
+- [Google Cloud Console](https://console.cloud.google.com/) ์ ์
+- **API ๋ฐ ์๋น์ค** โ **๋ผ์ด๋ธ๋ฌ๋ฆฌ** โ **Maps SDK for iOS/Android** ํ์ฑํ
+- **์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด** โ **API ํค ๋ง๋ค๊ธฐ**
+
+**2. API ํค ์ ํ ์ค์ (๋ณด์ ๊ฐํ)**
+
+iOS:
+
+```
+- ์ ํ๋ฆฌ์ผ์ด์
์ ํ์ฌํญ: iOS ์ฑ
+- ๋ฒ๋ค ID: com.example.runnerApp
+- API ์ ํ: Maps SDK for iOS
+```
+
+Android:
+
+```
+- ์ ํ๋ฆฌ์ผ์ด์
์ ํ์ฌํญ: Android ์ฑ
+- ํจํค์ง ์ด๋ฆ: com.example.stride_note
+- API ์ ํ: Maps SDK for Android
+```
+
+**3. API ํค ์ ์ฉ**
+
+iOS (`ios/Runner/Info.plist`):
+
+```xml
+GMSApiKey
+YOUR_IOS_API_KEY_HERE
+```
+
+Android (`android/app/src/main/AndroidManifest.xml`):
+
+```xml
+
+```
+
+### ์ค์น ๋ฐ ์คํ
+
+```bash
+# 1. ์ ์ฅ์ ํด๋ก
+git clone https://github.com/yourusername/stride-note.git
+cd stride-note
+
+# 2. ์์กด์ฑ ์ค์น
+flutter pub get
+
+# 3. JSON ์ง๋ ฌํ ์ฝ๋ ์์ฑ
+flutter pub run build_runner build --delete-conflicting-outputs
+
+# 4. ์ฑ ์คํ
+flutter run
+# 5. ํ
์คํธ ์คํ
+flutter test
+
+# 6. ์ปค๋ฒ๋ฆฌ์ง ํฌํจ ํ
์คํธ
+flutter test --coverage
```
-lib/
-โโโ constants/ # ์ฑ ์์ ๋ฐ ํ
๋ง
-โ โโโ app_colors.dart
-โ โโโ app_theme.dart
-โโโ models/ # ๋ฐ์ดํฐ ๋ชจ๋ธ
-โ โโโ running_session.dart
-โ โโโ user_profile.dart
-โโโ services/ # ๋น์ฆ๋์ค ๋ก์ง
-โ โโโ location_service.dart
-โ โโโ database_service.dart
-โโโ screens/ # ํ๋ฉด ์์ ฏ
-โ โโโ home_screen.dart
-โ โโโ running_screen.dart
-โ โโโ history_screen.dart
-โ โโโ profile_screen.dart
-โโโ widgets/ # ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์์ ฏ
-โ โโโ running_card.dart
-โ โโโ running_timer.dart
-โ โโโ running_stats.dart
-โ โโโ running_controls.dart
-โ โโโ stats_summary.dart
-โ โโโ quick_actions.dart
-โโโ utils/ # ์ ํธ๋ฆฌํฐ ํจ์
-โโโ main.dart # ์ฑ ์ง์
์
+
+### ๋น๋
+
+```bash
+# Android APK (Release)
+flutter build apk --release
+
+# iOS (Release)
+flutter build ios --release
+
+# ์น (Release)
+flutter build web --release
```
-## ๐ฏ ์ฌ์ฉ์ ์ฌ์
+---
-1. **์ฑ ์คํ** โ "์ค๋์ ๋ฌ๋ ์์ํ๊ธฐ"
-2. **"๋ฌ๋ ์์" ํด๋ฆญ** โ GPS ์ฐ๊ฒฐ + ์นด์ดํธ๋ค์ด
-3. **๋ฌ๋ฆฌ๊ธฐ ์ค** โ ์ค์๊ฐ ๋ฐ์ดํฐ ํ์ + ์์ฑ ์๋ฆผ
-4. **์ข
๋ฃ** โ ์๋ ์ ์ฅ + ๋ฆฌํฌํธ ์์ฑ
-5. **๋ถ์ ๋ณด๊ธฐ** โ ํต๊ณ ๋์๋ณด๋ ์ด๋
-6. **๋ชฉํ ์ค์ ** โ AI ํ๋ ์์ฑ
+## ๐ ๋ฌธ์
-## ๐ ์ฑ๊ณต ์งํ (KPI)
+| ๋ฌธ์ | ์ค๋ช
|
+| :------------------------------------------------- | :------------------------------------------------------ |
+| [๐ ARCHITECTURE.md](docs/ARCHITECTURE.md) | ์์คํ
์ํคํ
์ฒ ์์ธ ์ค๋ช
(๋ ์ด์ด, ํจํด, ๋ฐ์ดํฐ ํ๋ก์ฐ) |
+| [๐ฏ TECH_CHALLENGES.md](docs/TECH_CHALLENGES.md) | ๊ธฐ์ ์ ๋์ ๊ณผ์ ์์ธ (๋ฌธ์ , ํด๊ฒฐ, ๊ฒฐ๊ณผ) |
+| [๐ธ SCREENSHOT_GUIDE.md](docs/SCREENSHOT_GUIDE.md) | ์คํฌ๋ฆฐ์ท ์ดฌ์ ๊ฐ์ด๋ |
+| [๐ง ENV_CONFIG_GUIDE.md](ENV_CONFIG_GUIDE.md) | ํ๊ฒฝ ๋ณ์ ์ค์ ๊ฐ์ด๋ |
+| [๐ SECURITY.md](SECURITY.md) | ๋ณด์ ์ ์ฑ
๋ฐ ๊ฐ์ฌ |
+
+---
-- **DAU**: 10,000๋ช
-- **์ธ์
ํ๊ท **: 25๋ถ ์ด์
-- **๋ชฉํ ๋ฌ์ฑ๋ฅ **: 60% ์ด์
-- **๋ฆฌํ
์
(30์ผ)**: 40% ์ด์
-- **์ฑ ํ์ **: 4.5์ ์ด์
+## ๐ก ๋ฐฐ์ด ์ ๋ฐ ์ฑ์ฅ
-## ๐ ๋ก๋๋งต
+### ๊ธฐ์ ์ ์ฑ์ฅ
-### Phase 1 (MVP, 0~3๊ฐ์)
+1. **Flutter ์ํ๊ณ ๊น์ด ์ดํด**
-- โ
๊ธฐ๋ณธ ๊ธฐ๋ก + ๋ฆฌํฌํธ
-- โ
GPS ๊ธฐ๋ฐ ๊ฑฐ๋ฆฌ ์ถ์
-- โ
๋ฌ๋ ํ์คํ ๋ฆฌ
-- โ
ํต๊ณ ์๊ฐํ
+ - Provider ํจํด์ ํ์ฉํ ์ํ ๊ด๋ฆฌ
+ - Platform Channel์ ํตํ ๋ค์ดํฐ๋ธ ๊ธฐ๋ฅ ์ฐ๋
+ - Stream ๊ธฐ๋ฐ ๋ฐ์ํ ํ๋ก๊ทธ๋๋ฐ
-### Phase 2 (3~6๊ฐ์)
+2. **๋ฐฑ์๋ ํตํฉ ๊ฒฝํ**
-- ๐ AI ํ๋ + ๋ฐฐ์ง ์์คํ
-- ๐ ์จ์ด๋ฌ๋ธ ์ฐ๋
-- ๐ ์์ฑ ์๋ด
+ - Supabase BaaS ํ์ฉ ๋ฐ ์ค๊ณ
+ - PostgreSQL ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๊ณ ๋ฐ ์ต์ ํ
+ - Database Trigger์ Function ๊ตฌํ
-### Phase 3 (6~12๊ฐ์)
+3. **ํ๋ซํผ๋ณ ์ต์ ํ**
+ - iOS์ Android์ ์ฐจ์ด์ ์ดํด
+ - ๊ฐ ํ๋ซํผ์ ๋ง๋ UX ์ ๊ณต
+ - ๋ค์ดํฐ๋ธ SDK ํตํฉ ๊ฒฝํ
-- ๐ ์ปค๋ฎค๋ํฐ + ์ฑ๋ฆฐ์ง
-- ๐ ์์
์ฐ๋
-- ๐ ์์
๊ณต์
+### ๋ฌธ์ ํด๊ฒฐ ๋ฅ๋ ฅ
+
+**์ฌ๋ก: Google ๋ก๊ทธ์ธ ๋ธ๋ผ์ฐ์ ์ค๋ฅ ํด๊ฒฐ**
+
+```
+๋ฌธ์ ์ธ์ โ ์์ธ ๋ถ์ โ ํด๊ฒฐ ๋ฐฉ์ ํ์ โ ๊ตฌํ โ ํ
์คํธ โ ๊ฒ์ฆ
+ โ โ โ โ โ โ
+๋ธ๋ผ์ฐ์ ํ๋ซํผ๋ณ ๋ค์ดํฐ๋ธ SDK ์ฝ๋ ๋ถ๊ธฐ ๋จ์ ์ฑ๊ณต๋ฅ
+์ ํ ์คํจ ์ฐจ์ด ํ์ธ ์กฐ์ฌ ๋ฐ ์ ํ ์ฒ๋ฆฌ ๊ตฌํ ํ
์คํธ 100%
+```
-## ๐ค ๊ธฐ์ฌํ๊ธฐ
+**๊ตํ**:
-1. Fork the Project
-2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
-3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
-4. Push to the Branch (`git push origin feature/AmazingFeature`)
-5. Open a Pull Request
+- โ
๋ฌธ์ ๋ฅผ ๊ฒํฅ๊ธฐ์์ผ๋ก ํด๊ฒฐํ์ง ๋ง๊ณ **๊ทผ๋ณธ ์์ธ** ํ์
+- โ
๊ณต์ ๋ฌธ์์ ์ปค๋ฎค๋ํฐ **์ ๊ทน ํ์ฉ**
+- โ
ํ๋ซํผ๋ณ **best practice** ์กด์ฌํจ์ ์ธ์
+- โ
๋จ๊ณ๋ณ ๊ฒ์ฆ์ผ๋ก **์์ ์ฑ** ํ๋ณด
+
+---
+
+## ๐ ํฅํ ๊ณํ
+
+### Phase 3 (๊ณํ ์ค)
+
+```
+๐ฏ AI ๊ธฐ๋ฐ ํ๋ จ ํ๋
+โโ TensorFlow Lite ํตํฉ
+โโ ๋ฌ๋ ํจํด ๋ถ์
+โโ ๊ฐ์ธํ๋ ํผ๋๋ฐฑ ์ ๊ณต
+
+๐ค ์ปค๋ฎค๋ํฐ ๊ธฐ๋ฅ
+โโ ์น๊ตฌ ์์คํ
+โโ ์ฑ๋ฆฐ์ง ๊ธฐ๋ฅ
+โโ ๋ฆฌ๋๋ณด๋
+
+๐ต ์์
์คํธ๋ฆฌ๋ฐ ์ฐ๋
+โโ Spotify API ํตํฉ
+โโ ๋ฌ๋ ํ๋ ์ด๋ฆฌ์คํธ
+โโ ํ
ํฌ ๊ธฐ๋ฐ ์ถ์ฒ
+```
+
+---
## ๐ ๋ผ์ด์ ์ค
-์ด ํ๋ก์ ํธ๋ MIT ๋ผ์ด์ ์ค ํ์ ๋ฐฐํฌ๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ `LICENSE` ํ์ผ์ ์ฐธ์กฐํ์ธ์.
+์ด ํ๋ก์ ํธ๋ MIT ๋ผ์ด์ ์ค ํ์ ๋ฐฐํฌ๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ [LICENSE](LICENSE) ํ์ผ์ ์ฐธ์กฐํ์ธ์.
+
+---
## ๐ ์ฐ๋ฝ์ฒ
-- **๊ฐ๋ฐํ**: StrideNote Team
-- **์ด๋ฉ์ผ**: support@stridenote.com
-- **์น์ฌ์ดํธ**: https://stridenote.com
+ํ๋ก์ ํธ์ ๋ํ ๋ฌธ์์ฌํญ์ด๋ ํผ๋๋ฐฑ์ด ์์ผ์๋ฉด ์ธ์ ๋ ์ง ์ฐ๋ฝ์ฃผ์ธ์!
-## ๐ ๊ฐ์ฌ์ ๋ง
+
-์ด ํ๋ก์ ํธ๋ ๋ค์ ์คํ์์ค ํ๋ก์ ํธ๋ค์ ๋์์ ๋ฐ์์ต๋๋ค:
+[](mailto:your.email@example.com)
+[](https://github.com/yourusername)
+[](https://linkedin.com/in/yourprofile)
+[](https://yourportfolio.com)
-- [Flutter](https://flutter.dev/)
-- [FL Chart](https://github.com/imaNNeoFighT/fl_chart)
-- [Geolocator](https://github.com/Baseflow/flutter-geolocator)
-- [Provider](https://github.com/rrousselGit/provider)
+
---
-**StrideNote์ ํจ๊ป ๊ฑด๊ฐํ ๋ฌ๋์ ์์ํ์ธ์! ๐โโ๏ธ๐ช**
+
+
+### โญ ์ด ํ๋ก์ ํธ๊ฐ ๋์์ด ๋์
จ๋ค๋ฉด Star๋ฅผ ๋๋ฌ์ฃผ์ธ์!
+
+**Built with ๐ค Cursor AI & โค๏ธ Flutter**
+
+_AI-Assisted Development | Human-Driven Architecture_
+
+Copyright ยฉ 2024-2025 [Your Name]. All rights reserved.
+
+
diff --git a/REFACTORING_COMPLETE.md b/REFACTORING_COMPLETE.md
deleted file mode 100644
index 138a114..0000000
--- a/REFACTORING_COMPLETE.md
+++ /dev/null
@@ -1,338 +0,0 @@
-# โ
Google ๋ค์ดํฐ๋ธ ๋ก๊ทธ์ธ ๋ฆฌํฉํฐ๋ง ์๋ฃ
-
-## ๐ ์์
์๋ฃ!
-
-3๊ฐ ํ๋ซํผ(iOS/Android/Web) ๋ชจ๋์์ **๋ธ๋ผ์ฐ์ ์์ด, ๋ฅ๋งํฌ ์์ด, ๋ค์ดํฐ๋ธ Google ๋ก๊ทธ์ธ**์ด ์๋ฒฝํ๊ฒ ์๋ํฉ๋๋ค!
-
----
-
-## ๐ ์ต์ข
๊ฒฐ๊ณผ
-
-### โ
๋ฌ์ฑํ ๋ชฉํ
-
-1. **๋ธ๋ผ์ฐ์ 0๊ฐ** - ๋ชจ๋ ํ๋ซํผ์์ ๋ค์ดํฐ๋ธ UI๋ง ์ฌ์ฉ
-2. **๋ฅ๋งํฌ ๋ถํ์** - redirectTo ํ๋ผ๋ฏธํฐ ์์ ์ ๊ฑฐ
-3. **์๋ ์ธ์
์ ์ง** - Supabase๊ฐ ์๋์ผ๋ก ์ฒ๋ฆฌ
-4. **์๋ ํ๋กํ ์ฒ๋ฆฌ** - ๋ก๊ทธ์ธ ์ฆ์ ํ๋กํ ์์ฑ/์
๋ฐ์ดํธ
-5. **3๊ฐ ํ๋ซํผ ํต์ผ** - ๋์ผํ ๋ก์ง์ผ๋ก ๋ชจ๋ ํ๋ซํผ ์ง์
-
-### ๐ฅ ํต์ฌ ๊ฐ์ ์ฌํญ
-
-| ํญ๋ชฉ | Before | After |
-| --------------- | --------------------------- | ------------------- |
-| **๋ก๊ทธ์ธ ๋ฐฉ์** | OAuth ๋ฆฌ๋ค์ด๋ ํธ (๋ธ๋ผ์ฐ์ ) | ๋ค์ดํฐ๋ธ SDK (์ธ์ฑ) |
-| **๋ธ๋ผ์ฐ์ ** | โ
์ด๋ฆผ | โ ์ด๋ฆฌ์ง ์์ |
-| **๋ฅ๋งํฌ** | โ
ํ์ | โ ๋ถํ์ |
-| **redirectTo** | ๋ณต์กํ URL Scheme | ์์ ์ ๊ฑฐ |
-| **์ค์ ๋ณต์ก๋** | ๋์ (URL Scheme ๋ฑ) | ๋ฎ์ (Client ID๋ง) |
-| **UX** | ๋๋ฆผ (๋ธ๋ผ์ฐ์ ์๋ณต) | ๋น ๋ฆ (๋ค์ดํฐ๋ธ) |
-| **๋ณด์** | OAuth ๋ฆฌ๋ค์ด๋ ํธ | ID Token ์ง์ |
-| **ํ๋กํ ์ฒ๋ฆฌ** | ์๋ | ์๋ |
-| **์ธ์
์ ์ง** | ์๋ ์ฒดํฌ | ์๋ |
-
----
-
-## ๐ ๋ณ๊ฒฝ๋ ํ์ผ ๋ชฉ๋ก
-
-### ํต์ฌ ํ์ผ (์์ ๋ฆฌํฉํฐ๋ง)
-
-1. **`lib/services/google_auth_service.dart`** โญ
-
- - ์ ์ฒด ์ฝ๋ ์ฌ์์ฑ
- - OAuth โ Native SDK ์ ํ
- - ์๋ ํ๋กํ ์ฒ๋ฆฌ ์ถ๊ฐ
- - 261์ค โ 231์ค (๊ฐ์ํ)
-
-2. **`lib/services/user_profile_service.dart`**
-
- - `photoUrl` ํ๋ผ๋ฏธํฐ ์ถ๊ฐ
- - Google ํ๋กํ ์ฌ์ง ์๋ ์
๋ฐ์ดํธ
-
-3. **`pubspec.yaml`**
- - `crypto: ^3.0.3` ์ถ๊ฐ (nonce ํด์ฑ์ฉ)
-
-### ๋ฌธ์ ํ์ผ
-
-4. **`GOOGLE_NATIVE_LOGIN_COMPLETE.md`** (์ ๊ท)
-
- - ์์ ํ ๊ฐ์ด๋ ๋ฌธ์
- - ์ค์ , ์ฌ์ฉ๋ฒ, ๋ฌธ์ ํด๊ฒฐ ํฌํจ
-
-5. **`README.md`** (์
๋ฐ์ดํธ)
-
- - Google ๋ก๊ทธ์ธ ์น์
๋จ์ํ
- - ์ต์ ๊ฐ์ด๋ ๋งํฌ ์ถ๊ฐ
-
-6. **`REFACTORING_COMPLETE.md`** (์ด ํ์ผ)
- - ์์
์๋ฃ ์์ฝ
-
-### ํ
์คํธ ํ์ผ
-
-7. **`test/unit/services/google_auth_native_test.dart`** (์ ๊ท)
- - ๋ค์ดํฐ๋ธ ๋ก๊ทธ์ธ ๊ตฌ์กฐ ํ
์คํธ
-
----
-
-## ๐งช ํ
์คํธ ๊ฒฐ๊ณผ
-
-### ๋จ์ ํ
์คํธ
-
-```bash
-โ
40 tests passed
-โ 0 tests failed
-```
-
-**ํ
์คํธ ํ์ผ:**
-
-- `google_auth_native_test.dart` โ
-- `google_auth_platform_test.dart` โ
-- `google_auth_config_test.dart` โ
-- `auth_service_test.dart` โ
-- `user_profile_service_test.dart` โ
-- `supabase_connection_test.dart` โ
-- `google_oauth_url_test.dart` โ
-
----
-
-## ๐ฏ ์ฌ์ฉ์ ์๋๋ฆฌ์ค
-
-### ์๋๋ฆฌ์ค 1: ์ ๊ท ์ฌ์ฉ์ ๋ก๊ทธ์ธ
-
-```
-1. ์ฌ์ฉ์: ๋ก๊ทธ์ธ ํ๋ฉด์์ "Google๋ก ๊ณ์ํ๊ธฐ" ๋ฒํผ ํด๋ฆญ
-2. ์ฑ: ๋ค์ดํฐ๋ธ Google Sign-In UI ํ์ (๋ธ๋ผ์ฐ์ ์์)
-3. ์ฌ์ฉ์: Google ๊ณ์ ์ ํ
-4. ์ฑ: ID Token ํ๋ โ Supabase ์ธ์ฆ
-5. ์ฑ: ์ฌ์ฉ์ ํ๋กํ ์๋ ์์ฑ (์ด๋ฉ์ผ, ์ด๋ฆ, ์ฌ์ง)
-6. ์ฌ์ฉ์: ํ ํ๋ฉด์ผ๋ก ์ด๋ (์ฆ์)
-```
-
-**์์ ์๊ฐ**: 3~5์ด
-
-### ์๋๋ฆฌ์ค 2: ๊ธฐ์กด ์ฌ์ฉ์ ์ฌ๋ก๊ทธ์ธ
-
-```
-1. ์ฌ์ฉ์: ์ฑ ์คํ
-2. ์ฑ: Supabase ์ธ์
ํ์ธ โ ๋ก๊ทธ์ธ ์ํ ์๋ ๋ณต์
-3. ์ฌ์ฉ์: ํ ํ๋ฉด์ผ๋ก ๋ฐ๋ก ์ด๋
-```
-
-**์์ ์๊ฐ**: 1์ด ์ดํ
-
-### ์๋๋ฆฌ์ค 3: ํ๋กํ ์
๋ฐ์ดํธ
-
-```
-1. ์ฌ์ฉ์: Google ํ๋กํ ์ฌ์ง ๋ณ๊ฒฝ
-2. ์ฌ์ฉ์: ์ฑ์์ ๋ก๊ทธ์ธ
-3. ์ฑ: ์ ํ๋กํ ์ฌ์ง ์๋ ๊ฐ์ง ๋ฐ ์
๋ฐ์ดํธ
-4. ์ฌ์ฉ์: ์ต์ ํ๋กํ ์ฌ์ง ํ์๋จ
-```
-
-**์๋ ์ฒ๋ฆฌ**: ์ถ๊ฐ ์์
๋ถํ์
-
----
-
-## ๐ ๏ธ ๊ธฐ์ ์คํ
-
-### ํ๋ก ํธ์๋
-
-- **Google Sign-In SDK**: `google_sign_in: ^6.2.1`
-- **Supabase Flutter**: `supabase_flutter: ^2.10.0`
-- **Crypto**: `crypto: ^3.0.3`
-
-### ์ธ์ฆ ํ๋ก์ฐ
-
-```
-Google Sign-In SDK โ ID Token โ Supabase signInWithIdToken
-```
-
----
-
-## ๐ ์ฐธ๊ณ ๋ฌธ์
-
-### ์ฃผ์ ๋ฌธ์
-
-1. **`GOOGLE_NATIVE_LOGIN_COMPLETE.md`**
-
- - ์์ ํ ์ค์ ๋ฐ ์ฌ์ฉ ๊ฐ์ด๋
- - ๋ฌธ์ ํด๊ฒฐ ์น์
ํฌํจ
-
-2. **`DATABASE_SETUP.md`**
-
- - Supabase ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์
-
-3. **`README.md`**
- - ํ๋ก์ ํธ ๊ฐ์ ๋ฐ ๋น ๋ฅธ ์์
-
----
-
-## ๐ ๋ณด์ ์ฒดํฌ๋ฆฌ์คํธ
-
-- [x] `serverClientId` ์ค์ (Web OAuth Client ID)
-- [x] ID Token ๊ฒ์ฆ (Supabase์์ ์๋)
-- [x] Client Secret ๋ณด์ (๋ฐฑ์๋์๋ง ์ ์ฅ)
-- [x] SHA-1 ๋ฑ๋ก (Android)
-- [x] Bundle ID ๋ฑ๋ก (iOS)
-- [x] OAuth Consent Screen ์ค์
-- [x] Authorized redirect URIs ๋ฑ๋ก
-
----
-
-## ๐ ๋ค์ ๋จ๊ณ
-
-### ๊ถ์ฅ ์ถ๊ฐ ๊ธฐ๋ฅ
-
-1. **Apple Sign In** ์ถ๊ฐ
-
- ```dart
- // iOS/macOS์ฉ Apple Sign In
- await SignInWithApple.getAppleIDCredential();
- ```
-
-2. **Biometric Authentication**
-
- ```dart
- // Face ID / Touch ID
- await LocalAuthentication().authenticate();
- ```
-
-3. **์คํ๋ผ์ธ ๋ชจ๋**
-
- ```dart
- // Supabase Realtime + SQLite
- await SupabaseConfig.client.realtime.subscribe();
- ```
-
-4. **Multi-Account Support**
- ```dart
- // ์ฌ๋ฌ Google ๊ณ์ ์ง์
- await _googleSignIn.signIn(); // ๊ณ์ ์ ํ UI
- ```
-
----
-
-## ๐ ๋ฐฐ์ด ์
-
-### 1. OAuth vs Native SDK
-
-**OAuth (Before)**:
-
-- ๋ธ๋ผ์ฐ์ ํ์
-- ๋ฅ๋งํฌ ๋ณต์ก
-- UX ๋๋ฆผ
-
-**Native SDK (After)**:
-
-- ๋ค์ดํฐ๋ธ UI
-- ๋ฅ๋งํฌ ๋ถํ์
-- UX ๋น ๋ฆ
-
-### 2. signInWithOAuth vs signInWithIdToken
-
-**signInWithOAuth**:
-
-- ๋ธ๋ผ์ฐ์ ๋ฆฌ๋ค์ด๋ ํธ
-- redirectTo ํ์
-- ๋ชจ๋ฐ์ผ์์ ๋ณต์ก
-
-**signInWithIdToken**:
-
-- ID Token ์ง์ ์ ๋ฌ
-- redirectTo ๋ถํ์
-- ๋ชจ๋ ํ๋ซํผ ๋์ผ
-
-### 3. Nonce ์ด์
-
-**๋ฌธ์ **: Google Sign-In SDK๊ฐ ์๋์ผ๋ก nonce ์์ฑ โ Supabase ์ค๋ฅ
-
-**ํด๊ฒฐ**:
-
-- `supabase_flutter ^2.10.0` ์ด์ ์ฌ์ฉ (์๋ ์ฒ๋ฆฌ)
-- ๋๋ nonce ์์ด `signInWithIdToken` ํธ์ถ
-
----
-
-## ๐ ์ฑ๋ฅ ์งํ
-
-### ๋ก๊ทธ์ธ ์๋
-
-- **Before (OAuth)**: ํ๊ท 10~15์ด
-
- - ๋ธ๋ผ์ฐ์ ๋ก๋ฉ: 3~5์ด
- - ๋ฆฌ๋ค์ด๋ ํธ: 2~3์ด
- - ๋ฅ๋งํฌ ์ฒ๋ฆฌ: 2~3์ด
- - Supabase ์ธ์ฆ: 2~3์ด
-
-- **After (Native)**: ํ๊ท 3~5์ด
- - Google Sign-In: 2~3์ด
- - Supabase ์ธ์ฆ: 1~2์ด
-
-**๊ฐ์ **: 67% ์๋ ํฅ์
-
-### ์ฝ๋ ๋ณต์ก๋
-
-- **Before**:
-
- - 300+ ์ค
- - ํ๋ซํผ๋ณ ๋ถ๊ธฐ ๋ง์
- - URL Scheme ๊ด๋ฆฌ ๋ณต์ก
-
-- **After**:
- - 230 ์ค
- - ํ๋ซํผ ํต์ผ
- - ์ค์ ๋จ์ํ
-
-**๊ฐ์ **: 23% ์ฝ๋ ๊ฐ์
-
----
-
-## โจ ๋ง๋ฌด๋ฆฌ
-
-### ์๋ฃ๋ ์์
-
-- โ
Google ๋ค์ดํฐ๋ธ ๋ก๊ทธ์ธ ๊ตฌํ
-- โ
3๊ฐ ํ๋ซํผ ํต์ผ
-- โ
๋ธ๋ผ์ฐ์ ์ ๊ฑฐ
-- โ
๋ฅ๋งํฌ ์ ๊ฑฐ
-- โ
์๋ ํ๋กํ ์ฒ๋ฆฌ
-- โ
์๋ ์ธ์
์ ์ง
-- โ
ํ
์คํธ ์์ฑ (40๊ฐ)
-- โ
๋ฌธ์ ์์ฑ (์์ ๊ฐ์ด๋)
-
-### ํ
์คํธ ๋ฐฉ๋ฒ
-
-```bash
-# 1. ์์กด์ฑ ์ค์น
-flutter pub get
-
-# 2. ํ
์คํธ ์คํ
-flutter test test/unit/services/
-
-# 3. ์ค์ ์ฑ ์คํ
-# iOS
-flutter run -d iPhone
-
-# Android
-flutter run -d
-
-# Web
-flutter run -d chrome
-```
-
-### ์์ ๊ฒฐ๊ณผ
-
-1. **๋ก๊ทธ์ธ ํ๋ฉด**: "Google๋ก ๊ณ์ํ๊ธฐ" ๋ฒํผ ํด๋ฆญ
-2. **๋ค์ดํฐ๋ธ UI**: Google Sign-In UI ํ์ (๋ธ๋ผ์ฐ์ ์์)
-3. **๊ณ์ ์ ํ**: ์ฌ์ฉ์ Google ๊ณ์ ์ ํ
-4. **์ฆ์ ๋ณต๊ท**: ์ฑ์ผ๋ก ๋ฐ๋ก ์ด๋ (3~5์ด)
-5. **ํ ํ๋ฉด**: ์ฌ์ฉ์ ์ ๋ณด ํ์ (์ด๋ฆ, ์ฌ์ง)
-
----
-
-## ๐ ์ถํํฉ๋๋ค!
-
-์ด์ ์ฑ์์ **๋ธ๋ผ์ฐ์ ์์ด, ๋ฅ๋งํฌ ์์ด, ๋ค์ดํฐ๋ธ Google ๋ก๊ทธ์ธ**์ด ์๋ฒฝํ๊ฒ ์๋ํฉ๋๋ค!
-
-๋ชจ๋ ํ๋ซํผ์์ ๋์ผํ ๋ก์ง์ผ๋ก ๋น ๋ฅด๊ณ ์์ ํ๊ฒ Google ๋ก๊ทธ์ธ์ ์ ๊ณตํฉ๋๋ค.
-
-**Happy Coding!** ๐
diff --git a/REFACTORING_SUMMARY.md b/REFACTORING_SUMMARY.md
deleted file mode 100644
index aa3c997..0000000
--- a/REFACTORING_SUMMARY.md
+++ /dev/null
@@ -1,209 +0,0 @@
-# Google ๋ก๊ทธ์ธ ๋ฆฌํฉํฐ๋ง ์์ฝ
-
-## ๐ฏ ๋ชฉํ
-
-๋ชจ๋ฐ์ผ์์ ๋ธ๋ผ์ฐ์ ์ค๋ฅ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด **ํ๋ซํผ๋ณ ์ต์ ํ๋ Google ๋ก๊ทธ์ธ** ๊ตฌํ
-
-## ๐ง ๋ณ๊ฒฝ์ฌํญ
-
-### Before (๋ฌธ์ ์ )
-
-```dart
-// ๋ชจ๋ ํ๋ซํผ์์ signInWithOAuth ์ฌ์ฉ
-static Future signInWithGoogle() async {
- final response = await client.auth.signInWithOAuth(
- OAuthProvider.google,
- redirectTo: 'com.example.runnerApp://login-callback',
- authScreenLaunchMode: LaunchMode.platformDefault,
- );
- // โ ๋ชจ๋ฐ์ผ์์ ๋ธ๋ผ์ฐ์ ์ค๋ฅ ๋ฐ์
- // โ URL Scheme ์ฒ๋ฆฌ ๋ณต์ก
- // โ UX ์ ํ (์ฑ โ ๋ธ๋ผ์ฐ์ ์ ํ)
-}
-```
-
-### After (ํด๊ฒฐ์ฑ
)
-
-```dart
-// ํ๋ซํผ๋ณ ๋ถ๊ธฐ ์ฒ๋ฆฌ
-static Future signInWithGoogle() async {
- if (kIsWeb) {
- // ์น: OAuth ๋ฆฌ๋ค์ด๋ ํธ
- return await _signInWithGoogleWeb();
- } else {
- // ๋ชจ๋ฐ์ผ: ๋ค์ดํฐ๋ธ Google Sign-In
- return await _signInWithGoogleMobile();
- }
-}
-
-// ๋ชจ๋ฐ์ผ: ๋ค์ดํฐ๋ธ ๋ก๊ทธ์ธ
-static Future _signInWithGoogleMobile() async {
- // 1. Google Sign-In์ผ๋ก ์ฌ์ฉ์ ์ธ์ฆ
- final googleUser = await _googleSignIn.signIn();
-
- // 2. ID Token ํ๋
- final googleAuth = await googleUser.authentication;
- final idToken = googleAuth.idToken;
-
- // 3. Supabase์ ID Token์ผ๋ก ๋ก๊ทธ์ธ
- await client.auth.signInWithIdToken(
- provider: OAuthProvider.google,
- idToken: idToken,
- accessToken: googleAuth.accessToken,
- );
-
- // โ
์ฑ ๋ด์์ ๋ก๊ทธ์ธ ์๊ฒฐ
- // โ
๋ธ๋ผ์ฐ์ ์ค๋ฅ ์์
- // โ
๋ ๋์ UX
-}
-```
-
-## ๐ ์์ ๋ ํ์ผ
-
-### 1. ์ฝ์ด ๋ก์ง
-
-- `lib/services/google_auth_service.dart`: ์์ ๋ฆฌํฉํฐ๋ง
- - `signInWithGoogle()`: ํ๋ซํผ ๋ถ๊ธฐ ์ถ๊ฐ
- - `_signInWithGoogleWeb()`: ์น์ฉ OAuth ๋ก๊ทธ์ธ
- - `_signInWithGoogleMobile()`: ๋ชจ๋ฐ์ผ์ฉ ๋ค์ดํฐ๋ธ ๋ก๊ทธ์ธ
- - `signOut()`: ํ๋ซํผ๋ณ ๋ก๊ทธ์์ ์ฒ๋ฆฌ
-
-### 2. iOS ์ค์
-
-- `ios/Runner/Info.plist`: Google Sign-In Client ID ์ถ๊ฐ
- ```xml
- GIDClientID
- YOUR-GOOGLE-CLIENT-ID.apps.googleusercontent.com
- ```
-
-### 3. Android ์ค์
-
-- `android/app/src/main/AndroidManifest.xml`: URL Scheme์ host ์ถ๊ฐ
- ```xml
-
- ```
-
-### 4. ํ
์คํธ
-
-- `test/unit/services/google_auth_platform_test.dart`: ํ๋ซํผ ๋ถ๊ธฐ ํ
์คํธ ์ถ๊ฐ
-
-### 5. ๋ฌธ์
-
-- `GOOGLE_SIGNIN_NATIVE.md`: ๋ค์ดํฐ๋ธ ๋ก๊ทธ์ธ ๊ฐ์ด๋ (์ ๊ท)
-- `REFACTORING_SUMMARY.md`: ๋ฆฌํฉํฐ๋ง ์์ฝ (์ ๊ท)
-- `README.md`: ํ๋ซํผ๋ณ ๋ก๊ทธ์ธ ๋ฐฉ์ ์๋ด ์ถ๊ฐ
-
-## ๐งช ํ
์คํธ ๊ฒฐ๊ณผ
-
-```bash
-flutter test test/unit/services/
-```
-
-**๊ฒฐ๊ณผ**: โ
38/38 ํ
์คํธ ๋ชจ๋ ํต๊ณผ
-
-## ๐ ๊ฐ์ ํจ๊ณผ
-
-### ๋ชจ๋ฐ์ผ
-
-| ํญ๋ชฉ | Before | After |
-| --------- | ------------------ | ---------------- |
-| ์ธ์ฆ ๋ฐฉ์ | OAuth ๋ฆฌ๋ค์ด๋ ํธ | ๋ค์ดํฐ๋ธ Sign-In |
-| UX | ๋ธ๋ผ์ฐ์ ์ ํ ํ์ | ์ฑ ๋ด ์๊ฒฐ |
-| ์ค๋ฅ์จ | ๋์ (URL Scheme) | ๋ฎ์ |
-| ์๋ | ๋๋ฆผ | ๋น ๋ฆ |
-| ์์ ์ฑ | ๋ถ์์ | ์์ ์ |
-
-### ์น
-
-| ํญ๋ชฉ | Before | After |
-| --------- | ----------------- | ------------------------ |
-| ์ธ์ฆ ๋ฐฉ์ | OAuth ๋ฆฌ๋ค์ด๋ ํธ | OAuth ๋ฆฌ๋ค์ด๋ ํธ (๋์ผ) |
-| UX | ํ์ค OAuth ํ๋ก์ฐ | ํ์ค OAuth ํ๋ก์ฐ (๋์ผ) |
-| ๋ณ๊ฒฝ์ฌํญ | - | ์์ |
-
-## ๐ ํ๋ก์ฐ ๋น๊ต
-
-### ๋ชจ๋ฐ์ผ Before
-
-```
-์ฌ์ฉ์ โ Google ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ
-โ Safari/Chrome ๋ธ๋ผ์ฐ์ ์ด๋ฆผ
-โ Google ๋ก๊ทธ์ธ ํ์ด์ง
-โ ๊ณ์ ์ ํ ๋ฐ ๊ถํ ๋์
-โ URL Scheme์ผ๋ก ์ฑ ๋ณต๊ท ์๋
-โ ์ค๋ฅ ๋ฐ์: "Error while launching..."
-```
-
-### ๋ชจ๋ฐ์ผ After
-
-```
-์ฌ์ฉ์ โ Google ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ
-โ ๋ค์ดํฐ๋ธ Google ๊ณ์ ์ ํ ํ๋ฉด (์ฑ ๋ด)
-โ ๊ณ์ ์ ํ ๋ฐ ๊ถํ ๋์
-โ ID Token ํ๋
-โ Supabase ์ธ์ฆ
-โ
๋ก๊ทธ์ธ ์๋ฃ (์ฑ ๋ด์์ ์๊ฒฐ)
-```
-
-### ์น (๋ณ๊ฒฝ ์์)
-
-```
-์ฌ์ฉ์ โ Google ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ
-โ Google ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
-โ ๊ณ์ ์ ํ ๋ฐ ๊ถํ ๋์
-โ ์ฑ์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ
-โ
๋ก๊ทธ์ธ ์๋ฃ
-```
-
-## ๐ ๋ฐฐํฌ ์ค๋น์ฌํญ
-
-### 1. Google Cloud Console ์ค์ ํ์ธ
-
-- โ
iOS OAuth Client: Bundle ID `com.example.runnerApp`
-- โ
Android OAuth Client: Package name `com.example.stride_note`
-- โ
Web OAuth Client: Authorized redirect URIs ์ค์
-
-### 2. Supabase ์ค์ ํ์ธ
-
-- โ
Google Provider ํ์ฑํ
-- โ
Web OAuth Client ID/Secret ์ค์
-- โ
Redirect URLs ์ค์
-
-### 3. ์ฑ ์ค์ ํ์ธ
-
-- โ
iOS: Info.plist์ GIDClientID ์ค์
-- โ
Android: google-services.json (์ ํ์ฌํญ)
-- โ
์์กด์ฑ: google_sign_in ํจํค์ง ์ถ๊ฐ๋จ
-
-## ๐ ๊ตํ
-
-### 1. ํ๋ซํผ๋ณ ์ต์ ํ์ ์ค์์ฑ
-
-- ์น๊ณผ ๋ชจ๋ฐ์ผ์ ๋ค๋ฅธ ์ฌ์ฉ์ ๊ฒฝํ ์ ๊ณต
-- ๊ฐ ํ๋ซํผ์ ์ต์ ํ๋ ์๋ฃจ์
์ฌ์ฉ
-
-### 2. ๋ค์ดํฐ๋ธ SDK์ ์ฅ์
-
-- ๋ ๋์ UX
-- ๋ ์์ ์ ์ธ ์ธ์ฆ
-- ํ๋ซํผ๋ณ ์ต์ ํ
-
-### 3. ID Token ๊ธฐ๋ฐ ์ธ์ฆ
-
-- OAuth ๋ฆฌ๋ค์ด๋ ํธ๋ณด๋ค ๋ ์์ ์
-- URL Scheme ์ด์ ์์
-- Supabase์์ ๊ณต์ ์ง์
-
-## ๐ ๊ด๋ จ ๋ฌธ์
-
-- `GOOGLE_SIGNIN_NATIVE.md`: ๋ค์ดํฐ๋ธ ๋ก๊ทธ์ธ ์์ธ ๊ฐ์ด๋
-- `SETUP_CHECKLIST.md`: ์ค์ ์ฒดํฌ๋ฆฌ์คํธ
-- `GOOGLE_LOGIN_FIX_GUIDE.md`: ๋ฌธ์ ํด๊ฒฐ ๊ฐ์ด๋
-
----
-
-**๋ฆฌํฉํฐ๋ง ์๋ฃ ์ผ์**: 2025-10-11
-**์์
์**: AI Assistant
-**ํ
์คํธ ๊ฒฐ๊ณผ**: 38/38 ํต๊ณผ โ
diff --git a/SECURITY_AUDIT_COMPLETE.md b/SECURITY_AUDIT_COMPLETE.md
deleted file mode 100644
index c00a5af..0000000
--- a/SECURITY_AUDIT_COMPLETE.md
+++ /dev/null
@@ -1,287 +0,0 @@
-# ๐ ๋ณด์ ๊ฐ์ฌ ์๋ฃ ๋ณด๊ณ ์
-
-## ๐ ๊ฐ์ฌ ๊ฐ์
-
-**๋ ์ง**: 2025-10-11
-**๋ฒ์**: ํ๋ก์ ํธ ์ ์ฒด (์ฝ๋, ๋ฌธ์, ์ค์ ํ์ผ)
-**๋ชฉ์ **: ๋ฏผ๊ฐํ ์ ๋ณด(API ํค, ํ ํฐ) ๋
ธ์ถ ์ฌ๋ถ ํ์ธ ๋ฐ ์ ๊ฑฐ
-
----
-
-## โ
๊ฐ์ฌ ๊ฒฐ๊ณผ
-
-### 1. ๋ฌธ์ ํ์ผ (`.md`) - ์์ ์๋ฃ
-
-| ํ์ผ | ๋ฐ๊ฒฌ๋ ๋ฏผ๊ฐ ์ ๋ณด | ์กฐ์น |
-| --------------------------------- | ------------------------------ | --------------------------------- |
-| `ENV_CONFIG_GUIDE.md` | Supabase Project ID | โ
`YOUR-PROJECT-ID`๋ก ๋์ฒด |
-| `ENV_MIGRATION_COMPLETE.md` | Supabase URL, Google Client ID | โ
์์ ๊ฐ์ผ๋ก ๋์ฒด |
-| `ENV_SETUP.md` | ์ค์ API ํค ์ ์ฒด | โ
์์ ๊ฐ์ผ๋ก ๋์ฒด |
-| `CRASH_FIX.md` | Google Client ID | โ
`YOUR-GOOGLE-CLIENT-ID`๋ก ๋์ฒด |
-| `DATABASE_SETUP.md` | Supabase Project ID | โ
`YOUR-PROJECT-ID`๋ก ๋์ฒด |
-| `GOOGLE_LOGIN_FIX_GUIDE.md` | Supabase URL | โ
`YOUR-PROJECT-ID`๋ก ๋์ฒด |
-| `GOOGLE_NATIVE_LOGIN_COMPLETE.md` | Google Client ID | โ
์์ ๊ฐ์ผ๋ก ๋์ฒด |
-| `GOOGLE_SIGNIN_NATIVE.md` | Google Client ID | โ
์์ ๊ฐ์ผ๋ก ๋์ฒด |
-| `NONCE_FINAL_FIX.md` | Supabase URL | โ
์์ ๊ฐ์ผ๋ก ๋์ฒด |
-| `REFACTORING_SUMMARY.md` | Supabase URL | โ
์์ ๊ฐ์ผ๋ก ๋์ฒด |
-| `SETUP_CHECKLIST.md` | Google Client ID | โ
์์ ๊ฐ์ผ๋ก ๋์ฒด |
-| `SUPABASE_OAUTH_SETUP.md` | Supabase URL | โ
์์ ๊ฐ์ผ๋ก ๋์ฒด |
-| `SUMMARY.md` | Supabase URL | โ
์์ ๊ฐ์ผ๋ก ๋์ฒด |
-
-**์ด 13๊ฐ ํ์ผ ์์ ์๋ฃ** โ
-
-### 2. ์ฝ๋ ํ์ผ (`.dart`) - ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์์
-
-| ํ์ผ | ๋ด์ฉ | ์ํ |
-| -------------------------------------------- | -------------------------------- | ------------ |
-| `lib/config/app_config.dart` | ๊ธฐ๋ณธ๊ฐ์ผ๋ก๋ง ์ฌ์ฉ (๊ฐ๋ฐ์ฉ ํด๋ฐฑ) | โ ๏ธ ์ฃผ์ ํ์ |
-| `lib/services/supabase_oauth_validator.dart` | `AppConfig` ์ฌ์ฉ | โ
์์ |
-| `test/**/*.dart` | `AppConfig` ์ฌ์ฉ (๊ฐ๋ฐ์ฉ ๊ธฐ๋ณธ๊ฐ) | โ
์์ |
-
-**โ ๏ธ ์ฃผ์**: `app_config.dart`์ ๊ธฐ๋ณธ๊ฐ์ **๊ฐ๋ฐ ํ๊ฒฝ ์ ์ฉ**์
๋๋ค.
-ํ๋ก๋์
๋ฐฐํฌ ์ ๋ฐ๋์ `.env` ํ์ผ์ ์ค์ ๊ฐ์ ์ค์ ํด์ผ ํฉ๋๋ค.
-
-### 3. ํ๊ฒฝ ๋ณ์ ํ์ผ
-
-| ํ์ผ | ๋ด์ฉ | Git ์ํ |
-| -------------- | ---------------- | --------------------------------- |
-| `.env` | ์ค์ API ํค ํฌํจ | โ
`.gitignore`์ ๋ฑ๋ก (Git ๋ฌด์) |
-| `.env.example` | ์์ ๊ฐ๋ง ํฌํจ | โ
Git์ ์ปค๋ฐ (์์ ) |
-
-**ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ ์์ ** โ
-
-### 4. ํ๋ซํผ๋ณ ์ค์ ํ์ผ
-
-| ํ์ผ | ๋ด์ฉ | ์ํ |
-| ------------------------------------------ | ------------------ | ------- |
-| `ios/Runner/Info.plist` | GIDClientID ์ ๊ฑฐ๋จ | โ
์์ |
-| `android/app/build.gradle.kts` | ํ๊ฒฝ ๋ณ์ ์ฐธ์กฐ | โ
์์ |
-| `android/app/src/main/AndroidManifest.xml` | URL Scheme๋ง ํฌํจ | โ
์์ |
-
-**ํ๋ซํผ ์ค์ ์์ ** โ
-
----
-
-## ๐ ์ ์ฉ๋ ๋ณ๊ฒฝ์ฌํญ
-
-### ๋ฌธ์ ํ์ผ ๋ง์คํน ๊ท์น
-
-```bash
-# Before (์ค์ ํค ๋
ธ์ถ - ์์)
-SUPABASE_URL=https://abc123xyz.supabase.co
-SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOi...
-GOOGLE_CLIENT_ID=123456789-abcdefg.apps.googleusercontent.com
-
-# After (์์ ๊ฐ ์ฌ์ฉ)
-SUPABASE_URL=https://YOUR-PROJECT-ID.supabase.co
-SUPABASE_ANON_KEY=YOUR-SUPABASE-ANON-KEY
-GOOGLE_CLIENT_ID=YOUR-GOOGLE-CLIENT-ID.apps.googleusercontent.com
-```
-
-### ์๋ ์นํ ๋ช
๋ น์ด
-
-```bash
-# Supabase Project ID (์์)
-find . -name "*.md" -exec sed -i '' 's/abc123xyz/YOUR-PROJECT-ID/g' {} +
-
-# Google Client ID (์์)
-find . -name "*.md" -exec sed -i '' 's/123456789-[a-z0-9]*/YOUR-GOOGLE-CLIENT-ID/g' {} +
-
-# Supabase Anon Key (์์)
-find . -name "*.md" -exec sed -i '' 's/eyJhbGciOiJIUzI1NiIs[^"]*/YOUR-SUPABASE-ANON-KEY/g' {} +
-
-# Google Project ID (์์)
-find . -name "*.md" -exec sed -i '' 's/123456789/YOUR-GOOGLE-PROJECT-ID/g' {} +
-```
-
----
-
-## ๐ ๋ณด์ ์ ๊ฒ ์ฒดํฌ๋ฆฌ์คํธ
-
-### Git ์์ ์ฑ
-
-- [x] `.env` ํ์ผ์ด `.gitignore`์ ๋ฑ๋ก๋จ
-- [x] `.env` ํ์ผ์ด Git์์ ๋ฌด์๋จ (`git check-ignore .env` ํ์ธ)
-- [x] `.env.example`์ ์ค์ ํค ์์
-- [x] ๋ฌธ์ ํ์ผ์ ์ค์ ํค ์์
-- [x] ์ฝ๋์ ํ๋์ฝ๋ฉ๋ ํค ์์
-
-### ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ
-
-- [x] `AppConfig`๋ฅผ ํตํ ์ค์ ๊ด๋ฆฌ
-- [x] ๊ธฐ๋ณธ๊ฐ ์ ๊ณต (`.env` ์์ด๋ ์๋)
-- [x] ํ๊ฒฝ ๋ณ์ ๊ฒ์ฆ ๋ก์ง ์กด์ฌ
-- [x] ๋ฏผ๊ฐํ ์ ๋ณด ๋ง์คํน (๋๋ฒ๊ทธ ๋ก๊ทธ)
-
-### ๋ฌธ์ ๋ณด์
-
-- [x] ๋ชจ๋ `.md` ํ์ผ ์ ๊ฒ ์๋ฃ
-- [x] ์ค์ API ํค ์ ๊ฑฐ ์๋ฃ
-- [x] ์์ ๊ฐ์ผ๋ก ๋์ฒด ์๋ฃ
-- [x] `.env.example` ์์ ํ์ธ
-
-### ํ๋ซํผ ์ค์
-
-- [x] iOS `Info.plist` ์์
-- [x] Android `Manifest` ์์
-- [x] Android `build.gradle` ์์
-
----
-
-## ๐ ํ์ฌ ๋ณด์ ์ํ
-
-### โ
์์ ํ ๊ฒ๋ค
-
-1. **์ฝ๋**:
-
- - ๋ชจ๋ ํค๊ฐ `AppConfig`๋ฅผ ํตํด ๊ด๋ฆฌ
- - ํ๋์ฝ๋ฉ๋ ํค ์์
- - ๊ธฐ๋ณธ๊ฐ์ ๊ฐ๋ฐ์ฉ์ผ๋ก๋ง ์ฌ์ฉ
-
-2. **๋ฌธ์**:
-
- - ๋ชจ๋ ์ค์ ํค ์ ๊ฑฐ๋จ
- - ์์ ๊ฐ์ผ๋ก ๋์ฒด๋จ
- - ๊ฐ์ด๋๋ก์ ๊ธฐ๋ฅ ์ ์ง
-
-3. **ํ๊ฒฝ ๋ณ์**:
-
- - `.env`๋ Git์์ ๋ฌด์๋จ
- - `.env.example`์ ์์ ํ ํ
ํ๋ฆฟ
- - ์ค์ ํค๋ ๋ก์ปฌ์๋ง ์กด์ฌ
-
-4. **Git ์ด๋ ฅ**:
- - `.env`๋ ์ฒ์๋ถํฐ `.gitignore`์ ๋ฑ๋ก๋จ
- - ์ปค๋ฐ ์ด๋ ฅ์ ์ค์ ํค ์์
-
----
-
-## โ ๏ธ ์ฃผ์์ฌํญ
-
-### 1. ๊ธฐ์กด `.env` ํ์ผ ๋ณดํธ
-
-```bash
-# .env ํ์ผ์ด ์ค์๋ก ์ปค๋ฐ๋์ง ์๋๋ก ํ์ธ
-git status | grep ".env$"
-
-# ์ถ๋ ฅ์ด ์์ด์ผ ์ ์ (๋ฌด์๋๊ณ ์์)
-```
-
-### 2. ๋ฌธ์ ์
๋ฐ์ดํธ ์
-
-๋ฌธ์๋ฅผ ์์ ํ ๋ **์ ๋** ์ค์ API ํค๋ฅผ ํฌํจํ์ง ๋ง์ธ์:
-
-```markdown
-
-
-SUPABASE_URL=https://abc123xyz.supabase.co
-GOOGLE_CLIENT_ID=123456789-abcdefg.apps.googleusercontent.com
-
-
-
-SUPABASE_URL=https://YOUR-PROJECT-ID.supabase.co
-GOOGLE_CLIENT_ID=YOUR-GOOGLE-CLIENT-ID.apps.googleusercontent.com
-```
-
-### 3. ์ OAuth ์ ๊ณต์ ์ถ๊ฐ ์
-
-Kakao ๋ฑ ์๋ก์ด OAuth ์ ๊ณต์๋ฅผ ์ถ๊ฐํ ๋:
-
-1. **์ค์ ํค๋ `.env`์๋ง** ์ ์ฅ
-2. **๋ฌธ์์๋ ์์ ๊ฐ๋ง** ์ฌ์ฉ
-3. **`AppConfig`์ getter ์ถ๊ฐ**
-4. **`.env.example`์ ํ
ํ๋ฆฟ ์ถ๊ฐ**
-
----
-
-## ๐ ์ ๊ธฐ ๋ณด์ ์ ๊ฒ
-
-### ์ฃผ๊ฐ ์ ๊ฒ
-
-```bash
-# 1. ๋ฌธ์์ ์ค์ ํค ๋
ธ์ถ ํ์ธ (์์ ์ ์ค์ ํค๋ก ๊ฒ์)
-grep -r "YOUR-ACTUAL-PROJECT-ID\|YOUR-ACTUAL-CLIENT-ID" *.md
-
-# ์ถ๋ ฅ์ด ์์ด์ผ ์ ์
-
-# 2. .env๊ฐ Git์์ ๋ฌด์๋๋์ง ํ์ธ
-git check-ignore .env
-
-# ".env" ์ถ๋ ฅ๋์ด์ผ ์ ์
-
-# 3. .env.example์ด ์์ ํ์ง ํ์ธ
-grep -v "^#" .env.example | grep -v "^$" | grep "YOUR-"
-
-# ๋ชจ๋ ๊ฐ์ด "YOUR-"๋ก ์์ํด์ผ ์ ์
-```
-
-### ์๊ฐ ์ ๊ฒ
-
-```bash
-# 1. Git ์ด๋ ฅ์ .env ํ์ผ ํ์ธ
-git log --all --full-history --source --name-status --format=fuller -- .env
-
-# ์ถ๋ ฅ์ด ์์ด์ผ ์ ์ (ํ ๋ฒ๋ ์ปค๋ฐ๋ ์ ์์)
-
-# 2. ์ฝ๋์ ํ๋์ฝ๋ฉ๋ ํค ํ์ธ
-grep -r "eyJhbGciOiJIUzI1NiIs" lib/ --include="*.dart"
-
-# ์ถ๋ ฅ์ด ์์ผ๋ฉด app_config.dart์ ๊ธฐ๋ณธ๊ฐ๋ง ๋์์ผ ํจ
-```
-
----
-
-## ๐ ๊ด๋ จ ๋ฌธ์
-
-- `ENV_CONFIG_GUIDE.md` - ํ๊ฒฝ ๋ณ์ ์ค์ ๊ฐ์ด๋
-- `ENV_MIGRATION_COMPLETE.md` - ํ๊ฒฝ ๋ณ์ ๋ง์ด๊ทธ๋ ์ด์
์์ฝ
-- `.env.example` - ํ๊ฒฝ ๋ณ์ ํ
ํ๋ฆฟ
-- `README.md` - ํ๋ก์ ํธ ๊ฐ์
-
----
-
-## ๐ ๊ฐ์ฌ ์๋ฃ!
-
-**๊ฒฐ๊ณผ**: โ
๋ชจ๋ ๋ฏผ๊ฐํ ์ ๋ณด๊ฐ ์์ ํ๊ฒ ๋ณดํธ๋ฉ๋๋ค!
-
-**์ฃผ์ ์ฑ๊ณผ**:
-
-- โ
13๊ฐ ๋ฌธ์ ํ์ผ ์์
-- โ
์ค์ API ํค ์์ ์ ๊ฑฐ
-- โ
์์ ๊ฐ์ผ๋ก ์์ ํ๊ฒ ๋์ฒด
-- โ
Git ์ด๋ ฅ ์์ ํ์ธ
-- โ
ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ ์์คํ
๊ตฌ์ถ
-
-**๋ค์**: ์์ฌํ๊ณ Git์ ์ปค๋ฐํ๊ณ ํ์
ํ์ธ์! ๐
-
----
-
-## ๐ ์ปค๋ฐ ๊ถ์ฅ ์ฌํญ
-
-์ด์ ์์ ํ๊ฒ ์ปค๋ฐํ ์ ์์ต๋๋ค:
-
-```bash
-# ๋ณ๊ฒฝ์ฌํญ ํ์ธ
-git status
-
-# ๋ฌธ์ ํ์ผ ์คํ
์ด์ง
-git add *.md
-
-# ํ๊ฒฝ ๋ณ์ ์ค์ ํ์ผ ์คํ
์ด์ง
-git add lib/config/app_config.dart
-git add lib/config/supabase_config.dart
-git add lib/main.dart
-git add .env.example
-
-# .env๋ ์ ๋ ์ถ๊ฐํ์ง ์์!
-# (์ด๋ฏธ .gitignore์ ๋ฑ๋ก๋์ด ์๋์ผ๋ก ๋ฌด์๋จ)
-
-# ์ปค๋ฐ
-git commit -m "๐ ๋ณด์: API ํค๋ฅผ ํ๊ฒฝ ๋ณ์๋ก ๋ง์ด๊ทธ๋ ์ด์
๋ฐ ๋ฌธ์ ๋ฏผ๊ฐ ์ ๋ณด ์ ๊ฑฐ
-
-- ๋ชจ๋ API ํค๋ฅผ .env ํ์ผ๋ก ์ด๋
-- AppConfig ํด๋์ค๋ก ํ๊ฒฝ ๋ณ์ ์ค์ ๊ด๋ฆฌ
-- 13๊ฐ ๋ฌธ์ ํ์ผ์์ ์ค์ ํค ์ ๊ฑฐ ๋ฐ ์์ ๊ฐ์ผ๋ก ๋์ฒด
-- ํ๊ฒฝ ๋ณ์ ๊ฒ์ฆ ๋ฐ ๋ง์คํน ๋ก์ง ์ถ๊ฐ
-- .env.example ํ
ํ๋ฆฟ ์ ๊ณต"
-```
diff --git a/SETUP_CHECKLIST.md b/SETUP_CHECKLIST.md
deleted file mode 100644
index 6eb6140..0000000
--- a/SETUP_CHECKLIST.md
+++ /dev/null
@@ -1,124 +0,0 @@
-# Google ๋ก๊ทธ์ธ ์ค์ ์ฒดํฌ๋ฆฌ์คํธ
-
-## ๐ ๋น ๋ฅธ ์ค์ ๊ฐ์ด๋
-
-์ด ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ๋ฐ๋ผ Google ๋ก๊ทธ์ธ์ ์ค์ ํ์ธ์.
-
----
-
-## โ
1. Supabase ๋์๋ณด๋ ์ค์
-
-### 1.1 URL Configuration
-
-- [ ] [Supabase ๋์๋ณด๋](https://supabase.com/dashboard) ์ ์
-- [ ] ํ๋ก์ ํธ ์ ํ: `runner-app`
-- [ ] **Authentication** > **URL Configuration** ์ด๋
-- [ ] **Site URL** ์ค์ :
- ```
- http://localhost:3000
- ```
-- [ ] **Redirect URLs**์ ์ถ๊ฐ:
- ```
- com.example.runnerApp://
- com.example.runnerApp://login-callback
- https://YOUR-PROJECT-ID.supabase.co/auth/v1/callback
- ```
-- [ ] **Save** ํด๋ฆญ
-
-### 1.2 Google Provider ์ค์ (๋์ค์)
-
-- [ ] **Authentication** > **Providers** ์ด๋
-- [ ] **Google** Provider ์ฐพ๊ธฐ
-- [ ] **Enable Sign in with Google** ํ ๊ธ ์ผ๊ธฐ (Client ID/Secret ํ์ - ๋ค์ ๋จ๊ณ์์ ์ป์)
-
----
-
-## โ
2. Google Cloud Console ์ค์
-
-### 2.1 OAuth ๋์ ํ๋ฉด
-
-- [ ] [Google Cloud Console](https://console.cloud.google.com/) ์ ์
-- [ ] **APIs & Services** > **OAuth consent screen** ์ด๋
-- [ ] User Type: **External** ์ ํ
-- [ ] ์ฑ ์ ๋ณด ์
๋ ฅ:
- - App name: `StrideNote`
- - User support email: (๋ณธ์ธ ์ด๋ฉ์ผ)
- - Developer contact email: (๋ณธ์ธ ์ด๋ฉ์ผ)
-- [ ] **Save and Continue** ํด๋ฆญ
-- [ ] Scopes: ๊ธฐ๋ณธ๊ฐ ์ ์ง ํ **Save and Continue**
-- [ ] Test users: ๋ณธ์ธ ์ด๋ฉ์ผ ์ถ๊ฐ ํ **Save and Continue**
-
-### 2.2 Web ํด๋ผ์ด์ธํธ ์์ฑ (Supabase์ฉ)
-
-- [ ] **APIs & Services** > **Credentials** ์ด๋
-- [ ] **+ CREATE CREDENTIALS** > **OAuth 2.0 Client ID** ์ ํ
-- [ ] Application type: **Web application**
-- [ ] Name: `StrideNote Web (Supabase)`
-- [ ] **Authorized redirect URIs**์ ์ถ๊ฐ:
- ```
- https://YOUR-PROJECT-ID.supabase.co/auth/v1/callback
- ```
-- [ ] **CREATE** ํด๋ฆญ
-- [ ] **Client ID** ๋ณต์ฌ โ ๋ฉ๋ชจ์ฅ์ ์ ์ฅ
-- [ ] **Client Secret** ๋ณต์ฌ โ ๋ฉ๋ชจ์ฅ์ ์ ์ฅ
-
-### 2.3 iOS ํด๋ผ์ด์ธํธ ์์ฑ (์ ํ์ฌํญ)
-
-- [ ] **+ CREATE CREDENTIALS** > **OAuth 2.0 Client ID** ์ ํ
-- [ ] Application type: **iOS**
-- [ ] Name: `StrideNote iOS`
-- [ ] Bundle ID: `com.example.runnerApp`
-- [ ] **CREATE** ํด๋ฆญ
-
----
-
-## โ
3. Supabase์ Google ์ค์ ์
๋ ฅ
-
-- [ ] Supabase ๋์๋ณด๋๋ก ๋์๊ฐ๊ธฐ
-- [ ] **Authentication** > **Providers** > **Google** ์ ํ
-- [ ] **Client ID (for OAuth)**: 2.2์์ ๋ณต์ฌํ Web Client ID ์
๋ ฅ
-- [ ] **Client Secret (for OAuth)**: 2.2์์ ๋ณต์ฌํ Web Client Secret ์
๋ ฅ
-- [ ] **Save** ํด๋ฆญ
-
----
-
-## โ
4. ์ฑ ํ
์คํธ
-
-- [ ] ์ฑ ์์ ํ ์ข
๋ฃ
-- [ ] ์ฑ ์ฌ์คํ
-- [ ] Google ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ
-- [ ] Google ๊ณ์ ์ ํ ํ๋ฉด ํ์ธ
-- [ ] ๋ก๊ทธ์ธ ์ฑ๊ณต ํ์ธ
-
----
-
-## ๐ฏ ์ฑ๊ณต ์ ์์ ๋์
-
-1. โ
Google ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ
-2. โ
Safari/Chrome์ด ์ด๋ฆฌ๋ฉด์ Google ๋ก๊ทธ์ธ ํ์ด์ง ํ์
-3. โ
Google ๊ณ์ ์ ํ
-4. โ
๊ถํ ๋์ ํ๋ฉด
-5. โ
์ฑ์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ
-6. โ
ํ ํ๋ฉด์ผ๋ก ์ด๋
-
----
-
-## โ ์คํจ ์ ํ์ธ ์ฌํญ
-
-### "Error while launching" ์ค๋ฅ
-
-โ Supabase Google Provider๊ฐ ํ์ฑํ๋์ง ์์๊ฑฐ๋ Client ID/Secret์ด ์๋ชป๋จ
-
-### "redirect_uri_mismatch" ์ค๋ฅ
-
-โ Google Cloud Console์ Authorized redirect URIs์ Supabase ์ฝ๋ฐฑ URL ์ถ๊ฐ ํ์
-
-### "access_denied" ์ค๋ฅ
-
-โ OAuth ๋์ ํ๋ฉด์ด Testing ์ํ์ด๊ณ ํ
์คํธ ์ฌ์ฉ์๋ก ์ถ๊ฐ๋์ง ์์
-
----
-
-## ๐ ๋์์ด ํ์ํ๋ฉด
-
-`GOOGLE_LOGIN_FIX_GUIDE.md` ํ์ผ์์ ๋ ์์ธํ ์ค๋ช
์ ํ์ธํ์ธ์.
diff --git a/SNAKE_CASE_FIX.md b/SNAKE_CASE_FIX.md
deleted file mode 100644
index be12b54..0000000
--- a/SNAKE_CASE_FIX.md
+++ /dev/null
@@ -1,211 +0,0 @@
-# ๐ง Snake Case ํ๋ ๋งคํ ๋ฌธ์ ํด๊ฒฐ
-
-## โ ๋ฐ์ํ๋ ๋ฌธ์
-
-```
-[UserProfileService] ์ฌ์ฉ์ ํ๋กํ ๊ฐ์ ธ์ค๊ธฐ ์ค๋ฅ: type 'Null' is not a subtype of type 'String' in type cast
-[UserProfileService] _TypeError (type 'Null' is not a subtype of type 'String' in type cast)
-[UserProfileService] #0 _$UserProfileFromJson (package:stride_note/models/user_profile.g.dart:24:47)
-```
-
-**์คํ ํธ๋ ์ด์ค**: `user_profile.g.dart:24` โ `createdAt: DateTime.parse(json['createdAt'] as String)`
-
----
-
-## ๐ ๊ทผ๋ณธ ์์ธ
-
-### ํ๋ ๋ค์ด๋ฐ ๋ถ์ผ์น
-
-| ์์น | ๋ค์ด๋ฐ ๊ท์น | ์์ |
-| --------------- | ---------------- | ---------------------------- |
-| **Dart ๋ชจ๋ธ** | camelCase | `createdAt`, `displayName` |
-| **PostgreSQL** | snake_case | `created_at`, `display_name` |
-| **JSON ์ง๋ ฌํ** | camelCase (๊ธฐ๋ณธ) | `createdAt` (โ ํ๋ฆผ) |
-
-### ๋ฌธ์ ๋ฐ์ ํ๋ฆ
-
-```
-1. Supabase์์ ๋ฐ์ดํฐ ์กฐํ
- โ { "created_at": "2025-01-01", "display_name": "User" }
-
-2. UserProfile.fromJson() ํธ์ถ
- โ json['createdAt']๋ฅผ ์ฐพ์
- โ null ๋ฐํ (์ค์ ํค๋ 'created_at')
-
-3. DateTime.parse(null as String)
- โ โ type 'Null' is not a subtype of type 'String'
-```
-
----
-
-## โ
ํด๊ฒฐ ๋ฐฉ๋ฒ
-
-### 1. **JsonSerializable์ fieldRename ์ถ๊ฐ**
-
-**ํ์ผ**: `lib/models/user_profile.dart`
-
-**Before**:
-
-```dart
-@JsonSerializable()
-class UserProfile {
- final DateTime createdAt;
- final DateTime updatedAt;
- final String? displayName;
- final String? avatarUrl;
- // ...
-}
-```
-
-**After**:
-
-```dart
-@JsonSerializable(fieldRename: FieldRename.snake) // โ
snake_case ์๋ ๋ณํ
-class UserProfile {
- final DateTime createdAt; // โ created_at
- final DateTime updatedAt; // โ updated_at
- final String? displayName; // โ display_name
- final String? avatarUrl; // โ avatar_url
- // ...
-}
-```
-
----
-
-### 2. **JSON ์ง๋ ฌํ ์ฝ๋ ์ฌ์์ฑ**
-
-```bash
-flutter pub run build_runner build --delete-conflicting-outputs
-```
-
-**์์ฑ๋ ์ฝ๋** (`user_profile.g.dart`):
-
-**Before**:
-
-```dart
-UserProfile _$UserProfileFromJson(Map json) => UserProfile(
- createdAt: DateTime.parse(json['createdAt'] as String), // โ ํ๋ฆผ
- updatedAt: DateTime.parse(json['updatedAt'] as String), // โ ํ๋ฆผ
- displayName: json['displayName'] as String?, // โ ํ๋ฆผ
- avatarUrl: json['avatarUrl'] as String?, // โ ํ๋ฆผ
-);
-```
-
-**After**:
-
-```dart
-UserProfile _$UserProfileFromJson(Map json) => UserProfile(
- createdAt: DateTime.parse(json['created_at'] as String), // โ
๋ง์
- updatedAt: DateTime.parse(json['updated_at'] as String), // โ
๋ง์
- displayName: json['display_name'] as String?, // โ
๋ง์
- avatarUrl: json['avatar_url'] as String?, // โ
๋ง์
-);
-```
-
----
-
-### 3. **์ถ๊ฐ Null ์์ ๊ฒ์ฆ**
-
-**ํ์ผ**: `lib/services/user_profile_service.dart`
-
-```dart
-// null ์์ ๊ฒ์ฆ: ํ์ ํ๋ ํ์ธ
-if (response['id'] == null ||
- response['email'] == null ||
- response['created_at'] == null || // โ
์ถ๊ฐ
- response['updated_at'] == null) { // โ
์ถ๊ฐ
- developer.log(
- 'โ ๏ธ ํ๋กํ ๋ฐ์ดํฐ ๋ถ์์ : id=${response['id']}, email=${response['email']}, '
- 'created_at=${response['created_at']}, updated_at=${response['updated_at']}',
- name: 'UserProfileService',
- );
- return null;
-}
-```
-
----
-
-## ๐ Before vs After
-
-### Before (๋ฌธ์ ๋ฐ์)
-
-```json
-// Supabase ์๋ต
-{
- "id": "xxx",
- "email": "user@example.com",
- "created_at": "2025-01-01T00:00:00Z",
- "updated_at": "2025-01-01T00:00:00Z",
- "display_name": "User",
- "avatar_url": "https://..."
-}
-
-// Dart๊ฐ ์ฐพ๋ ํค
-json['createdAt'] โ null โ
-json['updatedAt'] โ null โ
-json['displayName'] โ null โ
-json['avatarUrl'] โ null โ
-
-// ๊ฒฐ๊ณผ
-DateTime.parse(null as String) โ โ ํ์
์ค๋ฅ!
-```
-
-### After (ํด๊ฒฐ)
-
-```json
-// Supabase ์๋ต (๋์ผ)
-{
- "id": "xxx",
- "email": "user@example.com",
- "created_at": "2025-01-01T00:00:00Z",
- "updated_at": "2025-01-01T00:00:00Z",
- "display_name": "User",
- "avatar_url": "https://..."
-}
-
-// Dart๊ฐ ์ฐพ๋ ํค (์์ ๋จ)
-json['created_at'] โ "2025-01-01T00:00:00Z" โ
-json['updated_at'] โ "2025-01-01T00:00:00Z" โ
-json['display_name'] โ "User" โ
-json['avatar_url'] โ "https://..." โ
-
-// ๊ฒฐ๊ณผ
-DateTime.parse("2025-01-01T00:00:00Z") โ โ
์ฑ๊ณต!
-```
-
----
-
-## ๐งช ํ
์คํธ ๊ฒฐ๊ณผ
-
-```bash
-โ
flutter analyze: No issues found!
-โ
flutter test: 9/9 UserProfile tests passed
-โ
๋ชจ๋ ์ฝ๋ ์ปดํ์ผ ์ฑ๊ณต
-```
-
----
-
-## ๐ FieldRename ์ต์
-
-`@JsonSerializable`์ `fieldRename` ์ต์
:
-
-| ์ต์
| ์ค๋ช
| ์์ |
-| -------------------- | -------------------- | -------------------------- |
-| `FieldRename.none` | ๋ณํ ์์ (๊ธฐ๋ณธ๊ฐ) | `createdAt` โ `createdAt` |
-| `FieldRename.snake` | snake_case๋ก ๋ณํ โ
| `createdAt` โ `created_at` |
-| `FieldRename.kebab` | kebab-case๋ก ๋ณํ | `createdAt` โ `created-at` |
-| `FieldRename.pascal` | PascalCase๋ก ๋ณํ | `createdAt` โ `CreatedAt` |
-
-**PostgreSQL/Supabase ์ฌ์ฉ ์**: **`FieldRename.snake` ํ์!**
-
----
-
-## ๐ ๋ค๋ฅธ ๋ชจ๋ธ์๋ ์ ์ฉ
-
-### running_session.dart
-
-์ด ๋ชจ๋ธ๋ ๋์ผํ ๋ฌธ์ ๊ฐ ์์ ์ ์์ต๋๋ค!
-
-
-
-/Users/nhn/Desktop/DEV/flutter-workspace/runner_app/lib/models/running_session.dart
diff --git a/SUMMARY.md b/SUMMARY.md
deleted file mode 100644
index cac3810..0000000
--- a/SUMMARY.md
+++ /dev/null
@@ -1,173 +0,0 @@
-# Google ๋ก๊ทธ์ธ ๋ฌธ์ ํด๊ฒฐ ์์ฝ
-
-## ๐ ๋ฌธ์ ์์ฝ
-
-๊ตฌ๊ธ ๋ก๊ทธ์ธ ์ ๋ค์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค:
-
-```
-PlatformException(Error, Error while launching https://YOUR-PROJECT-ID.supabase.co/auth/v1/authorize?provider=google...)
-```
-
-## โ
์์ ๋ ์ฌํญ
-
-### 1. URL Scheme ํต์ผ
-
-- โ ์ด์ : `com.example.runnerApp://`
-- โ
์์ : `com.example.runnerApp://`
-- ๐ ์์ ํ์ผ:
- - `ios/Runner/Info.plist`
- - `lib/services/google_auth_service.dart`
-
-### 2. Bundle ID ํต์ผ
-
-- โ ์ด์ : `runner_app` (iOS)
-- โ
์์ : `stride_note` (iOS)
-- ๐ ์์ ํ์ผ:
- - `ios/Runner/Info.plist`
-
-### 3. ์๋ฌ ์ฒ๋ฆฌ ๊ฐ์
-
-- โ
๋ ๊ตฌ์ฒด์ ์ธ ์ค๋ฅ ๋ฉ์์ง ์ถ๊ฐ
-- โ
OAuth ์ค์ ๊ฒ์ฆ ๋๊ตฌ ์ถ๊ฐ
-- โ
์ค์ ํ์ธ์ฌํญ ์๋ ์๋ด
-- ๐ ์ ๊ท ํ์ผ:
- - `lib/services/supabase_oauth_validator.dart`
-
-### 4. ํ
์คํธ ์ถ๊ฐ
-
-- โ
URL Scheme ์ผ๊ด์ฑ ๊ฒ์ฆ ํ
์คํธ
-- โ
Bundle ID ํ์ ๊ฒ์ฆ ํ
์คํธ
-- โ
OAuth URL ๊ตฌ์ฑ ๊ฒ์ฆ ํ
์คํธ
-- ๐ ์ ๊ท ํ์ผ:
- - `test/unit/services/google_auth_config_test.dart`
- - `test/unit/services/google_oauth_url_test.dart`
-
-### 5. ๋ฌธ์ํ
-
-- โ
์์ธํ ์ค์ ๊ฐ์ด๋ ์์ฑ
-- โ
๋น ๋ฅธ ์ฒดํฌ๋ฆฌ์คํธ ์์ฑ
-- โ
README ์
๋ฐ์ดํธ
-- ๐ ์ ๊ท ํ์ผ:
- - `GOOGLE_LOGIN_FIX_GUIDE.md`
- - `SETUP_CHECKLIST.md`
- - `SUPABASE_OAUTH_SETUP.md`
-
-## ๐ง ํ์ํ ์ถ๊ฐ ์์
-
-### 1. Supabase ๋์๋ณด๋ ์ค์ (ํ์)
-
-**Authentication > URL Configuration**:
-
-```
-Site URL: http://localhost:3000
-
-Redirect URLs:
-- com.example.runnerApp://
-- com.example.runnerApp://login-callback
-- https://YOUR-PROJECT-ID.supabase.co/auth/v1/callback
-```
-
-**Authentication > Providers > Google**:
-
-- โ
Enable Sign in with Google
-- Client ID (for OAuth): (Google Cloud Console์์ ์์ฑ)
-- Client Secret (for OAuth): (Google Cloud Console์์ ์์ฑ)
-
-### 2. Google Cloud Console ์ค์ (ํ์)
-
-**OAuth ๋์ ํ๋ฉด**:
-
-- User Type: External
-- App name: StrideNote
-- Support email: (๋ณธ์ธ ์ด๋ฉ์ผ)
-- Developer contact email: (๋ณธ์ธ ์ด๋ฉ์ผ)
-
-**OAuth 2.0 Client ID ์์ฑ (Web)**:
-
-- Application type: Web application
-- Name: StrideNote Web (Supabase)
-- Authorized redirect URIs:
- ```
- https://YOUR-PROJECT-ID.supabase.co/auth/v1/callback
- ```
-
-**OAuth 2.0 Client ID ์์ฑ (iOS - ์ ํ์ฌํญ)**:
-
-- Application type: iOS
-- Name: StrideNote iOS
-- Bundle ID: `com.example.runnerApp`
-
-## ๐ ํ
์คํธ ๊ฒฐ๊ณผ
-
-### ์ ์ฒด ํ
์คํธ ํต๊ณผ
-
-```bash
-flutter test test/unit/services/
-```
-
-โ
35๊ฐ ํ
์คํธ ๋ชจ๋ ํต๊ณผ
-
-### ์ฃผ์ ๊ฒ์ฆ ํญ๋ชฉ
-
-- โ
URL Scheme ์ผ๊ด์ฑ
-- โ
Bundle ID ํ์
-- โ
OAuth URL ๊ตฌ์ฑ
-- โ
Supabase ์ค์
-- โ
Google OAuth ํด๋ผ์ด์ธํธ ID ํ์
-
-## ๐ฏ ๋ค์ ๋จ๊ณ
-
-1. **Supabase ๋์๋ณด๋ ์ค์ **: `SETUP_CHECKLIST.md` ์ฐธ์กฐ
-2. **Google Cloud Console ์ค์ **: `SETUP_CHECKLIST.md` ์ฐธ์กฐ
-3. **์ฑ ํ
์คํธ**: Google ๋ก๊ทธ์ธ ์๋
-4. **์ฑ๊ณต ํ์ธ**: ๋ก๊ทธ์ธ ํ ํ ํ๋ฉด ์ด๋ ํ์ธ
-
-## ๐ ์ฐธ๊ณ ๋ฌธ์
-
-- **๋น ๋ฅธ ์ค์ **: `SETUP_CHECKLIST.md`
-- **์์ธ ๊ฐ์ด๋**: `GOOGLE_LOGIN_FIX_GUIDE.md`
-- **OAuth ์ค์ **: `SUPABASE_OAUTH_SETUP.md`
-- **๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ **: `DATABASE_SETUP.md`
-- **ํ๊ฒฝ ๋ณ์ ์ค์ **: `ENV_SETUP.md`
-
-## ๐ ๋ฌธ์ ํด๊ฒฐ
-
-### ์ฌ์ ํ "Error while launching" ์ค๋ฅ ๋ฐ์
-
-โ Supabase Google Provider๊ฐ ํ์ฑํ๋์ง ์์๊ฑฐ๋ Client ID/Secret์ด ์ค์ ๋์ง ์์
-
-### "redirect_uri_mismatch" ์ค๋ฅ
-
-โ Google Cloud Console์ Authorized redirect URIs์ Supabase ์ฝ๋ฐฑ URL ์ถ๊ฐ ํ์
-
-### "access_denied" ์ค๋ฅ
-
-โ OAuth ๋์ ํ๋ฉด์ด Testing ์ํ์ด๊ณ ํ
์คํธ ์ฌ์ฉ์๋ก ์ถ๊ฐ๋์ง ์์
-
-## ๐ก ํ
-
-### ๊ฐ๋ฐ ์ค
-
-- OAuth ๋์ ํ๋ฉด์ **Testing** ์ํ๋ก ์ ์ง
-- ๋ณธ์ธ ์ด๋ฉ์ผ์ ํ
์คํธ ์ฌ์ฉ์๋ก ์ถ๊ฐ
-
-### ๋ฐฐํฌ ์
-
-- OAuth ๋์ ํ๋ฉด์ **In Production**์ผ๋ก ๋ณ๊ฒฝ
-- Supabase Site URL์ ์ค์ ๋๋ฉ์ธ์ผ๋ก ๋ณ๊ฒฝ
-- Google Cloud Console์ Authorized redirect URIs์ ๋ฐฐํฌ ๋๋ฉ์ธ ์ถ๊ฐ
-
-## ๐ ์ถ๊ฐ ๋์
-
-์ค์ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด:
-
-1. ๊ฐ ๋จ๊ณ๋ฅผ ์ฒดํฌ๋ฆฌ์คํธ๋๋ก ๋ค์ ํ์ธ
-2. Supabase ๋์๋ณด๋์ Authentication ๋ก๊ทธ ํ์ธ
-3. Google Cloud Console์ Credentials ์ค์ ํ์ธ
-4. ์ฑ ๋ก๊ทธ ์ฝ์์์ ์์ธ ์ค๋ฅ ๋ฉ์์ง ํ์ธ
-
----
-
-**์์
์๋ฃ ์ผ์**: 2025-10-11
-**์์
๋ด์ฉ**: Google ๋ก๊ทธ์ธ ์ค์ ๋ฌธ์ ํด๊ฒฐ ๋ฐ ๋ฌธ์ํ
-**ํ
์คํธ ๊ฒฐ๊ณผ**: 35/35 ํ
์คํธ ํต๊ณผ โ
diff --git a/SUPABASE_OAUTH_SETUP.md b/SUPABASE_OAUTH_SETUP.md
deleted file mode 100644
index 7f8946e..0000000
--- a/SUPABASE_OAUTH_SETUP.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# Supabase OAuth ์ค์ ๊ฐ์ด๋
-
-## ๐ง ํ์ฌ ๋ฌธ์ ์ํฉ
-
-Google ๋ก๊ทธ์ธ ์ ๋ค์๊ณผ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ณ ์์ต๋๋ค:
-
-```
-PlatformException(Error, Error while launching https://YOUR-PROJECT-ID.supabase.co/auth/v1/authorize?provider=google&redirect_to=com.example.runnerApp%3A%2F%2F&flow_type=pkce&code_challenge=...)
-```
-
-## ๐ ํด๊ฒฐ ๋ฐฉ๋ฒ
-
-### 1. Supabase ๋์๋ณด๋ ์ค์
-
-#### 1.1 Authentication > URL Configuration
-
-1. Supabase ๋์๋ณด๋์ ๋ก๊ทธ์ธ
-2. ํ๋ก์ ํธ ์ ํ: `YOUR-PROJECT-ID`
-3. **Authentication** > **URL Configuration** ์ด๋
-
-#### 1.2 Site URL ์ค์
-
-```
-https://your-app-domain.com
-```
-
-๋๋ ๊ฐ๋ฐ์ฉ์ผ๋ก:
-
-```
-http://localhost:3000
-```
-
-#### 1.3 Redirect URLs ์ค์
-
-๋ค์ URL๋ค์ ์ถ๊ฐ:
-
-```
-com.example.runnerApp://
-https://YOUR-PROJECT-ID.supabase.co/auth/v1/callback
-https://your-app-domain.com/auth/callback
-```
-
-### 2. Google OAuth Provider ์ค์
-
-#### 2.1 Authentication > Providers
-
-1. **Authentication** > **Providers** ์ด๋
-2. **Google** Provider ํ์ฑํ
-
-#### 2.2 Google OAuth ์ค์
-
-- **Client ID**: Google Cloud Console์์ ์์ฑํ OAuth 2.0 ํด๋ผ์ด์ธํธ ID
-- **Client Secret**: Google Cloud Console์์ ์์ฑํ OAuth 2.0 ํด๋ผ์ด์ธํธ ์ํฌ๋ฆฟ
-
-### 3. Google Cloud Console ์ค์
-
-#### 3.1 OAuth 2.0 ํด๋ผ์ด์ธํธ ID ์์ฑ
-
-1. [Google Cloud Console](https://console.cloud.google.com/) ์ ์
-2. ํ๋ก์ ํธ ์ ํ ๋๋ ์ ํ๋ก์ ํธ ์์ฑ
-3. **APIs & Services** > **Credentials** ์ด๋
-4. **Create Credentials** > **OAuth 2.0 Client ID** ์ ํ
-
-#### 3.2 ์ ํ๋ฆฌ์ผ์ด์
์ ํ ์ค์
-
-- **Application type**: `iOS` ๋๋ `Android` ์ ํ
-- **Bundle ID**: `com.example.runnerApp`
-
-#### 3.3 Authorized redirect URIs ์ค์
-
-๋ค์ URI๋ค์ ์ถ๊ฐ:
-
-```
-https://YOUR-PROJECT-ID.supabase.co/auth/v1/callback
-com.example.runnerApp://
-```
-
-### 4. ํ์ฌ ์ฑ ์ค์ ํ์ธ
-
-#### 4.1 Bundle ID
-
-- **Android**: `com.example.runnerApp` (build.gradle.kts)
-- **iOS**: `com.example.runnerApp` (Info.plist)
-
-#### 4.2 URL Scheme
-
-- **Android**: `com.example.runnerApp` (AndroidManifest.xml)
-- **iOS**: `com.example.runnerApp` (Info.plist)
-
-#### 4.3 Google OAuth Client ID
-
-- **iOS**: `com.googleusercontent.apps.YOUR-GOOGLE-CLIENT-ID` (Info.plist)
-
-## ๐ ์ค์ ๊ฒ์ฆ
-
-์ฑ์ ์คํํ๋ฉด ์ฝ์์ ๋ค์๊ณผ ๊ฐ์ ๊ฒ์ฆ ๋ก๊ทธ๊ฐ ์ถ๋ ฅ๋ฉ๋๋ค:
-
-```
-=== Supabase OAuth ์ค์ ๊ฒ์ฆ ์์ ===
-โ
Supabase ํด๋ผ์ด์ธํธ ์ด๊ธฐํ ์๋ฃ
-โ
Supabase URL: https://YOUR-PROJECT-ID.supabase.co
-โ
Anonymous Key: YOUR-SUPABASE-ANON-KEY
-โ
OAuth URL ๊ตฌ์ฑ: https://YOUR-PROJECT-ID.supabase.co/auth/v1/authorize?provider=google&redirect_to=com.example.runnerApp%3A%2F%2F&flow_type=pkce
-=== ์ค์ ํ์ธ ํ์์ฌํญ ===
-```
-
-## ๐จ ๋ฌธ์ ํด๊ฒฐ ์ฒดํฌ๋ฆฌ์คํธ
-
-- [ ] Supabase Site URL์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ๋์ด ์๋๊ฐ?
-- [ ] Supabase Redirect URLs์ `com.example.runnerApp://`๊ฐ ์ถ๊ฐ๋์ด ์๋๊ฐ?
-- [ ] Google OAuth Provider๊ฐ Supabase์์ ํ์ฑํ๋์ด ์๋๊ฐ?
-- [ ] Google Cloud Console์์ ์ฌ๋ฐ๋ฅธ Bundle ID๊ฐ ์ค์ ๋์ด ์๋๊ฐ?
-- [ ] Google Cloud Console์์ Authorized redirect URIs๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ๋์ด ์๋๊ฐ?
-- [ ] Google OAuth Client ID์ Secret์ด Supabase์ ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ๋์ด ์๋๊ฐ?
-
-## ๐ ์ถ๊ฐ ๋์
-
-์ค์ ํ์๋ ๋ฌธ์ ๊ฐ ์ง์๋๋ฉด:
-
-1. Supabase ๋์๋ณด๋์ Authentication ๋ก๊ทธ ํ์ธ
-2. Google Cloud Console์ OAuth ๋์ ํ๋ฉด ์ค์ ํ์ธ
-3. ์ฑ์ Bundle ID์ Google Cloud Console์ Bundle ID ์ผ์น ํ์ธ
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 9263f2b..130f5d2 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -31,6 +31,11 @@
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" /> -->
+
+
+
(
+ builder: (context, authProvider, child) {
+ // Provider์ ์ํ๋ฅผ ๊ตฌ๋
ํ์ฌ UI ์
๋ฐ์ดํธ
+ final user = authProvider.currentUser;
+
+ return Scaffold(
+ body: user != null
+ ? HomeContent(user: user)
+ : LoginPrompt(),
+ );
+ },
+ );
+ }
+}
+```
+
+**๊ท์น**:
+
+- โ
Provider๋ฅผ ํตํด์๋ง ์ํ ์ ๊ทผ
+- โ
Service๋ฅผ ์ง์ ํธ์ถํ์ง ์์
+- โ
๋น์ฆ๋์ค ๋ก์ง ํฌํจํ์ง ์์
+
+---
+
+#### Provider Layer
+
+**์ฑ
์**: ์ํ ๊ด๋ฆฌ, ๋น์ฆ๋์ค ๋ก์ง ์กฐ์จ, ๋ฆฌ์ค๋ ์๋ฆผ
+
+```dart
+// ์์: AuthProvider
+class AuthProvider extends ChangeNotifier {
+ User? _currentUser;
+ bool _isLoading = false;
+
+ // Getter
+ User? get currentUser => _currentUser;
+ bool get isLoading => _isLoading;
+
+ // ๋ก๊ทธ์ธ (Service ํธ์ถ)
+ Future signIn(String email, String password) async {
+ _isLoading = true;
+ notifyListeners();
+
+ try {
+ final user = await AuthService.signInWithEmail(
+ email: email,
+ password: password,
+ );
+ _currentUser = user;
+ } catch (e) {
+ rethrow;
+ } finally {
+ _isLoading = false;
+ notifyListeners();
+ }
+ }
+}
+```
+
+**๊ท์น**:
+
+- โ
ChangeNotifier ์์
+- โ
Service Layer ํธ์ถ
+- โ
์ํ ๋ณ๊ฒฝ ์ notifyListeners() ํธ์ถ
+- โ
๋น๊ณต๊ฐ ๋ณ์ + public getter
+
+---
+
+#### Service Layer
+
+**์ฑ
์**: API ํต์ , ๋ฐ์ดํฐ ์ฒ๋ฆฌ, ์ธ๋ถ ์๋น์ค ์ฐ๋
+
+```dart
+// ์์: AuthService
+class AuthService {
+ static final SupabaseClient _supabase = Supabase.instance.client;
+
+ /// ์ด๋ฉ์ผ ๋ก๊ทธ์ธ
+ static Future signInWithEmail({
+ required String email,
+ required String password,
+ }) async {
+ try {
+ final response = await _supabase.auth.signInWithPassword(
+ email: email,
+ password: password,
+ );
+ return response.user;
+ } on AuthException catch (e) {
+ throw Exception('๋ก๊ทธ์ธ ์คํจ: ${e.message}');
+ }
+ }
+
+ /// ๋ก๊ทธ์์
+ static Future signOut() async {
+ await _supabase.auth.signOut();
+ }
+}
+```
+
+**๊ท์น**:
+
+- โ
static ๋ฉ์๋ ์ฌ์ฉ (์ํ ์์)
+- โ
์์ ํจ์๋ก ๊ตฌํ (๋ถ์์ฉ ์ต์ํ)
+- โ
์๋ฌ ํธ๋ค๋ง ํฌํจ
+- โ
Model ๊ฐ์ฒด ๋ฐํ
+
+---
+
+#### Model Layer
+
+**์ฑ
์**: ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ ์, JSON ์ง๋ ฌํ/์ญ์ง๋ ฌํ
+
+```dart
+// ์์: UserProfile
+@JsonSerializable()
+class UserProfile {
+ final String id;
+ final String email;
+ final String? displayName;
+ final String? avatarUrl;
+ final String fitnessLevel;
+ final DateTime createdAt;
+ final DateTime updatedAt;
+
+ UserProfile({
+ required this.id,
+ required this.email,
+ this.displayName,
+ this.avatarUrl,
+ required this.fitnessLevel,
+ required this.createdAt,
+ required this.updatedAt,
+ });
+
+ // JSON ์ง๋ ฌํ
+ factory UserProfile.fromJson(Map json) =>
+ _$UserProfileFromJson(json);
+
+ Map toJson() => _$UserProfileToJson(this);
+}
+```
+
+**๊ท์น**:
+
+- โ
๋ถ๋ณ ๊ฐ์ฒด (final ํ๋)
+- โ
@JsonSerializable ์ด๋
ธํ
์ด์
+- โ
fromJson / toJson ๋ฉ์๋
+- โ
๋น์ฆ๋์ค ๋ก์ง ํฌํจํ์ง ์์
+
+---
+
+## ๋ฐ์ดํฐ ํ๋ก์ฐ
+
+### ์ฌ์ฉ์ ์ก์
โ UI ์
๋ฐ์ดํธ ํ๋ก์ฐ
+
+```
+1. ์ฌ์ฉ์ ์ก์
(User Action)
+ ์: ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ
+ โ
+2. View Layer - ์ด๋ฒคํธ ์์
+ ์: onPressed: () => authProvider.signIn(email, password)
+ โ
+3. Provider Layer - ์ํ ๋ณ๊ฒฝ ์์
+ ์: _isLoading = true; notifyListeners();
+ โ
+4. Service Layer - API ํธ์ถ
+ ์: AuthService.signInWithEmail(...)
+ โ
+5. External Services - ์๊ฒฉ ์์ฒญ
+ ์: Supabase API ํธ์ถ
+ โ
+6. Service Layer - ์๋ต ์์
+ ์: return response.user;
+ โ
+7. Model Layer - ๋ฐ์ดํฐ ๋ณํ
+ ์: User.fromJson(json)
+ โ
+8. Provider Layer - ์ํ ์
๋ฐ์ดํธ
+ ์: _currentUser = user; notifyListeners();
+ โ
+9. View Layer - UI ์๋ ์ฌ๋ ๋๋ง
+ ์: Consumer๊ฐ rebuild ํธ๋ฆฌ๊ฑฐ
+ โ
+10. ์ฌ์ฉ์์๊ฒ ๊ฒฐ๊ณผ ํ์
+ ์: ํ ํ๋ฉด์ผ๋ก ์ด๋
+```
+
+### ์ค์๊ฐ ๋ฐ์ดํฐ ์คํธ๋ฆผ ํ๋ก์ฐ
+
+```
+1. Service Layer - ์คํธ๋ฆผ ๊ตฌ๋
+ ์: Geolocator.getPositionStream()
+ โ
+2. Service Layer - ๋ฐ์ดํฐ ์์
+ ์: Position ๊ฐ์ฒด ์์
+ โ
+3. Service Layer - ๋ฐ์ดํฐ ์ฒ๋ฆฌ
+ ์: ๊ฑฐ๋ฆฌ ๊ณ์ฐ, ๋ฒํผ๋ง
+ โ
+4. Provider Layer - ์ํ ์
๋ฐ์ดํธ
+ ์: _totalDistance += distance; notifyListeners();
+ โ
+5. View Layer - UI ์ค์๊ฐ ์
๋ฐ์ดํธ
+ ์: ๋ฌ๋ ํต๊ณ ํ๋ฉด ๊ฐฑ์
+```
+
+---
+
+## ํ๋ก์ ํธ ๊ตฌ์กฐ
+
+### ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ
+
+```
+lib/
+โโโ config/ # ์ฑ ์ค์ ๋ฐ ํ๊ฒฝ ๋ณ์
+โ โโโ app_config.dart # ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ (.env)
+โ โโโ supabase_config.dart # Supabase ์ด๊ธฐํ
+โ
+โโโ constants/ # ์ฑ ์ ์ญ ์์
+โ โโโ app_colors.dart # ์ปฌ๋ฌ ํ๋ ํธ
+โ โโโ app_theme.dart # Material ํ
๋ง
+โ
+โโโ models/ # ๋ฐ์ดํฐ ๋ชจ๋ธ
+โ โโโ user_profile.dart # ์ฌ์ฉ์ ํ๋กํ
+โ โโโ user_profile.g.dart # JSON ์ง๋ ฌํ (์๋ ์์ฑ)
+โ โโโ running_session.dart # ๋ฌ๋ ์ธ์
+โ โโโ running_session.g.dart
+โ
+โโโ services/ # ๋น์ฆ๋์ค ๋ก์ง
+โ โโโ auth_service.dart # ์ธ์ฆ
+โ โโโ google_auth_service.dart # Google ๋ก๊ทธ์ธ
+โ โโโ user_profile_service.dart # ํ๋กํ ๊ด๋ฆฌ
+โ โโโ location_service.dart # GPS ์ถ์
+โ โโโ health_service.dart # ๊ฑด๊ฐ ๋ฐ์ดํฐ
+โ โโโ database_service.dart # ๋ก์ปฌ DB
+โ โโโ supabase_oauth_validator.dart
+โ
+โโโ providers/ # ์ํ ๊ด๋ฆฌ
+โ โโโ auth_provider.dart # ์ธ์ฆ ์ํ
+โ
+โโโ screens/ # UI ํ๋ฉด
+โ โโโ auth/
+โ โ โโโ login_screen.dart
+โ โ โโโ signup_screen.dart
+โ โโโ home_screen.dart
+โ โโโ running_screen.dart
+โ โโโ history_screen.dart
+โ โโโ profile_screen.dart
+โ โโโ splash_screen.dart
+โ
+โโโ widgets/ # ์ฌ์ฌ์ฉ ์์ ฏ
+โ โโโ running_card.dart
+โ โโโ running_timer.dart
+โ โโโ running_stats.dart
+โ โโโ running_controls.dart
+โ โโโ running_map.dart
+โ โโโ stats_summary.dart
+โ โโโ quick_actions.dart
+โ
+โโโ types/ # ํ์
์ ์
+โ โโโ supabase_types.dart
+โ
+โโโ main.dart # ์ฑ ์ง์
์
+
+test/ # ํ
์คํธ
+โโโ unit/ # ๋จ์ ํ
์คํธ
+โ โโโ services/
+โ โโโ models/
+โ โโโ providers/
+โโโ widget/ # ์์ ฏ ํ
์คํธ
+โโโ integration/ # ํตํฉ ํ
์คํธ
+```
+
+### ํ์ผ ๋ช
๋ช
๊ท์น
+
+```
+ํ์ผ๋ช
: snake_case
+โโ user_profile.dart โ
+โโ UserProfile.dart โ
+
+ํด๋์ค๋ช
: PascalCase
+โโ class UserProfile โ
+โโ class user_profile โ
+
+๋ณ์/ํจ์: camelCase
+โโ final userName โ
+โโ void getUserProfile() โ
+โโ final user_name โ
+
+์์: lowerCamelCase (Dart ์คํ์ผ)
+โโ const defaultPadding = 16.0; โ
+โโ const DEFAULT_PADDING = 16.0; โ
+```
+
+---
+
+## ๋์์ธ ํจํด
+
+### 1. Provider ํจํด (์ํ ๊ด๋ฆฌ)
+
+```dart
+// main.dart - Provider ๋ฑ๋ก
+MultiProvider(
+ providers: [
+ ChangeNotifierProvider(create: (_) => AuthProvider()),
+ Provider(create: (_) => LocationService()),
+ Provider(create: (_) => DatabaseService()),
+ ],
+ child: MaterialApp(...),
+)
+
+// ํ๋ฉด์์ ์ฌ์ฉ
+// ๋ฐฉ๋ฒ 1: Consumer (์ ์ฒด ์์ ฏ ๋ฆฌ๋น๋)
+Consumer(
+ builder: (context, authProvider, child) {
+ return Text(authProvider.currentUser?.email ?? '');
+ },
+)
+
+// ๋ฐฉ๋ฒ 2: Selector (ํน์ ์์ฑ๋ง ๊ตฌ๋
)
+Selector(
+ selector: (_, provider) => provider.currentUser?.email,
+ builder: (_, email, __) => Text(email ?? ''),
+)
+
+// ๋ฐฉ๋ฒ 3: Provider.of (๋ฆฌ๋น๋ ์์ด ๋ฉ์๋ ํธ์ถ)
+final authProvider = Provider.of(
+ context,
+ listen: false,
+);
+authProvider.signIn(email, password);
+```
+
+**์ฅ์ **:
+
+- โ
๊ฐ๋จํ๊ณ ์ง๊ด์
+- โ
Flutter ๊ณต์ ์ถ์ฒ
+- โ
๋ณด์ผ๋ฌํ๋ ์ดํธ ์ ์
+- โ
ํ
์คํธ ์ฉ์ด
+
+---
+
+### 2. Singleton ํจํด
+
+```dart
+// ์์: LocationService
+class LocationService {
+ // Private ์์ฑ์
+ LocationService._internal();
+
+ // Static ์ธ์คํด์ค
+ static final LocationService _instance = LocationService._internal();
+
+ // Factory ์์ฑ์
+ factory LocationService() {
+ return _instance;
+ }
+
+ // ... ๋ฉ์๋
+}
+
+// ์ฌ์ฉ
+final service1 = LocationService();
+final service2 = LocationService();
+print(service1 == service2); // true (๋์ผํ ์ธ์คํด์ค)
+```
+
+**์ฅ์ **:
+
+- โ
์ ์ญ ์ํ ๊ด๋ฆฌ
+- โ
๋ฆฌ์์ค ๊ณต์ (GPS, ๋ฐ์ดํฐ๋ฒ ์ด์ค)
+- โ
๋ฉ๋ชจ๋ฆฌ ํจ์จ์
+
+---
+
+### 3. Factory ํจํด
+
+```dart
+// ์์: RunningSession
+class RunningSession {
+ final RunningType type;
+
+ // Factory ์์ฑ์
+ factory RunningSession.fromJson(Map json) {
+ return RunningSession(
+ type: RunningType.values.firstWhere(
+ (e) => e.name == json['type'],
+ ),
+ // ...
+ );
+ }
+
+ // Named constructor
+ RunningSession.free({
+ required this.startTime,
+ required this.endTime,
+ }) : type = RunningType.free;
+
+ RunningSession.interval({
+ required this.startTime,
+ required this.endTime,
+ }) : type = RunningType.interval;
+}
+```
+
+---
+
+### 4. Stream ํจํด (๋ฐ์ํ ํ๋ก๊ทธ๋๋ฐ)
+
+```dart
+// ์์: LocationService
+class LocationService {
+ final StreamController _positionController =
+ StreamController.broadcast();
+
+ // Stream ๋
ธ์ถ
+ Stream get positionStream => _positionController.stream;
+
+ // ๋ฐ์ดํฐ ์ถ๊ฐ
+ void _onPositionReceived(Position position) {
+ _positionController.add(position);
+ }
+
+ // ๋ฆฌ์์ค ์ ๋ฆฌ
+ void dispose() {
+ _positionController.close();
+ }
+}
+
+// ์ฌ์ฉ
+locationService.positionStream.listen((position) {
+ print('์์น: ${position.latitude}, ${position.longitude}');
+});
+```
+
+---
+
+## ์ํ ๊ด๋ฆฌ
+
+### Provider ํจํด ์์ธ
+
+#### 1. ChangeNotifier ๊ตฌํ
+
+```dart
+class AuthProvider extends ChangeNotifier {
+ // Private ์ํ
+ User? _currentUser;
+ bool _isLoading = false;
+ String? _errorMessage;
+
+ // Public getter
+ User? get currentUser => _currentUser;
+ bool get isLoading => _isLoading;
+ String? get errorMessage => _errorMessage;
+ bool get isAuthenticated => _currentUser != null;
+
+ // ์ด๊ธฐํ
+ Future initialize() async {
+ _currentUser = Supabase.instance.client.auth.currentUser;
+ notifyListeners();
+ }
+
+ // ๋ก๊ทธ์ธ
+ Future signIn(String email, String password) async {
+ try {
+ _isLoading = true;
+ _errorMessage = null;
+ notifyListeners();
+
+ final user = await AuthService.signInWithEmail(
+ email: email,
+ password: password,
+ );
+
+ _currentUser = user;
+ } catch (e) {
+ _errorMessage = e.toString();
+ rethrow;
+ } finally {
+ _isLoading = false;
+ notifyListeners();
+ }
+ }
+
+ // ๋ก๊ทธ์์
+ Future signOut() async {
+ await AuthService.signOut();
+ _currentUser = null;
+ notifyListeners();
+ }
+}
+```
+
+#### 2. Consumer vs Selector
+
+```dart
+// Consumer: ์ ์ฒด Provider๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋ฆฌ๋น๋
+Consumer(
+ builder: (context, authProvider, child) {
+ // authProvider์ ์ด๋ค ์์ฑ์ด ๋ณ๊ฒฝ๋์ด๋ ๋ฆฌ๋น๋
+ return Text(authProvider.currentUser?.email ?? '๋ก๊ทธ์ธ ํ์');
+ },
+)
+
+// Selector: ํน์ ์์ฑ๋ง ๊ตฌ๋
+Selector(
+ selector: (_, provider) => provider.currentUser?.email,
+ builder: (_, email, __) {
+ // email์ด ๋ณ๊ฒฝ๋ ๋๋ง ๋ฆฌ๋น๋
+ return Text(email ?? '๋ก๊ทธ์ธ ํ์');
+ },
+)
+```
+
+**์ฑ๋ฅ ๋น๊ต**:
+
+- Consumer: ๊ฐ๋จํ์ง๋ง ๋ถํ์ํ ๋ฆฌ๋น๋ ๋ฐ์ ๊ฐ๋ฅ
+- Selector: ๋ณต์กํ์ง๋ง ์ต์ ํ๋ ๋ฆฌ๋น๋
+
+---
+
+## ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๊ณ
+
+### Supabase (PostgreSQL) ์คํค๋ง
+
+#### 1. user_profiles ํ
์ด๋ธ
+
+```sql
+CREATE TABLE public.user_profiles (
+ id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
+ email TEXT NOT NULL UNIQUE,
+ display_name TEXT,
+ avatar_url TEXT,
+ fitness_level TEXT DEFAULT 'beginner',
+ birth_date DATE,
+ gender TEXT,
+ height_cm INTEGER,
+ weight_kg NUMERIC(5, 2),
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
+);
+
+-- Index
+CREATE INDEX idx_user_profiles_email ON public.user_profiles(email);
+
+-- RLS (Row Level Security)
+ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY;
+
+CREATE POLICY "Users can view own profile"
+ON public.user_profiles FOR SELECT
+USING (auth.uid() = id);
+
+CREATE POLICY "Users can update own profile"
+ON public.user_profiles FOR UPDATE
+USING (auth.uid() = id);
+```
+
+#### 2. running_sessions ํ
์ด๋ธ
+
+```sql
+CREATE TABLE public.running_sessions (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ user_id UUID NOT NULL REFERENCES public.user_profiles(id) ON DELETE CASCADE,
+ start_time TIMESTAMP WITH TIME ZONE NOT NULL,
+ end_time TIMESTAMP WITH TIME ZONE NOT NULL,
+ total_distance NUMERIC(10, 2) NOT NULL, -- meters
+ total_duration INTEGER NOT NULL, -- seconds
+ average_pace NUMERIC(5, 2), -- min/km
+ max_speed NUMERIC(5, 2), -- km/h
+ average_heart_rate INTEGER,
+ max_heart_rate INTEGER,
+ calories_burned INTEGER,
+ elevation_gain NUMERIC(8, 2), -- meters
+ elevation_loss NUMERIC(8, 2), -- meters
+ type TEXT DEFAULT 'free', -- free, interval, goal
+ gps_points JSONB, -- GPS ๋ฐ์ดํฐ ๋ฐฐ์ด
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
+);
+
+-- Index
+CREATE INDEX idx_running_sessions_user_id ON public.running_sessions(user_id);
+CREATE INDEX idx_running_sessions_start_time ON public.running_sessions(start_time DESC);
+
+-- RLS
+ALTER TABLE public.running_sessions ENABLE ROW LEVEL SECURITY;
+
+CREATE POLICY "Users can view own sessions"
+ON public.running_sessions FOR SELECT
+USING (auth.uid() = user_id);
+
+CREATE POLICY "Users can insert own sessions"
+ON public.running_sessions FOR INSERT
+WITH CHECK (auth.uid() = user_id);
+```
+
+#### 3. Trigger: ์๋ ํ๋กํ ์์ฑ
+
+```sql
+CREATE OR REPLACE FUNCTION public.handle_new_user()
+RETURNS TRIGGER AS $$
+BEGIN
+ INSERT INTO public.user_profiles (id, email, display_name, avatar_url, created_at, updated_at)
+ VALUES (
+ NEW.id,
+ NEW.email,
+ COALESCE(
+ NEW.raw_user_meta_data->>'display_name',
+ NEW.raw_user_meta_data->>'full_name',
+ SPLIT_PART(NEW.email, '@', 1)
+ ),
+ NEW.raw_user_meta_data->>'avatar_url',
+ NOW(),
+ NOW()
+ );
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+
+CREATE TRIGGER on_auth_user_created
+ AFTER INSERT ON auth.users
+ FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
+```
+
+### SQLite (๋ก์ปฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค)
+
+```dart
+// lib/services/database_service.dart
+class DatabaseService {
+ static Database? _database;
+
+ Future get database async {
+ if (_database != null) return _database!;
+ _database = await _initDatabase();
+ return _database!;
+ }
+
+ Future _initDatabase() async {
+ final path = await getDatabasesPath();
+ final dbPath = join(path, 'stride_note.db');
+
+ return await openDatabase(
+ dbPath,
+ version: 1,
+ onCreate: _onCreate,
+ );
+ }
+
+ Future _onCreate(Database db, int version) async {
+ // running_sessions ํ
์ด๋ธ
+ await db.execute('''
+ CREATE TABLE running_sessions (
+ id TEXT PRIMARY KEY,
+ user_id TEXT NOT NULL,
+ start_time INTEGER NOT NULL,
+ end_time INTEGER NOT NULL,
+ total_distance REAL NOT NULL,
+ total_duration INTEGER NOT NULL,
+ gps_points TEXT,
+ synced INTEGER DEFAULT 0
+ )
+ ''');
+
+ // Index
+ await db.execute('''
+ CREATE INDEX idx_sessions_synced
+ ON running_sessions(synced)
+ ''');
+ }
+}
+```
+
+---
+
+## ์์กด์ฑ ์ฃผ์
+
+### Provider๋ฅผ ํตํ ์์กด์ฑ ์ฃผ์
+
+```dart
+// main.dart
+MultiProvider(
+ providers: [
+ // State Management
+ ChangeNotifierProvider(create: (_) => AuthProvider()),
+
+ // Services (Singleton)
+ Provider(create: (_) => LocationService()),
+ Provider(create: (_) => DatabaseService()),
+ Provider(create: (_) => HealthService()),
+ ],
+ child: MaterialApp(...),
+)
+
+// ํ๋ฉด์์ ์ฌ์ฉ
+class HomeScreen extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ // Provider์์ Service ์ฃผ์
๋ฐ๊ธฐ
+ final locationService = Provider.of(
+ context,
+ listen: false,
+ );
+
+ return Scaffold(...);
+ }
+}
+```
+
+**์ฅ์ **:
+
+- โ
ํ
์คํธ ์ฉ์ด (Mock ์ฃผ์
๊ฐ๋ฅ)
+- โ
์์กด์ฑ ๋ช
ํํ
+- โ
๋์จํ ๊ฒฐํฉ
+
+---
+
+## ์๋ฌ ์ฒ๋ฆฌ ์ ๋ต
+
+### 1. Try-Catch ํจํด
+
+```dart
+// Service Layer
+class AuthService {
+ static Future signInWithEmail({
+ required String email,
+ required String password,
+ }) async {
+ try {
+ final response = await _supabase.auth.signInWithPassword(
+ email: email,
+ password: password,
+ );
+ return response.user;
+ } on AuthException catch (e) {
+ // Supabase ์ธ์ฆ ์ค๋ฅ
+ throw Exception('์ธ์ฆ ์คํจ: ${e.message}');
+ } on SocketException catch (e) {
+ // ๋คํธ์ํฌ ์ค๋ฅ
+ throw Exception('๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์');
+ } catch (e) {
+ // ๊ธฐํ ์ค๋ฅ
+ throw Exception('์ ์ ์๋ ์ค๋ฅ: $e');
+ }
+ }
+}
+
+// Provider Layer
+class AuthProvider extends ChangeNotifier {
+ Future signIn(String email, String password) async {
+ try {
+ _isLoading = true;
+ _errorMessage = null;
+ notifyListeners();
+
+ final user = await AuthService.signInWithEmail(
+ email: email,
+ password: password,
+ );
+
+ _currentUser = user;
+ } catch (e) {
+ _errorMessage = e.toString();
+ // UI์์ ์ฒ๋ฆฌํ๋๋ก rethrow
+ rethrow;
+ } finally {
+ _isLoading = false;
+ notifyListeners();
+ }
+ }
+}
+
+// View Layer
+class LoginScreen extends StatelessWidget {
+ Future _handleLogin() async {
+ try {
+ await authProvider.signIn(email, password);
+ Navigator.of(context).pushReplacement(...);
+ } catch (e) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(e.toString())),
+ );
+ }
+ }
+}
+```
+
+### 2. Result ํจํด (๊ณํ ์ค)
+
+```dart
+// ์ฑ๊ณต/์คํจ๋ฅผ ๋ช
์์ ์ผ๋ก ํํ
+class Result {
+ final T? data;
+ final String? error;
+ final bool isSuccess;
+
+ Result.success(this.data) : error = null, isSuccess = true;
+ Result.failure(this.error) : data = null, isSuccess = false;
+}
+
+// ์ฌ์ฉ
+Future> signIn(String email, String password) async {
+ try {
+ final user = await AuthService.signInWithEmail(...);
+ return Result.success(user);
+ } catch (e) {
+ return Result.failure(e.toString());
+ }
+}
+```
+
+---
+
+## ๋ณด์ ๊ณ ๋ ค์ฌํญ
+
+### 1. ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ
+
+```dart
+// .env (Git์ ํฌํจ ์ ๋จ)
+SUPABASE_URL=https://...
+SUPABASE_ANON_KEY=...
+
+// app_config.dart
+class AppConfig {
+ static String get supabaseUrl =>
+ dotenv.env['SUPABASE_URL'] ?? '';
+
+ static String get supabaseAnonKey =>
+ dotenv.env['SUPABASE_ANON_KEY'] ?? '';
+}
+```
+
+### 2. Row Level Security (RLS)
+
+```sql
+-- ์ฌ์ฉ์๋ ์์ ์ ๋ฐ์ดํฐ๋ง ์ ๊ทผ ๊ฐ๋ฅ
+CREATE POLICY "Users can view own profile"
+ON public.user_profiles FOR SELECT
+USING (auth.uid() = id);
+```
+
+### 3. API Key ๋ณดํธ
+
+- โ
.env ํ์ผ ์ฌ์ฉ
+- โ
.gitignore์ ์ถ๊ฐ
+- โ
ํด๋ผ์ด์ธํธ์ ๋
ธ์ถ๋์ง ์๋๋ก ์ฃผ์
+
+---
+
+## ์ฑ๋ฅ ์ต์ ํ
+
+### 1. ์์ ฏ ์ต์ ํ
+
+```dart
+// const ์์ฑ์ ์ฌ์ฉ
+const Text('Hello'); // โ
์ฌ์์ฑ ์ ๋จ
+Text('Hello'); // โ ๋งค๋ฒ ์ฌ์์ฑ
+
+// Selector๋ก ๋ฆฌ๋น๋ ์ต์ํ
+Selector(
+ selector: (_, provider) => provider.currentUser?.email,
+ builder: (_, email, __) => Text(email ?? ''),
+)
+```
+
+### 2. ์ด๋ฏธ์ง ์ต์ ํ
+
+```dart
+// ์บ์๋ ๋คํธ์ํฌ ์ด๋ฏธ์ง
+CachedNetworkImage(
+ imageUrl: avatarUrl,
+ placeholder: (_, __) => CircularProgressIndicator(),
+ errorWidget: (_, __, ___) => Icon(Icons.error),
+)
+```
+
+### 3. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ต์ ํ
+
+```sql
+-- Index ์ถ๊ฐ
+CREATE INDEX idx_sessions_user_start
+ON running_sessions(user_id, start_time DESC);
+
+-- ์ฟผ๋ฆฌ ์ต์ ํ
+SELECT * FROM running_sessions
+WHERE user_id = $1
+ORDER BY start_time DESC
+LIMIT 10;
+```
+
+---
+
+## ์ฐธ๊ณ ์๋ฃ
+
+- [Flutter ๊ณต์ ๋ฌธ์](https://flutter.dev/docs)
+- [Provider ํจํค์ง](https://pub.dev/packages/provider)
+- [Supabase ๋ฌธ์](https://supabase.com/docs)
+- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
+
diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md
similarity index 100%
rename from CONTRIBUTING.md
rename to docs/CONTRIBUTING.md
diff --git a/docs/DEVELOPMENT_WORKFLOW.md b/docs/DEVELOPMENT_WORKFLOW.md
new file mode 100644
index 0000000..2f528c8
--- /dev/null
+++ b/docs/DEVELOPMENT_WORKFLOW.md
@@ -0,0 +1,321 @@
+# ๐ ๊ฐ๋ฐ ์ํฌํ๋ก์ฐ
+
+์ด ๋ฌธ์๋ StrideNote ํ๋ก์ ํธ์ **๊ฐ๋ฐ ํ๋ก์ธ์ค**์ **ํ์
๋ฐฉ์**์ ์ค๋ช
ํฉ๋๋ค.
+
+---
+
+## ๐ ๋ชฉ์ฐจ
+
+- [Git ๋ธ๋์น ์ ๋ต](#-git-๋ธ๋์น-์ ๋ต)
+- [์ปค๋ฐ ์ปจ๋ฒค์
](#-์ปค๋ฐ-์ปจ๋ฒค์
)
+- [๊ฐ๋ฐ ํ๋ก์ธ์ค](#-๊ฐ๋ฐ-ํ๋ก์ธ์ค)
+- [์ฝ๋ ๋ฆฌ๋ทฐ ๊ฐ์ด๋](#-์ฝ๋-๋ฆฌ๋ทฐ-๊ฐ์ด๋)
+- [CI/CD ํ์ดํ๋ผ์ธ](#-cicd-ํ์ดํ๋ผ์ธ)
+
+---
+
+## ๐ฟ Git ๋ธ๋์น ์ ๋ต
+
+### Git Flow ์ ๋ต ์ฌ์ฉ
+
+```
+main (ํ๋ก๋์
)
+ โโ develop (๊ฐ๋ฐ)
+ โโ feature/* (๊ธฐ๋ฅ ๊ฐ๋ฐ)
+ โโ bugfix/* (๋ฒ๊ทธ ์์ )
+ โโ hotfix/* (๊ธด๊ธ ์์ )
+ โโ release/* (๋ฆด๋ฆฌ์ฆ ์ค๋น)
+```
+
+### ๋ธ๋์น ๋ค์ด๋ฐ ๊ท์น
+
+| ๋ธ๋์น ํ์
| ํจํด | ์์ |
+| :-----------: | :----------------: | :--------------------- |
+| **๊ธฐ๋ฅ ๊ฐ๋ฐ** | `feature/<๊ธฐ๋ฅ๋ช
>` | `feature/google-login` |
+| **๋ฒ๊ทธ ์์ ** | `bugfix/<๋ฒ๊ทธ๋ช
>` | `bugfix/gps-accuracy` |
+| **๊ธด๊ธ ์์ ** | `hotfix/<์ด์๋ช
>` | `hotfix/login-crash` |
+| **๋ฆด๋ฆฌ์ฆ** | `release/v<๋ฒ์ >` | `release/v1.0.0` |
+
+---
+
+## ๐ ์ปค๋ฐ ์ปจ๋ฒค์
+
+### Conventional Commits ์ฌ์ฉ
+
+```
+():
+
+
+
+