Skip to content

20260226 #236 관리자 페이지 통계 대시보드 실시간 활동 로그 추가#266

Merged
discipline24 merged 19 commits intomainfrom
20260226_#236_관리자_페이지_통계_대시보드_실시간_활동_로그_추가
Mar 2, 2026

Hidden character warning

The head ref may contain hidden characters: "20260226_#236_\uad00\ub9ac\uc790_\ud398\uc774\uc9c0_\ud1b5\uacc4_\ub300\uc2dc\ubcf4\ub4dc_\uc2e4\uc2dc\uac04_\ud65c\ub3d9_\ub85c\uadf8_\ucd94\uac00"
Merged

20260226 #236 관리자 페이지 통계 대시보드 실시간 활동 로그 추가#266
discipline24 merged 19 commits intomainfrom
20260226_#236_관리자_페이지_통계_대시보드_실시간_활동_로그_추가

Conversation

@discipline24
Copy link
Contributor

@discipline24 discipline24 commented Mar 2, 2026

Summary by CodeRabbit

  • 새로운 기능

    • 관리자 대시보드 추가: 보드·방문자 주간 요약, 방문자 트렌드 및 보드 활동 분포 조회
    • 실시간 활동 로그 스트리밍 및 활동 로그 페이지네이션 제공
    • 대시보드용 통계 응답 포맷(요약·트렌드·보드 활동) 추가
  • 기타 / 보안

    • 보드 삭제 API 간소화
    • 일부 관리자 엔드포인트 접근 권한이 PRESIDENT 또는 SYSTEM_ADMIN 역할로 제한/조정됨
    • 메서드 수준 보안 활성화 및 권한 범위 확대(사용자 목록 조회 권한)에 관한 변경 적용

회원가입, 로그인, 베팅 참여, 게시물 작성, 출석 체크인에 이벤트 추가 완료
백테스팅, 댓글, 좋아요 이벤트 추가 고려
QrStreamService에 SseService 코드 사용 고려
백테스팅, 퀀트봇에 이벤트 발생 고려
QrStreamService에 SseService 코드 사용 고려
yml 수정에 따른 securityConfig 수정
oauth2를 더이상 쓰지 않으므로 변경
targetId String -> UUID로 변경
회원가입 시 출석 type 사용 오류 수정
모든 메시지 형식에 행위 대상 제외
이관된 메서드
비밀번호 초기화 및 변경

uri 변경에 따른 인증 화이트리스트 변경
redis 관련 코드 리팩토링
주간 출석률 제외하고 구현
SecurityConfig에 @PreAuthorize 관련 코드 추가
@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a255a7 and b2f5d9e.

📒 Files selected for processing (3)
  • backend/src/main/java/org/sejongisc/backend/admin/controller/AdminBoardController.java
  • backend/src/main/java/org/sejongisc/backend/admin/controller/AdminDashboardController.java
  • backend/src/main/java/org/sejongisc/backend/admin/controller/AdminUserController.java

둘러보기

관리자 대시보드 통계·트렌드 엔드포인트와 실시간 SSE 활동 스트리밍을 추가하고, 보드 삭제 로직을 사용자 권한 검증 없이 하위 보드·게시물 연쇄 삭제로 변경하며, SSE 채널을 상수화하고 메서드 수준 보안을 활성화했습니다.

변경 사항

Cohort / File(s) Summary
SSE 채널 상수화
backend/src/main/java/org/sejongisc/backend/activity/listener/ActivityEventListener.java, backend/src/main/java/org/sejongisc/backend/admin/service/AdminDashboardService.java
하드코딩된 "ADMIN_DASHBOARD" 문자열을 ADMIN_CHANNEL 상수로 대체하고 상수 사용으로 SSE 구독/전송을 통일.
활동 로그 저장소 쿼리 확장
backend/src/main/java/org/sejongisc/backend/activity/repository/ActivityLogRepository.java
기존 findTop20ByOrderByCreatedAtDesc() 제거, 기간 기반 집계·방문자 트렌드·페이징 조회 등 새로운 쿼리 메서드 추가 및 시그니처 조정.
관리자 보드 삭제 리팩토링
backend/src/main/java/org/sejongisc/backend/admin/controller/AdminBoardController.java, backend/src/main/java/org/sejongisc/backend/admin/service/AdminBoardService.java
컨트롤러와 서비스의 deleteBoard 시그니처에서 사용자 ID 제거 및 권한 검증 로직 삭제; 지정 보드와 하위 보드의 게시물 삭제 후 보드 일괄 삭제로 변경.
관리자 대시보드 기능 추가
backend/src/main/java/org/sejongisc/backend/admin/controller/AdminDashboardController.java, backend/src/main/java/org/sejongisc/backend/admin/service/AdminDashboardService.java
주간 요약(게시물/댓글/좋아요), 방문자 요약·추세, 보드 활동 분포, SSE 실시간 스트림 구독, 최근 활동 페이징 등 신규 엔드포인트·서비스 추가.
대시보드 DTO 추가
backend/src/main/java/org/sejongisc/backend/admin/dto/dashboard/SummaryResponse.java, .../VisitorTrendResponse.java, .../BoardActivityResponse.java
대시보드 응답용 불변 레코드 타입 3종 추가.
보안·예외 구성 변경
backend/src/main/java/org/sejongisc/backend/common/config/security/SecurityConfig.java, backend/src/main/java/org/sejongisc/backend/common/exception/controller/GlobalExceptionHandler.java
메서드 수준 보안(@EnableMethodSecurity) 활성화 및 예외 로그 호출에서 Throwable 제거로 로깅 간소화.
접근 제약 변경(사용자 목록)
backend/src/main/java/org/sejongisc/backend/admin/controller/AdminUserController.java
getAllUsers 접근 권한을 SYSTEM_ADMIN에서 PRESIDENT 또는 SYSTEM_ADMIN으로 확대.

시퀀스 다이어그램

sequenceDiagram
    participant Client
    participant AdminDashboardController
    participant AdminDashboardService
    participant ActivityLogRepository
    participant SseService
    participant ActivityEventListener

    rect rgba(100, 150, 200, 0.5)
    Note over Client,AdminDashboardService: 주간 보드 요약 조회
    Client->>AdminDashboardController: GET /api/admin/dashboard/stats/boards/summary
    AdminDashboardController->>AdminDashboardService: getWeeklyBoardSummary()
    AdminDashboardService->>ActivityLogRepository: 기간별 집계 쿼리 호출
    ActivityLogRepository-->>AdminDashboardService: 집계 결과
    AdminDashboardService-->>AdminDashboardController: SummaryResponse
    AdminDashboardController-->>Client: 200 OK
    end

    rect rgba(150, 100, 200, 0.5)
    Note over Client,SseService: 실시간 활동 스트리밍 구독
    Client->>AdminDashboardController: GET /api/admin/dashboard/activities/stream
    AdminDashboardController->>AdminDashboardService: subscribeActivityStream()
    AdminDashboardService->>SseService: subscribe(ADMIN_CHANNEL)
    SseService-->>AdminDashboardService: SseEmitter
    AdminDashboardService->>SseService: send CONNECT 이벤트
    AdminDashboardService-->>AdminDashboardController: SseEmitter
    AdminDashboardController-->>Client: SseEmitter 응답
    Client->>ActivityEventListener: (외부) 활동 발생 이벤트
    ActivityEventListener->>SseService: send(ADMIN_CHANNEL, payload)
    SseService-->>Client: 실시간 푸시
    end

    rect rgba(200, 100, 150, 0.5)
    Note over Client,ActivityLogRepository: 최근 활동 페이징 조회
    Client->>AdminDashboardController: GET /api/admin/dashboard/activities?page=0&size=20
    AdminDashboardController->>AdminDashboardService: getRecentActivities(pageable)
    AdminDashboardService->>ActivityLogRepository: findAllByOrderByCreatedAtDesc(Pageable)
    ActivityLogRepository-->>AdminDashboardService: Slice<ActivityLog>
    AdminDashboardService-->>AdminDashboardController: Slice<ActivityLog>
    AdminDashboardController-->>Client: 200 OK
    end
Loading

예상 코드 리뷰 노력

🎯 3 (보통) | ⏱️ ~25분

관련 가능성 있는 PR

🐰 새벽 코드를 톡톡, 통계가 피어나네,

실시간 줄 따라 깡충깡충 흐르고,
채널은 이름 하나로 모였고,
보드는 조용히 가지를 따라 지워지네,
토끼는 당근 들고 배포 축하하네 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.17% 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 (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 20260226_#236_관리자_페이지_통계_대시보드_실시간_활동_로그_추가

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

Caution

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

⚠️ Outside diff range comments (1)
backend/src/main/java/org/sejongisc/backend/admin/service/AdminBoardService.java (1)

72-76: ⚠️ Potential issue | 🔴 Critical

부모 보드를 먼저 삭제하면 무결성 제약으로 실패할 수 있습니다.

Line 82에서 현재 순서대로 삭제하면 부모가 자식보다 먼저 삭제됩니다. 또한 Line 72-76은 직계 하위만 수집하므로 손자 보드가 있으면 누락됩니다. 전체 하위 트리를 수집하고 leaf -> root 순으로 삭제해야 안전합니다.

🛠 제안 수정안
-    List<UUID> targetBoardIds = Stream.concat(
-        Stream.of(boardId), // 자신 포함
-        boardRepository.findAllByParentBoard_BoardId(boardId).stream()
-            .map(Board::getBoardId)
-    ).toList();
+    List<UUID> targetBoardIds = collectDescendantBoardIds(boardId); // 자신 + 모든 하위 보드
@@
-    targetBoardIds.forEach(boardRepository::deleteById);
+    for (int i = targetBoardIds.size() - 1; i >= 0; i--) {
+      boardRepository.deleteById(targetBoardIds.get(i)); // leaf -> root
+    }
private List<UUID> collectDescendantBoardIds(UUID rootId) {
  List<UUID> all = new java.util.ArrayList<>();
  java.util.ArrayDeque<UUID> queue = new java.util.ArrayDeque<>();
  queue.add(rootId);

  while (!queue.isEmpty()) {
    UUID current = queue.removeFirst();
    all.add(current);
    boardRepository.findAllByParentBoard_BoardId(current).stream()
        .map(Board::getBoardId)
        .forEach(queue::addLast);
  }
  return all;
}

Also applies to: 82-82

🤖 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/admin/service/AdminBoardService.java`
around lines 72 - 76, The current deletion collects only direct children and
deletes in parent-first order, causing FK violations; implement a traversal to
collect the full descendant tree (use
boardRepository.findAllByParentBoard_BoardId and Board::getBoardId) such as a
BFS/DFS method (e.g., collectDescendantBoardIds(UUID rootId)) that returns all
descendant IDs, then delete in reverse order (leaf -> root) so children are
removed before their parents.
🧹 Nitpick comments (4)
backend/src/main/java/org/sejongisc/backend/admin/service/AdminDashboardService.java (2)

104-109: SSE 초기 전송 실패 시 로깅/완료 처리를 추가하는 것이 좋습니다.

현재 예외가 조용히 흡수되어 원인 추적이 어렵습니다. 실패 원인 로그와 completeWithError를 같이 처리하면 운영 가시성이 좋아집니다.

🛠 제안 수정안
     try {
       // 연결 시 503 에러 방지를 위한 더미 이벤트 발송
       emitter.send(SseEmitter.event().name("CONNECT").data("Connected to Admin SSE Stream"));
     } catch (Exception e) {
+      log.warn("Admin SSE CONNECT event send failed", e);
       sseService.removeEmitter(ADMIN_CHANNEL, emitter);
+      emitter.completeWithError(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/admin/service/AdminDashboardService.java`
around lines 104 - 109, In AdminDashboardService inside the try/catch around
emitter.send (the CONNECT dummy event), replace the silent catch with logging
the exception and then calling emitter.completeWithError(e) in addition to
sseService.removeEmitter(ADMIN_CHANNEL, emitter); specifically, catch Exception
e, call your logger (e.g., log.error(...) or a configured logger in
AdminDashboardService) with a descriptive message including e, invoke
emitter.completeWithError(e) to mark the stream failed, and then call
sseService.removeEmitter(ADMIN_CHANNEL, emitter) to clean up the emitter.

34-34: SSE 채널 상수의 위치를 공용 상수 클래스로 분리하는 것을 권장합니다.

현재 상수를 서비스 클래스에 두면서 다른 도메인(activity)이 admin.service에 정적 의존하게 됩니다. 공용 계층(예: common.sse)으로 옮기면 모듈 경계가 더 안정적입니다.

🤖 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/admin/service/AdminDashboardService.java`
at line 34, ADMIN_CHANNEL constant is defined inside AdminDashboardService
causing unwanted static dependency; extract it into a shared constant class
(e.g., create SseChannels or CommonSseConstants in the common.sse package) and
move the value "ADMIN_DASHBOARD" there. Replace the ADMIN_CHANNEL field in
AdminDashboardService with a reference to the new shared constant (e.g.,
SseChannels.ADMIN_CHANNEL) and update all imports/usages across modules
(including the activity domain) to use the new constant; remove the old static
field from AdminDashboardService afterwards.
backend/src/main/java/org/sejongisc/backend/activity/repository/ActivityLogRepository.java (1)

40-44: countActivityByBoard 조건식 단순화로 조회 성능을 개선할 수 있습니다.

Line 42-43의 (:start IS NULL OR ...) 패턴은 인덱스 활용을 저해할 수 있습니다. 호출부가 항상 기간 값을 넘긴다면 BETWEEN :start AND :end로 고정하는 편이 안전합니다.

♻️ 제안 수정안
-    `@Query`("SELECT a.boardName, COUNT(a) FROM ActivityLog a " +
-        "WHERE a.activityType IN ('BOARD_POST', 'BOARD_COMMENT', 'BOARD_LIKE') " +
-        "AND (:start IS NULL OR a.createdAt >= :start) " +
-        "AND (:end IS NULL OR a.createdAt <= :end) " +
-        "GROUP BY a.boardName")
+    `@Query`("SELECT a.boardName, COUNT(a) FROM ActivityLog a " +
+        "WHERE a.activityType IN ('BOARD_POST', 'BOARD_COMMENT', 'BOARD_LIKE') " +
+        "AND a.createdAt BETWEEN :start AND :end " +
+        "GROUP BY a.boardName")
🤖 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 40 - 44, 현재 ActivityLogRepository의 countActivityByBoard 쿼리에서 사용된
"(:start IS NULL OR a.createdAt >= :start) AND (:end IS NULL OR a.createdAt <=
:end)" 조건은 인덱스 활용을 저해하므로, 호출부가 항상 기간을 전달한다는 전제하에 countActivityByBoard의 `@Query를`
간단히 변경해 a.createdAt BETWEEN :start AND :end로 고정하고 불필요한 NULL 체크를 제거하세요; 쿼리 안의
activityType IN 절과 GROUP BY a.boardName은 그대로 유지하고, 호출 코드가 null을 넘기지 않도록 호출자(메서드
사용처)를 확인해 기간 파라미터를 항상 전달하도록 보장하세요.
backend/src/main/java/org/sejongisc/backend/admin/controller/AdminDashboardController.java (1)

70-74: 엔티티(ActivityLog)를 API 응답으로 직접 노출하지 않는 편이 안전합니다.

응답 스키마가 엔티티 구조와 강결합되고, 필드 확장 시 의도치 않은 노출이 발생할 수 있습니다. DTO로 매핑해 반환하는 형태를 권장합니다.

🧩 제안 diff
-  public ResponseEntity<Slice<ActivityLog>> getRecentActivities(
+  public ResponseEntity<Slice<ActivityLogResponse>> getRecentActivities(
       `@PageableDefault`(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
   ) {
-    return ResponseEntity.ok(adminDashboardService.getRecentActivities(pageable));
+    return ResponseEntity.ok(adminDashboardService.getRecentActivityResponses(pageable));
   }
🤖 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/admin/controller/AdminDashboardController.java`
around lines 70 - 74, The controller currently returns the JPA entity
ActivityLog directly from getRecentActivities, which tightly couples the API to
the entity; change the endpoint to map the Slice<ActivityLog> returned by
adminDashboardService.getRecentActivities(pageable) into a Slice of a DTO (e.g.,
ActivityLogDto) and return ResponseEntity<Slice<ActivityLogDto>> instead; create
an ActivityLogDto with only the fields to expose, add a mapper method (e.g.,
ActivityLogMapper.toDto(ActivityLog) or a constructor/factory on ActivityLogDto)
and apply the mapping before building the ResponseEntity in
AdminDashboardController.getRecentActivities so the service can still return
entities but the controller maps them to DTOs.
🤖 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/admin/controller/AdminBoardController.java`:
- Around line 55-56: Add an explicit security check to
AdminBoardController.deleteBoard by annotating the deleteBoard(UUID boardId)
method with the same `@PreAuthorize` expression used by other admin endpoints
(restrict to PRESIDENT and SYSTEM_ADMIN), and ensure the PreAuthorize annotation
is imported (org.springframework.security.access.prepost.PreAuthorize); this
aligns deleteBoard with AdminUserController's protected methods and prevents
accidental exposure if global path-based rules change.

In
`@backend/src/main/java/org/sejongisc/backend/admin/controller/AdminDashboardController.java`:
- Around line 49-56: The methods getVisitorTrend(...) and
getBoardActivityStats(...) accept an unrestricted days param which can be
negative/zero/huge; add server-side validation in AdminDashboardController to
enforce a safe range (e.g., minDays = 1 and maxDays = 365) by checking the
incoming days at the start of each method and throwing a
ResponseStatusException(HttpStatus.BAD_REQUEST, "...") when days < minDays or
days > maxDays; keep the existing `@RequestParam` defaultValue, perform the check
in both getVisitorTrend and getBoardActivityStats before calling
adminDashboardService, and return/propagate the validated days to the service.

In
`@backend/src/main/java/org/sejongisc/backend/admin/service/AdminBoardService.java`:
- Around line 67-70: The deleteBoard method in AdminBoardService lacks
authorization checks allowing unauthorized deletes; add method-level security by
annotating the deleteBoard(UUID boardId) method with
`@PreAuthorize`("hasRole('PRESIDENT')") (and ensure
`@EnableGlobalMethodSecurity`(prePostEnabled = true) is configured), or
alternatively perform an explicit role check inside
AdminBoardService.deleteBoard (e.g., inject Authentication/Principal or a
SecurityService and verify the caller has role PRESIDENT before calling
boardRepository.findById(...)); update references to the deleteBoard method to
reflect the added security requirement.

In
`@backend/src/main/java/org/sejongisc/backend/admin/service/AdminDashboardService.java`:
- Around line 73-75: Validate the days parameter in
AdminDashboardService.getVisitorTrend by adding a guard that checks if days <= 0
and throws a clear exception (e.g., IllegalArgumentException or a
BadRequest-like exception) with a message like "days must be >= 1" before
computing startDate; apply the same validation to any other method in
AdminDashboardService that computes startDate using
LocalDate.now().minusDays(...) (the blocks around where startDate is created) so
you never pass a non-positive value into minusDays.

In
`@backend/src/main/java/org/sejongisc/backend/common/exception/controller/GlobalExceptionHandler.java`:
- Line 21: The current log call in GlobalExceptionHandler logs only
e.getMessage(), losing the stack trace; change the log invocation to include the
exception object (e) so the full stack trace is recorded (e.g., call log.error
with the message and pass e as a throwable parameter, e.g., on the
log.error("CustomException 발생: {}", e.getMessage(), e) or
log.error("CustomException 발생", e) used inside the exception handler method that
references variable e).

---

Outside diff comments:
In
`@backend/src/main/java/org/sejongisc/backend/admin/service/AdminBoardService.java`:
- Around line 72-76: The current deletion collects only direct children and
deletes in parent-first order, causing FK violations; implement a traversal to
collect the full descendant tree (use
boardRepository.findAllByParentBoard_BoardId and Board::getBoardId) such as a
BFS/DFS method (e.g., collectDescendantBoardIds(UUID rootId)) that returns all
descendant IDs, then delete in reverse order (leaf -> root) so children are
removed before their parents.

---

Nitpick comments:
In
`@backend/src/main/java/org/sejongisc/backend/activity/repository/ActivityLogRepository.java`:
- Around line 40-44: 현재 ActivityLogRepository의 countActivityByBoard 쿼리에서 사용된
"(:start IS NULL OR a.createdAt >= :start) AND (:end IS NULL OR a.createdAt <=
:end)" 조건은 인덱스 활용을 저해하므로, 호출부가 항상 기간을 전달한다는 전제하에 countActivityByBoard의 `@Query를`
간단히 변경해 a.createdAt BETWEEN :start AND :end로 고정하고 불필요한 NULL 체크를 제거하세요; 쿼리 안의
activityType IN 절과 GROUP BY a.boardName은 그대로 유지하고, 호출 코드가 null을 넘기지 않도록 호출자(메서드
사용처)를 확인해 기간 파라미터를 항상 전달하도록 보장하세요.

In
`@backend/src/main/java/org/sejongisc/backend/admin/controller/AdminDashboardController.java`:
- Around line 70-74: The controller currently returns the JPA entity ActivityLog
directly from getRecentActivities, which tightly couples the API to the entity;
change the endpoint to map the Slice<ActivityLog> returned by
adminDashboardService.getRecentActivities(pageable) into a Slice of a DTO (e.g.,
ActivityLogDto) and return ResponseEntity<Slice<ActivityLogDto>> instead; create
an ActivityLogDto with only the fields to expose, add a mapper method (e.g.,
ActivityLogMapper.toDto(ActivityLog) or a constructor/factory on ActivityLogDto)
and apply the mapping before building the ResponseEntity in
AdminDashboardController.getRecentActivities so the service can still return
entities but the controller maps them to DTOs.

In
`@backend/src/main/java/org/sejongisc/backend/admin/service/AdminDashboardService.java`:
- Around line 104-109: In AdminDashboardService inside the try/catch around
emitter.send (the CONNECT dummy event), replace the silent catch with logging
the exception and then calling emitter.completeWithError(e) in addition to
sseService.removeEmitter(ADMIN_CHANNEL, emitter); specifically, catch Exception
e, call your logger (e.g., log.error(...) or a configured logger in
AdminDashboardService) with a descriptive message including e, invoke
emitter.completeWithError(e) to mark the stream failed, and then call
sseService.removeEmitter(ADMIN_CHANNEL, emitter) to clean up the emitter.
- Line 34: ADMIN_CHANNEL constant is defined inside AdminDashboardService
causing unwanted static dependency; extract it into a shared constant class
(e.g., create SseChannels or CommonSseConstants in the common.sse package) and
move the value "ADMIN_DASHBOARD" there. Replace the ADMIN_CHANNEL field in
AdminDashboardService with a reference to the new shared constant (e.g.,
SseChannels.ADMIN_CHANNEL) and update all imports/usages across modules
(including the activity domain) to use the new constant; remove the old static
field from AdminDashboardService afterwards.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7ca229 and 2a255a7.

📒 Files selected for processing (11)
  • backend/src/main/java/org/sejongisc/backend/activity/listener/ActivityEventListener.java
  • backend/src/main/java/org/sejongisc/backend/activity/repository/ActivityLogRepository.java
  • backend/src/main/java/org/sejongisc/backend/admin/controller/AdminBoardController.java
  • backend/src/main/java/org/sejongisc/backend/admin/controller/AdminDashboardController.java
  • backend/src/main/java/org/sejongisc/backend/admin/dto/dashboard/BoardActivityResponse.java
  • backend/src/main/java/org/sejongisc/backend/admin/dto/dashboard/SummaryResponse.java
  • backend/src/main/java/org/sejongisc/backend/admin/dto/dashboard/VisitorTrendResponse.java
  • backend/src/main/java/org/sejongisc/backend/admin/service/AdminBoardService.java
  • backend/src/main/java/org/sejongisc/backend/admin/service/AdminDashboardService.java
  • backend/src/main/java/org/sejongisc/backend/common/config/security/SecurityConfig.java
  • backend/src/main/java/org/sejongisc/backend/common/exception/controller/GlobalExceptionHandler.java

@discipline24 discipline24 merged commit 7ae8b80 into main Mar 2, 2026
1 check was pending
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant