Skip to content

refactor(web): 프론트엔드 직접 Supabase 호출을 백엔드 API 경유로 마이그레이션 #158

@cocoyoon

Description

@cocoyoon

배경

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.tsauth.signInWithOAuth, auth.signOut, auth.getSession
  • lib/components/auth/AuthProvider.tsxauth.onAuthStateChange, auth.refreshSession
  • app/admin/login/page.tsxauth.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.tsfetchPostById() GET /posts/{id} 직접 대체
queries/posts.tsfetchPostWithSpotsAndSolutions() GET /posts/{id} (spots+top_solution) + GET /spots/{spotId}/solutions (전체) 2단계 호출
queries/posts.tsfetchPostsByUser() GET /posts?user_id={id} 직접 대체
queries/items.tsfetchSolutionsBySpotId() GET /spots/{spotId}/solutions user+vote_stats 포함
queries/items.tsfetchSpotsByPostId() GET /posts/{id}/spots 직접 대체
queries/items.tsfetchSolutionById() GET /solutions/{id} 직접 대체
hooks/useImages.tsuseInfinitePosts() GET /posts (has_magazine, has_solutions, sort, context 필터 + post_magazine_title) 직접 대체
hooks/useSolutions.ts → fallback GET /spots/{spotId}/solutions fallback 제거
authStore.tsupdateProfile() PATCH /users/me (display_name, avatar_url, bio) 직접 대체
queries/profile.tsfetchTryOnCount() 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

⚠️ 데이터 매핑 주의사항

  1. top_solution은 요약 버전vote_stats, user, match_type, link_type 누락. 모든 솔루션 필요 시 /spots/{spotId}/solutions 별도 호출 필수
  2. useInfinitePostspost_magazines!inner 조인 → 백엔드 has_magazine=true 필터 + post_magazine_title 필드로 대체 가능
  3. 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.tsfetchUserSocialAccounts() 전환
2-3 (B6,B7) spots/solutions에 user_id 필터 추가 queries/profile.tsfetchSpotsByUser(), fetchSolutionsByUser() 전환
2-4 (B3) /users/me/stats stub → 실제 구현 queries/profile.tsfetchUserProfileExtras() 전환
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)

완료 기준

  • packages/web/lib/supabase/queries/ 에서 클라이언트 사이드 .from() 호출 0건
  • supabaseBrowserClient가 auth 이외 용도로 사용되지 않음
  • lib/hooks/에서 Supabase fallback 패턴 제거
  • 미사용 파일 삭제 (storage.ts, debug/posts.ts)
  • 각 페이지 기능 정상 동작 확인

관련

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions