Files
startover/docs/BUG-REPORT-2026-03-08.md
Johngreen de531bfe11 Protect admin pages, add store/subsidy actions
Enforce authentication/authorization for admin pages and add several management actions and UI improvements.

Key changes:
- Added auth checks and redirects on admin pages (contracts, stores, subsidies, vendors) to restrict access to SUPER_ADMIN/OPS_MANAGER.
- Hooked server actions to authenticated user IDs (release escrow, review/publish stores, review subsidies/vendors, open disputes, create subsidy cases, create match requests, submit/delete store drafts).
- Implemented store publish flow including policy version resolution and StoreActionButtons update to show approve/reject/publish based on reviewStatus.
- Added filtering UIs and query handling: admin lists (stores/subsidies/vendors) now support status filters; public stores list uses a new client StoreFilters component to build search params.
- New client-side improvements: invite form triggers router.refresh() after success; register page accepts/validates optional phone and persists it; store creation now uses createStoreDraftService and shows error banner on failure.
- Matching and subsidies pages now support contextual forms to create match requests and subsidy cases when a storeId is provided.
- Various UX tweaks: disabled inspection button, dispute form, list link styling, and revalidation calls after server actions.
- Added docs/BUG-REPORT-2026-03-08.md.

These changes centralize auth, connect actions to real user IDs, and improve admin and store workflows and filtering.
2026-03-08 23:15:21 +09:00

204 lines
9.7 KiB
Markdown

# Startover DB 상호작용 전수조사 버그 리포트
**조사일**: 2026-03-08
**조사 범위**: DB와 상호작용하는 모든 페이지 (총 15개 페이지, 5개 API 라우트, 4개 서비스)
**조사 방법**: 코드 정적 분석 (모든 CRUD 경로 추적)
**빌드 검증**: `pnpm turbo build` 전체 통과 (7/7 tasks, 0 type errors)
---
## 수정 현황
| 심각도 | 발견 | 수정 완료 | 미수정 |
|--------|------|-----------|--------|
| CRITICAL | 6건 | **6건** | 0건 |
| HIGH | 9건 | **8건** | 1건 |
| MEDIUM | 8건 | **6건** | 2건 |
| **합계** | **23건** | **20건** | **3건** |
### 수정 완료 목록 (20건)
- BUG-001~006: 하드코딩 사용자 ID → `auth()` 세션 사용 (CRITICAL x6) ✅
- BUG-007: `/stores/new` 서비스 레이어 우회 → `createStoreDraftService` 호출 (HIGH) ✅
- BUG-008: 매장 DRAFT→SUBMITTED 제출/삭제 + admin APPROVED→PUBLISHED 공개 버튼 (HIGH) ✅
- BUG-010: `/stores` 필터 동작 연결 (HIGH) ✅
- BUG-011: `/contracts` 분쟁 접수 버튼 → `openDisputeService` 연결 (HIGH) ✅
- BUG-012: `/matching` 매칭 요청 생성 폼 + `matchRequest.create` 구현 (HIGH) ✅
- BUG-013: `/subsidies` 지원금 케이스 생성 폼 + `createSubsidyCaseService` 연결 (HIGH) ✅
- BUG-014: admin 3개 페이지 필터 → `searchParams` + `Link` 연동 (HIGH) ✅
- BUG-015: 초대 후 목록 갱신 (`router.refresh()`) (HIGH) ✅
- BUG-017: 회원가입 전화번호 필드 추가 (MEDIUM) ✅
- BUG-018: `/stores/new` 에러 핸들링 추가 (MEDIUM) ✅
- BUG-019~022: 관리자 4개 페이지 인증 검증 추가 (MEDIUM x4) ✅
### 미수정 (추가 작업 필요, 3건)
- BUG-009: 매장 수정 폼 (DRAFT 상태 수정 기능)
- BUG-016: 에스크로 RELEASE_REVIEW → RELEASED 전환 관리자 액션
- BUG-023: `/stores/new` form validation 빈 문자열 검증 (도메인 서비스에서 부분 처리됨)
---
## CRITICAL (즉시 수정 필요) - 전부 수정 완료 ✅
### BUG-001: `/stores/new` - 하드코딩된 사용자 ID ✅
- **파일**: `apps/web/src/app/stores/new/page.tsx`
- **수정**: `TEMP_OWNER_USER_ID = BigInt(1)``auth()` 세션에서 `session.user.dbId` 사용
### BUG-002: `/vendors` actions - 하드코딩된 사용자 ID ✅
- **파일**: `apps/web/src/app/vendors/actions.ts`
- **수정**: `ownerUserId: '1'``session.user.dbId`
### BUG-003: `/admin/stores` - 하드코딩된 actorUserId ✅
- **파일**: `apps/web/src/app/admin/stores/page.tsx`
- **수정**: `actorUserId = '1'``session.user.dbId`
### BUG-004: `/admin/vendors` - 하드코딩된 actorUserId ✅
- **파일**: `apps/web/src/app/admin/vendors/page.tsx`
- **수정**: 4개 server action 모두 `auth()` 세션 사용
### BUG-005: `/admin/contracts` - 하드코딩된 actorUserId ✅
- **파일**: `apps/web/src/app/admin/contracts/page.tsx`
- **수정**: `releaseEscrowService(prisma, contractPublicId, '1')``session.user.dbId`
### BUG-006: `/admin/subsidies` - 하드코딩된 actorUserId ✅
- **파일**: `apps/web/src/app/admin/subsidies/page.tsx`
- **수정**: 승인/반려 모두 `session.user.dbId` 사용
---
## HIGH (빠른 수정 필요)
### BUG-007: `/stores/new` - Server Action이 서비스 레이어를 우회 ✅
- **파일**: `apps/web/src/app/stores/new/page.tsx`
- **수정**: 직접 `prisma.store.create``createStoreDraftService` 호출
- **효과**: 도메인 검증, AuditLog, OutboxEvent 정상 동작
### BUG-008: 매장 등록 → 공개 플로우 단절 ✅
- **수정 내용**:
- `/stores/[id]`: DRAFT 상태에서 "검토 제출" 버튼 (→SUBMITTED) + "삭제" 버튼 추가
- `/admin/stores`: APPROVED 상태에서 "공개" 버튼 추가 (`publishStoreService` 연결)
- `StoreActionButtons`: 상태별 버튼 분기 (SUBMITTED: 승인/반려, APPROVED: 공개)
### BUG-009: `/stores/[id]` - 수정 기능 부재 ⚠️ 미수정
- **파일**: `apps/web/src/app/stores/[id]/page.tsx`
- **현재 상태**: 삭제는 가능 (DRAFT 상태), 수정 폼은 미구현
- **남은 작업**: 매장 정보 수정 폼 페이지 추가
### BUG-010: `/stores` - 필터가 동작하지 않음 ✅
- **파일**: `apps/web/src/app/stores/page.tsx`
- **수정**: `searchParams` 기반 Prisma `where` 조건 연동 (region/industry/status 필터)
### BUG-011: `/contracts` - "분쟁 접수" 버튼 미동작 ✅
- **파일**: `apps/web/src/app/contracts/page.tsx`
- **수정**: `handleOpenDispute` server action → `openDisputeService` 연결
- **참고**: "검수 요청" 버튼은 "준비 중" 상태로 비활성화
### BUG-012: `/matching` - 매칭 요청 생성 기능 없음 ✅
- **파일**: `apps/web/src/app/matching/page.tsx`
- **수정**: `handleCreateMatchRequest` server action + 매칭 요청 폼 구현
- `storeId` searchParams 처리, 매장 정보 조회
- matchType(인수/철거/인테리어) 선택 + 메시지 입력
- `matchRequest.create` + `auditLog.create` 트랜잭션
### BUG-013: `/subsidies` - 지원금 케이스 생성 UI 없음 ✅
- **파일**: `apps/web/src/app/subsidies/page.tsx`
- **수정**: `handleCreateSubsidyCase` server action + 신청 폼 구현
- `storeId` searchParams로 매장 연결
- `createSubsidyCaseService` 호출
- 체크리스트 진행률 프로그레스바 표시
### BUG-014: 관리자 페이지 필터 버튼 미동작 ✅
- **수정**:
- `/admin/stores`: `FILTER_LABELS` + `searchParams` + `StoreReviewStatus` 타입 캐스트
- `/admin/vendors`: `FILTER_BUTTONS` + `VendorCertificationStatus` 타입 캐스트
- `/admin/subsidies`: `FILTER_BUTTONS` + `SubsidyCaseStatus` 타입 캐스트
### BUG-015: `/admin/settings/invite` - 초대 후 목록 갱신 안 됨 ✅
- **파일**: `apps/web/src/app/admin/settings/invite/invite-form.tsx`
- **수정**: 성공 시 `router.refresh()` 추가
---
## MEDIUM (개선 필요)
### BUG-016: `/admin/contracts` - releaseAction이 RELEASE_REVIEW까지만 처리 ⚠️ 미수정
- **파일**: `apps/web/src/services/contract-service.ts:150-154`
- **증상**: `releaseEscrowService``RELEASE_REVIEW`로만 변경. `RELEASED` 전환 후속 처리 없음
- **남은 작업**: RELEASE_REVIEW → RELEASED 전환 관리자 액션 추가
### BUG-017: `/auth/register` - 전화번호 필드 누락 ✅
- **파일**: `apps/web/src/app/auth/register/page.tsx`, `actions.ts`
- **수정**: phone 입력 필드 추가, zod 스키마에 phone 정규식 검증 추가
### BUG-018: `/stores/new` - 에러 핸들링 부재 ✅
- **파일**: `apps/web/src/app/stores/new/page.tsx`
- **수정**: `useActionState` + ErrorBanner로 에러 표시
### BUG-019~022: 관리자 페이지 인증 검증 없음 ✅
- `/admin/stores`, `/admin/vendors`, `/admin/contracts`, `/admin/subsidies`
- **수정**: `auth()` + `SUPER_ADMIN`/`OPS_MANAGER` role 체크, 미인증 시 `/403` redirect
### BUG-023: `/stores/new` - form validation 불완전 ⚠️ 미수정
- **현재 상태**: `createStoreDraftService` 도메인 레이어에서 부분 검증
- **남은 작업**: 서버 측 빈 문자열 추가 검증
---
## 변경된 파일 목록 (15개 파일, +637 / -202 lines)
| 파일 | 변경 내용 |
|------|-----------|
| `apps/web/src/app/admin/contracts/page.tsx` | auth 가드, session.user.dbId |
| `apps/web/src/app/admin/settings/invite/invite-form.tsx` | router.refresh() |
| `apps/web/src/app/admin/stores/StoreActionButtons.tsx` | 상태별 버튼 분기 (publish 추가) |
| `apps/web/src/app/admin/stores/page.tsx` | auth 가드, publish, 필터, enum 캐스트 |
| `apps/web/src/app/admin/subsidies/page.tsx` | auth 가드, 필터, enum 캐스트 |
| `apps/web/src/app/admin/vendors/page.tsx` | auth 가드, 필터, enum 캐스트 |
| `apps/web/src/app/auth/register/actions.ts` | phone zod 스키마 + DB 저장 |
| `apps/web/src/app/auth/register/page.tsx` | phone 입력 필드 |
| `apps/web/src/app/contracts/page.tsx` | 분쟁 접수 server action |
| `apps/web/src/app/matching/page.tsx` | 매칭 요청 생성 폼 + server action |
| `apps/web/src/app/stores/[id]/page.tsx` | 검토 제출 + 삭제 server action |
| `apps/web/src/app/stores/new/page.tsx` | 서비스 레이어, auth, 에러 핸들링 |
| `apps/web/src/app/stores/page.tsx` | searchParams 필터 연동 |
| `apps/web/src/app/subsidies/page.tsx` | 지원금 신청 폼 + server action |
| `apps/web/src/app/vendors/actions.ts` | session.user.dbId |
---
## 페이지별 DB 상호작용 현황 (수정 후)
### 사용자 페이지
| 페이지 | CREATE | READ | UPDATE | DELETE | 상태 |
|--------|--------|------|--------|--------|------|
| `/stores` | - | O | - | - | ✅ 필터 동작 |
| `/stores/new` | O | - | - | - | ✅ 서비스 레이어, auth |
| `/stores/[id]` | - | O | O | O | ✅ 제출/삭제 (수정 미구현) |
| `/vendors` | O | O | - | - | ✅ auth 세션 사용 |
| `/contracts` | - | O | O | - | ✅ 분쟁 접수 연결 |
| `/subsidies` | O | O | - | - | ✅ 신청 폼 구현 |
| `/matching` | O | O | - | - | ✅ 요청 생성 구현 |
| `/auth/register` | O | O | - | - | ✅ phone 추가 |
| `/auth/complete-profile` | O | O | O | - | 정상 |
| `/auth/invite/[token]` | O | O | O | - | 정상 |
### 관리자 페이지
| 페이지 | CREATE | READ | UPDATE | DELETE | 상태 |
|--------|--------|------|--------|--------|------|
| `/admin/stores` | - | O | O | - | ✅ auth, 필터, publish |
| `/admin/vendors` | - | O | O | - | ✅ auth, 필터 |
| `/admin/contracts` | - | O | O | - | ✅ auth |
| `/admin/subsidies` | - | O | O | - | ✅ auth, 필터 |
| `/admin/settings/invite` | O | O | - | - | ✅ 목록 갱신 |
### API 라우트
| 엔드포인트 | 동작 | 상태 |
|-----------|------|------|
| `POST /api/v1/stores` | CREATE | 정상 |
| `POST /api/v1/stores/[id]/submit` | UPDATE | 정상 |
| `POST /api/auth/complete-profile` | UPDATE | 정상 |
| `POST /api/auth/invite/create` | CREATE | 정상 |
| `POST /api/auth/invite/accept` | CREATE | 정상 |