Skip to content

Conversation

@1winhyun
Copy link
Member

@1winhyun 1winhyun commented Jan 7, 2026

🔀 변경 내용

  • 일반 유저의 홈화면에 존재하는 조회 기능 중 일부를 구현했습니다.

✅ 작업 항목

  • 로그인한 일반 유저의 정보 조회 기능을 구현했습니다. (닉네임(만약 닉네임 설정 안했을 경우 카카오 이름), 학교, 단과대, 학과)
  • 현재 이용 가능한 제휴를 조회하는 기능을 구현했습니다.(학생회 타입 별로 조회할 수 있게 했습니다. 예를 들어 SCHOOL_COUNCIL을 선택할 경우 총학생회에서 올린 제휴 게시글을 확인할 수 있습니다.)

추가 구현

  • 학생회 전용 제휴 / 행사 보기 기능 및 72시간 이내 다가오는 행사 보기 기능을 구현했습니다.
  • User 테이블에 campusNickname 컬럼을 추가하고 닉네임을 변경할 수 있는 기능을 구현했습니다. (중복불가)

📸 스크린샷 (선택)

로그인한 일반 유저 정보 조회 기능

image

현재 이용 가능한 제휴 랜덤 조회 기능

image image

학생회 전용 제휴 / 행사 보기 기능

image image

학생회 전용 72시간 이내 다가오는 행사 보기 기능

image

닉네임 변경 기능 구현

image
  • 제대로 변경됨을 확인
image

📎 참고 이슈

관련 이슈 번호 #41

++ 추가적으로 홈화면의 조회의 경우 시간대별 장소 추천 및 큐레이션과 관련 기능을 구현해야합니다. 이는 지도 검색 관련 기능이 전부 구현된 이후 구현할 예정

Summary by CodeRabbit

  • New Features

    • 의회 유형별 게시물·이벤트 목록 및 전용 응답 형식 추가
    • 사용자 대상 활성 제휴 목록 조회 API 추가
    • 캠퍼스 닉네임 변경 API 및 현재 사용자 정보 조회 API 추가
    • OAuth 로그인 응답에 캠퍼스 닉네임 포함
  • Responses / Messages

    • 닉네임 중복 오류 코드 및 관련 예외 추가
    • 닉네임 변경·유저 정보 조회 성공 응답 코드 추가
  • Refactor

    • 컨트롤러·서비스 명칭 및 경로 정리(사용자/의회 컨텍스트 분리)

✏️ Tip: You can customize this high-level summary in your review settings.

@1winhyun 1winhyun requested review from 1224kang and jjaeroong January 7, 2026 22:46
@1winhyun 1winhyun self-assigned this Jan 7, 2026
@1winhyun 1winhyun added ♻️Refactor 리팩토링 ✨Feat 새로운 기능 개발 labels Jan 7, 2026
@1winhyun 1winhyun linked an issue Jan 7, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

Walkthrough

학생회 게시물의 councilType 기반 조회·예정 이벤트 조회 및 사용자 대상 활성 제휴 조회 기능이 추가되고, 캠퍼스 닉네임 관리(조회·수정) 관련 DTO·매퍼·서비스·레포지토리·컨트롤러·예외·응답코드가 함께 추가·확장되었습니다.

Changes

Cohort / File(s) 변경 내용
학생회 게시물 응답 DTO
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetActivePartnershipListForUserResponse.java, .../GetPostListForCouncilResponse.java, .../GetUpcomingEventListForCouncilResponse.java
세 개의 새로운 public Java record 추가(각 필드에 Swagger @Schema 주석 포함)
학생회 게시물 매퍼
src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java
import 추가 및 매핑 메서드 변경·추가: toPostListItemResponse 파라미터 변경(currentUserId → councilId), toPostResponsecurrentUserId 파라미터 추가, 신규 매핑 메서드 3개 추가
학생회 게시물 서비스
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
councilId/councilType 기반 게시물 조회·예정 이벤트 조회 메서드 추가(기존 범용 메서드 대체), 정렬·필터링·매핑 대상 DTO 변경
학생회 게시물 저장소
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
기존 예정 이벤트 쿼리 수정 및 findPostsByCouncilAndFilters, findRandomActivePartnerships 등 쿼리 추가, @EntityGraph 적용
학생회 게시물 컨트롤러
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java
클래스 레벨 보안·경로 조정, 엔드포인트 시그니처·반환 타입 변경(카운슬 타입/필수 councilId 추가) 및 서비스 호출 경로 업데이트
사용자 요청/응답 DTO
src/main/java/com/campus/campus/domain/user/application/dto/request/CampusNicknameUpdateRequest.java, .../UserWithdrawRequest.java, .../response/UserInfoResponse.java
CampusNicknameUpdateRequest 레코드 추가(campusNickname, @NotBlank, @Schema). UserWithdrawRequest 주석/검증 메시지 변경("닉네임" → "카카오 이름"). UserInfoResponse 레코드 추가
사용자 도메인 및 저장소
src/main/java/com/campus/campus/domain/user/domain/entity/User.java, .../domain/repository/UserRepository.java
User 엔티티에 campusNickname 필드 및 updateCampusNickname 추가. UserRepositoryexistsByCampusNicknameAndIdNot(String, Long) 추가
사용자 서비스 및 매퍼
src/main/java/com/campus/campus/domain/user/application/service/UserService.java, .../mapper/UserMapper.java
updateCampusNickname(중복검사 포함, 트랜잭션) 및 getUserInfo 추가. UserMappertoUserInfoResponse(User) 추가
사용자 컨트롤러 및 응답 코드
src/main/java/com/campus/campus/domain/user/presentation/UserController.java, .../UserResponseCode.java
PATCH /users/change/nickname 및 GET /users 엔드포인트 추가. 응답코드 NICKNAME_UPDATE_SUCCESS, GET_USER_INFO_SUCCESS 추가
사용자 예외 처리
src/main/java/com/campus/campus/domain/user/application/exception/ErrorCode.java, .../NicknameAlreadyExistsException.java
ErrorCodeNICKNAME_ALREADY_EXISTS 추가. NicknameAlreadyExistsException 신규 클래스 추가
글로벌 인증 DTO/매퍼 조정
src/main/java/com/campus/campus/global/auth/application/dto/OauthLoginResponse.java, .../mapper/LoginMapper.java
OauthLoginResponsecampusNickname 필드 추가(레코드 컴포넌트 순서 변경). LoginMapper에서 응답 생성 시 user.getCampusNickname() 전달하도록 업데이트
사용자용 학생회 게시물(Controller/Service) 리네이밍·확장
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java, .../presentation/StudentCouncilPostForUserController.java
클래스명·경로·보안 변경, findActivePartnershipForUser 추가 및 GET /partnerships/active 엔드포인트 추가

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Controller as StudentCouncilPostController
  participant Service as StudentCouncilPostService
  participant Repo as StudentCouncilPostRepository
  participant DB as Database

  Client->>Controller: GET /student-councils/posts?councilType=&category=&page=&size
  Controller->>Service: findPostListForCouncil(councilId, category, page, size)
  Service->>Repo: findPostsByCouncilAndFilters(councilId, category, now, pageable)
  Repo->>DB: SELECT ... JOIN writer, school, college, major WHERE filters ORDER BY ...
  DB-->>Repo: rows
  Repo-->>Service: Page<StudentCouncilPost>
  Service->>Controller: Page<GetPostListForCouncilResponse> (mapped)
  Controller->>Client: 200 OK (page payload)
Loading
sequenceDiagram
  participant Client
  participant Controller as UserController
  participant Service as UserService
  participant Repo as UserRepository
  participant DB as Database

  Client->>Controller: PATCH /users/change/nickname {campusNickname}
  Controller->>Service: updateCampusNickname(userId, request)
  Service->>Repo: existsByCampusNicknameAndIdNot(campusNickname, userId)
  Repo->>DB: SELECT EXISTS ...
  DB-->>Repo: false
  Repo-->>Service: false
  Service->>Repo: save(updated User)
  Repo->>DB: UPDATE users SET campus_nickname=...
  DB-->>Repo: OK
  Repo-->>Service: saved User
  Service->>Controller: void
  Controller->>Client: 200 OK (NICKNAME_UPDATE_SUCCESS)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • jjaeroong
  • 1224kang

Poem

🐰 당근 주머니에 새 DTO 한 줌,
매퍼가 잎을 정리하고 서비스가 흙을 만져요,
컨트롤러가 길을 내어 응답이 달려오고,
닉네임은 꽃피고 제휴는 뛰놀며,
토끼는 기뻐서 콩닥콩닥, 배포 축하! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 '일반 유저 홈화면 조회 기능 구현'으로, 변경 사항의 주요 내용(사용자 정보 조회, 제휴 및 행사 기능, 닉네임 변경 등)을 정확하게 요약하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 6

🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostListForCouncilResponse.java:
- Around line 23-24: The field name DateTime violates Java naming conventions;
rename the field in GetPostListForCouncilResponse from DateTime to a camelCase
name such as dateTime (or eventDateTime if clearer), update any
usages/constructors/getters/setters/serializers referencing DateTime, and adjust
the @Schema description to close the parenthesis (e.g., "시간(끝나는 시간 or 행사날짜)").
Ensure all references to the old symbol DateTime are updated to the new field
name to keep compilation and serialization consistent.

In
@src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetUpcomingEventListForCouncilResponse.java:
- Around line 23-24: In GetUpcomingEventListForCouncilResponse rename the field
DateTime to follow Java naming conventions (use camelCase, e.g., dateTime or
preferably endDateTime to match PostListItemResponse for consistency) and update
its @Schema description to close the missing parenthesis and explicitly state
whether this represents the event end time or the event date/time (for example:
"이벤트 종료 시간 (행사 종료 시각), 예: 2026-01-10T18:00:00"). Ensure you update all
references to the old symbol DateTime in the class and any consumers to the new
field name.

In
@src/main/java/com/campus/campus/domain/user/application/mapper/UserMapper.java:
- Around line 31-39: The mapper assumes school/college/major are non-null and
will NPE for users with incomplete profiles; update mapping to guard against
nulls by checking user.isProfileNotCompleted() in getUserInfo() before calling
toUserInfoResponse() or add null-safe logic inside toUserInfoResponse() and
toUserFirstProfileResponse() (e.g., use conditional expressions to return
null/empty strings when user.getSchool(), user.getCollege(), or user.getMajor()
are null and keep campusNickname fallback logic intact) so the methods no longer
dereference nullable fields.

In
@src/main/java/com/campus/campus/domain/user/application/service/UserService.java:
- Around line 74-79: getUserInfo currently uses userRepository.findById which
returns soft-deleted users, while updateCampusNickname uses
findByIdAndDeletedAtIsNull; change getUserInfo to use the same non-deleted
lookup (e.g.,
userRepository.findByIdAndDeletedAtIsNull(userId).orElseThrow(UserNotFoundException::new))
or add/use an equivalent repository method to filter out soft-deleted records so
both getUserInfo and updateCampusNickname behave consistently.

In @src/main/java/com/campus/campus/domain/user/domain/entity/User.java:
- Around line 49-50: The @Column name for the field campusNickname uses a hyphen
("campus-nickname") which can break SQL identifiers; update the @Column
annotation on the User.campusNickname field to use a safe snake_case name (e.g.,
"campus_nickname") and run or prepare a corresponding database migration to
rename the existing column, ensuring any JPQL/SQL references or native queries
are updated to the new column name.

In
@src/main/java/com/campus/campus/domain/user/domain/repository/UserRepository.java:
- Line 20: The repository method existsByNicknameAndIdNot is incorrectly mapped
to User.nickname; change it to existsByCampusNicknameAndIdNot so Spring Data JPA
queries User.campusNickname instead, update the method signature in
UserRepository (and its parameter name from campusNickname to campusNickname or
similar) to return boolean existsByCampusNicknameAndIdNot(String campusNickname,
Long userId), and run tests to ensure campus nickname duplication checks now
target User.campusNickname rather than User.nickname.
🧹 Nitpick comments (7)
src/main/java/com/campus/campus/domain/user/domain/entity/User.java (1)

73-75: 입력 검증 추가를 고려해보세요.

updateCampusNickname 메서드가 유효성 검사 없이 값을 직접 할당합니다. null 체크나 길이 제한 등의 기본 검증을 추가하면 데이터 무결성을 향상시킬 수 있습니다.

💡 검증 로직 추가 예시
 	public void updateCampusNickname(String campusNickname) {
+		if (campusNickname == null || campusNickname.isBlank()) {
+			throw new IllegalArgumentException("캠퍼스 닉네임은 비어있을 수 없습니다.");
+		}
 		this.campusNickname = campusNickname;
 	}
src/main/java/com/campus/campus/domain/user/application/dto/request/CampusNicknameUpdateRequest.java (1)

6-9: 닉네임 길이 제약 조건을 추가하는 것을 고려하세요.

현재 @NotBlank만 사용하여 빈 문자열을 방지하고 있지만, 닉네임의 최소/최대 길이 제한이 없습니다. 악의적인 사용자가 과도하게 긴 닉네임을 설정하거나, 너무 짧은 닉네임을 설정하는 것을 방지하기 위해 @Size 어노테이션 추가를 권장합니다.

♻️ 제안하는 개선 방안
 public record CampusNicknameUpdateRequest(
 	@Schema(description = "서비스 내 닉네임", example = "캠퍼스러버")
 	@NotBlank
+	@Size(min = 2, max = 20, message = "닉네임은 2자 이상 20자 이하여야 합니다.")
 	String campusNickname
 ) {
 }
src/main/java/com/campus/campus/domain/user/application/dto/response/UserInfoResponse.java (1)

9-10: API 문서의 명확성을 개선하세요.

Line 9의 설명이 비공식적이고 불명확합니다. 또한 example 속성이 누락되어 있어 API 문서 사용자가 예상 값을 이해하기 어렵습니다.

♻️ 제안하는 개선 방안
-	@Schema(description = "유저 닉네임(campusNickname 설정했으면 campusNickname, 아니면, nickname")
+	@Schema(description = "유저 닉네임 (캠퍼스 닉네임이 설정된 경우 캠퍼스 닉네임을, 그렇지 않으면 카카오 닉네임을 반환)", example = "캠퍼스러버")
 	String nickname,
src/main/java/com/campus/campus/domain/user/presentation/UserController.java (1)

36-43: 엔드포인트 경로 구조 개선 고려

현재 경로 /change/nickname은 동작을 나타내지만, RESTful 설계 관점에서 /nickname과 같이 리소스 중심 경로를 고려해볼 수 있습니다. PATCH 메서드 자체가 이미 '변경' 의미를 내포하고 있습니다.

♻️ 선택적 개선안
-	@PatchMapping("/change/nickname")
+	@PatchMapping("/nickname")
src/main/java/com/campus/campus/domain/user/application/service/UserService.java (1)

61-72: 명시적 save() 호출 검토

@Transactional 컨텍스트에서 JPA의 더티 체킹(dirty checking)이 자동으로 변경사항을 감지하여 저장하므로, Line 71의 명시적 save() 호출은 기술적으로 불필요할 수 있습니다. 다만 명시적 저장은 코드의 의도를 더 명확하게 표현하므로 현재 구현도 유효한 선택입니다.

src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (1)

72-89: function('RAND')는 MySQL 전용 함수입니다.

ORDER BY function('RAND')는 MySQL에서만 동작합니다. PostgreSQL은 RANDOM(), Oracle은 DBMS_RANDOM.VALUE를 사용합니다. 현재 프로젝트가 MySQL만 사용한다면 문제없지만, 다른 DB로 마이그레이션 시 수정이 필요합니다.

또한, 대용량 데이터에서 RAND()를 사용한 정렬은 성능 이슈가 발생할 수 있으니 데이터 규모를 고려해 주세요.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (1)

167-170: 하드코딩된 페이지 크기를 상수로 추출하는 것을 고려해 주세요.

PageRequest.of(0, 3)3이 하드코딩되어 있습니다. MAX_IMAGE_COUNTUPCOMING_EVENT_WINDOW_HOURS처럼 상수로 정의하면 유지보수성이 향상됩니다.

♻️ 개선 제안
 private static final int MAX_IMAGE_COUNT = 10;
 private static final long UPCOMING_EVENT_WINDOW_HOURS = 72L;
+private static final int ACTIVE_PARTNERSHIP_LIMIT = 3;
-Pageable partnershipCount = PageRequest.of(0, 3);
+Pageable partnershipCount = PageRequest.of(0, ACTIVE_PARTNERSHIP_LIMIT);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0985482 and 3fce97f.

📒 Files selected for processing (19)
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetActivePartnershipListForUserResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostListForCouncilResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetUpcomingEventListForCouncilResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
  • src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java
  • src/main/java/com/campus/campus/domain/user/application/dto/request/CampusNicknameUpdateRequest.java
  • src/main/java/com/campus/campus/domain/user/application/dto/request/UserWithdrawRequest.java
  • src/main/java/com/campus/campus/domain/user/application/dto/response/UserInfoResponse.java
  • src/main/java/com/campus/campus/domain/user/application/exception/ErrorCode.java
  • src/main/java/com/campus/campus/domain/user/application/exception/NicknameAlreadyExistsException.java
  • src/main/java/com/campus/campus/domain/user/application/mapper/UserMapper.java
  • src/main/java/com/campus/campus/domain/user/application/service/UserService.java
  • src/main/java/com/campus/campus/domain/user/domain/entity/User.java
  • src/main/java/com/campus/campus/domain/user/domain/repository/UserRepository.java
  • src/main/java/com/campus/campus/domain/user/presentation/AuthController.java
  • src/main/java/com/campus/campus/domain/user/presentation/UserController.java
  • src/main/java/com/campus/campus/domain/user/presentation/UserResponseCode.java
🧰 Additional context used
🧬 Code graph analysis (8)
src/main/java/com/campus/campus/domain/user/presentation/AuthController.java (1)
src/main/java/com/campus/campus/domain/user/application/service/KakaoOauthService.java (2)
  • Transactional (64-75)
  • Service (30-136)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetUpcomingEventListForCouncilResponse.java (3)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java (1)
  • PostListItemResponse (8-18)
src/main/java/com/campus/campus/domain/councilnotice/application/dto/response/NoticeListItemResponse.java (1)
  • Builder (7-15)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/NormalizedDateTime.java (1)
  • NormalizedDateTime (5-9)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetActivePartnershipListForUserResponse.java (4)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java (1)
  • PostListItemResponse (8-18)
src/main/java/com/campus/campus/domain/manager/application/dto/response/CouncilApproveOrDenyResponse.java (1)
  • CouncilApproveOrDenyResponse (5-12)
src/main/java/com/campus/campus/domain/school/application/dto/response/CollegeFindResponse.java (1)
  • CollegeFindResponse (5-12)
src/main/java/com/campus/campus/domain/user/application/dto/response/UserFirstProfileResponse.java (1)
  • Builder (6-17)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (1)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/StudentCouncilPost.java (1)
  • Entity (25-98)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostListForCouncilResponse.java (2)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java (1)
  • PostListItemResponse (8-18)
src/main/java/com/campus/campus/domain/councilpost/application/dto/request/PostRequest.java (1)
  • PostRequest (14-42)
src/main/java/com/campus/campus/domain/user/application/dto/request/CampusNicknameUpdateRequest.java (9)
src/main/java/com/campus/campus/domain/council/application/dto/request/StudentCouncilChangePasswordRequest.java (1)
  • StudentCouncilChangePasswordRequest (9-24)
src/main/java/com/campus/campus/domain/council/application/dto/request/StudentCouncilChangeEmailRequest.java (1)
  • StudentCouncilChangeEmailRequest (6-11)
src/main/java/com/campus/campus/domain/mail/application/dto/request/EmailVerificationRequest.java (1)
  • EmailVerificationRequest (6-11)
src/main/java/com/campus/campus/domain/user/application/dto/request/UserProfileRequest.java (1)
  • UserProfileRequest (5-12)
src/main/java/com/campus/campus/domain/council/application/dto/request/StudentCouncilSignUpRequest.java (1)
  • StudentCouncilSignUpRequest (11-44)
src/main/java/com/campus/campus/global/util/jwt/application/dto/request/TokenReissueRequest.java (1)
  • TokenReissueRequest (6-11)
src/main/java/com/campus/campus/domain/council/application/dto/request/StudentCouncilWithdrawRequest.java (1)
  • StudentCouncilWithdrawRequest (6-14)
src/main/java/com/campus/campus/domain/manager/application/dto/request/ManagerLoginRequest.java (1)
  • ManagerLoginRequest (6-15)
src/main/java/com/campus/campus/domain/manager/application/dto/request/CouncilApproveOrDenyRequest.java (1)
  • CouncilApproveOrDenyRequest (6-11)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (2)
src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
  • UserNotFoundException (5-9)
src/main/java/com/campus/campus/domain/council/application/exception/StudentCouncilNotFoundException.java (1)
  • StudentCouncilNotFoundException (5-9)
src/main/java/com/campus/campus/domain/user/application/service/UserService.java (2)
src/main/java/com/campus/campus/domain/user/application/exception/NicknameAlreadyExistsException.java (1)
  • NicknameAlreadyExistsException (5-9)
src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
  • UserNotFoundException (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (14)
src/main/java/com/campus/campus/domain/user/presentation/AuthController.java (1)

38-38: 변경 사항이 적절합니다.

DTO 필드명이 nickname에서 kakaoName으로 변경되어 의도가 더 명확해졌습니다. KakaoOauthService.withdraw 메서드가 검증하는 user.getNickname()은 실제로 카카오 이름을 저장하고 있으므로 로직상 문제가 없습니다.

src/main/java/com/campus/campus/domain/user/application/exception/ErrorCode.java (1)

16-17: 에러 코드 추가가 적절합니다.

닉네임 중복 검증을 위한 새로운 에러 코드가 올바르게 추가되었습니다. HTTP 상태 코드 CONFLICT(409)는 중복 리소스를 나타내는 데 적합하며, 에러 메시지도 명확합니다.

src/main/java/com/campus/campus/domain/user/application/dto/request/UserWithdrawRequest.java (1)

7-9: 필드명 변경이 의도를 명확하게 합니다.

nickname에서 kakaoName으로의 필드명 변경은 회원 탈퇴 검증 시 카카오 이름을 사용한다는 의도를 더 명확하게 전달합니다. 스키마 설명과 검증 메시지도 일관되게 업데이트되었습니다.

src/main/java/com/campus/campus/domain/user/application/exception/NicknameAlreadyExistsException.java (1)

1-9: LGTM!

예외 클래스가 기존 패턴과 일관되게 구현되어 있으며, ApplicationException을 올바르게 확장하고 있습니다.

src/main/java/com/campus/campus/domain/user/presentation/UserController.java (1)

45-51: 잘 구현되었습니다!

사용자 정보 조회 엔드포인트가 간결하고 명확하게 구현되었습니다.

src/main/java/com/campus/campus/domain/user/presentation/UserResponseCode.java (1)

15-17: 응답 코드 추가가 적절합니다!

새로운 기능에 대한 응답 코드가 명확하게 정의되었습니다.

src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetActivePartnershipListForUserResponse.java (1)

5-17: 깔끔하게 구현되었습니다!

DTO가 명확하고 적절하게 정의되었습니다. Swagger 문서화도 잘 되어 있습니다.

src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java (1)

40-70: LGTM! 새로운 매퍼 메서드들이 적절히 구현되었습니다.

각 응답 DTO에 맞게 필드를 정확히 매핑하고 있으며, 기존 코드 스타일과 일관성을 유지하고 있습니다.

src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (1)

57-62: 조건 로직이 정확합니다.

EVENT는 startDateTime >= :now로, PARTNERSHIP은 endDateTime >= :now로 필터링하여 아직 진행 중이거나 예정된 게시글만 조회하는 의도가 잘 반영되었습니다.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (2)

158-175: 일반 유저용 제휴 조회 로직이 잘 구현되었습니다.

사용자의 학교 ID를 기반으로 해당 학교의 활성 제휴만 조회하도록 구현되어 있으며, 랜덤 정렬로 다양한 제휴를 노출하는 UX 의도가 잘 반영되었습니다.


120-125: category가 null일 경우 정렬 기준을 확인해 주세요.

categorynull일 때 else 분기로 endDateTime 정렬이 적용됩니다. 이 경우 EVENT와 PARTNERSHIP이 혼합되어 정렬되는데, 의도된 동작인지 확인이 필요합니다.

src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java (3)

181-189: 일반 유저 엔드포인트에 접근 제어가 없습니다.

getActivePartnershipListForUser@PreAuthorize 어노테이션이 없습니다. @CurrentUserId를 사용하므로 인증된 사용자만 접근 가능하겠지만, 명시적으로 @PreAuthorize("hasRole('USER')") 또는 @PreAuthorize("isAuthenticated()")를 추가하면 보안 의도가 더 명확해집니다.

의도적으로 생략한 것이라면 무시해 주세요.


150-164: 학생회 전용 엔드포인트 보안이 적절히 적용되었습니다.

@PreAuthorize("hasRole('COUNCIL')")@CurrentCouncilId를 함께 사용하여 학생회 계정만 접근할 수 있도록 잘 설정되었습니다.


166-179: 72시간 이내 행사 조회 엔드포인트가 잘 구현되었습니다.

councilType을 선택적 파라미터로 받아 필터링할 수 있도록 구현되어 있으며, 페이지네이션도 적절히 적용되었습니다.

Comment on lines +31 to +39
public UserInfoResponse toUserInfoResponse(User user) {
return new UserInfoResponse(
user.getId(),
user.getCampusNickname() == null ? user.getNickname() : user.getCampusNickname(),
user.getSchool().getSchoolName(),
user.getCollege().getCollegeName(),
user.getMajor().getMajorName()
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "User.java" | grep -E "domain|entity" | head -20

Repository: our-campUS/our-campUS-BE

Length of output: 137


🏁 Script executed:

cat -n ./src/main/java/com/campus/campus/domain/user/domain/entity/User.java

Repository: our-campUS/our-campUS-BE

Length of output: 2820


🏁 Script executed:

find . -type f -name "UserService.java"

Repository: our-campUS/our-campUS-BE

Length of output: 150


🏁 Script executed:

cat -n ./src/main/java/com/campus/campus/domain/user/application/service/UserService.java

Repository: our-campUS/our-campUS-BE

Length of output: 4000


🏁 Script executed:

cat -n ./src/main/java/com/campus/campus/domain/user/application/mapper/UserMapper.java

Repository: our-campUS/our-campUS-BE

Length of output: 1600


🏁 Script executed:

rg "toUserInfoResponse|getUserInfo" --type java -B 2 -A 2

Repository: our-campUS/our-campUS-BE

Length of output: 3440


프로필 미완료 사용자에 대한 NPE 방지 필요

getUserInfo() 메서드는 프로필 완료 여부를 검증하지 않고 직접 toUserInfoResponse()를 호출합니다. User 엔티티의 school, college, major 필드는 @NotNull 제약이 없으므로 nullable이며, user.isProfileNotCompleted() 메서드가 이를 명시적으로 확인하고 있습니다. 프로필 미완료 상태의 사용자가 이 메서드를 호출하면 NPE가 발생합니다.

권장 사항:

  1. getUserInfo() 메서드에서 프로필 완료 여부를 검증한 후 매핑하거나
  2. toUserInfoResponse() 메서드에 null 체크를 추가하세요

toUserFirstProfileResponse() 메서드도 동일한 문제를 가지고 있습니다.

🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/user/application/mapper/UserMapper.java
around lines 31 - 39, The mapper assumes school/college/major are non-null and
will NPE for users with incomplete profiles; update mapping to guard against
nulls by checking user.isProfileNotCompleted() in getUserInfo() before calling
toUserInfoResponse() or add null-safe logic inside toUserInfoResponse() and
toUserFirstProfileResponse() (e.g., use conditional expressions to return
null/empty strings when user.getSchool(), user.getCollege(), or user.getMajor()
are null and keep campusNickname fallback logic intact) so the methods no longer
dereference nullable fields.

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

🧹 Nitpick comments (2)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (2)

56-82: JPQL 쿼리 스타일 및 조건 로직 검토 권장

두 가지 사항을 확인해주세요:

  1. JPQL에서 완전한 패키지명 사용 (Lines 68, 70): com.campus.campus.domain.councilpost.domain.entity.PostCategory.EVENT 대신 PostCategory.EVENT와 같이 간단한 형태를 사용하는 것이 일반적입니다.

  2. category가 NULL일 때의 동작: Lines 67-72의 조건식은 category가 NULL이면 EVENT 조건 또는 PARTNERSHIP 조건을 만족하는 모든 게시물을 반환합니다. 이것이 의도된 동작인지 확인이 필요합니다.

♻️ 제안: JPQL 쿼리 간소화
   AND (:category IS NULL OR p.category = :category)
   AND (
-    (p.category = com.campus.campus.domain.councilpost.domain.entity.PostCategory.EVENT
+    (p.category = 'EVENT'
       AND p.startDateTime >= :now)
-    OR (p.category = com.campus.campus.domain.councilpost.domain.entity.PostCategory.PARTNERSHIP
+    OR (p.category = 'PARTNERSHIP'
       AND p.endDateTime >= :now)
   )

또는 enum import 후:

import static com.campus.campus.domain.councilpost.domain.entity.PostCategory.*;
-    (p.category = com.campus.campus.domain.councilpost.domain.entity.PostCategory.EVENT
+    (p.category = EVENT

84-107: function('RAND') 함수는 MySQL 전용입니다

Line 97의 function('RAND')는 MySQL/MariaDB 전용 문법입니다. 현재 프로젝트는 MySQL을 사용하고 있으므로 정상 작동하지만, 향후 다른 데이터베이스로 마이그레이션을 고려한다면 데이터베이스 독립적인 방식으로 개선하는 것이 좋습니다.

데이터베이스 독립적인 대안

옵션 1: 애플리케이션 레벨에서 랜덤 처리

// 조건을 만족하는 데이터를 조회한 후 Java에서 셔플
List<StudentCouncilPost> all = repository.findActivePartnerships(...);
Collections.shuffle(all);
return all.stream().limit(pageSize).collect(toList());

옵션 2: Native query 사용 (현재 DB 의존적이지만 명시적)
프로젝트가 MySQL만 사용할 예정이라면 nativeQuery=true 옵션으로 명시적으로 표기하는 것도 고려할 수 있습니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fce97f and 42d2727.

📒 Files selected for processing (3)
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
  • src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
  • src/main/java/com/campus/campus/domain/user/application/dto/request/UserWithdrawRequest.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/campus/campus/domain/user/application/dto/request/UserWithdrawRequest.java (2)
src/main/java/com/campus/campus/domain/user/application/service/KakaoOauthService.java (1)
  • Transactional (64-75)
src/main/java/com/campus/campus/domain/user/domain/entity/User.java (1)
  • Entity (25-77)
🔇 Additional comments (7)
src/main/java/com/campus/campus/domain/user/application/dto/request/UserWithdrawRequest.java (1)

6-9: 코드를 검토한 결과, 이 리뷰 의견은 수정이 필요하지 않습니다.

관찰 내용:

  • User 엔티티의 nickname 필드(컬럼명: "name")는 카카오 이름을 저장합니다
  • campusNickname 필드는 User 엔티티(라인 50)에 실제로 존재합니다
  • KakaoOauthService.withdraw()는 user.getNickname().equals(nickname)으로 카카오 이름을 비교합니다
  • 파라미터 이름 nickname은 도메인 모델의 User.nickname 필드와 의미상 일치합니다

파라미터 이름은 일관성이 있습니다. User 엔티티에서 Kakao 이름을 저장하는 필드가 nickname이므로, UserWithdrawRequest의 파라미터명도 nickname으로 유지하는 것이 도메인 모델과 일치합니다. 이를 kakaoName으로 변경하면 KakaoOauthService.withdraw() 메서드 시그니처도 변경되어야 하며, 오히려 일관성이 깨질 수 있습니다.

annotation 문서화 변경은 적절합니다. "닉네임"에서 "카카오 이름"으로 변경한 것은 필드의 용도를 명확히 하는 좋은 개선입니다.

src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (2)

4-4: LGTM!

새로운 기능 구현에 필요한 import가 적절하게 추가되었습니다.

Also applies to: 9-9, 14-14


30-54: 다가오는 이벤트 조회 쿼리가 잘 구현되었습니다.

EntityGraph를 사용하여 N+1 문제를 방지하고, 학교/학생회 타입/단과대/학과 필터링이 적절하게 구현되었습니다. 72시간 이내 이벤트를 시작 시간 순으로 조회하는 로직이 정확합니다.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (4)

49-49: LGTM!

유저 정보 기반 제휴 조회 기능을 위한 UserRepository 의존성이 적절하게 추가되었습니다.


111-143: 학생회 타입별 게시물 조회 로직이 잘 구현되었습니다.

CouncilType에 따른 collegeId/majorId 필터링 로직이 정확하며, 카테고리별 정렬 기준(EVENT는 시작일, PARTNERSHIP은 종료일) 설정이 적절합니다. 페이지 인덱스 조정(Math.max(page - 1, 0))도 올바르게 처리되었습니다.


146-172: 다가오는 이벤트 조회 기능이 올바르게 구현되었습니다.

72시간 이내 이벤트를 학생회 타입별로 필터링하는 로직이 적절하며, 이전 메서드와 일관된 패턴으로 collegeId/majorId를 추출합니다. 상수 UPCOMING_EVENT_WINDOW_HOURS를 사용하여 시간 윈도우를 관리하는 것도 좋습니다.


174-211: 일반 유저 대상 활성 제휴 조회 기능이 안전하게 구현되었습니다.

유저의 학교/단과대/학과 정보가 없는 경우 빈 리스트를 반환하는 방어 로직이 잘 되어 있습니다. CouncilType별로 필요한 연관관계를 검증하고, 조건을 만족할 때만 쿼리를 실행하는 것이 효율적입니다. 3개의 랜덤 제휴를 조회하는 비즈니스 로직도 명확합니다.

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

🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostListForCouncilResponse.java:
- Around line 23-24: Fix the @Schema description on the LocalDateTime field in
GetPostListForCouncilResponse: close the missing parenthesis and make the text
unambiguous (e.g., "시간(종료 시간 또는 행사 날짜)", or similar wording that explicitly
states whether this is the end time or the event date), keeping the example
value unchanged; update the annotation on the LocalDateTime dateTime field so
the description is grammatically correct and clearly describes what the field
represents.
- Around line 17-18: The Schema annotation for the title field in
GetPostListForCouncilResponse has a typo in the example text ("중간고사 간식생사");
update the example to the correct text "중간고사 간식행사" in the @Schema(description =
"게시글 이름", example = "...") for the String title field to fix the spelling.

In
@src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetUpcomingEventListForCouncilResponse.java:
- Around line 17-18: Fix the typo in the Swagger example for the title field:
change the @Schema example string on the title field in
GetUpcomingEventListForCouncilResponse from "중간고사 간식생사" to "중간고사 간식행사" so the
example accurately reflects the intended text.
- Around line 23-24: Fix the @Schema description for the LocalDateTime field in
GetUpcomingEventListForCouncilResponse: close the missing parenthesis and
clarify the meaning (e.g., "시간(종료 시간 또는 행사 날짜)" or "종료 시간 또는 행사 날짜를 나타내는 시간") so
the description is grammatically correct and unambiguous for the field dateTime;
keep the example value unchanged.
- Around line 10-28: GetUpcomingEventListForCouncilResponse is missing the
thumbnailImageUrl field present in GetPostListForCouncilResponse; check the
mapper that currently excludes this field and either add thumbnailImageUrl to
GetUpcomingEventListForCouncilResponse (with appropriate @Schema and type, e.g.,
String) and map it in the mapper, or explicitly document/justify its omission.
Update the DTO declaration (GetUpcomingEventListForCouncilResponse) to include
thumbnailImageUrl and modify the mapper code that builds this record to pass the
thumbnail URL from the source post, ensuring field names/types match the
existing GetPostListForCouncilResponse.

In @src/main/java/com/campus/campus/domain/user/domain/entity/User.java:
- Around line 49-50: The campusNickname field in the User entity lacks a length
constraint; update the @Column on the campusNickname field in class User to
include a sensible length (e.g., length = 50) to enforce column size at the JPA
level, and verify the database schema includes/needs a UNIQUE constraint for
campusNickname to match the application-level uniqueness check performed by
existsByCampusNicknameAndIdNot.

In
@src/main/java/com/campus/campus/global/auth/application/dto/OauthLoginResponse.java:
- Around line 15-16: OauthLoginResponse currently exposes campusNickname without
null handling; update LoginMapper.toOauthLoginResponse() to apply the same
fallback as UserMapper.toUserInfoResponse() by returning
user.getCampusNickname() == null ? user.getNickname() : user.getCampusNickname()
for the campusNickname field so the frontend never receives null when a campus
nickname is unset.

In
@src/main/java/com/campus/campus/global/auth/application/mapper/LoginMapper.java:
- Line 20: LoginMapper currently maps campusNickname by passing
user.getCampusNickname() directly which can produce null in responses; change
the mapping to use the same fallback logic as UserMapper by assigning
campusNickname = user.getCampusNickname() == null ? user.getNickname() :
user.getCampusNickname() when building the UserInfoResponse (refer to
LoginMapper and UserMapper and UserInfoResponse schema).
🧹 Nitpick comments (2)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostListForCouncilResponse.java (1)

10-31: 필드 명명 일관성을 고려하세요.

기존 PostListItemResponse에서는 idendDateTime을 사용하는 반면, 이 DTO는 postIddateTime을 사용합니다. 두 DTO가 서로 다른 용도로 사용되는 경우 차이가 의도적일 수 있지만, 유사한 개념에 대해 일관된 명명을 사용하면 코드베이스의 가독성과 유지보수성이 향상됩니다.

src/main/java/com/campus/campus/domain/user/domain/entity/User.java (1)

73-75: updateCampusNickname 메서드에 유효성 검증이 필요합니다.

닉네임 업데이트 메서드에 입력값 검증이 없습니다. null, 빈 문자열, 공백만 있는 문자열 등의 검증이 필요합니다.

♻️ 제안하는 개선사항

도메인 엔티티에서 기본적인 null 체크를 추가하거나, 서비스 계층에서 완전한 유효성 검증을 수행하는 것을 권장합니다:

 	public void updateCampusNickname(String campusNickname) {
+		if (campusNickname == null || campusNickname.isBlank()) {
+			throw new IllegalArgumentException("Campus nickname cannot be null or blank");
+		}
 		this.campusNickname = campusNickname;
 	}

또는 서비스 계층에서 검증하는 경우, 해당 로직이 올바르게 구현되었는지 확인하세요.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42d2727 and 1dee857.

📒 Files selected for processing (7)
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostListForCouncilResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetUpcomingEventListForCouncilResponse.java
  • src/main/java/com/campus/campus/domain/user/application/service/UserService.java
  • src/main/java/com/campus/campus/domain/user/domain/entity/User.java
  • src/main/java/com/campus/campus/domain/user/domain/repository/UserRepository.java
  • src/main/java/com/campus/campus/global/auth/application/dto/OauthLoginResponse.java
  • src/main/java/com/campus/campus/global/auth/application/mapper/LoginMapper.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/campus/campus/domain/user/application/service/UserService.java
  • src/main/java/com/campus/campus/domain/user/domain/repository/UserRepository.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetUpcomingEventListForCouncilResponse.java (1)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java (1)
  • PostListItemResponse (8-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 8, 2026
@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 8, 2026
@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 8, 2026
@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 8, 2026
@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 8, 2026
@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 8, 2026
@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 8, 2026
@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 8, 2026
@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 8, 2026
… 어노테이션 클래스 범위로 수정, 학생회 전용 service, controller에서 특정 메서드 유저전용으로 이동
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: 2

🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java:
- Line 206: In StudentCouncilPostForUserService replace the timezone-naive
LocalDateTime.now() instantiation (the variable "now" at the creation site) with
the KST-aware call used elsewhere (LocalDateTime.now(KST)) so the method uses
the same timezone as the other methods (Lines with LocalDateTime.now(KST) at 91,
119, 147); update the reference to "now" accordingly so all time
comparisons/uses in this class are consistent and timezone-aware.

In
@src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java:
- Line 48: Remove the unused injected UserRepository field from
StudentCouncilPostService: delete the private final UserRepository
userRepository declaration and remove it from the constructor parameters and
assignments (constructor for class StudentCouncilPostService and any related
field initialization), ensuring no remaining references to userRepository exist;
if constructor injection is generated with Lombok (e.g.,
@RequiredArgsConstructor), remove UserRepository from the class fields so it is
no longer part of the generated constructor.
🧹 Nitpick comments (4)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (2)

31-55: @EntityGraphJOIN 조합 시 주의 필요

@EntityGraph와 JPQL JOIN을 함께 사용하면 중복 fetch가 발생할 수 있습니다. @EntityGraph는 이미 writer, writer.school 등을 eager fetch하는데, 쿼리에서도 JOIN p.writer w를 사용하고 있습니다.

@EntityGraph만 사용하고 JPQL에서는 alias만 참조하는 방식이 더 깔끔합니다. 현재 동작에는 문제가 없지만, 일관성을 위해 다른 쿼리들과 패턴을 맞추는 것을 권장합니다.


85-108: RAND() 사용 시 성능 고려

ORDER BY function('RAND')는 전체 결과셋을 스캔한 후 정렬하므로 데이터가 많아지면 성능이 저하될 수 있습니다. 현재는 Pageable로 3개만 가져오지만, 내부적으로는 전체 정렬이 발생합니다.

대안으로 애플리케이션 레벨에서 랜덤 샘플링하거나, 데이터 규모가 커지면 별도 캐싱 전략을 고려해보세요.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (1)

181-182: Repository 메서드 차이 확인

다른 메서드들은 findByIdWithAcademicInfo(userId)를 사용하여 학교/단과대/전공 정보를 eager fetch하지만, 이 메서드는 findByIdAndDeletedAtIsNull(userId)를 사용합니다.

이후 user.getSchool(), user.getCollege(), user.getMajor() 접근 시 lazy loading이 발생할 수 있습니다. 의도된 것이라면 괜찮지만, 일관성과 N+1 방지를 위해 동일한 fetch 전략 사용을 권장합니다.

♻️ 수정 제안
-		User user = userRepository.findByIdAndDeletedAtIsNull(userId)
+		User user = userRepository.findByIdWithAcademicInfo(userId)
 			.orElseThrow(UserNotFoundException::new);
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (1)

136-136: 타임존 미지정 이슈

LocalDateTime.now()가 서버 기본 타임존을 사용합니다. StudentCouncilPostForUserService에서는 LocalDateTime.now(KST)를 사용하므로, 일관성을 위해 동일한 타임존 처리를 적용하는 것을 권장합니다.

♻️ 수정 제안
+	private static final ZoneId KST = ZoneId.of("Asia/Seoul");
+
 	@Transactional(readOnly = true)
 	public Page<GetPostListForCouncilResponse> findPostListByCouncilTypeForCouncil(...) {
 		...
-		LocalDateTime now = LocalDateTime.now();
+		LocalDateTime now = LocalDateTime.now(KST);
 		...
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1dee857 and 36b30ba.

📒 Files selected for processing (6)
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
  • src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java
  • src/main/java/com/campus/campus/domain/user/domain/repository/UserRepository.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/campus/campus/domain/user/domain/repository/UserRepository.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (2)
src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
  • UserNotFoundException (5-9)
src/main/java/com/campus/campus/domain/council/application/exception/StudentCouncilNotFoundException.java (1)
  • StudentCouncilNotFoundException (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (11)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (1)

57-83: 카테고리 필터 로직 검증 필요

Line 67-73의 조건이 복잡합니다:

  • category가 null이 아닐 때 p.category = :category로 필터링
  • 동시에 EVENTstartDateTime >= now, PARTNERSHIP이면 endDateTime >= now 체크

category가 null인 경우, 두 카테고리 모두 포함되지만 각각 다른 시간 조건이 적용됩니다. 이 로직이 의도한 것인지 확인이 필요합니다.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (2)

38-39: 클래스 레벨 트랜잭션 적용 확인

@Transactional(readOnly = true)가 클래스 레벨에 적용되어 모든 메서드가 읽기 전용 트랜잭션으로 실행됩니다. 현재 이 서비스의 모든 메서드가 조회 전용이므로 적절한 변경입니다.


179-215: 새로운 Active Partnership 조회 메서드 구현 확인

findActivePartnershipForUser 메서드가 잘 구현되어 있습니다:

  • 사용자 존재 여부 및 학교 정보 null 체크
  • CouncilType에 따른 적절한 ID 파생
  • 빈 리스트 반환으로 안전한 fallback 처리

타임존 이슈만 수정되면 로직은 올바릅니다.

src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java (2)

28-35: 보안 및 라우팅 변경 확인

클래스 레벨 변경사항이 적절합니다:

  • @PreAuthorize("hasRole('USER')") 추가로 사용자 권한 검증
  • 엔드포인트 경로가 /users/student-council/posts로 명확하게 구분됨
  • 서비스 의존성이 StudentCouncilPostForUserService로 올바르게 연결됨

138-148: 새로운 Active Partnership 엔드포인트 검토

엔드포인트 구현이 깔끔합니다. 다만, councilType 파라미터가 필수(required=true, 기본값)인데, API 사용 편의성을 위해 기본값 설정을 고려해볼 수 있습니다.

현재 필수로 두는 것이 의도된 설계라면 문제없습니다.

src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java (3)

34-35: 클래스 레벨 보안 및 라우팅 변경

@PreAuthorize("hasRole('COUNCIL')")가 클래스 레벨에 적용되어 모든 엔드포인트가 학생회 권한으로 보호됩니다. 경로도 /student-councils/posts로 명확하게 분리되었습니다.


142-155: 학생회 타입별 게시글 목록 조회 엔드포인트

councilType이 optional로 설정되어 유연한 필터링이 가능합니다. 새로운 GetPostListForCouncilResponse DTO를 사용하여 응답 구조가 명확해졌습니다.


157-169: 72시간 이내 행사 조회 엔드포인트

GetUpcomingEventListForCouncilResponse DTO로 응답 타입이 변경되었고, councilType 필터링이 추가되었습니다. 구현이 깔끔합니다.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (3)

127-134: category가 null일 때 정렬 기준 확인

category가 null인 경우 else 분기로 endDateTime ASC 정렬이 적용됩니다. EVENT와 PARTNERSHIP이 혼합된 결과에서 endDateTime 정렬이 의도된 동작인지 확인이 필요합니다.

EVENT 게시글은 endDateTime이 null일 수 있어 정렬 결과가 예상과 다를 수 있습니다.


109-142: findPostListByCouncilTypeForCouncil 메서드 구현 확인

학생회 타입별 게시글 목록 조회 로직이 잘 구현되어 있습니다:

  • StudentCouncil 검증 및 조회
  • CouncilType에 따른 collegeId/majorId 파생
  • 카테고리별 정렬 로직 분기
  • 새로운 DTO 매핑

타임존 및 null category 정렬 이슈만 확인되면 좋겠습니다.


144-171: findUpcomingEventsByCouncilTypeForCouncil 메서드 구현 확인

72시간 이내 행사 조회 로직이 적절하게 구현되어 있습니다. UPCOMING_EVENT_WINDOW_HOURS 상수를 사용하여 시간 창을 관리하고, 새로운 DTO로 매핑합니다.

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: 1

🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java:
- Around line 181-182: The method currently loads the User with
userRepository.findByIdAndDeletedAtIsNull(userId) which is inconsistent with
other methods and can cause lazy-loading/N+1 when accessing
user.getSchool()/getCollege()/getMajor(); change the lookup to use
userRepository.findByIdWithAcademicInfo(userId) and keep the same
orElseThrow(UserNotFoundException::new) behavior so the User is fetched with
academic relations eagerly loaded (update the instantiation at
StudentCouncilPostForUserService where User user = ... is assigned).
🧹 Nitpick comments (3)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (2)

192-204: 반복적인 null 체크 로직을 추출할 수 있습니다.

COLLEGE_COUNCILMAJOR_COUNCIL에 대한 null 체크 및 ID 추출 로직이 유사합니다. 가독성을 위해 헬퍼 메서드로 추출하는 것을 고려해보세요.

♻️ 헬퍼 메서드 추출 제안

클래스에 다음 private 메서드들을 추가하세요:

private Long extractCollegeId(User user, CouncilType councilType) {
	if (CouncilType.COLLEGE_COUNCIL.equals(councilType)) {
		return user.getCollege() != null ? user.getCollege().getCollegeId() : null;
	}
	return null;
}

private Long extractMajorId(User user, CouncilType councilType) {
	if (CouncilType.MAJOR_COUNCIL.equals(councilType)) {
		return user.getMajor() != null ? user.getMajor().getMajorId() : null;
	}
	return null;
}

그런 다음 메서드를 다음과 같이 단순화하세요:

 		Long schoolId = user.getSchool().getSchoolId();
-		Long collegeId = null;
-		Long majorId = null;
-
-		if (CouncilType.COLLEGE_COUNCIL.equals(councilType)) {
-			if (user.getCollege() == null) {
-				return List.of();
-			}
-			collegeId = user.getCollege().getCollegeId();
-		}
-
-		if (CouncilType.MAJOR_COUNCIL.equals(councilType)) {
-			if (user.getMajor() == null) {
-				return List.of();
-			}
-			majorId = user.getMajor().getMajorId();
-		}
+		Long collegeId = extractCollegeId(user, councilType);
+		Long majorId = extractMajorId(user, councilType);
+		
+		if ((CouncilType.COLLEGE_COUNCIL.equals(councilType) && collegeId == null) ||
+			(CouncilType.MAJOR_COUNCIL.equals(councilType) && majorId == null)) {
+			return List.of();
+		}

207-207: 매직 넘버를 상수로 추출하는 것을 고려해보세요.

제휴 조회 개수 3이 하드코딩되어 있습니다. 이를 클래스 레벨 상수로 추출하면 의미가 명확해지고 향후 변경이 용이합니다.

♻️ 상수 추출 제안

클래스 상단에 상수를 추가하세요:

 	private static final int UPCOMING_HOURS = 72;
 	private static final ZoneId KST = ZoneId.of("Asia/Seoul");
+	private static final int ACTIVE_PARTNERSHIP_COUNT = 3;

그런 다음 사용 부분을 업데이트하세요:

-		Pageable partnershipCount = PageRequest.of(0, 3);
+		Pageable partnershipCount = PageRequest.of(0, ACTIVE_PARTNERSHIP_COUNT);
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (1)

140-166: 코드 중복을 제거하여 유지보수성을 개선하세요.

이 메서드의 149-157번 라인은 105-137번 라인 메서드의 112-120번 라인과 동일한 로직을 반복합니다(collegeIdmajorIdCouncilType에 따라 추출). 헬퍼 메서드로 추출하면 중복이 제거되고 일관성이 향상됩니다.

♻️ 공통 로직 추출 제안

클래스에 다음 private 헬퍼 메서드를 추가하세요:

private record CouncilContext(Long schoolId, Long collegeId, Long majorId) {}

private CouncilContext extractCouncilContext(StudentCouncil studentCouncil, CouncilType councilType) {
	Long schoolId = studentCouncil.getSchool().getSchoolId();
	Long collegeId = CouncilType.COLLEGE_COUNCIL.equals(councilType)
		&& studentCouncil.getCollege() != null
		? studentCouncil.getCollege().getCollegeId()
		: null;
	Long majorId = CouncilType.MAJOR_COUNCIL.equals(councilType)
		&& studentCouncil.getMajor() != null
		? studentCouncil.getMajor().getMajorId()
		: null;
	
	return new CouncilContext(schoolId, collegeId, majorId);
}

그런 다음 두 메서드에서 사용하세요:

 		StudentCouncil studentCouncil = studentCouncilRepository
 			.findByIdAndManagerApprovedIsTrueAndDeletedAtIsNull(councilId)
 			.orElseThrow(StudentCouncilNotFoundException::new);
 
-		Long schoolId = studentCouncil.getSchool().getSchoolId();
-		Long collegeId = CouncilType.COLLEGE_COUNCIL.equals(councilType)
-			&& studentCouncil.getCollege() != null
-			? studentCouncil.getCollege().getCollegeId()
-			: null;
-		Long majorId = CouncilType.MAJOR_COUNCIL.equals(councilType)
-			&& studentCouncil.getMajor() != null
-			? studentCouncil.getMajor().getMajorId()
-			: null;
+		CouncilContext context = extractCouncilContext(studentCouncil, councilType);

그리고 이후 코드에서 context.schoolId(), context.collegeId(), context.majorId()를 사용하세요.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36b30ba and 49e67a7.

📒 Files selected for processing (2)
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (1)
src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
  • UserNotFoundException (5-9)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (1)
src/main/java/com/campus/campus/domain/council/application/exception/StudentCouncilNotFoundException.java (1)
  • StudentCouncilNotFoundException (5-9)
🔇 Additional comments (3)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (1)

38-39: 클래스 레벨 트랜잭션 설정이 적절합니다.

모든 메서드가 조회 기능이므로 클래스 레벨에서 @Transactional(readOnly = true)를 설정한 것은 중복을 제거하고 일관성을 높이는 좋은 리팩토링입니다.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (2)

122-127: 정렬 방향을 확인해주세요.

EVENT 카테고리는 startDateTime 오름차순, 다른 카테고리는 endDateTime 오름차순으로 정렬됩니다. 둘 다 오름차순(ASC)을 사용하는데, 이는 이벤트의 경우 다가오는 순서대로, 제휴/공지의 경우 종료가 임박한 순서대로 표시하는 것을 의미합니다. 이것이 의도된 동작인지 확인해주세요. 일반적으로는 최신순(내림차순)으로 정렬하는 경우가 많습니다.


105-137: 학생회 타입별 게시글 조회 로직이 적절하게 구현되었습니다.

CouncilType에 따라 collegeIdmajorId를 동적으로 설정하는 로직이 올바르게 구현되었으며, 리포지토리의 findPostsBySchoolAndFilters 메서드에서 null 파라미터를 적절히 처리하고 있습니다. 쿼리에서 (:collegeId IS NULL OR c.collegeId = :collegeId)(:majorId IS NULL OR m.majorId = :majorId) 조건으로 null 값을 올바르게 처리합니다. 카테고리별로 정렬 기준을 다르게 적용한 것도 합리적입니다(EVENT는 시작일시, 나머지는 종료일시).

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: 1

🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java:
- Line 119: The code in StudentCouncilPostService uses LocalDateTime.now() which
can cause timezone mismatches; replace both occurrences (the one at
LocalDateTime now = LocalDateTime.now(); and the similar one at line 136) to use
KST-aware time (e.g., LocalDateTime.now(ZoneId.of("Asia/Seoul")) or the existing
KST constant if present) so the service matches
StudentCouncilPostForUserService's timezone handling and yields consistent
filtering; update any imports/usages in the class accordingly.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 49e67a7 and ef6943d.

📒 Files selected for processing (4)
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
  • src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (3)
src/main/java/com/campus/campus/domain/council/application/exception/StudentCouncilNotFoundException.java (1)
  • StudentCouncilNotFoundException (5-9)
src/main/java/com/campus/campus/domain/councilpost/application/service/UserPostService.java (1)
  • Service (34-183)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/StudentCouncilPost.java (1)
  • Entity (25-98)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (1)
src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
  • UserNotFoundException (5-9)
🔇 Additional comments (6)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (1)

178-215: LGTM! 잘 구현된 메서드입니다.

CouncilType에 따른 조건 분기 및 필수 컨텍스트 누락 시 빈 리스트 반환 처리가 적절합니다. KST 타임존 사용도 파일 내 다른 메서드들과 일관성 있습니다.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (1)

104-125: LGTM! 카테고리별 정렬 로직 적절함

EVENTstartDateTime, PARTNERSHIPendDateTime 기준 정렬이 비즈니스 요구사항에 맞게 구현되었습니다.

src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (2)

49-67: LGTM! 카테고리별 날짜 필터링 로직 적절

EVENT는 시작 시간 기준, PARTNERSHIP은 종료 시간 기준으로 활성 게시글을 필터링하는 로직이 정확합니다. JPQL에서 enum의 FQN 사용도 적절합니다.


69-93: Database 호환성 concern이 적용되지 않음

프로젝트는 MySQL을 exclusive로 사용하므로 function('RAND())` 사용은 문제가 없습니다. 프로덕션 및 개발 환경 모두 MySQLDialect를 사용하며, 테스트 환경도 H2를 MySQL 호환 모드(MODE=MYSQL)로 구성되어 있어 RAND() 함수를 정상적으로 처리합니다. PostgreSQL 지원 계획이 없으므로 현재 구현은 적절합니다.

src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java (2)

34-35: LGTM! 학생회 전용 접근 제어 적절

@PreAuthorize("hasRole('COUNCIL')")로 학생회 권한 사용자만 접근 가능하도록 보안이 적절히 적용되었습니다.


142-154: LGTM! 엔드포인트 구조 적절

페이지네이션 기본값 설정과 @CurrentCouncilId를 통한 로그인한 학생회 ID 추출이 적절합니다. 서비스 계층으로의 위임도 명확합니다.

);
Pageable pageable = PageRequest.of(Math.max(page - 1, 0), size, sort);

LocalDateTime now = LocalDateTime.now();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

타임존 불일치: LocalDateTime.now() 대신 KST 사용 권장

StudentCouncilPostForUserService에서는 LocalDateTime.now(KST)를 사용하지만, 이 서비스에서는 시스템 기본 타임존을 사용합니다. 배포 환경에 따라 시간 필터링 결과가 달라질 수 있습니다.

🔧 제안하는 수정 사항
+	private static final ZoneId KST = ZoneId.of("Asia/Seoul");
+
 	@Transactional(readOnly = true)
 	public Page<GetPostListForCouncilResponse> findPostListForCouncil(Long councilId, PostCategory category,
 		int page, int size) {
 		// ...
 		Pageable pageable = PageRequest.of(Math.max(page - 1, 0), size, sort);

-		LocalDateTime now = LocalDateTime.now();
+		LocalDateTime now = LocalDateTime.now(KST);

Line 136도 동일하게 수정 필요합니다.

📝 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.

Suggested change
LocalDateTime now = LocalDateTime.now();
private static final ZoneId KST = ZoneId.of("Asia/Seoul");
@Transactional(readOnly = true)
public Page<GetPostListForCouncilResponse> findPostListForCouncil(Long councilId, PostCategory category,
int page, int size) {
// ...
Pageable pageable = PageRequest.of(Math.max(page - 1, 0), size, sort);
LocalDateTime now = LocalDateTime.now(KST);
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
at line 119, The code in StudentCouncilPostService uses LocalDateTime.now()
which can cause timezone mismatches; replace both occurrences (the one at
LocalDateTime now = LocalDateTime.now(); and the similar one at line 136) to use
KST-aware time (e.g., LocalDateTime.now(ZoneId.of("Asia/Seoul")) or the existing
KST constant if present) so the service matches
StudentCouncilPostForUserService's timezone handling and yields consistent
filtering; update any imports/usages in the class accordingly.

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

Labels

✨Feat 새로운 기능 개발 ♻️Refactor 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 홈화면 조회 기능 구현

2 participants