20260226 #236 관리자 페이지 통계 대시보드 실시간 활동 로그 추가#249
Hidden character warning
Conversation
회원가입, 로그인, 베팅 참여, 게시물 작성, 출석 체크인에 이벤트 추가 완료 백테스팅, 댓글, 좋아요 이벤트 추가 고려 QrStreamService에 SseService 코드 사용 고려
백테스팅, 퀀트봇에 이벤트 발생 고려 QrStreamService에 SseService 코드 사용 고려 yml 수정에 따른 securityConfig 수정
Walkthrough새로운 액티비티 로깅 시스템을 도입해 서비스 동작(출석, 게시물/댓글/좋아요, 베팅, 로그인/가입)을 ActivityEvent로 발행하고 DB에 저장한 뒤 SSE로 실시간 대시보드에 전송하도록 통합하였습니다. 또한 일부 서비스 메서드 시그니처를 사용자명 인자를 받도록 확장하고 프론트엔드 URL 설정을 단일 프로퍼티로 통합했습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Service as 서비스 (Attendance/Betting/Board)
participant EventPublisher as ApplicationEventPublisher
participant Listener as ActivityEventListener
participant Repository as ActivityLogRepository
participant SseService as SseService
User->>Service: 행위 수행 (출석/베팅/댓글/게시글/로그인/가입)
Service->>Service: 비즈니스 로직 실행
Service->>EventPublisher: publishEvent(ActivityEvent)
EventPublisher->>Listener: handleActivityEvent() (`@TransactionalEventListener` AFTER_COMMIT)
Listener->>Repository: save(ActivityLog)
Repository-->>Listener: 저장 완료
Listener->>SseService: send(ADMIN_DASHBOARD, eventName, ActivityLog)
SseService->>SseService: 채널의 emitters 순회 및 전송
SseService-->>User: SSE 이벤트 전송 (구독자에게)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Suggested reviewers
토끼의 시
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTransactionalTest.java (1)
70-77:⚠️ Potential issue | 🔴 Critical테스트가
postUserBet을 호출하지 않아verify()호출이 실패합니다.
assertThatThrownBy전체가 주석 처리되어 있으므로bettingService.postUserBet()메서드 호출 자체가 실행되지 않습니다. 따라서verify(pointHistoryRepository, times(1)).save(...)및verify(userBetRepository, times(1)).save(...)는 "Wanted but not invoked" 오류로 실패할 것입니다.또한
postUserBet()메서드 시그니처가 3개 파라미터를 요구하지만(userId, username, userBetRequest), 테스트는 2개만 전달하고 있습니다.제안된 수정사항
- //assertThatThrownBy(() -> bettingService.postUserBet(userId, req)).isInstanceOf(RuntimeException.class).hasMessageContaining("외부 트랜잭션 롤백"); + assertThatThrownBy(() -> bettingService.postUserBet(userId, "testUser", req)) + .isInstanceOf(RuntimeException.class); verify(pointHistoryRepository, times(1)).save(any(PointHistory.class)); verify(userBetRepository, times(1)).save(any());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTransactionalTest.java` around lines 70 - 77, The test fails because postUserBet(...) is never invoked and the wrong overload is used; uncomment and call bettingService.postUserBet with the correct three-argument signature (userId, username, userBetRequest) inside the assertThatThrownBy so the RuntimeException thrown by the mocked userBetRepository.save triggers and the verifications run; ensure the assertThatThrownBy wraps bettingService.postUserBet(userId, username, req) and then verify pointHistoryRepository.save(...) and userBetRepository.save(...) were each called once.
🧹 Nitpick comments (6)
backend/src/main/java/org/sejongisc/backend/attendance/service/QrTokenStreamService.java (1)
107-108: Line 107 URL 결합 시 슬래시 정규화를 권장합니다.
app.frontend-url에 trailing slash가 들어오면//attendance가 생성될 수 있어 URL 포맷을 고정해두는 편이 안전합니다.제안 코드
public String createQrUrl(UUID roundId, String token) { - String baseUrl = frontendUrl; + String baseUrl = frontendUrl.endsWith("/") + ? frontendUrl.substring(0, frontendUrl.length() - 1) + : frontendUrl; return String.format("%s%s?roundId=%s&token=%s", baseUrl, ATTENDANCE_PATH, roundId, token); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/org/sejongisc/backend/attendance/service/QrTokenStreamService.java` around lines 107 - 108, The URL builder in QrTokenStreamService that does return String.format("%s%s?roundId=%s&token=%s", baseUrl, ATTENDANCE_PATH, roundId, token) can produce a double slash if app.frontend-url has a trailing slash; normalize the base and path before joining by trimming any trailing '/' from frontendUrl (or ensure ATTENDANCE_PATH is normalized to start with a single '/'), or use a standard join/resolve approach (e.g., URI.resolve) to ensure exactly one separator between base and ATTENDANCE_PATH; update the code that sets baseUrl and/or the ATTENDANCE_PATH constant and then keep the same formatting for query parameters.backend/src/main/java/org/sejongisc/backend/common/sse/SseService.java (2)
55-62:removeEmitter에서 경합 조건 가능성 (낮은 영향도)
list.isEmpty()확인과emitters.remove(channelId)사이에 다른 스레드가 emitter를 추가할 수 있습니다. 실제로 SSE 클라이언트가 재연결하면 되므로 영향은 미미하지만,compute를 사용하면 원자적으로 처리할 수 있습니다.🔧 원자적 제거 방식
public void removeEmitter(String channelId, SseEmitter emitter) { - CopyOnWriteArrayList<SseEmitter> list = emitters.get(channelId); - if (list != null) { - list.remove(emitter); - if (list.isEmpty()) { - emitters.remove(channelId); - } - } + emitters.computeIfPresent(channelId, (key, list) -> { + list.remove(emitter); + return list.isEmpty() ? null : list; + }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/org/sejongisc/backend/common/sse/SseService.java` around lines 55 - 62, The removeEmitter method has a race between list.isEmpty() and emitters.remove(channelId); change it to perform the remove-and-maybe-delete atomically using ConcurrentMap.compute/computeIfPresent on emitters: inside removeEmitter use emitters.compute(channelId, (k, list) -> { if list==null return null; remove the provided SseEmitter from list; return list.isEmpty() ? null : list; }) so the removal and channel cleanup happen in one atomic operation on the map.
43-45: 예외 발생 시 로깅 추가를 권장합니다.
send()메서드에서 예외 발생 시 로깅 없이 emitter만 제거하고 있습니다. 디버깅을 위해 경고 레벨 로그를 추가하는 것이 좋습니다.🔧 제안된 수정사항
} catch (Exception e) { + log.warn("SSE 전송 실패: channelId={}, eventName={}", channelId, eventName, e); removeEmitter(channelId, emitter); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/org/sejongisc/backend/common/sse/SseService.java` around lines 43 - 45, send()의 catch (Exception e) 블록에서 단순히 removeEmitter(channelId, emitter)만 호출하고 예외를 기록하지 않으므로 디버깅이 어렵습니다; 해당 블록에 SseService의 로거(예: private static final Logger log 또는 log 변수)를 사용해 channelId와 관련 정보(가능하면 emitter 식별자)를 포함한 경고 수준 로그를 남기고, 예외 객체(e)를 함께 전달하여 스택트레이스가 기록되도록 변경하세요(즉, send()의 catch문에서 먼저 log.warn("...", channelId, ..., e) 호출 후 removeEmitter(channelId, emitter) 호출).backend/src/main/java/org/sejongisc/backend/activity/listener/ActivityEventListener.java (1)
20-35: 비동기 이벤트 처리에 에러 핸들링 추가를 권장합니다.
@Async실행 시 예외가 발생하면 호출자에게 전파되지 않고 조용히 실패합니다. 로그 저장이나 SSE 전송 실패 시 최소한 로깅을 통해 문제를 추적할 수 있도록 에러 핸들링을 추가하는 것이 좋습니다.♻️ 에러 핸들링 추가 제안
+import lombok.extern.slf4j.Slf4j; + `@Component` `@RequiredArgsConstructor` +@Slf4j public class ActivityEventListener { private final ActivityLogRepository activityLogRepository; private final SseService sseService; // 실시간 전송용 서비스 `@Async` `@TransactionalEventListener`(phase = TransactionPhase.AFTER_COMMIT) public void handleActivityEvent(ActivityEvent event) { + try { // DB 저장 (마이페이지 및 관리자 통계용) ActivityLog log = activityLogRepository.save(ActivityLog.builder() .userId(event.userId()) .username(event.username()) .type(event.type()) .message(event.message()) .targetId(event.targetId()) .boardName(event.boardName()) .build()); // 관리자 채널에 실시간 SSE 전송 (메인 대시보드 피드용) sseService.send("ADMIN_DASHBOARD", "newLog", log); + } catch (Exception e) { + log.error("활동 로그 처리 실패: userId={}, type={}", event.userId(), event.type(), e); + } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/org/sejongisc/backend/activity/listener/ActivityEventListener.java` around lines 20 - 35, Wrap the body of ActivityEventListener.handleActivityEvent in a try-catch that catches Exception, so failures during activityLogRepository.save(...) or sseService.send(...) are not silently swallowed by `@Async`; on catch, call the class logger (e.g., logger.error) with a clear message including identifying event data (event.userId(), event.type(), event.targetId(), event.boardName()) and the exception, and optionally record a metric or increment a failure counter — do not change transaction semantics, just ensure errors are logged for diagnosis.backend/src/main/java/org/sejongisc/backend/activity/repository/ActivityLogRepository.java (1)
27-30: 게시판 활동 집계 쿼리에서도 동일한 enum 리터럴 이슈
'BOARD_POST','BOARD_COMMENT','BOARD_LIKE'문자열 리터럴 사용에 동일한 주의가 필요합니다. 대용량 데이터에서 성능을 위해createdAt과type컬럼에 복합 인덱스 추가를 고려해 주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/org/sejongisc/backend/activity/repository/ActivityLogRepository.java` around lines 27 - 30, The JPQL in ActivityLogRepository uses hard-coded enum string literals ('BOARD_POST','BOARD_COMMENT','BOARD_LIKE') which is brittle; change the query to use a parameterized enum list (e.g., a :types parameter of List<ActivityType>) or use fully-qualified enum literals instead, updating the repository method signature to accept List<ActivityType> and bind it to :types in the `@Query` on ActivityLogRepository; additionally, add a composite DB index on (type, created_at) (or createdAt/type depending on your DB) to improve performance for large datasets when filtering by type and date.backend/src/main/java/org/sejongisc/backend/activity/entity/ActivityLog.java (1)
42-42:createdAt필드에@Column어노테이션 추가 고려.다른 필드들과 달리
createdAt에는@Column어노테이션이 없습니다. 일관성을 위해 추가하거나, Hibernate의@CreationTimestamp를 사용하면 생성자에서 직접 설정하지 않아도 됩니다.옵션 1: `@Column` 추가
+ `@Column`(nullable = false, updatable = false) private LocalDateTime createdAt;옵션 2: `@CreationTimestamp` 사용
+import org.hibernate.annotations.CreationTimestamp;+ `@CreationTimestamp` + `@Column`(nullable = false, updatable = false) private LocalDateTime createdAt;이 경우 생성자에서
this.createdAt = LocalDateTime.now();라인을 제거할 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/org/sejongisc/backend/activity/entity/ActivityLog.java` at line 42, The createdAt field in ActivityLog lacks column/configuration annotations; update ActivityLog to either (A) add `@Column` on the private LocalDateTime createdAt field to match other fields, or (B) prefer using Hibernate's `@CreationTimestamp` on createdAt and remove the manual initialization (this.createdAt = LocalDateTime.now()) from the ActivityLog constructor so creation time is set automatically. Ensure imports for javax.persistence.Column or org.hibernate.annotations.CreationTimestamp are added and that the field remains private LocalDateTime createdAt.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/src/main/java/org/sejongisc/backend/activity/entity/ActivityLog.java`:
- Around line 35-36: The message field in ActivityLog is currently
`@Column`(nullable = false) which defaults to VARCHAR(255); decide whether
messages may exceed 255 chars and update the mapping accordingly: either set an
explicit length (e.g., add length=512 or another appropriate size) on the
message column or switch to a text-capable mapping (e.g., use `@Lob` or
columnDefinition="TEXT") to allow longer content; update the ActivityLog.message
annotation to the chosen option so the DB schema reflects the intended max
length.
In
`@backend/src/main/java/org/sejongisc/backend/common/config/security/SecurityConfig.java`:
- Around line 131-134: The call to config.setAllowedOriginPatterns(List.of(...))
can throw NPE if env.getProperty("app.frontend-url") returns null; change the
code that builds the allowed-origin list used by config.setAllowedOriginPatterns
to omit null values (e.g., collect only non-null/blank entries or provide a
default) so that the list contains no null elements; update the code where
env.getProperty("app.frontend-url") is referenced (the expression passed into
config.setAllowedOriginPatterns) to filter or conditionally add the property
value before calling config.setAllowedOriginPatterns (for example, build the
list via a stream or conditional add and then pass the resulting non-null list).
In `@backend/src/main/java/org/sejongisc/backend/user/service/UserService.java`:
- Around line 85-90: The ActivityEvent for user signup is being published with
ActivityType.ATTENDANCE which is semantically incorrect; update the event to use
a SIGNUP activity type (replace ActivityType.ATTENDANCE with ActivityType.SIGNUP
in the publishEvent call in UserService where ActivityEvent is constructed) and
if ActivityType.SIGNUP does not exist, add SIGNUP to the ActivityType enum so
the new type can be used for signup events and statistics.
---
Outside diff comments:
In
`@backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTransactionalTest.java`:
- Around line 70-77: The test fails because postUserBet(...) is never invoked
and the wrong overload is used; uncomment and call bettingService.postUserBet
with the correct three-argument signature (userId, username, userBetRequest)
inside the assertThatThrownBy so the RuntimeException thrown by the mocked
userBetRepository.save triggers and the verifications run; ensure the
assertThatThrownBy wraps bettingService.postUserBet(userId, username, req) and
then verify pointHistoryRepository.save(...) and userBetRepository.save(...)
were each called once.
---
Nitpick comments:
In
`@backend/src/main/java/org/sejongisc/backend/activity/entity/ActivityLog.java`:
- Line 42: The createdAt field in ActivityLog lacks column/configuration
annotations; update ActivityLog to either (A) add `@Column` on the private
LocalDateTime createdAt field to match other fields, or (B) prefer using
Hibernate's `@CreationTimestamp` on createdAt and remove the manual initialization
(this.createdAt = LocalDateTime.now()) from the ActivityLog constructor so
creation time is set automatically. Ensure imports for javax.persistence.Column
or org.hibernate.annotations.CreationTimestamp are added and that the field
remains private LocalDateTime createdAt.
In
`@backend/src/main/java/org/sejongisc/backend/activity/listener/ActivityEventListener.java`:
- Around line 20-35: Wrap the body of ActivityEventListener.handleActivityEvent
in a try-catch that catches Exception, so failures during
activityLogRepository.save(...) or sseService.send(...) are not silently
swallowed by `@Async`; on catch, call the class logger (e.g., logger.error) with a
clear message including identifying event data (event.userId(), event.type(),
event.targetId(), event.boardName()) and the exception, and optionally record a
metric or increment a failure counter — do not change transaction semantics,
just ensure errors are logged for diagnosis.
In
`@backend/src/main/java/org/sejongisc/backend/activity/repository/ActivityLogRepository.java`:
- Around line 27-30: The JPQL in ActivityLogRepository uses hard-coded enum
string literals ('BOARD_POST','BOARD_COMMENT','BOARD_LIKE') which is brittle;
change the query to use a parameterized enum list (e.g., a :types parameter of
List<ActivityType>) or use fully-qualified enum literals instead, updating the
repository method signature to accept List<ActivityType> and bind it to :types
in the `@Query` on ActivityLogRepository; additionally, add a composite DB index
on (type, created_at) (or createdAt/type depending on your DB) to improve
performance for large datasets when filtering by type and date.
In
`@backend/src/main/java/org/sejongisc/backend/attendance/service/QrTokenStreamService.java`:
- Around line 107-108: The URL builder in QrTokenStreamService that does return
String.format("%s%s?roundId=%s&token=%s", baseUrl, ATTENDANCE_PATH, roundId,
token) can produce a double slash if app.frontend-url has a trailing slash;
normalize the base and path before joining by trimming any trailing '/' from
frontendUrl (or ensure ATTENDANCE_PATH is normalized to start with a single
'/'), or use a standard join/resolve approach (e.g., URI.resolve) to ensure
exactly one separator between base and ATTENDANCE_PATH; update the code that
sets baseUrl and/or the ATTENDANCE_PATH constant and then keep the same
formatting for query parameters.
In `@backend/src/main/java/org/sejongisc/backend/common/sse/SseService.java`:
- Around line 55-62: The removeEmitter method has a race between list.isEmpty()
and emitters.remove(channelId); change it to perform the remove-and-maybe-delete
atomically using ConcurrentMap.compute/computeIfPresent on emitters: inside
removeEmitter use emitters.compute(channelId, (k, list) -> { if list==null
return null; remove the provided SseEmitter from list; return list.isEmpty() ?
null : list; }) so the removal and channel cleanup happen in one atomic
operation on the map.
- Around line 43-45: send()의 catch (Exception e) 블록에서 단순히
removeEmitter(channelId, emitter)만 호출하고 예외를 기록하지 않으므로 디버깅이 어렵습니다; 해당 블록에
SseService의 로거(예: private static final Logger log 또는 log 변수)를 사용해 channelId와 관련
정보(가능하면 emitter 식별자)를 포함한 경고 수준 로그를 남기고, 예외 객체(e)를 함께 전달하여 스택트레이스가 기록되도록
변경하세요(즉, send()의 catch문에서 먼저 log.warn("...", channelId, ..., e) 호출 후
removeEmitter(channelId, emitter) 호출).
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
.github/workflows/deploy.ymlbackend/src/main/java/org/sejongisc/backend/activity/entity/ActivityLog.javabackend/src/main/java/org/sejongisc/backend/activity/entity/ActivityType.javabackend/src/main/java/org/sejongisc/backend/activity/event/ActivityEvent.javabackend/src/main/java/org/sejongisc/backend/activity/listener/ActivityEventListener.javabackend/src/main/java/org/sejongisc/backend/activity/repository/ActivityLogRepository.javabackend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceController.javabackend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.javabackend/src/main/java/org/sejongisc/backend/attendance/service/QrTokenStreamService.javabackend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.javabackend/src/main/java/org/sejongisc/backend/betting/service/BettingService.javabackend/src/main/java/org/sejongisc/backend/board/repository/PostRepository.javabackend/src/main/java/org/sejongisc/backend/board/service/PostInteractionService.javabackend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.javabackend/src/main/java/org/sejongisc/backend/common/auth/service/AuthService.javabackend/src/main/java/org/sejongisc/backend/common/config/db/PrimaryDataSourceConfig.javabackend/src/main/java/org/sejongisc/backend/common/config/db/StockDataSourceConfig.javabackend/src/main/java/org/sejongisc/backend/common/config/security/SecurityConfig.javabackend/src/main/java/org/sejongisc/backend/common/sse/SseService.javabackend/src/main/java/org/sejongisc/backend/user/service/UserService.javabackend/src/main/resources/application-prod.ymlbackend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTransactionalTest.java
backend/src/main/java/org/sejongisc/backend/activity/entity/ActivityLog.java
Outdated
Show resolved
Hide resolved
backend/src/main/java/org/sejongisc/backend/common/config/security/SecurityConfig.java
Show resolved
Hide resolved
backend/src/main/java/org/sejongisc/backend/user/service/UserService.java
Show resolved
Hide resolved
oauth2를 더이상 쓰지 않으므로 변경
targetId String -> UUID로 변경 회원가입 시 출석 type 사용 오류 수정
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java (1)
44-44:checkIn의username인자는 제거해도 됩니다.Line 53에서
userRef를 이미 확보하므로, 이벤트 메시지는userRef.getName()으로 구성하면 중복 파라미터 없이 같은 결과를 얻고 호출부 결합도도 낮출 수 있습니다.Also applies to: 53-53, 74-79
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java` at line 44, Remove the redundant username parameter from AttendanceService.checkIn and compose any event/message using the resolved user reference instead: after obtaining userRef (userRef variable at the start of checkIn), replace uses of the removed username with userRef.getName() when building event payloads/messages; update the method signature (remove String username), adjust all internal references inside checkIn (e.g., event creation around line where userRef is used) and update all callers to pass only the UUID (or to call the new signature), ensuring compile-time fixes across the codebase.backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1)
75-84: 게시글 생성 이벤트는 첨부파일 처리 완료 후 발행하는 편이 의미적으로 더 정확합니다.현재는 게시글 저장 직후 이벤트를 먼저 발행합니다. “게시글 작성 완료”를 첨부파일까지 포함한 단위로 본다면, 파일 저장 성공 이후로 이벤트 시점을 이동하는 구성이 더 일관적입니다.
Also applies to: 86-103
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java` around lines 75 - 84, The ActivityEvent is being published immediately after postRepository.save(post) in PostServiceImpl (via eventPublisher.publishEvent(new ActivityEvent(...))), but it should be emitted only after attachment processing completes; move the publishEvent call so it runs after successful file/attachment save logic (the same change must be applied to the other occurrence referenced around lines 86-103), using the saved Post and board data (post.getPostId(), board.getBoardName(), user.getName()) to populate the ActivityEvent and ensuring any attachment failures prevent or rollback the event emission.backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java (1)
147-147:username을 파라미터로 전달받기보다 서비스 내부에서 일관되게 결정하는 편이 안전합니다.
userId를 이미 받고 있으므로 활동 로그용 사용자명도 같은 소스(사용자 엔티티/인증 컨텍스트)에서 조회해 기록하면, 호출부 의존과 데이터 불일치 가능성을 줄일 수 있습니다.Also applies to: 209-213
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java` at line 147, Change postUserBet to stop accepting the external username and instead resolve the username inside the service using the user source tied to userId: update the method signature postUserBet(UUID userId, UserBetRequest userBetRequest) (remove the String username param) and, inside postUserBet, load the User entity (e.g., via userRepository.findById(userId) or your authentication/context service) to obtain the canonical username for logging and activity creation; also update any related internal calls (the other affected block around lines 209-213) to pass only userId and UserBetRequest and to use the same resolved username when creating activity logs or UserBetResponse so there is no mismatch between caller-supplied and persisted usernames.backend/src/main/java/org/sejongisc/backend/activity/event/ActivityEvent.java (1)
7-14:ActivityEvent생성 실수를 줄이려면 타입별 팩토리 메서드가 더 안전합니다.현재처럼 nullable 필드를 포함한 위치 기반 record 생성은 호출부에서 인자 순서/
null위치 실수가 나기 쉽습니다.forAttendance(...),forBoardLike(...)같은 정적 팩토리를 두면 유지보수성이 좋아집니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/org/sejongisc/backend/activity/event/ActivityEvent.java` around lines 7 - 14, Add type-specific static factory methods to the ActivityEvent record (e.g., forAttendance(...), forBoardLike(...), forBoardComment(...)) that accept only the fields relevant to that ActivityType and return a properly populated ActivityEvent (setting unused fields to null). Use ActivityType enum values inside these methods and perform null checks on required args (userId, username, type-specific ids) so callers cannot accidentally swap positional parameters; keep the canonical record signature but prefer callers use these factories like ActivityEvent.forAttendance(userId, username, message) or ActivityEvent.forBoardLike(userId, username, targetId, boardName).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/src/main/java/org/sejongisc/backend/board/service/PostInteractionService.java`:
- Around line 88-95: The `@Retryable` methods createComment and toggleLike call
eventPublisher.publishEvent which can throw non-retryable exceptions (e.g.,
EventPublicationFailedException) and Spring Retry will look for a matching
`@Recover` method; add either a `@Recover` method in PostInteractionService to
handle those non-retryable exceptions (e.g., a recover method signature
accepting Throwable or the specific exception and the same parameters as the
retried method) or wrap the eventPublisher.publishEvent call in a try-catch
inside createComment and toggleLike to log/handle publication failures
separately so a missing `@Recover` does not trigger "Cannot locate recovery
method" errors.
---
Nitpick comments:
In
`@backend/src/main/java/org/sejongisc/backend/activity/event/ActivityEvent.java`:
- Around line 7-14: Add type-specific static factory methods to the
ActivityEvent record (e.g., forAttendance(...), forBoardLike(...),
forBoardComment(...)) that accept only the fields relevant to that ActivityType
and return a properly populated ActivityEvent (setting unused fields to null).
Use ActivityType enum values inside these methods and perform null checks on
required args (userId, username, type-specific ids) so callers cannot
accidentally swap positional parameters; keep the canonical record signature but
prefer callers use these factories like ActivityEvent.forAttendance(userId,
username, message) or ActivityEvent.forBoardLike(userId, username, targetId,
boardName).
In
`@backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java`:
- Line 44: Remove the redundant username parameter from
AttendanceService.checkIn and compose any event/message using the resolved user
reference instead: after obtaining userRef (userRef variable at the start of
checkIn), replace uses of the removed username with userRef.getName() when
building event payloads/messages; update the method signature (remove String
username), adjust all internal references inside checkIn (e.g., event creation
around line where userRef is used) and update all callers to pass only the UUID
(or to call the new signature), ensuring compile-time fixes across the codebase.
In
`@backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java`:
- Line 147: Change postUserBet to stop accepting the external username and
instead resolve the username inside the service using the user source tied to
userId: update the method signature postUserBet(UUID userId, UserBetRequest
userBetRequest) (remove the String username param) and, inside postUserBet, load
the User entity (e.g., via userRepository.findById(userId) or your
authentication/context service) to obtain the canonical username for logging and
activity creation; also update any related internal calls (the other affected
block around lines 209-213) to pass only userId and UserBetRequest and to use
the same resolved username when creating activity logs or UserBetResponse so
there is no mismatch between caller-supplied and persisted usernames.
In
`@backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java`:
- Around line 75-84: The ActivityEvent is being published immediately after
postRepository.save(post) in PostServiceImpl (via
eventPublisher.publishEvent(new ActivityEvent(...))), but it should be emitted
only after attachment processing completes; move the publishEvent call so it
runs after successful file/attachment save logic (the same change must be
applied to the other occurrence referenced around lines 86-103), using the saved
Post and board data (post.getPostId(), board.getBoardName(), user.getName()) to
populate the ActivityEvent and ensuring any attachment failures prevent or
rollback the event emission.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
backend/src/main/java/org/sejongisc/backend/activity/entity/ActivityLog.javabackend/src/main/java/org/sejongisc/backend/activity/entity/ActivityType.javabackend/src/main/java/org/sejongisc/backend/activity/event/ActivityEvent.javabackend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.javabackend/src/main/java/org/sejongisc/backend/betting/service/BettingService.javabackend/src/main/java/org/sejongisc/backend/board/service/PostInteractionService.javabackend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.javabackend/src/main/java/org/sejongisc/backend/common/config/security/SecurityConfig.javabackend/src/main/java/org/sejongisc/backend/user/service/UserService.java
🚧 Files skipped from review as they are similar to previous changes (3)
- backend/src/main/java/org/sejongisc/backend/activity/entity/ActivityLog.java
- backend/src/main/java/org/sejongisc/backend/activity/entity/ActivityType.java
- backend/src/main/java/org/sejongisc/backend/user/service/UserService.java
Summary by CodeRabbit
새 기능
개선 사항
기타