@@ -2,8 +2,10 @@ import { NextFunction, Request, RequestHandler, Response } from 'express';
2
2
import { SlackService } from '@/services/slack.service' ;
3
3
import { SentryService } from '@/services/sentry.service' ;
4
4
import { SentryWebhookData , SlackMessage } from '@/types' ;
5
+ import { SentryActionData , SentryApiAction } from '@/types/models/Sentry.type' ;
5
6
import logger from '@/configs/logger.config' ;
6
7
import { formatSentryIssueForSlack , createStatusUpdateMessage } from '@/utils/slack.util' ;
8
+ import { getNewStatusFromAction } from '@/utils/sentry.util' ;
7
9
8
10
export class WebhookController {
9
11
constructor (
@@ -37,6 +39,51 @@ export class WebhookController {
37
39
}
38
40
} ;
39
41
42
+ handleSlackInteractive : RequestHandler = async (
43
+ req : Request ,
44
+ res : Response ,
45
+ next : NextFunction ,
46
+ ) : Promise < void > => {
47
+ try {
48
+ const payload = JSON . parse ( req . body . payload ) ;
49
+
50
+ if ( payload . type === 'interactive_message' && payload . actions && payload . actions [ 0 ] ) {
51
+ const action = payload . actions [ 0 ] ;
52
+
53
+ if ( action . name === 'sentry_action' ) {
54
+ const [ actionType , issueId , organizationSlug , projectSlug ] = action . value . split ( ':' ) ;
55
+
56
+ const actionData : SentryActionData = {
57
+ action : actionType as SentryApiAction ,
58
+ issueId,
59
+ organizationSlug,
60
+ projectSlug,
61
+ } ;
62
+
63
+ if ( actionData . issueId && actionData . organizationSlug && actionData . projectSlug ) {
64
+ logger . info ( 'Processing Sentry action:' , actionData ) ;
65
+
66
+ const result = await this . sentryService . handleIssueAction ( actionData ) ;
67
+
68
+ if ( result . success ) {
69
+ const updatedMessage = this . createSuccessMessage ( actionData , payload . original_message || { } ) ;
70
+ res . json ( updatedMessage ) ;
71
+ } else {
72
+ const errorMessage = this . createErrorMessage ( result . error || 'Unknown error' , payload . original_message || { } ) ;
73
+ res . json ( errorMessage ) ;
74
+ }
75
+ return ;
76
+ }
77
+ }
78
+ }
79
+
80
+ res . json ( { text : '❌ 잘못된 요청입니다.' } ) ;
81
+ } catch ( error ) {
82
+ logger . error ( 'Slack Interactive 처리 실패:' , error instanceof Error ? error . message : '알 수 없는 오류' ) ;
83
+ next ( error ) ;
84
+ }
85
+ } ;
86
+
40
87
private async formatSentryDataForSlack ( sentryData : SentryWebhookData ) : Promise < SlackMessage | null > {
41
88
const { action, data } = sentryData ;
42
89
@@ -106,4 +153,52 @@ export class WebhookController {
106
153
107
154
return formatSentryIssueForSlack ( sentryData , this . sentryService . hasSentryToken ( ) ) ;
108
155
}
156
+
157
+ private createSuccessMessage ( actionData : SentryActionData , originalMessage : unknown ) : unknown {
158
+ const { action } = actionData ;
159
+
160
+ const updatedMessage = JSON . parse ( JSON . stringify ( originalMessage ) ) ;
161
+
162
+ if ( updatedMessage . attachments && updatedMessage . attachments [ 0 ] ) {
163
+ const newStatus = getNewStatusFromAction ( action ) ;
164
+ const statusColors = {
165
+ 'resolved' : 'good' ,
166
+ 'ignored' : 'warning' ,
167
+ 'archived' : '#808080' ,
168
+ 'unresolved' : 'danger' ,
169
+ } ;
170
+
171
+ updatedMessage . attachments [ 0 ] . color = statusColors [ newStatus as keyof typeof statusColors ] || 'good' ;
172
+
173
+ const statusMapping = {
174
+ 'resolved' : 'RESOLVED' ,
175
+ 'ignored' : 'IGNORED' ,
176
+ 'archived' : 'ARCHIVED' ,
177
+ 'unresolved' : 'UNRESOLVED' ,
178
+ } ;
179
+
180
+ const statusText = statusMapping [ newStatus as keyof typeof statusMapping ] || newStatus . toUpperCase ( ) ;
181
+ updatedMessage . attachments [ 0 ] . footer = `✅ ${ statusText } | 처리 완료: ${ new Date ( ) . toLocaleString ( 'ko-KR' , { timeZone : 'Asia/Seoul' } ) } ` ;
182
+
183
+ delete updatedMessage . attachments [ 0 ] . actions ;
184
+ }
185
+
186
+ return updatedMessage ;
187
+ }
188
+
189
+ private createErrorMessage ( error : string , originalMessage : unknown ) : unknown {
190
+ const updatedMessage = JSON . parse ( JSON . stringify ( originalMessage ) ) ;
191
+
192
+ if ( updatedMessage . attachments && updatedMessage . attachments [ 0 ] ) {
193
+ updatedMessage . attachments [ 0 ] . fields . push ( {
194
+ title : '❌ 오류 발생' ,
195
+ value : error ,
196
+ short : false ,
197
+ } ) ;
198
+
199
+ updatedMessage . attachments [ 0 ] . color = 'danger' ;
200
+ }
201
+
202
+ return updatedMessage ;
203
+ }
109
204
}
0 commit comments