블로그 서비스 시작 (version_1.0.0)
(2026) 10. 28 도메인 연결, 넘버링은 Semantic versioning으로 진행합니다.
AI 설계 초기 프롬프트입니다.
기능을 ERD형태로 구상하고, 드롭다운 형식으로 타이핑 후 코덱스에 먹임.
CODEX에 명령 : 커서에서 클로드를 에이전트로 블로그를 만드는 프로젝트를 진행하고자 한다. 클로드가 이해 할 수 있도록 프롬프트를 작성해줘.
아래의 결과를 커서에 넣어 블로그 서비스 토대를 만들었습니다.
(백엔드 서버 및 DB 연결 이후) 운영정책 내용 추가 (영국 ICO의 개인정보정책 기반으로 카카오 정책을 일부 참하였음.)
너는 이 프로젝트의 시니어 풀스택 개발자다.
아래 요구사항에 따라 개인 블로그와 소규모 쇼핑몰을 구현해라.
단순한 화면 목업이 아니라 인증, DB, 권한, 게시글 CRUD,
파일 업로드, 댓글 관리, 운영정책, 주문·결제가 연결되어야 한다.
먼저 저장소의 코드, package.json, AGENTS.md와 설정을 확인한다.
기존 프로젝트 구조와 코딩 규칙을 존중한다.
프로젝트가 비어 있으면 아래 기술 구성으로 초기화한다.
공식 문서에서 현재 지원되는 API와 호환되는 안정 버전을 확인한다.
아래 명세에서 정한 기능과 권한을 임의로 변경하지 않는다.
━━━━━━━━━━━━━━━━━━━━
1. 기술 구성과 처리 방식
━━━━━━━━━━━━━━━━━━━━
- Next.js App Router
- TypeScript strict
- Supabase PostgreSQL
- Supabase Auth
- Supabase Storage
- Vercel 배포
- 토스페이먼츠 결제
- 카카오 우편번호 서비스
프런트와 백엔드를 하나의 Next.js 프로젝트에서 관리한다.
초기에는 EC2나 별도 백엔드 서버를 만들지 않는다.
처리 방식:
- 조회: Server Components와 서버 전용 조회 함수
- 생성·수정·삭제: Server Actions
- 결제 웹훅·콜백·파일 제공: Route Handlers
- 권한·가격·재고·결제·댓글 관리: 서버 전용 서비스 계층
Server Actions와 Route Handlers는 서비스 계층을 호출하도록 한다.
클라이언트에 비밀키나 신뢰가 필요한 비즈니스 로직을 넣지 않는다.
여러 DB 변경의 원자성은 실제 DB 트랜잭션으로 보장한다.
━━━━━━━━━━━━━━━━━━━━
2. 서비스 구성과 UI
━━━━━━━━━━━━━━━━━━━━
개인 블로그에 자료실과 소규모 쇼핑몰을 결합한다.
한국어 UI와 모바일·태블릿·데스크톱 반응형 화면을 제공한다.
네이버 블로그·티스토리의 일반적인 이용 방식을 참고하되,
브랜드·로고·화면 디자인을 그대로 복제하지 않는다.
헤더 카테고리:
1. who: 주인장 소개 단일 페이지
2. 익힘: 프로그래밍 스택 리뷰 게시판
3. 느낌: 경험 게시판
4. 헤엄: 수영 게시판
5. 시쓰루: 에세이·시 게시판
6. 자료실: 게시글과 첨부파일 다운로드
7. 팝니다: 상품 목록·상세·주문·결제
카테고리 이름·헤더 표시 여부·순서는 DB에서 관리한다.
메인 대시보드:
- 최근 작성글
- 게시판별 작성글 수
- 누적 방문자 수
- 오늘 방문자 수
공통 푸터:
- “운영정책” 링크를 처음부터 항상 표시
- 클릭 시 /policies/operation으로 이동
- 정책 내용이 없어도 준비 중 페이지가 정상적으로 열림
━━━━━━━━━━━━━━━━━━━━
3. 회원가입·로그인
━━━━━━━━━━━━━━━━━━━━
Supabase Auth로 다음을 제공한다.
- 비밀번호 없는 이메일 인증 링크 로그인
- 구글 로그인
이메일 인증 성공 시 신규 회원이면 가입,
기존 회원이면 로그인되는 흐름이다.
인증 링크·JWT·Refresh Token 관리 테이블을 중복 구현하지 않는다.
Supabase 공식 Next.js SSR 인증 방식을 사용한다.
사용자와 세션은 서버에서 검증한다.
profiles:
- id UUID PK/FK → auth.users.id
- nickname
- role: OWNER | MEMBER
- status: ACTIVE | SUSPENDED | WITHDRAWN
- created_at
- updated_at
일반 가입자는 MEMBER다.
프로필은 서버/DB에서 안전하게 생성한다.
사용자가 role·status를 임의 변경할 수 없어야 한다.
OWNER 초기 지정 절차를 문서화한다.
━━━━━━━━━━━━━━━━━━━━
4. 게시글 CRUD와 공개 범위
━━━━━━━━━━━━━━━━━━━━
글·소개·상품 등록은 OWNER만 가능하다.
수정·삭제는 OWNER이면서 해당 콘텐츠 작성자 본인만 가능하다.
author_id는 생성 시 서버가 지정한다.
수정 요청으로 author_id를 변경할 수 없다.
게시 상태:
- DRAFT
- PUBLISHED
- ARCHIVED
공개 범위:
- PUBLIC
- PRIVATE
게시 상태와 공개 범위는 별개다.
PUBLISHED + PRIVATE는 게시 완료된 비밀글이다.
OWNER:
- 일반 블로그 화면에서도 공개·비공개 게시글을 모두 본다.
- 비공개 글에는 비공개 표시를 붙인다.
- 비공개 글을 보기 위해 관리 화면에 들어갈 필요가 없다.
- 임시 저장·게시 종료 글은 작성 관리 화면에서 관리한다.
다른 회원·비로그인 방문자:
- PUBLISHED + PUBLIC + 미삭제 콘텐츠만 조회한다.
- 비공개 글은 목록·검색·최근 글·공개 집계에서 제외한다.
- URL 직접 접근으로도 비공개 내용을 볼 수 없다.
편집 화면에서 내용과 공개/비공개를 함께 수정할 수 있어야 한다.
공개 → 비공개 전환 시 사진·첨부파일·댓글도 접근 차단한다.
매 변경 요청에서 서버가 인증·계정 상태·역할·작성자를 검증한다.
버튼을 숨기는 것만으로 권한을 구현하지 않는다.
Supabase RLS에도 같은 접근 규칙을 적용한다.
권한별 응답이 공유 캐시로 노출되지 않게 한다.
공개 범위 변경 시 상세·목록·검색·집계 캐시를 갱신한다.
━━━━━━━━━━━━━━━━━━━━
5. 카테고리·콘텐츠·상품 DB
━━━━━━━━━━━━━━━━━━━━
categories:
- id BIGINT PK
- slug UNIQUE
- name
- kind: PAGE | BOARD | SHOP
- display_order
- is_visible
is_visible은 헤더 표시 여부이며 접근 권한이 아니다.
contents:
- id UUID PK
- category_id FK
- author_id UUID FK → profiles.id
- title
- body JSONB
- status
- visibility
- published_at
- created_at
- updated_at
- deleted_at
본문은 파일 ID를 참조할 수 있는 구조화된 에디터 문서로 저장한다.
에디터를 선택하고 문서 스키마 검증·안전한 렌더링을 구현한다.
XSS 방어를 적용한다.
외부 저장소의 영구 파일 URL을 본문에 직접 저장하지 않는다.
products:
- content_id UUID PK/FK → contents.id
- price NUMERIC
- stock_quantity
- sale_status: ON_SALE | PAUSED | ENDED
- created_at
- updated_at
초기 상품은 옵션 없는 실물 상품이다.
PAGE는 카테고리당 미삭제 콘텐츠 하나만 허용한다.
SHOP 콘텐츠와 상품 정보는 일관되게 생성·수정한다.
주문이 있는 상품은 물리 삭제하지 않는다.
━━━━━━━━━━━━━━━━━━━━
6. 사진·첨부파일·저장소 교체
━━━━━━━━━━━━━━━━━━━━
초기 파일 저장소는 Supabase Storage다.
추후 EC2 기반 저장소로 이전해도 본문과 파일 ID를 유지한다.
EC2 구현·배포는 현재 범위에 포함하지 않는다.
저장소 어댑터 경계와 이전 절차를 마련한다.
storage_backends:
- id BIGINT PK
- name UNIQUE
- driver: SUPABASE | EC2
- config_key
- accepts_uploads
files:
- id UUID PK
- storage_backend_id FK
- uploaded_by UUID FK → profiles.id
- object_key
- original_name
- mime_type
- size_bytes
- checksum_sha256
- status: UPLOADING | READY | DELETED
- created_at
- deleted_at
- UNIQUE(storage_backend_id, object_key)
content_files:
- id BIGINT PK
- content_id FK
- file_id FK UNIQUE
- purpose: IMAGE | DOWNLOAD
- display_order
파일 하나는 콘텐츠 하나에만 연결한다.
본문 이미지도 content_files에 등록한다.
다른 콘텐츠에 동일 파일을 올리면 별도 파일로 취급한다.
파일 공개 범위는 연결된 콘텐츠를 따른다.
파일별 공개 범위 컬럼은 두지 않는다.
미연결 파일은 업로더 본인만 접근한다.
원본은 비공개 버킷에 보관한다.
파일 식별 주소는 /api/files/{fileId}를 사용한다.
파일 제공 API:
- 현재 콘텐츠 권한 검증
- 비공개 파일은 작성자인 OWNER만 접근
- 공개 → 비공개 전환 후 새 요청 차단
- 재사용 가능한 외부 URL 대신 서버에서 파일 전달
- 공유 캐시에 저장하지 않는 응답 헤더 적용
이미 내려받은 파일까지 회수할 수 있다고 설명하지 않는다.
대용량 파일은 메모리에 전부 적재하지 않도록 처리한다.
Vercel 실행·응답 제한과 호환되는지 확인한다.
지원 업로드 크기를 화면·서버·문서에 일치시킨다.
플랫폼 제한과 충돌하면 근거와 대안을 제시한다.
스토리지 어댑터 책임:
- 업로드 준비
- 실제 파일 정보 확인
- 권한 검증 이후 서버에서 읽기
- 삭제
서버가 업로드 결과를 검증하고 READY로 변경한다.
MIME·확장자·크기 검증과 경로 조작 방어를 적용한다.
object_key는 서버에서 안전하게 생성한다.
미완료 업로드·고아 파일 정리 방법을 마련한다.
이전 순서:
1. 새 업로드 대상 변경
2. 기존 파일 복사
3. 크기·체크섬·읽기 검증
4. 검증된 파일의 저장 위치 변경
5. 안정화 후 원본 삭제
서로 다른 저장소의 파일이 공존해도 정상 동작해야 한다.
비밀키는 DB가 아닌 서버 환경설정으로 관리한다.
━━━━━━━━━━━━━━━━━━━━
7. 댓글·별점·답글
━━━━━━━━━━━━━━━━━━━━
소개·대시보드·운영정책 페이지에는 댓글을 제공하지 않는다.
게시판·자료실·상품에 댓글을 허용한다.
댓글 작성은 로그인한 활성 회원만 가능하다.
comments:
- id UUID PK
- content_id FK
- author_id FK
- body TEXT
- rating SMALLINT NULL
- created_at
- updated_at
- deleted_at
일반 게시글·자료실:
- 내용 필수
- rating은 NULL
상품:
- 내용 필수
- rating은 1~3 필수
- 별 모양 UI로 단일 값 선택
- 구매하지 않은 회원도 작성 가능
- 회원당 복수 댓글·별점 허용
- 평균 별점·회원당 평가 1회 제한은 임의로 추가하지 않음
일반 회원은 공개·게시된 콘텐츠에만 댓글을 작성한다.
비공개 콘텐츠의 댓글은 OWNER만 조회한다.
댓글 수정:
- 작성자 본인만 가능
- OWNER도 타인 댓글의 내용·별점을 수정할 수 없음
- 삭제된 댓글은 작성자가 수정·복구할 수 없음
댓글 삭제:
- 작성자는 본인 댓글 삭제 가능
- OWNER는 작성자와 관계없이 모든 댓글 삭제 가능
- 일반 회원은 타인 댓글 삭제 불가
- 기본적으로 논리 삭제
- 서버에서 매 요청 권한 검증
- 중복 삭제는 멱등하게 처리
comment_replies:
- id UUID PK
- comment_id FK UNIQUE
- author_id FK
- body TEXT
- created_at
- updated_at
- deleted_at
OWNER만 답글을 작성·수정·삭제한다.
원댓글당 답글 하나이며 수정 가능하다.
답글의 답글과 답글 별점은 없다.
삭제된 원댓글에는 새 답글을 작성할 수 없다.
━━━━━━━━━━━━━━━━━━━━
8. 운영자 댓글 삭제·이력
━━━━━━━━━━━━━━━━━━━━
OWNER에게 타인 댓글 삭제 메뉴를 제공한다.
삭제 전 확인 UI와 삭제 사유 입력을 제공한다.
운영자 삭제:
- reason_code 필수
- 상세 메모 입력 가능
- 초기 사유: 욕설·비방, 스팸, 기타
- 기타 선택 시 상세 사유 필수
- 향후 운영정책에 맞춰 사유 확장 가능
- 아직 정해지지 않은 정책 조항을 만들어 적용하지 않음
작성자 본인 삭제에는 사유를 요구하지 않는다.
comment_deletion_logs:
- id UUID PK
- comment_id FK UNIQUE
- actor_id UUID FK → profiles.id
- actor_role_snapshot
- deletion_type: AUTHOR | MODERATOR
- reason_code NULL
- reason_detail TEXT NULL
- created_at
AUTHOR:
- 작성자가 본인 댓글을 삭제한 경우
- OWNER가 본인 댓글을 삭제한 경우 포함
MODERATOR:
- OWNER가 타인 댓글을 삭제한 경우
댓글 삭제와 이력 저장은 하나의 DB 트랜잭션으로 처리한다.
일반 회원이 삭제 로그를 직접 생성·수정·삭제하지 못하게 한다.
삭제 로그는 OWNER만 조회한다.
운영 이력은 일반 UI에서 수정·삭제할 수 없게 한다.
삭제된 댓글:
- 본문·별점은 일반 사용자 응답에서 제외
- 답글이 없으면 공개 목록에서 제외
- 답글이 있으면 원댓글 자리에 삭제 안내 표시
- 작성자 삭제: “작성자가 삭제한 댓글입니다.”
- 운영자 삭제: “운영자에 의해 삭제된 댓글입니다.”
- 미삭제된 OWNER 답글은 유지
- 내부 삭제 사유·메모는 공개하지 않음
안전한 조회 함수·뷰·응답 DTO를 사용한다.
Supabase 직접 조회로 삭제 원문이 노출되지 않도록
테이블·뷰·RPC 권한을 함께 설계한다.
RLS만으로 필드별 노출 제한이 해결된다고 가정하지 않는다.
원문 무기한 보관 정책은 임의로 확정하지 않는다.
원문 보관·파기 정책과 운영 이력을 구분해 문서화한다.
댓글 수정마다 원문을 별도 로그에 복제하는 기능은 만들지 않는다.
━━━━━━━━━━━━━━━━━━━━
9. 운영정책 페이지·푸터 링크
━━━━━━━━━━━━━━━━━━━━
운영정책 내용은 아직 정해지지 않았다.
하지만 푸터 링크와 연결 페이지는 처음부터 구현해야 한다.
필수 동작:
- 공통 푸터에 “운영정책” 링크를 항상 표시
- 링크 주소: /policies/operation
- 비로그인 사용자도 접근 가능
- 내용이 없어도 정상 페이지로 열림
- 내용이 없다는 이유로 링크 숨김·비활성화·404 처리 금지
초기 페이지:
- 공통 헤더·푸터와 반응형 레이아웃
- 제목: “운영정책”
- 안내: “운영정책을 준비 중입니다.”
- 임의 정책 본문·법적 문구·시행일 생성 금지
- 빈 날짜·버전 정보는 표시하지 않음
- 댓글·별점 없음
나중에 OWNER가 관리 화면에서 정책 내용을 입력한다.
코드 수정·재배포 없이 게시할 수 있어야 한다.
policy_documents:
- id UUID PK
- slug UNIQUE
- title
- created_at
- updated_at
policy_revisions:
- id UUID PK
- policy_document_id FK
- version_number
- body JSONB
- status: DRAFT | PUBLISHED | ARCHIVED
- effective_at NULL
- published_at NULL
- created_by UUID FK → profiles.id
- created_at
- updated_at
- UNIQUE(policy_document_id, version_number)
초기 데이터:
- slug=operation, title=운영정책 문서 준비
- 공개 정책 본문은 생성하지 않음
- 초기 데이터 중복 실행 시 중복 생성 방지
- 기본 운영정책 문서 삭제로 링크가 깨지지 않도록 보호
관리 기능:
- OWNER만 초안 작성·수정·미리보기·게시·게시 중단 가능
- 제목·본문 등 게시 필수값 검증
- 빈 본문 게시 불가
- 새 초안 작성 중 기존 공개 버전 유지
- 게시된 내용을 바꾸려면 새 버전 초안 작성
- 새 버전 게시와 기존 버전 보관을 하나의 트랜잭션으로 처리
- 문서당 PUBLISHED 버전 최대 하나
- 과거 버전과 초안은 OWNER만 조회
- 예약 게시 기능은 초기 범위에 포함하지 않음
- effective_at은 시행일 표기용이며 예약 게시와 구분
공개 페이지:
- PUBLISHED 버전이 있으면 해당 본문 표시
- 공개 버전이 없으면 “운영정책을 준비 중입니다.” 표시
- 게시 중단 후에도 페이지와 푸터 링크 유지
- 게시 중단 후에는 준비 중 안내로 돌아감
- 버전·게시일·시행일은 값이 있을 때만 표시
- 일반 방문자에게 초안·과거 버전 노출 금지
- 존재하지 않는 다른 정책 slug는 404 처리 가능
OWNER 미리보기:
- 별도 권한 검증
- 공유 캐시·검색 색인 노출 금지
캐시:
- 게시·변경·게시 중단 시 관련 페이지 캐시 갱신
- 푸터 링크는 본문 상태와 관계없이 항상 유지
댓글과 연결:
- 댓글 작성 영역에도 같은 운영정책 링크 제공
- 정책이 준비 중이어도 댓글·운영자 삭제 기능 정상 동작
- 존재하지 않는 정책 조항을 삭제 근거로 만들지 않음
- 정책 게시로 과거 삭제 기록을 소급 변경하지 않음
- 자동 욕설 판정·자동 삭제는 초기 범위에 넣지 않음
정책 문서는 게시판·최근 글·글 수 집계에서 제외한다.
이용약관·개인정보처리방침을 임의 생성하지 않는다.
법적 요건을 충족했다고 주장하지 않는다.
회원별 약관 동의·필수 동의 이력은 이번 범위에 포함하지 않는다.
━━━━━━━━━━━━━━━━━━━━
10. 주문·결제·배송
━━━━━━━━━━━━━━━━━━━━
상품 열람은 로그인 없이 가능하다.
주문·구매는 로그인이 필요하다.
사이트 안에서 주문서 작성·결제·주문 조회를 제공한다.
카카오 주소검색:
- 우편번호·기본주소 입력
- 상세주소·수령인·연락처·배송 메모는 별도 입력
초기 기준:
- 옵션 없는 실물 상품
- 주문당 배송지 하나
orders:
- id UUID PK
- user_id FK
- order_number UNIQUE
- status
- items_amount
- shipping_amount
- total_amount
- expires_at
- created_at
- updated_at
order_items:
- id UUID PK
- order_id FK
- product_id FK → products.content_id
- product_name_snapshot
- unit_price_snapshot
- quantity
- line_amount
order_addresses:
- order_id PK/FK
- recipient_name
- recipient_phone
- postal_code
- address
- detail_address
- delivery_memo
payments:
- id UUID PK
- order_id FK
- provider_order_id UNIQUE
- payment_key UNIQUE NULL
- status
- amount
- method
- approved_at
- created_at
- updated_at
payment_cancels:
- id UUID PK
- payment_id FK
- idempotency_key UNIQUE
- provider_transaction_key UNIQUE NULL
- amount
- reason
- status
- created_at
stock_reservations:
- id UUID PK
- order_item_id FK UNIQUE
- quantity
- status
- expires_at
- created_at
- updated_at
shipments:
- id UUID PK
- order_id FK
- carrier
- tracking_number
- status
- shipped_at
- delivered_at
주문·결제·예약·배송 상태와 허용 전이를 문서화한다.
공급자 결제 상태와 내부 주문 상태는 구분한다.
원화 금액은 NUMERIC, 정수·음수 방지 제약을 적용한다.
수량은 양수로 제한한다.
서버가 가격·배송비·합계·공개 상태·판매 상태·재고를 검증한다.
주문·배송지·결제 정보는 구매자 본인과 OWNER만 조회한다.
상품명·가격·배송지는 주문 당시 스냅샷으로 보관한다.
상품 수정·비공개 전환이 기존 주문을 바꾸면 안 된다.
비공개 상품의 신규 주문은 차단한다.
재고:
- stock_quantity는 판매 확정으로 차감하지 않은 보유 수량
- 구매 가능 수량 = 보유 수량 - 유효 예약 수량
- 주문 생성·예약은 하나의 DB 트랜잭션
- 상품 행 잠금으로 동시 주문 제어
- 결제 확정 시 예약 확정과 재고 차감
- 실패·만료 시 예약 해제
- 결제 진행과 예약 만료 경합 처리
- 취소 시 재고 복원은 명시한 정책에 따라 한 번만 처리
Supabase 호출 여러 번을 트랜잭션처럼 취급하지 않는다.
PostgreSQL 함수/RPC 또는 실제 DB 트랜잭션을 사용한다.
RPC도 호출자 권한과 소유권을 검증한다.
토스페이먼츠:
- 공식 문서에 따른 테스트 결제로 구현
- 주문번호·금액·소유자·상태 서버 검증
- 성공 화면 진입만으로 결제 완료 처리 금지
- 주문당 중복 성공 결제 방지
- 승인·취소 멱등 처리
- 위조 요청·중복 웹훅 방어
- 승인 응답 유실 시 공급자 조회로 상태 복구
- 외부 API와 DB를 하나의 트랜잭션으로 가정하지 않음
웹훅 검증은 실제 지원되는 공식 방식을 확인한다.
존재하지 않는 서명 헤더나 인증 기능을 만들지 않는다.
예약 만료·결제 복구·고아 파일 정리 작업의
스케줄 실행·인증·배포 방법을 문서화한다.
요청 종료 후 작업이 계속 실행된다고 가정하지 않는다.
━━━━━━━━━━━━━━━━━━━━
11. 방문자·대시보드
━━━━━━━━━━━━━━━━━━━━
daily_visits:
- visit_date DATE
- visitor_key_hash
- first_visited_at
- PRIMARY KEY(visit_date, visitor_key_hash)
비회원도 집계한다.
서버가 발급·검증하는 임의 방문자 쿠키를 기준으로
한국 시간 하루 한 번 기록한다.
DB 고유 제약으로 중복 요청을 방지한다.
누적 방문자는 일별 방문자 수의 합이다.
쿠키 삭제·다른 기기는 동일인으로 합치지 못한다.
대시보드:
- 최근 게시판 글
- 게시판별 작성글 수
- 누적 방문자 수
- 오늘 방문자 수
OWNER에게는 게시된 비공개 글도 표시·집계한다.
다른 사용자에게는 공개 글만 표시·집계한다.
임시 저장·삭제 글·소개·상품·정책 문서는
기본 게시판 글 수 집계에서 제외한다.
━━━━━━━━━━━━━━━━━━━━
12. DB·보안·코드 품질
━━━━━━━━━━━━━━━━━━━━
- 재현 가능한 Supabase SQL 마이그레이션
- FK·UNIQUE·CHECK·필요한 인덱스
- 페이지네이션과 안정적인 정렬
- Supabase RLS·Storage 정책
- 역할·주문 상태 임의 변경 차단
- service role 키는 서버 전용
- service role 사용 시 서비스 계층에서 별도 권한 검증
- 서버 입력 검증과 안전한 오류 응답
- 개인정보·토큰·비밀키 로그 출력 금지
- 공개 응답에 비공개 데이터 포함 금지
- 상태 변경에 GET 사용 금지
- 서버 전용 코드와 클라이언트 코드 구분
- 비즈니스 로직과 UI 분리
- SECURITY DEFINER 최소화
- 필요한 경우 search_path·실행 권한 제한
RLS와 서버 권한 규칙이 모순되지 않게 검토한다.
다른 테이블을 확인하는 제약은
적절한 함수·트리거·트랜잭션으로 보장한다.
━━━━━━━━━━━━━━━━━━━━
13. 구현 순서
━━━━━━━━━━━━━━━━━━━━
1. 저장소 확인, 최종 ERD·구현 계획
2. 프로젝트 기반·마이그레이션·RLS
3. 이메일·구글 로그인과 OWNER 지정
4. 카테고리·소개·게시글 CRUD
5. 공개/비공개 전환과 권한별 조회
6. 에디터·파일·저장소 어댑터
7. 댓글·별점·답글·운영자 삭제·이력
8. 운영정책 페이지·관리·미리보기·게시·푸터 링크
9. 상품·주문서·재고 예약
10. 토스 테스트 결제·웹훅·취소·상태 복구
11. 배송·방문자·대시보드
12. 통합 검증·설정·배포 문서
각 단계에서:
구현 → 관련 타입·린트·빌드 검사 → 핵심 테스트 → 결과 요약
순서로 진행한다.
일반 구현 선택은 합리적으로 결정하고 근거를 기록한다.
매 단계마다 승인 질문으로 중단하지 않는다.
요구사항 충돌·유료 가입·실제 결제·배포처럼
사용자 결정이 필요한 사항만 명확하게 질문한다.
키가 없어도 구현 가능한 코드는 완성한다.
연동되지 않은 기능을 성공한 것처럼 표시하지 않는다.
테스트 결과를 꾸미지 않는다.
━━━━━━━━━━━━━━━━━━━━
14. 필수 검증 시나리오
━━━━━━━━━━━━━━━━━━━━
콘텐츠:
- 비로그인 공개 글 열람
- 일반 회원의 글 작성·수정·삭제 차단
- OWNER의 일반 목록에서 공개·비공개 글 조회
- OWNER의 본인 콘텐츠만 편집
- 공개 범위 변경 후 목록·상세·검색·집계 갱신
- 비공개 본문·파일·댓글 직접 접근 차단
- 비공개 응답의 공유 캐시 노출 방지
- 비공개 → 공개 후 정상 열람
댓글:
- 소개·대시보드·정책에 댓글 기능 없음
- 일반 게시글 별점 차단
- 상품 별점 1~3 검증
- 일반 회원 답글 차단
- 원댓글당 답글 하나
- 본인 댓글 수정·삭제 허용
- 일반 회원의 타인 댓글 수정·삭제 차단
- OWNER의 타인 댓글 삭제 허용
- OWNER의 타인 댓글 수정 차단
- 운영자 삭제 사유 필수
- 삭제와 이력의 원자성·멱등성
- 삭제 원문·별점·내부 메모 노출 차단
- 삭제 댓글의 작성자 수정·복구 차단
- 삭제 원댓글의 답글·자리표시자 처리
- 일반 회원의 삭제 로그 접근·변조 차단
운영정책:
- 초기부터 푸터에 운영정책 링크 표시
- 비로그인 상태에서 링크 클릭 시 정상 페이지 진입
- 본문이 없으면 “운영정책을 준비 중입니다.” 표시
- OWNER만 초안 작성·편집·미리보기 가능
- 빈 정책 게시 차단
- 초안 저장만으로 공개 본문이 변경되지 않음
- 게시 후 같은 주소에서 실제 정책 표시
- 새 초안 작성 중 기존 공개 버전 유지
- 새 버전 게시 시 공개 버전 원자적 교체
- 초안·과거 버전 외부 접근 차단
- 게시 중단 후에도 푸터 링크·페이지 유지
- 게시 중단 후 준비 중 안내 표시
- 정책이 없어도 댓글 삭제 기능 정상 동작
- 정책 변경으로 과거 삭제 이력이 바뀌지 않음
주문·기타:
- 변조한 가격·수량·주문자·주문번호 거부
- 마지막 재고의 동시 주문 초과 예약 방지
- 결제 확정·예약 만료 경합 처리
- 중복 승인·웹훅·취소의 중복 반영 방지
- 타인 주문·배송지·결제 정보 조회 차단
- 같은 방문자의 같은 날 중복 집계 방지
- 저장소 교체 후 파일 ID·본문 연결 유지
━━━━━━━━━━━━━━━━━━━━
15. 산출물과 완료 보고
━━━━━━━━━━━━━━━━━━━━
- 동작하는 소스 코드
- 통합 Mermaid ERD
- SQL 마이그레이션
- RLS·Storage 정책
- 실제 비밀값 없는 .env.example
- 로컬 실행·Supabase·OAuth 설정 문서
- OWNER 지정 방법
- 댓글 삭제 권한·사유·이력·원문 보관 정책 문서
- 운영정책 작성·미리보기·게시·버전 교체 방법
- 푸터 링크와 준비 중 페이지 동작 설명
- 토스 테스트 결제·카카오 주소검색 설정
- 스케줄 작업 설정
- Vercel 배포 방법과 플랫폼 제한
- Supabase → EC2 저장소 이전 설계
- 실행한 검사·테스트 결과
- 외부 설정이 없어 검증하지 못한 항목
미구현 기능과 실패한 검사를 숨기지 않는다.
운영 전 서비스별 요금제·상업적 사용 조건 확인 사항을 문서화한다.
먼저 저장소를 확인하고 docs/ 안에 ERD와 구현 계획을 작성한 뒤,
DB·인증 기반부터 순서대로 구현을 시작해라.