# Tasteby 작업 기록

> 작업 내용, 이슈, 해결 방법을 기록하는 문서. 커밋/배포 시 참고용.

---

## 2026-06-15

### ♿ #301+#302 모달 접근성 + race condition + 필터 상태 동기화 (v0.1.14)
- 공통 훅 `frontend/src/lib/hooks/useModalA11y.ts` 신규 (useEscapeKey, useFocusTrap, useBodyScrollLock)
- BottomSheet/FilterSheet: role='dialog', aria-modal, aria-label/labelledby, ESC 닫기, focus trap
- RestaurantDetail: useEffect cancelled 플래그로 restaurant.id 변경 시 race condition 차단
- page.tsx: `exitSearchMode` 헬퍼 → 검색결과 모드에서 필터 변경 시 자동 검색 모드 해제 + 원본 재로드
- 후속 분리: #319 (BottomSheet 매직넘버/UX), #320 (필터 정밀도/접근성/테스트)
- Refs: #301 #302 (close)

### 🔧 #316 — backend resource request 재산정 + RollingUpdate 정책 복귀
- **변경 전**: cpu 500m/1, mem 768Mi/1536Mi, strategy maxSurge=0/maxUnavailable=1 (임시 패치)
- **변경 후**: cpu 300m/800m, mem 512Mi/1024Mi, strategy 25%/25% (기본 복귀)
- **근거**: 실측 idle 0.7% CPU, RSS ~305 MB. peak 30-40% 추정 안에서 안전.
- **검증**: rollout 후 노드 잔여 330m → 다음 배포 시 두 Pod 공존 가능, 무중단 RollingUpdate 회복.
- **다운타임**: 이번 1회 ~25초 (구 Pod 500m 점유 해제 위해 강제 종료). 다음 배포부터 0초.
- **설계서**: `docs/design/316-backend-resource-rightsize/README.md` (Approved).
- Refs: #316 (close)

### 🏗 OKE 인프라 — 노드 다운사이징 + LB 정리
- **Orphan Classic LB 삭제**: 132.226.175.247 (100Mbps shape, OKEclusterName 태그만 남고 DNS/Service 참조 없음) → 비용 절감
- **노드풀 교체 (블루-그린)**: `pool1` (2 노드 × 2 OCPU / 8 GB) → `pool2` (2 노드 × 1 OCPU / 6 GB)
  - 사유: ARM64 Always Free 쿼터 변경 (4 OCPU/24 GB → 2 OCPU/12 GB)
  - 절차: 새 노드풀 생성 → 기존 노드 cordon + drain → 기존 노드풀 삭제 → 무중단 확인
- **backend Deployment strategy 임시 패치**: `maxSurge: 25% → 0`, `maxUnavailable: 25% → 1`
  - 노드당 1 OCPU 환경에서 backend(500m 요청) 두 Pod 공존 불가 → rollingUpdate 데드락 회피
  - **⚠️ 다음 배포 시 ~30초 다운타임** 발생. 후속 이슈에서 resource request 재산정 권고.

### 🚀 운영 배포 v0.1.13
- 보안 핫픽스 #267 배포 (백엔드만)
- OCIR push + kubectl rolling update + git tag v0.1.13 완료
- 검증: `Anonymous /api/admin/users → 403`, `Bad-token → 403`, `정상 동작 영향 없음`

### 🔴 보안 핫픽스 #267 — AdminUserController GET 4종 권한 우회
- `listUsers`, `userFavorites`, `userReviews`, `userMemos`가 인증만 요구하고 admin 검사를 하지 않아 일반 사용자 토큰으로 전체 사용자 목록 및 타인 활동 조회 가능했음
- 4개 메서드 첫 줄에 `AuthUtil.requireAdmin()` 추가 → non-admin 호출 시 403
- 설계서 §3 인수조건에 `/api/admin/users/**` 권한 강제 항목 추가
- Refs: #267 (현행화 Reviewer 반려 → Developer 수정 → 다시 통과)

### ch-bootstrap 적용 (페르소나 파이프라인 + Design-First)
- Redmine 8단계 페르소나 큐(`01-Planner` ~ `09-Done`) + 9개 카테고리 자동 생성
- Design-First 게이트(설계서 없으면 코드 작성 금지) 도입
- `.claude/agents/` 8개 페르소나 + `.claude/workflows/persona-pipeline.js`
- 안전-최대 권한 정책(`.claude/settings.json`)
- `docs/{design,adr,pipeline}/` 골격 + `scripts/enqueue.sh`
- 기존 Tasteby 고유 규칙(존댓말, CHANGELOG, 디자인 패턴, CORS, PM2)은 `CLAUDE.md` 0/7/8장으로 보존
- Redmine 프로젝트 description + Wiki 4페이지(Overview/Dev-Env/Prod-Env/Deploy) 작성

### tasteby 기존 18개 기능 Design-First 현행화
- 백엔드 12개(auth/user/restaurant/video/extract-pipeline/search/review-memo/channel/stats/daemon/cache/health) + 프론트 6개(map/restaurant-detail/filter/review-memo/admin/login)
- 각 기능별 `docs/design/<issue>-<slug>/README.md` 12개 섹션 채움 (총 3,830줄)
- 추적성: 각 설계서가 구현 파일/Redmine 이슈/커밋 SHA와 연결됨
- **Reviewer 결과**: 17 PASS w/notes, 1 REJECT (#267 admin 권한 critical)
- 후속 17개 개선 이슈(#289~#305) 자동 등록 — 결함 총 124건(critical 3 / major 46 / minor 75) 백로그 반영
- 코드 변경 없음 — 문서화 + 백로그화 전용

---

## 2026-04-04

### 코드 리뷰 스크립트 추가 + 리뷰 지적사항 반영
- `scripts/code_review.py`: 페르소나 기반 코드 리뷰 스크립트 (OpenRouter API, 프론트/백엔드/보안/아키텍처 4관점)
- `UserService.updateAdmin()`: 존재하지 않는 userId에 대해 404 응답 추가
- `AdminUserController.updateAdmin()`: 자기 자신 admin 권한 변경 차단 + 감사 로그 추가 + 응답에 변경 결과 포함
- `JsonUtil.normalizeEvaluation()`: evaluation 정규화 로직을 공통 유틸로 통합 (RestaurantService, VideoService 중복 제거)
- `RestaurantService.linkVideoRestaurant()`: evaluation 저장 시 평문→JSON 정규화 + 300자 제한

### 가격대 필터 5단계 세분화
- 기존 3단계(저렴/보통/고가) → 5단계(저렴/가성비/보통/프리미엄/럭셔리)
- `PRICE_GROUPS` 상수 수정, 정규식 패턴 세분화

### 모바일 터치 영역 개선 (44×44px 통일)
- **별점 선택기**: 0.5단위 10개 숫자 버튼(24px) → 별 아이콘 5개(44px), 탭으로 정수/반점수 전환
- **FilterSheet 닫기 버튼**: `p-1` → `p-2` (터치 영역 확대)
- **RestaurantDetail 찜 버튼**: 패딩 추가 + `touch-manipulation` 적용
- **필터 초기화 X 버튼**: 아이콘 12px → 14px + 패딩 추가

### 채널 필터 시 식당이 3개만 나오는 버그 수정
- **원인**: 전체 식당 500개만 가져와서 클라이언트 필터링 → 특정 채널 식당이 상위 500개에 일부만 포함
- **수정**: `page.tsx`에서 채널 필터 변경 시 서버에 `channel` 파라미터를 보내 서버 사이드 필터링 적용

---

## 2026-03-29

### 식당 평가(evaluation) 표시 안 되는 버그 수정
- **원인**: LLM이 추출한 evaluation이 대부분 평문 문자열로 DB에 저장되어 있었으나, 프론트에서 `evaluation.text`로 접근하여 표시되지 않음
- **수정**:
  - `JsonUtil.parseMap()`: JSON 파싱 실패 시 `{"text":"원본문자열"}`로 감싸서 반환
  - `VideoService.findDetail()`: `VideoRestaurantLink`의 evaluation 평문을 JSON 객체로 정규화

---

## 2026-03-16

### Admin 유저 관리 — 관리자 권한 토글 기능 추가
- **Backend**
  - `UserMapper.xml`: `findAllWithCounts`에 `is_admin` 컬럼 추가, `updateAdmin` 쿼리 추가
  - `UserMapper.java`: `updateAdmin()` 메서드 추가
  - `UserService.java`: `updateAdmin()` 메서드 추가
  - `AdminUserController.java`: `PATCH /api/admin/users/{userId}/admin` 엔드포인트 추가
- **Frontend**
  - `api.ts`: `updateAdminUserAdmin()` API 함수 추가, 유저 타입에 `is_admin` 필드 추가
  - `admin/page.tsx`: 유저 테이블에 "관리자" 컬럼 + ON/OFF 토글 버튼 추가

### CORS PATCH 메서드 허용
- **문제**: PATCH 요청 시 CORS preflight(OPTIONS)에서 403 차단
- **원인**: `WebConfig.java`의 `allowedMethods`에 `PATCH`가 빠져 있었음
- **해결**: `List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")` → `"PATCH"` 추가

### Icon 시스템 개선
- Material Symbols `sake` 아이콘 종횡비 문제 수정 — `width`/`height`를 `fontSize`와 동일하게 고정 + `overflow: hidden`
- 이자카야 아이콘: `sake` → `local_bar` (술잔 모양으로 변경)
- 삼겹살/돼지구이, 족발/보쌈, 돈카츠: `PiggyBank` → `food:pig` (커스텀 돼지 SVG)

### LLM 추출 프롬프트 수정
- `ExtractorService.java`: `evaluation` 필드 → "평가 내용을 100자 이내로 요약"으로 변경

### 브랜드 가이드 문서 생성
- `frontend/docs/brand-guide.md`: 브랜드 아이덴티티, 컬러, 타이포, 아이콘 정책 등 정리

### PM2 프론트엔드 포트 고정
- **문제**: `pm2 restart` 후 Next.js가 3000(Gitea 포트)으로 fallback → nginx 502
- **해결**: PM2에 `PORT=3001` 환경변수 고정하여 재등록 + `pm2 save`

---

## 2026-03-14

### 홈 탭 장르 카드 픽토그램 적용
- Phosphor Icons (`@phosphor-icons/react`) + 커스텀 SVG FoodIcon 시스템 구축
- `cuisine-icons.ts`에 `getPhosphorCuisineIcon()` 함수 추가 (46개 소분류 매핑)
- `FoodIcon.tsx` 생성 — jjigae, tteok, noodle, tempura, pig 커스텀 SVG 아이콘
- `food:` 접두어로 Phosphor vs 커스텀 SVG 분기 처리

### 지역 필터 추가 + 배포
- 홈 탭에 지역 필터 드롭다운 추가
- v0.1.11로 OKE 배포 완료

---

## 참고: 주의사항

| 항목 | 내용 |
|------|------|
| 새 HTTP 메서드 추가 시 | `WebConfig.java`의 CORS `allowedMethods`에 반드시 추가 |
| 백엔드 코드 수정 후 | `bootJar` 빌드 성공 확인 → `pm2 restart tasteby-api` |
| 프론트엔드 dev 포트 | 3001 고정 (3000은 Gitea) |
| tasteby-web 실행 방식 | `npm run dev` (standalone 아님) |
