Skip to content

[25.06.25 / TASK-214] Feature - 센트리 슬랙 연동 및 API 통합 #36

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

Closed
wants to merge 2 commits into from

Conversation

six-standard
Copy link
Member

@six-standard six-standard commented Jun 25, 2025

🔥 변경 사항

웹훅 (현재는 봇으로 구현됨) 을 통해 센트리와 슬랙을 연결하고, 오류가 발생하면 알림이 뜨도록 개선하였습니다
해당 과정에서 후킹 서버가 필요하여 순수 NodeJS 서버로 제작했으나, SPoF 문제에 대해 현우님이 말씀해주셔서 우선 현재 API 서버에 통합하는 방식으로 개선해보았습니다.

참고로 AI가 서버 코드를 읽고 맞춰줬기 때문에 제가 놓친 문제가 있을 확률이 있습니다
동작 테스트는 해두었으나 한 번 잘 읽어보시고 조금이라도 이상한 부분이 있으면 바로 리뷰 남겨주세요!

(그러고 보니 테스트 코드의 필요성이 보이네요.. 백엔드 분들 정말 존경합니다!)

🏷 관련 이슈

  • 관련 이슈: X

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

(변경 사항이 UI와 관련된 경우 스크린샷을 첨부해주세요.)

📌 체크리스트

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

Summary by CodeRabbit

  • 신규 기능

    • Sentry 이슈 이벤트 및 상태 변경을 Slack 채널에 알림으로 전송하는 웹훅 연동 기능이 추가되었습니다.
    • Slack 메시지 내에서 Sentry 이슈 상태 변경(해결, 무시, 삭제 등) 버튼을 통한 상호작용이 지원됩니다.
    • Slack 및 Sentry API와의 통합을 위한 서비스 및 타입 정의가 추가되었습니다.
  • 문서화

    • Sentry 및 Slack 웹훅 엔드포인트에 대한 Swagger 문서가 추가되었습니다.
  • 기타

    • Slack 및 Sentry 관련 타입과 유틸리티 함수가 추가되었습니다.
    • PostgreSQL 연결 설정에서 SSL 기본값이 비활성화되었습니다.

@six-standard six-standard self-assigned this Jun 25, 2025
@six-standard six-standard added the enhancement New feature or request label Jun 25, 2025
Copy link

센트리 슬랙 연동

Copy link
Contributor

coderabbitai bot commented Jun 25, 2025

Walkthrough

Sentry와 Slack의 연동을 위한 웹훅 컨트롤러, 서비스, 타입, 유틸리티가 신규로 도입되었습니다. Sentry 이슈 이벤트를 Slack 메시지로 전송하고, Slack 인터랙티브 액션을 통해 Sentry 이슈 상태를 변경할 수 있도록 전체적인 흐름이 구현되었습니다. 라우터, 타입, DB 설정 등도 이에 맞게 보완되었습니다.

Changes

파일/경로 요약 변경 내용 요약
src/controllers/webhook.controller.ts
src/routes/webhook.router.ts
src/routes/index.ts
Sentry/Slack 웹훅 컨트롤러 및 라우터 신규 도입, 메인 라우터에 통합
src/services/sentry.service.ts Sentry API 연동 서비스 신규 구현 (이슈 상태 변경, 삭제 등 지원)
src/services/slack.service.ts Slack 메시지 송수신 및 권한 확인 서비스 신규 구현
src/types/models/Sentry.type.ts
src/types/models/Slack.type.ts
src/types/dto/requests/slackRequest.type.ts
src/types/index.ts
Sentry/Slack 관련 타입 및 DTO 신규 정의 및 통합 export
src/utils/sentry.util.ts Sentry API 상태/URL 유틸리티 함수 신규 추가
src/utils/slack.util.ts Sentry 이슈를 Slack 메시지로 포매팅하는 유틸리티 함수 신규 추가
src/configs/db.config.ts PostgreSQL SSL 옵션 기본값을 false로 명시, 관련 코드 주석 처리

Sequence Diagram(s)

sequenceDiagram
    participant Sentry
    participant Express
    participant WebhookController
    participant SlackService
    participant SentryService

    Sentry->>Express: Sentry Webhook POST /webhook/sentry
    Express->>WebhookController: handleSentryWebhook
    WebhookController->>SlackService: format & send Slack message
    SlackService-->>WebhookController: Slack 메시지 송신 결과
    WebhookController-->>Express: HTTP 200 응답

    participant Slack
    Slack->>Express: Slack Interactive POST /webhook/slack/interactive
    Express->>WebhookController: handleSlackInteractive
    WebhookController->>SentryService: handleIssueAction (이슈 상태 변경)
    SentryService-->>WebhookController: 처리 결과 반환
    WebhookController->>SlackService: Slack 메시지 업데이트
    SlackService-->>WebhookController: 업데이트 결과
    WebhookController-->>Express: HTTP 200 응답
Loading

Suggested reviewers

  • Nuung
  • Jihyun3478

Poem

🐇
새벽에 토끼가 귀를 쫑긋 세우고,
Sentry와 Slack을 연결해 보았죠.
이슈가 오면 알림을 띄우고,
버튼을 누르면 상태도 바꾸고!
코드 속에서 깡충깡충,
협업의 힘을 한껏 담았답니다.
🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Post Copyable Unit Tests in Comment
  • Commit Unit Tests in branch feature/sentry-slack-notification

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • 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 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 for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for 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 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 for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 Nitpick comments (3)
src/types/models/Slack.type.ts (1)

1-87: Slack API 타입 정의가 포괄적이고 잘 구성되어 있습니다.

전반적으로 Slack의 메시징 및 상호작용 API 구조를 잘 모델링했습니다. 다음 몇 가지 개선사항을 제안합니다:

  1. SlackAction 인터페이스에서 type 필드가 'button'으로 고정되어 있는데, Slack은 다른 액션 타입도 지원합니다 (예: select, datepicker 등).
  2. SlackMessage[key: string]: unknown 인덱스 시그니처가 타입 안전성을 약화시킬 수 있습니다.

다음과 같은 개선안을 고려해보세요:

export interface SlackAction {
-  type: 'button';
+  type: 'button' | 'select' | 'datepicker';
  text: string;
  name?: string;
  value?: string;
  url?: string;
  style?: 'default' | 'primary' | 'danger' | 'good';
  confirm?: {
    title: string;
    text: string;
    ok_text: string;
    dismiss_text: string;
  };
}
export interface SlackMessage {
  text: string;
  attachments?: SlackAttachment[];
  response_type?: 'in_channel' | 'ephemeral';
-  [key: string]: unknown;
+  channel?: string;
+  username?: string;
+  icon_emoji?: string;
+  icon_url?: string;
}
src/types/dto/responses/slackResponse.type.ts (1)

4-6: 응답 DTO 구조가 일관성 있게 잘 정의되었습니다.

BaseResponseDto 패턴을 활용한 구조가 깔끔합니다. SlackSuccessResponseDtoRecord<string, unknown> 타입은 유연성을 제공하지만, 가능하다면 더 구체적인 타입을 정의하는 것을 고려해보세요.

더 타입 안전한 접근을 위해 다음을 고려해보세요:

+export interface SlackSuccessData {
+  message?: string;
+  channelId?: string;
+  timestamp?: string;
+  [key: string]: unknown;
+}

-export class SlackSuccessResponseDto extends BaseResponseDto<Record<string, unknown>> {}
+export class SlackSuccessResponseDto extends BaseResponseDto<SlackSuccessData> {}
src/utils/slack.util.ts (1)

47-168: 복잡한 함수 리팩토링 권장

이 함수가 너무 길고 복잡합니다. 유지보수성을 위해 분리를 고려하세요.

상태별 액션 생성을 별도 함수로 분리하세요:

const actionGenerators: Record<string, (baseActionData: any) => SlackAction[]> = {
  unresolved: generateUnresolvedActions,
  resolved: generateResolvedActions,
  ignored: generateIgnoredActions,
};

export function generateIssueActions(issue: SentryIssue, issueUrl: string, hasSentryToken: boolean = false): SlackAction[] {
  // ... 기본 로직 ...
  
  const generator = actionGenerators[issueStatus];
  if (generator) {
    actions.push(...generator(baseActionData));
  }
  
  return actions;
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2009215 and d786e46.

📒 Files selected for processing (14)
  • src/controllers/slack.controller.ts (1 hunks)
  • src/controllers/webhook.controller.ts (1 hunks)
  • src/routes/index.ts (2 hunks)
  • src/routes/slack.router.ts (1 hunks)
  • src/routes/webhook.router.ts (1 hunks)
  • src/services/sentry.service.ts (1 hunks)
  • src/services/slack.service.ts (1 hunks)
  • src/types/dto/requests/slackRequest.type.ts (1 hunks)
  • src/types/dto/responses/slackResponse.type.ts (1 hunks)
  • src/types/index.ts (1 hunks)
  • src/types/models/Sentry.type.ts (1 hunks)
  • src/types/models/Slack.type.ts (1 hunks)
  • src/utils/sentry.util.ts (1 hunks)
  • src/utils/slack.util.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/controllers/slack.controller.ts

[error] 128-131: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 151-153: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 160-165: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build-and-test (21)
  • GitHub Check: build-and-test (20)
  • GitHub Check: build-and-test (22)
  • GitHub Check: build-and-test (23)
🔇 Additional comments (7)
src/routes/index.ts (1)

7-8: 새로운 라우터 통합이 기존 패턴과 일관성 있게 구현되었습니다.

WebhookRouter와 SlackRouter가 기존 라우터들과 동일한 패턴으로 깔끔하게 추가되었습니다.

Also applies to: 22-23

src/types/dto/requests/slackRequest.type.ts (1)

1-3: Slack interactive payload 구조가 올바르게 정의되었습니다.

Slack의 interactive 컴포넌트는 실제로 JSON 문자열을 payload 필드에 담아서 전송하므로 이 구조가 정확합니다.

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

15-55: Swagger 문서화가 매우 상세하고 잘 작성되었습니다.

API 엔드포인트에 대한 문서화가 포괄적이고 명확하여 API 사용자에게 도움이 될 것입니다.

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

40-66: 메서드 구조가 잘 설계됨

formatSentryDataForSlack 메서드가 다양한 Sentry 액션을 적절히 처리하고 있으며, 각 상황에 맞는 응답을 생성합니다.


68-108: 상태 변경 처리 로직이 견고함

기존 메시지 업데이트 실패 시 새 메시지로 폴백하는 로직이 잘 구현되어 있습니다. 에러 처리와 로깅도 적절합니다.

src/types/index.ts (1)

41-77: 타입 정의가 체계적으로 구성됨

Sentry와 Slack 관련 타입들이 기존 패턴을 따라 잘 구성되어 있으며, 필요한 모든 타입과 DTO가 적절히 export되고 있습니다.

src/types/models/Sentry.type.ts (1)

1-71: Sentry 타입 정의가 포괄적이고 잘 구조화됨

Sentry 도메인의 모든 주요 엔티티를 적절히 모델링했으며, 문자열 리터럴 타입과 인터페이스의 활용이 적절합니다. 옵셔널 프로퍼티 사용도 Sentry API의 실제 동작에 맞게 잘 설계되었습니다.

Comment on lines +8 to +13
// 서비스 인스턴스 생성
const sentryService = new SentryService();
const slackService = new SlackService();

// 컨트롤러 인스턴스 생성
const webhookController = new WebhookController(slackService, sentryService);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

의존성 주입 패턴 개선이 필요합니다.

현재 서비스와 컨트롤러 인스턴스를 직접 생성하는 방식은 다음과 같은 문제가 있습니다:

  • 테스트하기 어려움 (목 객체 주입 불가)
  • 높은 결합도
  • 싱글톤 패턴 부재로 인한 메모리 비효율성

다음과 같은 개선안을 고려해보세요:

-// 서비스 인스턴스 생성
-const sentryService = new SentryService();
-const slackService = new SlackService();

-// 컨트롤러 인스턴스 생성
-const webhookController = new WebhookController(slackService, sentryService);
+// 의존성 주입을 위한 팩토리 함수 또는 DI 컨테이너 사용
+import { Container } from '@/container'; // 예시
+const webhookController = Container.get(WebhookController);

또는 최소한 싱글톤 패턴을 적용하세요:

+// 싱글톤 인스턴스 사용
+import { SentryService } from '@/services/sentry.service';
+import { SlackService } from '@/services/slack.service';
+
+const sentryService = SentryService.getInstance();
+const slackService = SlackService.getInstance();

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/routes/webhook.router.ts around lines 8 to 13, the direct instantiation
of service and controller instances causes tight coupling and hinders testing.
Refactor the code to use dependency injection by passing service instances into
the controller from outside, ideally using a dependency injection container or
factory pattern. At minimum, implement the singleton pattern for the services to
ensure only one instance exists and improve memory efficiency.

* 500:
* description: 서버 오류
*/
router.post('/webhook/sentry', webhookController.handleSentryWebhook);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

웹훅 엔드포인트에 보안 및 검증 미들웨어 추가를 고려하세요.

Sentry 웹훅은 외부에서 들어오는 요청이므로 다음과 같은 보안 조치가 필요할 수 있습니다:

  • 요청 서명 검증
  • Rate limiting
  • 요청 본문 크기 제한
  • CORS 설정
+import { validateSentryWebhook } from '@/middleware/webhook.middleware';
+
-router.post('/webhook/sentry', webhookController.handleSentryWebhook);
+router.post('/webhook/sentry', 
+  validateSentryWebhook,
+  webhookController.handleSentryWebhook
+);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
router.post('/webhook/sentry', webhookController.handleSentryWebhook);
// add this import at the top of src/routes/webhook.router.ts
import { validateSentryWebhook } from '@/middleware/webhook.middleware';
router.post(
'/webhook/sentry',
validateSentryWebhook,
webhookController.handleSentryWebhook
);
🤖 Prompt for AI Agents
In src/routes/webhook.router.ts at line 56, the Sentry webhook endpoint lacks
security and validation middleware. Add middleware to verify request signatures
to ensure authenticity, implement rate limiting to prevent abuse, set request
body size limits to avoid large payload attacks, and configure CORS policies to
restrict allowed origins. Integrate these middleware functions before the
webhookController.handleSentryWebhook handler in the router.post call.

* 500:
* description: 서버 오류
*/
router.get('/slack/check-permissions', slackController.checkPermissions);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

컨트롤러 메서드의 컨텍스트 바인딩 필요

클래스 메서드를 콜백으로 전달할 때 this 컨텍스트가 손실될 수 있습니다.

메서드를 바인딩하거나 화살표 함수로 래핑하세요:

-router.get('/slack/check-permissions', slackController.checkPermissions);
+router.get('/slack/check-permissions', slackController.checkPermissions.bind(slackController));

-router.post('/slack/test-bot', slackController.testBot);
+router.post('/slack/test-bot', slackController.testBot.bind(slackController));

-router.post('/slack/interactive', slackController.handleInteractive);
+router.post('/slack/interactive', slackController.handleInteractive.bind(slackController));

또는 컨트롤러 생성자에서 메서드를 바인딩하세요.

Also applies to: 52-52, 88-88

🤖 Prompt for AI Agents
In src/routes/slack.router.ts at lines 31, 52, and 88, the controller methods
are passed as callbacks without binding, causing loss of the `this` context. Fix
this by either binding the methods to the controller instance using
`.bind(this)` when passing them as callbacks or by wrapping the calls in arrow
functions to preserve context. Alternatively, bind the methods in the
controller's constructor to ensure `this` is correctly set.

Comment on lines 8 to 10
const slackService = new SlackService();
const sentryService = new SentryService();
const slackController = new SlackController(slackService, sentryService);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

의존성 주입 패턴으로 리팩토링 필요

서비스들이 라우터 파일에서 직접 인스턴스화되고 있습니다. 이는 테스트 가능성을 저해하고 여러 인스턴스가 생성될 수 있는 문제가 있습니다.

서비스 인스턴스를 외부에서 주입받도록 수정하세요:

-const slackService = new SlackService();
-const sentryService = new SentryService();
-const slackController = new SlackController(slackService, sentryService);
+export const createSlackRouter = (slackService: SlackService, sentryService: SentryService): Router => {
+  const router: Router = express.Router();
+  const slackController = new SlackController(slackService, sentryService);

그리고 파일 끝부분을 다음과 같이 수정하세요:

-export default router;
+  // 라우트 정의들...
+  
+  return router;
+};
🤖 Prompt for AI Agents
In src/routes/slack.router.ts around lines 8 to 10, the SlackService and
SentryService are instantiated directly inside the router file, which reduces
testability and can cause multiple instances. Refactor the code to accept these
service instances via dependency injection by modifying the router to receive
them as parameters or through constructor injection. Remove direct instantiation
from this file and ensure the services are passed in from an external module or
the application entry point.

Comment on lines +30 to +36
switch (action) {
case 'resolve':
return { method: 'PUT', data: { status: 'resolved' } };
case 'unresolve':
return { method: 'PUT', data: { status: 'unresolved' } };
case 'archive':
return { method: 'PUT', data: { status: 'ignored' } };
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

'archive' 액션의 상태 매핑 불일치

getNewStatusFromAction 함수에서는 'archive'가 'archived'로 매핑되지만, 여기서는 'ignored'로 매핑됩니다. 이는 혼란을 야기할 수 있습니다.

일관성을 위해 매핑을 통일하거나, 두 함수의 용도를 명확히 구분해야 합니다. Sentry API 문서를 확인하여 올바른 매핑을 사용하세요.

🤖 Prompt for AI Agents
In src/utils/sentry.util.ts between lines 30 and 36, the 'archive' action is
mapped to 'ignored' status, which conflicts with the 'archived' status mapping
in the getNewStatusFromAction function. To fix this, verify the correct status
for the 'archive' action according to the Sentry API documentation and update
this switch case to use the same status as getNewStatusFromAction, ensuring
consistent status mapping across both functions.

const statusText = statusMapping[newStatus as keyof typeof statusMapping] || newStatus.toUpperCase();
updatedMessage.attachments[0].footer = `✅ ${statusText} | 처리 완료: ${new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' })}`;

delete updatedMessage.attachments[0].actions;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

delete 연산자 사용 지양

delete 연산자는 성능에 부정적 영향을 미칩니다. undefined 할당을 사용해주세요.

-     delete updatedMessage.attachments[0].actions;
+     updatedMessage.attachments[0].actions = undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
delete updatedMessage.attachments[0].actions;
updatedMessage.attachments[0].actions = undefined;
🤖 Prompt for AI Agents
In src/controllers/slack.controller.ts at line 144, replace the use of the
delete operator on updatedMessage.attachments[0].actions with assigning
undefined to updatedMessage.attachments[0].actions to avoid performance issues
caused by delete.

}

private createErrorMessage(error: string, originalMessage: unknown): unknown {
const updatedMessage = JSON.parse(JSON.stringify(originalMessage));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

깊은 복사 방식 개선 (에러 메시지 생성)

여기서도 동일한 비효율적인 복사 방식이 사용되고 있습니다.

-   const updatedMessage = JSON.parse(JSON.stringify(originalMessage));
+   const updatedMessage = structuredClone(originalMessage);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const updatedMessage = JSON.parse(JSON.stringify(originalMessage));
const updatedMessage = structuredClone(originalMessage);
🧰 Tools
🪛 Biome (1.9.4)

[error] 151-153: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🤖 Prompt for AI Agents
In src/controllers/slack.controller.ts at line 151, the code uses
JSON.parse(JSON.stringify(originalMessage)) for deep copying, which is
inefficient and can cause issues with certain data types. Replace this with a
more efficient and reliable deep copy method, such as using a utility function
like lodash's cloneDeep or a custom deep copy function that handles complex
objects properly.

Comment on lines 123 to 132
if (updatedMessage.attachments && updatedMessage.attachments[0]) {
const newStatus = getNewStatusFromAction(action);
const statusColors = {
'resolved': 'good',
'ignored': 'warning',
'archived': '#808080',
'unresolved': 'danger',
};

updatedMessage.attachments[0].color = statusColors[newStatus as keyof typeof statusColors] || 'good';
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

옵셔널 체이닝 사용 권장

정적 분석 도구에서 제안한 대로 옵셔널 체이닝을 사용하여 코드를 더 안전하게 만들어주세요.

-   if (updatedMessage.attachments && updatedMessage.attachments[0]) {
+   if (updatedMessage.attachments?.[0]) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (updatedMessage.attachments && updatedMessage.attachments[0]) {
const newStatus = getNewStatusFromAction(action);
const statusColors = {
'resolved': 'good',
'ignored': 'warning',
'archived': '#808080',
'unresolved': 'danger',
};
updatedMessage.attachments[0].color = statusColors[newStatus as keyof typeof statusColors] || 'good';
if (updatedMessage.attachments?.[0]) {
const newStatus = getNewStatusFromAction(action);
const statusColors = {
'resolved': 'good',
'ignored': 'warning',
'archived': '#808080',
'unresolved': 'danger',
};
updatedMessage.attachments[0].color = statusColors[newStatus as keyof typeof statusColors] || 'good';
🧰 Tools
🪛 Biome (1.9.4)

[error] 128-131: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🤖 Prompt for AI Agents
In src/controllers/slack.controller.ts around lines 123 to 132, the code
accesses updatedMessage.attachments[0] without optional chaining, which can
cause runtime errors if attachments or the first element is undefined. Update
the code to use optional chaining when accessing attachments and its first
element to safely handle cases where these might be undefined, improving code
safety and preventing potential crashes.

next: NextFunction,
): Promise<void> => {
try {
const payload = JSON.parse(req.body.payload);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

JSON 파싱에 에러 처리 추가 필요

JSON.parse에 대한 에러 처리가 없어 잘못된 페이로드로 인해 서버가 크래시될 수 있습니다.

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

-     const payload = JSON.parse(req.body.payload);
+     let payload;
+     try {
+       payload = JSON.parse(req.body.payload);
+     } catch (error) {
+       logger.error('Invalid JSON payload:', error);
+       res.json({ text: '❌ 잘못된 요청 형식입니다.' });
+       return;
+     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const payload = JSON.parse(req.body.payload);
let payload;
try {
payload = JSON.parse(req.body.payload);
} catch (error) {
logger.error('Invalid JSON payload:', error);
res.json({ text: '❌ 잘못된 요청 형식입니다.' });
return;
}
🤖 Prompt for AI Agents
In src/controllers/slack.controller.ts at line 79, the JSON.parse call on
req.body.payload lacks error handling, which can cause the server to crash if
the payload is malformed. Wrap the JSON.parse call in a try-catch block to catch
parsing errors and handle them gracefully, such as by returning an error
response or logging the issue without crashing the server.

private createSuccessMessage(actionData: SentryActionData, originalMessage: unknown): unknown {
const { action } = actionData;

const updatedMessage = JSON.parse(JSON.stringify(originalMessage));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

비효율적인 깊은 복사 방식 개선

JSON.parse(JSON.stringify())는 성능에 부정적 영향을 미치고 순환 참조나 함수 등을 처리할 수 없습니다.

구조화된 복제(structured clone) 또는 라이브러리 사용을 권장합니다:

-   const updatedMessage = JSON.parse(JSON.stringify(originalMessage));
+   const updatedMessage = structuredClone(originalMessage);

또는 lodash를 사용:

+   import { cloneDeep } from 'lodash';
-   const updatedMessage = JSON.parse(JSON.stringify(originalMessage));
+   const updatedMessage = cloneDeep(originalMessage);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const updatedMessage = JSON.parse(JSON.stringify(originalMessage));
const updatedMessage = structuredClone(originalMessage);
🤖 Prompt for AI Agents
In src/controllers/slack.controller.ts at line 121, replace the inefficient deep
copy using JSON.parse(JSON.stringify()) with a more robust method such as
structuredClone if available, or use a utility function from a library like
lodash's cloneDeep to handle deep copying safely and efficiently, especially to
support circular references and functions.

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

♻️ Duplicate comments (3)
src/routes/webhook.router.ts (2)

8-13: 의존성 주입 패턴 개선 필요

현재 구현은 서비스 인스턴스를 직접 생성하여 테스트하기 어렵고 결합도가 높습니다. 의존성 주입 컨테이너나 팩토리 패턴 사용을 고려해보세요.


56-56: 웹훅 엔드포인트 보안 강화 필요

Sentry 웹훅은 외부 요청이므로 서명 검증, Rate limiting, 요청 크기 제한 등의 보안 미들웨어 추가가 필요합니다.

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

22-22: 웹훅 페이로드 입력 검증 추가 필요

req.body를 직접 사용하는 것은 보안상 위험합니다. Sentry 웹훅의 유효성 검증 로직을 추가해주세요.

🧹 Nitpick comments (3)
src/controllers/webhook.controller.ts (3)

160-160: 깊은 복사 방식 개선 권장

JSON.parse(JSON.stringify())는 성능상 비효율적이고 함수, undefined 등을 제대로 처리하지 못합니다.

-const updatedMessage = JSON.parse(JSON.stringify(originalMessage));
+import { cloneDeep } from 'lodash';
+const updatedMessage = cloneDeep(originalMessage);

또는 구조 분해를 활용한 얕은 복사를 고려해보세요:

-const updatedMessage = JSON.parse(JSON.stringify(originalMessage));
+const updatedMessage = { ...originalMessage };

183-183: Delete 연산자 대신 undefined 할당 권장

성능상 이유로 delete 연산자 대신 undefined 할당을 사용하는 것이 좋습니다.

-delete updatedMessage.attachments[0].actions;
+updatedMessage.attachments[0].actions = undefined;

87-155: 컨트롤러 메소드 분리 권장

formatSentryDataForSlackhandleIssueStatusChange 메소드가 복잡하고 여러 책임을 가지고 있습니다. 별도 서비스나 유틸리티 클래스로 분리하는 것을 고려해보세요.

// 예시: SentryMessageFormatter 클래스 생성
export class SentryMessageFormatter {
  async formatForSlack(sentryData: SentryWebhookData, hasSentryToken: boolean): Promise<SlackMessage | null> {
    // 현재 formatSentryDataForSlack 로직
  }
  
  async handleStatusChange(sentryData: SentryWebhookData, slackService: SlackService, hasSentryToken: boolean): Promise<SlackMessage | null> {
    // 현재 handleIssueStatusChange 로직
  }
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d786e46 and 9d11a69.

📒 Files selected for processing (5)
  • src/configs/db.config.ts (1 hunks)
  • src/controllers/webhook.controller.ts (1 hunks)
  • src/routes/index.ts (2 hunks)
  • src/routes/webhook.router.ts (1 hunks)
  • src/types/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/routes/index.ts
  • src/types/index.ts
🧰 Additional context used
🪛 Biome (1.9.4)
src/controllers/webhook.controller.ts

[error] 168-171: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 192-193: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 202-204: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build-and-test (20)
  • GitHub Check: build-and-test (22)
  • GitHub Check: build-and-test (21)
  • GitHub Check: build-and-test (23)
🔇 Additional comments (2)
src/configs/db.config.ts (1)

19-29: 프로덕션 환경에서 SSL 비활성화 검증 필요

현재 변경사항으로 인해 프로덕션 환경에서도 SSL이 완전히 비활성화됩니다. 이는 데이터베이스 연결 시 보안 위험을 초래할 수 있습니다.

프로덕션 환경에서 SSL 없이 데이터베이스에 연결하는 것이 의도된 변경인지 확인해 주세요. 일반적으로 프로덕션 환경에서는 SSL 연결이 권장됩니다.

다음 스크립트로 현재 데이터베이스 연결 설정을 확인해 보세요:

#!/bin/bash
# 데이터베이스 SSL 설정 관련 환경변수 및 설정 확인
echo "현재 NODE_ENV: $NODE_ENV"
echo "데이터베이스 호스트: $POSTGRES_HOST"

# 프로덕션 환경에서 SSL 없이 연결하는 것이 안전한지 확인
if [ "$NODE_ENV" = "production" ]; then
  echo "⚠️  프로덕션 환경에서 SSL이 비활성화되어 있습니다."
  echo "데이터베이스가 프라이빗 네트워크에 있거나 다른 보안 조치가 있는지 확인하세요."
fi
src/controllers/webhook.controller.ts (1)

190-190: 깊은 복사 최적화 (중복 이슈)

createErrorMessage에서도 동일한 깊은 복사 이슈가 있습니다. 위의 createSuccessMessage와 동일하게 수정해주세요.
[suggest_optional_refacter]

* 400:
* description: 잘못된 요청
*/
router.post('/webhook/slack/interactive', webhookController.handleSlackInteractive);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Slack 인터랙티브 엔드포인트에도 보안 미들웨어 적용 필요

Slack 인터랙티브 엔드포인트도 외부 요청을 처리하므로 Sentry 웹훅과 동일한 보안 조치가 필요합니다.

+import { validateSlackWebhook } from '@/middleware/webhook.middleware';
+
-router.post('/webhook/slack/interactive', webhookController.handleSlackInteractive);
+router.post('/webhook/slack/interactive', 
+  validateSlackWebhook,
+  webhookController.handleSlackInteractive
+);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
router.post('/webhook/slack/interactive', webhookController.handleSlackInteractive);
// add this import alongside your other middleware imports
import { validateSlackWebhook } from '@/middleware/webhook.middleware';
// replace the single‐line handler registration with the secured version
-router.post('/webhook/slack/interactive', webhookController.handleSlackInteractive);
+router.post('/webhook/slack/interactive',
+ validateSlackWebhook,
+ webhookController.handleSlackInteractive
+);
🤖 Prompt for AI Agents
In src/routes/webhook.router.ts at line 92, the Slack interactive endpoint lacks
security middleware. Apply the same security middleware used for the Sentry
webhook endpoint to the Slack interactive route to ensure external requests are
properly validated and secured.

Comment on lines +1 to +94
import express, { Router } from 'express';
import { WebhookController } from '@/controllers/webhook.controller';
import { SentryService } from '@/services/sentry.service';
import { SlackService } from '@/services/slack.service';

const router: Router = express.Router();

// 서비스 인스턴스 생성
const sentryService = new SentryService();
const slackService = new SlackService();

// 컨트롤러 인스턴스 생성
const webhookController = new WebhookController(slackService, sentryService);

/**
* @swagger
* /webhook/sentry:
* post:
* summary: Sentry webhook 처리
* description: Sentry에서 전송되는 webhook 이벤트를 처리합니다.
* tags: [Webhook]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* action:
* type: string
* description: Sentry 액션 타입
* enum: [created, resolved, unresolved, ignored]
* data:
* type: object
* properties:
* issue:
* type: object
* description: Sentry 이슈 정보
* actor:
* type: object
* description: 액션을 수행한 사용자 정보
* responses:
* 200:
* description: Webhook 처리 성공
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: "Webhook 처리 완료"
* 500:
* description: 서버 오류
*/
router.post('/webhook/sentry', webhookController.handleSentryWebhook);

/**
* @swagger
* /webhook/slack/interactive:
* post:
* summary: Slack Interactive Components 처리
* description: Slack에서 전송되는 버튼 클릭 등의 상호작용을 처리합니다.
* tags: [Webhook]
* requestBody:
* required: true
* content:
* application/x-www-form-urlencoded:
* schema:
* type: object
* properties:
* payload:
* type: string
* description: JSON 형태의 Slack payload (URL encoded)
* responses:
* 200:
* description: 상호작용 처리 성공
* content:
* application/json:
* schema:
* type: object
* properties:
* text:
* type: string
* example: "버튼 클릭 처리 완료"
* response_type:
* type: string
* enum: [in_channel, ephemeral]
* 400:
* description: 잘못된 요청
*/
router.post('/webhook/slack/interactive', webhookController.handleSlackInteractive);

export default router;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 핸들링 미들웨어 추가 권장

현재 라우터에 전역 에러 핸들링이 없어 컨트롤러에서 발생한 에러가 적절히 처리되지 않을 수 있습니다.

+import { errorHandler } from '@/middleware/error.middleware';
+
// 라우터 정의 후
+router.use(errorHandler);
+
export default router;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import express, { Router } from 'express';
import { WebhookController } from '@/controllers/webhook.controller';
import { SentryService } from '@/services/sentry.service';
import { SlackService } from '@/services/slack.service';
const router: Router = express.Router();
// 서비스 인스턴스 생성
const sentryService = new SentryService();
const slackService = new SlackService();
// 컨트롤러 인스턴스 생성
const webhookController = new WebhookController(slackService, sentryService);
/**
* @swagger
* /webhook/sentry:
* post:
* summary: Sentry webhook 처리
* description: Sentry에서 전송되는 webhook 이벤트를 처리합니다.
* tags: [Webhook]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* action:
* type: string
* description: Sentry 액션 타입
* enum: [created, resolved, unresolved, ignored]
* data:
* type: object
* properties:
* issue:
* type: object
* description: Sentry 이슈 정보
* actor:
* type: object
* description: 액션을 수행한 사용자 정보
* responses:
* 200:
* description: Webhook 처리 성공
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: "Webhook 처리 완료"
* 500:
* description: 서버 오류
*/
router.post('/webhook/sentry', webhookController.handleSentryWebhook);
/**
* @swagger
* /webhook/slack/interactive:
* post:
* summary: Slack Interactive Components 처리
* description: Slack에서 전송되는 버튼 클릭 등의 상호작용을 처리합니다.
* tags: [Webhook]
* requestBody:
* required: true
* content:
* application/x-www-form-urlencoded:
* schema:
* type: object
* properties:
* payload:
* type: string
* description: JSON 형태의 Slack payload (URL encoded)
* responses:
* 200:
* description: 상호작용 처리 성공
* content:
* application/json:
* schema:
* type: object
* properties:
* text:
* type: string
* example: "버튼 클릭 처리 완료"
* response_type:
* type: string
* enum: [in_channel, ephemeral]
* 400:
* description: 잘못된 요청
*/
router.post('/webhook/slack/interactive', webhookController.handleSlackInteractive);
export default router;
import express, { Router } from 'express';
import { WebhookController } from '@/controllers/webhook.controller';
import { SentryService } from '@/services/sentry.service';
import { SlackService } from '@/services/slack.service';
import { errorHandler } from '@/middleware/error.middleware';
const router: Router = express.Router();
// 서비스 인스턴스 생성
const sentryService = new SentryService();
const slackService = new SlackService();
// 컨트롤러 인스턴스 생성
const webhookController = new WebhookController(slackService, sentryService);
/**
* @swagger
* /webhook/sentry:
* post:
* summary: Sentry webhook 처리
* description: Sentry에서 전송되는 webhook 이벤트를 처리합니다.
* tags: [Webhook]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* action:
* type: string
* description: Sentry 액션 타입
* enum: [created, resolved, unresolved, ignored]
* data:
* type: object
* properties:
* issue:
* type: object
* description: Sentry 이슈 정보
* actor:
* type: object
* description: 액션을 수행한 사용자 정보
* responses:
* 200:
* description: Webhook 처리 성공
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: "Webhook 처리 완료"
* 500:
* description: 서버 오류
*/
router.post('/webhook/sentry', webhookController.handleSentryWebhook);
/**
* @swagger
* /webhook/slack/interactive:
* post:
* summary: Slack Interactive Components 처리
* description: Slack에서 전송되는 버튼 클릭 등의 상호작용을 처리합니다.
* tags: [Webhook]
* requestBody:
* required: true
* content:
* application/x-www-form-urlencoded:
* schema:
* type: object
* properties:
* payload:
* type: string
* description: JSON 형태의 Slack payload (URL encoded)
* responses:
* 200:
* description: 상호작용 처리 성공
* content:
* application/json:
* schema:
* type: object
* properties:
* text:
* type: string
* example: "버튼 클릭 처리 완료"
* response_type:
* type: string
* enum: [in_channel, ephemeral]
* 400:
* description: 잘못된 요청
*/
router.post('/webhook/slack/interactive', webhookController.handleSlackInteractive);
// 라우터 정의 후
router.use(errorHandler);
export default router;
🤖 Prompt for AI Agents
In src/routes/webhook.router.ts around lines 1 to 94, there is no global error
handling middleware to catch and process errors thrown by the webhookController
methods. To fix this, add an error-handling middleware at the end of the router
that captures any errors, logs them if necessary, and sends an appropriate HTTP
response with error details. This ensures that errors from controller methods
are properly handled and do not cause unhandled exceptions.

const updatedMessage = JSON.parse(JSON.stringify(originalMessage));

if (updatedMessage.attachments && updatedMessage.attachments[0]) {
updatedMessage.attachments[0].fields.push({
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optional chaining으로 안전성 향상

배열 접근 시 optional chaining을 사용하여 런타임 에러를 방지할 수 있습니다.

-updatedMessage.attachments[0].fields.push({
+updatedMessage.attachments?.[0]?.fields?.push({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
updatedMessage.attachments[0].fields.push({
updatedMessage.attachments?.[0]?.fields?.push({
🧰 Tools
🪛 Biome (1.9.4)

[error] 192-193: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🤖 Prompt for AI Agents
In src/controllers/webhook.controller.ts at line 193, the code accesses
updatedMessage.attachments[0].fields directly, which may cause runtime errors if
attachments or fields are undefined. Use optional chaining when accessing
attachments, the first element, and fields to safely handle cases where these
properties might be missing, preventing potential runtime exceptions.

'unresolved': 'danger',
};

updatedMessage.attachments[0].color = statusColors[newStatus as keyof typeof statusColors] || 'good';
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optional chaining으로 안전성 향상

Static analysis 도구가 제안한 대로 optional chaining을 사용하여 더 안전한 코드로 개선할 수 있습니다.

-updatedMessage.attachments[0].color = statusColors[newStatus as keyof typeof statusColors] || 'good';
+updatedMessage.attachments?.[0] && (updatedMessage.attachments[0].color = statusColors[newStatus as keyof typeof statusColors] || 'good');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
updatedMessage.attachments[0].color = statusColors[newStatus as keyof typeof statusColors] || 'good';
// 기존 attachments 배열이 있을 때에만 색상을 설정하도록 변경
- updatedMessage.attachments[0].color = statusColors[newStatus as keyof typeof statusColors] || 'good';
+ updatedMessage.attachments?.[0] && (updatedMessage.attachments[0].color = statusColors[newStatus as keyof typeof statusColors] || 'good');
🧰 Tools
🪛 Biome (1.9.4)

[error] 168-171: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🤖 Prompt for AI Agents
In src/controllers/webhook.controller.ts at line 171, improve code safety by
using optional chaining when accessing updatedMessage.attachments[0].color.
Modify the code to safely access attachments and its first element before
setting the color, preventing runtime errors if attachments or the first element
is undefined.

next: NextFunction,
): Promise<void> => {
try {
const payload = JSON.parse(req.body.payload);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

JSON 파싱 에러 처리 개선 필요

JSON.parse(req.body.payload)에서 잘못된 JSON으로 인한 에러가 발생할 수 있습니다.

-const payload = JSON.parse(req.body.payload);
+let payload;
+try {
+  payload = JSON.parse(req.body.payload);
+} catch (parseError) {
+  logger.error('Slack payload 파싱 실패:', parseError);
+  res.status(400).json({ text: '잘못된 페이로드 형식입니다.' });
+  return;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const payload = JSON.parse(req.body.payload);
let payload;
try {
payload = JSON.parse(req.body.payload);
} catch (parseError) {
logger.error('Slack payload 파싱 실패:', parseError);
res.status(400).json({ text: '잘못된 페이로드 형식입니다.' });
return;
}
🤖 Prompt for AI Agents
In src/controllers/webhook.controller.ts at line 48, the JSON.parse call on
req.body.payload can throw an error if the payload is not valid JSON. Wrap the
JSON.parse call in a try-catch block to handle parsing errors gracefully, and
respond with an appropriate error message or status code if parsing fails.

@six-standard
Copy link
Member Author

아직 문제가 많은 PR로 보여서, 우선은 닫아두고 개선 후 다시 PR 업로드하겠습니다!
알림 받으신 분들께 죄송하다는 말씀 드립니다..

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.

1 participant