6.2 KiB
6.2 KiB
2026-06-29 게임 "좋아요" 서버 영속화
세션: 20260629-151216 (구현) · 20260629-171042 (배포·실환경 검증) · 검증: L1 PASS + 실환경 스모크 PASS (2026-06-29, docker compose dev) · L2 dev DB skip
한 줄 요약
게임 좋아요가 서버에 전혀 저장되지 않고 브라우저 localStorage 로만 토글되던 버그를 수정. 신규 POST /game/{id}/like 토글 엔드포인트를 추가해 game_likes row 와 games.like_count 비정규화 컬럼을 단일 트랜잭션으로 동기 변경하고, explore 목록은 무변경으로 증가된 컬럼을 읽어 좋아요 수가 반영된다.
배경 / 버그
- 증상: 게임 상세페이지에서 좋아요를 눌러도 explore(탐색) 페이지에서 좋아요 수가 올라가지 않는다.
- 근본원인(seed 가정 반전): "explore 와 상세가 서로 다른 카운트 소스를 읽는 동기화 불일치" 가 아니라 좋아요가 어떤 영속 저장소에도 쓰이지 않았다.
- 좋아요 토글 서버 엔드포인트가 애초에 부재 (GameController 매핑 0개).
GameLikesMapper.addGameLike/updateGameLike는 호출자 0건 dead code.- 상세 JSP 좋아요 버튼이 서버 호출 없이
localStorage('bibimbap-game-liked')만 토글하고 화면 카운트를baseLikes ± 1로 로컬 계산. - explore 5개 조회 + 상세 모두 동일하게
games.like_count컬럼을 직접 SELECT — 그 컬럼이 갱신되지 않으므로 영원히 그대로.
- 근본원인 상세:
.atp/work-session/20260629-151216/research/like-count-rootcause.md
사용자 결정
- D1 (수정 범위 / 카운트 모델): 서버 영속 +
games.like_count비정규화 컬럼 동기화. explore 쿼리는 무변경. - D2 (좋아요 주체): 로그인 사용자 기준(
sessionUserId). 미로그인 시 401 로 거부하고 로그인 유도. 사용자당 게임당 1회(멱등 토글).
설계 상세: .atp/work-session/20260629-151216/artifacts/like-persistence-design.md
변경 내용
신규 엔드포인트: POST /game/{id}/like
GameController.toggleLike(id, request, session)(@Transactional).- 게이트 순서: CSRF(403) → 로그인(401) → 게임 존재(404). 기존 컨트롤러 메서드와 동일.
- 동작: 현재 좋아요 row 조회 후 분기 — 없으면 추가(
addGameLike+incrementLikeCount, liked=true), 있으면 취소(deleteByGameAndUser+decrementLikeCount, liked=false). 변경 직후 컬럼을 재조회해 진실값 응답. - 응답(200):
{ status:200, liked:<bool>, likeCount:<int> }. 실패: 403 / 401 / 404. - 계약 상세는 contracts 가 아닌 본 문서 + 설계 문서에 기록(별도 contract 기준 문서는 미생성 — 아래 concerns 참조).
매퍼
GameLikesMapper:findByGameAndUser(gameId, userKey)/deleteByGameAndUser(gameId, userKey)신규.addGameLike는 기존 재사용.user_key = String.valueOf(sessionUserId).GamesMapper:incrementLikeCount(id)(+1) /decrementLikeCount(id)(GREATEST(like_count - 1, 0)음수 방어) /getLikeCount(id)신규. 모두#{}바인딩,${}동적치환 없음.
상세 컨트롤러 / JSP
addGameModel이 현재 사용자의 기존 좋아요 여부를liked모델 속성으로 주입. 카탈로그 폴백 경로는liked=false.game-detail.jsp좋아요 버튼을localStorage토글 →fetch POST(window.BibimbapCsrf.headers)로 교체. 서버 응답likeCount/liked로textContent갱신. 초기 상태는 서버 주입${liked}사용.
explore 무변경
- explore 조회 쿼리(
GamesMapper의g.like_count AS likeCountSELECT)는 손대지 않음. 컬럼이 갱신되므로 자동 반영. (검증 AC-8:g.like_count AS likeCount패턴 7건 유지)
DB 스키마
db/schema.sql:game_likes에UNIQUE(game_id, user_key)제약 반영(비권위 복원본 동기).db/migrations/20260629-game-likes-unique.sql: 신규. 운영 DB 미적용 — 중복 row 점검 SELECT 선행 후 적용 대기. 적용 절차는 maintenance/post-deploy-verification-checklist.md 참조.
검증
- L1 PASS:
test-compile+GameLikeControllerTest7/7 +*ControllerTest회귀 219/219 GREEN (eclipse-temurin:21-jdk 컨테이너). - 실환경 스모크 PASS (2026-06-29, 세션
20260629-171042): docker compose dev(로컬 override) 에 배포 후 로그인 사용자가 상세에서 좋아요 클릭 → 탐색(explore) 목록 카운트 반영 확인(사용자 검증). 신 엔드포인트 라이브(POST /game/{id}/likeCSRF 미동반 → 403) 확인. - L2 (dev DB contract): skip — dev DB 미기동 + harness 미구축. like 매퍼는 INSERT/DELETE/UPDATE int 반환 위주라 camelCase Map alias 케이스폴딩 리스크 낮음.
- 검증 상세:
.atp/work-session/20260629-151216/artifacts/verification-result.md, 배포·스모크:.atp/work-session/20260629-171042/report.md
잔여 / 미수행 (needs_user_verification)
- UNIQUE 마이그레이션(
db/migrations/20260629-game-likes-unique.sql) 운영 적용 미수행 — 중복 점검 후 적용 게이트 대기. (로컬 dev DB 볼륨은 기존 init 이라schema.sql의 UNIQUE 변경도 미반영 — 앱레벨 멱등 토글로 기본 동작엔 무관.) - 상세 초기
aria-pressed상태(AC-7) 정밀 수동 확인은 선택(좋아요 반영 핵심 동작은 스모크로 확인됨). - L2 dev DB contract: PostgreSQL dev 기동 후 mapper SQL 실DB 동작 확인.
- 기존
like_count컬럼값과game_likesrow 수 초기 불일치는 보존(±1 상대 증감, 절대 재계산은 비목표). 선택적 정합 보정 쿼리는 설계 문서 롤아웃 §3 참조.
관련 문서
- 보안 체크리스트 B3(좋아요 서버 영속화 연결): security/security-remediation-checklist.md
- 운영 적용 절차: maintenance/post-deploy-verification-checklist.md
- 프로젝트 분석(미완성 기능 항목): analysis/2026-06-16-project-analysis.md
- 댓글 서버 영속화(선행 유사 작업): changes/2026-06-18-w3-2-comments-reviews.md