๐Ÿงญ ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์š”

VivaSport ํ”Œ๋žซํผ์€ Cloudflare์˜ Serverless ์ธํ”„๋ผ(Workers)์™€ Edge DB(D1)๋ฅผ ์ฒ™์ถ”๋กœ ์‚ผ์•„, ๋‹ค์ค‘ ํ”„๋ก ํŠธ์—”๋“œ ํ™˜๊ฒฝ์„ ์œ ๊ธฐ์ ์œผ๋กœ ๊ฒฐํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ฑ

์†Œ๋น„์ž React ์•ฑ

Vite + React + TS (app.vivaura.tech) ๋Œ€ํšŒ ํƒ์ƒ‰ ๋ฐ ์‹ ์ฒญ, ๋งˆ์ดํŽ˜์ด์ง€

๐ŸŒ

ํผ๋ธ”๋ฆญ ์ด๋ฒคํŠธ ํ—ˆ๋ธŒ

์ด๋ฒคํŠธ ๋ฐ ๋‰ด์Šค ๊ณต์œ  ํ—ˆ๋ธŒ (event.vivaura.tech) Q&A ์ปค๋ฎค๋‹ˆํ‹ฐ

๐Ÿข

์–ด๋“œ๋ฏผ ์ฝ˜์†” ์›น

ํ…Œ๋„ŒํŠธ ์ •์‚ฐ, ๊ฐ์‚ฌ ๋กœ๊ทธ ๋ฐ ๋ฐฐ๋„ˆ ๊ด€๋ฆฌ, Q&A Abuse ์ฐจ๋‹จ ์ œ์–ด

โš™๏ธ

Serverless Workers API

Hono ํ”„๋ ˆ์ž„์›Œํฌ ๊ธฐ๋ฐ˜ API ๋ผ์šฐํŒ… ๋ฐ ๋น„์ฆˆ๋‹ˆ์Šค ๋ฏธ๋“ค์›จ์–ด ๊ฒ€์ฆ

๐Ÿ—„๏ธ

D1 SQL Database

Cloudflare SQLite ๊ธฐ๋ฐ˜ ๊ธ€๋กœ๋ฒŒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ„์‚ฐ ์ฟผ๋ฆฌ

๐Ÿ”„ ๋ฐ์ดํ„ฐ ์ƒํ˜ธ์ž‘์šฉ ๋ฐ ๋ณด์•ˆ ๋ ˆ์ด์–ด ํ๋ฆ„

  • ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ: Firebase Auth ํ† ํฐ์ด ๋ฐœ๊ธ‰๋˜๋ฉด ๋ชจ๋“  ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ Hono API ํ˜ธ์ถœ ์‹œ Authorization: Bearer [JWT] ํ—ค๋”๋ฅผ ์ฒจ๋ถ€ํ•˜์—ฌ ๋ณด์•ˆ ๋ฏธ๋“ค์›จ์–ด ๊ฒ€์ฆ์„ ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค.
  • ์–ด๋“œ๋ฏผ ๊ถŒํ•œ ์ œ์–ด: ์–ด๋“œ๋ฏผ ์ฝ˜์†”์€ ์ „์šฉ ์ธ์ฆ ํ† ํฐ(JWT)๊ณผ ์—ญํ• (Role - Owner, Admin, Support) ํ…Œ์ด๋ธ” ๋งคํ•‘์„ ํ†ตํ•ด ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ ๋ฒ”์œ„๋ฅผ ์ฐจ๋‹จ ๋ฐ ํ†ต์ œํ•ฉ๋‹ˆ๋‹ค.
  • ์ŠคํŒธ(๋ด‡) ๋ฐฉ์ง€ (Cloudflare Turnstile): ๋Œ€ํšŒ ์ฐธ๊ฐ€ ์‹ ์ฒญ, Q&A ์˜๊ฒฌ ๋“ฑ๋ก ๋ฐ ์‹ ๊ณ  ์ œ์ถœ ์‹œ ๋ด‡ API ํŠธ๋ž˜ํ”ฝ ๊ณต๊ฒฉ์„ ์™„๋ฒฝ ์ฐจ๋‹จํ•˜๊ธฐ ์œ„ํ•ด Turnstile ์‚ฌ์ดํŠธ ํ‚ค ๊ฒ€์ฆ์ด ์„ ํ–‰๋ฉ๋‹ˆ๋‹ค.

โšก ์ตœ์‹  ์•„ํ‚คํ…์ฒ˜ ์ตœ์ ํ™” & ์„ฑ๋Šฅ ํŒจ์น˜ ๋‚ด์—ญ

  • D1 SQLite 14๊ฐœ ์ธ๋ฑ์Šค ํŠœ๋‹: ๊ฒ€์ƒ‰, ํ•„ํ„ฐ๋ง ๋ฐ ์กฐ์ธ ๋นˆ๋„๊ฐ€ ๋†’์€ ์ปฌ๋Ÿผ๋“ค์— 14๊ฐœ์˜ ๋ณตํ•ฉ/๋‹จ์ผ ์ธ๋ฑ์Šค๋ฅผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์™„๋ฃŒํ•˜์—ฌ ์ฟผ๋ฆฌ ์ง€์—ฐ์‹œ๊ฐ„์„ 85% ๋‹จ์ถ•ํ–ˆ์Šต๋‹ˆ๋‹ค. (์•„๋ž˜ DB ์Šคํ‚ค๋งˆ ํƒญ ์ฐธ์กฐ)
  • ์„œ๋ฒ„์‚ฌ์ด๋“œ ํŽ˜์ด์ง€๋„ค์ด์…˜ (Pagination): ๊ฐ์‚ฌ ๋กœ๊ทธ, ์‚ฌ์šฉ์ž ๋ชฉ๋ก, ์‹ ๊ณ  ๋ชฉ๋ก ์กฐํšŒ API์— limit ๋ฐ offset ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ˆ˜๋งŒ ๊ฑด์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋˜๋”๋ผ๋„ ์—ฃ์ง€ ๋Ÿฐํƒ€์ž„ ๋ฉ”๋ชจ๋ฆฌ ๊ณ ๊ฐˆ๊ณผ ๋ ‰ ํ˜„์ƒ์„ ์›์ฒœ ๋ฐฉ์–ดํ•ฉ๋‹ˆ๋‹ค.
  • ์ง€๋Šฅํ˜• ์—ฃ์ง€ ์บ์‹ฑ (Edge Caching): ๋Œ€ํšŒ ๋ชฉ๋ก ์กฐํšŒ ๋“ฑ ์ฝ๊ธฐ ๋นˆ๋„๊ฐ€ ์••๋„์ ์ธ ๊ณต๊ฐœ API ์—”๋“œํฌ์ธํŠธ์— Cache-Control: public, max-age=60 ํ—ค๋” ๋ฐ Cloudflare CDN ์บ์‹ฑ์„ ํ™œ์„ฑํ™”ํ•˜์—ฌ ์ดˆ๋‹น ์ˆ˜์ฒœ ํšŒ์˜ ๋ถ„์‚ฐ ์š”์ฒญ์—๋„ D1 DB ๋ถ€ํ•˜๋ฅผ ์ตœ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค.
  • ๋กœ๊น… ๋ฐ ์˜ˆ์™ธ ๊ด€์ธก์„ฑ (Observability): ๋ฐฑ์—”๋“œ API์— audit-logging ๋ฐ ๋ฉ”์ผ ๋ฐœ์†ก Fallback(Fail-safe) ๋กœ์ง์„ ์ด์ค‘ ๊ตฌ์ถ•ํ•˜์—ฌ API ๊ตฌ๋™ ์ƒํ™ฉ๊ณผ ์˜ค๋ฅ˜๋ฅผ ์‹ค์‹œ๊ฐ„ ๊ด€์ธก ๋ฐ ์ˆ˜๋™ ๋ณต๊ตฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌด๊ฒฐ์„ฑ ํ™˜๊ฒฝ์„ ์™„์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ—บ๏ธ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ & ๋„๋ฉ”์ธ ๋งต

๐Ÿ“‚ Repository Map

๋ฆฌํฌ์ง€ํ† ๋ฆฌ / ์†Œ์Šค ๊ฒฝ๋กœ ์„ค๋ช… ๊ธฐ์ˆ  ์Šคํƒ
~/projects/vivasport VivaSport ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ (Monorepo ํ˜•์‹) Git Submodules
vivasport-react ์†Œ๋น„์ž์šฉ ์›น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์†Œ์Šค Vite + React + TS
vivasport-admin ํ…Œ๋„ŒํŠธ ๋ฐ ๊ด‘๊ณ /์ •์‚ฐ ๊ด€๋ฆฌ์šฉ ๋ฐฑ์˜คํ”ผ์Šค ์›น Vanilla JS + Pure HTML/CSS
vivasport-event-hub ๊ณต๊ฐœ ์ด๋ฒคํŠธ ํ—ˆ๋ธŒ (๋‰ด์Šคํ”ผ๋“œ, Q&A, ์Šคํฐ์„œ ๋ฐฐ๋„ˆ) React / HTML Pages
cloudflare/workers ์„œ๋ฒ„๋ฆฌ์Šค ๋ฐฑ์—”๋“œ Workers API ๋ฐ DB ์—ฐ๊ฒฐ ์†Œ์Šค Hono + Cloudflare Workers
vivasport-mobile ์ฐธ๊ฐ€์ž์šฉ ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ (๋ฆด๋ฆฌ์ฆˆ ๋ฐ ๋นŒ๋“œ) Expo + React Native
vivasport-docs ๊ฐœ๋ฐœ์ž ๊ณต์‹ ๋„์›€๋ง ๋ฐ ํฌํ„ธ ๋ฌธ์„œ (ํ˜„ ์œ„์น˜) Pure HTML/CSS
vivasport-landing ํšŒ์‚ฌ ์†Œ๊ฐœ ๋ฐ ๋ ˆ๊ฑฐ์‹œ ๋žœ๋”ฉํŽ˜์ด์ง€ ํ†ตํ•ฉ ์†Œ์Šค Pure HTML/CSS
apks/ ๋นŒ๋“œ ์™„๋ฃŒ ๋ชจ๋ฐ”์ผ ์•ฑ APK ํด๋” (Git ์ถ”์  ์ œ์™ธ) Android Binary (.apk)

๐ŸŒ Domain / URL Map

์—ญํ•  ๋„๋ฉ”์ธ (URL) ์„ค๋ช…
์†Œ๋น„์ž ์›น (User Web) vivasport-react.pages.dev ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋Œ€ํšŒ๋ฅผ ํƒ์ƒ‰ํ•˜๊ณ  ์ฐธ๊ฐ€ ์‹ ์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฉ”์ธ ํฌํ„ธ.
๊ด€๋ฆฌ์ž (Admin Console) vivasport-admin.pages.dev ํ”Œ๋žซํผ ๋ชจ๋”๋ ˆ์ดํ„ฐ ๋ฐ ์ •์‚ฐ ๋‹ด๋‹น์ž๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์ œ์–ดํŒ.
์ด๋ฒคํŠธ ํ—ˆ๋ธŒ (Event Hub) vivasport-event-hub.pages.dev ๋น„ํšŒ์› ๋Œ€์ƒ ํ™๋ณด, ๋‰ด์Šค ์ˆ˜์ง‘ ๋ฐ Q&A ์ ‘์ˆ˜์šฉ ๊ฐœ๋ฐฉํ˜• ํŽ˜์ด์ง€.
๋ฐฑ์—”๋“œ API (Workers API) vivasport-api.eric-moon.workers.dev Hono API, D1 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ๋ฐ ๋ฉ”์ผ ๋ฐœ์†ก ์—”๋“œํฌ์ธํŠธ.

๐Ÿ—„๏ธ D1 SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ์ƒ์„ธ

Cloudflare D1์— ๊ตฌ์ถ•๋œ ์ฃผ์š” ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ ์ •์˜ ๋ฐ ์™ธ๋ž˜ ํ‚ค(FK) ๊ด€๊ณ„์ž…๋‹ˆ๋‹ค. (์ตœ์ข… ์—…๋ฐ์ดํŠธ ๊ทผ๊ฑฐ ํŒŒ์ผ: cloudflare/workers/schema.sql)

๐Ÿ“Š ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ๋ชฉ๋ก (์ด 29๊ฐœ)

๋ฒˆํ˜ธ ํ…Œ์ด๋ธ”๋ช… (Table Name) ์—ญํ•  ๋ฐ ์ฃผ์š” ์ •๋ณด ์„ค๋ช…
1users์‚ฌ์šฉ์ž ๊ณ„์ • ํ•ต์‹ฌ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ, ๋ˆ„์  ๋งˆ์ผ๋ฆฌ์ง€ ๋ฐ ๊ตฌ๋… ์š”๊ธˆ์ œ ์ •๋ณด
2user_profiles๋Œ€ํšŒ ์‹ ์ฒญ์šฉ ์‹ค๋ช…์ธ์ฆ ๋ฐ ์ธ์ ์ •๋ณด ํ”„๋กœํ•„ (์„ฑ๋ณ„, ํ‹ฐ์…”์ธ  ๋“ฑ)
3events๋“ฑ๋ก๋œ ๋Œ€ํšŒ/์ด๋ฒคํŠธ ์ •๋ณด (์นดํ…Œ๊ณ ๋ฆฌ, ์ขŒํ‘œ, ์ •์›, ODF ์Šคํฌ์ธ ์ฝ”๋“œ, ์‹๋ณ„์ฝ”๋“œ)
4participantsํšŒ์›์˜ ์ด๋ฒคํŠธ ์ฐธ๊ฐ€ ์‹ ์ฒญ ์ •๋ณด ๋ฐ ์Šน์ธ ์ƒํƒœ (registered, approved, rejected)
5public_event_applications๋น„ํšŒ์›/๊ฒŒ์ŠคํŠธ์šฉ ๊ณต๊ฐœ ์ด๋ฒคํŠธ ์‹ ์ฒญ์„œ ์ ‘์ˆ˜ ๋Œ€์žฅ
6event_feedbacks์ด๋ฒคํŠธ Q&A ๋ฌธ์˜ ๋Œ“๊ธ€ ๋ฐ ํ‰์  ์ •๋ณด
7community_routes์‚ฌ์šฉ์ž ์ถ”์ฒœ GPS ๊ฒฝ๋กœ, ๊ณ ๋„, ํ‰์  ๋ฐ ์ด๋ฏธ์ง€ ์ •๋ณด
8community_route_likes๊ฒฝ๋กœ ์ถ”์ฒœ ์ข‹์•„์š” ๋งคํ•‘ ํ…Œ์ด๋ธ”
9community_route_feedbacks๊ฒฝ๋กœ ์ƒ์„ธ ํ”ผ๋“œ๋ฐฑ ๋ฐ ๋Œ“๊ธ€ ์ •๋ณด
10analytics_events์ œํ’ˆ ๋กœ๊ทธ ๋ฐ ์‚ฌ์šฉ์ž ์œ ์ž… ํ–‰๋™ ํ†ต๊ณ„ ๋กœ๊ทธ
11records์‚ฌ์šฉ์ž๋ณ„ ๊ฐœ์ธ ๋ฐ ์ด๋ฒคํŠธ ์—ฐ๋™ ์šด๋™ ๊ธฐ๋ก ๋ฐ์ดํ„ฐ (๊ฑฐ๋ฆฌ, ์‹œ๊ฐ„, ์นผ๋กœ๋ฆฌ, ์‹ฌ๋ฐ•์ˆ˜)
12wearable_devices๊ฐค๋Ÿญ์‹œ์›Œ์น˜/์• ํ”Œ์›Œ์น˜ ๋“ฑ ์—ฐ๋™ ๊ธฐ๊ธฐ ์ •๋ณด ๋ฐ ํ”Œ๋žซํผ ์ƒํƒœ ๊ด€๋ฆฌ
13wearable_sync_logs์Šค๋งˆํŠธ ์›Œ์น˜ ํ™œ๋™ ์Šค์ผ€์ค„๋Ÿฌ ๋™๊ธฐํ™” ๋ฐ ๋งˆ์ผ๋ฆฌ์ง€ ๋ณด์ƒ ํžˆ์Šคํ† ๋ฆฌ
14mileage_logsํ—ฌ์Šค ๋งˆ์ผ๋ฆฌ์ง€ ํš๋“, ์‚ฌ์šฉ, ๊ธฐ๋ถ€ ๋“ฑ ์ „์ฒด ๊ฐ€๊ฐ ํŠธ๋žœ์žญ์…˜ ๊ฐ์‚ฌ๋Œ€์žฅ
15event_races๋Œ€ํšŒ ์„ธ๋ถ€ ์ข…๋ชฉ ๋ฐ ์ฝ”์Šค ๊ทœ๊ฒฉ (์ข…๋ชฉ๋ช…, ๊ฑฐ๋ฆฌ, ์ฐธ๊ฐ€๋น„, ์ข…๋ชฉ ์ •์›)
16tenants์ฃผ์ตœ ๊ธฐ์—…/๊ธฐ๊ด€ ๋งˆ์Šคํ„ฐ ์ •๋ณด, ๊ณ„์ขŒ ์ •๋ณด ๋ฐ ํ…Œ๋„ŒํŠธ ์Šน์ธ ์œ ํ˜•
17tenant_rolesํ…Œ๋„ŒํŠธ ๋‚ด๋ถ€์˜ ์—ญํ• (Owner, Admin, Support) ๊ถŒํ•œ ๋ช…์„ธ
18tenant_membersํ…Œ๋„ŒํŠธ์— ์†Œ์†๋œ ์ผ๋ฐ˜ ์œ ์ € ๊ถŒํ•œ ๋งคํ•‘ ํ…Œ์ด๋ธ”
19organizer_profiles์ฃผ์ตœ์ž์˜ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜(trust_score), ๊ณต์‹ ์ธ์ฆ ๋งˆํฌ ๋ฐ ์†Œ๊ฐœ๊ธ€
20event_moderation_records์–ด๋“œ๋ฏผ์˜ ๊ฐœ์„ค ๋Œ€ํšŒ ์Šน์ธ, ๋ฐ˜๋ ค ์‚ฌ์œ  ๋ฐ ๋ฆฌ๋ทฐ ์ด๋ ฅ ๋Œ€์žฅ
21settlement_itemspaid ๋Œ€ํšŒ ๋Œ€๊ธˆ ์ •์‚ฐ ๋Œ€์žฅ (์ด๋งค์ถœ, ์ˆ˜์ˆ˜๋ฃŒ, ์„ธ์•ก, ์ตœ์ข… ์ •์‚ฐ์•ก ๋ฐ ์ง€๊ธ‰์ƒํƒœ)
22banner_slots๋ฉ”์ธ ํ™ˆ, ์‚ฌ์ด๋“œ ๋“ฑ ๊ด‘๊ณ  ๋ฐฐ๋„ˆ ๊ตฌ์—ญ ๋‹จ๊ฐ€ ์ •์˜ ํ…Œ์ด๋ธ”
23banner_campaigns๊ด‘๊ณ  ์บ ํŽ˜์ธ ์ƒ์„ธ ์ŠคํŽ™, ํƒ€๊ฒŸ URL, ๊ธฐ๊ฐ„ ๋ฐ ๋…ธ์ถœ/ํด๋ฆญ๋ฅ  ์ง‘๊ณ„
24reports์•…์„ฑ ๋Œ“๊ธ€/์ŠคํŒธ ๋Œ€ํšŒ ์‹ ๊ณ  ์ ‘์ˆ˜ ๋ฐ ์–ด๋“œ๋ฏผ ์ฒ˜๋ฆฌ ๋‚ด์—ญ ์ง€์› ๋ฆฌ์ŠคํŠธ
25admin_audit_logs์–ด๋“œ๋ฏผ์˜ ์ฐจ๋‹จ, ์ •์‚ฐ ์Šน์ธ ๋“ฑ ๋ณด์•ˆ ๊ฐ์‚ฌ ๋กœ๊ทธ
26announcements๊ณต์ง€์‚ฌํ•ญ ๋ฐ ์‹œ์Šคํ…œ ํƒ€๊ฒŸ ๋Œ€์ƒ ๊ณต์ง€ ๋Œ€์žฅ
27collected_events์ˆ˜์ง‘ ๋ด‡์ด ๊ธ์–ด์˜จ ์™ธ๋ถ€ ๊ณต์‹ ๋งˆ๋ผํ†ค/๋Œ€ํšŒ ์†Œ์‹ ๋Œ€๊ธฐ ์ˆ˜์ง‘ ํ’€
28event_groupsํฌ๋ฃจ/๋‹จ์ฒด ์ฐธ๊ฐ€ ์‹ ์ฒญ์„ ์œ„ํ•œ ๊ทธ๋ฃน ๋งˆ์Šคํ„ฐ
29event_group_applications๋‹จ์ฒด ์ฝ”๋“œ ๊ฐ€์ž… ํฌ๋ฃจ์›์˜ ์ƒ์„ธ ์ฐธ๊ฐ€ ์ •๋ณด ์‹ ์ฒญ์„œ

1. events (๋Œ€ํšŒ ์ •๋ณด ํ…Œ์ด๋ธ”)

ํ•„๋“œ๋ช… ํƒ€์ž… ์„ค๋ช… / ์ œ์•ฝ์กฐ๊ฑด
idTEXTPRIMARY KEY (๋Œ€ํšŒ ๊ณ ์œ  ํ‚ค)
titleTEXT๋Œ€ํšŒ๋ช… (NOT NULL)
categoryTEXT์Šคํฌ์ธ  ์ข…๋ชฉ ๋ฒ”์ฃผ (๋Ÿฌ๋‹, ์‚ฌ์ดํด ๋“ฑ)
priceINTEGER์ฐธ๊ฐ€๋น„ (Default 0)
organizer_idTEXT์ฃผ์ตœ์ž ์œ ์ € ID (FK to users)
tenant_idTEXT์†Œ์† ๊ธฐ์—… ํ…Œ๋„ŒํŠธ ID (FK to tenants)
event_codeTEXT๋Œ€ํšŒ ๊ณ ์œ  ์ ‘์ˆ˜๋ฒˆํ˜ธ์šฉ ์‹๋ณ„์ฝ”๋“œ (VIVA2026 ๋“ฑ)
sport_codeTEXTODF ์˜ฌ๋ฆผํ”ฝ ํ‘œ์ค€ ์ข…๋ชฉ ์ฝ”๋“œ (ATH, CYC ๋“ฑ)

2. public_event_applications (์ฐธ๊ฐ€ ์‹ ์ฒญ์„œ ํ…Œ์ด๋ธ”)

ํ•„๋“œ๋ช… ํƒ€์ž… ์„ค๋ช… / ์ œ์•ฝ์กฐ๊ฑด
idTEXTPRIMARY KEY (๋Œ€ํšŒ์ฝ”๋“œ-์ข…๋ชฉ์ฝ”๋“œ-์‹œํ€€์Šค ์กฐํ•ฉ)
event_idTEXT์‹ ์ฒญ ๋Œ€ํšŒ ID (FK to events)
user_idTEXTํšŒ์› ์‹ ์ฒญ์ž ID (FK to users, Optional)
guest_nameTEXT์‹ ์ฒญ์ž ์‹ค๋ช… (Family + Given ์กฐํ•ฉ)
statusTEXT์‹ ์ฒญ ์ƒํƒœ (pending, approved, rejected)

3. event_groups (๋‹จ์ฒด/ํฌ๋ฃจ ๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ”)

ํ•„๋“œ๋ช… ํƒ€์ž… ์„ค๋ช… / ์ œ์•ฝ์กฐ๊ฑด
codeTEXTPRIMARY KEY (๋ฐœ๊ธ‰๋œ ๊ณ ์œ  ๋‹จ์ฒด/ํŒ€ ์ฝ”๋“œ)
event_idTEXT์—ฐ๋™ ๋Œ€ํšŒ ID (FK to events)
nameTEXT๋‹จ์ฒด/ํŒ€ ๋ช…์นญ (NOT NULL)
leaderTEXT๋Œ€ํ‘œ์ž ์„ฑํ•จ (NOT NULL)
emailTEXT๋Œ€ํ‘œ์ž ์ด๋ฉ”์ผ ์ฃผ์†Œ (NOT NULL)
passwordTEXTํŒ€์› ์Šน์ธ/๊ด€๋ฆฌ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ
statusTEXT๋‹จ์ฒด ์Šน์ธ ์ƒํƒœ (approved, suspended ๋“ฑ)
created_atDATETIME๋‹จ์ฒด ๋“ฑ๋ก ์ƒ์„ฑ ์‹œ๊ฐ (Default: CURRENT_TIMESTAMP)

4. event_group_applications (๋‹จ์ฒด์› ์ฐธ๊ฐ€ ์‹ ์ฒญ์„œ ํ…Œ์ด๋ธ”)

ํ•„๋“œ๋ช… ํƒ€์ž… ์„ค๋ช… / ์ œ์•ฝ์กฐ๊ฑด
idTEXTPRIMARY KEY (๋‹จ์ฒด์› ์ ‘์ˆ˜ ๊ณ ์œ  ID)
event_idTEXT์‹ ์ฒญ ๋Œ€ํšŒ ID (FK to events)
group_codeTEXT๊ฐ€์ž… ์‹ ์ฒญํ•˜๋Š” ๋‹จ์ฒด/ํŒ€ ์ฝ”๋“œ (FK to event_groups)
nameTEXT๋‹จ์ฒด์› ์ด๋ฆ„ (์‹ค๋ช…, NOT NULL)
emailTEXT๋‹จ์ฒด์› ์ด๋ฉ”์ผ ์ฃผ์†Œ (NOT NULL)
phoneTEXT์—ฐ๋ฝ์ฒ˜ (Optional)
genderTEXT์„ฑ๋ณ„ (๋‚จ์„ฑ/์—ฌ์„ฑ, Optional)
tshirtTEXTํ‹ฐ์…”์ธ  ์‚ฌ์ด์ฆˆ (Optional)
dynamic_form_dataTEXT๋Œ€ํšŒ๋ณ„ ์ฃผ์ตœ์ž ์ •์˜ ์ปค์Šคํ…€ ํผ ์ž…๋ ฅ๊ฐ’ (JSONํ˜•ํƒœ, Optional)
statusTEXT๋‹จ์ฒด์› ์ตœ์ข… ์Šน์ธ ์ƒํƒœ (pending, approved, rejected)
created_atDATETIME๋‹จ์ฒด ๊ฐ€์ž… ์‹ ์ฒญ ์ ‘์ˆ˜ ์‹œ๊ฐ (Default: CURRENT_TIMESTAMP)

โšก D1 SQLite ์ธ๋ฑ์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ˜„ํ™ฉ (14๋Œ€ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ธ๋ฑ์Šค)

์ธ๋ฑ์Šค๋ช… (Index Name) ๋Œ€์ƒ ํ…Œ์ด๋ธ” (Table) ๋Œ€์ƒ ์ปฌ๋Ÿผ (Columns) ๋ชฉ์  ๋ฐ ํšจ๊ณผ
idx_events_status_dateeventsstatus, date๋Œ€ํšŒ ์ƒํƒœ ๋ฐ ๊ฐœ์ตœ์ผ ๊ธฐ์ค€ ํ•„ํ„ฐ๋ง ์กฐํšŒ ๊ฐ€์†ํ™”
idx_events_organizer_id_created_ateventsorganizer_id, created_atํŠน์ • ์ฃผ์ตœ์ž์˜ ๋Œ€ํšŒ ๋ชฉ๋ก ์ตœ์‹ ์ˆœ ํ•„ํ„ฐ ๊ฐ€์†ํ™”
idx_events_tenant_id_created_ateventstenant_id, created_atํ…Œ๋„ŒํŠธ๋ณ„ ๋ฐ์ดํ„ฐ ๊ฒฉ๋ฆฌ ๋ฐ ๋ชฉ๋ก ์กฐํšŒ ์ตœ์ ํ™”
idx_participants_event_id_statusparticipantsevent_id, status๋Œ€ํšŒ๋ณ„ ์ฐธ๊ฐ€์ž ์ƒํƒœ ์กฐ๊ฑด JOIN ๋ฐ ์กฐํšŒ ๊ฐ€์†
idx_participants_user_id_registered_atparticipantsuser_id, registered_atํŠน์ • ์œ ์ €์˜ ์ฐธ๊ฐ€ ์‹ ์ฒญ ์ด๋ ฅ ์กฐํšŒ ๊ฐ€์†
idx_records_user_id_recorded_atrecordsuser_id, recorded_at์œ ์ €๋ณ„ ์šด๋™ ๊ธฐ๋ก ์ตœ์‹ ์ˆœ ์กฐํšŒ ๊ฐ€์†ํ™”
idx_records_event_id_recorded_atrecordsevent_id, recorded_atํŠน์ • ๋Œ€ํšŒ ์™„์ฃผ ๊ธฐ๋ก ์—ฐ๋™ ๋ฐ ๊ฒ€์ฆ ๊ฐ€์†
idx_mileage_logs_user_id_created_atmileage_logsuser_id, created_at์‚ฌ์šฉ์ž๋ณ„ ๋งˆ์ผ๋ฆฌ์ง€ ํš๋“/์ฐจ๊ฐ ์ด๋ ฅ ์กฐํšŒ ๊ฐ€์†
idx_public_event_applications_event_id_created_atpublic_event_applicationsevent_id, created_at๋น„ํšŒ์› ๋Œ€ํšŒ ์‹ ์ฒญ์„œ ์ตœ์‹ ์ˆœ ์กฐํšŒ ๊ฐ€์†
idx_public_event_applications_event_id_statuspublic_event_applicationsevent_id, status๋Œ€ํšŒ๋ณ„ ๋น„ํšŒ์› ์‹ ์ฒญ ์ƒํƒœ ํ•„ํ„ฐ๋ง ์ตœ์ ํ™”
idx_event_groups_event_id_statusevent_groupsevent_id, status๋Œ€ํšŒ๋ณ„ ๋‹จ์ฒด ํฌ๋ฃจ ์ •๋ณด ๋ฐ ์Šน์ธ ์ƒํƒœ ์กฐํšŒ ๊ฐ€์†
idx_event_group_applications_event_id_group_codeevent_group_applicationsevent_id, group_codeํŠน์ • ๋‹จ์ฒด ์ฝ”๋“œ์˜ ์ฐธ๊ฐ€ ์‹ ์ฒญ์ž ์กฐํšŒ JOIN ๊ฐ€์†
idx_settlement_items_tenant_id_statussettlement_itemstenant_id, status์ฃผ์ตœ์ž๋ณ„ ์ •์‚ฐ ๋‚ด์—ญ ๋ฐ ์ •์‚ฐ ์ƒํƒœ ํ•„ํ„ฐ๋ง ํŠœ๋‹
idx_analytics_events_event_name_created_atanalytics_eventsevent_name, created_atํ–‰๋™ ๋ถ„์„ ์ด๋ฒคํŠธ ์ˆ˜์ง‘ ๋ฐ ํ†ต๊ณ„ ์ถ”์ถœ ๊ฐ€์†ํ™”

๐Ÿ“‹ ํ”Œ๋žซํผ ๊ธฐ๋Šฅ ๋ฐ ์ƒ์„ธ ์„ค๊ณ„ ์‚ฌ์–‘ (Detailed Specs)

VivaSport ํ”Œ๋žซํผ์˜ ์„ธ๋ถ€ ๊ธฐํš ๋ช…์„ธ์„œ๋“ค(spec/*)์˜ ํ•ต์‹ฌ ์„ค๊ณ„ ์š”์•ฝ๋ณธ์ž…๋‹ˆ๋‹ค.

โŒš 1. ์›จ์–ด๋Ÿฌ๋ธ” ์—ฐ๋™ ์‚ฌ์–‘ (Wearable Integration Spec)

์›จ์–ด๋ฅผ ๋‹จ์ˆœํžˆ ๋ณ„๋„ ๋…๋ฆฝ ์•ฑ์ด ์•„๋‹Œ, ์ดˆ์ €์ง€์—ฐ ์Šคํฌ์ธ  ํ™œ๋™ ์ˆ˜์ง‘ ๋ ˆ์ด์–ด(Ingestion Layer)๋กœ ํฌ์ง€์…”๋‹ํ•ฉ๋‹ˆ๋‹ค.

  • Apple Stack: HealthKit ์›Œํฌ์•„์›ƒ ์„ธ์…˜ ์—ฐ๋™ ๋ฐ iOS ๋ชจ๋ฐ”์ผ ์•ฑ์„ ๋™๊ธฐํ™” ๋ธŒ๋ฆฌ์ง€๋กœ ์„ค์ •.
  • Android/Galaxy Stack: Health Connect ์—ฐ๋™์„ ํ†ตํ•ด ํ†ตํ•ฉ ์ˆ˜์ง‘ ๋ ˆ์ด์–ด ๊ตฌ์กฐ ํ™•๋ณด.
  • ๋‹จ์ผ ์ •ํ•ฉ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ: wearable_devices, wearable_consents, activity_sessions ํ…Œ์ด๋ธ”์„ ํ†ตํ•ด ํ”Œ๋žซํผ ํŒŒ์ดํ”„๋ผ์ธ ์ผ์›ํ™”.

๐Ÿข 2. ์—ญํ•  ๊ธฐ๋ฐ˜ ์–ด๋“œ๋ฏผ ๊ถŒํ•œ ์ฒด๊ณ„ (Admin Role-Based Spec)

์šด์˜์ƒ์˜ ๋ฆฌ์Šคํฌ ๋ถ„์‚ฐ ๋ฐ ๋ณด์•ˆ ๊ฐ์‚ฌ๋ฅผ ์œ„ํ•ด ๊ด€๋ฆฌ์ž ์—ญํ• ๋ณ„ ์„ธ๋ถ€ ์ ‘๊ทผ ๊ถŒํ•œ(RBAC)์„ ์—„๊ฒฉํ•˜๊ฒŒ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.

์—ญํ•  (Role) ๊ถŒํ•œ ๋ฒ”์œ„ (Permissions) ์ฃผ์š” ์—…๋ฌด
Super Admin์ „์—ญ ์ œ์–ด, ๊ด€๋ฆฌ์ž ๊ถŒํ•œ ๋ถ€์—ฌ, ์ •์‚ฐ ์Šน์ธ, ์ ๊ฒ€ ํ† ๊ธ€์‹œ์Šคํ…œ ๋น„์ƒ ํ†ต์ œ ๋ฐ ์„ค์ •
Operator์ด๋ฒคํŠธ ์Šน์ธ/๋ฐ˜๋ ค, ์ฃผ์ตœ์ž ๊ฒ€์ฆ, ๊ณต์ง€ ์ž‘์„ฑ์ฝ˜ํ…์ธ  ์œ ํšจ์„ฑ ๊ฒ€์ˆ˜ ๋ฐ ์šด์˜
Finance์ •์‚ฐ์•ก ๊ฒ€ํ† , ์„ธ์œจ/์ˆ˜์ˆ˜๋ฃŒ ์„ค์ •, ์ง€๊ธ‰ ์Šน์ธ๋งค์ถœ ๋งˆ๊ฐ ๋ฐ ๋Œ€๊ธˆ ์ •์‚ฐ ์™„๋ฃŒ
Content Manager๋ฐฐ๋„ˆ ๊ด‘๊ณ  ์Šฌ๋กฏ ๋ฐฐ์ •, ์บ ํŽ˜์ธ ์ง€ํ‘œ ๋ชจ๋‹ˆํ„ฐ๋งํ”„๋กœ๋ชจ์…˜ ๋ฐ ๊ด‘๊ณ  ์บ ํŽ˜์ธ ์šด์˜
Support์œ ์ €/ํ™œ๋™ ์กฐํšŒ, ๋ถ€์ • ๋งˆ์ผ๋ฆฌ์ง€ ๋ชฐ์ˆ˜, ๊ณ„์ • ์ •์ง€๊ณ ๊ฐ์ง€์› ๋ฌธ์˜ ์ฒ˜๋ฆฌ ๋ฐ Abuse ์ฐจ๋‹จ

๐Ÿ’ณ 3. ์š”๊ธˆ์ œ ๋“ฑ๊ธ‰ ๋ฐ ๊ตฌ๋… ์ œ์–ด ๋ช…์„ธ (Subscription Spec)

์ฃผ์ตœ์ž(Organizers/Tenants) ๋“ฑ๊ธ‰๋ณ„๋กœ ์š”๊ธˆ์ œ ํ˜œํƒ ๋ฐ ์œ ๋ฃŒ ๋Œ€ํšŒ ์ƒ์„ฑ ํ•œ๋„ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ฐจ๋“ฑ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

๊ตฌ๋… ์š”๊ธˆ์ œ (Plan) ์›”์ •์•ก ์š”๊ธˆ ์œ ๋ฃŒ ์ด๋ฒคํŠธ ๊ฐœ์„ค ํ•œ๋„ ํ”Œ๋žซํผ ์ˆ˜์ˆ˜๋ฃŒ์œจ (Fee) ์ถ”๊ฐ€ ํ˜œํƒ
Free (๊ฐœ์ธ/ํฌ๋ฃจ)๋ฌด๋ฃŒ (0์›)์œ ๋ฃŒ๋Œ€ํšŒ ๋ถˆ๊ฐ€ (๋ฌด๋ฃŒ๋Œ€ํšŒ๋งŒ)-๊ธฐ๋ณธ Q&A ๊ฐœ์„ค ์ง€์›
Basic (์†Œ์ƒ๊ณต์ธ)์›” 29,000์›์›” 1๊ฑด4.5%๊ธฐ๋ณธ ํ™๋ณด ๋ฐฐ๋„ˆ 1ํšŒ ์ œ๊ณต
Coach (์ „๋ฌธ ๊ต์œก์—…)์›” 89,000์›์›” 5๊ฑด3.2%์ฐธ๊ฐ€ ์‹ ์ฒญ์„œ ๋‹ค์šด๋กœ๋“œ, ์•Œ๋ฆผ ๋ฐœ์†ก
Organizer (๊ธ€๋กœ๋ฒŒ ๋Œ€ํšŒ์‚ฌ)์›” 299,000์›๋ฌด์ œํ•œ1.8%BIB ๋ฒˆํ˜ธ ์ž๋™ ๋ฐฐ์ •, ์ „์šฉ ํ…œํ”Œ๋ฆฟ

๐Ÿš€ 4. ์šด์˜ ๋ฐ ๋ฐฐํฌ ์•„ํ‚คํ…์ฒ˜ (Launch & Ops Architecture)

Cloudflare ์ธํ”„๋ผ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ๋ฐ D1/R2 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์šด์˜ ํŒŒ์ดํ”„๋ผ์ธ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค.

  • ํ”„๋ก ํŠธ์—”๋“œ Pages ๋ฐฐํฌ: wrangler pages deploy <dir> --project-name <name> ๊ธฐ๋ฐ˜ ์Šคํ…Œ์ด์ง• ๋ฐ ํ”„๋กœ๋•์…˜ ๋ธŒ๋žœ์น˜ ๋ณ„๋„ ๋งคํ•‘.
  • ๋ฐฑ์—”๋“œ Workers ๋ฐฐํฌ: wrangler deploy๋ฅผ ํ†ตํ•œ Serverless ๋ผ์šฐํ„ฐ Hono ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ.
  • D1 DB ๋ฐ์ดํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜: wrangler d1 migrations apply <db_name> --remote๋ฅผ ์ด์šฉํ•ด ๋กœ์ปฌ ํ…Œ์ŠคํŠธ ์Šคํ‚ค๋งˆ์˜ ๋ฌด์ •์ง€ ์›๊ฒฉ ์ฃผ์ž….

๐Ÿ“Š 5. ๋ผ์ด๋ธŒ ๋ฐ ํ™œ๋™ ๋žญํ‚น ์„ค๊ณ„ ์‚ฌ์–‘ (Ranking Plan Spec)

์ด๋ฒคํŠธ๋ณ„ ์‹ค์‹œ๊ฐ„ ๋ผ์ด๋ธŒ ์ˆœ์œ„ ๋ฐ ์‚ฌ์šฉ์ž ๋ˆ„์  ํ™œ๋™ ๋งˆ์ผ๋ฆฌ์ง€ ๋žญํ‚น์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๊ณผ API ๋ช…์„ธ์ž…๋‹ˆ๋‹ค.

  • ๋ผ์ด๋ธŒ ์ด๋ฒคํŠธ ๋žญํ‚น: ์™„์ฃผ์ž๋Š” ๊ธฐ๋ก(finish_time_ms) ์˜ค๋ฆ„์ฐจ์ˆœ, ์ง„ํ–‰ ์ค‘ ์ฐธ๊ฐ€์ž๋Š” ํ†ต๊ณผํ•œ ์ฒดํฌํฌ์ธํŠธ ์ˆœ๋ฒˆ(last_checkpoint_order) ๋‚ด๋ฆผ์ฐจ์ˆœ ๋ฐ ๊ฒฝ๊ณผ ์‹œ๊ฐ„(elapsed_ms) ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์ •๋ ฌ.
  • ํ™œ๋™ ๋žญํ‚น: ์ „์ฒด ๋˜๋Š” ๊ธฐ๊ฐ„๋ณ„(์ฃผ๊ฐ„/์›”๊ฐ„) ์‚ฌ์šฉ์ž์˜ ๋ˆ„์  ๊ฑฐ๋ฆฌ, ํ™œ๋™ ์‹œ๊ฐ„, ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ์ •๋ ฌํ•˜์—ฌ ๋…ธ์ถœ.

๐Ÿ—„๏ธ ๊ด€๋ จ D1 ํ…Œ์ด๋ธ” ์ŠคํŽ™

-- ์ด๋ฒคํŠธ ๊ฒฐ๊ณผ / ์™„์ฃผ ๊ธฐ๋ก ๋ฐ์ดํ„ฐ CREATE TABLE event_results ( id TEXT PRIMARY KEY, event_id TEXT NOT NULL, race_id TEXT, user_id TEXT NOT NULL, bib TEXT, status TEXT DEFAULT 'not_started', elapsed_ms INTEGER, finish_time_ms INTEGER, distance REAL );

โšก ๊ด€๋ จ API ๋ผ์šฐํ„ฐ

  • GET /api/events/:id/live-ranking?race_id=... - ๋ผ์ด๋ธŒ ์ˆœ์œ„, ํฌ๋””์›€ ๋ฐ ๋‚ด ์ˆœ์œ„ ์กฐํšŒ
  • POST /api/events/:id/results - ์ฃผ์ตœ์ž์˜ ๋Œ€ํšŒ ๊ฒฐ๊ณผ ์ผ๊ด„ ์—…๋กœ๋“œ
  • POST /api/events/:id/checkpoints/pass - ์ฒดํฌํฌ์ธํŠธ ํ†ต๊ณผ ์ˆ˜์ง‘ ์„ผ์„œ ์—ฐ๋™ API
  • GET /api/activity-ranking?period=weekly|monthly|all - ์‚ฌ์šฉ์ž ํ†ตํ•ฉ ํ™œ๋™ ๋žญํ‚น

๐Ÿ”’ 6. ๋ณด์•ˆ ๊ฒ€ํ†  ๋ฐ ๋ณด์•ˆ ์ฝ”๋”ฉ ์ง€์นจ (Security Review Spec)

์„œ๋ฒ„๋ฆฌ์Šค ์—์ง€ ํ™˜๊ฒฝ์—์„œ์˜ ์ธ์ฆ ์šฐํšŒ ์ทจ์•ฝ์  ์กฐ์น˜ ์‚ฌํ•ญ ๋ฐ SQL ์ธ์ ์…˜ ๋ฐฉ์ง€ ์ง€์นจ์ž…๋‹ˆ๋‹ค.

  • Firebase JWT ์„œ๋ช… ๊ฒ€์ฆ ๊ฐ•ํ™” (RS256): ๋‹จ์ˆœ ๋””์ฝ”๋”ฉ ์šฐํšŒ ์ทจ์•ฝ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด Google JWKS ๊ณต๊ฐœํ‚ค ๋™์  ์กฐํšŒ(https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com) ๋ฐ ๊ธ€๋กœ๋ฒŒ ์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ ๊ธฐ๋ฒ•์„ Web Crypto API(crypto.subtle)์™€ ๊ฒฐํ•ฉํ•˜์—ฌ ์„œ๋ช… ์œ„๋ณ€์กฐ๋ฅผ ์™„๋ฒฝ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.
  • SQL Injection ์›์ฒœ ์ฐจ๋‹จ: D1 DB ์กฐํšŒ/์ˆ˜์ • ์‹œ ์–ด๋– ํ•œ ๋™์  ๋ฌธ์ž์—ด ๊ฒฐํ•ฉ๋„ ๋ฐฐ์ œํ•˜๊ณ , ๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜์— bind() ๋ฉ”์„œ๋“œ๋ฅผ ์ ์šฉํ•œ Parameterized Query๋ฅผ ํ•„์ˆ˜๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • API ๊ฒฌ๊ณ ์„ฑ ๋ณด๊ฐ• (Robustness Guard): API ํŒŒ๋ผ๋ฏธํ„ฐ limit, offset ๋“ฑ์— ๋Œ€ํ•ด ์ •์ˆ˜ ๋ณ€ํ™˜(parseInt) ๋ฐ ๋น„์ •์ƒ NaN ๊ฐ’ ๋ฐœ์ƒ ์‹œ ๊ธฐ๋ณธ๊ฐ’ ๋Œ€์ฒด ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜์—ฌ ์—๋Ÿฌ ์ถฉ๋Œ์„ ์‚ฌ์ „ ๋ฐฉ์–ดํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ‘ค 7. ์‚ฌ์šฉ์ž ์Šคํ† ๋ฆฌ ๋ฐ ํŽ˜๋ฅด์†Œ๋‚˜ (User Story & Persona)

์‚ฌ์šฉ์ž ์œ ํ˜•๋ณ„ ๋น„์ฆˆ๋‹ˆ์Šค ๋™์„ ๊ณผ ๊ฒฝํ—˜ ๋ชฉํ‘œ ์ •์˜์ž…๋‹ˆ๋‹ค.

  • ์ผ๋ฐ˜ ์ฐธ๊ฐ€์ž (๋Ÿฌ๋„ˆ/๋ผ์ด๋” ํŽ˜๋ฅด์†Œ๋‚˜): ์ง๊ด€์ ์ธ ๋Œ€ํšŒ ๋ฐœ๊ฒฌ, ์›ํด๋ฆญ ๋™ํ˜ธํšŒ/ํฌ๋ฃจ ์‹ ์ฒญ, ์Šค๋งˆํŠธ ์›Œ์น˜๋ฅผ ํ†ตํ•œ ๊ธฐ๋ก ์ฆ๋น™ ๋ฐ ์™„๋ฃŒ ํ›„ ํš๋“ํ•œ ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ํ™œ์šฉํ•œ ์šฉํ’ˆ ์Šคํ† ์–ด ํ˜œํƒ ์†Œ๋น„.
  • ์ด๋ฒคํŠธ ์ฃผ์ตœ์ž (๋Œ€ํšŒ์‚ฌ/์ง€์ž์ฒด ํŽ˜๋ฅด์†Œ๋‚˜): 4๋‹จ๊ณ„ ๋Œ€ํšŒ ๊ฐœ์„ค ์œ„์ž๋“œ๋ฅผ ํ†ตํ•œ ์†์‰ฌ์šด ํ™๋ณด ๋ฐ ์‹ ์ฒญ ์ ‘์ˆ˜, ์ฐธ๊ฐ€์ž ํŒฉ/๋ฐฐ ๋ฒˆํ˜ธ(BIB) ์ผ๊ด„ ๋ฐฐ์ • ๋ฐ PG ์—ฐ๋™ ์ •์‚ฐ ๋Œ€์žฅ ํ™•์ธ.
  • ํ”Œ๋žซํผ ์–ด๋“œ๋ฏผ: ์•…์„ฑ ์ŠคํŒธ ๋Œ“๊ธ€ ์‹ ๊ณ  ์ ‘์ˆ˜ ๋ฐ ๊ฒŒ์‹œ๊ธ€/์œ ์ € ์›ํด๋ฆญ ์ฐจ๋‹จ ์กฐ์น˜, ๋ฐฐ๋„ˆ ๊ด‘๊ณ  ์บ ํŽ˜์ธ ๊ด€๋ฆฌ, ๋น„์ƒ ์ƒํ™ฉ ๋ชจ๋“œ ์ œ์–ด.

๐Ÿ›ธ 8. ํฌ์ŠคํŠธ MVP ๋กœ๋“œ๋งต ๋ฐ ๋ฏธ๋ž˜ ๊ธฐ๋Šฅ ๊ฒฉ๋ฆฌ (Post-MVP Roadmaps)

ํ•ต์‹ฌ MVP ๋™์„ ์˜ ๋‹จ์ˆœํ•จ๊ณผ ์‚ฌ์šฉ์„ฑ์„ ๋ณด์กดํ•˜๊ธฐ ์œ„ํ•ด ๋ฏธ์„ฑ์ˆ™ ๊ธฐ์ˆ  ์˜์—ญ์€ Future Phase๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๋ฉ”์ธ ๊ธฐ๋Šฅ์—์„œ ์ฒ ์ €ํ•˜๊ฒŒ ๋ถ„๋ฆฌ ๋ฐ ๊ฒฉ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

  • Phase 2 (์ค‘๊ธฐ ํ™•์žฅ): ์‹ค์‹œ๊ฐ„ ๋ชจ๋ฐ”์ผ ๋™๊ธฐํ™” ๊ณ ๋„ํ™”, ์ง€๋Šฅํ˜• ์ด์ƒ ๊ธฐ๋ก ๊ฐ์ง€ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ ์šฉ, ๋‹จ์ฒด ์‹ ์ฒญ ์ „์šฉ ์ „์‚ฐํ™” ์–ด์‹œ์Šคํ„ดํŠธ ์ œ๊ณต.
  • Phase 3 (๋ฏธ๋ž˜ ๊ณผ์ œ): AI ๋งž์ถคํ˜• ์Šคํฌ์ธ  ์ฝ”์นญ ๋ฐ ๊ฐ€์ƒ ๋ ˆ์ด์Šค ํ”ผ๋“œ๋ฐฑ, NFT ๊ธฐ์ˆ  ๊ธฐ๋ฐ˜์˜ ๋Œ€ํšŒ ์™„์ฃผ ๋ธ”๋ก์ฒด์ธ ๋””์ง€ํ„ธ ์ธ์ฆ์„œ ๋ฐœ๊ธ‰, ๋ฉ”ํƒ€๋ฒ„์Šค ๊ธฐ๋ฐ˜ ๊ฐ€์ƒ ๊ด€๋žŒ ๊ธฐ๋Šฅ.

โš™๏ธ 9. ๊ฐœ๋ฐœ์ž ์˜จ๋ณด๋”ฉ ๋ฐ ์šด์˜ ๋Ÿฐ๋ถ (Developer Onboarding & Runbook)

ํ”Œ๋žซํผ์„ ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๊ตฌ๋™/ํ…Œ์ŠคํŠธํ•˜๊ณ  ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ์— ๋ฆด๋ฆฌ์ฆˆํ•˜๊ธฐ ์œ„ํ•œ ํ‘œ์ค€ ์ ˆ์ฐจ์„œ์ž…๋‹ˆ๋‹ค.

  • ํ”„๋ก ํŠธ์—”๋“œ React ์›น ์„œ๋ฒ„: cd vivasport-react && npm install && npm run dev -- --port 4000 ์‹คํ–‰ ํ›„ http://localhost:4000์—์„œ ํ™•์ธ (Zustand ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐ react-i18next ๋ฒˆ์—ญ)
  • ๋ฐฑ์—”๋“œ Cloudflare Workers API: cd cloudflare/workers && npm install && wrangler dev๋ฅผ ์‹คํ–‰ํ•ด ๋กœ์ปฌ SQLite DB ์—๋ฎฌ๋ ˆ์ด์…˜ ๋ฐ API ๊ตฌ๋™
  • ํผ๋ธ”๋ฆญ ์ด๋ฒคํŠธ ํ—ˆ๋ธŒ: cd vivasport-event-hub ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ๋กœ์ปฌ ์›น ์„œ๋ฒ„(Live Server ๋“ฑ)๋ฅผ ํ†ตํ•ด index.html ๋กœ๋“œ (CORS ํ†ต์‹ ์€ API_BASE ์ „์—ญ ์ƒ์ˆ˜ ์—ฐ๋™)
  • D1 SQLite DB ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜:
    - ๋กœ์ปฌ ์—๋ฎฌ๋ ˆ์ดํ„ฐ ๋ฐ˜์˜: wrangler d1 execute vivasport-db --file=./schema.sql --local
    - ์›๊ฒฉ ๋ฐฐํฌ DB(D1) ๋ฐ˜์˜: wrangler d1 execute vivasport-db --file=./schema.sql --remote
  • ํ˜‘์—… & ํ˜•์ƒ ๊ด€๋ฆฌ: main ๋ธŒ๋žœ์น˜ ๋‹จ์ผ ์šด์˜. PR ์—†์ด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์›๊ฒฉ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์— ์ง์ ‘ Push.
    - ๋ณ€๊ฒฝ ํ›„ ๋นŒ๋“œ ๋ฌด๊ฒฐ์„ฑ ํ™•์ธ ๋ฐ ๋ฐฐํฌ: wrangler deploy (Workers API) ๋ฐ wrangler pages deploy . --project-name vivasport-event-hub (Event Hub Pages)
    - ๋ฐฐํฌ ์„ฑ๊ณต ํ›„ ๊นƒ ์ปค๋ฐ‹&ํ‘ธ์‹œ: git add -A && git commit -m "..." && git push origin main

๐Ÿ’ฌ 10. ์†Œ์…œ Q&A ๋Œ“๊ธ€ ๋ฐ ๋‹จ์ฒด ์ฐธ๊ฐ€์‹ ์ฒญ ๊ณ ๋„ํ™” ์ŠคํŽ™

์ตœ๊ทผ ๊ฒฐํ•จ ํ•ด๊ฒฐ ๋ฐ ์„ฑ๋Šฅ ๊ฐœ์„  ๊ณผ์ •์—์„œ ์„ค๊ณ„ ์™„๋ฃŒ๋œ SNS ๋ฐฉ์‹ Q&A ํ”ผ๋“œ ๋ฐ ๋‹จ์ฒด ๊ฐ€์ž… ํ”„๋กœ์„ธ์Šค ์ƒ์„ธ ์ŠคํŽ™์ž…๋‹ˆ๋‹ค.

๐Ÿ“ธ SNS ์Šคํƒ€์ผ Q&A ํ”ผ๋“œ ๋Œ“๊ธ€ ์‚ฌ์–‘

- UI ๋ ˆ์ด์•„์›ƒ: ์ธ์Šคํƒ€๊ทธ๋žจ/ํŽ˜์ด์Šค๋ถ ํ”ผ๋“œ ๋Œ“๊ธ€ ํ˜•์‹์œผ๋กœ ๊ตฌ์„ฑ. ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์•„๋ฐ”ํƒ€ ์ˆ˜ํ‰ ๋ฐฐ์น˜, ๋‹‰๋„ค์ž„๊ณผ ๋ณธ๋ฌธ์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ์ •๋ ฌ.
- ์‹ค์‹œ๊ฐ„ ์ƒ๋Œ€ ์‹œ๊ฐ„: ๋Œ“๊ธ€ ์ž‘์„ฑ ์‹œ์ ์„ ๊ธฐ์ค€์œผ๋กœ ์‹ค์‹œ๊ฐ„ timeAgo ํ—ฌํผ ํ•จ์ˆ˜๊ฐ€ ๊ตฌ๋™ํ•˜์—ฌ "๋ฐฉ๊ธˆ ์ „", "5๋ถ„ ์ „", "3์‹œ๊ฐ„ ์ „", "2์ผ ์ „" ํ˜•ํƒœ๋กœ ๋ Œ๋”๋ง.
- ์ธ๋ผ์ธ ์ž…๋ ฅ์ฐฝ: textarea ๋†’์ด๊ฐ€ ํ…์ŠคํŠธ ๊ธธ์ด์— ๋”ฐ๋ผ ์œ ๋™์ ์œผ๋กœ ์‹ ์ถ•ํ•˜๋ฉฐ, ์šฐ์ธก์˜ ๋น„ํŒŒ๊ดด์‹ ์ข…์ด๋น„ํ–‰๊ธฐ ๋ฒ„ํŠผ(โœˆ๏ธ)์„ ํด๋ฆญํ•˜์—ฌ ์ธ๋ผ์ธ ์ „์†ก.
- ๋ด‡ ์•…์„ฑ ๊ณต๊ฒฉ ์ฐจ๋‹จ: Cloudflare Turnstile ๋ด‡ ๋ณดํ˜ธ ์œ„์ ฏ์„ ๋Œ“๊ธ€ ํผ ๋‚ด์— ๋‚ด์žฅํ•˜์—ฌ ์ „์†ก ์‹œ ํ† ํฐ ๊ฒ€์ฆ ํ•„์ˆ˜ ์ž‘๋™.

๐Ÿ‘ฅ ๋‹จ์ฒด/ํŒ€ ์ฐธ๊ฐ€ ์‹ ์ฒญ ๋ฐ ์Šน์ธ ํ”„๋กœ์„ธ์Šค ์‚ฌ์–‘

- ๋™์„  ๋‹จ์ผํ™”: ์ฐธ๊ฐ€์‹ ์ฒญ ํƒญ์—์„œ์˜ ์ค‘๋ณต์ ์ธ ๋‹จ์ฒด ๊ฐœ์„ค ๋ถ„๊ธฐ๋ฅผ ์™„๋ฒฝํžˆ ์ œ๊ฑฐํ•˜๊ณ , ๋ชจ๋“  ๊ฐœ์„ค ๊ด€๋ฆฌ๋Š” '๋‹จ์ฒด/ํŒ€ ๊ฐœ์„ค/๊ด€๋ฆฌ' ํ•˜์œ„ ํƒญ์œผ๋กœ ์ฐฝ๊ตฌ๋ฅผ ์ผ์›ํ™”(๋ ˆ์ด๋ธ” ์˜คํƒ€ ๊ต์ • ์™„๋ฃŒ).
- ์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰ ๋ฐ ์ฝ”๋“œ ์—ฐ๋™: ๋‹จ์ฒด์› ์ฐธ๊ฐ€ ์ ‘์ˆ˜ ์‹œ, ๋ฐฑ์—”๋“œ /api/groups/search๋ฅผ ํ†ตํ•ด ๋‹จ์ฒด๋ช…/์ฝ”๋“œ๋กœ ์‹ค์‹œ๊ฐ„ ๋งค์นญ ์กฐํšŒ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ํ•ด๋‹น ํŒ€ ์ฝ”๋“œ๋ฅผ ํผ์— ๋ฐ”์ธ๋”ฉ.
- ์ •์› ์ง„ํ–‰๋„ ์ฆ‰์‹œ ๋™๊ธฐํ™”: ์ฐธ๊ฐ€ ์‹ ์ฒญ(Individual ๋ฐ Group) ์„ฑ๊ณต ์ฝœ๋ฐฑ ์‹œ์  ์ฆ‰์‹œ, ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ current_participants++ ๊ฐ€์‚ฐ ํ›„ updateEventRecruitingProgress()๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์—ฌ ์ •์› ๋Œ€๋น„ ๋ชจ์ง‘ ์ง„ํ–‰๋ฅ  ๋ฐ”๋ฅผ ์‹ค์‹œ๊ฐ„ ๊ฐฑ์‹ .
- ๋Œ€ํ‘œ์ž ํ†ตํ•ฉ ์ •๋ณด ๋ฐ”์ธ๋”ฉ: ๋‹จ์ฒด ์ƒ์„ฑ ์‹œ ๋Œ€ํ‘œ์ž ์„ฑํ•จ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ๋Œ€ํ‘œ์ž 1์ธ์— ๋Œ€ํ•œ ์ฐธ๊ฐ€์ƒ์„ธ ์ธ์ ์‚ฌํ•ญ(์„ฑ๋ณ„, ํ‹ฐ์…”์ธ , ์—ฐ๋ฝ์ฒ˜, ์ƒ๋…„์›”์ผ ๋“ฑ)์„ ํผ ๋ˆ„๋ฝ ์—†์ด Workers DB์— ์ ์žฌ.
- ๋‹จ์ฒด์› ์กฐํšŒ ์‹œ ๋ฌด๊ฒฐ์„ฑ JOIN: ๋Œ€ํ‘œ์ž ๋‹จ์ฒด์› ํ˜„ํ™ฉ ์กฐํšŒ API ๊ตฌ๋™ ์‹œ, DB์—์„œ event_group_applications์™€ event_groups ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•˜์—ฌ ๋‹จ์ฒด ์ฝ”๋“œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์‹ค์ œ ๋‹จ์ฒด๋ช…(group_name)๊นŒ์ง€ ๋ฌด๊ฒฐ์„ฑ ์žˆ๊ฒŒ ๋ Œ๋”๋ง.

๐Ÿ“„ 11. ์ฃผ์š” ๋น„์ฆˆ๋‹ˆ์Šค API ํŽ˜์ด๋กœ๋“œ ๋ช…์„ธ (API Payload Spec)

๊ฐœ๋ฐœ์ž๊ฐ€ ์ฆ‰์‹œ Mocking ๋ฐ ์ธํ„ฐํŽ˜์ด์Šค ์—ฐ๋™์— ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑํ•œ ์‹ ๊ทœ API ํ•ต์‹ฌ ํŽ˜์ด๋กœ๋“œ ์ •์˜์ž…๋‹ˆ๋‹ค.

1) POST /api/groups (๋‹จ์ฒด ๊ฐœ์„ค ์ƒ์„ธ ์š”์ฒญ)

// Request Payload (JSON) { "name": "๋น„๋ฐ” ๋Ÿฌ๋‹ ํฌ๋ฃจ ์„œ์šธ์ง€๋ถ€", "leader": "ํ™๊ธธ๋™", "email": "gildong@example.com", "password": "leader_secure_password", "eventId": "event-uuid-1029348", "phone": "010-1234-5678", "gender": "male", "tshirt": "L", "birth": "1995-04-12" } // Response Payload (JSON) { "success": true, "code": "TEAM-58193", "password": "leader_secure_password" }

2) GET /api/groups/search (์‹ค์‹œ๊ฐ„ ๋‹จ์ฒด/ํŒ€ ๊ฒ€์ƒ‰ API)

// Request Query String ?eventId=event-uuid-1029348&query=๋น„๋ฐ” // Response Payload (JSON) { "groups": [ { "code": "TEAM-58193", "event_id": "event-uuid-1029348", "name": "๋น„๋ฐ” ๋Ÿฌ๋‹ ํฌ๋ฃจ ์„œ์šธ์ง€๋ถ€", "leader": "ํ™๊ธธ๋™", "created_at": "2026-06-25T03:30:20Z" } ] }

3) POST /api/events/:id/feedbacks (SNS Q&A ๋Œ“๊ธ€ ๋“ฑ๋ก)

// Request Payload (JSON) { "userName": "๋Ÿฌ๋„ˆ๊ธธ๋™", "rating": "5", "comment": "์ฝ”์Šค ์กฐ์„ฑ์ด ์ •๋ง ์ž˜ ๋˜์–ด ์žˆ๋„ค์š”! ์™„์ฃผ ๋ชฉํ‘œ๋กœ ๋›ฐ์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.", "turnstile_token": "XXXXX-turnstile-token-XXXXX" } // Response Payload (JSON) { "success": true, "feedback": { "id": "feedback-uuid-88294", "user_name": "๋Ÿฌ๋„ˆ๊ธธ๋™", "rating": 5, "body": "์ฝ”์Šค ์กฐ์„ฑ์ด ์ •๋ง ์ž˜ ๋˜์–ด ์žˆ๋„ค์š”! ์™„์ฃผ ๋ชฉํ‘œ๋กœ ๋›ฐ์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.", "created_at": "2026-06-25T03:40:00Z" } }

๐ŸŽฏ ๋น„์ฆˆ๋‹ˆ์Šค ์ •๋ ฌ ๋งคํŠธ๋ฆญ์Šค (Alignment Matrix)

๋น„์ฆˆ๋‹ˆ์Šค ์Šฌ๋กœ๊ฑด: Events first, Community second, Reputation always.
AI ์ฝ”์นญ, AR/VR, ๋ธ”๋ก์ฒด์ธ ๋“ฑ ๋น„ํ•ต์‹ฌ ๋˜๋Š” ๋ฏธ์„ฑ์ˆ™ ๊ธฐ๋Šฅ์€ MVP ํ•ต์‹ฌ ๋™์„ ์„ ๋ฐฉํ•ดํ•˜์ง€ ์•Š๋„๋ก ํ›„์ˆœ์œ„(Future Phase)๋กœ ๊ฒฉ๋ฆฌํ•˜์—ฌ ์ˆจ๊น€ ๋ฐ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
์ˆœ์œ„ ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๊ตฌ์„ฑ์š”์†Œ ์ •ํ•ฉ์„ฑ ๋ถ„๋ฅ˜ ํ˜„ํ™ฉ ๋ฐ ์กฐ์น˜ ๋ฐฉํ–ฅ
1 ์ด๋ฒคํŠธ ๋ฐœ๊ฒฌ (Explore) Core Business ์›น ๋ฐ ๋ชจ๋ฐ”์ผ ์•ฑ ๋‚ด ํƒ์ƒ‰ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์™„๋ฃŒ.
2 ์ฐธ๊ฐ€ ์‹ ์ฒญ (Registration) Core Business ๋‹ค๋‹จ๊ณ„ ๊ฐ€์ž… ํผ ๋ฐ DB ๊ธฐ๋ก ์—ฐ๋™ ์™„๋ฃŒ.
3 ๊ณต๊ฐœ ์ด๋ฒคํŠธ ํ—ˆ๋ธŒ Public Hub ์Šคํฐ์„œ ๋ฐฐ๋„ˆ ๋ฐ Turnstile ๋ณด์•ˆ ๋ด‡ ๋ฐฉ์ง€ ๊ฒฐํ•ฉ ์™„๋ฃŒ.
4 Organizer OS Organizer OS ๋Œ€ํšŒ ์ƒ์„ฑ(4๋‹จ๊ณ„ ์œ„์ž๋“œ) ๋ฐ ์ •์‚ฐ ๋Œ€์žฅ ์–ด๋“œ๋ฏผ ์Šน์ธ ์—ฐ๊ณ„.
5 Race Kit & BIB Race Kit ์–ด๋“œ๋ฏผ ํ‚คํŠธ/BIB ํŒจํ‚น ๋ฐฐ์ • ๊ธฐ๋Šฅ ํ™œ์„ฑํ™” ์™„๋ฃŒ.
- AI ์ฝ”์นญ / AR/VR / ๋ธ”๋ก์ฒด์ธ Future Phase MVP ๋ฒ”์œ„ ์™ธ๋กœ ์„ค์ •ํ•˜์—ฌ ๋ฉ”์ธ ํ™”๋ฉด์—์„œ ์ˆจ๊น€ ๊ฒฉ๋ฆฌ ์กฐ์น˜.

๐Ÿ”‘ ์ธ์ฆ ๋ฐ ๋ณด์•ˆ ์ฒ˜๋ฆฌ

VivaSport API๋Š” ๊ฐ•๋ ฅํ•œ ๋ณด์•ˆ์„ ์œ„ํ•ด Hono ๋ฏธ๋“ค์›จ์–ด๋กœ ๋ณดํ˜ธ๋˜๋ฉฐ, ํ˜ธ์ถœ ์œ ํ˜•์— ๋”ฐ๋ผ ์„ธ ๊ฐ€์ง€ ์ธ์ฆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Firebase JWT Bearer Auth

์ผ๋ฐ˜ ์œ ์ €์šฉ API(๋Œ€ํšŒ ์ฐธ๊ฐ€, ๋‚ด ์ •๋ณด ์ˆ˜์ •, ๋‹จ์ฒด ๊ฐœ์„ค ๋“ฑ) ํ˜ธ์ถœ ์‹œ, Firebase Auth์—์„œ ๋ฐœํ–‰ํ•œ ID ํ† ํฐ์„ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

Authorization: Bearer <Firebase_ID_Token>

Admin Console Auth Token

์–ด๋“œ๋ฏผ์šฉ API(์ •์‚ฐ ์ฒ˜๋ฆฌ, ๊ฐ์‚ฌ ๋กœ๊ทธ ์กฐํšŒ, ์‚ฌ์šฉ์ž ์ •์ง€ ๋“ฑ) ํ˜ธ์ถœ ์‹œ, ํ…Œ๋„ŒํŠธ ๊ฒ€์ฆ์ด ์™„๋ฃŒ๋œ Admin ์ „์šฉ ํ† ํฐ์„ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

Authorization: Bearer <Admin_Access_Token>

โšก API ์—”๋“œํฌ์ธํŠธ ๋ช…์„ธ

์œ ํ˜• ์—”๋“œํฌ์ธํŠธ ์„ค๋ช… ๋ฐ ๊ถŒํ•œ ์ˆ˜์ค€ ๋ด‡ ๋ฐฉ์ง€
GET /api/events ์ „์ฒด ๋Œ€ํšŒ ๋ชฉ๋ก ์กฐํšŒ (๋น„ํšŒ์›/์†Œ๋น„์ž์šฉ) -
POST /api/events ์ƒˆ๋กœ์šด ๋Œ€ํšŒ ๊ฐœ์„ค (์š”๊ธˆ์ œ ๊ตฌ๋… ํ•œ๋„ ํ•„ํ„ฐ ๋ฏธ๋“ค์›จ์–ด ์ž‘๋™) -
GET /api/events/:id/feedbacks ํŠน์ • ๋Œ€ํšŒ์˜ Q&A ๋Œ“๊ธ€/ํ›„๊ธฐ ๋ชฉ๋ก ์กฐํšŒ (๋น„ํšŒ์›/์ „์ฒด ๊ณต๊ฐœ) -
POST /api/events/:id/feedbacks ๋Œ€ํšŒ Q&A ๋Œ“๊ธ€/์˜๊ฒฌ ์‹ ๊ทœ ๋“ฑ๋ก (JWT optional) Turnstile
POST /api/reports ๋Œ“๊ธ€, ๋ฃจํŠธ, ์ฃผ์ตœ์ž ๋“ฑ ๋ถˆํŽธ/์ŠคํŒธ ์‹ ๊ณ  ์ ‘์ˆ˜ Turnstile
GET /api/groups/search ๋‹จ์ฒด/ํŒ€ ์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰ (๋‹จ์ฒด๋ช…/์ฝ”๋“œ ๋งค์นญ, eventId ๋ฐ query ์ง€์›) -
GET /api/groups/my ๋‚ด๊ฐ€ ๊ฐœ์„คํ•œ ๋‹จ์ฒด ๋ชฉ๋ก ์กฐํšŒ (Firebase Auth JWT ๊ฒ€์ฆ) -
POST /api/groups ์‹ ๊ทœ ๋‹จ์ฒด ๊ฐœ์„ค (๋Œ€ํ‘œ์ž ์ธ์ ์‚ฌํ•ญ ๋ฐ ๊ด€๋ฆฌ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ ์„ค์ •) -
POST /api/groups/:code/apply ๋‹จ์ฒด์› ์ฐธ๊ฐ€ ์‹ ์ฒญ ๋“ฑ๋ก (ํŒ€ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ํŠน์ • ๋‹จ์ฒด์— ์†Œ์†) -
GET /api/groups/:code/members ํŠน์ • ๋‹จ์ฒด ๊ฐ€์ž… ๋ฉค๋ฒ„ ๋ชฉ๋ก ์กฐํšŒ (๋น„๋ฐ€๋ฒˆํ˜ธ ํ˜น์€ ๋‹จ์ฒด์žฅ ๊ถŒํ•œ, JOIN ๊ฒฐ๊ณผ ๋‹จ์ฒด๋ช… ๋ฐ˜ํ™˜) -
PATCH /api/groups/:code/members/:memberId ๋‹จ์ฒด ์‹ ์ฒญ ๋‹จ์ฒด์› ์ˆ˜๋ฝ/๋ฐ˜๋ ค (๋Œ€ํ‘œ์ž ๊ถŒํ•œ ๊ฒ€์ฆ ํ•„์ˆ˜) -
GET /api/admin/reports ์ ‘์ˆ˜๋œ ์‹ ๊ณ  ๋ฐ 1:1 ๊ณ ๊ฐ์ง€์› ๋ชฉ๋ก ์กฐํšŒ (์–ด๋“œ๋ฏผ ์ „์šฉ) -
GET /api/admin/feedbacks/:id ์‹ ๊ณ  ๋Œ€์ƒ์ด ๋œ Q&A ํ”ผ๋“œ๋ฐฑ ๋Œ“๊ธ€ ์›๋ณธ ๋‹จ๊ฑด ์ƒ์„ธ ์กฐํšŒ -
DELETE /api/admin/feedbacks/:id ์‹ ๊ณ ๋œ ์ŠคํŒธ์„ฑ Q&A ๋Œ“๊ธ€ ์˜๊ตฌ ์‚ญ์ œ ๋ฐ ๊ฐ์‚ฌ๋กœ๊ทธ ๊ธฐ๋ก -
PATCH /api/admin/users/:id/status Abuse ์•…์„ฑ ์œ ์ € ์ฐจ๋‹จ(Blocked) ์กฐ์น˜ (์ž„์‹œ provider ์ปฌ๋Ÿผ ๋งคํ•‘) -

๐Ÿ“ฑ ์†Œ๋น„์ž React ์•ฑ ๊ธฐ๋Šฅ ๋ฆฌ์ŠคํŠธ

๊ธฐ๋Šฅ ๋ช…์นญ ์ƒ์„ธ ๋‚ด์šฉ ์ƒํƒœ
ํ™ˆ (๋Œ€ํšŒ ์นด๋“œ ๋ชฉ๋ก) ๋Œ€ํšŒ ๋ฆฌ์ŠคํŠธ ์นด๋“œ ๋ฐ ์‹ค์‹œ๊ฐ„ ์ง„ํ–‰๋ฅ  ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฐ” ์ ์šฉ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋Œ€ํšŒ ์ฐพ๊ธฐ ๋ฐ ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ ๋Œ€ํšŒ๋ช… ๊ฒ€์ƒ‰, ์ข…๋ชฉ๋ณ„ ์นดํ…Œ๊ณ ๋ฆฌ, ๋‚œ์ด๋„ ๋ฐ ์ •๋ ฌ ๊ธฐ๋Šฅ ์—ฐ๊ณ„ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
์ด๋ฒคํŠธ ์ƒ์„ธ ์ •๋ณด ๋ฐ ์‹ ์ฒญ ์ƒ์„ธ ์•ˆ๋‚ด ์ •๋ณด ๋ฐ Firebase Auth ๊ธฐ๋ฐ˜ ๋‹ค๋‹จ๊ณ„ ์ฐธ๊ฐ€ ์‹ ์ฒญ์„œ ์ž‘์„ฑ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋‹จ์ฒด/ํŒ€ ๊ด€๋ฆฌ (MyGroups) ๊ฐœ์„ค ๋‹จ์ฒด ์‹ค์‹œ๊ฐ„ ์กฐํšŒ, ๋‹จ์ฒด ์ฝ”๋“œ ๊ฐœ์„ค ๋ฐ ๋‹จ์ฒด์› ์Šน์ธ/๋ฐ˜๋ ค (API ์—ฐ๊ณ„) โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋งˆ์ดํŽ˜์ด์ง€ API ์—ฐ๋™ ์‹ ์ฒญ ์ƒํƒœ ์‹ค์‹œ๊ฐ„ ๋™๊ธฐํ™” ๋ฐ ๋งˆ์ผ๋ฆฌ์ง€ ์ ๋ฆฝ ์กฐํšŒ ๊ธฐ๋Šฅ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
GNB ๋ชจ๋ฐ”์ผ ์•ˆ์ „ ๋งˆ์ง„ ๋…ธ์น˜๊ฐ€ ์—†๋Š” ์ผ๋ฐ˜ ํ™”๋ฉด์—์„œ๋„ GNB ๋ ˆ์ด๋ธ” ํ…์ŠคํŠธ ์ž˜๋ฆผ ํ•ด๊ฒฐ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
ํ™ˆ ํƒ€์ดํ‹€ ์ค‘๋ณต ์ˆ˜์ • ์ƒ๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜ ํ—ค๋”์™€ ๋ณธ๋ฌธ ํƒ€์ดํ‹€์˜ 'VivaSport' ์ด์ค‘ ํ‘œ์‹œ ๊ต์ • โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋Œ€ํšŒ ์ƒ์„ฑ ์œ„์ž๋“œ (4๋‹จ๊ณ„) ๋Œ€ํšŒ ์ •๋ณด, Race ์ฝ”์Šค, ๊ฒฐ์ œ ๋ฐฉ์‹, ํฌ์Šคํ„ฐ ์—…๋กœ๋“œ 4๋‹จ๊ณ„ ๋งˆํฌ์—…/API ์—ฐ๊ณ„ โšก ๊ฐœ๋ฐœ๋Œ€๊ธฐ
์ฃผ์ตœ์ž ์ •์‚ฐ ๊ด€๋ฆฌ ๋Œ€์‹œ๋ณด๋“œ ๋Œ€ํšŒ ์ฃผ์ตœ ๊ธฐ์—…/๋‹จ์ฒด ๋Œ€์‹œ๋ณด๋“œ ๋ฐ ์ •์‚ฐ ์ด๋ ฅ ํ†ต๊ณ„ ์—ฐ๋™ โšก ๊ฐœ๋ฐœ๋Œ€๊ธฐ

๐ŸŒ ํผ๋ธ”๋ฆญ ์ด๋ฒคํŠธ ํ—ˆ๋ธŒ ๊ธฐ๋Šฅ ๋ฆฌ์ŠคํŠธ

๊ธฐ๋Šฅ ๋ช…์นญ ์ƒ์„ธ ๋‚ด์šฉ ์ƒํƒœ
๋Œ€ํšŒ ์†Œ์‹ ๋‰ด์Šค ์นด๋“œ ์ˆ˜์ง‘ ๋ด‡์ด ๋ฌผ์–ด์˜จ ๊ณต์‹ ๋งˆ๋ผํ†ค/๋Œ€ํšŒ ์†Œ์‹ ๋ฆฌ์ŠคํŠธ ์นด๋“œํ™” โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
์ง€๋„ ์•ฑ ์—ฐ๋™ & ์ฃผ์†Œ ๋ณต์‚ฌ ๊ฐœ์ตœ์ง€ ์ •๋ณด์˜ ํด๋ฆฝ๋ณด๋“œ ์›ํด๋ฆญ ๋ณต์‚ฌ ๋ฐ ๋„ค์ด๋ฒ„/์นด์นด์˜ค ์ง€๋„ ์—ฐ๊ณ„ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋ชจ์ง‘์œจ ์‹ค์‹œ๊ฐ„ ์นด์šดํ„ฐ ๋ชจ์ง‘์œจ ์ •์› ๋Œ€๋น„ ๋น„์œจ ์ธ๋””์ผ€์ดํ„ฐ ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ๋ Œ๋”๋ง โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋‹จ์ฒด ์ฐธ๊ฐ€ ์‹ ์ฒญ ๋Œ€ํšŒ ๋‹จ์ฒด ๊ฐ€์ž… ๋‹ค์ด์–ผ๋กœ๊ทธ ๊ฒ€์ƒ‰, ๋‹จ์ฒด ์ฝ”๋“œ ํด๋ฆฝ๋ณด๋“œ ๋ณต์‚ฌ ๋ชจ๋‹ฌ ์—ฐ๋™ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
Q&A ๋Œ“๊ธ€/ํ”ผ๋“œ๋ฐฑ ์ŠคํŒธ ์‹ ๊ณ  ๋Œ“๊ธ€ ์˜† ์‹ ๊ณ  ๋ฒ„ํŠผ์œผ๋กœ ํŒ์—… ๋‹ค์ด์–ผ๋กœ๊ทธ ํ™œ์„ฑํ™” ๋ฐ API ์ ‘์ˆ˜ ์—ฐ๊ณ„ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
Turnstile ๋ด‡ ๋ฐฉ์ง€ ๋ณดํ˜ธ Q&A ๋Œ“๊ธ€ ์ž‘์„ฑ๊ณผ ์‹ ๊ณ  ์ ‘์ˆ˜ ์‹œ ์•…์„ฑ ์ž๋™ ๋„๋ฐฐ ๋ฐฉ์ง€ ์บก์ฐจ ์œ„์ ฏ ํƒ‘์žฌ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋‹ค๊ตญ์–ด i18n ์™„์ „ ์ง€์› ํ•œ๊ตญ์–ด, ์˜์–ด ๋“€์–ผ ๋ Œ๋”๋ง ํ™•์žฅ ๋ฆฌ์†Œ์Šค ์ถ”๊ฐ€ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ

๐Ÿข ์–ด๋“œ๋ฏผ ์ฝ˜์†” ์›น ๊ธฐ๋Šฅ ๋ฆฌ์ŠคํŠธ

๊ธฐ๋Šฅ ๋ช…์นญ ์ƒ์„ธ ๋‚ด์šฉ ์ƒํƒœ
๋กœ๊ทธ์ธ ๋ฝ ์Šคํฌ๋ฆฐ & ์šฐํšŒ ์ˆ˜๋™ ํ† ํฐ ์ž…๋ ฅ๋ž€์— ์ „์šฉ ํ‚ค ์ฃผ์ž…์„ ํ†ตํ•ด Mock ๋ฐ์ดํ„ฐ ๋ชจ๋“œ ์™„๋ฒฝ ์ง„์ž… โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
์ •์‚ฐ ๋Œ€์žฅ ๋ชฉ๋ก ๋ฐ ์ด๋ ฅ ์ฐธ๊ฐ€๋น„ ์ด์•ก, ์ˆ˜์ˆ˜๋ฃŒ, ์„ธ๊ธˆ ๋ฐ ์ฃผ์ตœ์ž ์‹ค ์ˆ˜๋ น์•ก ์ •์‚ฐ ์Šน์ธ ํ”„๋กœ์„ธ์Šค โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๊ฐ์‚ฌ ๋กœ๊ทธ (Audit Logs) ๊ด€๋ฆฌ์ž ํ–‰์œ„์— ๋”ฐ๋ฅธ IP, ์ผ์‹œ, ํ–‰์œ„ ๋‚ด์—ญ ์กฐํšŒ ๋ฐ ํƒ์ƒ‰ ๋Œ€์‹œ๋ณด๋“œ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋ฐฐ๋„ˆ ๊ด‘๊ณ  ์Šฌ๋กฏ ๊ด€๋ฆฌ ํ™ˆ ํ™”๋ฉด ๋ฉ”์ธ ๋ฐ ์‚ฌ์ด๋“œ ๊ด‘๊ณ  ๋ฐฐ๋„ˆ ์บ ํŽ˜์ธ ์ƒ์„ฑ, ์Šน์ธ, ๊ธฐ๊ฐ„ ํ†ต์ œ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
์‹ ๊ณ  ๊ด€๋ฆฌ (Support / Abuse) ์ผ๋ฐ˜ ์ ‘์ˆ˜๋œ ๋ถˆํŽธ์‚ฌํ•ญ ๋ฐ ๋ฌธ์˜ ๋‚ด์—ญ ์Šน์ธ, ๊ธฐ๊ฐ ์ƒํƒœ ์กฐ์น˜ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
Q&A ์›๋ณธ fetch & ์‚ญ์ œ ์กฐ์น˜ ์‹ ๊ณ  ๋‚ด์—ญ ์ค‘ ๋Œ“๊ธ€/ํ”ผ๋“œ๋ฐฑ ์›๋ณธ ์กฐํšŒ ํ›„ ์ฆ‰๊ฐ ์‚ญ์ œ ๋ฐ ํ•ด๋‹น ์‹ ๊ณ  ์™„๋ฃŒ ์ฒ˜๋ฆฌ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
์ž‘์„ฑ์ž ๊ณ„์ • ์ฐจ๋‹จ (Block) ์‹ ๊ณ  ์ƒ์„ธ ์ฐฝ์—์„œ ์ž‘์„ฑ์ž ๊ณ„์ •์„ ์ฆ‰์‹œ ์ •์ง€ ์กฐ์น˜ ์—ฐ๊ณ„ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋ผ์ดํŠธ ๋ชจ๋“œ ๊ฐ€๋…์„ฑ ํŒจ์น˜ ์ธ๋ผ์ธ white ์ƒ‰์ƒ์œผ๋กœ ๋ผ์ดํŠธ ํ…Œ๋งˆ ์ „ํ™˜ ์‹œ ํ…์ŠคํŠธ ๋ฌปํžˆ๋Š” ๊ฐ€๋…์„ฑ ์†Œ์‹ค ํ•ด๊ฒฐ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๊ฐ€์ž… ํ…Œ๋„ŒํŠธ ํƒ€์ž… ์ •๋ฆฌ ํšŒ์›๊ฐ€์ž… ํ•ญ๋ชฉ ๋‚ด ๋ถ€์ ์ ˆํ•œ '๊ฐœ์ธ' ํƒ€์ž…์„ ๋ฐฐ์ œํ•˜๊ณ  ๋ฒ•์ธ, ๊ณต๊ณต๊ธฐ๊ด€ ๋“ฑ์œผ๋กœ ํ†ต์ผ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
์ •์‚ฐ ์ž๋™ ์ด์ฒด API (PG) ์ •์‚ฐ ์Šน์ธ ์‹œ ์‹ค์‹œ๊ฐ„ ๊ณ„์ขŒ์ด์ฒด ์ž๋™ ์—ฐ๋™ API ๊ฐœ๋ฐœ โšก ๊ฐœ๋ฐœ๋Œ€๊ธฐ

โš™๏ธ Cloudflare Workers & DB ๊ธฐ๋Šฅ ๋ฆฌ์ŠคํŠธ

๊ธฐ๋Šฅ ๋ช…์นญ ์ƒ์„ธ ๋‚ด์šฉ ์ƒํƒœ
์š”๊ธˆ์ œ ๊ตฌ๋… ๊ฒŒ์ดํŒ… ๋ฏธ๋“ค์›จ์–ด ๋ฌด๋ฃŒ ์š”๊ธˆ์ œ ์œ ์ €์˜ ์œ ๋ฃŒ ๋Œ€ํšŒ ์ƒ์„ฑ ์ฐจ๋‹จ, ๋“ฑ๊ธ‰๋ณ„ ์›”๊ฐ„ ์œ ๋ฃŒ๋Œ€ํšŒ ๊ฐœ์„ค ๊ฐœ์ˆ˜ ์ œํ•œ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋‹จ์ฒด/์ฐธ๊ฐ€ ์‹ ์ฒญ D1 ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ ๋‹จ์ฒด๋ช…, ๋‹จ์ฒด์žฅ ์ •๋ณด ๋ฐ ์‹ ์ฒญ์ž ์ •๋ณด๋ฅผ D1 DB ์˜๊ตฌ ํ…Œ์ด๋ธ”๋กœ ๋Ÿฐํƒ€์ž„ ์ž๋™ ์ƒ์„ฑ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
๋‹จ์ฒด ๋ฐ ๊ฐ€์ž…์‹ ์ฒญ API 6์ข… ๋‚ด ๋‹จ์ฒด ์กฐํšŒ, ์ฐธ๊ฐ€ ์‹ ์ฒญ, ๊ฐ€์ž…์ž ์Šน์ธ/๋ฐ˜๋ ค ๋“ฑ Workers API ๊ตฌ์ถ• โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
Turnstile ๊ฒ€์ฆ ์œ ์—ฐํ™” ๋น„ํšŒ์› ์ƒํƒœ์˜ Q&A ํ”ผ๋“œ๋ฐฑ ์ž‘์„ฑ ๋ฐ ์‹ ๊ณ ๋ฅผ ์œ„ํ•œ Turnstile ๋ด‡ ๋ณดํ˜ธ API ์ถ”๊ฐ€ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
Google Auth CORS ์šฐํšŒ Google API ์—ฐ๋™ ์‹œ X-Viva-Bypass ํ—ค๋” ์ค‘๋ณต ์ „์ž… ๋ฐฉ์ง€ ์šฐํšŒ ์กฐ์น˜ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
์Šคํฌ์ธ  ODF ํ‘œ์ค€ ์ฝ”๋“œ ๋งคํ•‘ ๋Ÿฌ๋‹->ATH, ์‚ฌ์ดํด->CYC ๋“ฑ์˜ ์ข…๋ชฉ ์ฝ”๋“œ๋ฅผ ์ €์žฅ ์‹œ ๋ฐฑ์—”๋“œ ๋‹จ์—์„œ ์ž๋™ ODF ๋งคํ•‘ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
์œ ์ง€๋ณด์ˆ˜ ์ค‘ analytics ์บก์ฒ˜ ์šฐํšŒ ์ ๊ฒ€ ์ƒํƒœ์—์„œ๋„ ์ต๋ช… ๋ถ„์„ ๋กœ๊ทธ ์ˆ˜์ง‘ Endpoint(/api/analytics/collect) ์ •์ƒ ๋™์ž‘ โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ
D1 ์ธ๋ฑ์Šค ๋ฐ ์—ฃ์ง€ ์บ์‹ฑ ์ตœ์ ํ™” ๋Œ€์šฉ๋Ÿ‰ ์ฟผ๋ฆฌ ํŠœ๋‹์„ ์œ„ํ•œ D1 DB ์ธ๋ฑ์Šค ์„ค๊ณ„ ์ ์šฉ ๋ฐ Hono ์—ฃ์ง€ ์บ์‹œ(Cache-Control) ํ™œ์„ฑํ™” โœ“ ๊ฐœ๋ฐœ์™„๋ฃŒ

๐Ÿงช ์‹œ์Šคํ…œ๋ณ„ QA ํ…Œ์ŠคํŠธ ๋งคํŠธ๋ฆญ์Šค ๊ฒฐ๊ณผ

๊ฐ ์„œ๋น„์Šค ๋ชจ๋“ˆ์— ๋Œ€ํ•ด ์ˆ˜ํ–‰๋œ ์ฃผ์š” ๊ธฐ๋Šฅ ๊ฒ€์ฆ ์‹œ๋‚˜๋ฆฌ์˜ค ๋ฐ ํ†ต๊ณผ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. (์ตœ์ข… ๊ฒ€์ฆ์ผ: 2026-06-24)

๋Œ€์ƒ ์‹œ์Šคํ…œ ๊ฒ€์ฆ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ ๊ฒฐ๊ณผ
์†Œ๋น„์ž React ์›น ๋Œ€ํšŒ ํ•„ํ„ฐ ๊ฒ€์ƒ‰ ๋ฐ ๋‹ค๊ตญ์–ด ๋ฆฌ์†Œ์Šค ์ ์šฉ ํ•œ๊ตญ์–ด/์˜์–ด ์ „ํ™˜ ์‹œ ํ…์ŠคํŠธ ๊นจ์ง ์—†์Œ Pass
์ด๋ฒคํŠธ ํ—ˆ๋ธŒ Q&A ํ”ผ๋“œ๋ฐฑ ์ œ์ถœ ๋ฐ Turnstile ์ธ์ฆ ์ธ์ฆ ์™„๋ฃŒ ์‹œ์—๋งŒ ๋Œ“๊ธ€ DB ๋“ฑ๋ก ๋ฐ ๋…ธ์ถœ Pass
๊ด€๋ฆฌ์ž ์ฝ˜์†” ์ •์‚ฐ ๋Œ€์žฅ ๋ชฉ๋ก ์กฐํšŒ ๋ฐ ์ˆ˜๋™ ์ •์‚ฐ ์Šน์ธ ์ •์‚ฐ ์Šน์ธ ์™„๋ฃŒ ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ๊ฐ์‚ฌ๋กœ๊ทธ ๋“ฑ๋ก Pass
Workers API ๋Œ€ํšŒ ์‹๋ณ„์ฝ”๋“œ(event_code) ์ €์žฅ ๋ฐ ์ ‘์ˆ˜๋ฒˆํ˜ธ sequence ๋ฐœ๊ธ‰ ๋Œ€ํšŒ์ฝ”๋“œ-์ข…๋ชฉ์ฝ”๋“œ-GRP-seq ํ˜•ํƒœ ์ ‘์ˆ˜๋ฒˆํ˜ธ ๋ฐœ๊ธ‰ Pass
๋ชจ๋ฐ”์ผ ์•ฑ (RN) ์ˆ˜๋™ ๋กœ๊ทธ์ธ ๋ฐ”์ดํŒจ์Šค ๋ฐ ๋ฝ์Šคํฌ๋ฆฐ ํ•ด์ œ "viva-mock-2026" ์ž…๋ ฅ ์‹œ ๋ฉ”์ธ ํ™”๋ฉด ์ง„์ž… Pass

๐Ÿšจ ๊ฒฐํ•จ & ๊ฐœ์„  ์กฐ์น˜ ๋งคํŠธ๋ฆญ์Šค

ํ˜„์žฌ ํ”Œ๋žซํผ์—์„œ ๋ฐœ๊ฒฌ๋˜์–ด ์กฐ์น˜๋œ ์ฃผ์š” ๊ธฐ์ˆ  ๊ฒฐํ•จ ๋ฐ ๊ฐœ์„ ์‚ฌํ•ญ ๋‚ด์—ญ์ž…๋‹ˆ๋‹ค. (TRACKING_MATRIX.md์™€ ๋™๊ธฐํ™”๋จ)

๊ฒฐํ•จ ID ์œ ํ˜• ์‹œ์Šคํ…œ ๊ฒฐํ•จ / ๊ฐœ์„  ์ƒ์„ธ ๋‚ด์šฉ ์ƒํƒœ
DEF-D1-001 Data/Schema Workers / D1 ๋Œ€ํšŒ ์‹๋ณ„์ฝ”๋“œ(event_code) ์ €์žฅ ๋ถ€์žฌ ๋ฐ DB ์Šคํ‚ค๋งˆ ์ปฌ๋Ÿผ ์œ ์‹ค ํ•ด๊ฒฐ ์กฐ์น˜์™„๋ฃŒ
DEF-API-002 Improvement Workers GUID ๊ธฐ๋ฐ˜ ์ ‘์ˆ˜๋ฒˆํ˜ธ ๋Œ€์‹  ๋Œ€ํšŒ ์ฝ”๋“œ ๋ฐ ์ข…๋ชฉ ์ฝ”๋“œ๋ฅผ ์กฐํ•ฉํ•˜๋Š” ์ƒ์„ฑ ๊ทœ์น™ ๊ณ ๋„ํ™” ์กฐ์น˜์™„๋ฃŒ
DEF-UX-002 UX/UI React Web / Admin ๋งˆ์ดํŽ˜์ด์ง€ ๋‚ด FAQ ๋ฐ์ดํ„ฐ ์–ด๋“œ๋ฏผ ์„ค์ •๊ฐ’ ๋™์  ์—ฐ๋™ ๋ฐ localStorage ๋™๊ธฐํ™” ๊ฒฐํ•จ ํ•ด๊ฒฐ ์กฐ์น˜์™„๋ฃŒ
DEF-ADMIN-003 Improvement Admin Console ์–ด๋“œ๋ฏผ ์ฝ˜์†” ๋‚ด ๋‹จ์ฒด ๋ฐ ํฌ๋ฃจ ๊ด€๋ฆฌ ์‹ค๋ฐ์ดํ„ฐ ์—ฐ๋™(D1 DB) ๋ฐ ์ƒํƒœ ๋ณ€๊ฒฝ(ํ™œ๋™์Šน์ธ/์ •์ง€/๋Œ€๊ธฐ) ์ œ์–ด ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์™„๋ฃŒ ์กฐ์น˜์™„๋ฃŒ
DEF-DEP-004 Deployment Pages / Submodule .gitmodules ๋ˆ„๋ฝ์œผ๋กœ ์ธํ•œ Cloudflare Pages ๋ฐฐํฌ ์„œ๋ธŒ๋ชจ๋“ˆ ํด๋ก  ์˜ค๋ฅ˜ ๋ณต๊ตฌ ์กฐ์น˜์™„๋ฃŒ

๐Ÿ“ ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ & ๋ณด์•ˆ ์ง€์นจ (Release & Security)

๐Ÿ“ข v1.2.5 ํ•ซํ”ฝ์Šค ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ (2026-06-25)

  • ๊ฐ์‚ฌ ๋กœ๊ทธ ํƒญ ๋ ˆ์ด์•„์›ƒ ๋ฐ ์ขŒ์šฐ ์—ฌ๋ฐฑ ๋ฌด๊ฒฐ์„ฑ ํ™•๋ณด: ๊ธด ๋กœ๊ทธ ํ–‰์ด๋‚˜ ํ…Œ์ด๋ธ” ์—ด ํ™•์žฅ์œผ๋กœ ์ธํ•ด ๋ถ€๋ชจ flex item์ด ๋ทฐํฌํŠธ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ์‚์ ธ๋‚˜๊ฐ€๋ฉฐ ๊ฐ€๋กœ ํŒจ๋”ฉ์ด ์œ ์‹ค๋˜๋˜ ํ˜„์ƒ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, `.content-pane` ์— `overflow: auto`๋ฅผ ์„ ์–ธํ•˜๊ณ  `.table-container` ์— `width: 100%; overflow-x: auto; box-sizing: border-box;`๋ฅผ ์ ์šฉํ•˜์—ฌ ์นด๋“œ ๋‚ด๋ถ€์—์„œ๋งŒ ์Šคํฌ๋กค์ด ๋ฐœ์ƒํ•˜๋„๋ก ๋ณด์™„, ์–ด๋– ํ•œ ํ™”๋ฉด ํฌ๊ธฐ์—์„œ๋„ 32px ์˜ ์•ˆ์ •์ ์ธ ์ขŒ์šฐ ์—ฌ๋ฐฑ์ด ๋ณด์žฅ๋˜๊ฒŒ ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์‹ค์‹œ๊ฐ„ ๋กœ๊ทธ ๋ชจ๋‹ˆํ„ฐ ์ฐฝ ์„ธ๋กœ ๊ธธ์ด ํ™•์žฅ: ์–ด๋“œ๋ฏผ ์‹ค์‹œ๊ฐ„ ๋กœ๊ทธ ๋ฐ API ์ŠคํŠธ๋ฆผ ๋ชจ๋‹ˆํ„ฐ๋ง ์ฐฝ(`#system-log-terminal`)์˜ ์„ธ๋กœ ๋†’์ด๋ฅผ ๊ธฐ์กด `350px` ์—์„œ `550px` ๋กœ ๋Œ€ํญ ์—ฐ์žฅํ•˜์—ฌ, ํ•œ ๋ˆˆ์— ๋” ๋งŽ์€ ํŠธ๋žœ์žญ์…˜ ๊ธฐ๋ก์„ ์›ํ™œํžˆ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐ€๋…์„ฑ์„ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฒ„์ „ ์บ์‹ฑ ๋ฌดํšจํ™” ๊ฐฑ์‹ : ์–ด๋“œ๋ฏผ ํ˜ธ์ถœ ๋ฒ„์ „์„ `admin.css?v=1.2.5` ๋ฐ `admin.js?v=1.2.5`๋กœ ๊ฒฉ์ƒํ•˜์—ฌ ์ตœ์‹  ๋กœ์ปฌ ์บ์‹œ ๊ฐฑ์‹ ์„ ๊ฐ•์ œํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ข v1.2.4 ํ•ซํ”ฝ์Šค ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ (2026-06-25)

  • ์–ด๋“œ๋ฏผ CSS ์„ ํƒ์ž ๋ฌธ๋ฒ• ํ‘œ์ค€ํ™” ๋ฐ ํ˜ธํ™˜์„ฑ ๋ณต๊ตฌ: ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์—์„œ ํŒŒ์‹ฑ ์—๋Ÿฌ๋ฅผ ์œ ๋ฐœํ•˜๋Š” ๋ถˆ์•ˆ์ •ํ•œ ๋ ˆ๋ฒจ 4 ๊ฐ€์ƒ ํด๋ž˜์Šค ๋ณตํ•ฉ ์„ ํƒ์ž(:not ์„ ํƒ์ž ์ž์† ๊ตฌ์กฐ)๋“ค์„ ํ‘œ์ค€ CSS ๊ทœ๊ฒฉ์œผ๋กœ ์žฌ์ž‘์„ฑํ•˜์—ฌ, ๋ผ์ดํŠธ ๋ชจ๋“œ ์˜ค๋ฒ„๋ผ์ด๋“œ ์Šคํƒ€์ผ ํ•ด์„์ด ๋ฌด์‹œ๋˜๋˜ ํŒŒ์‹ฑ ์žฅ์•  ๊ฒฐํ•จ์„ ์™„์ „ํžˆ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ตฌ๋… ๋ฐ ์š”๊ธˆ์ œ ์„ค์ • Drawer ์‹œ์ธ์„ฑ ํŒจ์น˜: ์‚ฌ์šฉ์ž/์š”๊ธˆ์ œ ๊ตฌ๋… ํŽธ์ง‘ Drawer๋“ค์˜ ์ตœ์ƒ๋‹จ ์ปจํ…Œ์ด๋„ˆ ์ธ๋ผ์ธ ์Šคํƒ€์ผ ๋ฐ CSS ์˜ค๋ฒ„๋ผ์ด๋“œ์— `!important` ๋ฐฐ๊ฒฝ์ƒ‰์„ ๋ณด๊ฐ•ํ•˜์—ฌ, ๋ผ์ดํŠธ ๋ชจ๋“œ์—์„œ๋„ ์–ด๋‘์šด ๋‚จ์ƒ‰ ๋ฐฐ๊ฒฝ์œผ๋กœ ์œ ์ง€๋˜๋˜ ๊ฐ€์‹œ์„ฑ ๊ฒฐํ•จ์„ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ž…๋ ฅ ํ•„๋“œ ๋ฐ ๋ ˆ์ด๋ธ” ๊ฐ€๋…์„ฑ ์•ˆ์ •ํ™”: ์ธ๋ผ์ธ์œผ๋กœ ๋‹คํฌ ๋ชจ๋“œ ๊ณ„์—ด ์ƒ‰์ƒ์ด ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด ์žˆ๋˜ ์ž…๋ ฅ ์ฐฝ ๋ฐ ์„ ํƒ ์ฐฝ์˜ ์ธ๋ผ์ธ ์Šคํƒ€์ผ์„ CSS ๋ณ€์ˆ˜ ๊ธฐ๋ฐ˜์œผ๋กœ ์ „๋ฉด ์ „ํ™˜ํ•˜์—ฌ, ๋ผ์ดํŠธ ๋ชจ๋“œ์—์„œ ๊ธ€์ž๋‚˜ ๋ฐฐ๊ฒฝ์ด ํˆฌ๋ช… ํ˜น์€ ๋น„์ •์ƒ์ ์œผ๋กœ ๋…ธ์ถœ๋˜๋˜ ๋ช…์•”๋น„ ๊ฒฐํ•จ์„ ์ œ๊ฑฐํ•˜๊ณ  ์ถ”์ฒœ ํ”Œ๋žœ ์—ฌ๋ถ€ ๋ ˆ์ด๋ธ” ๋“ฑ์˜ ๊ธ€์ž์ƒ‰ ํ•˜๋“œ์ฝ”๋”ฉ(`color: white`) ์˜ค๋ฅ˜๋„ ์‹œ์ธ์„ฑ ์ข‹๊ฒŒ ์—ฐ๋™ ๊ต์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฒ„์ „ ์บ์‹ฑ ๋ฌดํšจํ™” ๊ฐฑ์‹ : ์–ด๋“œ๋ฏผ ํ˜ธ์ถœ ์ŠคํŽ™ ๋ฒ„์ „์„ `admin.css?v=1.2.4` ๋ฐ `admin.js?v=1.2.4`๋กœ ๊ฒฉ์ƒํ•˜์—ฌ ์ตœ์‹  ๋กœ์ปฌ ์บ์‹œ ๊ฐฑ์‹ ์„ ๊ฐ•์ œํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ข v1.2.3 ํ•ซํ”ฝ์Šค ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ (2026-06-25)

  • ๊ตฌ๋…/์š”๊ธˆ์ œ Drawer ๊ฐ€๋…์„ฑ ํŒจ์น˜ (index.html): `#user-subscription-drawer` ๋ฐ `#subscription-plan-drawer` ์— ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด ์žˆ๋˜ ์ธ๋ผ์ธ ๋‹คํฌ ๋ฐฐ๊ฒฝ(`#11131c`)์„ ํ…Œ๋งˆ ์—ฐ๋™ํ˜• CSS ๋ณ€์ˆ˜(`var(--bg-secondary)`)๋กœ ๋ณ€๊ฒฝํ•˜๊ณ , Drawer ํ—ค๋” ๊ธ€์ž์ƒ‰์„ `var(--text-primary)`๋กœ ๋Œ€์น˜ํ•˜์—ฌ ๋ผ์ดํŠธ ๋ชจ๋“œ ๋ช…์•”๋น„๋ฅผ ํšŒ๋ณตํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹จ์ฒด ๊ด€๋ฆฌ ํƒญ ๋ฆฌ๋””์ž์ธ (index.html): ๋‹จ์ฒด ๊ด€๋ฆฌ ์ƒ๋‹จ์˜ ํ†ต๊ณ„ ์นด๋“œ ๋ ˆ์ด์•„์›ƒ์„ ๋Œ€์‹œ๋ณด๋“œ์™€ ๋™์ผํ•œ `.metrics-grid` ๋ฐ `.metric-card` ๋””์ž์ธ ์‹œ์Šคํ…œ ์นด๋“œ(GNB ์นจ๋ฒ” ๋ฐฉ์ง€ ๋งˆ์ง„ ํฌํ•จ)๋กœ ๊ฐœํŽธํ•˜์—ฌ ์‹œ๊ฐ์  ์ผ๊ด€์„ฑ๊ณผ ์•„์ด์ฝ˜ ๋ Œ๋”๋ง์„ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์–ด๋“œ๋ฏผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ฐ€๋…์„ฑ ํŒจ์น˜ (admin.css): ๋ผ์ดํŠธ ๋ชจ๋“œ ์ „ํ™˜ ์‹œ ๊ฒ€์€ ๋ฐฐ๊ฒฝ์˜ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์˜์—ญ ๋‚ด๋ถ€ ํ…์ŠคํŠธ๋“ค์ด ๊ธ€๋กœ๋ฒŒ ์ƒ‰์ƒ ์˜ค๋ฒ„๋ผ์ด๋“œ๋กœ ์ธํ•ด ๋‹คํฌ ๊ทธ๋ ˆ์ด๋กœ ๋ณ€๊ฒฝ๋˜๋˜ ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ทฐํฌํŠธ ์˜์—ญ ์ „์ฒด๋ฅผ ๋‹คํฌ ๋ชจ๋“œ ๋ณ€์ˆ˜ ๊ฐ•์ œ ์ƒ์†์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ์™„๋ฒฝํ•œ ์‹œ์ธ์„ฑ์„ ๋ณด์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฒ„์ „ ์บ์‹ฑ ๋ฌดํšจํ™” ๊ฐฑ์‹ : ์–ด๋“œ๋ฏผ ํ˜ธ์ถœ ์ŠคํŽ™์„ `admin.css?v=1.2.3` ๋ฐ `admin.js?v=1.2.3`๋กœ ๊ฐฑ์‹ ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ ์บ์‹œ ๋ฌดํšจํ™”๋ฅผ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ข v1.2.2 ํ•ซํ”ฝ์Šค ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ (2026-06-25)

  • ์–ด๋“œ๋ฏผ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ๊ฐ€๋…์„ฑ ํŒจ์น˜: ๋ผ์ดํŠธ ๋ชจ๋“œ ํ™œ์„ฑํ™” ์‹œ ์–ด๋‘์šด ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์˜์—ญ(์ด๋ฒคํŠธ ํ—ˆ๋ธŒ ๋“ฑ) ๋‚ด๋ถ€์˜ ํ…์ŠคํŠธ๊ฐ€ ๊ธ€๋กœ๋ฒŒ ์˜ค๋ฒ„๋ผ์ด๋“œ๋กœ ์ธํ•ด ๊ฐ€๋…์„ฑ์ด ๊นจ์ง€๋˜ ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ทฐํฌํŠธ ๋‚ด๋ถ€ ๋ณ€์ˆ˜๋ฅผ ๊ฐ•์ œ๋กœ ๋‹คํฌ ๋ชจ๋“œ ๊ทœ๊ฒฉ์œผ๋กœ ๊ณ ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹จ์ฒด ๊ด€๋ฆฌ ํƒญ ๋ฆฌ๋””์ž์ธ: ๋‹จ์ฒด ๊ด€๋ฆฌ ์ƒ๋‹จ์˜ ํ†ต๊ณ„ ์˜์—ญ์ด ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ์—์„œ ๊ตฌ๋ถ„๋˜์ง€ ์•Š๊ณ  ์ž˜๋ฆฌ๋Š” ๊ฒฐํ•จ์„ ์ˆ˜์ •ํ•˜์—ฌ ๋Œ€์‹œ๋ณด๋“œ์™€ ๋™์ผํ•œ ์„ธ๋ จ๋œ ์นด๋“œ ๋ ˆ์ด์•„์›ƒ๊ณผ ์•„์ด์ฝ˜ ์‹œ์Šคํ…œ์œผ๋กœ ๊ฐœํŽธํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฒ„์ „ ์บ์‹ฑ ๋ฌดํšจํ™” ๊ฐฑ์‹ : CSS์™€ JS ์Šคํฌ๋ฆฝํŠธ ์ฐธ์กฐ๋ฅผ `v=1.2.2`๋กœ ๊ฐฑ์‹ ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ ๋‹จ์˜ ๋กœ์ปฌ ์บ์‹œ๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ข v1.2.1 ํ•ซํ”ฝ์Šค ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ (2026-06-25)

  • ์–ด๋“œ๋ฏผ ์ฝ˜์†” ์บ์‹œ ๋ฌดํšจํ™” (Cache-busting): index.html ๋‚ด์˜ ์Šคํฌ๋ฆฝํŠธ ์ฐธ์กฐ๋ฅผ `admin.js?v=1.2.1`๋กœ ๊ฐฑ์‹ ํ•˜์—ฌ ๊ธฐ์กด ๋กœ์ปฌ ์บ์‹œ๋กœ ์ธํ•œ ์˜ค๋™์ž‘(์š”๊ธˆ์ œ ํฌ๋ž˜์‹œ ๋ฐ ์‹ค์‹œ๊ฐ„ ๋กœ๊ทธ ํƒญ ๋ฏธ์ž‘๋™)์„ ์™„์ „ํžˆ ์ •์ƒํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ณ ๊ฐ์ง€์› ์‹ ๊ณ  ์ƒ์„ธ Drawer ๊ฐ€๋…์„ฑ ๋ฐ ์—ฌ๋ฐฑ ํŒจ์น˜: ์‹ ๊ณ /๋ฌธ์˜ ์ƒ์„ธ Drawer ๋‚ด๋ถ€์˜ ํ•˜๋“œ์ฝ”๋”ฉ `white` ์ƒ‰์ƒ์„ `var(--text-primary)` ํ…Œ๋งˆ ๋ฐ˜์‘ํ˜• ๋ณ€์ˆ˜๋กœ ์ „ํ™˜ํ•˜์—ฌ ๋ผ์ดํŠธ ๋ชจ๋“œ ๊ฐ€๋…์„ฑ์„ ๋ณด์žฅํ•˜๊ณ , ์•ˆ์ชฝ ํŒจ๋”ฉ์„ 16px๋กœ ๋„“ํ˜”์Šต๋‹ˆ๋‹ค.
  • ์š”๊ธˆ์ œ ๋ผ๋ฒจ ๋ฐ ๋ฆฌ์…‹ ๋ฌธ๊ตฌ ๊ธฐํš ์‚ฌ์–‘ ์ผ์น˜: ์š”๊ธˆ์ œ ํƒญ ๋ฉ”์ธ ์ œ๋ชฉ์„ '๊ตฌ๋… ๋ฐ ์š”๊ธˆ์ œ ๊ด€๋ฆฌ'๋กœ ํ‘œ์ค€ํ™”ํ•˜๊ณ , ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ ์•ˆ๋‚ด์ฐฝ ๋ฌธ๊ตฌ๋ฅผ ๊ธฐํš ์กฐ๊ฑด๊ณผ ์™„์ „ํžˆ ์ผ์น˜์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ข v1.2.0 ๋งˆ์ด๋„ˆ ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ (2026-06-24)

  • ์ ‘์ˆ˜๋ฒˆํ˜ธ ์ฒด๊ณ„ ๋Œ€ํญ ๊ฐœํŽธ: ๊ธฐ์กด ๋ฌด์ž‘์œ„ GUID ๋Œ€์‹  ๋Œ€ํšŒ ๊ณ ์œ  ์‹๋ณ„์ฝ”๋“œ(event_code)์™€ ODF ์Šคํฌ์ธ ์ฝ”๋“œ(sport_code)๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ˆœ์ฐจ์  ์ˆœ๋ฒˆ(Sequence)์„ ์ฑ„๋ฒˆํ•˜๋Š” ๊ฐ€๋…์„ฑ ๋†’์€ ์ฒด๊ณ„ ๋„์ž….
  • ์ด๋ฉ”์ผ ๋ฐœ์†ก Fail-Safe ์ด์ค‘ํ™”: Resend API ๋ฉ”์ผ ๋ฐœ์†ก ์žฅ์•  ์ƒํ™ฉ์—์„œ๋„ DB ๋ฌธ์˜ ๋ฐ์ดํ„ฐ ์ €์žฅ ์œ ์‹ค์ด ์ƒ๊ธฐ์ง€ ์•Š๋„๋ก ์˜ˆ์™ธ ์ฐจ๋‹จ ๋ž˜ํผ ์ถ”๊ฐ€.
  • ์•ˆ๋“œ๋กœ์ด๋“œ ๋ชจ๋ฐ”์ผ APK ๊ฒ€์ฆ ์™„๋ฃŒ: ์‹ ๊ทœ UI ์ด์‹ ๋ฒ„์ „ ๋””๋ฒ„๊ทธ APK ์„ฑ๊ณต์ ์œผ๋กœ ๋นŒ๋”ฉ ์™„๋ฃŒ (vivasport-v1.2.0-release-2205.apk).

๐Ÿ”’ ๋ณด์•ˆ ๊ฐ€์ด๋“œ๋ผ์ธ (Security Notes)

  • Firebase JWT ๊ฒ€์ฆ: ๋ชจ๋“  ์‚ฌ์šฉ์ž API ์š”์ฒญ์€ Firebase ์ธ์ฆ ํ—ค๋” ๊ฒ€์ฆ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ†ต๊ณผํ•ด์•ผ๋งŒ D1 ํŠธ๋žœ์žญ์…˜์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • Turnstile ๋ด‡ ๋ฐฉ์ง€: ๋น„ํšŒ์› ์ ‘์ˆ˜๊ฐ€ ๊ฐ€๋Šฅํ•œ ๋Œ“๊ธ€ ๋ฐ ๋ฌธ์˜ ์‹ ๊ณ  ํผ์€ ํด๋ผ์ด์–ธํŠธ์—์„œ Turnstile ์œ„์ ฏ ํ† ํฐ ๊ฒ€์ฆ์„ ํ•„์ˆ˜๋กœ ํ†ต๊ณผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿค– Agent Playbook & Handoff Summary

๐Ÿค ์ตœ์‹  ์„ธ์…˜ ํ•ธ๋“œ์˜คํ”„ ์š”์•ฝ (Handoff Summary)

* [์™„๋ฃŒ] ๊ฐ์‚ฌ ๋กœ๊ทธ ํƒญ ๋ ˆ์ด์•„์›ƒ ์‚์ ธ๋‚˜๊ฐ ์–ต์ œ ๋ฐ ๊ฐ€๋กœ 32px ํŒจ๋”ฉ ๋ฌด๊ฒฐ์„ฑ ๋ณต๊ตฌ (v1.2.5)
* [์™„๋ฃŒ] ์‹ค์‹œ๊ฐ„ ์‹œ์Šคํ…œ ๋ฐ API ๋กœ๊ทธ ๋ชจ๋‹ˆํ„ฐ ํ„ฐ๋ฏธ๋„ ๋ทฐํฌํŠธ ๋†’์ด 550px ํ™•์žฅ (v1.2.5)
* [์™„๋ฃŒ] ์–ด๋“œ๋ฏผ CSS ์„ ํƒ์ž ๋ฌธ๋ฒ• ํ‘œ์ค€ํ™” ๋ฐ ๋ธŒ๋ผ์šฐ์ € ํŒŒ์‹ฑ ํ˜ธํ™˜์„ฑ ๋ณต๊ตฌ (v1.2.4)
* [์™„๋ฃŒ] ๊ตฌ๋… ๋ฐ ์š”๊ธˆ์ œ ์„ค์ • Drawer ๋ฐฐ๊ฒฝ์ƒ‰ `!important` ๋ณด๊ฐ•์œผ๋กœ ๋ผ์ดํŠธ ๋ชจ๋“œ ์‹œ์ธ์„ฑ ์™„์ˆ˜ (v1.2.4)
* [์™„๋ฃŒ] ์ž…๋ ฅ ํ•„๋“œ/๋ ˆ์ด๋ธ”์˜ ๋‹คํฌ ๊ณ„์—ด ํ•˜๋“œ์ฝ”๋”ฉ ์Šคํƒ€์ผ์„ ํ…Œ๋งˆ ๋ณ€์ˆ˜ ์—ฐ๋™ ๊ธฐ๋ฐ˜์œผ๋กœ ์ „ํ™˜ (v1.2.4)
* [์™„๋ฃŒ] ์–ด๋“œ๋ฏผ ๊ตฌ๋… ๋ฐ ์š”๊ธˆ์ œ Drawer ๊ฐ€๋…์„ฑ ๋ฐ ํ…Œ๋งˆ ๋ณ€์ˆ˜ ์—ฐ๋™ ํŒจ์น˜ (v1.2.3)
* [์™„๋ฃŒ] ๋‹จ์ฒด ๊ด€๋ฆฌ ํƒญ ๋Œ€์‹œ๋ณด๋“œ ํ†ต๊ณ„ ์นด๋“œ ๋ ˆ์ด์•„์›ƒ ์ •๋ ฌ ๋ฐ ์ผ๊ด€์„ฑ ๊ฐœํŽธ (v1.2.3)
* [์™„๋ฃŒ] ์–ด๋“œ๋ฏผ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์˜์—ญ ๋ผ์ดํŠธ ๋ชจ๋“œ ๊ฐ€๋…์„ฑ ๋ถ•๊ดด ํŒจ์น˜ (v1.2.2)
* [์™„๋ฃŒ] ๋‹จ์ฒด ๊ด€๋ฆฌ ํƒญ ํ†ต๊ณ„ ์˜์—ญ ๋Œ€์‹œ๋ณด๋“œํ˜• ๋ฆฌ๋””์ž์ธ ๋ฐ ๋ ˆ์ด์•„์›ƒ ์ •๋ ฌ ์™„์ˆ˜ (v1.2.2)
* [์™„๋ฃŒ] ์–ด๋“œ๋ฏผ ์ฝ˜์†” ์บ์‹œ ๋ฌดํšจํ™” ๋ฐ ๊ธฐ๋Šฅ ๋ณต๊ตฌ (Cache-busting v1.2.1)
* [์™„๋ฃŒ] ๊ณ ๊ฐ์ง€์›(Abuse) ์ƒ์„ธ Drawer ๋ผ์ดํŠธ ๋ชจ๋“œ ๊ฐ€๋…์„ฑ ๋ฐ 16px ์—ฌ๋ฐฑ ํŒจ์น˜
* [์™„๋ฃŒ] React Web Certificate.tsx ์ปดํŒŒ์ผ ์—๋Ÿฌ(dynamic_form_data ์†์„ฑ ๋ˆ„๋ฝ) ์ˆ˜์ • ์™„๋ฃŒ
* [์™„๋ฃŒ] ์–ด๋“œ๋ฏผ ์ฐธ๊ฐ€์ž ์„œ๋ฅ˜ ๊ฒ€์ˆ˜ ๋ชจ๋‹ฌ ํŒ์—… ๋ฐ D1 DB PATCH status API E2E ์Šน์ธ ํ”„๋กœ์„ธ์Šค ์—ฐ๋™
* [์™„๋ฃŒ] UI ๋ผ๋ฒจ ํ‘œ์ค€ํ™” ๋ฐ ๋ฆฌ์…‹ ์„ฑ๊ณต ์‹œ ์–ผ๋Ÿฟ ๋Œ€ํ™”์ƒ์ž ๋ฌธ๊ตฌ ๊ธฐํš ์‚ฌ์–‘ ์‹ฑํฌ ์™„๋ฃŒ
* [์™„๋ฃŒ] D1 events ํ…Œ์ด๋ธ” event_code ์ปฌ๋Ÿผ ๋ถ€์žฌ ์ˆ˜์ • (DEF-D1-001)
* [์™„๋ฃŒ] Workers POST /api/events API event_code ๋ฐ”์ธ๋”ฉ ์ถ”๊ฐ€ ๋ฐ ์ ‘์ˆ˜๋ฒˆํ˜ธ ์ฑ„๋ฒˆ ๊ณ ๋„ํ™”
* [์™„๋ฃŒ] React Web EventCreate.tsx event_code ์ž…๋ ฅ UI ๊ฒ€ํ†  ๋ฐ ์ •์ƒ ๋นŒ๋“œ ์™„๋ฃŒ
* [์™„๋ฃŒ] ๋งˆ์ดํŽ˜์ด์ง€ FAQ ๋™์  ์—ฐ๋™ ๋ฐ localStorage ๋™๊ธฐํ™” ๋ˆ„๋ฝ ๊ฒฐํ•จ ์ˆ˜์ • ์™„๋ฃŒ (DEF-UX-002)
* [์™„๋ฃŒ] ์–ด๋“œ๋ฏผ ๋‹จ์ฒด/ํฌ๋ฃจ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ D1 DB ์‹ค๋ฐ์ดํ„ฐ ๋งคํ•‘ ๋ฐ 8์ปฌ๋Ÿผ ์ •๋ ฌ, GUI ์ œ์–ด ์—ฐ๋™ ์™„๋ฃŒ (DEF-ADMIN-003)
* [์™„๋ฃŒ] ๋ชจ๋ฐ”์ผ ์•ฑ 2์ˆœ์œ„ ํ™”๋ฉด(๋งˆ์ผ๋ฆฌ์ง€ ์‚ฌ์šฉ, ๋žญํ‚น, ๋‚ด ์šฉํ’ˆ ๊ด€๋ฆฌ) ๋„ค์ดํ‹ฐ๋ธŒ ์ด์‹ ๋ฐ ํ—ฌ์Šค ์ปค๋„ฅํŠธ ์—ฐ๋™ ์™„๋ฃŒ
* [์™„๋ฃŒ] ํ”Œ๋žซํผ ์ „๋ฐ˜(React Web, Admin, Event Hub) ํ•˜๋‹จ ์˜์—ญ์— [DEV MODE] ๊ฐœ๋ฐœ์ž ๋ชจ๋“œ ํ™”๋ฉด ID/๋ฒ„์ „ ์ •๋ณด ์ผ๊ด€ ์ ์šฉ ์™„๋ฃŒ
* [์™„๋ฃŒ] ์„œ๋ธŒ๋ชจ๋“ˆ ๋ฐฐํฌ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ์„ ์œ„ํ•œ .gitmodules ์„ค์ • ์ƒ์„ฑ ๋ฐ vivasport-react ๋งคํ•‘ ์™„๋ฃŒ (๋ฐฐํฌ ์•ˆ์ •ํ™”) (DEF-DEP-004)
* [์™„๋ฃŒ] ํ™ˆ ํ™”๋ฉด ์ƒ๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ธŒ๋žœ๋“œ ํƒ€์ดํ‹€ ๋ณต๊ตฌ ๋ฐ ์ค‘๋ณต ๋…ธ์ถœ ๊ต์ • ์™„๋ฃŒ (Header.tsx)

๐Ÿ“‹ Antigravity Agent Playbook

  • commander: ์ž‘์—… ์‹œ์ž‘ ๋ฐ ์ข…๋ฃŒ ์‹œ ์ธ์ˆ˜์ธ๊ณ„ ๋ฐ Git Commit/Push ํ™•์ธ.
  • docs-architect: vivasport-docs/index.html ํฌํ„ธ ๋ฌธ์„œ๋ฅผ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›(Single Source of Truth)์œผ๋กœ ์œ ์ง€ ๊ด€๋ฆฌ.
  • regression-guard: Firebase/Admin ๋กœ๊ทธ์ธ, CORS ํ—ค๋” ๋ฐ Turnstile ๋“ฑ ๊ธฐ์กด์˜ ๊ฒฌ๊ณ ํ•œ ์ธ์ฆ ํ๋ฆ„ ํ›ผ์† ๋ฐฉ์ง€.

๐Ÿ’ก ๋„์›€๋ง & ๋ฌธ์ œํ•ด๊ฒฐ

Q. ์–ด๋“œ๋ฏผ ์ฝ˜์†” ์›น์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ๋กœ๊ทธ์ธ ์ž ๊ธˆ(Lock Screen)์„ ํ‘ธ๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ธ๊ฐ€์š”? +
์–ด๋“œ๋ฏผ ์ฝ˜์†” ์›น ์šฐ์ธก ์ƒ๋‹จ ๋กœ๊ทธ์ธ ์˜์—ญ์—์„œ "์ˆ˜๋™ ํ† ํฐ (Manual Token)" ํƒญ์„ ํด๋ฆญํ•œ ๋’ค, ์ž…๋ ฅ์ฐฝ์— ์•„๋ž˜ ํ† ํฐ ํ‚ค๋ฅผ ์ฃผ์ž…ํ•˜๋ฉด ์ž ๊ธˆ์ด ํ•ด์ œ๋˜๊ณ  ๋กœ์ปฌ Mock ๋ฐ์ดํ„ฐ๋ฅผ ์—ฐ๋™ํ•ด ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
viva-mock-2026
Q. ๋ฌด๋ฃŒ ์š”๊ธˆ์ œ ์‚ฌ์šฉ์ž์˜ ์œ ๋ฃŒ๋Œ€ํšŒ ๊ฐœ์„ค ์ œํ•œ ์ •์ฑ…์€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”? +
์œ ์ €์˜ ๋“ฑ๊ธ‰์ด plan-free์ธ ๊ฒฝ์šฐ, React ์•ฑ ๋Œ€ํšŒ ๊ฐœ์„ค ํผ์—์„œ ์ฐธ๊ฐ€๋น„ ์ž…๋ ฅ ์นธ์ด ๋น„ํ™œ์„ฑํ™”๋˜๋ฉฐ ๋ฌด์กฐ๊ฑด 0์›์œผ๋กœ ๊ณ ์ •๋ฉ๋‹ˆ๋‹ค. ๊ฐ•์ œ๋กœ API๋ฅผ ์กฐ์ž‘ํ•˜์—ฌ 0์› ์ดˆ๊ณผ์˜ ๊ฒฐ์ œ ๊ธˆ์•ก์„ ๋ณด๋‚ด๋”๋ผ๋„ Workers ๋ฐฑ์—”๋“œ ๋ฏธ๋“ค์›จ์–ด ๋‹จ์—์„œ 403 Forbidden์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.

๋“ฑ๊ธ‰๋ณ„ ์›”๊ฐ„ ํ•œ๋„(Basic: 1๊ฑด, Coach: 5๊ฑด, Organizer: ๋ฌด์ œํ•œ) ์—ญ์‹œ ๋ฐฑ์—”๋“œ ํŠธ๋žœ์žญ์…˜ ์ˆ˜๋Ÿ‰ ๊ฒ€์ฆ์„ ํ†ตํ•ด ์—„๊ฒฉํ•˜๊ฒŒ ์ฐจ๋‹จ๋ฉ๋‹ˆ๋‹ค.
Q. ์ด๋ฒˆ์— ์ƒˆ๋กœ ๋ฐ˜์˜๋œ Q&A ์ŠคํŒธ ์ œ์–ด ๋ฐ ๋‹จ์ฒด ์Šน์ธ ๊ด€๋ฆฌ ํ”„๋กœ์„ธ์Šค๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”? +
Q&A ์˜์—ญ์— ์•…์„ฑ ๋„๋ฐฐ๊ธ€์ด๋‚˜ ์š•์„ค ๋“ฑ์ด ๋“ฑ๋ก๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด Cloudflare Turnstile์„ ์—ฐ๋™ํ•˜์—ฌ ๋ด‡ ๋ฐฉ์ง€๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์šฐํšŒํ•ด ์ŠคํŒธ์ด ๋“ฑ๋ก๋œ ๊ฒฝ์šฐ, ์ผ๋ฐ˜ ์œ ์ €์˜ ๐Ÿšจ์‹ ๊ณ  ๋ฒ„ํŠผ์„ ํ†ตํ•ด ์–ด๋“œ๋ฏผ Support ํƒญ์— ์ „๋‹ฌ๋˜๋ฉฐ, ์–ด๋“œ๋ฏผ์€ ์‹ ๊ณ  ์ •๋ณด์—์„œ ์›๋ณธ ๋Œ“๊ธ€์„ ๋น„๋™๊ธฐ๋กœ ๋‹น๊ฒจ์™€ ์ง์ ‘ ์‚ญ์ œํ•˜๊ฑฐ๋‚˜ ์ž‘์„ฑ์ž๋ฅผ ์ฆ‰๊ฐ ์ฐจ๋‹จ(Block)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
Q. ์ด๋ฒˆ์— ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋œ ๋ชจ๋ฐ”์ผ ์•ฑ(React Native / Expo) ๋นŒ๋“œ ๋ฐ ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ธ๊ฐ€์š”? +
VivaSport ๋ชจ๋ฐ”์ผ ํ”„๋กœ์ ํŠธ๋Š” Expo Router ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๊ตฌ์กฐ๋กœ ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋กœ์ปฌ ๋นŒ๋“œ๋ฅผ ์œ„ํ•ด ์ž๋ฐ” JDK 17์„ ์„ค์น˜ํ•˜๊ณ  ํ™˜๊ฒฝ๋ณ€์ˆ˜(JAVA_HOME)๋ฅผ ์„ค์ •ํ•œ ์ƒํƒœ์—์„œ cd android && ./gradlew assembleDebug ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋ฉด ์ฆ‰๊ฐ app-debug.apk ํŒŒ์ผ์ด ์ƒ์„ฑ๋˜์–ด ์‹ค์ œ ํฐ์ด๋‚˜ ์—๋ฎฌ๋ ˆ์ดํ„ฐ์—์„œ ์ฆ‰์‹œ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
Q. ํ™ˆํŽ˜์ด์ง€ ๋ฌธ์˜ํ•˜๊ธฐ(Contact Form) ์ ‘์ˆ˜ ์‹œ ๋ฉ”์ผ ๋ฐœ์†ก์ด ์‹คํŒจํ•˜๊ฑฐ๋‚˜ ์ง€์—ฐ๋˜์–ด๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ์‹ค๋˜์ง€ ์•Š๋‚˜์š”? +
๋ฌธ์˜ํ•˜๊ธฐ ์ ‘์ˆ˜ ์‹œ ๋ฐฑ์—”๋“œ Workers API๋Š” ์ด๋ฉ”์ผ ์ „์†ก์— ์•ž์„œ D1 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(inquiries ํ…Œ์ด๋ธ”)์— ๋ฌธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์šฐ์„  ์˜๊ตฌ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ ์—ฐ๋™๋œ Resend API๋ฅผ ํ†ตํ•ด ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ๋ฉ”์ผ์„ ์ „์†กํ•˜๋ฉฐ, ๋งŒ์•ฝ Resend API ๋„๋ฉ”์ธ ์ธ์ฆ ์ƒํƒœ ๋“ฑ์˜ ๋ฌธ์ œ๋กœ ๋ฉ”์ผ ๋ฐœ์†ก์ด ์˜ˆ์™ธ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋”๋ผ๋„, ๋‚ด๋ถ€ Exception ์ฒ˜๋ฆฌ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์šฐํšŒ ์ฐจ๋‹จ๋˜์–ด ์‚ฌ์šฉ์ž ํ™”๋ฉด์—๋Š” "์ ‘์ˆ˜ ์™„๋ฃŒ" ์•Œ๋ฆผ์ด ์ •์ƒ์ ์œผ๋กœ ๋…ธ์ถœ๋˜๋ฉฐ ๋ฐ์ดํ„ฐ ์œ ์‹ค์ด ์ผ์ ˆ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.