배경
decoded 시스템 철학: 프론트엔드는 무조건 백엔드 API를 통해 요청해야 한다.
현재 packages/web/에서 클라이언트 사이드 Supabase를 직접 호출하는 곳이 8개 파일, 10+ 테이블에 걸쳐 존재. RLS 우회 위험, 스키마 변경 시 프론트/백 동시 수정 필요, API 레이어의 캐싱/rate-limiting/로깅 미적용 등 문제가 있음.
감사 결과
🔴 클라이언트 사이드 직접 Supabase 쿼리 (위반 — 마이그레이션 대상)
| # |
파일 |
테이블 |
연산 |
심각도 |
| 1 |
lib/stores/authStore.ts |
users |
select + update (프로필 직접 mutation) |
HIGH |
| 2 |
lib/supabase/queries/posts.ts |
posts, spots, solutions |
select (포스트 상세, 스팟, 솔루션) |
HIGH |
| 3 |
lib/supabase/queries/items.ts |
solutions, spots |
select (솔루션/스팟 조회) |
HIGH |
| 4 |
lib/supabase/queries/profile.ts |
posts, spots, solutions, users, user_social_accounts, user_tryon_history |
select (프로필 6개 테이블) |
HIGH |
| 5 |
lib/hooks/useImages.ts |
posts, post_magazines |
select (메인 피드 무한스크롤 + Supabase fallback) |
HIGH |
| 6 |
lib/hooks/useTrendingArtists.ts |
posts |
select + 클라이언트 사이드 집계 |
MEDIUM |
| 7 |
lib/hooks/useSolutions.ts |
solutions |
select (백엔드 실패 시 fallback) |
MEDIUM |
| 8 |
app/admin/picks/page.tsx |
posts |
select (검색 자동완성, 인라인 클라이언트 생성) |
LOW |
✅ 허용 — 마이그레이션 불필요
Supabase Auth 클라이언트 호출 (SDK 표준 패턴):
lib/stores/authStore.ts — auth.signInWithOAuth, auth.signOut, auth.getSession
lib/components/auth/AuthProvider.tsx — auth.onAuthStateChange, auth.refreshSession
app/admin/login/page.tsx — auth.signInWithOAuth, auth.signInWithPassword
Next.js API Route Handler 서버 사이드 (서버에서 실행되므로 허용):
app/api/auth/callback/route.ts, app/api/auth/session/route.ts
app/api/admin/* 라우트 (13개) — admin warehouse 작업
app/api/v1/events — 행동 이벤트 기록
lib/supabase/queries/warehouse-entities.server.ts, images.server.ts
🗑️ 삭제 대상
| 파일 |
이유 |
lib/supabase/storage.ts |
upload/delete 정의만 있고 어디서도 import 안 됨 |
lib/supabase/queries/debug/posts.ts |
개발용, production 미사용 |
백엔드 API 갭 분석
✅ 이미 대응 엔드포인트 있음 (프론트만 전환하면 됨)
| 프론트 Supabase 쿼리 |
대응 백엔드 API |
비고 |
queries/posts.ts → fetchPostById() |
GET /posts/{id} |
직접 대체 |
queries/posts.ts → fetchPostWithSpotsAndSolutions() |
GET /posts/{id} (spots+top_solution) + GET /spots/{spotId}/solutions (전체) |
2단계 호출 |
queries/posts.ts → fetchPostsByUser() |
GET /posts?user_id={id} |
직접 대체 |
queries/items.ts → fetchSolutionsBySpotId() |
GET /spots/{spotId}/solutions |
user+vote_stats 포함 |
queries/items.ts → fetchSpotsByPostId() |
GET /posts/{id}/spots |
직접 대체 |
queries/items.ts → fetchSolutionById() |
GET /solutions/{id} |
직접 대체 |
hooks/useImages.ts → useInfinitePosts() |
GET /posts (has_magazine, has_solutions, sort, context 필터 + post_magazine_title) |
직접 대체 |
hooks/useSolutions.ts → fallback |
GET /spots/{spotId}/solutions |
fallback 제거 |
authStore.ts → updateProfile() |
PATCH /users/me (display_name, avatar_url, bio) |
직접 대체 |
queries/profile.ts → fetchTryOnCount() |
GET /users/me/tries (paginated, total count 포함) |
직접 대체 |
🔧 백엔드 수정/추가 필요
| # |
기능 |
현재 상태 |
필요한 작업 |
난이도 |
| B1 |
ink_credits, style_dna in UserResponse |
백엔드 users 엔티티에 컬럼 없음 |
Rust 엔티티+DTO에 추가 |
LOW |
| B2 |
Social accounts 엔드포인트 |
없음 |
GET /users/me/social-accounts 신규 |
LOW |
| B3 |
User stats 실제 데이터 |
GET /users/me/stats 존재하지만 stub (모두 0) |
실제 count 구현 |
MEDIUM |
| B4 |
User activities 실제 데이터 |
GET /users/me/activities 존재하지만 빈 배열 |
실제 조회 구현 (코드에 Phase 6+ TODO) |
MEDIUM |
| B5 |
트렌딩 아티스트 |
없음. 프론트에서 posts 200건→JS 집계 |
GET /rankings/artists?period=weekly 신규 (SQL 서버 집계) |
MEDIUM |
| B6 |
유저별 spots 목록 |
없음 |
GET /spots?user_id={id} 추가 |
LOW |
| B7 |
유저별 solutions 목록 |
없음 |
GET /solutions?user_id={id} 추가 |
LOW |
⚠️ 데이터 매핑 주의사항
top_solution은 요약 버전 — vote_stats, user, match_type, link_type 누락. 모든 솔루션 필요 시 /spots/{spotId}/solutions 별도 호출 필수
useInfinitePosts의 post_magazines!inner 조인 → 백엔드 has_magazine=true 필터 + post_magazine_title 필드로 대체 가능
useTrendingArtists 클라이언트 집계 → B5 완료 시 서버 SQL 집계로 성능 대폭 개선
마이그레이션 계획
Phase 1: 프론트만 전환 (백엔드 API 이미 존재)
| 작업 |
파일 |
방법 |
| 1-1 |
authStore.ts |
.from("users") select/update → GET/PATCH /users/me |
| 1-2 |
queries/posts.ts |
전체 함수 → /posts/{id}, /posts/{id}/spots, /spots/{spotId}/solutions |
| 1-3 |
queries/items.ts |
전체 함수 → /spots/{spotId}/solutions, /solutions/{id} 등 |
| 1-4 |
hooks/useImages.ts |
Supabase 직접 쿼리 + fallback → GET /posts (Orval 생성 훅 사용) |
| 1-5 |
hooks/useSolutions.ts |
Supabase fallback 제거 → 백엔드만 사용 |
Phase 2: 백엔드 추가 후 전환
| 작업 |
백엔드 (Rust) |
프론트 |
| 2-1 (B1) |
users 엔티티에 ink_credits, style_dna 추가 |
authStore.ts 프로필 조회에서 해당 필드 사용 |
| 2-2 (B2) |
GET /users/me/social-accounts 신규 |
queries/profile.ts → fetchUserSocialAccounts() 전환 |
| 2-3 (B6,B7) |
spots/solutions에 user_id 필터 추가 |
queries/profile.ts → fetchSpotsByUser(), fetchSolutionsByUser() 전환 |
| 2-4 (B3) |
/users/me/stats stub → 실제 구현 |
queries/profile.ts → fetchUserProfileExtras() 전환 |
| 2-5 (B5) |
GET /rankings/artists 신규 |
hooks/useTrendingArtists.ts 전환 |
Phase 3: 정리
| 작업 |
내용 |
| 3-1 |
app/admin/picks/page.tsx 인라인 클라이언트 → Meilisearch /search 또는 admin API |
| 3-2 |
lib/supabase/storage.ts 삭제 (미사용) |
| 3-3 |
lib/supabase/queries/debug/posts.ts 삭제 |
| 3-4 |
B4 (/users/me/activities 실제 구현) — 별도 이슈로 분리 (코드에 Phase 6+ TODO) |
완료 기준
관련
배경
decoded 시스템 철학: 프론트엔드는 무조건 백엔드 API를 통해 요청해야 한다.
현재
packages/web/에서 클라이언트 사이드 Supabase를 직접 호출하는 곳이 8개 파일, 10+ 테이블에 걸쳐 존재. RLS 우회 위험, 스키마 변경 시 프론트/백 동시 수정 필요, API 레이어의 캐싱/rate-limiting/로깅 미적용 등 문제가 있음.감사 결과
🔴 클라이언트 사이드 직접 Supabase 쿼리 (위반 — 마이그레이션 대상)
lib/stores/authStore.tsuserslib/supabase/queries/posts.tsposts,spots,solutionslib/supabase/queries/items.tssolutions,spotslib/supabase/queries/profile.tsposts,spots,solutions,users,user_social_accounts,user_tryon_historylib/hooks/useImages.tsposts,post_magazineslib/hooks/useTrendingArtists.tspostslib/hooks/useSolutions.tssolutionsapp/admin/picks/page.tsxposts✅ 허용 — 마이그레이션 불필요
Supabase Auth 클라이언트 호출 (SDK 표준 패턴):
lib/stores/authStore.ts—auth.signInWithOAuth,auth.signOut,auth.getSessionlib/components/auth/AuthProvider.tsx—auth.onAuthStateChange,auth.refreshSessionapp/admin/login/page.tsx—auth.signInWithOAuth,auth.signInWithPasswordNext.js API Route Handler 서버 사이드 (서버에서 실행되므로 허용):
app/api/auth/callback/route.ts,app/api/auth/session/route.tsapp/api/admin/*라우트 (13개) — admin warehouse 작업app/api/v1/events— 행동 이벤트 기록lib/supabase/queries/warehouse-entities.server.ts,images.server.ts🗑️ 삭제 대상
lib/supabase/storage.tslib/supabase/queries/debug/posts.ts백엔드 API 갭 분석
✅ 이미 대응 엔드포인트 있음 (프론트만 전환하면 됨)
queries/posts.ts→fetchPostById()GET /posts/{id}queries/posts.ts→fetchPostWithSpotsAndSolutions()GET /posts/{id}(spots+top_solution) +GET /spots/{spotId}/solutions(전체)queries/posts.ts→fetchPostsByUser()GET /posts?user_id={id}queries/items.ts→fetchSolutionsBySpotId()GET /spots/{spotId}/solutionsqueries/items.ts→fetchSpotsByPostId()GET /posts/{id}/spotsqueries/items.ts→fetchSolutionById()GET /solutions/{id}hooks/useImages.ts→useInfinitePosts()GET /posts(has_magazine, has_solutions, sort, context 필터 + post_magazine_title)hooks/useSolutions.ts→ fallbackGET /spots/{spotId}/solutionsauthStore.ts→updateProfile()PATCH /users/me(display_name, avatar_url, bio)queries/profile.ts→fetchTryOnCount()GET /users/me/tries(paginated, total count 포함)🔧 백엔드 수정/추가 필요
ink_credits,style_dnain UserResponseGET /users/me/social-accounts신규GET /users/me/stats존재하지만 stub (모두 0)GET /users/me/activities존재하지만 빈 배열GET /rankings/artists?period=weekly신규 (SQL 서버 집계)GET /spots?user_id={id}추가GET /solutions?user_id={id}추가top_solution은 요약 버전 —vote_stats,user,match_type,link_type누락. 모든 솔루션 필요 시/spots/{spotId}/solutions별도 호출 필수useInfinitePosts의post_magazines!inner조인 → 백엔드has_magazine=true필터 +post_magazine_title필드로 대체 가능useTrendingArtists클라이언트 집계 → B5 완료 시 서버 SQL 집계로 성능 대폭 개선마이그레이션 계획
Phase 1: 프론트만 전환 (백엔드 API 이미 존재)
authStore.ts.from("users")select/update →GET/PATCH /users/mequeries/posts.ts/posts/{id},/posts/{id}/spots,/spots/{spotId}/solutionsqueries/items.ts/spots/{spotId}/solutions,/solutions/{id}등hooks/useImages.tsGET /posts(Orval 생성 훅 사용)hooks/useSolutions.tsPhase 2: 백엔드 추가 후 전환
ink_credits,style_dna추가authStore.ts프로필 조회에서 해당 필드 사용GET /users/me/social-accounts신규queries/profile.ts→fetchUserSocialAccounts()전환user_id필터 추가queries/profile.ts→fetchSpotsByUser(),fetchSolutionsByUser()전환/users/me/statsstub → 실제 구현queries/profile.ts→fetchUserProfileExtras()전환GET /rankings/artists신규hooks/useTrendingArtists.ts전환Phase 3: 정리
app/admin/picks/page.tsx인라인 클라이언트 → Meilisearch/search또는 admin APIlib/supabase/storage.ts삭제 (미사용)lib/supabase/queries/debug/posts.ts삭제/users/me/activities실제 구현) — 별도 이슈로 분리 (코드에 Phase 6+ TODO)완료 기준
packages/web/lib/supabase/queries/에서 클라이언트 사이드.from()호출 0건supabaseBrowserClient가 auth 이외 용도로 사용되지 않음lib/hooks/에서 Supabase fallback 패턴 제거storage.ts,debug/posts.ts)관련