๋๋ค ๊ธฐ๋ฐ ๋ฐ๋ ค๋๋ฌผ ์ฐ์ฑ ยท๋๋ด ๋งค์นญ & ์ปค๋ฎค๋ํฐ โ Flutter(iOS ยท Android ยท Web) + Supabase
GPS ๋๋ค ์ธ์ฆ์ ๊ธฐ๋ฐ์ผ๋ก ์ด์๊ณผ ๋ฐ๋ ค๋๋ฌผ ์ฐ์ฑ ๋ฉ์ดํธ๋ฅผ ์ฐพ๊ณ , ๋๋ดยท๋ถ์ยท์ ์์ ๋งค์นญํ๋ฉฐ, ์ฃผ๋ณ ๋ฐ๋ ค๋๋ฌผ ์์ค์ ์ง๋์์ ์ฐพ์ ํ๊ธฐ๋ฅผ ๋๋๋ ํ์ดํผ๋ก์ปฌ ์๋น์ค์ ๋๋ค.
๐ ์ง๊ธ ๋ฐ๋ก ๋ณด๊ธฐ โ https://app.pawmate.kr
์ฒซ ํ๋ฉด์ "๋ก๊ทธ์ธ ์์ด ๋๋ฌ๋ณด๊ธฐ" ๋ก ๊ฐ์ ์์ด ํผ๋ยท์ง๋ยท์์ค ํ๊ธฐ๋ฅผ ๋ณผ ์ ์์ต๋๋ค. (๊ธ์ฐ๊ธฐยท์ฑํ ๋ฑ ์ฐ๊ธฐ ๋์์ ์ ํ ์ธ์ฆ์ด ํ์ํฉ๋๋ค. ๋ชจ๋ฐ์ผ ์ฑ์ ์คํ ์ด ์ถ์ ์ค๋น ์ค)
๋ฐ๋ ค๋๋ฌผ ๋ถ์ ๊ฒฝ๋ก 1์๋ ํซ์(28.7%)์ด ์๋๋ผ ์ง์ธ(46.0%) ์ด๋ค(๋๋ฆผ์ถ์ฐ์ํ๋ถ, 2025). ์ด ์์ฅ์ ์ฃผ๋ฅ ๊ฑฐ๋๋ ์ด๋ฏธ ์ด์ยท์ง์ธ ์ฌ์ด์์ ์ผ์ด๋๋๋ฐ, ๊ทธ๊ฑธ ์ง์ํ๋ ์๋น์ค๊ฐ ์์ด์ ์ฌ๋๋ค์ ๋ง์นดํยท์ค๊ณ ๊ฑฐ๋ ๊ฒ์ํ์์ ๊ฑฐ๋ํ๋ค โ ์๋๋ ๊ทธ ๋๋ฌผ๋ ํ์ธํ ๋ฐฉ๋ฒ์ด ์๋ ๊ณณ์์.
๊ธฐ์กด ํซ์ํฐ ํ๋ซํผ์ ํ์ฌ๊ฐ ๊ณต๊ธ์๋ฅผ ์ฌ์ฌํด ์ด ๋ฌธ์ ๋ฅผ ํผ๋ค(์ ์ยท๋ฉด์ ยท๋ฒ์ฃ์ด๋ ฅ). ํ์คํ์ง๋ง ์ด์ ๊ฐ ๊ฑฐ๋์๋ ์ธ ์ ์๋ค โ ์์ง ์ฌ๋์ ํ์ฌ๊ฐ ๋ฉด์ ํ ์๋ ์๋ค.
ํ์ฌ๊ฐ ์ฌ๋์ ์ฌ์ฌํ ์ ์๋ P2P ์์, ์ ๋ขฐ๋ฅผ ์ด๋ป๊ฒ ๋ง๋ค ๊ฒ์ธ๊ฐ?
์ด ์ง๋ฌธ์ด ์ค๊ณ์ ์ค์ฌ์ด๋ค. ์ฌ๋์ ์ฌ์ฌํ๋ ๋์ ํ์์ ๋์์ ๊ฒ์ฆํ๋ค โ ์ง์ญ ์ธ์ฆ, AI ๊ฐ์ฒด ๋์กฐ, ์ํธ ํ๊ธฐ. ์ ๋ถ ํด๋ผ์ด์ธํธ๊ฐ ์๋๋ผ ์๋ฒ๊ฐ ๊ฐ์ ํ๋ค.
๊ฐ์ ๋ฌธ์ ๊ฐ ์์ค ์ ํ์์๋ ๋ฐ๋ณต๋๋ค. ๋๋ค ๋ณ์ยท๋ฏธ์ฉ์ค ํ๊ธฐ๋ ๋ฒ์ฉ ์ง๋ ์๋น์ค์ ์์ง๋ง ์์ฑ์๊ฐ ๊ทธ ๋๋ค ์ฌ๋์ธ์ง, ์ค์ ๋ก ๊ฐ๋์ง ์ ์ ์๋ค. ๊ทธ๋์ ์์ค ์์ฅ์ ๊ณต๊ณต๋ฐ์ดํฐ์์ ๊ฐ์ ธ์ค๊ณ , ํ๊ธฐ์๋ ์ ํ ์ธ์ฆ์ ์๊ตฌํ๋ฉฐ(๊ฐ์ ์ ์ฌ์ฉ์๋ ํ๊ธฐ ํ ๊ฑด๋ง๋ค ์ฌ์ธ์ฆ), ์ ์ฒด๋ ๊ตญ์ธ์ฒญ ๊ณ์์ฌ์ ์ ํ์ธ์ ํต๊ณผํด์ผ ์๊ธฐ ์์ค์ ์ด์ํ๋ค. ๋ฐ๋ ค๋๋ฌผ ๋งค์นญ๊ณผ ์์ค ํ๊ธฐ๋ฅผ ํ ์ฑ์์ ๋ค๋ฃจ๋ ์๋น์ค๋ ์กฐ์ฌํ ๋ฒ์์ ์์๋ค.
๋ฌธ์ ์ ์ยท๊ธฐ์กด ์๋น์ค ๋น๊ตยท๊ฒ์ฆ ๊ณํ: docs/problem-statement.md
| ์ปค๋ฎค๋ํฐ ํผ๋ | ๊ฒ์๊ธ ์์ฑ (WYSIWYG) | ์ฝ์ ์บ๋ฆฐ๋ |
|---|---|---|
![]() |
![]() |
![]() |
| ์์ค ์ง๋ | ์์ค ์์ธ (์ธ์ฆ ์ ์ฒด) | ์ฑํ |
|---|---|---|
![]() |
![]() |
![]() |
| ๋ด ์ ๋ณด (ํซ ํ์ด๋ก) | ์ ์ฒด ๋ชจ๋ (๋ ์ผ๊ตด) | AI ์ ์ ์ธ์ฆ |
|---|---|---|
![]() |
![]() |
![]() |
๊ธฐ๋ฅ ๋ชฉ๋ก๋ณด๋ค ์ด ์ ์ด ์ ์ฅ์์ ์์ ์ ๋๋ค. ์๋๋ ์ ๋ถ CI ์์ ๊ฐ์ ๋๋ฉฐ, ๊ฐ๊ฐ ์ค์ ๋ก ๋นํ ์ฌ๊ณ ๊ฐ ์์ด์ ๋ง๋ ์ฅ์น์ ๋๋ค.
| ์ฅ์น | ๋ฌด์์ ๋ง๋ | ์์น |
|---|---|---|
dart:io ๊ธ์ง ๊ฒ์ดํธ |
์น์์ ์ปดํ์ผ์ ๋์ง๋ง Platform.isIOS ๋ฑ์ด ๋ฐํ์์ UnsupportedError ๋ฅผ ๋์ง๋ค โ ๋น๋๋ก๋ ์ ์กํ๊ณ ํฐ ํ๋ฉด์ผ๋ก๋ง ๋๋ฌ๋๋ค |
ci.yml |
| ๋ง์ด๊ทธ๋ ์ด์ ๋ฆฌํ๋ ์ด | ๋น DB ์ ๋ง์ด๊ทธ๋ ์ด์ 175๊ฑด์ ์ฒ์๋ถํฐ ์ฌ์ํด ์ด์ ์ค๋ ์ท๊ณผ ๋์กฐ โ ๋ง์ด๊ทธ๋ ์ด์ ์์ด ์ด์ DB ์ ์ง์ ์น DDL ์ด ์ฆ์ ๋นจ๊ฐ๋ถ | pmdb/db-tests.yml |
| pgTAP ๋ถ๋ณ์ 16์ข | ์๊ธฐ์ด๋ ์ฐจ๋จยทํ๋ก์ฐ ์ผ๊ตด ๋ถ๋ฆฌยท์ฑํ ์ญ์ ๊ถํ ๋ฑ์ DB ๋ ๋ฒจ์์ ๊ฒ์ฆ(ํด๋ผ์ด์ธํธ๊ฐ ์๋๋ผ) | pmdb/tests/ |
| ์ปค๋ฒ๋ฆฌ์ง ๋์นซ (โฅ17%) | ๋ฌ์ฑ ๋ชป ํ 80% ๋ฅผ ์ ์ด ๋๋ ๋์ ํ์ฌ๊ฐ์ ํํ์ผ๋ก ๊ณ ์ โ ๋จ์ด์ง๋ฉด ์คํจ | ci.yml |
catch (_) ๋์นซ (โค122) |
์์ธ๋ฅผ ์ผํจ ํ๋จ์ด ๋ฐํ์์ ํ์ ์ ์ ๋จ๊ธฐ๋ ๊ฒ โ ์ธ ๋ฑ๊ธ ์ค ํ๋๋ฅผ ์ฐ๊ฒ ๊ฐ์ ํ๋ค(์ ์ฑ ) | ci.yml |
| ์น ๋น๋ ๊ฒ์ดํธ | ์ฑ ๋ณ๊ฒฝ์ด ์น ๋ฒ๋ค์ ์กฐ์ฉํ ๊นจ๋ ๊ฒ | ci.yml |
| ํฌ๋งทยท์ ์ ๋ถ์ | dart format --set-exit-if-changed, flutter analyze |
ci.yml |
๊ท๋ชจ โ Dart 48,869์ค(ํ๋ฉด 59๊ฐ) ยท ๋ง์ด๊ทธ๋ ์ด์ 175๊ฑด ยท Edge Functions 20๊ฐ ยท ํ ์ด๋ธ 50 ยท RLS ์ ์ฑ 76 ยท ํธ๋ฆฌ๊ฑฐ 73 ยท DB ํจ์ 180
์ง์ ์ง๋จํด ํฐ์ผ์ผ๋ก ๋จ๊ธด ๊ฒ๋ค์ ๋๋ค. ๊ฐ์ถ์ง ์๋ ํธ์ด ์ ํํฉ๋๋ค.
| ๋ถ์ฑ | ํํฉ |
|---|---|
| ๊ด์ธก์ฑ | 3๋ฑ๊ธ ์ ์ฑ
+ ์์ฒด ์์ง(app.client_errors)ยท๊ด๋ฆฌ์ ํ๋ฉด๊น์ง ๋ถ์๋ค(error-policy.md). ๋ฑ๊ธํ๊ฐ ๋จ์ catch (_) 122๊ณณ(162โ122)์ CI ๋์นซ์ผ๋ก ์ค์ด๋ ์ค (#157) |
| God Widget | 1,000์ค ๋๋ ํ๋ฉด 8๊ฐ(์ต๋ 2,065์ค) (#155) |
| ์ํ๊ด๋ฆฌยทDI | ํ๋ ์์ํฌ ์์ด ChangeNotifier ํ๋๋ก ์ ์ง ์ ํ ์ค(59๊ฐ ์ค 6๊ฐ) (#156 ยท ADR-0008) |
| ์ปค๋ฒ๋ฆฌ์ง 17% | ์ ๊ตฌ์กฐ์ ๊ฒฐ๊ณผ. ๋์นซ์ผ๋ก ํํ๋ง ์งํค๋ ์ค (#158) |
| i18n ๋ฏธ๋์ | ํ๋์ฝ๋ฉ ํ๊ธ ๋ฌธ์์ด (#159) |
์ค๊ณ ๊ฒฐ์ ๊ธฐ๋ก (ADR) โ docs/adr/
์ ๊ทธ๋ ๊ฒ ๋ง๋ค์๋์ง, ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ ํ์ ๋๊ฐ๋ก ๋ฌด์์ ๋ฐ์๋์ง ๋ฅผ 10๊ฑด์ผ๋ก ๋จ๊ฒผ์ต๋๋ค. ๋๋ถ๋ถ ์ฆ์๊ณผ ์์ธ์ด ๋ฉ์ด์ ๊ธฐ๋กํด ๋ ๊ฐ์น๊ฐ ์์๋ ๊ฒ๋ค์ ๋๋ค.
| ๊ฒฐ์ | ๋ฌด์์ ๋ฐ์๋ |
|---|---|
| Supabase Auth ๋์ ์ปค์คํ ์ธ์ฆ | ๋ฌด์ํ JWT ๋ผ ์ ์ง์ํจ ์ฌ์ฉ์๊ฐ 30์ผ๊ฐ ๊ณ์ ์ ๊ทผ๋๋ค |
| ๋ถ๋ณ์์ DB ์์ ๊ฐ์ | ํจ์๋ ๋ง๊ณ ์์์ง๋ง PostgREST ์ง์ INSERT ๋ก ๋ซ๋ ธ๋ค |
| DEFINER ๊ฐ์ฒด์ ์ฐ๊ธฐ ๊ฒฝ๋ก ๊ธ์ง | ๊ณต๊ฐ anon ํค๋ง์ผ๋ก ๋ก๊ทธ์ธ ์์ด ๊ด๋ฆฌ์ ์น๊ฒฉ์ด ๊ฐ๋ฅํ๋ค(์ค์ฆ ํ์ธ) |
| RLS ๋น๊ฐ์ ํ ์ฐ๊ธฐ | ์ํํธ ์ญ์ ๊ฐ 42501. INVOKER RPC ๋ ์๋ฌ ์์ด NULL ์ ๋๋ ค์ค๋ค |
| ๋ง์ด๊ทธ๋ ์ด์ ๋ฆฌํ๋ ์ด | ์ผ์๋ง์ ์ฌ์ ์๋ก ์ ์ค๋ ์๋ฆผ ํํฐ๊ฐ ์กํ๋ค |
dart:io CI ๊ธ์ง |
์ปดํ์ผ์ ๋๋๋ฐ ๋ฐํ์์ ํฐ ํ๋ฉด |
์ ์ฒด 10๊ฑด๊ณผ ํ์์ docs/adr/README.md.
| ์์ญ | ๋ด์ฉ |
|---|---|
| ๐๏ธ ๋๋ค ์ธ์ฆ | GPS + ํ์ ๋ ์ญ์ง์ค์ฝ๋ฉ ๊ธฐ๋ฐ ์ง์ญ ์ธ์ฆ(30์ผ ์ฃผ๊ธฐ ์ฌ์ธ์ฆ) โ ๋ชจ๋ ๊ฒ์๊ธยทํผ๋๊ฐ ์ธ์ฆ ๋๋ค ๊ธฐ์ค |
| ๐ ๋งค์นญ | ๋๋ฐ์ฐ์ฑ / ๋๋ฆฌ์ฐ์ฑ / ๋๋ด / ๋ถ์ / ์ ์ ๊ฒ์๊ธ โ ์ง์ โ ์๋ฝ โ ์ฝ์ โ ์ํธ ํ๊ธฐ |
| ๐ธ AI ์ฌ์ง ์ธ์ฆ | ๋ฐ๋ ค๋๋ฌผ ์ ์ ์ธ์ฆ(์์ ๋ฑ๋ก) + ๊ฒ์๊ธ ์ฌ์ง ์ค์กด ๊ฒ์ฆ(AI ๊ฐ์ฒด ๋์กฐยท์์น ๊ฒ์ฆ) โ ํ์ ๋งค๋ฌผ ์ฐจ๋จ |
| ๐บ๏ธ ์์ค ์ง๋ | ๋ค์ด๋ฒ ์ง๋ ๊ธฐ๋ฐ ๋ฐ๋ ค๋๋ฌผ ์์ค(๋ณ์ยท์นดํ ๋ฑ) ํ์, ๋ฐฉ๋ฌธ ํ๊ธฐยท๋ณ์ ยทํ๊ธฐ ๋๊ธ |
| ๐ช ์ ์ฒด ๊ณ์ | ์ฌ์ ์ ์ธ์ฆ(๊ตญ์ธ์ฒญ ์กฐํ + ๊ณต๊ณต๋ฐ์ดํฐ ๋์กฐ ์๋์น์ธ) โ ํ ๊ณ์ ์ ๊ฐ์ธ/์ ์ฒด ๋ ์ผ๊ตด ๋ถ๋ฆฌ |
| ๐ฌ ์ฑํ | 1:1 ์ค์๊ฐ ์ฑํ (Realtime), ์ฌ์ง ์ ์ก, ๋ฉ์์ง ์ญ์ (30์ผ ์ ์ ํ๊ธฐ), ์ ์ฒด ๋ฌธ์ ๋ถ๋ฆฌ |
| ๐ ์๋ฆผ | FCM ํธ์ + ์ธ์ฑ ์ค์๊ฐ ์๋ฆผ, ํญ ์ ํด๋น ํ๋ฉด ์งํ ๋ฅ๋งํฌ, ์ ํ๋ณ ์์ ์ค์ |
| ๐ฅ ์์ | ํ๋ก์ฐ(Pawing/Pawmate โ ๊ฐ์ธยท์ ์ฒด ์ผ๊ตด ๋ ๋ฆฝ), ์ฌ์ฉ์ยท๋ฐ๋ ค๋๋ฌผยท์ํธ ๊ฒ์ |
flowchart LR
subgraph App["๐ฑ Flutter (this repo)"]
UI[Screens / Widgets] --> SVC[Services ยท Repository ๊ณ์ธต]
MOTION[motion/ ์คํ๋ง ๋ชจ์
์์คํ
] --> UI
end
subgraph Supabase["โ๏ธ Supabase (pmdb repo)"]
PG[(PostgreSQL<br/>RLS ยท ํธ๋ฆฌ๊ฑฐ ยท RPC)]
EF[Edge Functions<br/>Deno]
RT[Realtime]
ST[Storage]
end
SVC -- "PostgREST + ์ปค์คํ
JWT" --> PG
SVC -- "์ธ์ฆ/SMS/AI ๊ฒ์ฆ" --> EF
SVC -- "์ฑํ
ยท์๋ฆผ ๊ตฌ๋
" --> RT
SVC -- "์ด๋ฏธ์ง ์
๋ก๋" --> ST
EF --> EXT["๊ตญ์ธ์ฒญ ยท Solapi SMS ยท Gemini ยท<br/>juso.go.kr ยท ๋ค์ด๋ฒ ์ง์ญ๊ฒ์"]
PG -- "pg_cron + pg_net" --> FCM[FCM ํธ์]
- ์ธ์ฆ: ์ ํ๋ฒํธ OTP ๊ธฐ๋ฐ ์ปค์คํ
์ธ์ฆ(HS256 JWT + refresh ํ ํฐ). Supabase Auth ๋ฏธ์ฌ์ฉ โ
๋ชจ๋ RLS ๊ฐ
app.uid()(JWT sub) ๊ธฐ์ค์ผ๋ก ๋์ํฉ๋๋ค. โ ADR-0001 - ๋ณด์ ์์น: ํด๋ผ์ด์ธํธ์๋ publishable ํค๋ง. ์ฐ๊ธฐ ๊ฒ์ฆ์ด ํ์ํ ์์ ์ SECURITY DEFINER RPC / DB ํธ๋ฆฌ๊ฑฐ๊ฐ ์ต์ข ๊ฐ์ (ํด๋ผ์ด์ธํธ ๊ฒ์ฆ์ UX ์ฉ). โ ADR-0002 ยท ADR-0003
- ๋ชจ์
:
lib/motion/์ ์คํ๋ง ํ๋ฆฌ๋ฏธํฐ๋ธ(PressableยทEntranceยทCollapseRoute ๋ฑ)๋ฅผ ์ ํ๋ฉด์ด ๊ณต์ โ ์นด๋ ํ์ฅ/์ถ์, ์ธ์ด๋ด๋ ค ๋ซ๊ธฐ ๋ฑ ์ผ๊ด๋ ์ ํ ์ธ์ด.
lib/
main.dart # ์ฑ ์ด๊ธฐํ ยท ํธ์/๋ฅ๋งํฌ ๋ผ์ฐํ
screen/ # ํ๋ฉด (tabs/ ๋ฉ์ธ 5ํญ, admin/ ๊ด๋ฆฌ์)
services/ # Supabase ์ ๊ทผ ๋ฆฌํฌ์งํ ๋ฆฌ ยท ํธ์ ยท ์ค์๊ฐ ยท ์์น
state/ # ํ๋ฉด ์ํ ํ๋ (ChangeNotifier โ docs/architecture-state.md)
models/ # ๋๋ฉ์ธ ๋ชจ๋ธ
widgets/ # ๊ณต์ฉ ์์ ฏ (ํผ๋ ์นด๋ ยท ํ์ผ ยท ์ํธ)
motion/ # ์คํ๋ง ๋ชจ์
์์คํ
(์ ํ ยท ์ด๊ฐ ํผ๋๋ฐฑ)
theme/ # ๋ผ์ดํธ/๋คํฌ ํ๋ ํธ ยท ํ
๋ง
utils/ # platform_info ยท ์กฐ๊ฑด๋ถ import ํ์ฌ๋(์น ๋์)
docs/
adr/ # ์ค๊ณ ๊ฒฐ์ ๊ธฐ๋ก
web-port.md # ์น ์ด์ ์ค๊ณยท๋ฐฐํฌยท๊ฒ์ฆ ํจ์
architecture-state.md # ์ํ ํ๋ ํจํด
test/ # ์์ ฏยท์ํ ํ๋ ํ
์คํธ (34๊ฐ ํ์ผ)
| ์ ์ฅ์ | ๋ด์ฉ |
|---|---|
| pmdb | Supabase ๋ฐฑ์๋ โ ๋ง์ด๊ทธ๋ ์ด์ 175๊ฑด ยท Edge Functions 20๊ฐ ยท pgTAP 16์ข ยท ๋ฆฌํ๋ ์ด CI ยท DB ์ ์ฒด ๋ ํผ๋ฐ์ค ๋ฌธ์ |
| pmlegal | ์๋น์ค ์ด์ฉ์ฝ๊ด ยท ๊ฐ์ธ์ ๋ณด์ฒ๋ฆฌ๋ฐฉ์นจ ยท ์์น๊ธฐ๋ฐ์๋น์ค ์ฝ๊ด (์ ๋ณธ) |
# Flutter stable ์ฑ๋ (Dart SDK ^3.12)
flutter pub get
flutter run # ์ฐ๊ฒฐ๋ ๊ธฐ๊ธฐ/์๋ฎฌ๋ ์ดํฐ
flutter run -d chrome # ์น
# ๊ฒ์ฌ & ํ
์คํธ (CI ์ ๋์ผ)
dart format --output=none --set-exit-if-changed lib test
flutter analyze --no-fatal-infos
flutter test --coverage- ์ง๋ยทSupabase ๋ฑ ํด๋ผ์ด์ธํธ ๊ณต๊ฐ ํค๋
lib/env.dart์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋์ํ๋ฉฐ,--dart-define=SUPABASE_URL=...๋ฑ์ผ๋ก ํ๊ฒฝ๋ณ ์ฌ์ ์ํ ์ ์์ต๋๋ค. - ์ฃผ์๊ฒ์(ํ์๋ถ juso) ํค๋ ์ฟผํฐ ๋จ์ฉ ์ฌ์ง๊ฐ ์์ด ๊ธฐ๋ณธ๊ฐ ์์ด
--dart-define=JUSO_API_KEY=...๋ก๋ง ์ฃผ์ ํฉ๋๋ค โ ๋ฏธ์ค์ ์ ์ฃผ์ ๊ฒ์๋ง ๋นํ์ฑํ๋๊ณ (์๋ ์ ๋ ฅ ํด๋ฐฑ) ๋๋จธ์ง ๊ธฐ๋ฅ์ ์ ์ ๋์ํฉ๋๋ค. - ํธ์(FCM/APNs)๋ ์ค๊ธฐ๊ธฐ + ์๋ช ์ค์ ์ด ํ์ํฉ๋๋ค.








