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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/configs/db.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ const poolConfig: pg.PoolConfig = {
max: 10, // 최대 연결 수
idleTimeoutMillis: 30000, // 연결 유휴 시간 (30초)
connectionTimeoutMillis: 5000, // 연결 시간 초과 (5초)
ssl: false,
// ssl: {
// rejectUnauthorized: false,
// },
};

if (process.env.NODE_ENV === 'production') {
poolConfig.ssl = {
rejectUnauthorized: false,
};
}
// if (process.env.NODE_ENV === 'production') {
// poolConfig.ssl = {
// rejectUnauthorized: false,
// };
// }

const pool = new Pool(poolConfig);

Expand Down
204 changes: 204 additions & 0 deletions src/controllers/webhook.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { NextFunction, Request, RequestHandler, Response } from 'express';
import { SlackService } from '@/services/slack.service';
import { SentryService } from '@/services/sentry.service';
import { SentryWebhookData, SlackMessage } from '@/types';
import { SentryActionData, SentryApiAction } from '@/types/models/Sentry.type';
import logger from '@/configs/logger.config';
import { formatSentryIssueForSlack, createStatusUpdateMessage } from '@/utils/slack.util';
import { getNewStatusFromAction } from '@/utils/sentry.util';

export class WebhookController {
constructor(
private slackService: SlackService,
private sentryService: SentryService,
) {}

handleSentryWebhook: RequestHandler = async (
req: Request,
res: Response,
next: NextFunction,
): Promise<void> => {
try {
const sentryData = req.body;
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

웹훅 페이로드에 대한 입력 검증 추가 필요

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

다음과 같은 개선사항을 제안합니다:

  handleSentryWebhook: RequestHandler = async (
    req: Request,
    res: Response,
    next: NextFunction,
  ): Promise<void> => {
    try {
-     const sentryData = req.body;
+     const sentryData = req.body as SentryWebhookData;
+     
+     // 웹훅 페이로드 기본 검증
+     if (!sentryData || !sentryData.action || !sentryData.data) {
+       logger.warn('유효하지 않은 Sentry 웹훅 페이로드:', sentryData);
+       res.status(400).json({ message: 'Invalid webhook payload' });
+       return;
+     }
      
      const slackMessage = await this.formatSentryDataForSlack(sentryData);
🤖 Prompt for AI Agents
In src/controllers/webhook.controller.ts at line 20, the code directly assigns
req.body to sentryData without validating the webhook payload, which poses a
security risk. Add input validation logic to verify that req.body contains the
expected structure and fields of a valid Sentry webhook payload before using it.
This can include checking required properties, data types, and possibly
verifying a signature or token if applicable.


const slackMessage = await this.formatSentryDataForSlack(sentryData);

if (slackMessage === null) {
logger.info('기존 메시지 업데이트 완료, 새 메시지 전송 생략');
res.status(200).json({ message: 'Webhook processed successfully' });
return;
}

const issueId = sentryData.data?.issue?.id;
await this.slackService.sendMessage(slackMessage, issueId);

res.status(200).json({ message: 'Webhook processed successfully' });
} catch (error) {
logger.error('Sentry webhook 처리 실패:', error instanceof Error ? error.message : '알 수 없는 오류');
next(error);
}
};

handleSlackInteractive: RequestHandler = async (
req: Request,
res: Response,
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.


if (payload.type === 'interactive_message' && payload.actions && payload.actions[0]) {
const action = payload.actions[0];

if (action.name === 'sentry_action') {
const [actionType, issueId, organizationSlug, projectSlug] = action.value.split(':');

const actionData: SentryActionData = {
action: actionType as SentryApiAction,
issueId,
organizationSlug,
projectSlug,
};

if (actionData.issueId && actionData.organizationSlug && actionData.projectSlug) {
logger.info('Processing Sentry action:', actionData);

const result = await this.sentryService.handleIssueAction(actionData);

if (result.success) {
const updatedMessage = this.createSuccessMessage(actionData, payload.original_message || {});
res.json(updatedMessage);
} else {
const errorMessage = this.createErrorMessage(result.error || 'Unknown error', payload.original_message || {});
res.json(errorMessage);
}
return;
}
}
}

res.json({ text: '❌ 잘못된 요청입니다.' });
} catch (error) {
logger.error('Slack Interactive 처리 실패:', error instanceof Error ? error.message : '알 수 없는 오류');
next(error);
}
};

private async formatSentryDataForSlack(sentryData: SentryWebhookData): Promise<SlackMessage | null> {
const { action, data } = sentryData;

if (action === 'resolved' || action === 'unresolved' || action === 'ignored') {
return await this.handleIssueStatusChange(sentryData);
}

if (action === 'created' && data.issue) {
return formatSentryIssueForSlack(sentryData, this.sentryService.hasSentryToken());
}

return {
text: `🔔 Sentry 이벤트: ${action || 'Unknown action'}`,
attachments: [
{
color: 'warning',
fields: [
{
title: '이벤트 타입',
value: action || 'Unknown',
short: true,
},
],
},
],
};
}

private async handleIssueStatusChange(sentryData: SentryWebhookData): Promise<SlackMessage | null> {
const { data } = sentryData;
const issue = data.issue;

if (!issue) {
logger.warn('이슈 정보가 없습니다:', sentryData);
return formatSentryIssueForSlack(sentryData, this.sentryService.hasSentryToken());
}

logger.info(`이슈 상태 변경 감지: ${issue.id} → ${sentryData.action}`);

const messageInfo = this.slackService.getMessageInfo(issue.id);

if (messageInfo) {
logger.info('기존 메시지 발견, 업데이트 시도');

try {
const updatedMessage = createStatusUpdateMessage(
sentryData,
this.sentryService.hasSentryToken()
);

await this.slackService.updateMessage(
messageInfo.channel,
messageInfo.ts,
updatedMessage
);

logger.info('기존 메시지 업데이트 완료');
return null;

} catch (error) {
logger.error('메시지 업데이트 실패, 새 메시지로 전송:', error instanceof Error ? error.message : '알 수 없는 오류');

}
} else {
logger.info('기존 메시지 없음, 새 메시지 생성');
}

return formatSentryIssueForSlack(sentryData, this.sentryService.hasSentryToken());
}

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

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

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

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.


const statusMapping = {
'resolved': 'RESOLVED',
'ignored': 'IGNORED',
'archived': 'ARCHIVED',
'unresolved': 'UNRESOLVED',
};

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;
}

return updatedMessage;
}

private createErrorMessage(error: string, originalMessage: unknown): unknown {
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.

title: '❌ 오류 발생',
value: error,
short: false,
});

updatedMessage.attachments[0].color = 'danger';
}

return updatedMessage;
}
}
3 changes: 3 additions & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import PostRouter from './post.router';
import NotiRouter from './noti.router';
import LeaderboardRouter from './leaderboard.router';
import TotalStatsRouter from './totalStats.router';
import WebhookRouter from './webhook.router';


const router: Router = express.Router();

Expand All @@ -16,5 +18,6 @@ router.use('/', PostRouter);
router.use('/', NotiRouter);
router.use('/', LeaderboardRouter);
router.use('/', TotalStatsRouter);
router.use('/', WebhookRouter);

export default router;
94 changes: 94 additions & 0 deletions src/routes/webhook.router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,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);
Comment on lines +8 to +13
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.


/**
* @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);
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.


/**
* @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);
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.


export default router;
Comment on lines +1 to +94
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.

Loading
Loading