diff --git a/src/common/util/slack/slack-template.ts b/src/common/util/slack/slack-template.ts index e39d4cf..3acfd6f 100644 --- a/src/common/util/slack/slack-template.ts +++ b/src/common/util/slack/slack-template.ts @@ -15,10 +15,6 @@ type Viewer = { viewerText: string; }; type MessageOptions = { - /** - * 서버 이름 - */ - appType: string; /** * 메시지 헤더 */ @@ -26,7 +22,7 @@ type MessageOptions = { /** * 메시지 타입 */ - type: 'Alert' | 'Error'; + type: 'Alert' | 'Error' | 'Report'; /** * 메세지 발행 주체 ex)className */ @@ -45,6 +41,11 @@ type ErrorMessageOptions = MessageOptions & { error: Error; request?: Request; }; +type ReportAlertMessageOptions = MessageOptions & { + type: 'Report'; + feed: { id: number; content: string; reportCount: number }; + reason: string; +}; export class SlackTemplate { public static alertTemplate( @@ -88,8 +89,44 @@ export class SlackTemplate { }; } + public static reportAlertTemplate( + options: ReportAlertMessageOptions, + ): IncomingWebhookSendArguments { + const { viewer, feed, reason } = options; + const defaultAttachment = this.makeDefaultAttachment(options); + const viewerAttachment = this.makeViewerAttachment({ + ...viewer, + viewerUrl: `${viewer.viewerUrl}/${feed.id}`, + }); + return { + attachments: [ + defaultAttachment, + { + color: 'good', + fields: [ + { + title: `*피드 정보*:`, + value: + '```' + + `- 피드 id: ${feed.id}\n- 피드 내용: ${feed.content}\n- 누적 신고 횟수: ${feed.reportCount}` + + '```', + short: false, + }, + { + title: `*신고 내용*:`, + value: '```' + reason + '```', + short: false, + }, + ], + }, + , + viewerAttachment, + ], + }; + } + private static makeDefaultAttachment( - options: AlertMessageOptions | ErrorMessageOptions, + options: MessageOptions, ): MessageAttachment { return { blocks: [ diff --git a/src/config/app/environment/local.ts b/src/config/app/environment/local.ts index b15e907..95443fb 100644 --- a/src/config/app/environment/local.ts +++ b/src/config/app/environment/local.ts @@ -78,5 +78,11 @@ export const LocalConfig: AppConfig = { description: process.env.SLACK_DESCRIPTION_BY_SERVER_ERROR_ALERT, viewerUrl: process.env.SLACK_VIEWER_URL_BY_SERVER_ERROR_ALERT, }, + feedReportAlert: { + webHooklUrl: process.env.SLACK_WEB_HOOK_URI_BY_FEED_REPORT_ALERT, + channelName: process.env.SLACK_CHANNEL_NAME_BY_FEED_REPORT_ALERT, + description: process.env.SLACK_DESCRIPTION_BY_FEED_REPORT_ALERT, + viewerUrl: process.env.SLACK_VIEWER_URL_BY_FEED_REPORT_ALERT, + }, }, }; diff --git a/src/config/app/environment/production.ts b/src/config/app/environment/production.ts index f4cbf56..aca3e8c 100644 --- a/src/config/app/environment/production.ts +++ b/src/config/app/environment/production.ts @@ -78,5 +78,11 @@ export const ProdConfig: AppConfig = { description: process.env.SLACK_DESCRIPTION_BY_SERVER_ERROR_ALERT, viewerUrl: process.env.SLACK_VIEWER_URL_BY_SERVER_ERROR_ALERT, }, + feedReportAlert: { + webHooklUrl: process.env.SLACK_WEB_HOOK_URI_BY_FEED_REPORT_ALERT, + channelName: process.env.SLACK_CHANNEL_NAME_BY_FEED_REPORT_ALERT, + description: process.env.SLACK_DESCRIPTION_BY_FEED_REPORT_ALERT, + viewerUrl: process.env.SLACK_VIEWER_URL_BY_FEED_REPORT_ALERT, + }, }, }; diff --git a/src/config/monitor/slack.config.ts b/src/config/monitor/slack.config.ts index 58a3ece..47accf2 100644 --- a/src/config/monitor/slack.config.ts +++ b/src/config/monitor/slack.config.ts @@ -21,4 +21,7 @@ export class SlackAlertOptions { export class SlackConfig { @InstanceValidator(SlackAlertOptions) readonly serverErrorAlert: SlackAlertOptions; + + @InstanceValidator(SlackAlertOptions) + readonly feedReportAlert: SlackAlertOptions; } diff --git a/src/custom/nest/interceptor/slack-sender.interceptor.ts b/src/custom/nest/interceptor/slack-sender.interceptor.ts index 30f7854..d742c07 100644 --- a/src/custom/nest/interceptor/slack-sender.interceptor.ts +++ b/src/custom/nest/interceptor/slack-sender.interceptor.ts @@ -41,7 +41,6 @@ export class SlackSenderInterceptor implements NestInterceptor { const message = SlackTemplate.errorTemplate({ error, request, - appType: this.config.get('appType'), header: `${appName}-API-Server 버그 발생`, type: 'Error', trigger: 'SlackInterceptor', diff --git a/src/domain/feed/domain/feed.domain.ts b/src/domain/feed/domain/feed.domain.ts index a020143..7d7bad2 100644 --- a/src/domain/feed/domain/feed.domain.ts +++ b/src/domain/feed/domain/feed.domain.ts @@ -143,6 +143,10 @@ export class Feed extends BaseDomain { return this.props.activationAt >= new Date(); } + get isLockFeed(): boolean { + return this.props.reportCount >= 5; + } + /* ========== method ========== */ addViewCount(): this { this.props.viewCount++; diff --git a/src/domain/feed/feed.repository.ts b/src/domain/feed/feed.repository.ts index 71cdb0d..bdc9b6c 100644 --- a/src/domain/feed/feed.repository.ts +++ b/src/domain/feed/feed.repository.ts @@ -251,6 +251,7 @@ export class FeedRepositoryImpl qb.select(); qb.innerJoin('comment.user', 'user') // .addSelect(['user.id', 'user.nickname', 'user.mbtiType']); + qb.where('comment.feedId = :feedId', { feedId }); if (!Util.isNil(nextCursor)) { sort === 'ASC' && qb.andWhere('comment.id >= :id', { id: nextCursor }); sort !== 'ASC' && qb.andWhere('comment.id <= :id', { id: nextCursor }); diff --git a/src/domain/feed/feed.service.ts b/src/domain/feed/feed.service.ts index 5546316..2e37d09 100644 --- a/src/domain/feed/feed.service.ts +++ b/src/domain/feed/feed.service.ts @@ -5,7 +5,7 @@ import { NotFoundException, } from '@nestjs/common'; -import { Util, errorMessage } from '@app/common'; +import { SlackTemplate, Util, errorMessage } from '@app/common'; import { InjectDataSource } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; import { UserRepository, UserRepositoryToken } from '../user/user.repository'; @@ -25,6 +25,10 @@ import { import { FeedRepository, FeedRepositoryToken } from './feed.repository'; import { RecommendType } from '@app/entity'; import { UploadService, UploadServiceToken } from './upload/upload.service'; +import { ConfigService } from '@nestjs/config'; +import { SlackAlertOptions, SlackConfig } from '@app/config'; +import { IncomingWebhook } from '@slack/client'; +import { Feed } from './domain'; export const FeedServiceToken = Symbol('FeedServiceToken'); export interface FeedService { @@ -72,12 +76,19 @@ export interface FeedService { @Injectable() export class FeedServiceImpl implements FeedService { + private readonly reportAlartConfig: SlackAlertOptions; + private readonly webhook: IncomingWebhook; + constructor( @InjectDataSource() private readonly dataSource: DataSource, @Inject(FeedRepositoryToken) private readonly feedRepo: FeedRepository, @Inject(UserRepositoryToken) private readonly userRepo: UserRepository, @Inject(UploadServiceToken) private readonly uploadService: UploadService, - ) {} + config: ConfigService, + ) { + this.reportAlartConfig = config.get('slack').feedReportAlert; + this.webhook = new IncomingWebhook(this.reportAlartConfig.webHooklUrl); + } async getFeeds(getDto: GetFeedsRequestDTO): Promise { const feeds = @@ -247,10 +258,13 @@ export class FeedServiceImpl implements FeedService { if (isExist) throw new ConflictException(errorMessage.E409_FEED_003); await txFeedRepo.createReportHistory(userId, feedId, postDto); + // TODO: report 로직 향후 변경 예정 await txFeedRepo.updateProperty(feedId, { reportCount: feed.addReportCount().reportCount, + activationAt: feed.isLockFeed ? new Date() : feed.activationAt, }); - // TODO: 신고 횟수가 5회 이상이면 슬랙에 알림을 보내는 로직을 추가한다. + + feed.isLockFeed && (await this.feedReportAlert(feed, postDto.reason)); await queryRunner.commitTransaction(); } catch (error) { await queryRunner.rollbackTransaction(); @@ -306,4 +320,20 @@ export class FeedServiceImpl implements FeedService { await queryRunner.release(); } } + + private async feedReportAlert(feed: Feed, reason: string) { + const { viewerUrl } = this.reportAlartConfig; + const message = SlackTemplate.reportAlertTemplate({ + header: '피드 신고 알림', + type: 'Report', + trigger: 'FeedService', + viewer: { + viewerUrl, + viewerText: '피드 신고 내역 확인', + }, + feed, + reason, + }); + await this.webhook.send(message); + } } diff --git a/src/entity/feed/report-history.entity.ts b/src/entity/feed/report-history.entity.ts index 11af9cb..3f78cce 100644 --- a/src/entity/feed/report-history.entity.ts +++ b/src/entity/feed/report-history.entity.ts @@ -15,11 +15,11 @@ export class ReportHistoryEntity extends BaseEntity { @ApiProperty({ description: '신고 사유', type: String, - minLength: 10, + minLength: 5, maxLength: 200, }) @Expose() - @StringValidator({ minLength: 10, maxLength: 200 }) + @StringValidator({ minLength: 5, maxLength: 200 }) @Column('varchar', { comment: '신고 사유', length: 200 }) reason: string;