Skip to content

Comments

[25.12.23 / TASK-261] Feature - 직접 새로고침 API (프로듀서) 추가#48

Merged
ooheunda merged 8 commits intomainfrom
feature/stats-refresh
Jan 2, 2026
Merged

[25.12.23 / TASK-261] Feature - 직접 새로고침 API (프로듀서) 추가#48
ooheunda merged 8 commits intomainfrom
feature/stats-refresh

Conversation

@ooheunda
Copy link
Member

@ooheunda ooheunda commented Dec 23, 2025

🔥 변경 사항

직접 새로고침의 프로듀서 역할을 하는 API를 추가하였습니다.

  • 레디스 서비스에 필요한 대기열 관련 함수들을 추가하였습니다.
  • 최신 업데이트가 되어있는 경우lastUpdatedAt을 내려보내야 해서, 에러를 던지는 대신 컨트롤러에서 분기하여 처리하도록 했습니다.
  • 외에 이미 진행중인 경우도 에러를 던지는 대신 정상적으로 응답하도록 했습니다.

즐거운 성탄절 보내세요!! 🎄🧑🏻‍🎄

🏷 관련 이슈

  • X

📸 스크린샷 (UI 변경 시 필수)

X

📌 체크리스트

  • 기능이 정상적으로 동작하는지 테스트 완료
  • 코드 스타일 가이드 준수 여부 확인
  • 관련 문서 업데이트 완료 (필요 시)

Summary by CodeRabbit

  • 새 기능

    • 통계 수동 새로고침 엔드포인트 추가 (POST /stats-refresh) 및 상태별 응답(예약 수락, 이미 최신: 409 + lastUpdatedAt, 진행 중: 409)
    • 새로고침 요청을 큐에 예약해 중복 처리 방지
  • 수정/설정

    • Redis 키 접두사 및 캐시·큐 네임스페이스 간소화
  • 테스트

    • 통계 새로고침 흐름과 캐시/큐 동작에 대한 단위 테스트 대폭 확장

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

@ooheunda ooheunda self-assigned this Dec 23, 2025
@ooheunda ooheunda added the enhancement New feature or request label Dec 23, 2025
@notion-workspace
Copy link

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

Walkthrough

통계 강제 갱신(refresh) 흐름이 추가되었습니다. 컨트롤러·라우터·서비스·저장소에 refresh 경로와 로직이 도입되고, Redis 기반 큐(push/isUserInQueue) 및 최신 갱신 시각 조회, 응답 DTO와 관련 단위 테스트들이 추가되었습니다.

Changes

Cohort / File(s) 변경 요약
컨트롤러 / 라우팅
src/controllers/totalStats.controller.ts, src/routes/totalStats.router.ts
refreshStats 핸들러 및 /stats-refresh POST 라우트 추가. 인증 미들웨어 적용, 다양한 상태(202/409/500) 응답 및 Swagger 문서 추가.
서비스 레이어
src/services/totalStats.service.ts, src/services/__test__/totalStats.service.test.ts
refreshStats(userId) 추가: 최신 업데이트 비교(15분 기준), 처리 중 중복 확인, 메인 큐에 작업 푸시 로직 및 관련 단위 테스트(성공/업투데이트/진행중/오류 등).
저장소 레이어
src/repositories/totalStats.repository.ts, src/repositories/__test__/totalStats.repo.test.ts
getLatestUpdatedAt(userId) 추가: DB에서 최신 updated_at 조회(없으면 null) 및 테스트 추가/포맷 정리.
Redis 캐시 / 큐 기능
src/modules/cache/redis.cache.ts, src/modules/cache/__test__/redis.cache.test.ts, src/configs/cache.config.ts, .env.sample
기본 keyPrefix 변경('vd2:cache:' → 'vd2:'), 키에 타입 접두사 도입(<prefix><type>:<key>), pushToQueue/isUserInQueue 메서드 추가 및 관련 테스트 확장.
타입 / DTO
src/types/dto/responses/totalStatsResponse.type.ts, src/types/index.ts
StatsRefreshResponseDto 추가(선택적 lastUpdatedAt 포함) 및 exports에 등록.
테스트 보강
src/services/__test__/totalStats.service.test.ts, src/repositories/__test__/totalStats.repo.test.ts, src/modules/cache/__test__/redis.cache.test.ts
refresh 흐름 및 Redis 큐 동작에 대한 단위 테스트 추가/확장.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant Controller as TotalStatsController
    participant Service as TotalStatsService
    participant Repo as TotalStatsRepository
    participant Cache as RedisCache
    participant Redis

    Client->>Controller: POST /stats-refresh (auth, userId)
    Controller->>Service: refreshStats(userId)

    Service->>Repo: getLatestUpdatedAt(userId)
    Repo-->>Service: lastUpdatedAt (string|null)

    alt lastUpdatedAt within 15 minutes
        Service-->>Controller: {success:false, reason:'up-to-date', lastUpdatedAt}
        Controller-->>Client: HTTP 409 + payload
    else
        Service->>Cache: isUserInQueue(PROCESSING_QUEUE_KEY, userId)
        Cache->>Redis: LRANGE processingKey 0 -1
        Redis-->>Cache: list items
        Cache-->>Service: boolean (inQueue)

        alt inQueue == true
            Service-->>Controller: {success:false, reason:'in-progress'}
            Controller-->>Client: HTTP 409 + payload
        else
            Service->>Cache: pushToQueue(MAIN_QUEUE_KEY, jobData)
            Cache->>Redis: LPUSH mainKey serialized(jobData)
            Redis-->>Cache: new length
            Cache-->>Service: number|null

            alt push succeeded
                Service-->>Controller: {success:true}
                Controller-->>Client: HTTP 202 + payload
            else
                Service-->>Controller: throws
                Controller-->>Client: HTTP 500 + payload
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • six-standard
  • Nuung
  • Jihyun3478

Poem

🐇 통계 갱신, 깡충깡충 뛰며,
큐에 한 줄, 요청을 싣고,
열다섯 분 전이면 쉬어가고,
진행중이면 살짝 멈추네,
끝나면 당근으로 축하해!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경 사항의 주요 내용을 명확하게 반영하고 있습니다. '직접 새로고침 API (프로듀서) 추가'는 이 PR에서 추가된 새로운 refreshStats 엔드포인트와 관련 기능을 정확하게 설명합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/stats-refresh

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
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/modules/cache/redis.cache.ts (1)

276-276: 키 접두사 일관성 문제 (pushToQueue와 동일)

isUserInQueue도 하드코딩된 vd2:queue: 접두사를 사용합니다. pushToQueue와 함께 설정 가능한 접두사 패턴으로 변경하는 것이 좋습니다.

🧹 Nitpick comments (4)
src/services/totalStats.service.ts (1)

57-69: 큐 등록 로직이 적절합니다.

큐 실패 시 명확한 에러를 throw하고 있습니다.

디버깅을 위해 성공 시에도 큐 길이를 로깅하는 것을 고려해보세요:

선택적 개선사항
 const queueLength = await redisCache.pushToQueue(this.MAIN_QUEUE_KEY, {
   userId: userId,
   requestedAt: new Date().toISOString(),
   retryCount: 0,
 });

 if (queueLength === null) {
   logger.error('Failed to add job to Redis Queue');
   throw new Error('통계 새로고침 작업 등록에 실패했습니다.');
 }

+logger.info(`Stats refresh job queued for user ${userId}, queue length: ${queueLength}`);
 return { success: true };
src/modules/cache/redis.cache.ts (2)

245-261: 키 접두사 일관성 문제: 하드코딩된 접두사 사용

pushToQueue 메서드가 하드코딩된 vd2:queue: 접두사를 사용하고 있습니다. 다른 캐시 메서드들은 설정 가능한 this.keyPrefix를 사용하는 패턴을 따르고 있어 일관성이 떨어집니다. 큐 전용 접두사가 필요하다면 생성자에서 설정 가능하도록 하거나, 최소한 getFullKey 패턴을 따르도록 개선하는 것이 좋습니다.

🔎 설정 가능한 큐 접두사를 위한 제안

생성자에 queuePrefix 추가:

  constructor(config: CacheConfig) {
    this.keyPrefix = config.keyPrefix || 'vd2:cache:';
+   this.queuePrefix = config.queuePrefix || 'vd2:queue:';
    this.defaultTTL = config.defaultTTL || 300;

그리고 메서드에서 사용:

  async pushToQueue<T>(queueKey: string, data: T): Promise<number | null> {
    try {
      if (!this.connected) {
        logger.warn('Redis not connected, skipping queue push');
        return null;
      }

-     const fullKey = `vd2:queue:${queueKey}`;
+     const fullKey = `${this.queuePrefix}${queueKey}`;
      const serializedData = JSON.stringify(data);

      const length = await this.client.lPush(fullKey, serializedData);
      return length;
    } catch (error) {
      logger.error(`Queue PUSH error for key ${queueKey}:`, error);
      return null;
    }
  }

269-298: 큐 조회의 O(n) 성능 특성 고려

isUserInQueue 메서드가 LRANGE 0 -1을 사용하여 전체 큐를 스캔하므로 큐 크기에 따라 O(n) 성능 특성을 가집니다. 현재 코드는 단 하나의 위치(getTotalStats의 중복 처리 방지)에서만 호출되며, 통계 갱신 요청이라는 제한된 사용 사례이므로 실질적인 성능 문제는 미미할 것으로 예상됩니다.

그러나 향후 큐 사용이 확장될 경우를 대비하여, 다음과 같은 개선을 고려할 수 있습니다:

  • Redis Set을 병행하여 O(1) 조회 구현 (예: vd2:queue:users:${queueKey})
  • 큐 처리 워커의 추가 및 명확한 생명주기 관리
  • 불필요한 데이터는 TTL 설정으로 자동 정리

현재 구현이 요구사항을 충족한다면 그대로 유지하되, 큐 규모가 커질 경우 이를 재검토하는 것이 권장됩니다.

src/controllers/totalStats.controller.ts (1)

30-62: 제어 흐름 개선 제안

refreshStats 메서드의 로직이 전반적으로 정확하지만, 다음과 같은 개선사항을 고려할 수 있습니다:

  1. Line 41: as string 타입 단언은 result.lastUpdatedAt의 타입이 보장되지 않음을 시사합니다. 서비스 레이어의 반환 타입을 명확히 정의하면 타입 안전성이 향상됩니다.

  2. Lines 37-50: 각 조건 블록 내에서 응답을 보낸 후 명시적으로 return하는 것이 제어 흐름을 더 명확하게 만듭니다. 현재는 line 52의 return에 의존하고 있습니다.

  3. Exhaustiveness: result.success가 false이지만 reason이 'up-to-date'나 'in-progress'가 아닌 경우에 대한 처리가 없습니다. 예상치 못한 상태에 대한 기본 처리나 로깅을 추가하는 것이 좋습니다.

🔎 제어 흐름 개선 예시
  refreshStats: RequestHandler = async (req: Request, res: Response<StatsRefreshResponseDto>, next: NextFunction) => {
    try {
      const { id } = req.user;

      const result = await this.totalStatsService.refreshStats(id);

      if (!result.success) {
        if (result.reason === 'up-to-date') {
          const response = new StatsRefreshResponseDto(
            false,
            '통계가 최신 상태입니다.',
            { lastUpdatedAt: result.lastUpdatedAt as string },
            null,
          );
-         res.status(409).json(response);
+         return res.status(409).json(response);
        }

        if (result.reason === 'in-progress') {
          const response = new StatsRefreshResponseDto(false, '이미 통계 새로고침이 진행 중입니다.', {}, null);
-         res.status(409).json(response);
+         return res.status(409).json(response);
        }

-       return;
+       // 예상치 못한 실패 케이스 처리
+       logger.warn(`Unexpected refresh result: ${JSON.stringify(result)}`);
+       const response = new StatsRefreshResponseDto(false, '통계 새로고침 요청을 처리할 수 없습니다.', {}, null);
+       return res.status(500).json(response);
      }

      const response = new StatsRefreshResponseDto(true, '통계 새로고침 요청이 성공적으로 등록되었습니다.', {}, null);

-     res.status(202).json(response);
+     return res.status(202).json(response);
    } catch (error) {
      logger.error('통계 새로고침 실패:', error);
      next(error);
    }
  };
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 442f13e and ba9dad5.

📒 Files selected for processing (10)
  • src/controllers/totalStats.controller.ts
  • src/modules/cache/__test__/redis.cache.test.ts
  • src/modules/cache/redis.cache.ts
  • src/repositories/__test__/totalStats.repo.test.ts
  • src/repositories/totalStats.repository.ts
  • src/routes/totalStats.router.ts
  • src/services/__test__/totalStats.service.test.ts
  • src/services/totalStats.service.ts
  • src/types/dto/responses/totalStatsResponse.type.ts
  • src/types/index.ts
🧰 Additional context used
🧬 Code graph analysis (5)
src/repositories/totalStats.repository.ts (1)
src/exception/index.ts (1)
  • DBError (2-2)
src/types/dto/responses/totalStatsResponse.type.ts (2)
src/types/index.ts (3)
  • TotalStatsResponseDto (39-39)
  • TotalStatsItem (37-37)
  • StatsRefreshResponseDto (40-40)
src/types/dto/responses/baseResponse.type.ts (1)
  • BaseResponseDto (28-40)
src/services/totalStats.service.ts (1)
src/configs/cache.config.ts (1)
  • cache (17-17)
src/routes/totalStats.router.ts (1)
src/middlewares/auth.middleware.ts (1)
  • authMiddleware (110-113)
src/controllers/totalStats.controller.ts (2)
src/types/dto/responses/totalStatsResponse.type.ts (1)
  • StatsRefreshResponseDto (66-66)
src/types/index.ts (1)
  • StatsRefreshResponseDto (40-40)
🔇 Additional comments (13)
src/types/index.ts (1)

40-40: LGTM!

새로운 DTO 타입을 일관된 패턴으로 export하고 있습니다.

src/routes/totalStats.router.ts (2)

73-119: LGTM!

새로고침 엔드포인트에 대한 Swagger 문서가 잘 작성되어 있습니다. 다양한 응답 시나리오(최신 상태, 진행 중)에 대한 예시가 명확하게 제공되어 있습니다.


120-120: LGTM!

라우트가 인증 미들웨어로 보호되고 있으며, 요청 본문이 없으므로 별도의 유효성 검사 미들웨어가 필요하지 않습니다.

src/types/dto/responses/totalStatsResponse.type.ts (1)

44-66: LGTM!

StatsRefreshResponseDto가 적절하게 정의되어 있습니다. lastUpdatedAt 필드가 선택적으로 설계되어 다양한 응답 시나리오(최신 상태 vs 대기열 등록)를 잘 지원합니다.

src/repositories/__test__/totalStats.repo.test.ts (1)

226-256: LGTM!

getLatestUpdatedAt 메서드에 대한 테스트 커버리지가 적절합니다. 성공 케이스, null 반환, 에러 처리 시나리오를 모두 검증하고 있습니다.

src/modules/cache/__test__/redis.cache.test.ts (2)

545-601: LGTM!

pushToQueue 메서드의 테스트가 포괄적입니다. JSON 직렬화, 에러 처리, 연결 상태 확인 등 주요 시나리오를 잘 검증하고 있습니다.


603-694: LGTM!

isUserInQueue 메서드의 테스트가 매우 철저합니다. 특히 잘못된 JSON 항목이 있어도 검색을 계속하는 복원력 테스트(Lines 661-671)가 훌륭합니다.

src/services/__test__/totalStats.service.test.ts (1)

1-135: LGTM!

refreshStats 메서드에 대한 테스트 커버리지가 훌륭합니다. 가짜 타이머를 사용하여 시간 기반 로직을 정확하게 테스트하고 있으며, 성공/실패/엣지 케이스를 모두 검증합니다.

src/services/totalStats.service.ts (3)

8-10: LGTM!

새로고침 간격과 큐 키 상수가 명확하게 정의되어 있습니다. 15분 간격은 Swagger 문서의 설명과 일치합니다.


36-47: LGTM!

시간 비교 로직이 정확합니다. 15분 이내의 업데이트는 최신 상태로 처리하고, null 케이스(이전 업데이트 없음)도 적절히 처리합니다.


70-74: LGTM!

에러 처리가 일관되게 구현되어 있습니다. 에러를 로깅한 후 상위 레이어로 전파하는 패턴이 기존 getTotalStats 메서드와 동일합니다.

src/controllers/totalStats.controller.ts (2)

3-3: LGTM: 새로운 응답 DTO 임포트

StatsRefreshResponseDto 임포트가 적절하게 추가되었습니다.


55-57: 적절한 HTTP 상태 코드 사용

비동기 처리를 위한 202 Accepted 상태 코드 사용이 적절합니다. 큐에 작업이 등록되었고 나중에 처리될 것임을 올바르게 나타냅니다.

Comment on lines 36 to 57
if (!result.success) {
if (result.reason === 'up-to-date') {
const response = new StatsRefreshResponseDto(
false,
'통계가 최신 상태입니다.',
{ lastUpdatedAt: result.lastUpdatedAt as string },
null,
);
res.status(409).json(response);
}

if (result.reason === 'in-progress') {
const response = new StatsRefreshResponseDto(false, '이미 통계 새로고침이 진행 중입니다.', {}, null);
res.status(409).json(response);
}

return;
}

const response = new StatsRefreshResponseDto(true, '통계 새로고침 요청이 성공적으로 등록되었습니다.', {}, null);

res.status(202).json(response);
Copy link
Member

Choose a reason for hiding this comment

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

  1. 이럴땐 차라리 result.successtrue 일때를 처리하는게 나을 것 같아요.
  2. reason 이 2가지 말고 다른 이유일 가능성이 없나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

에러 필터에 걸리지 않게끔 처리하는 케이스에 대해선 이 2가지만 있는 것 같습니다!

const fullKey = `vd2:queue:${queueKey}`;

// LRANGE로 큐의 모든 항목 조회
const items = await this.client.lRange(fullKey, 0, -1);
Copy link
Member

Choose a reason for hiding this comment

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

이게 무조건 O(n) 이 될 수 밖에 없는데,
pushToQueue 에 되는 것을 "특정 유저 값" 과 같은 유니크 값으로 "다른 큐에" 에 Add를 같이 한다면, sIsMember 로 O(1) 체크할 수 있어요.

그 대신 그 만큼의 메모리를 더 쓰겠지만, 이는 trade-off 에 대해 고민해 볼 법 한 것 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

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

너무 좋은 아이디어 감사합니다! 😊
실제 구현도 해보고 고민해봤는데 현재로썬 Queue 크기가 그렇게 클 것 같지도 않고, 백오피스에서도 대응 개발해야하는 부분이나 API 서버 코드에서도 복잡도가 좀 올라가는 것 같아서 해당하는 부분은 나중에 병목 생기면 리팩토링 하는 방향으로 가고 싶습니다. 괜찮을까요?

Copy link
Member

@Nuung Nuung left a comment

Choose a reason for hiding this comment

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

좋았던 점

  • redis 에 대한 처리를 같이 하다보면 생각보다 QA와 커버할게 넓어져서 쉽지 않았을텐데, 너무 잘 마무리해주신 것 같아요! 고생 많으셨습니다!
  • 기존 redis 구현체 재활용과 테스트 코드까지 짱이었어요 ㅎㄷㄷ

아쉬운 점

  • redis를 사용할때 고민할 디테일들이 조금 아쉬웠습니다. 코멘트 확인해 주세요!
  • 그 외 디테일한 부분이었고, 전체적으로 크게 아쉬운게 없어요!! 너무 고생많으셨어요!

Copy link
Contributor

@Jihyun3478 Jihyun3478 left a comment

Choose a reason for hiding this comment

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

코드 잘 봤습니다! 전반적으로 비동기 작업 처리 구조가 노션에 기재해주셨던 방향대로 탄탄하게 설계해주신 것 같고, 테스트도 꼼꼼히 구현해주신 점이 좋았습니다:)

좋았던 점

  • pushToQueue와 isUserInQueue 메서드를 테스트 코드로 꼼꼼하게 검증하신 부분이 인상적이었습니다. 특히 잘못된 JSON 파싱 케이스까지 고려하신 부분이 좋았습니다👍
  • 15분 간격 제한을 상수로 관리하고, 큐 키도 클래스 속성으로 관리해 매직 넘버 및 스트링을 제거하신 점이 좋았습니다!

Copy link
Member

@six-standard six-standard left a comment

Choose a reason for hiding this comment

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

코드 잘 읽었습니다!
현우님이랑 의견 동일해서, 저 부분들만 처리해주시면 될 듯 합니다..!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/modules/cache/redis.cache.ts (1)

286-286: 엄격한 동등 비교(===)를 사용해야 합니다.

느슨한 동등 비교(==)는 타입 강제 변환을 허용하여 예상치 못한 동작을 유발할 수 있습니다. 예를 들어, parsedItem.userId가 문자열 "123"이고 userId가 숫자 123일 경우 ==true를 반환하지만, 이는 타입 불일치를 숨길 수 있습니다.

🔎 수정 제안
-          if (parsedItem.userId == userId) {
+          if (parsedItem.userId === userId) {
             return true;
           }
🧹 Nitpick comments (1)
src/controllers/totalStats.controller.ts (1)

36-54: 응답 분기 로직을 더 명시적으로 개선할 수 있습니다.

현재 로직은 reason이 'up-to-date'나 'in-progress'가 아닐 경우 암묵적으로 성공으로 처리합니다. result.success 속성을 명시적으로 확인하여 예상치 못한 응답 구조에 대한 견고성을 높이는 것을 권장합니다.

🔎 제안하는 개선 방안
 const result = await this.totalStatsService.refreshStats(id);

+if (result.success) {
+  const response = new StatsRefreshResponseDto(true, '통계 새로고침 요청이 성공적으로 등록되었습니다.', {}, null);
+  res.status(202).json(response);
+  return;
+}
+
 if (result.reason === 'up-to-date') {
   const response = new StatsRefreshResponseDto(
     false,
     '통계가 최신 상태입니다.',
     { lastUpdatedAt: result.lastUpdatedAt },
     null,
   );
   res.status(409).json(response);
   return;
 }

 if (result.reason === 'in-progress') {
   const response = new StatsRefreshResponseDto(false, '이미 통계 새로고침이 진행 중입니다.', {}, null);
   res.status(409).json(response);
   return;
 }

-const response = new StatsRefreshResponseDto(true, '통계 새로고침 요청이 성공적으로 등록되었습니다.', {}, null);
-res.status(202).json(response);
+// 예상치 못한 응답 구조 처리
+throw new Error('Unexpected refresh result structure');
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba9dad5 and bfb93d4.

📒 Files selected for processing (4)
  • src/configs/cache.config.ts
  • src/controllers/totalStats.controller.ts
  • src/modules/cache/__test__/redis.cache.test.ts
  • src/modules/cache/redis.cache.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/modules/cache/test/redis.cache.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/controllers/totalStats.controller.ts (2)
src/types/dto/responses/totalStatsResponse.type.ts (1)
  • StatsRefreshResponseDto (66-66)
src/types/index.ts (1)
  • StatsRefreshResponseDto (40-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build-and-test (20)
  • GitHub Check: build-and-test (23)
  • GitHub Check: build-and-test (22)
  • GitHub Check: build-and-test (21)
🔇 Additional comments (3)
src/configs/cache.config.ts (1)

11-11: 키 prefix 변경이 올바르게 적용되었습니다.

기존 'vd2:cache:'에서 'vd2:'로 변경하여 getFullKey에서 타입별로 prefix를 추가하는 새로운 구조와 일치합니다. 이 변경으로 캐시와 큐가 명확히 구분됩니다.

src/modules/cache/redis.cache.ts (2)

13-13: 키 prefix 리팩토링이 잘 적용되었습니다.

getFullKey를 범용적으로 사용하도록 개선하여 캐시와 큐를 명확히 구분할 수 있게 되었습니다. 타입 파라미터를 통해 타입 안정성도 확보되었습니다.

Also applies to: 58-60


246-262: 큐 추가 메서드가 올바르게 구현되었습니다.

pushToQueue 메서드는 Redis List를 활용하여 큐 기능을 제공하며, 에러 처리와 연결 상태 확인도 적절히 수행합니다.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
.env.sample (1)

24-29: 선택사항: Redis 환경 변수 정렬 순서 개선

dotenv-linter 분석 결과, REDIS_KEY_PREFIX (29줄)이 REDIS_PASSWORD (27줄) 이후에 위치하고 있습니다. 일반적인 env 파일 관례에 따라 관련 설정들을 그룹화하고 알파벳 순서로 정렬하면 가독성이 개선될 수 있습니다. 프로젝트의 스타일 가이드에 따라 정렬 순서를 일관되게 유지할 것을 권장합니다.

🔎 제안된 정렬 순서 조정
# Cache (redis)
REDIS_HOST=localhost
-REDIS_PORT=6379
-REDIS_PASSWORD=notion-check-plz
-REDIS_DB=0
REDIS_KEY_PREFIX=vd2:
+REDIS_PASSWORD=notion-check-plz
+REDIS_PORT=6379
+REDIS_DB=0
CACHE_DEFAULT_TTL=300
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bfb93d4 and bf5d1cf.

📒 Files selected for processing (1)
  • .env.sample
🧰 Additional context used
🪛 dotenv-linter (4.0.0)
.env.sample

[warning] 29-29: [UnorderedKey] The REDIS_KEY_PREFIX key should go before the REDIS_PASSWORD key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build-and-test (22)
  • GitHub Check: build-and-test (20)
  • GitHub Check: build-and-test (23)
  • GitHub Check: build-and-test (21)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/services/totalStats.service.ts (1)

102-103: 타입 단언 관련 의도 주석 추가 권장

이전 리뷰에서 논의된 대로, ICache는 범용 인터페이스로 유지하고 Redis 전용 메서드는 타입 단언으로 사용하는 설계 결정이 확인되었습니다. 코드의 의도를 명확히 하기 위해 주석을 추가하는 것을 권장합니다.

🔎 주석 추가 제안
      // 3. Processing Queue에 userId 존재 확인
+     // Redis 전용 큐 메서드 사용을 위한 타입 단언 (ICache는 범용 인터페이스로 유지)
      const redisCache = cache as RedisCache;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf5d1cf and c6d1a3d.

📒 Files selected for processing (6)
  • src/controllers/totalStats.controller.ts
  • src/repositories/totalStats.repository.ts
  • src/routes/totalStats.router.ts
  • src/services/totalStats.service.ts
  • src/types/dto/responses/totalStatsResponse.type.ts
  • src/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/routes/totalStats.router.ts
  • src/types/index.ts
  • src/repositories/totalStats.repository.ts
  • src/controllers/totalStats.controller.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/types/dto/responses/totalStatsResponse.type.ts (2)
src/types/index.ts (1)
  • StatsRefreshResponseDto (47-47)
src/types/dto/responses/baseResponse.type.ts (1)
  • BaseResponseDto (28-40)
src/services/totalStats.service.ts (2)
src/configs/cache.config.ts (1)
  • cache (17-17)
src/modules/cache/redis.cache.ts (1)
  • RedisCache (6-300)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build-and-test (23)
  • GitHub Check: build-and-test (20)
  • GitHub Check: build-and-test (21)
  • GitHub Check: build-and-test (22)
🔇 Additional comments (5)
src/types/dto/responses/totalStatsResponse.type.ts (1)

185-205: 잘 구현되었습니다!

새로운 StatsRefreshResponseDto가 기존 파일의 패턴을 잘 따르고 있으며, 타입 정의와 Swagger 문서화가 일관성 있게 작성되었습니다. lastUpdatedAt을 선택적 필드로 정의한 것은 409(이미 최신 상태)와 202(큐에 추가됨) 응답을 모두 유연하게 처리할 수 있어 적절합니다.

src/services/totalStats.service.ts (4)

5-6: LGTM!

캐시 관련 import가 적절하게 추가되었습니다.


16-18: LGTM!

상수 정의가 명확하고, 큐 키 네이밍 컨벤션이 일관성 있게 유지되었습니다.


84-126: 전체적인 refreshStats 로직 승인

새로고침 API의 프로듀서 역할이 명확하게 구현되었습니다:

  • 15분 간격 체크로 불필요한 새로고침 방지
  • 진행 중인 작업 중복 방지
  • 적절한 에러 핸들링 및 로깅
  • 반환 타입이 명확하여 컨트롤러에서 분기 처리 용이

위에서 언급한 메인 큐 중복 등록 가능성만 확인해 주시면 됩니다.


92-98: 타임존 처리 관련 타입 주석 검증 필요

리포지토리 메서드가 Promise<string | null>로 선언되어 있으나, pg 라이브러리의 기본 동작(v8.13.1)은 PostgreSQL TIMESTAMP 컬럼을 Date 객체로 변환합니다. 현재 코드의 new Date(latestUpdatedAt)는 문자열과 Date 객체 모두에서 동작하지만, 타입 선언과 실제 동작이 일치하는지 확인이 필요합니다.

테스트에서는 '2025-05-30T12:00:00.000Z' 형식의 ISO 8601 문자열(Z 타임존 포함)로 모킹하고 있으므로, 실제 데이터베이스 반환값도 타임존 정보를 포함하는지 검증하세요.

@ooheunda ooheunda merged commit da0e3e6 into main Jan 2, 2026
5 checks passed
@ooheunda ooheunda deleted the feature/stats-refresh branch January 2, 2026 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants