Skip to content

[25.06.03 / TASK-191]Feature - 전체 조회수/좋아요/게시글 디테일 API 대응개발 #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 3, 2025

Conversation

Nuung
Copy link
Member

@Nuung Nuung commented Jun 3, 2025

🔥 변경 사항

  • 쿠키 hotfix 배포 하고 싶었는데, FE 이미지 빌드가 먼저 적용되어서, 전체 조회수/좋아요/게시글 디테일 API 빠르게 대응 개발
  • 왜이렇게 diff file 이 많은가 => prettier 적용을 해보니 우리가 이때까지 해당 린팅을 안따르고 있었음 ㅎㅎ 그래서 포맷팅 돌림
  • totalStats 관련 파일이 핵심 변경 파일
  • flow chart
flowchart TD
    A[Client Request] --> B[Express Router]
    B --> C{Authentication Middleware}
    C -->|Valid JWT| D[Request Validation]
    C -->|Invalid JWT| Z1[401 Unauthorized]
    
    D --> E{Query Validation<br/>GetTotalStatsQueryDto}
    E -->|Valid| F[TotalStatsController.getTotalStats]
    E -->|Invalid| Z2[400 Bad Request]
    
    F --> G[Extract User ID from req.user]
    F --> H[Extract Query Parameters<br/>period?, type!]
    
    G --> I[TotalStatsService.getTotalStats]
    H --> I
    
    I --> J{Check Stats Type}
    J -->|type = 'view'| K[Get View Statistics]
    J -->|type = 'like'| L[Get Like Statistics]
    J -->|type = 'post'| M[Get Post Statistics]
    
    K --> N[Database Query<br/>PostStatistics Table]
    L --> O[Database Query<br/>PostStatistics Table]
    M --> P[Database Query<br/>Posts Table]
    
    N --> Q[Calculate Period Stats<br/>7 or 30 days]
    O --> R[Calculate Period Stats<br/>7 or 30 days]
    P --> S[Calculate Period Stats<br/>7 or 30 days]
    
    Q --> T[Return Aggregated Data]
    R --> T
    S --> T
    
    T --> U[TotalStatsService.getSuccessMessage]
    U --> V[Create TotalStatsResponseDto]
    
    V --> W[Success Response<br/>200 OK]
    
    I -->|Database Error| X[Logger.error]
    X --> Y[Error Middleware]
    Y --> Z3[500 Internal Server Error]
    
    style A fill:#1e3a8a,stroke:#3b82f6,stroke-width:2px,color:#ffffff
    style W fill:#166534,stroke:#22c55e,stroke-width:2px,color:#ffffff
    style Z1 fill:#dc2626,stroke:#ef4444,stroke-width:2px,color:#ffffff
    style Z2 fill:#dc2626,stroke:#ef4444,stroke-width:2px,color:#ffffff
    style Z3 fill:#dc2626,stroke:#ef4444,stroke-width:2px,color:#ffffff
    style F fill:#ea580c,stroke:#f97316,stroke-width:2px,color:#ffffff
    style I fill:#7c3aed,stroke:#8b5cf6,stroke-width:2px,color:#ffffff
Loading
  • 테스트 코드는 repo 와 E2E, SQL raw query 직접 테스팅 모두 완료!

📌 체크리스트

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

Summary by CodeRabbit

  • 신규 기능

    • 사용자의 조회수, 좋아요, 게시물 수 등 통계 데이터를 7일 또는 30일 단위로 조회할 수 있는 통계 API가 추가되었습니다. 해당 통계는 인증된 사용자만 조회할 수 있습니다.
    • 통계 API를 위한 새로운 라우터, 컨트롤러, 서비스, 리포지토리 및 DTO가 도입되었습니다.
  • 버그 수정

    • 해당 없음
  • 문서화

    • 통계 API 엔드포인트에 대한 Swagger 기반 API 문서가 추가되었습니다.
  • 리팩터링/스타일

    • 코드 전반에 걸쳐 import 정리, 문자열 스타일 통일, 함수 및 클래스 선언부의 포맷 개선 등 스타일 및 가독성 향상이 이루어졌습니다.
    • 테스트 코드에서 중복되는 모킹 유틸리티를 별도 파일로 분리하여 재사용성을 높였습니다.
  • 테스트

    • 통계 관련 신규 기능에 대한 단위 및 통합 테스트가 추가되었습니다.
  • 기타

    • 다양한 환경과 도구를 아우르는 포괄적인 .gitignore 규칙으로 교체되어 불필요한 파일의 Git 추적이 방지됩니다.

@Nuung Nuung self-assigned this Jun 3, 2025
@Nuung Nuung added bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request labels Jun 3, 2025
Copy link

Copy link
Contributor

coderabbitai bot commented Jun 3, 2025

## Walkthrough

이 변경 사항은 사용자 활동 통계(조회수, 좋아요, 게시글 수 등)를 집계하여 반환하는 "Total Stats" 기능을 새로 추가합니다. 이를 위해 컨트롤러, 서비스, 리포지토리, DTO, 라우터, 테스트 파일 등이 새롭게 도입되었으며, 기존 코드에서는 주로 스타일 및 포매팅 개선이 이루어졌습니다. 테스트 유틸리티 및 통합 테스트도 보강되었습니다.

## Changes

| 파일/그룹                                      | 변경 요약                                                         |
|------------------------------------------------|-------------------------------------------------------------------|
| .gitignore                                     | 다양한 플랫폼/툴을 아우르는 포괄적 ignore 규칙으로 전면 교체         |
| src/controllers/totalStats.controller.ts        | TotalStatsController 신설, 통계 조회 엔드포인트 컨트롤러 구현        |
| src/services/totalStats.service.ts              | TotalStatsService 신설, 통계 데이터 가공 및 메시지 제공              |
| src/repositories/totalStats.repository.ts       | TotalStatsRepository 신설, 통계 SQL 쿼리 및 데이터 반환              |
| src/routes/totalStats.router.ts                 | /total-stats 라우터 신설, 인증 및 DTO 검증, Swagger 문서화           |
| src/types/dto/requests/getTotalStatsQuery.type.ts | 통계 쿼리 파라미터용 타입/DTO 신설, 검증 및 Swagger 문서화           |
| src/types/dto/responses/totalStatsResponse.type.ts | 통계 응답 DTO 신설, Swagger 문서화                                 |
| src/types/index.ts                             | 타입/DTO export 구조를 영역별로 그룹화, TotalStats 관련 export 추가  |
| src/routes/index.ts                            | totalStats 라우터 등록 추가                                        |
| src/repositories/__test__/totalStats.repo.test.ts | TotalStatsRepository 단위 테스트 신설, 다양한 케이스 커버            |
| src/repositories/__test__/fixtures.ts           | Jest용 mockPool, createMockQueryResult 유틸 신설                    |
| src/repositories/__test__/leaderboard.repo.test.ts<br>src/repositories/__test__/post.repo.test.ts<br>src/repositories/__test__/qr.repo.test.ts | mock 유틸 외부 import로 리팩토링, 중복 코드 제거                     |
| src/repositories/__test__/integration/*.test.ts | pg import 방식 통합, 경로 정리                                     |
| src/controllers/*.ts<br>src/services/*.ts<br>src/repositories/*.ts | 포매팅, import 경로/문자 스타일 통일, 불필요 공백 제거               |
| src/exception/index.ts                         | export 구문 포매팅 변경                                            |
| src/utils/date.util.ts<br>src/utils/generateRandomToken.util.ts | 공백/포매팅 조정                                                   |
| src/routes/post.router.ts                      | 라우트 핸들러에 트레일링 콤마 추가                                 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant Router
    participant AuthMiddleware
    participant Controller
    participant Service
    participant Repository
    participant DB

    Client->>Router: GET /total-stats?period=7&type=view
    Router->>AuthMiddleware: 인증 토큰 검증
    AuthMiddleware-->>Router: 인증 성공
    Router->>Controller: getTotalStats(req, res, next)
    Controller->>Service: getTotalStats(userId, period, type)
    Service->>Repository: getTotalStats(userId, period, type)
    Repository->>DB: 통계 쿼리 실행 (SQL)
    DB-->>Repository: Raw 통계 데이터 반환
    Repository-->>Service: Raw 통계 데이터 반환
    Service-->>Controller: 가공된 통계 데이터, 메시지 반환
    Controller-->>Client: 200 OK, 통계 응답 JSON

Possibly related PRs

  • #30: 사용자 인증 및 토큰 처리 방식 개선과 관련된 코드 변경이 있어, 인증 흐름과 통계 기능에서 사용자 식별 방식이 연결될 수 있음.
  • #25: post.repository.ts 및 테스트 파일을 함께 수정하며, 게시글 활성화 필터링과 테스트 커버리지 확장과 관련 있음.

Suggested reviewers

  • six-standard
  • Jihyun3478

Poem

🥕
새로운 통계, 토끼도 궁금해
조회수, 좋아요, 게시글까지
한눈에 쏙 들어오는 숫자
데이터 들판을 뛰노는 기쁨
코드도 깔끔, 테스트도 든든
통계의 봄바람, 우리 함께 달려요!
🐇📊


<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between eb05ca882d2a31545e2408af87922df7f3c8e0e0 and 19d9e7ced4259b62083081afba7fa79bdcf85766.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `src/controllers/totalStats.controller.ts` (1 hunks)
* `src/services/totalStats.service.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* src/controllers/totalStats.controller.ts
* src/services/totalStats.service.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: build-and-test (23)
* GitHub Check: build-and-test (22)
* GitHub Check: build-and-test (21)
* GitHub Check: build-and-test (20)

</details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Check-Data-Out/velog-dashboard-v2-api&utm_content=33):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

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

🔭 Outside diff range comments (1)
src/repositories/__test__/qr.repo.test.ts (1)

110-110: ⚠️ Potential issue

테스트 어설션에 버그가 있습니다.

toHaveBeenCalledTimes(targetUserId)는 잘못된 로직입니다. 이 메서드는 항상 한 번만 호출되어야 하므로 toHaveBeenCalledTimes(1)이어야 합니다.

다음과 같이 수정해주세요:

-      expect(mockPool.query).toHaveBeenCalledTimes(targetUserId);
+      expect(mockPool.query).toHaveBeenCalledTimes(1);
🧹 Nitpick comments (8)
.gitignore (2)

75-81: 중복된 로그 파일 ignore 패턴 통합 제안
파일 상단의 logs/*, *.log 패턴과 이 섹션의 logs, *.log 등이 중복됩니다. 유지보수성과 가독성을 위해 하나의 패턴으로 통합하길 추천합니다.


149-154: 중복된 dotenv 파일 ignore 패턴 정리 제안
상단에서 이미 .env, .env.local, .env.*.local을 지정했으므로 하단의 dotenv 관련 패턴(.env.development.local, .env.test.local, 등)을 하나로 병합하거나 중복 제거하는 것이 좋습니다.

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

8-20: 주요 비즈니스 로직 구현 우수

getTotalStats 메서드가 잘 구현되었습니다:

  • 적절한 에러 핸들링과 로깅
  • 데이터 변환 로직이 명확함
  • 타입 안전성을 위한 Number() 변환 사용

다만 Number(stat.total_value) 변환에서 total_value가 유효하지 않은 값일 경우 NaN이 반환될 수 있습니다.

더 안전한 타입 변환을 위해 다음과 같이 개선할 수 있습니다:

-        value: Number(stat.total_value),
+        value: Number(stat.total_value) || 0,

22-30: 헬퍼 메서드 구현 양호하나 개선 가능

getSuccessMessage 메서드가 깔끔하게 구현되었습니다.

향후 다국어 지원이나 메시지 관리를 위해 별도의 상수 파일이나 i18n 시스템을 고려해볼 수 있습니다.

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

19-20: 런타임 타입 검증의 필요성을 이해하지만 개선 방안을 고려해보세요.

미들웨어에서 이미 GetTotalStatsQueryDto로 검증하지만 런타임과 IDE에서 구분을 위해 추가 검증을 하는 것을 이해합니다. 하지만 더 깔끔한 해결 방법을 고려해볼 수 있습니다.

개선 방안:

- // 미들웨어에서 GetTotalStatsQueryDto 에 의해 걸리는데 런타임과 IDE 에서 구분을 못함, 이를 위해 추가
- if (!type) throw new BadRequestError('type 파라미터가 필요합니다.');
+ // type assertion - validation middleware에서 이미 검증됨
+ const validatedType = type as TotalStatsType;

또는 GetTotalStatsQueryDtotype 필드를 required로 만들어 타입 레벨에서 해결하는 것도 고려해보세요.

src/repositories/totalStats.repository.ts (1)

40-63: Like 통계 조회 메서드에서 코드 중복이 발생합니다.

View 통계 조회 메서드와 거의 동일한 구조를 가지고 있어 코드 중복이 발생합니다. 공통 로직을 추출하여 중복을 줄일 수 있습니다.

공통 메서드 추출을 제안합니다:

+  private async getGenericDailyStats(
+    userId: number, 
+    period: number, 
+    columnName: string, 
+    errorMessage: string
+  ): Promise<RawStatsResult[]> {
+    try {
+      const startDateKST = getKSTDateStringWithOffset(-period * 24 * 60);
+      const query = `
+        SELECT 
+          pds.date,
+          COALESCE(SUM(pds.${columnName}), 0) AS total_value
+        FROM posts_postdailystatistics pds
+        JOIN posts_post p ON p.id = pds.post_id
+        WHERE p.user_id = $1 
+          AND p.is_active = true
+          AND pds.date >= $2
+        GROUP BY pds.date
+        ORDER BY pds.date ASC;
+      `;
+      const result = await this.pool.query(query, [userId, startDateKST]);
+      return result.rows;
+    } catch (error) {
+      logger.error('TotalStats Repo getGenericDailyStats error:', error);
+      throw new DBError(errorMessage);
+    }
+  }
+
   private async getTotalLikeStats(userId: number, period: number): Promise<RawStatsResult[]> {
-    try {
-      const startDateKST = getKSTDateStringWithOffset(-period * 24 * 60);
-      const query = `...`;
-      const result = await this.pool.query(query, [userId, startDateKST]);
-      return result.rows;
-    } catch (error) {
-      logger.error('TotalStats Repo getTotalLikeStats error:', error);
-      throw new DBError('좋아요 통계 조회 중 문제가 발생했습니다.');
-    }
+    return this.getGenericDailyStats(userId, period, 'daily_like_count', '좋아요 통계 조회 중 문제가 발생했습니다.');
   }
src/repositories/__test__/totalStats.repo.test.ts (1)

126-129: Post 통계 테스트에서 중복 검증이 발생합니다.

동일한 쿼리에 대해 두 번의 expect 호출이 있어 중복되고 있습니다.

중복 제거를 제안합니다:

         expect(mockPool.query).toHaveBeenCalledWith(
-          expect.stringContaining('WITH date_series AS'),
-          [userId, mockStartDate]
-        );
-        expect(mockPool.query).toHaveBeenCalledWith(
-          expect.stringContaining('SUM(dp.post_count) OVER'),
+          expect.stringMatching(/WITH date_series AS.*SUM\(dp\.post_count\) OVER/s),
           [userId, mockStartDate]
         );
src/types/index.ts (1)

41-43: 파일 끝에 빈 줄이 누락되었습니다.

코딩 스타일 일관성을 위해 파일 끝에 빈 줄을 추가하는 것이 좋습니다.

 // Common
 export { EmptyResponseDto } from '@/types/dto/responses/emptyReponse.type';
+
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 059a4c0 and 3a8a67e.

📒 Files selected for processing (33)
  • .gitignore (1 hunks)
  • src/controllers/leaderboard.controller.ts (1 hunks)
  • src/controllers/noti.controller.ts (1 hunks)
  • src/controllers/post.controller.ts (1 hunks)
  • src/controllers/totalStats.controller.ts (1 hunks)
  • src/controllers/user.controller.ts (6 hunks)
  • src/exception/index.ts (1 hunks)
  • src/middlewares/auth.middleware.ts (2 hunks)
  • src/repositories/__test__/fixtures.ts (1 hunks)
  • src/repositories/__test__/integration/leaderboard.repo.integration.test.ts (1 hunks)
  • src/repositories/__test__/integration/post.repo.integration.test.ts (1 hunks)
  • src/repositories/__test__/integration/qr.repo.integration.test.ts (1 hunks)
  • src/repositories/__test__/leaderboard.repo.test.ts (1 hunks)
  • src/repositories/__test__/post.repo.test.ts (1 hunks)
  • src/repositories/__test__/qr.repo.test.ts (1 hunks)
  • src/repositories/__test__/totalStats.repo.test.ts (1 hunks)
  • src/repositories/leaderboard.repository.ts (1 hunks)
  • src/repositories/noti.repository.ts (2 hunks)
  • src/repositories/post.repository.ts (2 hunks)
  • src/repositories/totalStats.repository.ts (1 hunks)
  • src/repositories/user.repository.ts (1 hunks)
  • src/routes/index.ts (2 hunks)
  • src/routes/post.router.ts (1 hunks)
  • src/routes/totalStats.router.ts (1 hunks)
  • src/services/noti.service.ts (1 hunks)
  • src/services/post.service.ts (1 hunks)
  • src/services/totalStats.service.ts (1 hunks)
  • src/services/user.service.ts (3 hunks)
  • src/types/dto/requests/getTotalStatsQuery.type.ts (1 hunks)
  • src/types/dto/responses/totalStatsResponse.type.ts (1 hunks)
  • src/types/index.ts (1 hunks)
  • src/utils/date.util.ts (3 hunks)
  • src/utils/generateRandomToken.util.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (9)
src/services/noti.service.ts (2)
src/repositories/noti.repository.ts (1)
  • NotiRepository (5-29)
src/types/models/NotiPost.type.ts (1)
  • NotiPost (1-9)
src/controllers/post.controller.ts (1)
src/services/post.service.ts (1)
  • PostService (6-100)
src/controllers/noti.controller.ts (2)
src/services/noti.service.ts (1)
  • NotiService (4-10)
src/types/dto/responses/notiResponse.type.ts (1)
  • NotiPostsResponseDto (33-33)
src/services/user.service.ts (4)
src/repositories/user.repository.ts (1)
  • UserRepository (7-144)
src/types/index.ts (2)
  • VelogUserCurrentResponse (3-3)
  • User (2-2)
src/modules/velog/velog.type.ts (1)
  • VelogUserCurrentResponse (17-24)
src/types/models/User.type.ts (1)
  • User (1-11)
src/repositories/__test__/qr.repo.test.ts (2)
src/repositories/user.repository.ts (1)
  • UserRepository (7-144)
src/repositories/__test__/fixtures.ts (1)
  • mockPool (16-20)
src/controllers/user.controller.ts (1)
src/services/user.service.ts (1)
  • UserService (12-159)
src/types/dto/responses/totalStatsResponse.type.ts (2)
src/types/index.ts (2)
  • TotalStatsItem (37-37)
  • TotalStatsResponseDto (39-39)
src/types/dto/responses/baseResponse.type.ts (1)
  • BaseResponseDto (28-40)
src/controllers/totalStats.controller.ts (5)
src/services/totalStats.service.ts (1)
  • TotalStatsService (5-30)
src/types/index.ts (2)
  • GetTotalStatsQuery (35-35)
  • TotalStatsResponseDto (39-39)
src/types/dto/requests/getTotalStatsQuery.type.ts (1)
  • GetTotalStatsQuery (7-10)
src/types/dto/responses/totalStatsResponse.type.ts (1)
  • TotalStatsResponseDto (44-44)
src/exception/index.ts (1)
  • BadRequestError (11-11)
src/repositories/__test__/totalStats.repo.test.ts (5)
src/repositories/totalStats.repository.ts (1)
  • TotalStatsRepository (12-115)
src/utils/date.util.ts (1)
  • getKSTDateStringWithOffset (44-57)
src/repositories/__test__/fixtures.ts (2)
  • mockPool (16-20)
  • createMockQueryResult (49-57)
src/exception/index.ts (1)
  • DBError (2-2)
src/types/index.ts (1)
  • TotalStatsType (34-34)
🔇 Additional comments (54)
.gitignore (1)

22-23: 템플릿 기반 .gitignore 확장으로 다중 플랫폼/도구 지원 강화
toptal 템플릿을 활용해 macOS, Windows, VSCode, Node.js 등 다양한 환경에 일관된 ignore 패턴을 적용한 점이 훌륭합니다.

src/utils/generateRandomToken.util.ts (1)

15-15: EOF 개행 추가 확인
파일 끝에 개행(newline)을 추가하여 POSIX 준수 및 에디터 호환성 향상을 도모합니다.

src/repositories/__test__/integration/leaderboard.repo.integration.test.ts (1)

9-9: import 구문 통합으로 가독성 향상
pg 모듈의 기본 및 명명된 Pool을 한 줄로 가져와서 import 선언이 간결해졌습니다.

src/exception/index.ts (1)

3-9: export 블록 다중 라인으로 정렬
토큰 관련 예외들을 블록 형태로 다중 라인에 걸쳐 내보내기 하여 가독성이 개선되었습니다.

src/repositories/noti.repository.ts (2)

6-6: 생성자 중괄호 스타일 정리
constructor(private pool: Pool) {} 형태로 중괄호 내부 공백이 제거되어 일관된 코드 스타일을 유지합니다.


29-29: EOF 개행 추가 확인
파일 끝에 개행을 추가하여 POSIX 표준 및 일부 도구 호환성을 확보했습니다.

src/services/noti.service.ts (2)

1-2: import 구문 스타일 일관성 확인
단일 인용 부호와 경로 별칭(@/) 사용으로 코드 전체 스타일 가이드에 부합합니다.


5-10: 들여쓰기 및 메서드 포맷 통일
생성자 및 getAllNotiPosts 메서드의 들여쓰기가 2스페이스로 통일되었고, await 사용 위치도 적절히 포맷팅되었습니다.

src/repositories/leaderboard.repository.ts (1)

13-13: 형식 변경 확인
buildLeaderboardCteQuery 호출 뒤 불필요한 공백이 제거되었습니다. 기능에 영향이 없으므로 무시 가능합니다.

src/routes/post.router.ts (1)

140-140: 트레일링 쉼표 추가
라우트 핸들러에 트레일링 쉼표가 추가되었지만 기능에는 영향이 없습니다.

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

9-9: 경로 단순화 확인
@/types/index 대신 @/types로 import 경로가 간결해졌습니다.

src/repositories/__test__/integration/post.repo.integration.test.ts (2)

2-2: pg import 통합 확인
pg 모듈과 Pool을 하나의 import로 통합하여 중복을 제거했습니다.


4-4: 절대 경로 import 확인
PostRepository를 절대경로(@/repositories/post.repository)로 가져와 일관성을 향상시켰습니다.

src/repositories/user.repository.ts (1)

4-4: 따옴표 스타일 일관화
타입 import시 " "에서 ' '로 변경되어 코드 스타일이 통일되었습니다.

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

14-14: 코드 포맷팅 개선이 잘 적용되었습니다.

생성자 선언에서 불필요한 공백이 제거되어 코드 스타일이 일관되게 개선되었습니다.

src/repositories/__test__/integration/qr.repo.integration.test.ts (1)

2-3: import 문 구조화가 잘 적용되었습니다.

pg 라이브러리의 여러 export를 하나의 import 문으로 통합하여 코드가 더 깔끔해졌습니다.

src/middlewares/auth.middleware.ts (2)

35-36: 함수 포맷팅이 개선되었습니다.

arrow function이 멀티라인으로 포맷팅되어 가독성이 향상되었습니다.


55-56: 데이터베이스 쿼리 포맷팅이 개선되었습니다.

쿼리문이 여러 라인으로 분할되어 코드 가독성이 좋아졌습니다.

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

1-4: 테스트 유틸리티 중앙화 개선이 잘 되었습니다.

인라인 목 정의를 제거하고 공유 fixtures 모듈을 사용하는 리팩토링이 잘 적용되었습니다. 이를 통해 코드 중복을 줄이고 테스트 유틸리티의 일관성을 확보할 수 있습니다.

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

1-5: 일관된 테스트 리팩토링 패턴이 적용되었습니다.

post.repo.test.ts와 동일한 패턴으로 fixtures 모듈을 사용하여 목 유틸리티를 중앙화했습니다. 테스트 파일 간 일관성이 확보되어 좋습니다.

src/services/post.service.ts (2)

7-7: 생성자 포맷팅이 일관되게 개선되었습니다.

중괄호 내부 공백을 제거하여 일관된 코드 스타일을 적용했습니다.


12-14: 문자열 리터럴과 조건문 포맷팅이 개선되었습니다.

  • 문자열 리터럴에서 일관된 single quote 사용
  • else 절을 같은 줄로 이동하여 표준 스타일 가이드를 따름

이러한 변경으로 코드 일관성이 향상되었습니다.

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

13-13: 생성자 포맷팅이 일관되게 적용되었습니다.

다른 서비스 파일들과 동일한 생성자 포맷팅을 적용하여 코드 일관성을 확보했습니다.


53-57: 매개변수 포맷팅이 가독성을 향상시켰습니다.

긴 매개변수 목록을 멀티라인으로 포맷팅하여 가독성이 크게 개선되었습니다. 각 매개변수가 명확하게 구분되어 코드 이해가 쉬워졌습니다.


102-102: trailing comma 추가로 일관성이 개선되었습니다.

trailing comma를 추가하여 향후 diff 관리가 더 수월해지고 코드 일관성이 향상되었습니다.

src/repositories/__test__/qr.repo.test.ts (2)

5-7: 테스트 설정이 일관되게 개선되었습니다.

fixtures 모듈 사용과 jest.mock 추가로 다른 테스트 파일들과 일관된 패턴을 적용했습니다.


13-13: 타입 캐스팅 접근법이 개선되었습니다.

더블 캐스팅(as unknown as Pool)을 사용하여 타입 안전성을 향상시켰습니다.

src/controllers/noti.controller.ts (3)

3-4: 포매팅 개선 잘 적용됨

import 문에서 일관되게 단일 따옴표를 사용하도록 표준화한 변경사항이 좋습니다.


7-7: 생성자 포매팅 개선

생성자를 단일 행으로 정리하여 가독성이 향상되었습니다.


9-12: 메서드 시그니처 및 응답 객체 생성 포매팅 개선

메서드 시그니처와 DTO 인스턴스 생성을 단일 행으로 정리하여 코드가 더 간결해졌습니다.

src/utils/date.util.ts (1)

3-57: JSDoc 및 함수 포매팅 개선

JSDoc 주석과 함수 본문의 공백 및 포매팅이 일관되게 개선되었습니다. 코드 가독성 향상에 도움이 됩니다.

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

1-44: 잘 구조화된 DTO 타입 정의

새로운 total statistics 기능을 위한 DTO가 잘 정의되었습니다:

  • TotalStatsItem 인터페이스의 타입 정의가 명확합니다
  • Swagger 문서화가 상세하고 예시값도 포함되어 있어 API 문서화에 도움이 됩니다
  • BaseResponseDto를 적절히 상속하여 일관된 응답 구조를 유지합니다
  • 날짜는 ISO 8601 형식 문자열, 값은 음수가 아닌 정수로 적절히 제약이 설정되었습니다
src/controllers/user.controller.ts (6)

12-12: 생성자 포매팅 개선

생성자를 단일 행으로 정리하여 간결성이 향상되었습니다.


24-24: 문자열 리터럴 따옴표 통일

도메인 설정에서 단일 따옴표로 일관성 있게 변경되었습니다.


62-66: 메서드 시그니처 멀티라인 포매팅

sampleLogin 메서드의 매개변수를 멀티라인으로 정리하여 가독성이 향상되었습니다.


83-84: 테스트 데이터 따옴표 통일

테스트 사용자 정보의 문자열 리터럴에서 단일 따옴표로 일관성 있게 변경되었습니다.


120-127: IP 추출 로직 포매팅 개선

createToken 메서드에서 IP 추출 로직을 멀티라인으로 포매팅하여 가독성이 크게 향상되었습니다.


132-132: 응답 메시지 따옴표 통일

QR 토큰 응답 메시지에서 단일 따옴표로 일관성 있게 변경되었습니다.

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

5-6: 적절한 의존성 주입 패턴

생성자를 통한 의존성 주입 패턴이 잘 적용되어 테스트하기 쉬운 구조입니다.

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

1-74: 구현이 우수합니다! Total Stats 라우터가 잘 설계되었습니다.

새로운 전체 통계 기능의 라우터 구현이 Express.js 모범 사례를 잘 따르고 있습니다:

  • 의존성 주입 패턴이 올바르게 적용됨
  • 인증 → 검증 → 컨트롤러 순서의 미들웨어 체인이 적절함
  • Swagger 문서화가 상세하고 예시가 포함되어 있음
  • 라우트 핸들러가 적절히 바인딩됨
src/controllers/totalStats.controller.ts (1)

7-33: 컨트롤러 구현이 우수합니다!

Total Stats 컨트롤러가 Express.js 패턴을 잘 따르고 있습니다:

  • 적절한 TypeScript 타입 사용
  • 서비스 레이어와의 의존성 주입
  • 에러 핸들링과 로깅
  • DTO를 사용한 응답 구조화
  • 적절한 HTTP 상태 코드 사용
src/repositories/__test__/fixtures.ts (1)

1-57: 테스트 유틸리티 구현이 훌륭합니다!

PostgreSQL 쿼리 모킹을 위한 중앙화된 유틸리티가 잘 설계되었습니다:

  • 적절한 TypeScript 제네릭 사용으로 타입 안전성 확보
  • 상세한 JSDoc 문서화와 사용 예시 제공
  • PostgreSQL QueryResult 인터페이스를 정확히 구현
  • 테스트 코드 중복을 줄이는 중앙화된 모킹 설정
  • 명확한 네이밍 컨벤션

이러한 테스트 유틸리티는 일관된 테스트 작성과 유지보수성 향상에 크게 기여할 것입니다.

src/repositories/post.repository.ts (1)

7-7: 코드 포매팅 개선이 적절합니다.

Prettier를 통한 일관된 코드 스타일 적용이 확인됩니다:

  • 생성자 매개변수 포매팅 정리
  • 메서드 매개변수 단일 라인 포매팅
  • 최신 JavaScript/TypeScript 관례에 따른 trailing comma 추가

기능적 변경 없이 코드베이스의 일관성을 향상시키는 좋은 개선입니다.

Also applies to: 9-9, 128-128

src/repositories/totalStats.repository.ts (4)

1-6: 임포트 및 인터페이스 정의가 적절합니다.

필요한 의존성들이 올바르게 임포트되었고, RawStatsResult 인터페이스 정의도 명확합니다.


15-38: View 통계 조회 메서드에서 SQL 인젝션 방지가 잘 되어 있습니다.

파라미터화된 쿼리를 사용하여 SQL 인젝션을 방지하고 있으며, COALESCE를 사용한 NULL 처리도 적절합니다. 에러 처리도 잘 구현되어 있습니다.


103-115: 메인 메서드의 타입 안전성이 잘 구현되어 있습니다.

Switch 문을 사용한 타입별 분기 처리가 적절하며, default case에서 에러를 던지는 것도 좋습니다. TypeScript의 타입 안전성을 잘 활용하고 있습니다.


65-101:

❓ Verification inconclusive

Post 통계 쿼리에서 잠재적인 성능 이슈가 있습니다.

CTE와 윈도우 함수를 사용한 복잡한 쿼리입니다. 큰 데이터셋에서는 성능 이슈가 발생할 수 있으므로 인덱스 최적화를 고려해야 합니다.

데이터베이스 인덱스 확인을 위한 스크립트를 실행해주세요:


🏁 Script executed:

#!/bin/bash
# Description: posts_post 테이블의 인덱스와 테이블 구조 확인
# Expected: user_id, is_active, released_at 컬럼에 적절한 인덱스가 있는지 확인

# posts_post 테이블의 인덱스 정보 검색
rg -A 10 -B 5 "CREATE.*INDEX.*posts_post" --type sql
rg -A 10 -B 5 "user_id.*index\|index.*user_id" --type sql
rg -A 10 -B 5 "released_at.*index\|index.*released_at" --type sql

Length of output: 629


인덱스 존재 및 최적화 검증 필요

CTE와 윈도우 함수를 사용하는 조회 쿼리는 데이터가 많아질수록 성능 저하가 우려됩니다.
posts_post 테이블에 아래 칼럼 조합에 대한 적절한 인덱스가 있는지 DB 차원에서 직접 확인해 주세요.

• 확인용 예시 SQL

-- 기본 인덱스 조회
SELECT indexname, indexdef
  FROM pg_indexes
 WHERE tablename = 'posts_post';

-- 필요 시 사용자·활성화·날짜 칼럼 복합 인덱스 존재 여부 확인
SELECT indexname, indexdef
  FROM pg_indexes
 WHERE tablename = 'posts_post'
   AND indexdef LIKE '%(user_id, is_active, released_at)%';

• 인덱스가 없다면, 아래와 같은 복합 인덱스 추가를 검토해주세요.

CREATE INDEX idx_posts_post_user_active_released
  ON posts_post(user_id, is_active, released_at);
src/types/dto/requests/getTotalStatsQuery.type.ts (1)

1-11: 타입 정의와 인터페이스가 명확하고 적절합니다.

TotalStatsPeriodTotalStatsType의 타입 제한이 명확하며, 인터페이스 정의도 적절합니다.

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

1-26: 테스트 설정과 모킹이 잘 구현되어 있습니다.

의존성 모킹과 테스트 설정이 적절하며, beforeEach에서 모킹을 초기화하는 것도 좋습니다.


37-68: View 통계 테스트가 포괄적입니다.

성공 케이스와 에러 케이스를 모두 테스트하고 있으며, 쿼리 호출 검증도 적절합니다.


143-152: 잘못된 타입 처리 테스트가 훌륭합니다.

타입 안전성을 테스트하고 있으며, 데이터베이스 쿼리가 호출되지 않는 것을 확인하는 것도 좋습니다.


184-223: SQL 쿼리 검증 테스트가 매우 유용합니다.

실제 생성되는 SQL 쿼리의 내용을 검증하여 비즈니스 로직이 올바르게 구현되었는지 확인하고 있습니다. 이는 매우 좋은 테스트 접근법입니다.

src/types/index.ts (2)

1-8: 사용자 및 로그인 관련 export가 잘 구성되어 있습니다.

기능별로 명확하게 그룹화되어 있으며, export 방식도 일관성이 있습니다.


31-40: 새로 추가된 Total Stats 관련 export가 적절합니다.

다른 기능 그룹과 동일한 패턴으로 구성되어 있으며, 필요한 타입들이 모두 포함되어 있습니다.

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.

코드 잘 읽었습니다!
실제 로직 추가 외에도, 코드 컨벤션 맞춤이나 구조 리펙토링이 많은 것 같네요.
크게 나쁜 점은 없었고, 제가 남겨둔 질문만 한 번 확인해주시면 될 것 같습니다!

좋았던 점

  • 전체적으로 코드 컨벤션이 통일된 것 같습니다 (혹시 저장 시 자동 린팅을 결국 키셨을까요?)
  • 이전에 말 나왔던 구조 (반복적으로 Mock 값을 작성하는 등) 가 개선된 것 같습니다

const { id } = req.user;
const { period, type } = req.query;

// 미들웨어에서 GetTotalStatsQueryDto 에 의해 걸리는데 런타임과 IDE 에서 구분을 못함, 이를 위해 추가
Copy link
Member

Choose a reason for hiding this comment

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

이 부분이 정확히 어떤 것을 의미하는지 조금 이해하기 어렵네요..
정확히 무슨 이유로 추가해주신걸까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

기준님도 아시다시피 type, interface (typscript) 문법들은 run-time 에서는 없어지잖아요~
그렇기때문에 벨리데이션은 무조건 DTO 로 해야합니다. validateRequestDto(GetTotalStatsQueryDto, 'query')

하지만 컨트롤러의 Request 객체는 class type 선언을 쓰기엔 복잡하죠(제네릭부터 선언해야할 것이 엄청 많음). 그래서 interface 로 사용하죠! (그래서 내용이 같은거, express 가 ts 랑 사실 그렇게 궁합이 맞냐라는 질문에 역시 세모인 이유)

그래서 IDE와 린팅은 이를 인터페이스 타입이라 인지하고 옵셔널 값으로 인지해버려요. (더욱이 require 로 해버리면 제네릭을 써야하는 상황) 그래서 이 라인이 필요합니다!

@six-standard
Copy link
Member

API를 연동하면서 실제 동작 테스트도 진행해봤는데, 다른 API들은 멀쩡하나 총 게시글 통계쪽 API가 이상하게 동작하는 것 같습니다!
제가 리뷰하면서 SQL쪽 문제를 놓쳤을 가능성이 있을 것 같은데, 여유 있으실 때 한 번 확인해주세요!
(아니면 현재 0개로 뜨는게 의도된 동작일까요?)

정상 동작 (총 조회수 통계)

Screenshot 2025-06-03 at 3 33 08 PM

비정상 추정 동작 (총 게시글 통계) (글 갯수가 0개로 표시되며, 넘어온 통계의 갯수가 9개인 상황)

Screenshot 2025-06-03 at 3 48 06 PM

@Nuung
Copy link
Member Author

Nuung commented Jun 3, 2025

API를 연동하면서 실제 동작 테스트도 진행해봤는데, 다른 API들은 멀쩡하나 총 게시글 통계쪽 API가 이상하게 동작하는 것 같습니다! 제가 리뷰하면서 SQL쪽 문제를 놓쳤을 가능성이 있을 것 같은데, 여유 있으실 때 한 번 확인해주세요! (아니면 현재 0개로 뜨는게 의도된 동작일까요?)

비정상 추정 동작 (총 게시글 통계) (글 갯수가 0개로 표시되며, 넘어온 통계의 갯수가 9개인 상황)

Screenshot 2025-06-03 at 3 48 06 PM

@six-standard
기준님! 총 게시글 통계, "증가순" 이라서요 그냥 개수로 갈까요?

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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e73b84 and eb05ca8.

📒 Files selected for processing (2)
  • src/repositories/__test__/totalStats.repo.test.ts (1 hunks)
  • src/repositories/totalStats.repository.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/repositories/totalStats.repository.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/repositories/__test__/totalStats.repo.test.ts (5)
src/repositories/totalStats.repository.ts (1)
  • TotalStatsRepository (12-109)
src/utils/date.util.ts (1)
  • getKSTDateStringWithOffset (44-57)
src/repositories/__test__/fixtures.ts (2)
  • mockPool (16-20)
  • createMockQueryResult (49-57)
src/exception/index.ts (1)
  • DBError (2-2)
src/types/index.ts (1)
  • TotalStatsType (34-34)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build-and-test (21)
  • GitHub Check: build-and-test (22)
  • GitHub Check: build-and-test (20)
  • GitHub Check: build-and-test (23)
🔇 Additional comments (9)
src/repositories/__test__/totalStats.repo.test.ts (9)

1-16: 의존성 모킹 설정이 적절합니다.

필요한 모듈들을 올바르게 임포트하고, 로거와 날짜 유틸리티를 적절히 모킹했습니다. 테스트 격리를 위한 좋은 설정입니다.


17-36: 테스트 설정이 잘 구성되어 있습니다.

beforeEach에서 모킹된 함수들을 적절히 설정하고 각 테스트마다 모킹을 초기화하는 것이 좋습니다.


37-69: view 타입 테스트가 포괄적입니다.

성공 케이스와 에러 케이스를 모두 테스트하고 있으며, 쿼리 호출과 매개변수 검증도 적절합니다.


71-103: like 타입 테스트가 적절합니다.

view 타입과 일관된 패턴으로 테스트가 작성되어 있어 좋습니다.


143-152: 잘못된 타입 처리 테스트가 적절합니다.

지원되지 않는 타입에 대한 에러 처리와 데이터베이스 호출이 발생하지 않는 것을 확인하는 테스트가 좋습니다.


154-168: 다양한 기간 테스트가 유용합니다.

30일 기간에 대한 테스트로 기간 계산 로직이 올바르게 작동하는지 확인하고 있습니다.


170-182: 빈 결과 처리 테스트가 적절합니다.

데이터가 없을 때의 동작을 테스트하는 것이 좋습니다.


108-112:

✅ Verification successful

데이터 타입 일관성을 확인해야 합니다.

post 타입 테스트에서 total_value가 숫자 타입(5, 7, 10)으로 설정되어 있는데, view와 like 테스트에서는 문자열 타입('100', '150' 등)을 사용하고 있습니다. 실제 데이터베이스 응답과 일치하는지 확인해야 합니다.

다음 스크립트로 실제 데이터베이스 응답 타입을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# 데이터베이스 스키마와 타입 정보 확인
ast-grep --pattern $'interface RawStatsResult {
  $$$
}'

Length of output: 311


RawStatsResult 타입 확인 완료: total_valuestring | number으로 정의되어 있어, 테스트에서 숫자(5, 7, 10)와 문자열('100', '150' 등)을 혼용해도 유효합니다. 변경이 필요 없습니다.


184-221:

✅ Verification successful

SQL 쿼리 검증 테스트가 포괄적입니다.

각 통계 타입별로 생성되는 SQL 쿼리의 주요 구성 요소들을 검증하고 있어 좋습니다. 특히 post 타입의 복잡한 CTE 쿼리 구조를 확인하는 것이 유용합니다.

PR 목표에서 언급된 post 통계 API에서 0개 게시글이 표시되는 문제와 관련하여, 실제 SQL 쿼리 로직을 한 번 더 검증해보시기 바랍니다:


🏁 Script executed:

#!/bin/bash
# getTotalPostStats 메서드의 실제 SQL 쿼리 로직 확인
ast-grep --pattern $'getTotalPostStats($_, $_) {
  $$$
}'

Length of output: 58


아래와 같이 메서드 이름과 파라미터 수를 수정한 패턴으로 다시 SQL 쿼리 로직을 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# getTotalStats 메서드 구현 확인 (3개 파라미터)
ast-grep --pattern $'getTotalStats($_, $_, $_) {
  $$$
}'

Length of output: 58


getTotalStats 메서드 정의 위치와 구현을 확인하기 위해 아래 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
# getTotalStats 메서드 정의 위치 및 구현 내용 출력
rg "getTotalStats" -n src/repositories -A5 -B2

Length of output: 9640


post 통계 SQL 로직 검증을 위해 getTotalPostStats 구현부를 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# post 통계 SQL 로직 확인: getTotalPostStats 구현부 추출
rg "async getTotalPostStats" -n src/repositories -A10 -B2

Length of output: 1019


SQL 쿼리 로직 검증 완료

getTotalPostStats 구현부(src/repositories/totalStats.repository.ts:65–75)에서

  • CTE 선언(WITH date_series AS)
  • generate_series 호출

두 가지 핵심 요소가 모두 포함된 것을 확인했습니다. 기존 테스트가 view/like/post 모든 통계 타입의 SQL을 포괄적으로 검증하고 있으므로, 추가 검증 없이 해당 로직이 의도대로 동작할 것으로 판단됩니다.

Copy link
Contributor

@ooheunda ooheunda left a comment

Choose a reason for hiding this comment

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

코드 잘 봤습니다. 코멘트 가볍게 확인 부탁드릴게요!
고생하셨습니다~!! 👍 🔥

좋았던 점

  • CTE, 윈도우 함수를 통해 만드신 게시물 통계 쿼리가 인상 깊었습니다. (윈도우 함수는 수정되어 사라졌네요...😿)
  • 각 테스트에서 사용중이던 중복된 mockQueryResult 함수를 fixture로 뺀 부분 너무 좋습니다ㅎㅎ
  • 테스트 코드도 너무 깔끔하고 좋았습니다!

Comment on lines 19 to 23
// 미들웨어에서 GetTotalStatsQueryDto 에 의해 걸리는데 런타임과 IDE 에서 구분을 못함, 이를 위해 추가
if (!type) throw new BadRequestError('type 파라미터가 필요합니다.');

const stats = await this.totalStatsService.getTotalStats(id, period, type);
const message = this.totalStatsService.getSuccessMessage(type);
Copy link
Contributor

Choose a reason for hiding this comment

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

요 부분 서비스단에서 type 매개변수도 기본값 설정해주면 따로 체크 안해도 괜찮을 것 같은데 어떻게 생각하시나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

#33 (comment)

와 같은 맥락입니다! optional 빼면 여기 Type 체크를 제네릭부터 다시 시작하거나 강제 casting 을 해야 해서요!
미들웨어 벨리데이션에서 이미 검증하기에 컨트롤러 도착할땐 무조건 type 이 이미 존재하긴 합니다! IDE 를 위한 라인..!

Copy link
Contributor

Choose a reason for hiding this comment

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

넵 그 부분 이해했습니다!
저는 period도 동일하게 DTO의 생성자에서 기본값 설정이 된 채로 들어오는데, 서비스단에서 기본값 설정을 해두셨길래 type도 똑같이 해두면 되지 않을까 싶었습니다.

export class TotalStatsService {
  async getTotalStats(
    userId: number,
    period: TotalStatsPeriod = 7, // 기존에도 있었던 부분
    type: TotalStatsType = 'view',
  ): Promise<TotalStatsItem[]> {}
}

이렇게요! 그럼 상위에서도 TS에러가 뜨지 않아서요. (확인해보니 리더보드도 이런 식으로 우회되었네요.)
근데 이런 식으로 쓰려면 getSuccessMessage 와 같은 메서드에서도 기본값을 정해두어야 해서, 애매하게 보이긴 하네요.

현우님께서 편하신 방향으로 🙏

Copy link
Member Author

Choose a reason for hiding this comment

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

오케이! 이왕 default 값 넣어버린거, 서비스 arguments 에 모두 default 값 넣는 걸로 update 할게요! 좋은 지적 너무 감사해요!

Comment on lines +143 to +152
describe('잘못된 타입 처리', () => {
it('지원되지 않는 통계 타입이 전달되면 DBError를 던져야 한다', async () => {
// When & Then
await expect(
repository.getTotalStats(userId, period, 'invalid' as unknown as TotalStatsType)
).rejects.toThrow(new DBError('지원되지 않는 통계 타입입니다.'));

expect(mockPool.query).not.toHaveBeenCalled();
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

DTO에서 enum에 해당하지 않는 type 값이면 모두 400을 받을 것 같은데, 이 케이스는 고려하게 되신 특정한 이유가 있었는지 궁금합니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

미들웨에서 처리하고 사실 이 테스트는 API level 이 아니라 우리가 잘 못 쓰는 것을 막아야 할 때에 대한 테스트라고 보면 될 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

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

(라우터, validateRequestDto(GetTotalStatsQueryDto, 'query'))

Copy link
Contributor

Choose a reason for hiding this comment

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

말씀하신대로 '우리가 잘 못 쓰는 것을 막아야 할 때'에 한해서 저는 이 케이스가 TS 컴파일러의 몫이라고 생각했습니다!getTotalStatstype이 잘 정의되어 있어서 문제가 없을 것 같은데, TS의 영향밖인 런타임에서 저 type값이 바뀌는 케이스가 있을까요?

Copy link
Member Author

@Nuung Nuung Jun 3, 2025

Choose a reason for hiding this comment

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

저도 그런 부분들 항상 고민이긴 했어요,, 오버테스팅인지 아닌지 ..

근데 요즘은 next 를 더 많이 고민하게 되었어요. 나중에 getTotalStats 에 TotalStatsType 대신 타입을 바꿧다고 해볼게요. (TotalStatsType 에 타입 추가 경우로, 예시 'follwer' )

이 경우, 다른 부분 하나도 안바꾸고 타입만 바꾼뒤 실행해도 에러가 안납니다! 오히려 추가된 타입이라고 예를 들자면 follwer 라는 값을 넣으면 DTO 에서는 통과가 될거에요. 하지만 레포지토리에서 터지죠. 이걸 노린거라고 봐주시면 좋아요! @ooheunda
(실행도 잘 될거에용)

그래서 예상 된 값 외에 무조건 터져야 할 때, 무조건 터지는지! 를 추가한 로직임다!

Copy link
Contributor

Choose a reason for hiding this comment

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

아하 이해했습니다. 예시 들어 설명해주셔서 너무 감사해요!
단순히 타입으로 지정되어 있으니 괜찮지 않을까 싶었는데, 레포 코드에선 타입을 그대로 받지 않고 스위치문으로 분기처리하니 말씀하신 경우에서 무조건 에러가 나겠네요. 그래서 default문도 설정해주신 거고요.
제가 생각이 좀 짧았네요ㅎㅎㅎ 감사합니다!

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.

코드 잘 봤습니다! 고생하셨습니다~!!

좋았던 점

  • getTotalViewStats, getTotalLikeStats, getTotalPostStats 모두 각각의 메서드로 분리하셔서 추후 유지보수에 좋을 것 같습니다!!
  • 이에 따라 각 에러에 대한 원인 파악이 훨씬 명확할 것 같네요:)

@Nuung Nuung merged commit d099a2f into main Jun 3, 2025
4 of 5 checks passed
@Nuung Nuung deleted the feature/total-daily-stats branch June 3, 2025 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants