Skip to content

Conversation

@rlajm1203
Copy link
Contributor

@rlajm1203 rlajm1203 commented Sep 5, 2025

📌 관련 이슈

closes #280

✒️ 작업 내용

기존에 Calendar 라는 클래스가 존재했지만,
해당 클래스는 '동아리 한 학기 일정' 이라는 의미를 나타내는 클래스이므로 용도에 맞게 기존 클래스의 이름을 변경하였습니다.

  • (기존) Calendar -> (변경) SemesterPeriod

캘린더 기능을 구현합니다.

  • 캘린더 모델/엔티티 설계
    • CalendarModel / CalendarEntity : 모델/엔티티
    • CalendarType : 일정 타입 (행사부, 기타)
    • CalendarValidator : 일정 유효성 검증
  • 캘린더 조회/생성 API 추가
    • 시큐리티 필터체인 적용 완료
  • 이외 유틸리티 메소드 추가
    • DateConverter.toMillis
    • DateConverter.toLocalDateTime

스크린샷 🏞️ (선택)

💬 REVIEWER에게 요구사항 💬

  • 이 PR은, 기존 기능의 클래스명 변경 및 관련된 클래스명 변경으로 인해, 수정된 파일의 수가 많습니다. (PR을 나눠서 날려야 한다는 것을 이제야 알았네요..)그렇기 때문에, 클래스 수정과 관련된 작업만 보고 싶으시다면

    • 해당 커밋 으로 들어가셔서 봐주시면 감사하겠습니다.
  • 이 브랜치는 회원의 부서 관리 기능에 의존하고 있으므로,
    BM/feat/#277/department 브랜치에서 생성하여, BM/feat/#277/department 향해 PR을 열었습니다.

Summary by CodeRabbit

  • New Features
    • 캘린더 기능 추가: 생성/수정/삭제, 기간별 조회, D-Day 기반 예정 일정 조회 제공
    • 응답에 작성자 이름 포함, 날짜 유효성·타입 권한 검증 및 안내 메시지 제공
    • 학기 기간 API 추가: 조회 및 관리자 수정(시작/종료일) 엔드포인트 제공
  • Refactor
    • 기존 “캘린더 기간” 기능을 “학기 기간”으로 전환하고 엔드포인트를 /api/semester-periods, /api/admin/semester-periods로 변경
    • 출결 관련 기본 기간 참조를 학기 기간으로 일원화
  • Chores
    • 학기 기간 엔드포인트 인증 보호 추가
    • 날짜 변환 및 월 말일 계산 유틸 추가

@rlajm1203 rlajm1203 self-assigned this Sep 5, 2025
@rlajm1203
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Sep 10, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai bot added the 🔧refactor 코드 수정 label Sep 10, 2025
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: 23

Caution

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

⚠️ Outside diff range comments (3)
eeos/src/main/java/com/blackcompany/eeos/program/presentation/dto/UpdateSemesterPeriodRequest.java (1)

11-14: NPE 위험: @AssertTrue 메서드에서 null 접근 가능

Bean Validation에서 필드 검증 순서가 보장되지 않아 startDate/endDate가 null이면 NPE가 날 수 있습니다. null-세이프하게 수정해 주세요.

 	@AssertTrue(message = "종료일은 시작일 이후여야 합니다.")
 	public boolean isEndDateAfterStartDate() {
-		return endDate.getTime() > startDate.getTime();
+		return startDate != null
+				&& endDate != null
+				&& endDate.after(startDate);
 	}
eeos/src/main/java/com/blackcompany/eeos/program/application/model/SemesterPeriodModel.java (1)

19-37: 1–2월 기본 학기 계산 오류: 현재 월이 1–2월일 때 직전 해 9/1~당해 2월 말이어야 함

현 구현은 1–2월에도 3/1~8/31로 설정되어 잘못된 기본 학기 범위를 반환합니다. 아래처럼 분기(9–12월 / 3–8월 / 1–2월)를 명확히 나눠 주세요.

 	public SemesterPeriodModel() {
 		LocalDate now = LocalDate.now();
 
-		// 9월 이후이면 9월 1일부터 다음 해 2월 말까지
-		if (now.getMonthValue() >= Month.SEPTEMBER.getValue()) {
+		// 9–12월: 해당 해 9/1 ~ 다음 해 2월 말
+		if (now.getMonthValue() >= Month.SEPTEMBER.getValue()) {
 			startDate = Timestamp.valueOf(LocalDate.of(now.getYear(), Month.SEPTEMBER, 1).atStartOfDay());
 			endDate =
 					Timestamp.valueOf(
 							LocalDate.of(
 											now.getYear() + 1,
 											Month.FEBRUARY,
 											Month.FEBRUARY.length(Year.isLeap(now.getYear() + 1)))
 									.atTime(23, 59, 59));
-		} else {
-			// 3월 이후이면 3월 1일부터 8월 말까지
+		// 3–8월: 해당 해 3/1 ~ 8/31
+		} else if (now.getMonthValue() >= Month.MARCH.getValue()) {
 			startDate = Timestamp.valueOf(LocalDate.of(now.getYear(), Month.MARCH, 1).atStartOfDay());
 			endDate = Timestamp.valueOf(LocalDate.of(now.getYear(), Month.AUGUST, 31).atTime(23, 59, 59));
+		// 1–2월: 직전 해 9/1 ~ 해당 해 2월 말
+		} else {
+			startDate = Timestamp.valueOf(LocalDate.of(now.getYear() - 1, Month.SEPTEMBER, 1).atStartOfDay());
+			endDate =
+					Timestamp.valueOf(
+							LocalDate.of(
+											now.getYear(),
+											Month.FEBRUARY,
+											Month.FEBRUARY.length(Year.isLeap(now.getYear())))
+									.atTime(23, 59, 59));
 		}
 	}

추가 제안:

  • 서버 타임존 의존 최소화를 위해 Clock 주입을 고려하면 테스트가 쉬워집니다.
  • 종료 시각을 진짜 EOD로 다루려면 atStartOfDay().plusDays(1).minusNanos(1) 패턴을 고려해 보세요. (DB 정밀도에 따라 선택)
eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (1)

280-284: Null-safe 정렬 파라미터 처리

sortType.equals("asc")sortType이 null이면 NPE를 유발합니다. 기본값과 대소문자 무시 처리를 권장합니다.

- if (sortType.equals("asc")) {
+ if ("asc".equalsIgnoreCase(sortType)) {
    order = Sort.Order.asc("totalScore");
  } else {
    order = Sort.Order.desc("totalScore");
  }
🧹 Nitpick comments (38)
eeos/src/main/java/com/blackcompany/eeos/member/application/repository/MemberRepository.java (1)

29-30: findNameById 계약에 예외 명시를 추가해 주세요

구현체에서 NotFoundMemberException을 던지므로, 인터페이스에도 이를 문서화하면 호출 측에서 의도를 명확히 파악할 수 있습니다. (대안: Optional<String> 반환)

아래처럼 Javadoc을 추가하는 것을 제안합니다.

- String findNameById(Long memberId);
+ /**
+  * @throws NotFoundMemberException 존재하지 않는 멤버 ID인 경우
+  */
+ String findNameById(Long memberId);
eeos/src/main/java/com/blackcompany/eeos/calendar/application/usecase/DeleteCalendarUsecase.java (1)

3-6: 삭제 유스케이스: 예외/권한 실패 케이스를 계약에 문서화

삭제 시 대상 없음/권한 없음 등 도메인 에러가 발생할 수 있습니다. 인터페이스 수준에서 계약을 명시하면 컨트롤러·서비스 계층에서의 처리 일관성이 높아집니다.

 public interface DeleteCalendarUsecase {
-
-	void delete(Long calendarId);
+	/**
+	 * @throws com.blackcompany.eeos.common.exception.BusinessException 도메인 예외(대상 없음/권한 없음 등)
+	 */
+	void delete(Long calendarId);
 }
eeos/src/main/java/com/blackcompany/eeos/calendar/application/dto/CalendarQuery.java (1)

3-3: CalendarQuery 필드 의미/범위 주석화

date(null=월 전체?), duration(단위/최솟값)의 의미와 경계값(월=112, 일=131 등)을 Javadoc으로 명시하면 사용 측 혼동을 줄일 수 있습니다. 컨트롤러 레벨에서의 기본값 정책도 함께 기록해 주세요.

-public record CalendarQuery(Integer year, Integer month, Integer date, Integer duration) {}
+/**
+ * year, month는 KST 기준.
+ * date가 null이면 월 단위 조회로 간주(예: 1일~말일), duration은 일 단위이며 null이면 1로 처리(예시).
+ * 유효 범위: month=1..12, date=1..31, duration>=1.
+ */
+public record CalendarQuery(Integer year, Integer month, Integer date, Integer duration) {}
eeos/src/main/java/com/blackcompany/eeos/common/utils/DateConverter.java (1)

56-58: Null-safe 정렬 및 일관성

다른 메서드들은 null-safe인데 toMillis만 NPE 가능성이 있습니다. 동일한 패턴으로 null 처리해 주세요. (이전 러닝: toEpochSecond 반환 타입 혼동 이슈가 있었으므로, 본 메서드에서 null 처리로 호출부 혼선을 줄이는 것이 좋습니다.)

-	public static Long toMillis(LocalDateTime localDateTime) {
-		return localDateTime.atZone(ZoneId.of(KST)).toInstant().toEpochMilli();
-	}
+	public static Long toMillis(LocalDateTime localDateTime) {
+		if (localDateTime == null) {
+			return null;
+		}
+		return localDateTime.atZone(ZoneId.of(KST)).toInstant().toEpochMilli();
+	}

추가로, 미세 최적화로 아래처럼 ZoneId 캐싱을 고려해 주세요. (선택)

private static final ZoneId KST_ZONE = ZoneId.of(KST);
// 사용처: ...atZone(KST_ZONE)...
eeos/src/main/java/com/blackcompany/eeos/calendar/application/exception/NotFoundCalendarTypeException.java (1)

4-8: 불필요한 @Getter 제거 제안

필드가 없어 Lombok @Getter는 효과가 없습니다. 불필요한 의존을 줄여 주세요.

-import lombok.Getter;
@@
-@Getter
 public class NotFoundCalendarTypeException extends BusinessException {
eeos/src/main/java/com/blackcompany/eeos/member/persistence/MemberRepositoryImpl.java (1)

71-74: 단건 이름 조회 구현은 적절합니다. 다만 대량 조회 시 N+1 주의

CalendarQueryService 등에서 리스트 항목별로 반복 호출되면 N+1이 발생할 수 있습니다. 필요 시 ID 목록 기반 벌크 조회(findNamesByIds)를 추가하거나, 상위 계층에서 작성자 ID를 집계해 한 번에 가져오는 방식을 고려해 주세요.

eeos/src/main/java/com/blackcompany/eeos/calendar/application/dto/CalendarUpdateCommand.java (1)

3-6: 요청 DTO에 Bean Validation 추가 제안

기본 필드 제약을 명시하면 컨트롤러 단에서 빠르게 부적합 입력을 차단할 수 있습니다.

다음과 같이 최소 제약을 권장합니다.

 package com.blackcompany.eeos.calendar.application.dto;

 import com.blackcompany.eeos.common.support.dto.AbstractRequestDto;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;

-public record CalendarUpdateCommand(String title, String url, String type, Long startAt, Long endAt)
-		implements AbstractRequestDto {}
+public record CalendarUpdateCommand(
+		@NotBlank String title,
+		String url,
+		@NotBlank String type,
+		@NotNull Long startAt,
+		@NotNull Long endAt
+) implements AbstractRequestDto {}
eeos/src/main/java/com/blackcompany/eeos/calendar/application/dto/CalendarResponses.java (1)

6-6: 응답 리스트 불변성 확보 고려

record 자체는 불변이지만 List 내용은 가변입니다. 서비스/컨트롤러에서 List.copyOf(...)로 감싸거나, 팩토리 메서드를 두어 방어적 복사를 권장합니다.

eeos/src/main/java/com/blackcompany/eeos/calendar/application/exception/InvalidDateException.java (1)

14-17: 오류 메시지에 상세 맥락 포함 고려

가능하면 시작/종료 시각(또는 yyyy-MM-dd 포맷)을 포함하면 디버깅이 수월합니다. 국제화(MessageSource) 적용 시 메시지 키와 파라미터로 분리하는 것도 좋습니다.

eeos/src/main/java/com/blackcompany/eeos/calendar/application/exception/DeniedCalendarTypeException.java (1)

19-19: 한글 표기 통일(칼렌더 → 캘린더)

프로젝트 전반에 “캘린더” 표기를 사용 중이라면 메시지도 통일하는 것이 좋습니다.

-		return String.format("%s 부서는 해당 타입의 칼렌더를 생성할 수 없습니다.", department.name());
+		return String.format("%s 부서는 해당 타입의 캘린더를 생성할 수 없습니다.", department.name());
eeos/src/main/java/com/blackcompany/eeos/calendar/application/usecase/GetCalendarUsecase.java (1)

11-11: 파라미터 네이밍 컨벤션 정리 (DDaydDay)

자바 로컬 변수/파라미터는 lowerCamelCase가 관례입니다.

-	List<CalendarResponse> getCalendarForDDay(int DDay);
+	List<CalendarResponse> getCalendarForDDay(int dDay);
eeos/src/main/java/com/blackcompany/eeos/calendar/application/repository/CalendarRepository.java (3)

3-5: findById의 부재 케이스 표현을 명확히 해주세요 (Optional 권장).

조회 실패 시 예외/NULL/Optional 중 어떤 계약인지 불명확합니다. API 경계를 명확히 하기 위해 Optional 반환을 권장합니다.

아래와 같이 시그니처와 import를 조정해 주세요:

 import com.blackcompany.eeos.calendar.application.model.CalendarModel;
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Optional;

-	CalendarModel findById(Long id);
+	Optional<CalendarModel> findById(Long id);

Also applies to: 11-11


13-13: 메서드 네이밍을 의미와 일치시키세요 (JPA 구현과도 정합).

현재 findByBetweenDate는 “구간에 겹치는 일정”을 반환합니다. 내부 JPA는 findBetween을 사용하므로 인터페이스도 동일한 이름을 쓰는 편이 이해/정렬에 좋습니다.

-	List<CalendarModel> findByBetweenDate(LocalDateTime startAt, LocalDateTime endAt);
+	List<CalendarModel> findBetween(LocalDateTime startAt, LocalDateTime endAt);

13-15: 구간 포함 범위/시간대(타임존) 계약을 명시해 주세요.

현재 조건은 “양끝 포함” 겹침입니다. 프런트 요구사항(예: 끝=시작 접점 포함 여부)과 서버 내부 기준 시간대(UTC/Asia/Seoul) 합의를 문서/자바독에 명시하는 것을 권장합니다.

eeos/src/main/java/com/blackcompany/eeos/calendar/application/dto/CalendarResponse.java (2)

11-18: 에포크 변환의 시간대 기준과 NPE 방지 확인 필요.

DateConverter.toMillis의 기준 시간대(UTC vs KST)를 명시/고정하고, startAt/endAt가 null일 경우 NPE가 발생합니다. 최소한의 널 가드 추가를 권장합니다.

 import com.blackcompany.eeos.common.utils.DateConverter;
+import java.util.Objects;

  public static CalendarResponse toResponse(CalendarModel model, String writerName) {
-		Long startAt = DateConverter.toMillis(model.getStartAt());
-		Long endAt = DateConverter.toMillis(model.getEndAt());
+		Long startAt = DateConverter.toMillis(Objects.requireNonNull(model.getStartAt(), "startAt must not be null"));
+		Long endAt = DateConverter.toMillis(Objects.requireNonNull(model.getEndAt(), "endAt must not be null"));
 		String type = model.getType().name();

7-9: 필드 명칭 정합성(“writer” → “writerName” 여부) 검토.

응답의 writer 타입이 String(이름)인데, 도메인에서는 writer가 Long(작성자 ID)입니다. 혼동 방지를 위해 “writerName” 등 구체 명명으로의 변경을 검토해 주세요. API 호환성 영향이 크면 문서화라도 권장합니다.

eeos/src/main/java/com/blackcompany/eeos/calendar/persistence/CalendarJpaRepository.java (2)

11-20: 대규모 주석 블록 정리 제안.

과거 시도 쿼리 주석은 PR 병합 전 정리(제거)하는 편이 가독성과 유지보수에 유리합니다. 변경 이력은 Git에 남습니다.


21-26: 겹침 검색의 성능/용어 정합성 확인.

  • 쿼리는 “겹침(overlap)” 조건입니다. 상위 레이어 메서드 네이밍/문서도 이에 맞추세요(예: findBetween/Overlapping).
  • 대량 데이터에서 성능 이슈가 있으면, DB가 PostgreSQL인 경우 tsrange + GIST 인덱스(‘&&’ 연산자)로 전환을 검토하세요. JPA에는 네이티브 쿼리가 필요할 수 있습니다.
eeos/src/main/java/com/blackcompany/eeos/calendar/persistence/CalendarEntity.java (1)

51-56: 열 길이 제한 추가(특히 URL).

URL은 255자를 넘는 경우가 많습니다. DB 제약을 명시해 두는 편이 안전합니다. 타이틀도 최대 길이 정책을 명확히 하세요.

-	@Column(name = ENTITY_PREFIX + "_title", nullable = false)
+	@Column(name = ENTITY_PREFIX + "_title", nullable = false, length = 200)
 	private String title;

-	@Column(name = ENTITY_PREFIX + "_url", nullable = false)
+	@Column(name = ENTITY_PREFIX + "_url", nullable = false, length = 2048)
 	private String url;
eeos/src/main/java/com/blackcompany/eeos/calendar/application/validator/CalendarValidator.java (2)

35-40: 널 입력 방지로 유효성 검사를 견고하게.

startAt/endAt가 null이면 isAfter에서 NPE가 납니다. 간단한 널 가드를 추가해 주세요.

 	public void durationValidate(CalendarModel calendar) {
 		LocalDateTime startAt = calendar.getStartAt();
 		LocalDateTime endAt = calendar.getEndAt();
 
-		if (startAt.isAfter(endAt)) throw new InvalidDateException();
+		if (startAt == null || endAt == null || startAt.isAfter(endAt)) {
+			throw new InvalidDateException();
+		}
 	}

17-33: EnumMap/불변 컬렉션으로 초기화 안정성/성능 미세 개선.

HashMap 대신 EnumMap을 쓰고 put 이후 불변으로 고정하면 실수로의 변형을 막을 수 있습니다. 영향도는 낮으므로 취향에 따라 적용하세요.

eeos/src/main/java/com/blackcompany/eeos/calendar/persistence/CalendarRepositoryImpl.java (2)

7-7: 빈 메시지의 orElseThrow 개선: 원인 추적 가능한 예외로 변환

현재 NoSuchElementException이 메시지 없이 발생합니다. 식별자 포함 메시지로 치환해 디버깅/관찰성 향상하세요.

+import java.util.NoSuchElementException;
@@
-    return entity.map(CalendarEntity::toModel).orElseThrow();
+    return entity
+        .map(CalendarEntity::toModel)
+        .orElseThrow(() -> new NoSuchElementException("Calendar not found. id=" + id));

Also applies to: 23-28


36-40: 메서드 네이밍 일관성

도메인 레포는 findByBetweenDate, JPA 레포는 findBetween으로 서로 다릅니다. 한쪽으로 맞추어 가독성과 검색성을 높이는 것을 권장합니다.

eeos/src/main/java/com/blackcompany/eeos/calendar/application/service/CalendarCommandService.java (2)

41-42: @slf4j 사용: 주요 상태 변경 로깅 추가 또는 애노테이션 제거

현재 로그가 없어 @slf4j가 무용합니다. 생성/수정/삭제에 최소한의 감사 로그를 남기거나 @slf4j 제거를 제안합니다.

-    return repository.save(calendar);
+    Long savedId = repository.save(calendar);
+    log.info("Calendar created. id={}, writer={}", savedId, memberId);
+    return savedId;
@@
-    repository.save(model);
+    repository.save(model);
+    log.info("Calendar updated. id={}, writer={}", model.getId(), memberId);
@@
-    repository.delete(calendarId);
+    repository.delete(calendarId);
+    log.info("Calendar deleted. id={}, writer={}", calendarId, memberId);

Also applies to: 52-55, 65-66


34-36: RequestScope.getMemberId() 존재 보장 검증

미설정 시 NPE/권한우회 이슈가 될 수 있습니다. 필터에서 항상 세팅되는지 확인하거나 null 가드(예: requireNonNull) 추가를 권장합니다.

필요하시면 전역 인증 필터에서 RequestScope 설정 여부를 점검하는 테스트/헬스체크 코드를 제안드릴게요.

Also applies to: 46-47, 59-60

eeos/src/main/java/com/blackcompany/eeos/calendar/application/service/CalendarQueryService.java (4)

17-17: 조회 메서드에 readOnly 트랜잭션 부여

읽기 전용 트랜잭션은 플러시 방지/성능/일관성에 유리합니다.

+import org.springframework.transaction.annotation.Transactional;
@@
 @Override
+@Transactional(readOnly = true)
 public List<CalendarResponse> getCalendar(CalendarQuery query) {
@@
 @Override
+@Transactional(readOnly = true)
 public List<CalendarResponse> getCalendarForDDay(int DDay) {

Also applies to: 26-41, 43-50


68-76: 불필요 변수 및 분기 정리

result는 사용되지 않으며 if-else는 블록으로 감싸 가독성을 높일 수 있습니다.

-    LocalDateTime result;
     LocalDate localDate;
 
-    if (Objects.isNull(date)) localDate = LocalDate.of(year, month, 1);
-    else localDate = LocalDate.of(year, month, date);
+    if (Objects.isNull(date)) {
+        localDate = LocalDate.of(year, month, 1);
+    } else {
+        localDate = LocalDate.of(year, month, date);
+    }
 
     return LocalDateTime.of(localDate, LocalTime.MIDNIGHT);

56-66: 작성자 이름 조회 N+1 최소화

캘린더 항목마다 findNameById 호출로 N+1 발생 가능성이 큽니다. ID 집합을 모아 배치 조회(findNamesByIds) 후 매핑하거나, JPA 조인/프로젝션으로 한 번에 가져오기를 권장합니다.


27-33: 입력 유효성 보강 제안

year/month/date/duration에 대한 범위 검증이 보이지 않습니다(다른 레이어에서 하면 OK). 최소한 month(112), date(131), duration(>=0) 체크를 권장합니다.

eeos/src/main/java/com/blackcompany/eeos/calendar/presentation/controller/CalendarController.java (1)

64-69: DELETE는 204 No Content 권장 (본문 없이)

삭제 성공 시 본문 없는 204가 일반적인 REST 컨벤션입니다. 현재 래퍼로 본문을 생성한다면 API 일관성을 위해 컨벤션을 재검토해 주세요.

대안 A(컨벤션 변경):

 @DeleteMapping("/{calendarId}")
-public ApiResponse<SuccessBody<Void>> deleteCalendar(
-      @PathVariable("calendarId") Long calendarId) {
-  deleteUsecase.delete(calendarId);
-  return ApiResponseGenerator.success(HttpStatus.OK, MessageCode.DELETE);
-}
+@ResponseStatus(HttpStatus.NO_CONTENT)
+public void deleteCalendar(@PathVariable("calendarId") Long calendarId) {
+  deleteUsecase.delete(calendarId);
+}

대안 B(현 래퍼 유지): 프로젝트 전반에서 DELETE 응답을 200 + 표준 래퍼로 통일.

eeos/src/main/java/com/blackcompany/eeos/config/security/SecurityFilterChainConfig.java (1)

89-91: /api/admin/semester-periods/**에 관리자 권한 매처 추가
컨트롤러에서 @PutMapping("/admin/semester-periods")가 정의되어 있으므로(/api 접두사 기준) 아래와 같이 보안 설정에 명시해 주세요:

 httpSecurity.authorizeHttpRequests(requests -> {
     requests.requestMatchers("/api/admin/**").hasAnyRole(ADMIN);
+    requests.requestMatchers("/api/admin/semester-periods/**").hasAnyRole(ADMIN);
     requests.requestMatchers(HttpMethod.POST, "/api/programs").hasAnyRole(ADMIN);
     // …
 });
eeos/src/main/java/com/blackcompany/eeos/program/presentation/dto/UpdateSemesterPeriodRequest.java (1)

7-10: 타입 선택 제안: Timestamp 대신 LocalDateTime/Instant 고려

API 경계에서는 java.time 타입 사용이 직관적이고 직렬화/타임존 이슈 대응이 수월합니다. DTO를 LocalDateTime(또는 Instant)로 전환하는 방안을 검토해 주세요. 서비스/리포지토리 단에서 변환 유틸을 일관 적용하면 됩니다.

eeos/src/main/java/com/blackcompany/eeos/program/persistence/JpaSemesterPeriodRepository.java (1)

6-8: 최신 행 조회 전략 보완: 인덱스/제약 고려

findTopByOrderByCreatedDateDesc() 성능/정합성 강화를 위해:

  • createdDate에 인덱스를 추가하거나(엔티티 @Index)
  • 테이블 단일행 보장을 원하면 유니크 제약/체크 제약으로 중복 생성을 차단해 주세요.
eeos/src/main/java/com/blackcompany/eeos/program/application/repository/SemesterPeriodRepository.java (1)

6-10: 리포지토리 계약은 명확함. 메서드 네이밍 소폭 개선 제안

JPA 관례와 맞추려면 updateSemesterPeriodsaveSemesterPeriod로 변경하는 것도 고려해 주세요(신규 생성/업데이트 모두 포괄).

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (3)

288-296: 기간 기본값 처리 중복/가독성 개선

같은 메서드 블록에서 start/end를 각각 Provider로부터 별도로 가져옵니다. 상단에서 가져온 period를 재사용해 중복 호출을 제거하세요.

- Timestamp startTimestamp =
-     startDate == null
-         ? semesterPeriodProvider.getSemesterPeriod().getStartDate()
-         : new Timestamp(startDate);
- Timestamp endTimestamp =
-     endDate == null
-         ? semesterPeriodProvider.getSemesterPeriod().getEndDate()
-         : new Timestamp(endDate);
+ var period = semesterPeriodProvider.getSemesterPeriod();
+ Timestamp startTimestamp = (startDate == null) ? period.getStartDate() : new Timestamp(startDate);
+ Timestamp endTimestamp   = (endDate   == null) ? period.getEndDate()   : new Timestamp(endDate);

338-340: Penalty 랭킹에서도 동일한 기간 기본값 처리 패턴 적용

여기도 Provider 중복 호출 및 NPE 위험이 있습니다. 한 번만 받아 재사용하세요.

- Timestamp startDate = semesterPeriodProvider.getSemesterPeriod().getStartDate();
- Timestamp endDate = semesterPeriodProvider.getSemesterPeriod().getEndDate();
+ var period = semesterPeriodProvider.getSemesterPeriod();
+ Timestamp startDate = period.getStartDate();
+ Timestamp endDate   = period.getEndDate();

314-328: N+1/중복 계산 제거로 쿼리 수 절감

이미 pages에 [memberId, totalScore]가 들어왔다면, 각 멤버에 대해 다시 findTotalPenaltyScoreByMemberId를 호출할 필요가 없습니다. pages 결과를 Map으로 만들어 재사용하고, 랭킹 계산도 윈도우 함수(가능 시) 또는 배치 질의로 대체를 고려하세요.

원하시면 JPA/SQL 레벨에서 집계+랭킹을 한 번에 가져오는 쿼리 스케치 드리겠습니다.

eeos/src/main/java/com/blackcompany/eeos/program/persistence/SemesterPeriodEntity.java (1)

34-38: DB 레벨 무결성(종료 ≥ 시작) 보강 권장

엔티티/DB에 체크 제약을 추가하면 애플리케이션 버그나 동시성 이슈에도 데이터 무결성을 지킬 수 있습니다.

예: Hibernate 사용 시

@Check(constraints = "semester_period_end_date >= semester_period_start_date")

또는 마이그레이션에서 CHECK 제약 추가(Flyway/Liquibase).

원하시면 마이그레이션 스크립트 초안을 제공하겠습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad1e714 and 6412c5e.

📒 Files selected for processing (51)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/dto/CalendarCreateCommand.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/dto/CalendarQuery.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/dto/CalendarResponse.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/dto/CalendarResponses.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/dto/CalendarUpdateCommand.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/exception/DeniedCalendarTypeException.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/exception/DeniedCalendarUpdateException.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/exception/InvalidDateException.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/exception/NotFoundCalendarTypeException.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/model/CalendarModel.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/model/CalendarType.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/repository/CalendarRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/service/CalendarCommandService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/service/CalendarQueryService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/usecase/CreateCalendarUsecase.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/usecase/DeleteCalendarUsecase.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/usecase/GetCalendarUsecase.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/usecase/UpdateCalendarUsecase.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/application/validator/CalendarValidator.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/persistence/CalendarEntity.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/persistence/CalendarJpaRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/persistence/CalendarRepositoryImpl.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/calendar/presentation/controller/CalendarController.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/common/utils/DateConverter.java (2 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/common/utils/DateUtil.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/config/security/SecurityFilterChainConfig.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/member/application/repository/MemberRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/member/persistence/JpaMemberRepository.java (2 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/member/persistence/MemberRepositoryImpl.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/dto/request/SemesterPeriodApplicationCommand.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/dto/response/SemesterPeriodApplicationQuery.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/model/SemesterPeriodModel.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/repository/CalendarRepository.java (0 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/repository/SemesterPeriodRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/service/CalendarService.java (0 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/service/SemesterPeriodService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/support/CalendarProvider.java (0 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/support/SemesterPeriodProvider.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/GetCalendarUsecase.java (0 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/GetSemesterPeriodUsecase.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/UpdateCalendarUsecase.java (0 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/UpdateSemesterPeriodUsecase.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/persistence/CalendarRepositoryImpl.java (0 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/persistence/JpaCalendarRepository.java (0 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/persistence/JpaSemesterPeriodRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/persistence/SemesterPeriodEntity.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/persistence/SemesterPeriodRepositoryImpl.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/presentation/controller/CalendarController.java (0 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/presentation/controller/SemesterPeriodController.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/presentation/dto/UpdateSemesterPeriodRequest.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (5 hunks)
💤 Files with no reviewable changes (8)
  • eeos/src/main/java/com/blackcompany/eeos/program/persistence/JpaCalendarRepository.java
  • eeos/src/main/java/com/blackcompany/eeos/program/application/support/CalendarProvider.java
  • eeos/src/main/java/com/blackcompany/eeos/program/persistence/CalendarRepositoryImpl.java
  • eeos/src/main/java/com/blackcompany/eeos/program/application/repository/CalendarRepository.java
  • eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/GetCalendarUsecase.java
  • eeos/src/main/java/com/blackcompany/eeos/program/application/service/CalendarService.java
  • eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/UpdateCalendarUsecase.java
  • eeos/src/main/java/com/blackcompany/eeos/program/presentation/controller/CalendarController.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-03-30T10:51:03.804Z
Learnt from: rlajm1203
PR: JNU-econovation/EEOS-BE#250
File: eeos/src/main/java/com/blackcompany/eeos/program/application/service/ProgramQuitService.java:47-55
Timestamp: 2025-03-30T10:51:03.804Z
Learning: DateConverter.toEpochSecond(LocalDate) 메소드는 long 값이 아닌 Timestamp 객체를 반환하므로, epoch 시간을 밀리초 단위의 long 값으로 변환하려면 .getTime()을 호출해야 합니다.

Applied to files:

  • eeos/src/main/java/com/blackcompany/eeos/common/utils/DateConverter.java
🔇 Additional comments (17)
eeos/src/main/java/com/blackcompany/eeos/common/utils/DateUtil.java (1)

17-19: LGTM: YearMonth.lengthOfMonth() 적절히 활용

간결하고 표준 API를 사용한 구현입니다.

eeos/src/main/java/com/blackcompany/eeos/member/persistence/JpaMemberRepository.java (1)

26-27: LGTM: 필요한 필드만 선택하는 JPQL로 과다 로딩 방지

이름만 조회하여 성능/메모리 측면에서 효율적입니다.

eeos/src/main/java/com/blackcompany/eeos/calendar/application/usecase/CreateCalendarUsecase.java (1)

5-8: 인터페이스 설계 적절

간결한 계약(입력 DTO + id 반환)으로 용도에 부합합니다.

eeos/src/main/java/com/blackcompany/eeos/calendar/application/usecase/UpdateCalendarUsecase.java (1)

5-8: 계약 명확하고 적절

id와 요청 DTO를 받아 id를 반환하는 패턴이 일관적입니다.

eeos/src/main/java/com/blackcompany/eeos/calendar/application/model/CalendarType.java (1)

13-18: null/blank 입력 방어 코드 추가.

null이 들어오면 equalsIgnoreCase에서 NPE가 납니다. 명시적으로 예외를 던져주세요.

 	public static CalendarType findByName(String name) {
-		return Arrays.stream(CalendarType.values())
+		if (name == null || name.isBlank()) {
+			throw new NotFoundCalendarTypeException();
+		}
+		return Arrays.stream(CalendarType.values())
 				.filter(calendarType -> calendarType.name().equalsIgnoreCase(name))
 				.findFirst()
 				.orElseThrow(NotFoundCalendarTypeException::new);
 	}

Likely an incorrect or invalid review comment.

eeos/src/main/java/com/blackcompany/eeos/calendar/persistence/CalendarRepositoryImpl.java (1)

17-21: 엔티티-모델 매핑 및 저장 흐름 LGTM

모델→엔티티 변환 후 저장하고 식별자를 반환하는 흐름이 명확합니다.

eeos/src/main/java/com/blackcompany/eeos/calendar/application/service/CalendarQueryService.java (1)

46-50: D-Day 포함 범위 정의 확인

now(자정) ~ now.plusDays(DDay)에서 종료 포함/제외 기준을 명확히 해주세요. UX 상 DDay=0,1에서 기대치가 다를 수 있습니다(예: 오늘 포함 여부). 필요 시 plusDays(DDay + 1) 또는 종료 시각을 다음날 자정으로 조정하세요.

eeos/src/main/java/com/blackcompany/eeos/calendar/presentation/controller/CalendarController.java (2)

12-15: ApiResponseGenerator.success가 반환하는 ApiResponse는 ResponseEntity를 상속해 생성자에서 전달된 HttpStatus를 super로 호출하므로, 컨트롤러가 반환한 ApiResponse에 의도한 HTTP 상태 코드(201/200 등)가 정상적으로 설정됩니다.


29-33: 메서드 보안 활성화 어노테이션 누락 확인 필요
코드베이스 내에 @EnableMethodSecurity 또는 @EnableGlobalMethodSecurity 어노테이션이 검색되지 않습니다. 메서드 레벨 @PreAuthorize가 정상 동작하려면 Spring Security 설정 클래스에 해당 어노테이션을 적용했는지 검토해 주세요.

eeos/src/main/java/com/blackcompany/eeos/program/application/dto/response/SemesterPeriodApplicationQuery.java (1)

12-15: DTO 구성 일관성 양호

엔티티/모델과 필드명이 일치하고, 응답 DTO로 충분합니다.

eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/GetSemesterPeriodUsecase.java (1)

1-7: Usecase 정의 적절

단일 책임에 맞는 깔끔한 계약입니다.

eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/UpdateSemesterPeriodUsecase.java (1)

6-8: Usecase 계약 적절

명령-쿼리 DTO를 통해 의도 명확합니다.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (1)

221-223: 메서드 시그니처 확인 완료: @query로 startDate와 endDate를 모두 처리하도록 정의되어 있어 해당 호출은 컴파일 오류 없이 정상 동작합니다.

eeos/src/main/java/com/blackcompany/eeos/program/application/service/SemesterPeriodService.java (1)

22-26: 기간 미설정 케이스의 API 응답 정책 명확화

Provider가 예외를 던지도록 변경할 경우(권장), 컨트롤러에서 404/204 등 일관된 응답을 주도록 합의가 필요합니다. 현재는 빈/nullable 응답 위험이 있습니다.

보유한 글로벌 예외 핸들러에서 NotFoundSemesterPeriodException → 404 매핑이 있는지 확인해 주세요.

eeos/src/main/java/com/blackcompany/eeos/program/persistence/SemesterPeriodRepositoryImpl.java (1)

26-38: 매핑 단순명료, 역할 적합 — LGTM

엔티티↔모델 매핑과 최신 레코드 조회/저장 흐름이 명확합니다. 추가 변경 없이 승인합니다.

eeos/src/main/java/com/blackcompany/eeos/program/presentation/controller/SemesterPeriodController.java (1)

33-38: 기간 미설정 시 응답 정의

기간이 없다면 404 또는 204를 반환하도록 예외 매핑/로직을 정리하세요. 현재는 null 필드 포함 응답 가능성이 있습니다(Provider/Service 코멘트와 연계).

eeos/src/main/java/com/blackcompany/eeos/program/persistence/SemesterPeriodEntity.java (1)

21-25: 내림차순 인덱스 호환성 검증 요청
@Index(columnList = "createdDate DESC, semester_period_id DESC")DESC 구문이 실제 사용하는 DB/Dialect 및 Hibernate 설정에서 DDL에 반영되는지 확인하세요. 지원되지 않거나 오류가 발생하면 DESC를 제거한 복합 인덱스로 대체해야 합니다.

@rlajm1203 rlajm1203 marked this pull request as ready for review September 10, 2025 14:36
@rlajm1203 rlajm1203 merged commit d21e24c into BM/feat/#277/department Sep 21, 2025
4 checks passed
@rlajm1203 rlajm1203 deleted the BM/feat/#280/calendar branch September 21, 2025 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 기능 개발 🔧refactor 코드 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] EEOS 캘린더 기능을 구현합니다.

2 participants