Skip to content

Conversation

@kaswhy
Copy link
Member

@kaswhy kaswhy commented Aug 25, 2025

📌 연관된 이슈

✨ 작업 내용

  • api 공통 응답을 이상하게 짜놔서 싹다 리팩토링
  • 근데 내부 구현도 이상하게 짜놔서 아직 리팩토링 안 끝남
  • 일단 큰 불만 껐고 예외처리도 리팩토링 해야 됨

💬 리뷰 요구사항(선택)

Summary by CodeRabbit

  • New Features
    • 응답 포맷을 code/message/data/meta로 표준화하고 성공 메시지 일괄 적용
    • 목록 응답에 페이지네이션 메타 포함
    • 이미지 업로드 성공 메시지 및 최대 10MB 제한 명시
  • Refactor
    • 모든 API 경로를 /api/v1로 버저닝
    • 일부 엔드포인트 반환 타입/시그니처 조정 및 인증 오류를 예외 흐름으로 통일
  • Chores
    • 서버 기준 시간을 UTC로 전환 및 JPA 감사 설정 도입
    • 생성/수정 시각을 timestamptz로 변환하는 DB 마이그레이션 추가

@kaswhy kaswhy self-assigned this Aug 25, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 25, 2025

Walkthrough

프로젝트 전반에 API 응답을 제네릭형식 ApiResponse<T, M>로 통일하고 컨트롤러 기본 경로를 버전화(/api/v1). JPA 감사 설정을 전용 설정으로 이동하고 BaseEntity를 Instant 기반으로 변경했으며 DB 타임존/마이그레이션이 추가됨. 예외 처리는 ErrorCode/BusinessException 기반으로 정리됨.

Changes

Cohort / File(s) Summary
API 응답 표준화 및 컨트롤러 경로 변경
src/main/java/inha/gdgoc/domain/auth/controller/AuthController.java, src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java, src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java, src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java, src/main/java/inha/gdgoc/domain/resource/controller/ResourceController.java, src/main/java/inha/gdgoc/domain/study/controller/StudyController.java, src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java, src/main/java/inha/gdgoc/domain/user/controller/UserController.java
모든 엔드포인트가 ApiResponse<T, M> 사용으로 변경되고 성공 응답은 ApiResponse.ok(message, data[, meta]) 형태로 통일. 여러 컨트롤러에 클래스 레벨 @RequestMapping("/api/v1"...) 추가로 경로 버저닝 적용. 메서드 시그니처 및 반환 제네릭 업데이트.
도메인별 메시지 상수 추가
src/main/java/inha/gdgoc/domain/auth/controller/message/AuthMessage.java, src/main/java/inha/gdgoc/domain/game/controller/message/GameQuestionMessage.java, src/main/java/inha/gdgoc/domain/game/controller/message/GameUserMessage.java, src/main/java/inha/gdgoc/domain/recruit/controller/message/RecruitMemberMessage.java, src/main/java/inha/gdgoc/domain/study/controller/message/StudyMessage.java, src/main/java/inha/gdgoc/domain/study/controller/message/StudyAttendeeMessage.java, src/main/java/inha/gdgoc/domain/resource/controller/message/ResourceMessage.java, src/main/java/inha/gdgoc/domain/user/controller/message/UserMessage.java
컨트롤러 성공 메시지용 상수 클래스를 도메인별로 추가.
글로벌 응답/에러 구조 재구성
src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java, src/main/java/inha/gdgoc/global/dto/response/ErrorMeta.java, src/main/java/inha/gdgoc/global/dto/response/ErrorResponse.java, src/main/java/inha/gdgoc/global/error/ErrorCode.java, src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java, 삭제: src/main/java/inha/gdgoc/global/common/ApiResponse.java
기존 단일 제네릭 global.common.ApiResponse 제거 후 record ApiResponse<T,M> 추가, ErrorMeta 도입, ErrorResponse 패키지 이동, ErrorCode에 상태·메시지 접근자 추가. 핸들러/시큐리티 import 경로 조정.
엔티티 감사·타임존 관련 구조 변경
src/main/java/inha/gdgoc/global/entity/BaseEntity.java, src/main/java/inha/gdgoc/global/config/jpa/JpaAuditingConfig.java, src/main/java/inha/gdgoc/GdgocApplication.java, 다수 도메인 엔티티/DTO import 변경 (.../domain/*/entity/*.java, .../domain/*/dto/*)
BaseEntity 패키지 이동 및 필드 타입 LocalDateTime → Instant 변경, @PrePersist 제거하고 JPA 감사 애노테이션 사용. JPA 감사 설정을 별도 config(JpaAuditingConfig)로 분리. 관련 엔티티/DTO의 BaseEntity import 경로 일괄 수정.
환경 설정 및 DB 마이그레이션
src/main/resources/application-dev.yml, src/main/resources/application-local.yml, src/main/resources/application-prod.yml, src/main/resources/db/migration/V20250825__convert_created_updated_to_timestamptz.sql
Hibernate / JDBC 타임존을 UTC로 변경(환경별 yml 업데이트). Flyway local 설정 변경. 생성/수정일 컬럼을 timestamptz(6)로 변환하는 마이그레이션 스크립트 추가(변환 시 기존 값은 Asia/Seoul 기준 사용).
오류·예외 및 Auth 확장
src/main/java/inha/gdgoc/domain/auth/exception/AuthErrorCode.java, src/main/java/inha/gdgoc/domain/user/service/UserService.java, src/main/java/inha/gdgoc/global/error/NotFoundException.java
AuthErrorCode에 UNAUTHORIZED_USER, USER_NOT_FOUND 추가. NotFoundException 패키지 이동. UserService의 예외 import 경로 조정.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Controller
  participant Service
  participant GlobalHandler as GlobalExceptionHandler

  rect rgba(230,245,255,0.5)
  Client->>Controller: HTTP 요청 (/api/v1/...)
  Controller->>Service: 비즈니스 호출
  Service-->>Controller: 결과 반환
  Controller-->>Client: ApiResponse.ok(message, data[, meta])
  end

  alt 비즈니스 예외 발생
    Service-->>Controller: throws BusinessException(ErrorCode)
    Controller-->>GlobalHandler: 예외 전파
    GlobalHandler-->>Client: ApiResponse.error(code, message, ErrorMeta)
  end
Loading
sequenceDiagram
  autonumber
  participant App as SpringApp
  participant JpaConfig as JpaAuditingConfig
  participant DB as Database

  App->>JpaConfig: @EnableJpaAuditing 초기화
  JpaConfig-->>App: DateTimeProvider(UTC Instant)
  App->>DB: 엔티티 저장/수정
  DB-->>App: created_at / updated_at (timestamptz UTC)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
API 공통 응답 수정 (#195)
예외 처리 핸들링 리팩토링 (#195)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Hibernate/JDBC 타임존을 UTC로 변경 (src/main/resources/application-*.yml) 이 이슈(#195)는 API 응답/예외 리팩터링이 목표이며 환경 타임존 변경은 요구사항에 명시되어 있지 않음.
BaseEntity Instant 전환 및 패키지 이동, @PrePersist 제거 (src/main/java/inha/gdgoc/global/entity/BaseEntity.java) API 응답/예외 리팩터링 범위를 벗어나는 데이터 모델/감사 구조 변경임.
생성/수정일 컬럼을 timestamptz로 변환하는 마이그레이션 추가 (src/main/resources/db/migration/V20250825__convert_created_updated_to_timestamptz.sql) DB 스키마 마이그레이션은 이슈 목표(응답/예외)와 직접적 관련이 없음.

Possibly related PRs

  • [FEAT] #178: CI 추가 #180 — User 엔티티 변경과 관련: 동일한 User 파일의 BaseEntity/맵핑 변경과 연관 가능성 있음.

Poem

(_/) 깡총, 응답이 두 칸으로 반짝!
(•ᴥ•) 메시지는 깔끔, 데이터는 정돈됐지요.
(🍃) 예외는 코드로 다스리고, 시간은 UTC로 흘러요.
(^• ω •^) 마이그레이션도 척척, 배포 준비 끝!
─=≡Σ(•̀ω•́ )⁾⁾ 리뷰하자, 당근 한 움큼!

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/issue-195

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link

github-actions bot commented Aug 25, 2025

CI status
✅ Assemble 성공
✅ Test 성공

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
src/main/resources/application-local.yml (1)

54-54: 로거 카테고리 오타: org.hibername.typeorg.hibernate.type

현재 설정은 적용되지 않습니다. 오타를 수정해 주세요. 필요 시 바인딩 값 디버깅에는 org.hibernate.type.descriptor.jdbc 또는 org.hibernate.SQL 조합을 사용하세요.

적용 diff:

-    org.hibername.type: trace
+    org.hibernate.type: trace
src/main/java/inha/gdgoc/domain/auth/entity/RefreshToken.java (2)

13-13: 만료 시각 타입을 Instant로 통일

전역적으로 BaseEntityInstant(UTC)로 변경된 만큼 토큰 만료도 절대 시점 표현(Instant)이 적합합니다. LocalDateTime은 타임존 해석 리스크가 있습니다.

-import java.time.LocalDateTime;
+import java.time.Instant;
@@
-    private LocalDateTime expiryDate;
+    private Instant expiryDate;
@@
-    public void update(String token, LocalDateTime expiryDate) {
+    public void update(String token, Instant expiryDate) {

30-35: 보안 이슈: 리프레시 토큰 평문 저장은 위험합니다 (해시 저장 전환 권장)

DB 유출 시 계정 탈취로 직결됩니다. 해시(SHA-256 이상)로 저장하고, 비교는 상수 시간 비교로 수행하세요. 마이그레이션은 신규 발급 시 해시 컬럼 채우기 → 구 컬럼 삭제 순으로 점진 적용하는 방식을 권장합니다.

권장 변경 방향(요약):

  • 엔티티: tokentokenHash (unique)로 교체
  • 서비스: 발급 시 tokenRaw는 클라이언트에만 반환, DB에는 hash(tokenRaw + salt/pepper) 저장
  • 조회: 입력받은 tokenRaw를 동일 방식으로 해싱해 tokenHash로 조회
  • 마이그레이션: token_hash 컬럼 추가 → 신규 로그인/갱신 시 해시 저장 → 충분한 그레이스 기간 후 token 컬럼 제거

엔티티 예시:

-    @Column(nullable = false, unique = true, columnDefinition = "TEXT")
-    private String token;
+    @Column(name = "token_hash", nullable = false, unique = true, length = 64)
+    private String tokenHash;

서비스 계층 보일러플레이트 구현이 필요하시면 제공 가능합니다.

src/main/java/inha/gdgoc/domain/recruit/dto/response/SpecifiedMemberResponse.java (2)

5-17: 직렬화 가능성 이슈: 접근자 부재 + DTO가 BaseEntity 상속 (레이어 결합)

  • 현재 클래스는 private 필드만 존재하고 접근자(getter)가 없어 Jackson 설정에 따라 응답이 빈 객체로 직렬화될 수 있습니다.
  • DTO가 global.entity.BaseEntity(JPA 감사/엔티티 전제)를 상속하는 것은 표현 계층과 영속 계층의 결합을 유발합니다. 응답에 감사 필드가 필요하다면 DTO에 필요한 필드만 명시적으로 포함하는 편이 안전합니다.

권장 수정안(Record, Java 17 가정): 필드/접근자/불변성을 한 번에 해결하고, BaseEntity 상속을 제거합니다. 또한 boolean 필드명을 ‘paid’로 정정합니다(아래 코멘트 참조).

-package inha.gdgoc.domain.recruit.dto.response;
-
-import inha.gdgoc.global.entity.BaseEntity;
-
-public class SpecifiedMemberResponse extends BaseEntity {
-    private String name;
-    private String major;
-    private String studentId;
-    private boolean isPayed;
-
-    public SpecifiedMemberResponse(String name, String major, String studentId, boolean isPayed) {
-        this.name = name;
-        this.major = major;
-        this.studentId = studentId;
-        this.isPayed = isPayed;
-    }
-}
+package inha.gdgoc.domain.recruit.dto.response;
+
+public record SpecifiedMemberResponse(
+    String name,
+    String major,
+    String studentId,
+    boolean paid
+) {}

Java 17 사용이 어렵다면 대안(롬복):

import lombok.Getter;
import lombok.AllArgsConstructor;

@Getter
@AllArgsConstructor
public class SpecifiedMemberResponse {
  private final String name;
  private final String major;
  private final String studentId;
  private final boolean paid;
}

9-16: 불리언 필드 오탈자 “isPayed” → “paid” (호환성 및 DB 스키마 영향 주의)

현재 코드베이스 전반에서 isPayed라는 비표준 표기(과거형 동사 pay의 잘못된 표기)가 사용되고 있습니다. 다음 위치에서 검출되었습니다:

  • RecruitMember 엔티티 (src/main/java/inha/gdgoc/domain/recruit/entity/RecruitMember.java:73–76)
    • @Column(name = "is_payed", nullable = false)
    • private Boolean isPayed;
  • DTO
    • SpecifiedMemberResponse (src/main/java/inha/gdgoc/domain/recruit/dto/response/SpecifiedMemberResponse.java:9–15)
      • private boolean isPayed;
    • RecruitMemberRequest (src/main/java/inha/gdgoc/domain/recruit/dto/request/RecruitMemberRequest.java:28,43)
      • private Boolean isPayed;
      • .isPayed(isPayed) 빌더 호출
  • 테스트 (src/test/java/inha/gdgoc/domain/recruit/service/RecruitMemberServiceTest.java:40,68)
    • .isPayed(true)

payed는 잘못된 표기이므로, 필드명을 paid로 수정할 것을 권장합니다. 다만, 다음 사항을 반드시 검토 및 계획하신 후 진행하세요:

  • DB 컬럼명 변경(is_payedis_paid) 시점에 따른 마이그레이션 및 롤백 방안
  • 외부 공개된 API 응답 스키마 영향도(클라이언트 버전 호환성)
  • 기존 코드(엔티티, DTO, 서비스, 테스트) 전역 리팩토링 범위

아래 예시는 SpecifiedMemberResponse에만 적용한 diff이며, 실제 적용 시 전체 위치를 동기화해야 합니다:

-    private boolean isPayed;
+    private boolean paid;

-    public SpecifiedMemberResponse(String name, String major, String studentId, boolean isPayed) {
+    public SpecifiedMemberResponse(String name, String major, String studentId, boolean paid) {
         this.name = name;
         this.major = major;
         this.studentId = studentId;
-        this.isPayed = isPayed;
+        this.paid = paid;
    }

변경 범위가 넓어 API/DB 스키마, 클라이언트 호환성에 중대한 영향을 줄 수 있으므로,
임시 병행 노출(deprecated 애너테이션 → 제거 일정) 등 마이그레이션 전략 수립 후 진행 부탁드립니다.

src/main/java/inha/gdgoc/domain/user/service/UserService.java (1)

55-57: 공통 예외 흐름 통일: BusinessException(+GlobalErrorCode)로 전환 필요

아래 스크립트 결과, 여전히 레포 전역에 IllegalArgumentExceptionNotFoundException 사용이 다수 확인되었습니다. 공통 에러/응답 정책에 따라 각 위치를 BusinessException과 적절한 GlobalErrorCode로 교체해야 합니다.

– src/main/java/inha/gdgoc/global/security/TokenAuthenticationFilter.java:89
throw new IllegalArgumentException("토큰에 유효하지 않은 제어 문자가 포함되어 있습니다.");
– src/main/java/inha/gdgoc/domain/user/service/UserService.java:56
throw new IllegalArgumentException("해당 정보를 가진 사용자를 찾을 수 없습니다.");
– src/main/java/inha/gdgoc/domain/user/entity/SocialUrls.java:21, 26
throw new IllegalArgumentException("URL cannot be empty");
throw new IllegalArgumentException("Invalid URL format");
– src/main/java/inha/gdgoc/domain/recruit/enums/EnrolledClassification.java:25
throw new IllegalArgumentException("Invalid enrolledClassification value: " + status);
– src/main/java/inha/gdgoc/domain/recruit/enums/Gender.java:22
throw new IllegalArgumentException("Invalid gender value: " + type);
– src/main/java/inha/gdgoc/domain/recruit/enums/InputType.java:30
throw new IllegalArgumentException("Invalid question value: " + question);
– src/main/java/inha/gdgoc/domain/auth/entity/RefreshToken.java:41
throw new IllegalArgumentException("토큰 갱신 정보가 유효하지 않습니다.");
– src/main/java/inha/gdgoc/domain/study/service/StudyAttendeeService.java:
• 75, 107, 109, 145, 169 줄의 .orElseThrow(() -> new IllegalArgumentException(…))
– src/main/java/inha/gdgoc/domain/auth/service/AuthService.java:164
throw new IllegalArgumentException("user Id is null");
– src/main/java/inha/gdgoc/domain/user/service/UserService.java:45
.orElseThrow(() -> new NotFoundException("User not found user id: " + userId));

특히 UserService.findId 부분은 아래와 같이 수정 예시를 참고해주세요:

-import inha.gdgoc.global.error.BusinessException;
-import inha.gdgoc.global.error.GlobalErrorCode;
+import inha.gdgoc.global.error.BusinessException;
+import inha.gdgoc.global.error.GlobalErrorCode;

    // ...

-        if (user.isEmpty()) {
-            throw new IllegalArgumentException("해당 정보를 가진 사용자를 찾을 수 없습니다.");
-        }
+        if (user.isEmpty()) {
+            throw new BusinessException(GlobalErrorCode.RESOURCE_NOT_FOUND);
+        }

각 도메인별로 상황에 맞는 GlobalErrorCode를 매핑하여 일괄 교체하시고, 아래 스크립트를 다시 실행해 누락된 raw 예외가 없는지 검증을 마무리해주세요:

rg -n --hidden -S 'new\s+(IllegalArgumentException|NotFoundException)\(' -C2
rg -n --hidden -S 'orElseThrow\(\s*\(\)\s*->\s*new\s+NotFoundException\(' -C2
src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java (1)

62-66: 내부 예외 메시지가 그대로 노출됩니다 → 민감 정보 노출 위험

Exception 캐치-all 핸들러가 ex.getMessage()를 그대로 응답 본문에 담습니다. 운영 환경에서는 스택/메시지를 로그로 남기고, 클라이언트에는 표준화된 에러 코드/메시지만 내려야 합니다. 아래와 같이 수정해 주세요.

 package inha.gdgoc.global.error;

 import com.fasterxml.jackson.core.JsonParseException;
 import inha.gdgoc.global.dto.response.ErrorResponse;
 import io.jsonwebtoken.JwtException;
 import io.jsonwebtoken.MalformedJwtException;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
 import org.springframework.web.servlet.resource.NoResourceFoundException;
+import lombok.extern.slf4j.Slf4j;

-@RestControllerAdvice
+@RestControllerAdvice
+@Slf4j
 public class GlobalExceptionHandler {

 ...
     @ExceptionHandler(Exception.class)
     public ResponseEntity<ErrorResponse> handleUnhandledException(Exception ex) {
-        ErrorResponse response = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
-        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
+        // 내부 사유는 서버 로그로만 남기고, 표준화된 응답을 반환
+        log.error("Unhandled exception", ex);
+        ErrorResponse response = new ErrorResponse(GlobalErrorCode.INTERNAL_SERVER_ERROR);
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
     }
 }
src/main/java/inha/gdgoc/domain/auth/controller/AuthController.java (1)

69-86: 리프레시 토큰 값을 로그로 남김 → 심각한 보안 이슈 (즉시 제거 필요)

Line 78에서 리프레시 토큰 원문을 로그에 남깁니다. 토큰 탈취 및 계정 장악으로 직결될 수 있으므로 반드시 제거/마스킹하세요.

- log.info("리프레시 토큰 값: {}", refreshToken);
+ // 민감정보 로그 금지: refresh token 원문 출력 금지
+ log.debug("리프레시 토큰 요청 처리 시작"); // 필요 시 존재 여부만 로깅
🧹 Nitpick comments (67)
src/main/resources/application-local.yml (2)

30-30: local에서 flyway.baseline-on-migrate: false로 바뀐 영향 확인 필요

기존 로컬 DB에 스키마가 이미 존재하는 경우 baseline이 없으면 마이그레이션이 실패할 수 있습니다. 로컬 개발 편의 목적이라면, 초기 진입 장벽을 낮추기 위해 local만 true를 유지하거나, 최초 베이스라인 마이그레이션 스크립트가 있는지 확인 부탁드립니다.


25-25: Hibernate 설정 키 경로 통일 권장

로컬 프로필과 Dev/Prod 프로필 간에 time_zone 설정 키 경로가 불일치합니다. Hibernate 표준 키인 hibernate.jdbc.time_zone을 사용하도록 프로필 간 키를 통일해주세요.

• application-local.yml

spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 100
        jdbc:
          time_zone: UTC   # hibernate.jdbc.time_zone

• application-dev.yml, application-prod.yml

spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 100
        time_zone: UTC   # hibernate.time_zone (비표준)

제안: Dev/Prod 프로필도 로컬과 동일하게, jdbc 하위에 time_zone을 배치하도록 변경하세요.

 spring:
   jpa:
     properties:
       hibernate:
         default_batch_fetch_size: 100
-        time_zone: UTC
+        jdbc:
+          time_zone: UTC
src/main/java/inha/gdgoc/global/dto/response/ErrorResponse.java (1)

8-23: 에러 응답 스펙의 확장성(코드/세부 필드/널 처리) 고려

현재 status, message만 제공됩니다. 전역 ApiResponse와의 형태 일관성을 위해 다음을 검토해 보세요.

  • 에러 식별자(예: code = ErrorCode.name() 또는 별도 코드 값)
  • 세부 원인 목록(필드 에러 등)을 담을 errors(옵셔널)
  • 널 필드 미출력 설정

필요 시 다음과 같이 널 필드 미출력을 추가하면 역호환에 안전합니다.

적용 diff:

 package inha.gdgoc.global.dto.response;

+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@
-@Getter
+@Getter
+@JsonInclude(Include.NON_NULL)
 public class ErrorResponse {

원하시면 ErrorCode 기반의 code 필드 추가와 정적 팩토리 메서드도 제안드리겠습니다.

src/main/java/inha/gdgoc/global/error/NotFoundException.java (2)

6-11: @ResponseStatus와 전역 예외 처리의 역할 중복 여부 확인

PR 요지상 전역 예외 처리(예: GlobalExceptionHandler)를 통한 공통 응답 표준화가 목표라면, 개별 예외의 @ResponseStatus는 상태 코드/바디 결정이 중복될 수 있습니다. 한쪽으로 일원화하는 방식을 권장합니다.

원하시면 전역 핸들러에 매핑을 추가하고 본 예외의 어노테이션을 제거하는 간단한 정리를 위한 diff를 제안드릴 수 있습니다.


7-11: 부가 생성자/직렬화 ID는 선택적으로 고려

필수는 아니지만, 추적 편의를 위해 (원인 예외를 포함하는) 추가 생성자와 serialVersionUID를 추가해 두면 운영 이슈 분석에 유용합니다.

예:

public NotFoundException(String message, Throwable cause) { super(message, cause); }
private static final long serialVersionUID = 1L;
src/main/java/inha/gdgoc/global/security/SecurityConfig.java (5)

38-41: 컨트롤러 버저닝(/api/v1)이 적용되었다면 화이트리스트 경로도 함께 업데이트 필요

PR 요약에서 전역 경로가 /api/v1로 통일되었다고 되어 있습니다. 현재 화이트리스트는 /auth/**, /game/** 등 구버전 패턴만 허용하고 있어, 실제 컨트롤러가 /api/v1/...로 이전된 경우 인증 없이 접근해야 할 엔드포인트가 차단될 수 있습니다. 신규/구버전 패턴을 모두 허용하도록 아래처럼 갱신을 권장합니다.

-            .authorizeHttpRequests(auth -> auth
-                .requestMatchers("/swagger-ui/**", "/v3/api-docs/**",
-                    "/swagger-ui.html", "/auth/**", "/test/**", "/game/**", "/apply/**",
-                    "/check/**").permitAll()
-                .anyRequest().authenticated()
-            )
+            .authorizeHttpRequests(auth -> auth
+                .requestMatchers(
+                    "/swagger-ui/**",
+                    "/v3/api-docs/**",
+                    "/swagger-ui.html",
+                    // v1 버전 경로
+                    "/api/v1/auth/**",
+                    "/api/v1/test/**",
+                    "/api/v1/game/**",
+                    "/api/v1/apply/**",
+                    "/api/v1/check/**",
+                    // 레거시 호환(필요 시 유지/제거)
+                    "/auth/**",
+                    "/test/**",
+                    "/game/**",
+                    "/apply/**",
+                    "/check/**"
+                ).permitAll()
+                .anyRequest().authenticated()
+            )

47-49: INVALID_JWT는 403보다 401이 더 적절하며, WWW-Authenticate 헤더 추가 권장

미인증/유효하지 않은 토큰의 표준 응답은 401 Unauthorized입니다. 또한 Bearer 스킴을 사용하는 클라이언트를 위해 WWW-Authenticate 헤더를 명시하면 상호운용성이 좋아집니다.

-                    response.setStatus(HttpStatus.FORBIDDEN.value());
-                    response.setContentType("application/json; charset=UTF-8");
+                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
+                    response.setHeader("WWW-Authenticate",
+                        "Bearer error=\"invalid_token\", error_description=\"Invalid or missing JWT\"");
+                    response.setContentType("application/json; charset=UTF-8");

28-28: ObjectMapper 매번 생성 대신 Bean 주입으로 교체

보안 예외 진입 시마다 new ObjectMapper()를 생성하면 설정 일관성(모듈/직렬화 전략)과 성능 측면에서 불리합니다. 스프링이 관리하는 ObjectMapper Bean을 주입해 재사용하세요.

@@
-    private final TokenAuthenticationFilter tokenAuthenticationFilter;
+    private final TokenAuthenticationFilter tokenAuthenticationFilter;
+    private final ObjectMapper objectMapper;
@@
-                    ObjectMapper objectMapper = new ObjectMapper();
-                    response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
+                    response.getWriter().write(objectMapper.writeValueAsString(errorResponse));

Also applies to: 55-56


50-56: 전역 응답 스킴과의 일관성: ErrorResponse 직접 작성 대신 ApiResponse.error 사용 고려

본 PR의 목표가 “API 공통 응답 통일”이라면, 시큐리티 엔트리포인트에서도 ApiResponse<T, M>(예: ApiResponse<Void, ErrorMeta>)로 직렬화하는 편이 일관됩니다. ErrorMeta에 요청 경로/타임스탬프를 담아 내려주면 디버깅에도 유용합니다.

-                    // ErrorResponse 생성
-                    ErrorResponse errorResponse = new ErrorResponse(
-                        GlobalErrorCode.INVALID_JWT_REQUEST);
-
-                    // JSON 직렬화 후 응답에 쓰기
-                    ObjectMapper objectMapper = new ObjectMapper();
-                    response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
+                    // ApiResponse.error + ErrorMeta 사용 (공통 응답 스킴 일원화)
+                    var meta = new inha.gdgoc.global.dto.response.ErrorMeta(
+                        request.getRequestURI(),
+                        System.currentTimeMillis()
+                    );
+                    var body = inha.gdgoc.global.dto.response.ApiResponse
+                        .error(GlobalErrorCode.INVALID_JWT_REQUEST, meta);
+                    response.getWriter().write(objectMapper.writeValueAsString(body));

필요 시 다음 import를 추가하세요:

  • inha.gdgoc.global.dto.response.ApiResponse
  • inha.gdgoc.global.dto.response.ErrorMeta

65-82: CORS 허용 도메인 관리 방식 개선 제안

  • 운영/스테이징/프론트 배포 도메인이 늘거나 변경될 때마다 재배포가 필요합니다. 프로퍼티(예: application-*.yml 리스트) 기반 주입 또는 allowedOriginPatterns("https://*.gdgocinha.com")로 패턴 사용을 고려하세요(자격 증명 사용 시 정밀 제어 필요).
  • 현재 setAllowCredentials(true)를 쓰고 있으므로 와일드카드(*)는 사용하면 안 됩니다. 패턴 사용 시에도 실제 허용 목록을 환경별로 명확히 관리해 주세요.
src/main/java/inha/gdgoc/global/dto/response/ErrorMeta.java (1)

3-6: timestamp를 long(ms) 대신 Instant로 노출하여 일관된 시간 표현 유지

본 PR에서 엔티티 감사 필드가 Instant로 통일되었다면, 에러 메타도 Instant를 사용하여 표현 일관성을 맞추는 편이 낫습니다(클라이언트가 ISO-8601로 직렬화/역직렬화하기 용이).

+import java.time.Instant;
 
 public record ErrorMeta(
     String path,
-    long timestamp
+    Instant timestamp
 ) {
 
 }

추가로, 생성 지점을 표준화하려면 정적 팩토리를 제공하는 것도 방법입니다(참고 코드):

public static ErrorMeta from(HttpServletRequest req, Instant now) {
    return new ErrorMeta(req.getRequestURI(), now);
}
src/main/java/inha/gdgoc/domain/auth/dto/request/FindIdRequest.java (1)

3-3: DTO가 BaseEntity를 상속하는 구조는 계층 침투(Leaky Abstraction)입니다

요청 DTO가 JPA 베이스 엔티티를 상속하면 영속성/감사 속성이 API 계층으로 새어 나옵니다. 검증 애너테이션을 갖춘 순수 DTO로 전환을 권장합니다. 리팩터링 범위를 넘어선다면 추후 이슈로 분리해도 됩니다.

-import inha.gdgoc.global.entity.BaseEntity;
+// import 제거
 
@@
-public class FindIdRequest extends BaseEntity {
+public class FindIdRequest {
-    private String name;
-    private String major;
-    private String phoneNumber;
+    @jakarta.validation.constraints.NotBlank
+    private String name;
+    @jakarta.validation.constraints.NotBlank
+    private String major;
+    @jakarta.validation.constraints.Pattern(regexp = "^01[0-9]-?\\d{3,4}-?\\d{4}$")
+    private String phoneNumber;
 }

검증을 활성화하려면 컨트롤러 핸들러 파라미터에 @Valid 또는 @Validated를 적용했는지 확인해 주세요.

Also applies to: 9-13

src/main/java/inha/gdgoc/domain/resource/controller/message/ResourceMessage.java (1)

3-5: 유틸리티 클래스 불필요 인스턴스화 방지 및 메시지 국제화 고려

  • 인스턴스화 방지를 위해 final + private 생성자를 추가하세요.
  • S3와 같은 구현 상세는 메시지에서 배제하거나, 국제화(MessageSource)를 도입해 외부화하면 변경/다국어 대응이 용이합니다.
-public class ResourceMessage {
-    public static final String IMAGE_SAVE_SUCCESS = "성공적으로 이미지를 S3에 저장했습니다.";
-}
+public final class ResourceMessage {
+    public static final String IMAGE_SAVE_SUCCESS = "성공적으로 이미지를 저장했습니다.";
+
+    private ResourceMessage() {
+        throw new AssertionError("No instances");
+    }
+}
src/main/java/inha/gdgoc/global/config/jpa/JpaAuditingConfig.java (1)

14-17: 테스트 용이성과 UTC 보장을 위해 Clock 주입 사용

Instant.now() 직접 호출 대신 Clock.systemUTC()를 Bean으로 주입하면, 테스트에서 고정 시계를 주입할 수 있고 UTC도 명시적으로 강제할 수 있습니다.

+import java.time.Clock;
 import java.time.Instant;
 import java.util.Optional;
@@
-    @Bean
-    public DateTimeProvider utcDateTimeProvider() {
-        return () -> Optional.of(Instant.now());
-    }
+    @Bean
+    public Clock utcClock() {
+        return Clock.systemUTC();
+    }
+
+    @Bean
+    public DateTimeProvider utcDateTimeProvider(Clock utcClock) {
+        return () -> Optional.of(Instant.now(utcClock));
+    }

검증 체크(선택):

  • @CreatedDate/@LastModifiedDate 필드 타입이 Instant로 통일되었는지 확인.
  • DB/JDBC 드라이버 타임존이 UTC로 설정되어 있는지 환경별 설정(application-*.yml)도 재확인.
src/main/java/inha/gdgoc/domain/game/controller/message/GameQuestionMessage.java (2)

3-6: 상수 전용 클래스는 final + private 생성자로 봉인하세요

유틸리티 상수만 담는 클래스는 인스턴스화 방지를 위해 클래스를 final로 하고 private 생성자를 두는 것이 정석입니다.

-public class GameQuestionMessage {
+public final class GameQuestionMessage {
     public static final String GAME_QUESTION_SAVE_SUCCESS = "성공적으로 게임 질문을 저장했습니다.";
     public static final String GAME_QUESTION_RETRIEVED_SUCCESS = "성공적으로 게임 질문 리스트를 조회했습니다.";
-}
+    private GameQuestionMessage() {
+        throw new AssertionError("No instances.");
+    }
+}

4-5: 메시지 문자열 하드코딩 대신 코드/키 기반 관리 고려

다국어(i18n)나 메시지 일관성을 위해 enum(예: SuccessCode) + MessageSource 기반으로 관리하면 확장성이 좋아집니다. 공통 응답 리팩토링 취지와도 맞습니다. 컨트롤러에서는 메시지 문자열 대신 성공코드만 전달하고, 실제 문자열은 메시지 소스에서 해석하도록 구조화하는 것을 제안합니다.

원하시면 SuccessCode 설계 초안과 샘플 messages_ko.properties를 드리겠습니다.

src/main/java/inha/gdgoc/global/entity/BaseEntity.java (3)

15-24: 감사 미동작 시를 위한 안전망(prePersist) 선택적 추가 제안

운영/테스트 환경에서 Auditing 미설정으로 인한 삽입 실패를 방지하려면, 값이 비어있을 때만 기본값을 채우는 @PrePersist fallback을 추가하는 방법이 있습니다. 감사가 정상 동작하면 이 경로는 실행되지 않습니다.

 import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+import jakarta.persistence.PrePersist;

@@
 public abstract class BaseEntity {
@@
     @Column(name = "updated_at", nullable = false)
     private Instant updatedAt;
+
+    @PrePersist
+    private void prePersist() {
+        if (createdAt == null) {
+            createdAt = Instant.now();
+        }
+        if (updatedAt == null) {
+            updatedAt = createdAt;
+        }
+    }
 }

17-23: DB 타입 명시 및 마이그레이션 정합성 체크

PostgreSQL 기준 Instant는 일반적으로 timestamptz로 매핑됩니다. 스키마가 timestamp without time zone이면 해석 이슈가 생길 수 있습니다. DDL에서 created_at, updated_attimestamptz not null로 유지되는지, 기존 레코드 마이그레이션이 적용되었는지 확인 바랍니다.

필요 시 마이그레이션 SQL 템플릿을 제공하겠습니다.


17-23: 📝 확인 완료 및 추가 권장사항

  • JPA Auditing 설정

    • src/main/java/inha/gdgoc/global/config/jpa/JpaAuditingConfig.java@EnableJpaAuditing(dateTimeProviderRef = "utcDateTimeProvider")가 선언되어 있으며,
      BaseEntity@EntityListeners(AuditingEntityListener.class)가 적용된 것이 스크립트로 확인되었습니다.
    • 따라서 Auditing이 정상 로드될 경우 @CreatedDate/@LastModifiedDate 애너테이션이 필드 값을 자동 채워 nullable = false 제약에 걸리지 않습니다.
    • 다만, 해당 설정 클래스가 애플리케이션의 컴포넌트 스캔 경로(@SpringBootApplication 베이스 패키지)에 포함되는지 재확인해 주세요.
  • 타임존(JDBC time_zone) 설정 부재

    • application*.yml 파일에서 hibernate.jdbc.time_zone 또는 spring.jpa.properties.hibernate.jdbc.time_zone 설정이 발견되지 않았습니다.
    • Hibernate가 DB에 날짜/시간을 저장할 때 JVM 기본 타임존을 사용하는 것을 방지하려면, 예를 들어 UTC로 고정하기 위해 아래와 같이 설정 추가를 권장합니다.
      spring:
        jpa:
          properties:
            hibernate.jdbc.time_zone: UTC
  • PostgreSQL Instanttimestamptz 매핑 보장

    • JPA 기본 DDL 생성 시 TIMESTAMP WITHOUT TIME ZONE으로 컬럼이 생성될 수 있으므로,
      1. 마이그레이션 스크립트(Flyway/Liquibase)에서 created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL처럼 명시적으로 정의했는지,
      2. 또는 엔티티 필드에 @Column(columnDefinition = "TIMESTAMPTZ")를 추가 적용했는지
        위 두 가지 중 하나를 선택해 검토하시기 바랍니다.

위 사항을 점검·보완하시면 Auditing 의존성 하에서도 nullable=false 제약이 안전하게 보장됩니다.

src/main/java/inha/gdgoc/domain/recruit/entity/Answer.java (1)

36-39: FK 컬럼 네이밍/널 허용 정책 명시 권장

@JoinColumn(name = "recruit_member")는 관례적으로 _id를 포함시키는 편이 가독성과 일관성에 유리합니다. 또한 비즈니스 상 반드시 소속 멤버가 있어야 한다면 nullable = false를 명시하세요.

-    @JoinColumn(name = "recruit_member")
+    @JoinColumn(name = "recruit_member_id", nullable = false)
     private RecruitMember recruitMember;
src/main/java/inha/gdgoc/domain/recruit/entity/RecruitMember.java (2)

70-71: 불필요한 nullable = true 제거 가능

JPA 기본이 nullable 허용이므로 명시가 불필요합니다. 유지보수성을 위해 제거를 권장합니다.

-    @Column(name = "double_major", nullable = true)
+    @Column(name = "double_major")
     private String doubleMajor;

73-75: 필드/컬럼 네이밍 철자 교정 권고: isPayedisPaid (DB 컬럼 is_payedis_paid)

코드베이스 전반에 걸쳐 isPayed가 다음 위치에서 사용되고 있으므로, 일관된 철자 교정을 위해 아래 파일들을 함께 수정하는 것을 권장드립니다.

  • 엔티티: src/main/java/inha/gdgoc/domain/recruit/entity/RecruitMember.java
  • DTO (Response): src/main/java/inha/gdgoc/domain/recruit/dto/response/SpecifiedMemberResponse.java
  • DTO (Request): src/main/java/inha/gdgoc/domain/recruit/dto/request/RecruitMemberRequest.java
  • 테스트 코드: src/test/java/inha/gdgoc/domain/recruit/service/RecruitMemberServiceTest.java

각 파일별로 아래 사항을 반영해주세요.

  • 필드명 및 getter/setter(또는 빌더 메서드)명: isPayedisPaid
  • (엔티티) @Column(name = "is_payed", …) 속성 주석으로 “마이그레이션 시 is_paid로 교정 권장” 문구 추가
  • DB 마이그레이션 스크립트 작성:
    ALTER TABLE recruit_member
      RENAME COLUMN is_payed TO is_paid;
  • JSON 직·역직렬화(예: Jackson) 시점에 변경된 프로퍼티명이 외부 API 또는 프론트엔드에 영향이 없는지 확인
  • 모든 테스트 케이스에서 빌더 호출부(.isPayed(...))를 .isPaid(...)로 수정

예시 엔티티 변경(diff):

-    @Column(name = "is_payed", nullable = false)
-    private Boolean isPayed;
+    @Column(name = "is_payed", nullable = false) // 마이그레이션 시 is_paid로 컬럼명도 교정 권장
+    private Boolean isPaid;

위와 동일한 방식으로 DTO 및 테스트 코드에서도 필드명·메서드명을 일괄 변경해주시기 바랍니다.

src/main/java/inha/gdgoc/domain/user/controller/message/UserMessage.java (1)

3-7: 상수 전용 클래스는 인스턴스화 방지 및 final 선언 권장

메시지 컨테이너는 생성 불가하도록 막고(final + private 생성자 또는 Lombok @UtilityClass) 사용하는 편이 안전합니다.

-package inha.gdgoc.domain.user.controller.message;
-
-public class UserMessage {
+package inha.gdgoc.domain.user.controller.message;
+
+public final class UserMessage {
     public static final String USER_EMAIL_DUPLICATION_RETRIEVED_SUCCESS = "성공적으로 이메일 중복 조회를 완료했습니다.";
     public static final String USER_CREATE_SUCCESS = "성공적으로 회원 가입을 완료했습니다.";
     public static final String USER_EMAIL_RETRIEVED_SUCCESS = "성공적으로 사용자의 이메일을 조회했습니다.";
+    private UserMessage() {}
 }
src/main/resources/db/migration/V20250825__convert_created_updated_to_timestamptz.sql (2)

17-19: 시스템 스키마 추가 제외 제안 (pg_toast/pg_temp 등)

현재 pg_catalog, information_schema만 제외합니다. 안전을 위해 pg_toast 및 임시 스키마(pg_temp_%)도 제외하면 불필요한 스캔을 줄일 수 있습니다.

-        AND n.nspname NOT IN ('pg_catalog','information_schema')
+        AND n.nspname NOT IN ('pg_catalog','information_schema','pg_toast')
+        AND n.nspname NOT LIKE 'pg_temp_%'

1-32: 전반적 접근은 타당합니다 — 락/성능 관점 운영창 고려

테이블 단위 ALTER COLUMN TYPE ... USING은 테이블에 AccessExclusiveLock을 획득합니다. 대형 테이블이 있다면 운영 중 단일 트랜잭션으로 전체를 처리하는 것이 부담될 수 있습니다. 스키마/테이블 배치 처리 또는 유지보수 창에서 수행을 고려하세요. 필요 시 SET lock_timeout을 트랜잭션 앞단에 명시하는 방법도 있습니다.

 DO $$
 DECLARE
   r RECORD;
 BEGIN
+  -- 필요 시 타임아웃 설정 (무제한 대기 방지)
+  PERFORM set_config('lock_timeout', '5s', true);
   FOR r IN
     WITH target_cols AS (
src/main/java/inha/gdgoc/domain/study/controller/message/StudyMessage.java (1)

3-9: 메시지 컨테이너: final + private 생성자 권장

불필요한 인스턴스화를 방지해 주세요. UserMessage와 동일한 규칙 유지가 일관성에도 좋습니다.

-package inha.gdgoc.domain.study.controller.message;
-
-public class StudyMessage {
+package inha.gdgoc.domain.study.controller.message;
+
+public final class StudyMessage {
     public static final String STUDY_LIST_RETRIEVED_SUCCESS = "성공적으로 스터디 목록을 조회했습니다.";
     public static final String APPLIED_STUDY_LIST_RETRIEVED_SUCCESS = "성공적으로 해당 사용자가 지원한 스터디 목록을 조회했습니다.";
     public static final String MY_STUDY_LIST_RETRIEVED_SUCCESS = "성공적으로 해당 사용자가 개설한 스터디 목록을 조회했습니다.";
     public static final String STUDY_RETRIEVED_SUCCESS = "성공적으로 해당 스터디 정보를 조회했습니다.";
     public static final String STUDY_CREATE_SUCCESS = "성공적으로 스터디를 개설했습니다.";
+    private StudyMessage() {}
 }
src/main/java/inha/gdgoc/domain/recruit/controller/message/RecruitMemberMessage.java (2)

3-8: 유틸리티 메시지 클래스는 final 선언 + private 생성자로 인스턴스화 방지

상수만 보유하는 메시지 홀더는 상속/인스턴스화가 불필요합니다. final 선언과 private 생성자를 추가해 의도를 코드로 고정합시다.

- public class RecruitMemberMessage {
+ public final class RecruitMemberMessage {
     public static final String MEMBER_SAVE_SUCCESS = "성공적으로 해당 학기 멤버 가입을 완료했습니다.";
     public static final String STUDENT_ID_DUPLICATION_CHECK_SUCCESS = "성공적으로 학번 중복 조회를 완료했습니다.";
     public static final String PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS = "성공적으로 전화번호 중복 조회를 완료했습니다.";
     public static final String MEMBER_RETRIEVED_SUCCESS = "성공적으로 특정 멤버의 지원서를 조회했습니다.";
-}
+    private RecruitMemberMessage() {}
+}

4-7: 메시지 하드코딩 대신 코드/키 기반 설계 고려 (MessageSource + Enum)

향후 다국어(i18n)·운영 문구 변경에 유연하도록 메시지 문자열 대신 “코드(키) → MessageSource” 구조를 권장합니다. 컨트롤러/응답은 코드만 노출하고, 실제 문구는 properties에서 관리하면 재배포 없이 변경 가능합니다.

예시:

public enum SuccessCode {
  RECRUIT_MEMBER_SAVED("recruit.member.saved"),
  RECRUIT_STUDENT_ID_DUP_CHECKED("recruit.studentId.dup.checked"),
  RECRUIT_PHONE_DUP_CHECKED("recruit.phone.dup.checked"),
  RECRUIT_MEMBER_RETRIEVED("recruit.member.retrieved");
  private final String key;
  SuccessCode(String key) { this.key = key; }
  public String key() { return key; }
}
// Controller: ApiResponse.success(data, SuccessCode.RECRUIT_MEMBER_SAVED)
src/main/java/inha/gdgoc/domain/study/controller/message/StudyAttendeeMessage.java (1)

3-8: final + private 생성자로 유틸리티 클래스 의도 명확화

메시지 상수만 보유하는 클래스이므로 상속/생성 차단이 적절합니다.

- public class StudyAttendeeMessage {
+ public final class StudyAttendeeMessage {
     public static final String STUDY_ATTENDEES_RETRIEVED_SUCCESS = "성공적으로 스터디 신청자 목록을 조회했습니다.";
     public static final String STUDY_ATTENDEE_RETRIEVED_SUCCESS = "성공적으로 스터디 신청자를 조회했습니다.";
     public static final String STUDY_ATTENDEE_SAVE_SUCCESS = "성공적으로 스터디를 신청했습니다.";
     public static final String STUDY_ATTENDEE_UPDATE_SUCCESS = "성공적으로 스터디 지원자의 지원 상태를 업데이트했습니다.";
-}
+    private StudyAttendeeMessage() {}
+}
src/main/java/inha/gdgoc/domain/auth/controller/message/AuthMessage.java (2)

3-11: 메시지 홀더 클래스에 불필요한 상속/인스턴스화 차단

일관성을 위해 final 선언 및 private 생성자 추가를 권장합니다.

- public class AuthMessage {
+ public final class AuthMessage {
     public static final String OAUTH_LOGIN_SIGNUP_SUCCESS = "로그인/회원가입 요청이 성공적으로 실행됐습니다.";
     public static final String ACCESS_TOKEN_REFRESH_SUCCESS = "액세스 토큰이 성공적으로 재발급되었습니다.";
     public static final String LOGIN_WITH_PASSWORD_SUCCESS = "성공적으로 비밀번호를 사용하여 로그인했습니다.";
     public static final String LOGOUT_SUCCESS = "성공적으로 로그아웃했습니다.";
     public static final String CODE_CREATION_SUCCESS = "성공적으로 인증 코드를 발급했습니다.";
     public static final String PASSWORD_RESET_VERIFICATION_SUCCESS = "성공적으로 비밀번호 변경을 위한 인증 코드 검증이 완료되었습니다.";
     public static final String PASSWORD_CHANGE_SUCCESS = "성공적으로 비밀번호를 변경했습니다.";
-}
+    private AuthMessage() {}
+}

4-10: 사람이 읽는 문구 대신 응답 코드 우선 노출 권장

클라이언트 파싱 안정성을 위해 success 메시지(자연어) 노출은 선택적으로 유지하고, 기계가 해석 가능한 코드(enum/string code)를 표준 응답의 1급 필드로 고정하는 것을 권장합니다. 이는 에러/성공 응답 모두에 동일하게 적용하면 좋습니다.

src/main/java/inha/gdgoc/domain/user/service/UserService.java (1)

34-36: 사소한 스타일 정리: 불필요한 괄호 제거

메서드 레퍼런스에 남은 괄호는 제거 가능합니다.

-                .map((User::getId))
+                .map(User::getId)
src/main/java/inha/gdgoc/global/error/ErrorCode.java (1)

5-10: 기계가 읽는 고유 코드 추가를 고려해 주세요

API 클라이언트/프런트엔드에서 조건 분기와 번역 키로 쓰기 위해, 사람이 읽는 메시지와 별개로 안정적인 코드 값이 있으면 좋습니다. 선택적으로 아래 시그니처를 추가하는 것을 제안합니다.

 public interface ErrorCode {

     HttpStatus getStatus();

     String getMessage();
+
+    /**
+     * 머신 리더블한 고유 코드(예: "USER_NOT_FOUND"). enum name과 동일하게 가도 됩니다.
+     */
+    String getCode();
 }
src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java (2)

18-22: 상태 코드 결정을 ErrorCode에 위임해 일관성 유지

일부 핸들러에서 HttpStatus를 하드코딩하고 있습니다. ErrorCode가 상태를 들고 있으므로 이를 일관되게 사용하면 코드 중복과 불일치를 줄일 수 있습니다.

     @ExceptionHandler(MalformedJwtException.class)
     public ResponseEntity<ErrorResponse> handleMalformedJwtException(MalformedJwtException ex) {
-        ErrorResponse response = new ErrorResponse(GlobalErrorCode.INVALID_JWT_REQUEST);
-        return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED);
+        ErrorResponse response = new ErrorResponse(GlobalErrorCode.INVALID_JWT_REQUEST);
+        return ResponseEntity.status(GlobalErrorCode.INVALID_JWT_REQUEST.getStatus()).body(response);
     }

     @ExceptionHandler(JwtException.class)
     public ResponseEntity<ErrorResponse> handleJwtException(JwtException ex) {
-        ErrorResponse response = new ErrorResponse(GlobalErrorCode.INVALID_JWT_REQUEST);
-        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
+        ErrorResponse response = new ErrorResponse(GlobalErrorCode.INVALID_JWT_REQUEST);
+        return ResponseEntity.status(GlobalErrorCode.INVALID_JWT_REQUEST.getStatus()).body(response);
     }

또는 중복 제거를 위해 아래 유틸 메서드를 추가해 공통화할 수도 있습니다.

private static ResponseEntity<ErrorResponse> toResponse(ErrorCode code) {
    return ResponseEntity.status(code.getStatus()).body(new ErrorResponse(code));
}

Also applies to: 37-41


31-35: 검증 예외(Bean Validation) 핸들링 추가 제안

현재 MethodArgumentNotValidException/ConstraintViolationException에 대한 전용 핸들러가 보이지 않습니다. 요청 바디/쿼리 파라미터 검증 실패를 표준 에러 포맷으로 내려주도록 아래 추가를 고려해 주세요.

@ExceptionHandler(org.springframework.web.bind.MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleMethodArgumentNotValid(org.springframework.web.bind.MethodArgumentNotValidException ex) {
    // 필요시 필드 에러 요약을 ErrorResponse에 포함
    ErrorResponse response = new ErrorResponse(GlobalErrorCode.BAD_REQUEST);
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}

@ExceptionHandler(jakarta.validation.ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handleConstraintViolation(jakarta.validation.ConstraintViolationException ex) {
    ErrorResponse response = new ErrorResponse(GlobalErrorCode.BAD_REQUEST);
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}

원하시면 ErrorResponse에 필드별 에러 요약을 담는 포맷(예: errors 배열)을 함께 제안드리겠습니다.

src/main/java/inha/gdgoc/domain/game/controller/message/GameUserMessage.java (2)

3-6: 유틸성 메시지 홀더는 final + private 생성자로 고정하세요

인스턴스화 방지와 의도를 명확히 하기 위해 클래스를 final로, private 생성자를 추가하는 것을 권장합니다.

-public class GameUserMessage {
+public final class GameUserMessage {
     public static final String GAME_RANK_SAVE_SUCCESS = "성공적으로 유저 랭킹 정보를 저장했습니다.";
     public static final String GAME_RANK_RETRIEVED_SUCCESS = "성공적으로 랭킹 정보를 반환했습니다.";
-}
+    private GameUserMessage() {}
+}

4-5: 메시지 톤/대상 일관화

Line 4는 “유저 랭킹 정보”, Line 5는 “랭킹 정보”로 대상 표현이 다릅니다. 도메인 전반에 동일 톤을 유지해 주세요. 예:

  • “성공적으로 유저 랭킹 정보를 저장했습니다.”
  • “성공적으로 유저 랭킹 정보를 조회했습니다.”
src/main/java/inha/gdgoc/domain/auth/exception/AuthErrorCode.java (3)

13-14: 잘못된 Refresh Token은 401이 더 자연스럽습니다

인증 자격 증명(토큰)이 유효하지 않은 경우 일반적으로 401(Unauthorized)을 사용합니다. 현재 403은 “자격 증명은 유효하나 권한이 부족” 케이스에 가깝습니다. 상태 코드를 401로 조정하는 것을 권장합니다. 클라이언트 영향도(리프레시 플로우, 재로그인 트리거)도 함께 점검해 주세요.

-    INVALID_REFRESH_TOKEN(HttpStatus.FORBIDDEN, "잘못된 Refresh Token 값입니다."),
+    INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "잘못된 Refresh Token입니다."),

15-16: 사용자 식별 정보 노출 최소화(Enumeration 방지) 고려

USER_NOT_FOUND(404)는 자원 은닉에 유리하나, 인증/인가 컨텍스트에서 “존재하지 않는 사용자”를 구체적으로 알려주면 사용자 열거(enumeration) 위험이 있습니다. 상황에 따라 401/403으로 일반화하거나, 동일한 에러 메시지로 통일하는 전략을 고려해 주세요.


13-16: 메시지 문장부호 및 띄어쓰기 일관화

다른 항목은 “입니다.”로 끝나는데, USER_NOT_FOUND는 마침표가 없습니다. 또한 “Refresh Token 이”는 띄어쓰기 없이 “Refresh Token이”가 자연스럽습니다. 문장 규칙을 통일해 주세요.

-    INVALID_COOKIE(HttpStatus.FORBIDDEN, "Refresh Token 이 비어있습니다."),
+    INVALID_COOKIE(HttpStatus.FORBIDDEN, "Refresh Token이 비어있습니다."),
@@
-    USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다");
+    USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다.");
src/main/java/inha/gdgoc/domain/resource/controller/ResourceController.java (4)

28-28: MAX_FILE_SIZE는 설정으로 분리하세요

하드코딩된 10MB는 환경별로 달라질 수 있습니다. application.yml의 multipart 설정(spring.servlet.multipart.max-file-size, max-request-size) 또는 @ConfigurationProperties로 외부화해 주세요. 컨트롤러 수준 체크는 방어적으로 유지하되, 기본 제한은 서버 설정으로 위임하는 것이 좋습니다.


40-42: 입력 유효성 보강: 빈 파일, Content-Type 검증

현재는 크기 초과만 검사합니다.

  • 빈 파일(file.isEmpty() 혹은 size==0) 처리
  • 허용된 Content-Type 화이트리스트 검사(image/* 등)
  • 파일 이름/확장자 검증(필요시)

예외는 기존 패턴대로 BusinessException(ResourceException.XXX)로 매핑해 주세요.


46-49: 변수명 카멜케이스로 정리

Java 컨벤션에 따라 result_s3Key → resultS3Key가 가독성에 더 좋습니다.

-            String result_s3Key = s3Service.upload(userId, s3key, file);
-            S3ResultResponse response = new S3ResultResponse(result_s3Key);
+            String resultS3Key = s3Service.upload(userId, s3key, file);
+            S3ResultResponse response = new S3ResultResponse(resultS3Key);

33-35: TODO: 책임 분리 주석 처리

파일 용량 체크, 인증 사용자 조회, 업로드 호출까지 컨트롤러가 다루고 있습니다. 밸리데이션/전처리를 Service/Facade로 이동하면 테스트 용이성과 응집도가 좋아집니다.

src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java (1)

34-38: GET 목록 엔드포인트도 경로 정렬

POST를 /results로 조정하면 GET도 /results로 맞추는 것이 자연스럽습니다. 페이지네이션을 지원할 계획이 있다면 PageResponse 메타 포함 형태(다른 컨트롤러와 동일 패턴)로 확장 여지도 고려해 주세요.

-    @GetMapping("/game/results")
+    @GetMapping("/results")
     public ResponseEntity<ApiResponse<List<GameUserResponse>, Void>> getUserRankings() {
         List<GameUserResponse> response = gameUserService.findUserRankings();

         return ResponseEntity.ok(ApiResponse.ok(GAME_RANK_RETRIEVED_SUCCESS, response));
     }
src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java (3)

26-26: 개발 메모 주석 제거 권장

“// 얘 api 엔드포인트 바뀜!” 같은 메모성 주석은 PR 병합 전 정리해 주세요. Git 히스토리로 충분히 추적 가능합니다.

-    // 얘 api 엔드포인트 바뀜!

27-34: 요청 DTO @Valid 추가 및 빈 응답의 의도 명시

검증이 필요하다면 @Valid 부착을 권장합니다. 또한 데이터 없이 메시지만 반환하는 패턴은 일관성 있어 보입니다.

-    public ResponseEntity<ApiResponse<Void, Void>> saveQuestion(
-            @RequestBody GameQuestionRequest gameQuestionRequest
+    public ResponseEntity<ApiResponse<Void, Void>> saveQuestion(
+            @javax.validation.Valid @RequestBody GameQuestionRequest gameQuestionRequest
     ) {
         gameQuestionService.saveQuestion(gameQuestionRequest);

         return ResponseEntity.ok(ApiResponse.ok(GAME_QUESTION_SAVE_SUCCESS));
     }

36-41: 목록 응답: 메타 도입 여지

다른 컨트롤러(예: StudyAttendeeController)처럼 PageResponse 메타를 도입하면 추후 페이지네이션, 다국어 메시지 등에 확장성이 좋아집니다. 현재 구현은 깔끔하며 문제 없습니다.

src/main/java/inha/gdgoc/domain/user/controller/UserController.java (2)

41-48: 회원가입은 201 Created + Location 헤더가 적합합니다

리소스 생성 작업이므로 200 OK 대신 201 Created를 사용하고, 가능하면 생성된 사용자 리소스의 URI를 Location 헤더로 제공하세요. 또한 요청 DTO에 대한 @Valid 검증을 추가하세요. 컨트롤러에서 체크드 예외를 직접 던지기보다 서비스에서 도메인 예외로 매핑하는 편이 일관적입니다.

-    public ResponseEntity<ApiResponse<Void, Void>> userSignup(
-            @RequestBody UserSignupRequest userSignupRequest
-    ) throws NoSuchAlgorithmException, InvalidKeyException {
-        userService.saveUser(userSignupRequest);
-
-        return ResponseEntity.ok(ApiResponse.ok(USER_CREATE_SUCCESS));
+    public ResponseEntity<ApiResponse<Void, Void>> userSignup(
+            @jakarta.validation.Valid @RequestBody UserSignupRequest userSignupRequest
+    ) {
+        Long userId = userService.saveUser(userSignupRequest);
+        return org.springframework.http.ResponseEntity
+                .created(java.net.URI.create("/api/v1/users/" + userId))
+                .body(inha.gdgoc.global.dto.response.ApiResponse.created(USER_CREATE_SUCCESS));
     }

참고: ApiResponse.created(...)를 사용하면 본문 내 code=201과 HTTP 상태가 일치합니다.


50-57: [다음 단계] 사용자 ID 조회 엔드포인트의 예외 처리 및 응답 통일 검토

아래 사항을 확인 후, 취약점 완화를 위한 리팩토링을 권장합니다.

  • UserService.findId 메서드
    • 사용자 미검출 시 NotFoundException을 던지며,
    • 컨트롤러(UserController.findEmail)는 성공 시 200 OK/ApiResponse 반환, 실패 시 예외 → 404 등으로 처리

  • 전역 예외 처리(@ControllerAdvice)
    • NotFoundException을 개별 상태 코드/메시지로 매핑하고 있는지 확인 필요

  • 개선 제안

    1. 성공/실패 응답 메시지·코드 통일 (예: 항상 200 + “조회 완료되었습니다” 및 body에 이메일 유무 불명시)
    2. 요청 DTO에 @Valid 적용
    3. 레이트 리밋/캡차 도입
    4. 감사 로깅(audit logging)

위 사항을 반영하여 다음 파일을 우선 점검하세요.

  • src/main/java/inha/gdgoc/domain/user/service/UserService.java (findId)
  • src/main/java/inha/gdgoc/domain/user/controller/UserController.java (findEmail)
  • 전역 예외 처리 클래스 (ControllerAdvice)
src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java (3)

9-19: 매직 넘버(200) 대신 HttpStatus 상수를 사용하세요

가독성과 유지보수성을 위해 HttpStatus.OK.value()를 사용하세요. 상태 코드 정의가 한 곳에서 바뀌어도 안전합니다.

-    public static <T> ApiResponse<T, Void> ok(String message, T data) {
-        return new ApiResponse<>(200, message, data, null);
+    public static <T> ApiResponse<T, Void> ok(String message, T data) {
+        return new ApiResponse<>(org.springframework.http.HttpStatus.OK.value(), message, data, null);
     }
@@
-    public static <T, M> ApiResponse<T, M> ok(String message, T data, M meta) {
-        return new ApiResponse<>(200, message, data, meta);
+    public static <T, M> ApiResponse<T, M> ok(String message, T data, M meta) {
+        return new ApiResponse<>(org.springframework.http.HttpStatus.OK.value(), message, data, meta);
     }
@@
-    public static ApiResponse<Void, Void> ok(String message) {
-        return new ApiResponse<>(200, message, null, null);
+    public static ApiResponse<Void, Void> ok(String message) {
+        return new ApiResponse<>(org.springframework.http.HttpStatus.OK.value(), message, null, null);
     }

29-35: 에러 응답 팩토리에 편의 오버로드 추가 권장

메타 정보가 필요 없는 단순 에러 케이스가 빈번합니다. error(ErrorCode)error(int, String) 오버로드를 추가하면 사용성이 좋아집니다.

     public static ApiResponse<Void, ErrorMeta> error(ErrorCode errorCode, ErrorMeta meta) {
         return new ApiResponse<>(errorCode.getStatus().value(), errorCode.getMessage(), null, meta);
     }
 
     public static ApiResponse<Void, ErrorMeta> error(int code, String message, ErrorMeta meta) {
         return new ApiResponse<>(code, message, null, meta);
     }
+
+    // 편의 오버로드: meta 없음
+    public static ApiResponse<Void, ErrorMeta> error(ErrorCode errorCode) {
+        return new ApiResponse<>(errorCode.getStatus().value(), errorCode.getMessage(), null, null);
+    }
+
+    // 편의 오버로드: 임의 코드/메시지, meta 없음
+    public static ApiResponse<Void, ErrorMeta> error(int code, String message) {
+        return new ApiResponse<>(code, message, null, null);
+    }

7-7: 중복 상태 표기의 불일치 위험 안내

본체의 codeResponseEntity의 HTTP 상태가 다를 경우 클라이언트 혼란이 생깁니다. 컨트롤러에서 ResponseEntity.status(...).body(ApiResponse.*)를 사용하고, 팩토리 메서드는 항상 해당 상태 값을 셋업하도록 지금처럼 유지하는 것이 좋습니다. 팀 규약으로 명시를 권장합니다.

src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java (3)

75-89: 생성 엔드포인트는 201 Created가 적합합니다

참가 신청 생성은 리소스 생성이므로 201 응답으로 바꾸는 것이 REST 관례에 맞습니다. 요청 DTO에도 @Valid를 추가하세요.

-    public ResponseEntity<ApiResponse<GetStudyAttendeeResponse, Void>> createAttendee(
+    public ResponseEntity<ApiResponse<GetStudyAttendeeResponse, Void>> createAttendee(
             Authentication authentication,
             @PathVariable Long studyId,
-            @RequestBody AttendeeCreateRequest attendeeCreateRequest
+            @jakarta.validation.Valid @RequestBody AttendeeCreateRequest attendeeCreateRequest
     ) {
@@
-        return ResponseEntity.ok(ApiResponse.ok(STUDY_ATTENDEE_SAVE_SUCCESS, response));
+        return org.springframework.http.ResponseEntity
+                .status(org.springframework.http.HttpStatus.CREATED)
+                .body(inha.gdgoc.global.dto.response.ApiResponse.created(STUDY_ATTENDEE_SAVE_SUCCESS, response));

91-101: 부분 업데이트(PATCH)는 204 No Content 또는 변경된 리소스 반환 중 하나로 일관화하세요

현재 boolean true만 반환합니다. 변경 후 리소스를 다시 조회해 반환하거나(200 OK), 본문 없이 204를 반환하는 것이 일반적입니다. 또한 요청 DTO @Valid를 추가하세요.

-    public ResponseEntity<ApiResponse<Boolean, Void>> updateAttendee(
+    public ResponseEntity<ApiResponse<Boolean, Void>> updateAttendee(
             Authentication authentication,
             @PathVariable("studyId") Long studyId,
-            @RequestBody AttendeeUpdateRequest request
+            @jakarta.validation.Valid @RequestBody AttendeeUpdateRequest request
     ) {
@@
-        return ResponseEntity.ok(ApiResponse.ok(STUDY_ATTENDEE_UPDATE_SUCCESS, true));
+        return org.springframework.http.ResponseEntity
+                .status(org.springframework.http.HttpStatus.NO_CONTENT)
+                .body(inha.gdgoc.global.dto.response.ApiResponse.ok(STUDY_ATTENDEE_UPDATE_SUCCESS));

40-57: PageResponse를 전역 패키지로 이동하고 중복 제거 고려

현재 코드베이스에는 inha.gdgoc.domain.study.dto.response.PageResponse 클래스(레코드)만 정의되어 있으며, inha.gdgoc.global.dto.response 패키지에는 별도의 PageResponse가 없습니다. 전역으로 페이징 메타를 통일하면서 재사용성을 높이려면 다음을 고려해주세요:

• 전역 패키지에 PageResponse 레코드를 새로 정의

  • 위치: src/main/java/inha/gdgoc/global/dto/response/PageResponse.java
  • 기존 필드(int page, int pageCount) 유지

• 기존 정의(inha.gdgoc.domain.study.dto.response.PageResponse) 삭제 또는 deprecated 처리

• 모든 사용처(import 구문)에서 전역 패키지로 경로 변경

  • 예: import inha.gdgoc.global.dto.response.PageResponse;

위 작업을 통해 프로젝트 전반에서 동일한 페이징 메타 타입을 재사용할 수 있으며, 추후 유지보수 시 중복 코드를 방지할 수 있습니다.

src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java (3)

32-39: 멤버 신청 생성은 201 Created + 유효성 검증 필요

생성 동작이므로 201 Created가 적합하며, 본문 DTO에 @Valid를 붙여 입력 검증을 활성화하세요.

-    public ResponseEntity<ApiResponse<Void, Void>> recruitMemberAdd(
-            @RequestBody ApplicationRequest applicationRequest
+    public ResponseEntity<ApiResponse<Void, Void>> recruitMemberAdd(
+            @jakarta.validation.Valid @RequestBody ApplicationRequest applicationRequest
     ) {
         recruitMemberService.addRecruitMember(applicationRequest);
-
-        return ResponseEntity.ok(ApiResponse.ok(MEMBER_SAVE_SUCCESS));
+        return org.springframework.http.ResponseEntity
+                .status(org.springframework.http.HttpStatus.CREATED)
+                .body(inha.gdgoc.global.dto.response.ApiResponse.created(MEMBER_SAVE_SUCCESS));
     }

43-50: 단순 조회 파라미터는 @RequestParam이 더 간결합니다

@ModelAttribute도 동작하지만, 단일 파라미터 조회에는 @RequestParam이 간결하고 명확합니다. 현재 TODO에 적힌 방향과도 일치합니다.

-    public ResponseEntity<ApiResponse<Boolean, Void>> duplicatedStudentIdDetails(
-            @Valid @ModelAttribute CheckStudentIdRequest studentIdRequest
-    ) {
-        boolean exists = recruitMemberService.isRegisteredStudentId(studentIdRequest.getStudentId());
+    public ResponseEntity<ApiResponse<Boolean, Void>> duplicatedStudentIdDetails(
+            @org.springframework.web.bind.annotation.RequestParam("studentId") String studentId
+    ) {
+        boolean exists = recruitMemberService.isRegisteredStudentId(studentId);
         return ResponseEntity.ok(ApiResponse.ok(STUDENT_ID_DUPLICATION_CHECK_SUCCESS, exists));
     }

52-60: 전화번호 중복 조회도 @RequestParam으로 일관화 권장

위와 동일한 이유로 단일 파라미터에서는 @RequestParam을 권장합니다.

-    public ResponseEntity<ApiResponse<Boolean, Void>> duplicatedPhoneNumberDetails(
-            @Valid @ModelAttribute CheckPhoneNumberRequest phoneNumberRequest
-    ) {
-        boolean exists = recruitMemberService.isRegisteredPhoneNumber(phoneNumberRequest.getPhoneNumber());
+    public ResponseEntity<ApiResponse<Boolean, Void>> duplicatedPhoneNumberDetails(
+            @org.springframework.web.bind.annotation.RequestParam("phoneNumber") String phoneNumber
+    ) {
+        boolean exists = recruitMemberService.isRegisteredPhoneNumber(phoneNumber);
         return ResponseEntity.ok(ApiResponse.ok(PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS, exists));
     }
src/main/java/inha/gdgoc/domain/study/controller/StudyController.java (2)

97-106: 스터디 생성은 201 Created + 입력 검증을 추가하세요

생성 시 201 상태가 적합하며, StudyCreateRequest@Valid를 붙여 검증 실패를 전역 예외 처리로 위임하세요. 가능하면 Location 헤더도 제공하세요.

-    public ResponseEntity<ApiResponse<StudyDto, Void>> createStudy(
+    public ResponseEntity<ApiResponse<StudyDto, Void>> createStudy(
             Authentication authentication,
-            @RequestBody StudyCreateRequest body
+            @jakarta.validation.Valid @RequestBody StudyCreateRequest body
     ) {
         Long userId = authService.getAuthenticationUserId(authentication);
         StudyDto createdStudy = studyService.createStudy(userId, body);
-
-        return ResponseEntity.ok(ApiResponse.ok(STUDY_CREATE_SUCCESS, createdStudy));
+        return org.springframework.http.ResponseEntity
+                .status(org.springframework.http.HttpStatus.CREATED)
+                .body(inha.gdgoc.global.dto.response.ApiResponse.created(STUDY_CREATE_SUCCESS, createdStudy));
     }

47-61: DTO 응답 객체명 개선 제안: StudyListRequestStudyListResponse

응답용으로만 사용되는 StudyListRequest는 “Request” 보다는 “Response”를 의미하는 이름이 더 적합합니다. 클래스명과 파일명을 변경하여 코드 가독성과 일관성을 높이는 것을 권장드립니다.

  • 변경 대상 파일 및 위치
    src/main/java/inha/gdgoc/domain/study/dto/response/StudyListRequest.javaStudyListResponse.java (클래스명 및 파일명)
    src/main/java/inha/gdgoc/domain/study/controller/StudyController.java
    – import 구문
    – 제네릭 타입
    – 인스턴스 생성부
diff --git a/src/main/java/inha/gdgoc/domain/study/dto/response/StudyListRequest.java b/src/main/java/inha/gdgoc/domain/study/dto/response/StudyListResponse.java
- public class StudyListRequest {
+ public class StudyListResponse {
      private List<StudyDto> studyList;
  }

diff --git a/src/main/java/inha/gdgoc/domain/study/controller/StudyController.java b/src/main/java/inha/gdgoc/domain/study/controller/StudyController.java
- import inha.gdgoc.domain.study.dto.response.StudyListRequest;
+ import inha.gdgoc.domain.study.dto.response.StudyListResponse;
@@
- public ResponseEntity<ApiResponse<StudyListRequest, PageResponse>> getStudyList(
+ public ResponseEntity<ApiResponse<StudyListResponse, PageResponse>> getStudyList(
@@
-     StudyListRequest response = new StudyListRequest(result.getStudyList());
+     StudyListResponse response = new StudyListResponse(result.getStudyList());

이름 변경을 통해 응답과 요청 객체의 역할을 명확히 구분할 수 있습니다.

src/main/java/inha/gdgoc/domain/auth/controller/AuthController.java (6)

3-12: 정적 import 혼용 → 오류 코드는 한 가지 방식으로 일관화 권장

오류 코드 사용이 static import(UNAUTHORIZED_USER, USER_NOT_FOUND)와 정규 참조(AuthErrorCode.INVALID_COOKIE 등)로 혼용됩니다. 팀 컨벤션에 맞춰 한 가지 방식으로 통일해 가독성과 검색성을 높이는 것이 좋습니다.


60-66: OAuth 콜백 응답으로 Map<String, Object> 사용 → DTO로 명시화 권장

동적 Map은 스키마 안정성과 문서화에 불리합니다. 안정적인 API 계약을 위해 응답 DTO를 정의해 주세요. 예: Google 로그인 결과가 액세스/리프레시 토큰, 신규회원 여부 등을 담는다고 가정하면 아래처럼 명시형 DTO로 교체 권장.

- public ResponseEntity<ApiResponse<Map<String, Object>, Void>> handleGoogleCallback(
+ public ResponseEntity<ApiResponse<OAuthLoginResponse, Void>> handleGoogleCallback(
     @RequestParam String code,
     HttpServletResponse response
 ) {
-    Map<String, Object> data = authService.processOAuthLogin(code, response);
-    return ResponseEntity.ok(ApiResponse.ok(OAUTH_LOGIN_SIGNUP_SUCCESS, data));
+    OAuthLoginResponse data = authService.processOAuthLogin(code, response);
+    return ResponseEntity.ok(ApiResponse.ok(OAUTH_LOGIN_SIGNUP_SUCCESS, data));
 }

추가로, 응답 본문/쿠키/로그에 인가 토큰(특히 refresh token, ID token)이 포함되지 않도록 재확인 바랍니다.


69-86: 제네릭 명시 및 ApiResponse 오버로드 일관화

  • 메서드 시그니처가 ResponseEntity<?>로 느슨합니다. 컨벤션(다른 컨트롤러들)을 따라 구체 제네릭을 명시하세요.
  • 메타가 없을 때 ApiResponse.ok(message, data) 오버로드를 사용하면 null 전달을 피할 수 있어 일관성이 좋아집니다.
- @PostMapping("/refresh")
- public ResponseEntity<?> refreshAccessToken(
+ @PostMapping("/refresh")
+ public ResponseEntity<ApiResponse<AccessTokenResponse, Void>> refreshAccessToken(
     @CookieValue(value = "refresh_token", required = false) String refreshToken
 ) {
@@
- return ResponseEntity.ok(
-         ApiResponse.ok(ACCESS_TOKEN_REFRESH_SUCCESS, accessTokenResponse, null));
+ return ResponseEntity.ok(ApiResponse.ok(ACCESS_TOKEN_REFRESH_SUCCESS, accessTokenResponse));

93-100: 요청 DTO에 Bean Validation 적용 권장

인증/인가 입력값은 필수값/형식 검증이 필요합니다. @Valid 추가를 권장합니다. (DTO에 @NotBlank, @Email 등 선언 가정)

- public ResponseEntity<ApiResponse<LoginResponse, Void>> login(
-         @RequestBody UserLoginRequest userLoginRequest,
+ public ResponseEntity<ApiResponse<LoginResponse, Void>> login(
+         @Valid @RequestBody UserLoginRequest userLoginRequest,
          HttpServletResponse response
 ) ...

103-131: 로그아웃: PII/민감 로그 최소화 및 인증체크 보강 제안

  • 현재 로그에 이메일을 반복 출력합니다. 이메일은 PII이므로 userId만 로깅하거나 마스킹하세요.
  • isAuthenticated()AnonymousAuthenticationToken에서도 true일 수 있습니다. 익명 인증 차단 체크를 추가하는 것이 안전합니다.
- log.info("로그아웃 시도: 사용자 ID: {}, 이메일: {}", userId, email);
+ log.info("로그아웃 시도: 사용자 ID: {}", userId);

- if (authentication == null || !authentication.isAuthenticated()) {
+ if (authentication == null || !authentication.isAuthenticated()
+     || authentication.getPrincipal() == null
+     || "anonymousUser".equals(authentication.getPrincipal())) {
     throw new BusinessException(UNAUTHORIZED_USER);
 }
@@
- } else {
-     log.warn("사용자를 찾을 수 없습니다.");
- }
+ }

또한 TODO에 적어두신 대로 해당 로직은 서비스 계층으로 이전해 트랜잭션/감사 로깅을 일원화하는 것을 권장합니다.


149-157: 검증 API 입력값 검증 및 응답 정책 확인

  • @Valid 추가 권장.
  • 잘못된 코드일 때도 200 + verified=false를 반환하는 현재 정책이 의도된 것인지 확인 바랍니다. 인증 흐름상 400으로 처리하고 에러 코드/메시지를 주는 것이 클라이언트 단 처리에 유리할 수 있습니다.
- public ResponseEntity<ApiResponse<CodeVerificationResponse, Void>> verifyCode(
-         @RequestBody CodeVerificationRequest request
+ public ResponseEntity<ApiResponse<CodeVerificationResponse, Void>> verifyCode(
+         @Valid @RequestBody CodeVerificationRequest request
 ) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ac46047 and 9c5af7a.

📒 Files selected for processing (43)
  • src/main/java/inha/gdgoc/GdgocApplication.java (0 hunks)
  • src/main/java/inha/gdgoc/domain/auth/controller/AuthController.java (6 hunks)
  • src/main/java/inha/gdgoc/domain/auth/controller/message/AuthMessage.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/auth/dto/request/FindIdRequest.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/auth/dto/response/AccessTokenResponse.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/auth/entity/RefreshToken.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/auth/exception/AuthErrorCode.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java (2 hunks)
  • src/main/java/inha/gdgoc/domain/game/controller/message/GameQuestionMessage.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/game/controller/message/GameUserMessage.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/game/entity/GameUser.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/recruit/controller/message/RecruitMemberMessage.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/recruit/dto/response/SpecifiedMemberResponse.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/recruit/entity/Answer.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/recruit/entity/RecruitMember.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/resource/controller/ResourceController.java (2 hunks)
  • src/main/java/inha/gdgoc/domain/resource/controller/message/ResourceMessage.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java (3 hunks)
  • src/main/java/inha/gdgoc/domain/study/controller/StudyController.java (5 hunks)
  • src/main/java/inha/gdgoc/domain/study/controller/message/StudyAttendeeMessage.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/study/controller/message/StudyMessage.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/study/entity/Study.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/study/entity/StudyAttendee.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/user/controller/UserController.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/user/controller/message/UserMessage.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/user/entity/User.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/user/service/UserService.java (1 hunks)
  • src/main/java/inha/gdgoc/global/common/ApiResponse.java (0 hunks)
  • src/main/java/inha/gdgoc/global/config/jpa/JpaAuditingConfig.java (1 hunks)
  • src/main/java/inha/gdgoc/global/dto/response/ApiResponse.java (1 hunks)
  • src/main/java/inha/gdgoc/global/dto/response/ErrorMeta.java (1 hunks)
  • src/main/java/inha/gdgoc/global/dto/response/ErrorResponse.java (1 hunks)
  • src/main/java/inha/gdgoc/global/entity/BaseEntity.java (2 hunks)
  • src/main/java/inha/gdgoc/global/error/ErrorCode.java (1 hunks)
  • src/main/java/inha/gdgoc/global/error/GlobalExceptionHandler.java (1 hunks)
  • src/main/java/inha/gdgoc/global/error/NotFoundException.java (1 hunks)
  • src/main/java/inha/gdgoc/global/security/SecurityConfig.java (1 hunks)
  • src/main/resources/application-dev.yml (1 hunks)
  • src/main/resources/application-local.yml (1 hunks)
  • src/main/resources/application-prod.yml (1 hunks)
  • src/main/resources/db/migration/V20250825__convert_created_updated_to_timestamptz.sql (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/inha/gdgoc/global/common/ApiResponse.java
  • src/main/java/inha/gdgoc/GdgocApplication.java
🧰 Additional context used
🧬 Code graph analysis (8)
src/main/java/inha/gdgoc/domain/user/controller/UserController.java (3)
src/main/java/inha/gdgoc/domain/user/controller/message/UserMessage.java (1)
  • UserMessage (3-7)
src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java (1)
  • RequestMapping (19-42)
src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java (1)
  • RequestMapping (25-70)
src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java (4)
src/main/java/inha/gdgoc/domain/game/controller/message/GameQuestionMessage.java (1)
  • GameQuestionMessage (3-6)
src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java (1)
  • RequiredArgsConstructor (18-39)
src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java (1)
  • RequestMapping (25-70)
src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java (1)
  • RequestMapping (31-102)
src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java (1)
src/main/java/inha/gdgoc/domain/game/controller/message/GameUserMessage.java (1)
  • GameUserMessage (3-6)
src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java (3)
src/main/java/inha/gdgoc/domain/study/controller/message/StudyAttendeeMessage.java (1)
  • StudyAttendeeMessage (3-8)
src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java (1)
  • RequestMapping (19-42)
src/main/java/inha/gdgoc/domain/study/controller/StudyController.java (1)
  • RequestMapping (38-108)
src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java (5)
src/main/java/inha/gdgoc/domain/recruit/controller/message/RecruitMemberMessage.java (1)
  • RecruitMemberMessage (3-8)
src/main/java/inha/gdgoc/domain/recruit/dto/response/SpecifiedMemberResponse.java (1)
  • SpecifiedMemberResponse (5-17)
src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java (1)
  • RequiredArgsConstructor (18-39)
src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java (1)
  • RequestMapping (19-42)
src/main/java/inha/gdgoc/domain/user/controller/UserController.java (1)
  • RequestMapping (24-58)
src/main/java/inha/gdgoc/domain/resource/controller/ResourceController.java (3)
src/main/java/inha/gdgoc/domain/resource/controller/message/ResourceMessage.java (1)
  • ResourceMessage (3-5)
src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java (1)
  • RequestMapping (19-42)
src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java (1)
  • RequestMapping (31-102)
src/main/java/inha/gdgoc/domain/auth/controller/AuthController.java (5)
src/main/java/inha/gdgoc/domain/auth/controller/message/AuthMessage.java (1)
  • AuthMessage (3-11)
src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java (1)
  • RequestMapping (19-42)
src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java (1)
  • RequestMapping (25-70)
src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java (1)
  • RequestMapping (31-102)
src/main/java/inha/gdgoc/domain/user/controller/UserController.java (1)
  • RequestMapping (24-58)
src/main/java/inha/gdgoc/domain/study/controller/StudyController.java (2)
src/main/java/inha/gdgoc/domain/study/controller/message/StudyMessage.java (1)
  • StudyMessage (3-9)
src/main/java/inha/gdgoc/domain/study/controller/StudyAttendeeController.java (1)
  • RequestMapping (31-102)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/resources/application-prod.yml (1)

49-50: 프로덕션에서 org.hibernate.SQL: debug 비활성화 필요 — 성능/보안(PII) 리스크

운영 환경에서 SQL 로그를 debug로 남기면 대량 I/O로 성능 저하 및 민감 정보 노출 위험이 큽니다. spring.jpa.show-sql: false 설정과 충돌하며, 파라미터 타입 로그를 꺼도(SQL에 리터럴이 포함될 수 있어) 안전하지 않습니다. 운영 프로필에서는 최소 warn(또는 제거)로 낮추는 것을 권장합니다.

적용 예시:

 logging:
   level:
-    org.hibernate.SQL: debug
+    org.hibernate.SQL: warn
     org.hibernate.type: off

또는 해당 키를 제거하고, 개발/로컬 프로필에서만 SQL 디버그를 유지해 주세요.

♻️ Duplicate comments (3)
src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java (3)

19-21: 클래스 레벨 버저닝 경로 추가(LGTM)

@RequestMapping("/api/v1/game")로 베이스 경로를 일원화한 점 좋습니다. 다른 컨트롤러들과의 일관성이 확보됩니다. 이전 리뷰 지적사항을 적절히 반영하셨습니다.


26-33: POST 경로 복수형 통일 및 요청 유효성 검증(@Valid) 누락

  • 현재 POST는 "/result"(단수), GET은 "/results"(복수)로 불일치합니다. 리소스 관점에서 둘 다 "/results"가 자연스럽습니다.
  • 요청 DTO에 @Valid가 없어 검증이 적용되지 않습니다. 프로젝트 전반의 예외/응답 리팩토링 목표(#195)와도 직결되는 부분이므로 최소한 Controller 단 진입점에서 유효성 검증을 거치도록 하는 것을 권장합니다.

아래 최소 수정안을 제안합니다.

-    @PostMapping("/result")
+    @PostMapping("/results")
     public ResponseEntity<ApiResponse<List<GameUserResponse>, Void>> saveGameResult(
-            @RequestBody GameUserRequest request
+            @Valid @RequestBody GameUserRequest request
     ) {
         List<GameUserResponse> response = gameUserService.saveGameResultAndGetRanking(request);

         return ResponseEntity.ok(ApiResponse.ok(GAME_RANK_SAVE_SUCCESS, response));
     }

추가로 import가 필요합니다(별도 범위에 대한 보강 diff는 아래 코멘트 참고).

다음 스크립트로 잔존 단수 경로("/game/result")와 @Valid 미적용 여부를 점검할 수 있습니다.

#!/bin/bash
set -euo pipefail

echo "1) GameUserController의 POST 단수 경로 사용 여부:"
rg -nP '@PostMapping\(\s*"/result"\s*\)' src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java || echo "  - 단수 경로 사용 안 함"

echo "2) GameUserController.saveGameResult에 @Valid 적용 여부:"
rg -nP 'saveGameResult\s*\(\s*@Valid\s+@RequestBody\s+GameUserRequest' src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java || echo "  - @Valid 미적용"

echo "3) 과거 절대 경로(/game/result) 잔존 여부(레거시 라우팅 탐지):"
rg -nP '@(PostMapping|GetMapping)\(\s*"/game/result' src/main/java/inha/gdgoc/domain/**/controller || echo "  - 레거시 라우팅 없음(OK)"

echo "4) GameUserRequest DTO 제약 애노테이션 확인(참고용):"
sed -n '1,200p' src/main/java/inha/gdgoc/domain/game/dto/request/GameUserRequest.java || true

필요하시면 요청 DTO에 적절한 Bean Validation 제약(@NotNull, @SiZe 등)도 함께 제안드리겠습니다.


12-17: 유효성 검증을 위한 import 보강

@Valid 적용을 위해 jakarta.validation.Valid import를 추가해 주세요.

 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
+import jakarta.validation.Valid;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
🧹 Nitpick comments (3)
src/main/resources/application-prod.yml (1)

8-8: Jackson 타임존(Asia/Seoul) vs Hibernate/DB UTC — 직렬화 타임존 불일치 가능성 점검

운영 프로필에서만 spring.jackson.time-zone: Asia/Seoul이고, JPA/Hibernate는 UTC로 동작합니다. 엔티티/DTO에 Instant만 사용한다면 큰 문제는 없지만, OffsetDateTime/ZonedDateTime/LocalDateTime을 사용하는 응답이 있다면 환경별(KST vs UTC)로 표기가 달라질 수 있습니다. 의도된 비즈니스 요구(KST로 응답 고정)가 아니라면 Jackson도 UTC로 통일하는 것을 권장합니다.

가능한 정합화 예시:

 spring:
   jackson:
-    time-zone: Asia/Seoul
+    time-zone: UTC

의도적으로 KST 응답을 유지해야 한다면, API 스펙에 타임존을 명시하고, 내부 저장/연산은 UTC(Instant)로 고정하는 현재 전략을 문서화해 주세요.

src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java (2)

32-32: POST 응답 코드 201 Created 고려(선택 사항)

리소스 생성 성격의 작업이라면 200 OK 대신 201 Created를 고려할 수 있습니다. 프로젝트 내 다른 컨트롤러(예: GameQuestionController)가 200을 사용 중이라면 현재 방식도 허용되지만, 장기적으로 일관된 정책을 정하는 것을 권장합니다.

-        return ResponseEntity.ok(ApiResponse.ok(GAME_RANK_SAVE_SUCCESS, response));
+        return ResponseEntity.status(HttpStatus.CREATED)
+                .body(ApiResponse.ok(GAME_RANK_SAVE_SUCCESS, response));

추가 import:

import org.springframework.http.HttpStatus;

35-40: 리소스/메서드 명명 정합성 제안(선택 사항)

엔드포인트는 "/results"이지만 서비스/메서드 명은 findUserRankings(), getUserRankings로 “랭킹”에 초점이 맞춰져 있습니다. 도메인 용어를 통일하면 유지보수성 향상에 도움이 됩니다.

옵션:

  • 엔드포인트를 "/rankings"로 변경(POST/GET 모두) 또는
  • 메서드명을 getResults 등으로 조정하여 엔드포인트와 맞춤

예) 메서드명만 정리하는 최소 변경

-    public ResponseEntity<ApiResponse<List<GameUserResponse>, Void>> getUserRankings() {
+    public ResponseEntity<ApiResponse<List<GameUserResponse>, Void>> getResults() {

향후 API 문서(Swagger/Asciidoc 등)에서도 용어가 일치하는지 함께 확인해 주세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9c5af7a and fd6ea91.

📒 Files selected for processing (3)
  • src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java (1 hunks)
  • src/main/resources/application-dev.yml (1 hunks)
  • src/main/resources/application-prod.yml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/resources/application-dev.yml
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java (2)
src/main/java/inha/gdgoc/domain/game/controller/message/GameUserMessage.java (1)
  • GameUserMessage (3-6)
src/main/java/inha/gdgoc/domain/game/controller/GameQuestionController.java (1)
  • RequestMapping (19-42)
🔇 Additional comments (3)
src/main/resources/application-prod.yml (1)

25-26: 이전 hibernate.time_zone 설정 키가 코드베이스에 남아있지 않음 확인
제공된 스크립트(rg -n -C2 -g 'src/main/resources/**/*.yml' 'hibernate\.time_zone'rg -n -C2 -g 'src/main/resources/**/*.yml' 'hibernate\.jdbc\.time_zone') 실행 결과, 어떠한 YAML 파일에서도 구(hibernate.time_zone)·신(hibernate.jdbc.time_zone) 설정이 모두 조회되지 않았습니다.
따라서 기존 설정을 완전히 제거했고, 새 설정(spring.jpa.properties.hibernate.jdbc.time_zone: UTC)만 반영된 상태임을 확인했습니다.

• 추가 조치 사항 없음

src/main/java/inha/gdgoc/domain/game/controller/GameUserController.java (2)

3-5: 메시지 상수 static import 정리(LGTM)

메시지 상수를 static import로 가져와 가독성이 좋아졌습니다.


9-9: ApiResponse 패키지 전환 반영(LGTM)

inha.gdgoc.global.dto.response.ApiResponse로의 이전을 컨트롤러에서 정상적으로 적용했습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] API 공통 응답 수정

2 participants