bibimbap/docs/changes/2026-06-29-game-like-server...

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/likedtextContent 갱신. 초기 상태는 서버 주입 ${liked} 사용.

explore 무변경

  • explore 조회 쿼리(GamesMapperg.like_count AS likeCount SELECT)는 손대지 않음. 컬럼이 갱신되므로 자동 반영. (검증 AC-8: g.like_count AS likeCount 패턴 7건 유지)

DB 스키마

  • db/schema.sql: game_likesUNIQUE(game_id, user_key) 제약 반영(비권위 복원본 동기).
  • db/migrations/20260629-game-likes-unique.sql: 신규. 운영 DB 미적용 — 중복 row 점검 SELECT 선행 후 적용 대기. 적용 절차는 maintenance/post-deploy-verification-checklist.md 참조.

검증

  • L1 PASS: test-compile + GameLikeControllerTest 7/7 + *ControllerTest 회귀 219/219 GREEN (eclipse-temurin:21-jdk 컨테이너).
  • 실환경 스모크 PASS (2026-06-29, 세션 20260629-171042): docker compose dev(로컬 override) 에 배포 후 로그인 사용자가 상세에서 좋아요 클릭 → 탐색(explore) 목록 카운트 반영 확인(사용자 검증). 신 엔드포인트 라이브(POST /game/{id}/like CSRF 미동반 → 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_likes row 수 초기 불일치는 보존(±1 상대 증감, 절대 재계산은 비목표). 선택적 정합 보정 쿼리는 설계 문서 롤아웃 §3 참조.

관련 문서