feat(game-rythm8beat): 8박자 게임 랭킹 라우트 구현#308
Conversation
Walkthrough리듬 게임 점수 관리를 위한 REST API 엔드포인트, 서비스 로직, JPA 엔티티, DTO, 리포지토리를 추가하고, 데이터베이스 스키마를 생성하며, CORS 원점을 확장했습니다. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Controller as Rythm8beatScoreController
participant Service as Rythm8beatScoreService
participant Repository as Rythm8beatScoreRepository
participant DB as Database
Client->>Controller: POST /api/v1/game/rythm8beat/scores<br/>(phoneNumber, nickname, score, stageReached)
Controller->>Service: submitScore(request)
Service->>Repository: findByPhoneNumber(phoneNumber)
Repository->>DB: SELECT * FROM game_scores<br/>WHERE phone_number = ?
DB-->>Repository: Rythm8beatScore or null
Repository-->>Service: Optional<Rythm8beatScore>
alt Score exists and newScore > currentScore
Service->>Service: updateIfHigherScore(nickname, newScore, stageReached)
Service->>Repository: save(entity)
Repository->>DB: UPDATE game_scores SET ...
else New score or lower
Service->>Repository: save(newEntity)
Repository->>DB: INSERT INTO game_scores ...
end
DB-->>Repository: Success
Repository-->>Service: Rythm8beatScore
Service-->>Controller: void
Controller-->>Client: 200 OK<br/>ApiResponse(SCORE_SUBMITTED)
sequenceDiagram
participant Client
participant Controller as Rythm8beatScoreController
participant Service as Rythm8beatScoreService
participant Repository as Rythm8beatScoreRepository
participant DB as Database
Client->>Controller: GET /api/v1/game/rythm8beat/ranking<br/>?phoneNumber=010XXXXXXXX
Controller->>Service: getRanking(phoneNumber)
Service->>Repository: findTop3ByOrderByScoreDescUpdatedAtAsc()
Repository->>DB: SELECT * FROM game_scores<br/>ORDER BY score DESC LIMIT 3
DB-->>Repository: List<Rythm8beatScore>
Service->>Repository: findByPhoneNumber(phoneNumber)
Repository->>DB: SELECT * FROM game_scores<br/>WHERE phone_number = ?
DB-->>Repository: Rythm8beatScore
Service->>Repository: countByScoreGreaterThan(userScore)
Repository->>DB: SELECT COUNT(*) FROM game_scores<br/>WHERE score > ?
DB-->>Repository: count
Repository-->>Service: results
Service->>Service: Map to RankItemResponse + compute userRank
Service-->>Controller: Rythm8beatRankingResponse(top3, userRank)
Controller-->>Client: 200 OK<br/>ApiResponse(RANKING_RETRIEVED, payload)
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @smpringles24. * #308 (comment) The following files were modified: * `src/main/java/inha/gdgoc/domain/game/controller/Rythm8beatScoreController.java` * `src/main/java/inha/gdgoc/domain/game/entity/Rythm8beatScore.java` * `src/main/java/inha/gdgoc/domain/game/repository/Rythm8beatScoreRepository.java` * `src/main/java/inha/gdgoc/domain/game/service/Rythm8beatScoreService.java` * `src/main/java/inha/gdgoc/global/security/SecurityConfig.java`
|
CI status |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
src/main/java/inha/gdgoc/domain/game/entity/Rythm8beatScore.java (1)
41-47: 점수가 동일할 때 닉네임 업데이트 여부를 확인하세요.현재
updateIfHigherScore메서드는newScore가 기존 점수보다 클 때만 업데이트합니다. 동일한 점수로 닉네임을 변경하려는 사용자의 요청은 무시됩니다.의도된 동작이라면 괜찮지만, 사용자가 닉네임 변경을 기대할 수 있으므로 비즈니스 요구사항을 확인해 주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/inha/gdgoc/domain/game/entity/Rythm8beatScore.java` around lines 41 - 47, The method updateIfHigherScore currently only updates nickname/score/stageReached when newScore is strictly greater than the existing score, so equal scores will not update the nickname; decide the intended behavior and either change the comparison in updateIfHigherScore from this.score < newScore to this.score <= newScore to allow updates when scores are equal, or leave the strict check but document/guard it with business logic (e.g., add a flag or a separate method updateIfGreaterOrEqualScore) and ensure callers use the correct method; refer to updateIfHigherScore and the fields nickname, score, stageReached when making the change.src/main/java/inha/gdgoc/domain/game/service/Rythm8beatScoreService.java (3)
59-61: 삭제 작업에 대한 감사 로깅 권장모든 점수 데이터를 삭제하는 관리자 작업입니다. 운영 추적을 위해 삭제 전 레코드 수와 작업 수행자를 로깅하는 것을 권장합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/inha/gdgoc/domain/game/service/Rythm8beatScoreService.java` around lines 59 - 61, The resetAll method performs a destructive admin action; update Rythm8beatScoreService.resetAll to log the number of records and the acting user before deletion: obtain the record count via rythm8beatScoreRepository.count(), determine the operator (either add a String operator parameter to resetAll or fetch the current principal via SecurityContextHolder.getContext().getAuthentication().getName()), call logger.info with a clear message including the count and operator, then call rythm8beatScoreRepository.deleteAll(); ensure any exceptions are logged so the audit trail captures success/failure.
21-36:stageReachednull 처리 로직 중복라인 27과 33에서
stageReached != null ? request.getStageReached() : 1로직이 동일하게 반복됩니다. 메서드 상단에서 한 번만 계산하여 중복을 제거하세요.♻️ 리팩토링 제안
public void submitScore(Rythm8beatScoreRequest request) { + int stageReached = request.getStageReached() != null ? request.getStageReached() : 1; rythm8beatScoreRepository.findByPhoneNumber(request.getPhoneNumber()) .ifPresentOrElse( entity -> entity.updateIfHigherScore( request.getNickname(), request.getScore(), - request.getStageReached() != null ? request.getStageReached() : 1 + stageReached ), () -> rythm8beatScoreRepository.save(Rythm8beatScore.builder() .phoneNumber(request.getPhoneNumber()) .nickname(request.getNickname()) .score(request.getScore()) - .stageReached(request.getStageReached() != null ? request.getStageReached() : 1) + .stageReached(stageReached) .build()) ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/inha/gdgoc/domain/game/service/Rythm8beatScoreService.java` around lines 21 - 36, The submitScore method in Rythm8beatScoreService repeats the null-check logic for stageReached; compute a local variable (e.g., int stage = request.getStageReached() != null ? request.getStageReached() : 1) at the top of submitScore and use that variable in both the entity.updateIfHigherScore(...) call and the Rythm8beatScore.builder().stageReached(...) call to remove duplication and make the logic clearer.
48-53: 동점자 순위 계산 불일치 가능성
findTop3ByOrderByScoreDescUpdatedAtAsc는 점수 동점 시updatedAt을 보조 정렬 기준으로 사용하지만,countByScoreGreaterThan은 점수만 고려합니다. 동점자가 있을 경우 top3에서의 순서와userRank값이 불일치할 수 있습니다.예: top3에서 2위인 사용자가
userRank로 조회하면 동점자와 같은 순위로 표시될 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/inha/gdgoc/domain/game/service/Rythm8beatScoreService.java` around lines 48 - 53, The rank calculation is inconsistent because findTop3ByOrderByScoreDescUpdatedAtAsc uses score then updatedAt as tie-breaker but the service uses countByScoreGreaterThan which ignores ties; update the logic in Rythm8beatScoreService where rythm8beatScoreRepository.findByPhoneNumber(...) is mapped to compute rank by counting records that are "better" according to the same sort: add/use a repository method that counts rows with score > gs.getScore() OR (score == gs.getScore() AND updatedAt < gs.getUpdatedAt()), call that (e.g., countByScoreGreaterThanOrScoreEqualsAndUpdatedAtBefore) to compute rank = count + 1, and return the same Rythm8beatRankItemResponse((int) rank, gs.getNickname(), gs.getScore()) so top3 ordering and userRank remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/inha/gdgoc/domain/game/controller/Rythm8beatScoreController.java`:
- Around line 36-41: The getRanking endpoint in Rythm8beatScoreController
currently accepts a sensitive phoneNumber via `@RequestParam`; change it to accept
a POST with a request body DTO (e.g., GetRankingRequest with an optional
phoneNumber or better a non-PII userId/UUID) and update the mapping from
`@GetMapping`("/ranking") to `@PostMapping`("/ranking"); adjust the controller
method signature and call to rythm8beatScoreService.getRanking(...) to accept
the DTO (or UUID) and update Rythm8beatRankingResponse usage accordingly so
phoneNumber is no longer exposed in query strings or logs.
In
`@src/main/java/inha/gdgoc/domain/game/dto/request/Rythm8beatScoreRequest.java`:
- Line 29: The field stageReached in Rythm8beatScoreRequest lacks a
minimum-value validation, so negative or zero values can be accepted; add a
constraint to enforce stageReached >= 1 (e.g., annotate the Integer stageReached
field with `@Min`(1) from javax.validation.constraints) and import the annotation,
and ensure request validation is triggered in the controller (e.g., `@Valid` on
the request parameter) so invalid values are rejected before persisting.
In
`@src/main/java/inha/gdgoc/domain/game/repository/Rythm8beatScoreRepository.java`:
- Around line 14-17: Top3 ranking uses updatedAt to break ties but
countByScoreGreaterThan only checks score, causing inconsistent ranks; update
countByScoreGreaterThan to accept an additional updatedAt parameter and change
its query to "SELECT COUNT(r) FROM Rythm8beatScore r WHERE r.score > :score OR
(r.score = :score AND r.updatedAt < :updatedAt)" (use `@Param`("score") and
`@Param`("updatedAt")) so ranking uses the same tie-breaker as
findTop3ByOrderByScoreDescUpdatedAtAsc(), and adjust any callers to pass the
contestant's updatedAt (LocalDateTime/Timestamp) when computing rank.
In `@src/main/resources/db/migration/V20260309__create_game_scores.sql`:
- Around line 1-10: The migration stores PII in plain text: update the CREATE
TABLE for game_scores to avoid storing raw phone_number by either replacing
phone_number VARCHAR(20) with a non-reversible hash column (e.g., phone_hash
VARCHAR(64) NOT NULL UNIQUE) or an encrypted column and add metadata for
retention (e.g., retention_policy or deleted_at) plus auditing fields; also
rename the table from game_scores to rythm8beat_scores to match the entity
Rythm8beatScore (update any references to game_scores in your
codebase/migrations) and ensure any application code that inserts/queries uses
the chosen hashing/encryption method and logs access for audit.
---
Nitpick comments:
In `@src/main/java/inha/gdgoc/domain/game/entity/Rythm8beatScore.java`:
- Around line 41-47: The method updateIfHigherScore currently only updates
nickname/score/stageReached when newScore is strictly greater than the existing
score, so equal scores will not update the nickname; decide the intended
behavior and either change the comparison in updateIfHigherScore from this.score
< newScore to this.score <= newScore to allow updates when scores are equal, or
leave the strict check but document/guard it with business logic (e.g., add a
flag or a separate method updateIfGreaterOrEqualScore) and ensure callers use
the correct method; refer to updateIfHigherScore and the fields nickname, score,
stageReached when making the change.
In `@src/main/java/inha/gdgoc/domain/game/service/Rythm8beatScoreService.java`:
- Around line 59-61: The resetAll method performs a destructive admin action;
update Rythm8beatScoreService.resetAll to log the number of records and the
acting user before deletion: obtain the record count via
rythm8beatScoreRepository.count(), determine the operator (either add a String
operator parameter to resetAll or fetch the current principal via
SecurityContextHolder.getContext().getAuthentication().getName()), call
logger.info with a clear message including the count and operator, then call
rythm8beatScoreRepository.deleteAll(); ensure any exceptions are logged so the
audit trail captures success/failure.
- Around line 21-36: The submitScore method in Rythm8beatScoreService repeats
the null-check logic for stageReached; compute a local variable (e.g., int stage
= request.getStageReached() != null ? request.getStageReached() : 1) at the top
of submitScore and use that variable in both the entity.updateIfHigherScore(...)
call and the Rythm8beatScore.builder().stageReached(...) call to remove
duplication and make the logic clearer.
- Around line 48-53: The rank calculation is inconsistent because
findTop3ByOrderByScoreDescUpdatedAtAsc uses score then updatedAt as tie-breaker
but the service uses countByScoreGreaterThan which ignores ties; update the
logic in Rythm8beatScoreService where
rythm8beatScoreRepository.findByPhoneNumber(...) is mapped to compute rank by
counting records that are "better" according to the same sort: add/use a
repository method that counts rows with score > gs.getScore() OR (score ==
gs.getScore() AND updatedAt < gs.getUpdatedAt()), call that (e.g.,
countByScoreGreaterThanOrScoreEqualsAndUpdatedAtBefore) to compute rank = count
+ 1, and return the same Rythm8beatRankItemResponse((int) rank,
gs.getNickname(), gs.getScore()) so top3 ordering and userRank remain
consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 74be5994-00d1-4026-adcc-d77ef8c4c210
📒 Files selected for processing (10)
src/main/java/inha/gdgoc/domain/game/controller/Rythm8beatScoreController.javasrc/main/java/inha/gdgoc/domain/game/controller/message/Rythm8beatScoreMessage.javasrc/main/java/inha/gdgoc/domain/game/dto/request/Rythm8beatScoreRequest.javasrc/main/java/inha/gdgoc/domain/game/dto/response/Rythm8beatRankItemResponse.javasrc/main/java/inha/gdgoc/domain/game/dto/response/Rythm8beatRankingResponse.javasrc/main/java/inha/gdgoc/domain/game/entity/Rythm8beatScore.javasrc/main/java/inha/gdgoc/domain/game/repository/Rythm8beatScoreRepository.javasrc/main/java/inha/gdgoc/domain/game/service/Rythm8beatScoreService.javasrc/main/java/inha/gdgoc/global/security/SecurityConfig.javasrc/main/resources/db/migration/V20260309__create_game_scores.sql
| @GetMapping("/ranking") | ||
| public ResponseEntity<ApiResponse<Rythm8beatRankingResponse, Void>> getRanking( | ||
| @RequestParam(required = false) String phoneNumber) { | ||
| Rythm8beatRankingResponse response = rythm8beatScoreService.getRanking(phoneNumber); | ||
| return ResponseEntity.ok(ApiResponse.ok(RANKING_RETRIEVED, response)); | ||
| } |
There was a problem hiding this comment.
개인정보(전화번호)가 쿼리 파라미터로 노출됨
phoneNumber는 개인식별정보(PII)입니다. 쿼리 파라미터는 서버 액세스 로그, 브라우저 히스토리, 중간 프록시 등에 기록될 수 있어 개인정보 노출 위험이 있습니다.
보안을 강화하려면 POST 요청의 body로 전달하거나, 별도의 사용자 식별자(UUID 등)를 사용하는 것을 고려하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/inha/gdgoc/domain/game/controller/Rythm8beatScoreController.java`
around lines 36 - 41, The getRanking endpoint in Rythm8beatScoreController
currently accepts a sensitive phoneNumber via `@RequestParam`; change it to accept
a POST with a request body DTO (e.g., GetRankingRequest with an optional
phoneNumber or better a non-PII userId/UUID) and update the mapping from
`@GetMapping`("/ranking") to `@PostMapping`("/ranking"); adjust the controller
method signature and call to rythm8beatScoreService.getRanking(...) to accept
the DTO (or UUID) and update Rythm8beatRankingResponse usage accordingly so
phoneNumber is no longer exposed in query strings or logs.
| @Max(10000) | ||
| private Integer score; | ||
|
|
||
| private Integer stageReached; |
There was a problem hiding this comment.
stageReached 필드에 최소값 검증이 없습니다.
stageReached가 null이 아닌 경우 음수나 0이 입력될 수 있습니다. DB 스키마에서 기본값이 1이고, 서비스 레이어에서 null일 때 1로 처리하지만, 명시적으로 0이나 음수가 전달되면 그대로 저장됩니다.
제안된 수정
+ `@Min`(1)
private Integer stageReached;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private Integer stageReached; | |
| `@Min`(1) | |
| private Integer stageReached; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/inha/gdgoc/domain/game/dto/request/Rythm8beatScoreRequest.java`
at line 29, The field stageReached in Rythm8beatScoreRequest lacks a
minimum-value validation, so negative or zero values can be accepted; add a
constraint to enforce stageReached >= 1 (e.g., annotate the Integer stageReached
field with `@Min`(1) from javax.validation.constraints) and import the annotation,
and ensure request validation is triggered in the controller (e.g., `@Valid` on
the request parameter) so invalid values are rejected before persisting.
| List<Rythm8beatScore> findTop3ByOrderByScoreDescUpdatedAtAsc(); | ||
|
|
||
| @Query("SELECT COUNT(r) FROM Rythm8beatScore r WHERE r.score > :score") | ||
| long countByScoreGreaterThan(@Param("score") int score); |
There was a problem hiding this comment.
Top3 랭킹과 사용자 랭킹 계산 로직 간 불일치가 있습니다.
findTop3ByOrderByScoreDescUpdatedAtAsc()는 동점 시 updatedAt 오름차순으로 순위를 결정하지만, countByScoreGreaterThan()은 점수만 비교합니다.
예시: 사용자 A(100점, 먼저 제출)와 B(100점, 나중 제출)가 있을 때:
- Top3에서는 A가 1위, B가 2위
- 하지만 B의
countByScoreGreaterThan(100) + 1 = 1이므로 B도 1위로 표시됨
동점자 처리를 일관되게 하려면 countByScoreGreaterThan 쿼리에 updatedAt 조건을 추가하거나, 별도의 순위 계산 로직이 필요합니다.
제안된 수정
- `@Query`("SELECT COUNT(r) FROM Rythm8beatScore r WHERE r.score > :score")
- long countByScoreGreaterThan(`@Param`("score") int score);
+ `@Query`("SELECT COUNT(r) FROM Rythm8beatScore r WHERE r.score > :score OR (r.score = :score AND r.updatedAt < :updatedAt)")
+ long countByScoreGreaterThanOrEqualWithEarlierUpdate(`@Param`("score") int score, `@Param`("updatedAt") java.time.Instant updatedAt);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/inha/gdgoc/domain/game/repository/Rythm8beatScoreRepository.java`
around lines 14 - 17, Top3 ranking uses updatedAt to break ties but
countByScoreGreaterThan only checks score, causing inconsistent ranks; update
countByScoreGreaterThan to accept an additional updatedAt parameter and change
its query to "SELECT COUNT(r) FROM Rythm8beatScore r WHERE r.score > :score OR
(r.score = :score AND r.updatedAt < :updatedAt)" (use `@Param`("score") and
`@Param`("updatedAt")) so ranking uses the same tie-breaker as
findTop3ByOrderByScoreDescUpdatedAtAsc(), and adjust any callers to pass the
contestant's updatedAt (LocalDateTime/Timestamp) when computing rank.
| CREATE TABLE IF NOT EXISTS game_scores ( | ||
| id BIGSERIAL PRIMARY KEY, | ||
| phone_number VARCHAR(20) NOT NULL UNIQUE, | ||
| nickname VARCHAR(20) NOT NULL, | ||
| score INT NOT NULL DEFAULT 0, | ||
| stage_reached INT NOT NULL DEFAULT 1, | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP | ||
| ); | ||
| CREATE INDEX IF NOT EXISTS idx_game_scores_score ON game_scores(score DESC); |
There was a problem hiding this comment.
전화번호(PII) 저장에 대한 개인정보 보호 고려가 필요합니다.
phone_number 컬럼에 개인 식별 정보(PII)가 평문으로 저장됩니다. GDPR/개인정보보호법 준수를 위해 다음 사항을 검토하세요:
- 데이터 보존 정책 수립 필요
- 필요시 해시 처리 또는 암호화 고려
- 접근 로깅 및 감사 추적
또한 테이블명이 game_scores로 범용적인데, 엔티티명은 Rythm8beatScore입니다. 향후 다른 게임 추가 시 혼란을 피하려면 테이블명을 rythm8beat_scores로 변경하는 것을 고려해 보세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/db/migration/V20260309__create_game_scores.sql` around
lines 1 - 10, The migration stores PII in plain text: update the CREATE TABLE
for game_scores to avoid storing raw phone_number by either replacing
phone_number VARCHAR(20) with a non-reversible hash column (e.g., phone_hash
VARCHAR(64) NOT NULL UNIQUE) or an encrypted column and add metadata for
retention (e.g., retention_policy or deleted_at) plus auditing fields; also
rename the table from game_scores to rythm8beat_scores to match the entity
Rythm8beatScore (update any references to game_scores in your
codebase/migrations) and ensure any application code that inserts/queries uses
the chosen hashing/encryption method and logs access for audit.
There was a problem hiding this comment.
Pull request overview
8박자(rythm8beat) 게임의 점수 저장 및 랭킹 조회를 위한 백엔드 라우트/도메인 구성을 추가하고, 배포된 FE 접근을 위해 CORS 허용 Origin을 확장합니다.
Changes:
- 8박자 게임 점수 테이블(
game_scores) Flyway 마이그레이션 추가 - 점수 제출/랭킹 조회/전체 초기화 API 및 관련 Service/Repository/Entity/DTO 추가
- CORS 허용 Origin에 GitHub Pages 도메인 추가
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/resources/db/migration/V20260309__create_game_scores.sql | 8박자 게임 점수 저장용 테이블/인덱스 생성 |
| src/main/java/inha/gdgoc/global/security/SecurityConfig.java | CORS 허용 Origin 목록 확장 |
| src/main/java/inha/gdgoc/domain/game/service/Rythm8beatScoreService.java | 점수 upsert 및 랭킹 계산 로직 추가 |
| src/main/java/inha/gdgoc/domain/game/repository/Rythm8beatScoreRepository.java | 점수 조회/랭킹 조회용 JPA 쿼리 추가 |
| src/main/java/inha/gdgoc/domain/game/entity/Rythm8beatScore.java | 점수 엔티티 추가 (BaseEntity 상속) |
| src/main/java/inha/gdgoc/domain/game/dto/response/Rythm8beatRankingResponse.java | 랭킹 응답 DTO 추가 |
| src/main/java/inha/gdgoc/domain/game/dto/response/Rythm8beatRankItemResponse.java | 랭킹 항목 DTO 추가 |
| src/main/java/inha/gdgoc/domain/game/dto/request/Rythm8beatScoreRequest.java | 점수 제출 요청 DTO 및 validation 추가 |
| src/main/java/inha/gdgoc/domain/game/controller/message/Rythm8beatScoreMessage.java | 8박자 점수 API 메시지 상수 추가 |
| src/main/java/inha/gdgoc/domain/game/controller/Rythm8beatScoreController.java | 점수 제출/랭킹 조회/전체 초기화 API 추가 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP | ||
| ); | ||
| CREATE INDEX IF NOT EXISTS idx_game_scores_score ON game_scores(score DESC); |
There was a problem hiding this comment.
랭킹 조회가 ORDER BY score DESC, updated_at ASC 기준을 사용하므로(Repository 메서드 기준) score DESC 단일 인덱스만으로는 정렬을 충분히 지원하지 못해 테이블이 커지면 정렬 비용이 커질 수 있습니다. (score DESC, updated_at ASC) 복합 인덱스로 바꾸거나 추가하는 것을 고려해주세요.
| CREATE INDEX IF NOT EXISTS idx_game_scores_score ON game_scores(score DESC); | |
| CREATE INDEX IF NOT EXISTS idx_game_scores_score ON game_scores(score DESC, updated_at ASC); |
| @Min(0) | ||
| @Max(10000) | ||
| private Integer score; | ||
|
|
There was a problem hiding this comment.
stageReached는 null일 때 1로 보정하고 DB 기본값도 1로 두는 것으로 보아 1 이상의 값만 의미가 있는 필드로 보입니다. 그런데 현재는 값이 들어오는 경우에 대한 검증이 없어(음수/0 등) 잘못된 데이터가 저장될 수 있으니 @Min(1) 같은 validation을 추가해주세요(필요하면 상한도 함께).
| @Min(1) |
| "https://*.gdgocinha.com", | ||
| "https://smpringles24.github.io" | ||
| )); |
There was a problem hiding this comment.
CorsConfiguration#setAllowedOrigins does not support wildcard subdomains (e.g., https://*.gdgocinha.com). With the current setup, requests from subdomains may still fail CORS validation. Use setAllowedOriginPatterns for wildcard patterns (and keep setAllowedOrigins for exact matches), or list the exact origins you intend to allow.
| "https://*.gdgocinha.com", | |
| "https://smpringles24.github.io" | |
| )); | |
| "https://smpringles24.github.io" | |
| )); | |
| config.setAllowedOriginPatterns(List.of( | |
| "https://*.gdgocinha.com" | |
| )); |
| if (phoneNumber != null && !phoneNumber.isBlank()) { | ||
| userRank = rythm8beatScoreRepository.findByPhoneNumber(phoneNumber) | ||
| .map(gs -> { | ||
| long rank = rythm8beatScoreRepository.countByScoreGreaterThan(gs.getScore()) + 1; |
There was a problem hiding this comment.
userRank 계산이 top3 정렬 기준(score DESC, updatedAt ASC)과 일치하지 않습니다. 동점(같은 score)일 때는 countByScoreGreaterThan(score) + 1이 항상 1로 계산되어, top3에서 2~3등인 유저도 1등으로 표시될 수 있습니다. 동점 처리까지 포함해 동일한 정렬 기준으로 rank를 계산하도록(예: score가 더 크거나, score가 같고 updatedAt이 더 빠른 레코드 수를 카운트) 쿼리/로직을 맞춰주세요.
| long rank = rythm8beatScoreRepository.countByScoreGreaterThan(gs.getScore()) + 1; | |
| long rank = rythm8beatScoreRepository | |
| .countByScoreGreaterThanOrScoreEqualsAndUpdatedAtBefore( | |
| gs.getScore(), | |
| gs.getUpdatedAt() | |
| ) + 1; |
| @DeleteMapping("/scores/all") | ||
| public ResponseEntity<ApiResponse<Void, Void>> resetAll() { | ||
| rythm8beatScoreService.resetAll(); | ||
| return ResponseEntity.ok(ApiResponse.ok(SCORES_RESET)); |
There was a problem hiding this comment.
/scores/all은 인증/권한 체크 없이 전체 점수를 삭제할 수 있어 누구나 랭킹 데이터를 초기화할 수 있습니다(현재 SecurityConfig에서 /api/v1/game/**가 permitAll). 운영 환경에서 치명적이므로 이 엔드포인트를 제거하거나, 관리자 전용으로 보호(@PreAuthorize 등)하고 별도의 보호수단(예: 내부망/환경별 비활성화)을 추가해주세요.
✨ 작업 내용
Summary by CodeRabbit
릴리스 노트