🧑‍💻 바이브 코딩

[바이브 코딩 #6] 데이트 코스 추천 서비스 개발기

수구리 2025. 6. 29. 17:53
반응형

DAY6: 이미지 업로드 RLS 정책 문제 해결

📋 개요

DAY5에서 구현한 이미지 업로드 기능에서 발생한 Supabase Storage RLS(Row Level Security) 정책 관련 오류를 해결하고, 안정적인 이미지 업로드 시스템을 완성했습니다.

🐛 발견된 문제

에러 상황

Error uploading compressed image: SyntaxError: Unexpected token '<', "<html><h"... is not valid JSON

문제 분석

  1. Supabase Storage RLS 정책 부재: place-images 버킷에 적절한 RLS 정책이 설정되지 않음
  2. 403 Unauthorized 응답: 이미지 업로드 시 JSON 대신 HTML 에러 페이지 반환
  3. 클라이언트 JSON 파싱 실패: HTML 응답을 JSON으로 파싱하려다 에러 발생

근본 원인

  • Supabase Storage에서 RLS가 활성화되어 있으나 적절한 정책이 없어 인증된 사용자도 업로드 거부
  • 서버에서 HTML 형태의 에러 응답을 반환하는데, 클라이언트에서는 JSON을 기대하여 파싱 에러 발생

🔧 해결 과정

1단계: 문제 진단

  • Storage 버킷 존재 여부 확인
  • RLS 정책 상태 점검
  • 테스트 업로드 스크립트 작성으로 문제 재현

2단계: 임시 테스트 환경 구성

// scripts/setup-storage.mjs - Storage 설정 스크립트
const { data: buckets } = await supabase.storage.listBuckets()
const bucketExists = buckets?.find(bucket => bucket.name === 'place-images')
// scripts/test-image-upload.mjs - 업로드 테스트
const { data, error } = await supabase.storage
  .from('place-images')
  .upload(fileName, imageBuffer)
// 결과: "new row violates row-level security policy"

3단계: 서비스 키를 활용한 해결책 구현

// app/lib/user-places.server.ts
export async function uploadPlaceImage(request: Request, file: File): Promise<string> {
  const supabase = createSupabaseServerClient(request);

  const { data: { user }, error: authError } = await supabase.auth.getUser();
  if (authError || !user) {
    throw new Error("인증이 필요합니다");
  }

  // 서비스 키로 관리자 권한 클라이언트 생성 (RLS 우회)
  const adminSupabase = createClient(
    process.env.SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!,
    {
      auth: {
        autoRefreshToken: false,
        persistSession: false
      }
    }
  );

  // 관리자 권한으로 이미지 업로드
  const { data, error } = await adminSupabase.storage
    .from('place-images')
    .upload(fileName, file, {
      cacheControl: '3600',
      upsert: false
    });
}

✅ 해결 결과

기술적 성과

  • RLS 정책 우회: 서비스 키를 사용하여 관리자 권한으로 업로드
  • 에러 처리 개선: 구체적인 실패 원인과 메시지 제공
  • 보안 유지: 서버 사이드에서만 서비스 키 사용
  • 안정성 확보: 파일 유효성 검증 및 에러 핸들링 강화

사용자 경험 개선

  • 원활한 이미지 업로드: JSON 파싱 에러 완전 해결
  • 명확한 에러 메시지: 실패 시 구체적인 원인 표시
  • 일관된 동작: 모든 지원 이미지 형식에서 안정적 업로드

🔄 향후 개선 계획

보안 강화

  1. 적절한 RLS 정책 설정: 장기적으로 서비스 키 의존도 감소
    ```sql

-- 향후 적용할 RLS 정책 예시
CREATE POLICY "Users can upload their own images"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'place-images' AND
auth.uid()::text = (storage.foldername(name))[1]
);

```

  1. 파일 크기 제한: 스토리지 사용량 최적화
  2. 이미지 형식 검증: 보안 및 호환성 강화

성능 최적화

  1. CDN 활용: 이미지 로딩 속도 개선
  2. 압축 품질 조정: 파일 크기와 품질 균형
  3. 프로그레시브 로딩: 대용량 이미지 경험 개선

📊 성능 지표

업로드 성공률

  • 수정 전: 0% (모든 업로드 실패)
  • 수정 후: 100% (테스트 통과)

에러 처리

  • 명확한 에러 메시지: 실패 원인 즉시 파악 가능
  • 복구 가능성: 사용자가 재시도 가능한 상황 안내

개발 효율성

  • 디버깅 시간 단축: 구체적 로그와 에러 메시지
  • 테스트 용이성: 독립적인 테스트 스크립트 활용

🎯 핵심 학습 내용

Supabase Storage 이해

  • RLS 정책의 중요성: Storage에서도 적절한 정책 설정 필요
  • 서비스 키 활용: 관리자 권한이 필요한 작업에서의 적절한 사용
  • 에러 응답 형태: API 에러 vs HTML 에러 페이지 구분

에러 처리 패턴

  • 예상 가능한 에러: RLS, 인증, 파일 형식 등
  • 복구 가능한 에러: 사용자 액션으로 해결 가능한 상황
  • 시스템 에러: 개발자 개입이 필요한 상황

개발 프로세스 개선

  • 점진적 문제 해결: 진단 → 테스트 → 수정 → 검증
  • 독립적 테스트: 기능별 분리된 테스트 환경 구성
  • 문서화의 중요성: 문제 해결 과정 상세 기록

🚀 완료된 기능

이미지 업로드 시스템

  • ✅ 자동 이미지 압축 (60-80% 용량 절약)
  • ✅ 실시간 미리보기 및 진행 상태
  • ✅ 다중 이미지 업로드 (최대 3장)
  • ✅ 개별 이미지 관리 (제거, 순서 변경)
  • ✅ 안정적인 Supabase Storage 연동
  • ✅ 완전한 에러 처리 및 사용자 피드백

장소 등록 플로우

  • ✅ 카카오 지도 연동 (위치 검색 및 선택)
  • ✅ 자동 정보 추출 (장소명, 주소, 좌표)
  • ✅ 지역 자동 생성 및 매핑
  • ✅ 카테고리 선택 및 설명 입력
  • ✅ 태그 시스템 및 이미지 업로드
  • ✅ 완전한 데이터 검증 및 저장

DAY6을 통해 이미지 업로드 기능의 안정성을 크게 향상시켰고, 코스모스 서비스의 핵심 기능인 장소 등록 시스템이 완전히 완성되었습니다.

반응형