🧑‍💻 바이브 코딩

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

수구리 2025. 6. 27. 10:25
반응형

이번 단계에서는 유저가 직접 장소를 등록할 수 있는 기능을 개발해보았다.
추후에는 본인이 등록한 장소들 또는 기존에 다른 유저가 등록한 장소들을 가지고 코스를 등록할 수 있도록 기획을 해볼 예정이다!

새로운 데이터베이스 스키마를 추가했고 유저의 무분별한 장소 등록을 방지하고자 하루에 최대 3개정도까지만 등록할 수 있도록 기획했다.

그리고 유저 본인이 등록한 장소를 카드 형태로 확인할 수 있고, 장소 수정은 불가능하고 삭제 후 재등록을 통해서만 가능하도록 기획해보았다.

그리고 현재는 모든 장소가 관리되어지고 있는데 나중에는 관리자의 승인에 의해서만 장소 등록이 될 수 있도록도 고려를 해야하지 않을까 생각한다.

일단은 유저 참여형 서비스를 개발해보고 싶었다. 어드민에서 등록되어져 있는 장소들만 가지고 유저에게 보여주기에는 너무 서비스가 이상해보이기도 하고.... 일단 내 생각은 그럼

장소 테이블에서는 source를 추가해서 유저에 의해서 등록된 장소인지 관리자에 의해서 등록된 장소인지 구분이 가능하도록 했다.

 

DAY 4: 유저 장소 등록 기능 구현

📋 기능 개요

유저가 직접 데이트 장소를 추천하고 공유할 수 있는 기능을 구현했습니다.
이는 관리자가 등록한 장소와 함께 추천 시스템에서 활용될 예정입니다.

🔄 구현된 기능

1. 데이터베이스 스키마 변경

  • 위치: supabase/migrations/20240729000000_add_user_places_support.sql
  • 변경사항:
    • places 테이블에 user_idsource 필드 추가
    • RLS 정책 추가로 유저별 접근 제어
    • 인덱스 추가로 성능 최적화

2. 타입 정의 업데이트

  • 위치: app/types/database.types.ts, app/types/forms/index.ts
  • 내용:
    • 데이터베이스 타입에 user_id, source 필드 추가
    • 유저 장소 등록을 위한 폼 타입 정의

3. 서버 함수 구현

  • 위치: app/lib/user-places.server.ts
  • 기능:
    • 일일 등록 제한 체크 (하루 3개)
    • 유저 장소 등록
    • 유저 장소 목록 조회
    • 유저 장소 삭제
    • 이미지 업로드 처리

4. 라우트 구현

장소 등록 페이지 (/register-place)

  • 위치: app/routes/register-place.tsx
  • 기능:
    • 일일 등록 제한 확인 (3개/일)
    • 장소 정보 입력 폼
    • 이미지 업로드 (1-3장)
    • 태그 등록 (최대 5개)

내 장소 목록 페이지 (/my-places)

  • 위치: app/routes/my-places.tsx
  • 기능:
    • 등록한 장소 목록 조회
    • 장소 삭제 기능
    • 카드 형태의 깔끔한 UI

5. UI 개선

메인 페이지 개선

  • 위치: app/routes/_index.tsx
  • 변경사항:
    • 메인 페이지 헤더에 "내 장소" 버튼 추가
    • 로그인한 유저에게 쉬운 접근성 제공
    • 인증 에러 처리 및 사용자 알림 추가

내 장소 페이지 UX 개선

  • 위치: app/routes/my-places.tsx
  • 주요 기능:
    • 조건부 UI: 장소가 없을 때와 있을 때 다른 인터페이스 제공
    • 플로팅 액션 버튼: 장소가 1개 이상일 때 화면 우하단에 + 버튼 표시
    • 모바일 최적화: 반응형 디자인으로 모바일/데스크톱 모두 지원
    • 접근성: aria-label, 툴팁, 키보드 네비게이션 지원
    • 애니메이션: 호버/클릭 효과로 자연스러운 인터랙션

🛡️ 보안 및 제한사항

Row Level Security (RLS) 정책

-- 유저는 자신이 등록한 장소만 삭제 가능
CREATE POLICY "Users can delete their own places" ON places FOR DELETE USING (
  user_id = auth.uid() AND source = 'user'
);

-- 유저는 자신의 장소를 조회 가능
CREATE POLICY "Users can view their own places" ON places FOR SELECT USING (
  user_id = auth.uid() OR is_active = true
);

-- 인증된 유저는 장소 등록 가능
CREATE POLICY "Authenticated users can insert places" ON places FOR INSERT WITH CHECK (
  auth.uid() IS NOT NULL AND source = 'user'
);

등록 제한

  • 일일 제한: 하루 최대 3개 장소만 등록 가능
  • 이미지 제한: 최소 1장, 최대 3장까지 업로드
  • 태그 제한: 최대 5개까지 등록 가능

📊 데이터 구조

확장된 places 테이블

-- 기존 필드들 +
user_id UUID REFERENCES auth.users(id), -- 등록한 유저 ID
source VARCHAR(10) -- 'admin' 또는 'user'

이미지 저장

  • 저장소: Supabase Storage (place-images bucket)
  • 경로: {user_id}/{timestamp}-{random}.{ext}
  • 연결: place_images 테이블로 관계 관리

🔧 기술적 구현 세부사항

1. 권한 분리 아키텍처

// 관리자 전용 함수 (admin.server.ts)
export async function getRegions(request: Request) {
  const { supabase } = await requireAdmin(request) // 관리자 권한 필요
  // ...
}

// 일반 사용자용 함수 (recommendation.server.ts)
export async function getRegions(request: Request) {
  const supabase = createSupabaseServerClient(request) // 인증만 필요
  // ...
}

2. 플로팅 액션 버튼 구현

// 조건부 렌더링으로 UX 최적화
{places.length > 0 && (
  <div className="fixed bottom-6 right-6 z-50 group">
    {/* 반응형 크기 조정 */}
    <button className="w-14 h-14 sm:w-16 sm:h-16 ...">
      {/* 애니메이션 효과 */}
      <svg className="w-6 h-6 sm:w-7 sm:h-7 transition-transform">
    </button>
    {/* 접근성을 위한 툴팁 */}
    <div className="absolute bottom-full right-0 mb-2 opacity-0 group-hover:opacity-100 ...">
  </div>
)}

3. 에러 처리 개선

// 인증 에러 처리
export async function requireAuth(request: Request) {
  try {
    const { data: { user }, error } = await supabase.auth.getUser()
    if (error || !user) {
      throw redirect(`${ROUTES.LOGIN}?redirectTo=${encodeURIComponent(redirectTo)}`)
    }
    return { user, supabase }
  } catch (error) {
    if (error instanceof Response) throw error // redirect 유지
    throw redirect(`${ROUTES.HOME}?error=auth_failed`) // 일반 에러
  }
}

🔄 향후 개발 계획

1. 추천 시스템 통합

  • 유저 등록 장소를 기존 추천 알고리즘에 포함
  • 가중치 조정 (관리자 vs 유저 등록 장소)

2. 승인 시스템

  • 관리자가 유저 등록 장소를 검토/승인하는 기능
  • 부적절한 장소 필터링

3. 커뮤니티 기능

  • 다른 유저들의 리뷰/평점 시스템
  • 등록된 장소 좋아요/유저 코스 북마크 기능

4. 통계 및 분석

  • 인기 장소 분석
  • 유저 등록 패턴 분석

🚨 해결된 문제들

1. 인증 관련 문제 해결

  • 문제: 일반 유저가 장소 등록 시 /?error=unauthorized 에러 발생
  • 원인: register-place 라우트에서 관리자 전용 함수(admin.server.ts) 사용
  • 해결: 일반 유저용 함수를 recommendation.server.ts로 분리
  • 결과: 로그인한 모든 유저가 장소 등록 가능

2. Supabase Storage 설정 완료

  • 문제: "Bucket not found" 에러로 이미지 업로드 실패
  • 해결: Supabase Dashboard에서 place-images 버킷 생성 및 정책 설정
  • 결과: 이미지 업로드 기능 정상 작동

3. 사용자 경험 개선

  • 추가: 플로팅 액션 버튼으로 장소 추가 접근성 향상
  • 개선: 조건부 UI로 상황에 맞는 인터페이스 제공
  • 최적화: 모바일 친화적 반응형 디자인 적용

🚨 현재 제한사항

  1. 지도 API: 위치 검색 및 자동 좌표 설정 기능 미구현
  2. 이미지 최적화: 이미지 리사이징 및 압축 기능 없음
  3. 오프라인 지원: PWA 기능 미구현

🔧 설치 및 실행

1. 마이그레이션 적용

npx supabase db reset --linked
# 또는
npx supabase db push

2. 환경 변수 확인

SUPABASE_URL=your_supabase_url
SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key

3. 개발 서버 실행

npm run dev

📝 테스트 시나리오

1. 장소 등록 테스트

  1. 로그인 후 "장소 등록" 버튼 클릭
  2. 필수 정보 입력 (장소명, 카테고리, 지역, 주소, 설명)
  3. 이미지 1-3장 업로드
  4. 태그 입력 (쉼표로 구분)
  5. 등록 완료 확인

2. 일일 제한 테스트

  1. 하루에 3개 장소 등록
  2. 4번째 등록 시도 시 제한 메시지 확인

3. 내 장소 관리 테스트

  1. "내 장소" 페이지에서 등록한 장소 목록 확인
  2. 장소 삭제 기능 테스트
  3. 삭제 확인 모달 동작 확인
  4. 플로팅 + 버튼 동작 확인 (장소가 1개 이상일 때)
  5. 모바일 환경에서 터치 인터랙션 테스트

4. 사용자 경험 테스트

  1. 장소가 없을 때 "첫 장소 등록하기" 버튼 표시 확인
  2. 장소가 있을 때 플로팅 + 버튼 표시 확인
  3. 버튼 호버/클릭 애니메이션 동작 확인
  4. 툴팁 표시 확인
  5. 반응형 디자인 동작 확인 (모바일/태블릿/데스크톱)

🎯 성공 지표

  • ✅ 유저 장소 등록 기능 구현 완료
  • ✅ 일일 등록 제한 구현 (하루 3개)
  • ✅ 이미지 업로드 기능 구현 (1-3장)
  • ✅ 보안 정책 (RLS) 적용
  • ✅ 사용자 친화적 UI/UX 제공
  • ✅ 인증 문제 해결 및 접근성 개선
  • ✅ 플로팅 액션 버튼으로 UX 향상
  • ✅ 모바일 최적화 완료
  • ✅ 에러 처리 및 사용자 피드백 개선
  • ✅ 코드 정리 및 디버깅 로그 제거

 

반응형