[BE] SISC1-225 [FEAT] session user 구현#148
Conversation
Walkthrough세션 사용자 관리(추가/제거/조회)와 라운드별 참석 상태 업데이트 기능을 추가하고, 여러 DTO·엔티티·저장소·서비스·컨트롤러를 도입했으며 일부 불필요한 임포트를 제거했습니다. (요약 50단어 이내) Changes
Sequence Diagram(s)sequenceDiagram
participant Admin as Admin (Controller)
participant RoundCtrl as AttendanceRoundController
participant SessionCtrl as AttendanceSessionController
participant AttService as AttendanceService
participant SUService as SessionUserService
participant Repo as Repositories
participant DB as Database
rect rgb(220,240,255)
Note over Admin,DB: 시나리오 A — 세션에 사용자 추가
Admin->>SessionCtrl: POST /{sessionId}/users (userId)
SessionCtrl->>SUService: addUserToSession(sessionId, userId)
SUService->>Repo: validate session, user
Repo-->>SUService: session,user
SUService->>Repo: existsBySessionIdAndUserId?
Repo-->>SUService: false
SUService->>Repo: save SessionUser
Repo->>DB: INSERT session_user
SUService->>Repo: find rounds before today
Repo-->>SUService: list of past rounds
SUService->>Repo: for each round create ABSENT Attendance if missing
Repo->>DB: INSERT attendance (status=ABSENT)*
SUService-->>SessionCtrl: SessionUserResponse
SessionCtrl-->>Admin: 201 CREATED + body
end
rect rgb(220,255,220)
Note over Admin,DB: 시나리오 B — 라운드 참석 상태 업데이트
Admin->>RoundCtrl: PUT /rounds/{r}/attendances/{u} (status, reason)
RoundCtrl->>AttService: updateAttendanceStatusByRound(r, u, status, reason)
AttService->>Repo: validate round, user, status
Repo-->>AttService: entities & valid
AttService->>Repo: find existing Attendance
Repo-->>AttService: attendance? (optional)
AttService->>Repo: save updated/created Attendance
Repo->>DB: INSERT/UPDATE attendance
AttService-->>RoundCtrl: AttendanceResponse
RoundCtrl-->>Admin: 200 OK + body
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45분
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (10)
backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionResponse.java (1)
67-69: 반경 값에 대한 유효성 검증 추가를 고려하세요.
radiusMeters필드에 유효성 검증 어노테이션이 없습니다. 이 DTO가 요청에도 사용되거나 향후 사용될 가능성이 있다면, 다음 사항을 고려하세요:
- 음수 값 방지
- 합리적인 최대값 설정 (예: 1000미터 이하)
- null 허용 여부 명확화
예시:
@Schema(description = "출석 인정 반경 (미터)", example = "100") @Positive(message = "반경은 양수여야 합니다") @Max(value = 1000, message = "반경은 1000미터를 초과할 수 없습니다") private Integer radiusMeters;또한,
Integer타입 사용으로 null 값이 허용됩니다. 이것이 의도된 동작(선택적 필드)인지 확인하고, 필수 필드라면@NotNull어노테이션 추가를 검토하세요.backend/src/main/java/org/sejongisc/backend/attendance/dto/RoundAttendanceResponse.java (2)
17-20: 응답 DTO의 불변성을 고려해보세요.현재
@Data어노테이션은 setter를 포함하고 있습니다. 응답 DTO는 일반적으로 불변 객체로 만드는 것이 thread-safety와 명확성 측면에서 더 좋습니다.다음 diff를 적용하여 불변 DTO로 변경할 수 있습니다:
-@Data +@Value @Builder -@NoArgsConstructor -@AllArgsConstructor참고:
@Value는 모든 필드를final로 만들고 setter를 생성하지 않으며,@Builder와 함께 사용할 때@AllArgsConstructor가 자동으로 포함됩니다. Jackson 역직렬화가 필요한 경우 생성자 기반 역직렬화를 사용합니다.
23-30: 필드 정의가 적절합니다.
UUID타입의 userId는 보안과 고유성 측면에서 좋습니다.AttendanceStatusenum 사용으로 타입 안전성을 확보했습니다.- 응답 DTO이므로 validation 어노테이션이 없는 것이 올바릅니다.
선택적 제안:
@JsonProperty어노테이션의 값이 필드명과 동일한 경우, Jackson의 기본 설정에서는 중복입니다. 명시성을 위해 유지하는 것도 좋지만, 제거해도 동작에는 영향이 없습니다.backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceStatusUpdateRequest.java (1)
20-21:status필드에 enum 타입 사용 고려현재
String타입으로 선언되어 있어 유효하지 않은 값이 서비스 레이어까지 전달됩니다.AttendanceStatusenum을 직접 사용하면 역직렬화 시점에 유효성 검증이 가능합니다.+import org.sejongisc.backend.attendance.entity.AttendanceStatus; + @Data @Builder @NoArgsConstructor @AllArgsConstructor public class AttendanceStatusUpdateRequest { - @NotBlank(message = "출석 상태는 필수입니다 (PRESENT, LATE, ABSENT, EXCUSED)") - private String status; + @NotNull(message = "출석 상태는 필수입니다 (PRESENT, LATE, ABSENT, EXCUSED)") + private AttendanceStatus status; private String reason; }backend/src/main/java/org/sejongisc/backend/attendance/repository/AttendanceRoundRepository.java (1)
57-59: 메서드 이름과 엔티티 필드명 불일치메서드 이름에서
Session_SessionId를 사용하고 있으나, 엔티티에서는attendanceSession.attendanceSessionId를 사용합니다.@Query가 명시되어 있어 동작에는 문제가 없지만, 일관성을 위해 네이밍을 맞추는 것이 좋습니다.- List<AttendanceRound> findBySession_SessionIdAndRoundDateBefore( + List<AttendanceRound> findByAttendanceSession_AttendanceSessionIdAndRoundDateBefore( @Param("sessionId") UUID sessionId, @Param("date") LocalDate date);backend/src/main/java/org/sejongisc/backend/attendance/repository/SessionUserRepository.java (1)
4-4: 사용되지 않는 import입니다.
User클래스는 이 인터페이스에서 사용되지 않습니다.-import org.sejongisc.backend.user.entity.User;backend/src/main/java/org/sejongisc/backend/attendance/service/SessionUserService.java (3)
44-59: 예외 처리 패턴 일관성을 검토해 주세요.
IllegalArgumentException대신 프로젝트의CustomException과ErrorCode패턴을 사용하는 것이 일관성 있을 수 있습니다.CustomUserDetailsService에서CustomException(ErrorCode.USER_NOT_FOUND)를 사용하는 것을 참고하세요.- AttendanceSession session = attendanceSessionRepository.findById(sessionId) - .orElseThrow(() -> new IllegalArgumentException("세션을 찾을 수 없습니다: " + sessionId)); + AttendanceSession session = attendanceSessionRepository.findById(sessionId) + .orElseThrow(() -> new CustomException(ErrorCode.SESSION_NOT_FOUND)); - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다: " + userId)); + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
72-103: 과거 라운드 자동 결석 처리 로직이 잘 구현되었습니다.중복 체크 후 결석 기록을 생성하는 방식이 적절합니다. 다만, Line 102의 로그 메시지가 실제 생성된 레코드 수가 아닌 전체 과거 라운드 수를 출력합니다. 정확한 로깅을 원하시면 카운터를 추가하는 것을 고려해 주세요.
154-169: 세션 참여자 조회 메서드가 잘 구현되었습니다.
@Transactional(readOnly = true)사용이 적절합니다. 세션 존재 여부만 확인하는 용도라면existsById를 사용하여 불필요한 엔티티 로딩을 줄일 수 있습니다.- AttendanceSession session = attendanceSessionRepository.findById(sessionId) - .orElseThrow(() -> new IllegalArgumentException("세션을 찾을 수 없습니다: " + sessionId)); + if (!attendanceSessionRepository.existsById(sessionId)) { + throw new IllegalArgumentException("세션을 찾을 수 없습니다: " + sessionId); + }backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceRoundController.java (1)
10-10: DTO 와일드카드 import 대신 명시적 import 사용을 고려해 주세요현재
org.sejongisc.backend.attendance.dto.*와일드카드 import는 사용 DTO 파악이 어렵고, 향후 클래스 이름 충돌 가능성을 키울 수 있습니다. 이 파일에서 사용하는 DTO가 많지 않으므로 명시적으로 나열하는 편이 유지보수에 더 유리해 보입니다.예시:
-import org.sejongisc.backend.attendance.dto.*; +import org.sejongisc.backend.attendance.dto.AttendanceRoundRequest; +import org.sejongisc.backend.attendance.dto.AttendanceRoundResponse; +import org.sejongisc.backend.attendance.dto.AttendanceCheckInRequest; +import org.sejongisc.backend.attendance.dto.AttendanceCheckInResponse; +import org.sejongisc.backend.attendance.dto.AttendanceResponse; +import org.sejongisc.backend.attendance.dto.AttendanceStatusUpdateRequest;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceController.java(0 hunks)backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceRoundController.java(2 hunks)backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java(3 hunks)backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionResponse.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceStatusUpdateRequest.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/dto/RoundAttendanceResponse.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/dto/SessionUserRequest.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/dto/SessionUserResponse.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/entity/SessionUser.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/repository/AttendanceRepository.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/repository/AttendanceRoundRepository.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/repository/SessionUserRepository.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceSessionService.java(0 hunks)backend/src/main/java/org/sejongisc/backend/attendance/service/SessionUserService.java(1 hunks)
💤 Files with no reviewable changes (2)
- backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceController.java
- backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceSessionService.java
🧰 Additional context used
🧬 Code graph analysis (4)
backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceStatusUpdateRequest.java (3)
backend/src/main/java/org/sejongisc/backend/attendance/dto/RoundAttendanceResponse.java (1)
Data(17-31)backend/src/main/java/org/sejongisc/backend/attendance/dto/SessionUserRequest.java (1)
Data(11-19)frontend/src/utils/axios.js (1)
message(43-46)
backend/src/main/java/org/sejongisc/backend/attendance/dto/SessionUserRequest.java (2)
backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceStatusUpdateRequest.java (1)
Data(14-24)backend/src/main/java/org/sejongisc/backend/attendance/dto/SessionUserResponse.java (1)
Data(11-26)
backend/src/main/java/org/sejongisc/backend/attendance/service/SessionUserService.java (1)
backend/src/main/java/org/sejongisc/backend/common/auth/springsecurity/CustomUserDetailsService.java (1)
RequiredArgsConstructor(16-38)
backend/src/main/java/org/sejongisc/backend/attendance/dto/RoundAttendanceResponse.java (1)
backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceStatusUpdateRequest.java (1)
Data(14-24)
🔇 Additional comments (13)
backend/src/main/java/org/sejongisc/backend/attendance/dto/RoundAttendanceResponse.java (1)
12-16: 문서화가 잘 되어 있습니다!엔드포인트와 응답 형식을 명확하게 설명하는 JavaDoc이 작성되어 있어 좋습니다.
backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java (1)
296-348: LGTM!라운드 기반 출석 상태 수정 로직이 잘 구현되어 있습니다. 라운드와 사용자 검증, 상태 값 파싱, 기존 기록 유무에 따른 생성/업데이트 분기 처리가 적절합니다. 로깅도 충분히 상세합니다.
backend/src/main/java/org/sejongisc/backend/attendance/repository/AttendanceRepository.java (1)
63-65: LGTM!세션 내 특정 사용자의 모든 출석 기록(라운드 관계없이)을 조회하는 메서드가 적절하게 추가되었습니다. 기존
findByAttendanceSessionAndUser_UserId(단일 Optional 반환)와 용도가 명확히 구분됩니다.backend/src/main/java/org/sejongisc/backend/attendance/dto/SessionUserRequest.java (1)
11-19: LGTM!프로젝트의 다른 DTO들과 일관된 패턴으로 구현되었습니다.
@NotNull검증과 Lombok 어노테이션 사용이 적절합니다.backend/src/main/java/org/sejongisc/backend/attendance/dto/SessionUserResponse.java (1)
11-26: LGTM!DTO 구조가 적절하며, Lombok 어노테이션 사용과 필드 구성이
SessionUser엔티티와 잘 매핑됩니다.backend/src/main/java/org/sejongisc/backend/attendance/entity/SessionUser.java (2)
21-37: 엔티티 설계가 잘 되어 있습니다.유니크 제약 조건과 인덱스 설정이 적절하며, LAZY 페치 전략을 사용한 점이 좋습니다.
userName캐싱 방식도 의도가 명확하게 문서화되어 있습니다.
55-67: 순환 참조 방지를 위한toString오버라이드가 적절합니다.null 체크가 포함되어 있어 안전합니다. 다만, LAZY 로딩된 엔티티의 ID 접근 시 트랜잭션 외부에서 호출되면
LazyInitializationException이 발생할 수 있으니 주의가 필요합니다.backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java (2)
298-317:GET /{sessionId}/users엔드포인트에 권한 검증이 누락되었습니다.다른 관리자 전용 엔드포인트들과 달리 이 엔드포인트에는
@PreAuthorize어노테이션이 없습니다. 세션 참여자 정보가 민감할 수 있으므로 의도된 것인지 확인이 필요합니다.의도적으로 공개 API로 설계한 것이라면 주석이나 API 설명에 이를 명시해 주세요. 그렇지 않다면 다음과 같이 수정하세요:
@GetMapping("/{sessionId}/users") +@PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')") public ResponseEntity<List<SessionUserResponse>> getSessionUsers(@PathVariable UUID sessionId) {
248-296: 사용자 추가/제거 엔드포인트가 잘 구현되었습니다.
- HTTP 상태 코드(201 CREATED, 204 No Content)가 RESTful 규약에 맞게 적용되었습니다.
- 권한 검증과 로깅이 기존 패턴과 일관되게 구현되었습니다.
backend/src/main/java/org/sejongisc/backend/attendance/repository/SessionUserRepository.java (1)
17-45: JPQL 쿼리가 잘 정의되어 있습니다.정렬 순서, 파라미터 바인딩, 존재 여부 확인 쿼리 등이 적절하게 구현되었습니다.
backend/src/main/java/org/sejongisc/backend/attendance/service/SessionUserService.java (2)
116-149: 사용자 제거 로직이 적절합니다.
SessionUser와 관련된Attendance레코드를 함께 삭제하는 로직이 잘 구현되었습니다. 다만,SessionUserRepository.deleteBySessionIdAndUserId에@Modifying어노테이션이 필요합니다 (해당 파일의 리뷰 코멘트 참조).
179-190: DTO 변환 메서드가 간결하게 구현되었습니다.트랜잭션 컨텍스트 내에서 호출되므로 LAZY 로딩된 엔티티 접근에 문제가 없습니다.
backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceRoundController.java (1)
252-275: 출석 상태 수정 엔드포인트 구현이 컨벤션과 잘 맞습니다
- URL, HTTP 메서드,
@PreAuthorize권한 설정이 기존 컨트롤러 패턴과 일관적이고,- 요청 DTO(
AttendanceStatusUpdateRequest) + 응답 DTO(AttendanceResponse) 조합도 명확하며,- 상태/사유를 모두 로깅하고 서비스 레이어로 위임하는 구조가 적절해 보입니다.
컨트롤러 레벨에서 별도 예외 처리를 하지 않고 서비스/글로벌 예외 핸들러에 위임하는 구조라면, 현재 구현 그대로 사용해도 무방해 보입니다.
backend/src/main/java/org/sejongisc/backend/attendance/repository/SessionUserRepository.java
Show resolved
Hide resolved
backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
backend/src/main/java/org/sejongisc/backend/attendance/service/SessionUserService.java (1)
141-145: 삭제된 엔티티 순회 로깅 확인
deleteAll()호출 후 삭제된 엔티티들을 순회하며 로깅하고 있습니다. 메모리상의 객체는 여전히 유효하므로 동작에는 문제가 없으나, 로그 순서상 삭제 완료 메시지(Line 140) 이후에 개별 항목을 로깅하는 것이 의미적으로 혼란스러울 수 있습니다.- if (!attendancesToDelete.isEmpty()) { - log.info("🗑️ Attendance 레코드 삭제 시작: 삭제 대상 수={}", attendancesToDelete.size()); - - attendanceRepository.deleteAll(attendancesToDelete); - - log.info("✅ Attendance 레코드 삭제 완료: 삭제된 레코드 수={}", attendancesToDelete.size()); - for (Attendance a : attendancesToDelete) { - log.info(" - 삭제됨: roundId={}, status={}", - a.getAttendanceRound() != null ? a.getAttendanceRound().getRoundId() : "null", - a.getAttendanceStatus()); - } - } + if (!attendancesToDelete.isEmpty()) { + log.info("🗑️ Attendance 레코드 삭제 시작: 삭제 대상 수={}", attendancesToDelete.size()); + for (Attendance a : attendancesToDelete) { + log.info(" - 삭제 대상: roundId={}, status={}", + a.getAttendanceRound() != null ? a.getAttendanceRound().getRoundId() : "null", + a.getAttendanceStatus()); + } + + attendanceRepository.deleteAll(attendancesToDelete); + log.info("✅ Attendance 레코드 삭제 완료: 삭제된 레코드 수={}", attendancesToDelete.size()); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
backend/src/main/java/org/sejongisc/backend/attendance/repository/SessionUserRepository.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java(1 hunks)backend/src/main/java/org/sejongisc/backend/attendance/service/SessionUserService.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/src/main/java/org/sejongisc/backend/attendance/service/SessionUserService.java (1)
backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java (1)
Service(21-380)
🔇 Additional comments (6)
backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java (1)
291-349: LGTM!checkedAt누락 문제가 해결되었습니다.이전 리뷰에서 지적된
checkedAt필드 누락 문제가 Line 331에서java.time.LocalDateTime.now()로 설정되어 해결되었습니다. 라운드 기반 출석 상태 수정 로직이 올바르게 구현되어 있습니다.backend/src/main/java/org/sejongisc/backend/attendance/repository/SessionUserRepository.java (1)
1-61: LGTM!@Modifying어노테이션이 올바르게 추가되었습니다.이전 리뷰에서 지적된 DELETE 쿼리의
@Modifying어노테이션 누락 문제가 Line 51, 58에서 해결되었습니다. 레포지토리 인터페이스가 올바르게 구현되어 있습니다.backend/src/main/java/org/sejongisc/backend/attendance/service/SessionUserService.java (4)
44-110: 세션 사용자 추가 로직이 잘 구현되었습니다.사용자 추가 시 과거 라운드에 대한 자동 결석 처리 로직(Line 72-104)이 적절하게 구현되어 있습니다. 중복 참여 검증, 유효성 검사, 그리고 포괄적인 로깅이 포함되어 있어 관리 및 디버깅에 용이합니다.
156-178: 조회 메서드들이 올바르게 구현되었습니다.
getSessionUsers와isUserInSession메서드가@Transactional(readOnly = true)로 적절하게 설정되어 있어 읽기 성능 최적화가 잘 되어 있습니다.
133-133: MethodfindAllBySessionAndUserIdexists and is correctly defined.The method is properly defined at line 65 in
AttendanceRepository.javawith the correct signature and@Queryannotation. The call at line 133 inSessionUserService.javamatches the method signature exactly (both parameters align with their types:AttendanceSession sessionandUUID userId). No runtime error will occur.
73-76: Review comment is incorrect - no issue foundThe method
findBySession_SessionIdAndRoundDateBeforeexists and is correctly implemented inAttendanceRoundRepository(lines 54-61). It uses a@Queryannotation with explicit JPQL that properly referencesr.attendanceSession.attendanceSessionId, which correctly maps to theAttendanceRoundentity's field nameattendanceSession. The method call inSessionUserService.java(line 73) is valid and requires no changes.
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.