Files
tasteby/CHANGELOG.md

14 KiB
Raw Blame History

Tasteby 작업 기록

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


2026-06-15

🧹 P5-1 작은 후속 묶음 (v0.1.24)

  • #325: ThreadLocalRandom 통일, rebuildVectors not_implemented 이벤트, getTranscript JavaDoc 명세
  • #319: buildSearchQuery 헬퍼 + fn-doc(BottomSheet snap 정책)
  • #344: --z-bottom-sheet/--z-filter-sheet/--z-modal CSS 변수 + LoginMenu zIndex 99999 → var(--z-modal)
  • Refs: #319 #325 #344 (close)

P4-4 별점 공통화 + 로그인 모달 접근성 (v0.1.23)

  • #281: 공통 Stars 컴포넌트 (0.5단위 절반 채우기), StarSelector role=radiogroup + 44px + 반쪽 별 ⯨, try/catch + alert
  • #283: LoginMenu에 useEscapeKey/useFocusTrap/useBodyScrollLock 훅 적용, role=dialog/aria-modal/aria-labelledby, onError 인라인 alert
  • MyReviewsList: Math.round → Stars (0.5단위 정확 렌더)
  • 후속 분리: #343(next/image, ARIA Tabs, 테스트), #344(z-index 토큰, i18n)
  • Refs: #281 #283 (close)

🔐 P4-3 인증 메시지 + 지도 접근성 (v0.1.22)

  • #266: Google verifier 실패 메시지 고정 + log.warn (정보 누출 차단)
  • #278: boundsTimerRef cleanup, '내 위치' 44px + aria-label, dead code 제거
  • #277: 결함 모두 후속(#338) — deep health/version/테스트는 별도
  • 후속 분리: #338(deep health), #339(브랜드 토큰화/마커 ARIA), #340(다중 audience)
  • Refs: #266 #277 #278 (close)

⚙️ P4-2 데몬/캐시/통계 결함 (v0.1.21)

  • #275: updateConfig 가드(1+ 정수), Scheduler try-finally updateLastX, GET config admin-only
  • #276: ping try-with-resources + ConnectionFactory null 가드, makeKey null 가드
  • #274: SiteVisitStats int → long, recordVisit DataIntegrityViolationException 1회 재시도
  • 후속 분리: #335 (분산락), #336 (SCAN/자동복구), #337 (봇/레이트리밋)
  • Refs: #275 #276 #274 (close)

🧱 P4-1 백엔드 CRUD 결함 (v0.1.20)

  • #294: MemoService/ReviewService 동시성 DuplicateKeyException 가드, rating 0~5 검증, getAvgRating NVL
  • #295: 유니크 충돌 typed exception, channel_id "UC..." 형식 명시 분기, findByChannelId 컬럼 보완, body null 가드
  • #290: @PreDestroy executor shutdown, 캐시 silent → log.warn + cache.del, tabling/catchtable URL 스킴 화이트리스트
  • 후속 분리: #332(#290), #333(#295), #334(#294) — DTO/DDG/세분화/테스트
  • Refs: #290 #294 #295 (close)

🔍 #293 검색/벡터 결함 7건 (v0.1.19)

  • SearchController: q 빈값 400 가드 (%% 응답 폭발 차단)
  • SearchService: LIKE 와일드카드 escape (%, _, ), hybrid 모드에서 sem 결과에도 채널 부착
  • SearchService: ObjectMapper/TypeReference static 재사용, 알 수 없는 mode warn 로그
  • SearchService: maxDistance를 @Value("${app.search.max-distance:0.57}") 외부화 (env SEARCH_MAX_DISTANCE)
  • SearchMapper.xml: LIKE 절에 ESCAPE '' 추가
  • VectorService: embeddings null/empty 가드 (NPE 차단)
  • 후속 분리: #331 (batch insert + 테스트)
  • Refs: #293 (close)

🛠 #304+#323 어드민 LLM 검증 UI + 공통 유틸 (v0.1.18)

  • 신규 frontend/src/lib/admin-utils.ts:
    • getAdminToken / authHeaders / consumeSseStream
  • api.ts: Restaurant 타입에 hidden/hidden_reason/verified_at + verify/setRestaurantHidden API 4개
  • RestaurantsPanel:
    • 헤더: "미검증 N건 + LLM 검증" 버튼
    • 테이블: 검증 컬럼 (숨김/OK/미검증 배지 + 클릭으로 토글)
  • colSpan 7로 수정
  • 후속 분리: #329 (admin 전체 파일 분리 + localStorage/SSE 11+곳 통일)
  • Refs: #304 #323 #322 (close)

🔧 #291+#292 백엔드 결함 일괄 수정 (v0.1.17)

  • ExtractorService: transcript null/blank 가드 (NPE 방지)
  • PipelineService.processExtract: 진입 시 status='processing' 명시 전이 (SSE/사용자 가시성)
  • PipelineService: geocode 실패 시 좌표/place_id/주소 컬럼을 data에 put하지 않아 upsert COALESCE 보존 의도 명확화
  • GeocodingService.parseRegionFromAddress: 빈 토큰을 region 문자열에서 제거 ('한국||구' 깨짐 방지)
  • VideoService.saveVideosBatch: @Transactional 추가 → batch insert 원자성
  • .gitignore: backend-java/cookies.txt 및 **/cookies.txt
  • 후속 분리: #325 (#291 잔여 MINOR), #326 (parseJson 최적화 + #292 MINOR)
  • Refs: #291 #292 (close)

🧹 #322 LLM 검증으로 잘못된/프랜차이즈 식당 자동 숨김 (v0.1.16)

  • DB 마이그레이션: restaurants에 hidden(NUMBER(1)), hidden_reason(VARCHAR2(120)), verified_at(TIMESTAMP) + idx_restaurants_hidden
  • 도메인/Mapper/Service 확장: includeHidden 옵션, updateVerification, findUnverified 등
  • 신규 RestaurantVerifyService:
    • verifyAsync (신규 등록 자동 검증)
    • verifyAll (백필, 식당당 200ms sleep)
    • parseVerifyResponse (안전 기본값: 파싱 실패 시 valid=true → hidden 유지)
  • PipelineService.processExtract 끝에 verifyAsync(restId) 자동 호출
  • AdminRestaurantController 신규 (requireAdmin):
    • GET /api/admin/restaurants/verify/pending
    • POST /api/admin/restaurants/verify/all?batchSize=10
    • POST /api/admin/restaurants/{id}/verify
    • PATCH /api/admin/restaurants/{id}/hidden
  • 어드민 UI는 후속 #323으로 분리
  • Refs: #322 (close)

📺 #291 publishedAfter 페이징 조기 종료 버그 (v0.1.15) + dev/prod 데몬 분리

  • YouTubeService.fetchChannelVideos: stopPaging 플래그로 조기 종료 정확화 → 백필 효율 + YouTube API quota 절약
  • DaemonScheduler에 app.daemon.enabled (env DAEMON_ENABLED) 플래그
  • dev/prod가 같은 Oracle ATP를 공유하는 환경에서 dev DAEMON_ENABLED=false로 중복 폴링 차단
  • Refs: #291 #275 #321

#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-1p-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: findAllWithCountsis_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.javaallowedMethodsPATCH가 빠져 있었음
  • 해결: List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")"PATCH" 추가

Icon 시스템 개선

  • Material Symbols sake 아이콘 종횡비 문제 수정 — width/heightfontSize와 동일하게 고정 + overflow: hidden
  • 이자카야 아이콘: sakelocal_bar (술잔 모양으로 변경)
  • 삼겹살/돼지구이, 족발/보쌈, 돈카츠: PiggyBankfood: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.tsgetPhosphorCuisineIcon() 함수 추가 (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 아님)