Skip to content

Commit

Permalink
Merge pull request #64 from gsainfoteam/48-psw
Browse files Browse the repository at this point in the history
48 psw
  • Loading branch information
siwonpada authored Jul 17, 2024
2 parents 8d60d0a + a07075c commit 19f0323
Show file tree
Hide file tree
Showing 12 changed files with 465 additions and 7 deletions.
301 changes: 301 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.521.0",
"@nestjs/axios": "^3.0.2",
"@nestjs/bull": "^10.1.1",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.0",
"@nestjs/core": "^10.0.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^7.3.0",
"axios": "^1.6.7",
"bull": "^4.14.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.6",
Expand Down
12 changes: 12 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { ImageModule } from './image/image.module';
import { CrawlModule } from './crawl/crawl.module';
import { GroupModule } from './group/group.module';
import { FcmModule } from './fcm/fcm.module';
import { BullModule } from '@nestjs/bull';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AiModule } from './ai/ai.module';

@Module({
Expand All @@ -24,6 +26,16 @@ import { AiModule } from './ai/ai.module';
CrawlModule,
GroupModule,
FcmModule,
BullModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
redis: {
host: configService.getOrThrow<string>('REDIS_HOST'),
port: configService.getOrThrow<number>('REDIS_PORT'),
},
}),
}),
AiModule,
],
controllers: [AppController],
Expand Down
33 changes: 33 additions & 0 deletions src/fcm/fcm.consumer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
OnQueueCompleted,
OnQueueRemoved,
Process,
Processor,
} from '@nestjs/bull';
import { FcmService } from './fcm.service';
import { QueueDataType } from './types/queue.type';
import { Logger } from '@nestjs/common';
import { Job } from 'bull';

@Processor('fcm')
export class FcmConsumer {
private readonly logger = new Logger(FcmConsumer.name);
constructor(private readonly fcmService: FcmService) {}

@Process()
async handleFcmMessage(job: { data: QueueDataType }): Promise<void> {
this.logger.debug('Start processing fcm message');
const { targetUser, notification, data } = job.data;
await this.fcmService.postMessage(notification, targetUser, data);
}

@OnQueueCompleted()
async onCompleted(job: Job) {
this.logger.debug(`Job ${job.id} completed`);
}

@OnQueueRemoved()
async onRemoved(job: Job) {
this.logger.debug(`Job ${job.id} removed`);
}
}
10 changes: 8 additions & 2 deletions src/fcm/fcm.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import { FcmService } from './fcm.service';
import { ConfigModule } from '@nestjs/config';
import { FcmRepository } from './fcm.repository';
import { PrismaModule } from 'src/prisma/prisma.module';
import { BullModule } from '@nestjs/bull';
import { FcmConsumer } from './fcm.consumer';

@Module({
imports: [ConfigModule, PrismaModule],
providers: [FcmService, FcmRepository],
imports: [
ConfigModule,
PrismaModule,
BullModule.registerQueue({ name: 'fcm' }),
],
providers: [FcmService, FcmRepository, FcmConsumer],
exports: [FcmService],
})
export class FcmModule {}
25 changes: 25 additions & 0 deletions src/fcm/fcm.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import { App, cert, initializeApp } from 'firebase-admin/app';
import { Notification, getMessaging } from 'firebase-admin/messaging';
import { FcmRepository } from './fcm.repository';
import { FcmTargetUser } from './types/fcmTargetUser.type';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class FcmService {
private readonly app: App;
private readonly logger = new Logger(FcmService.name);
constructor(
@InjectQueue('fcm') private readonly fcmQueue: Queue,
private readonly configService: ConfigService,
private readonly fcmRepository: FcmRepository,
) {
Expand All @@ -24,6 +27,28 @@ export class FcmService {
});
}

async postMessageWithDelay(
jobId: string,
notification: Notification,
targetUser: FcmTargetUser,
data?: Record<string, string>,
): Promise<void> {
this.logger.log(`Adding message to queue with jobId ${jobId}`);
await this.fcmQueue.add(
{ notification, targetUser, data },
{
delay: this.configService.getOrThrow<number>('FCM_DELAY'),
removeOnComplete: true,
removeOnFail: true,
jobId,
},
);
}

async deleteMessageJobIdPattern(name: string): Promise<void> {
await this.fcmQueue.removeJobs(name);
}

async postMessage(
notification: Notification,
targetUser: FcmTargetUser,
Expand Down
8 changes: 8 additions & 0 deletions src/fcm/types/queue.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { FcmTargetUser } from './fcmTargetUser.type';
import { Notification } from 'firebase-admin/messaging';

export type QueueDataType = {
targetUser: FcmTargetUser;
notification: Notification;
data?: Record<string, string>;
};
3 changes: 3 additions & 0 deletions src/notice/dto/res/generalNotice.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ export class GeneralNoticeDto {
@ApiProperty()
currentDeadline: Date | null;

@ApiProperty()
publishedAt: Date | null;

@ApiPropertyOptional()
imageUrls?: string[];

Expand Down
16 changes: 16 additions & 0 deletions src/notice/notice.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ export class NoticeController {
return this.noticeService.createNotice(createNoticeDto, user.uuid, token);
}

@ApiOperation({
summary: 'Send notice alarm',
description: 'Send notice alarm',
})
@ApiOkResponse({ description: 'Return notice' })
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
@ApiInternalServerErrorResponse({ description: 'Internal Server Error' })
@Post(':id/alarm')
@UseGuards(IdPGuard)
async sendNotice(
@GetUser() user: User,
@Param('id', ParseIntPipe) id: number,
): Promise<void> {
return this.noticeService.sendNotice(id, user.uuid);
}

@ApiOperation({
summary: 'Add additional notice',
description: 'Add additional notice',
Expand Down
2 changes: 2 additions & 0 deletions src/notice/notice.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class NoticeMapper {
reminders,
category,
groupName,
publishedAt,
}: NoticeFullContent,
langFromDto?: string,
userUuid?: string,
Expand Down Expand Up @@ -87,6 +88,7 @@ export class NoticeMapper {
count: reactions.length,
isReacted: reactions.some(({ userId }) => userId === userUuid),
})),
publishedAt,
groupName,
};
}
Expand Down
19 changes: 16 additions & 3 deletions src/notice/notice.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { PrismaService } from 'src/prisma/prisma.service';
import { GetAllNoticeQueryDto } from './dto/req/getAllNotice.dto';
import dayjs from 'dayjs';
import { NoticeFullContent } from './types/noticeFullContent';
import { FileType } from '@prisma/client';
import { FileType, Notice } from '@prisma/client';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
import { CreateNoticeDto } from './dto/req/createNotice.dto';
import { AdditionalNoticeDto } from './dto/req/additionalNotice.dto';
Expand Down Expand Up @@ -307,7 +307,8 @@ export class NoticeRepository {
* this method is used to create the notice
* @param param0 create notice dto
* @param userUuid user's uuid
* @param createdAt created time
* @param createdAt created time (optional)
* @param publishedAt published time (optional) the time that will be sent fcm message.
* @returns NoticeFullContent
*/
async createNotice(
Expand All @@ -323,6 +324,7 @@ export class NoticeRepository {
}: CreateNoticeDto,
userUuid: string,
createdAt?: Date,
publishedAt?: Date,
): Promise<NoticeFullContent> {
this.logger.log(`createNotice`);
const findTags = await this.prismaService.tag.findMany({
Expand Down Expand Up @@ -388,6 +390,7 @@ export class NoticeRepository {
groupName === undefined
? undefined
: { connect: { name: groupName } },
publishedAt,
},
include: {
tags: true,
Expand Down Expand Up @@ -416,7 +419,6 @@ export class NoticeRepository {
.catch((error) => {
if (error instanceof PrismaClientKnownRequestError) {
if (error.code === 'P2025') {
console.log(error);
this.logger.debug(`User uuid not found`);
throw new NotFoundException(`User uuid not found`);
}
Expand All @@ -430,6 +432,17 @@ export class NoticeRepository {
});
}

async updatePublishedAt(id: number, publishedAt: Date): Promise<Notice> {
return this.prismaService.notice.update({
where: {
id,
},
data: {
publishedAt,
},
});
}

async addAdditionalNotice(
{ title, body, deadline }: AdditionalNoticeDto,
id: number,
Expand Down
41 changes: 39 additions & 2 deletions src/notice/notice.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ import { FcmService } from 'src/fcm/fcm.service';
import { FcmTargetUser } from 'src/fcm/types/fcmTargetUser.type';
import { htmlToText } from 'html-to-text';
import { Notification } from 'firebase-admin/messaging';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class NoticeService {
private readonly loggger = new Logger(NoticeService.name);
private fcmDelay: number;
constructor(
private readonly imageService: ImageService,
private readonly documentService: DocumentService,
Expand All @@ -40,7 +42,10 @@ export class NoticeService {
private readonly noticeMapper: NoticeMapper,
private readonly groupService: GroupService,
private readonly fcmService: FcmService,
) {}
private readonly configService: ConfigService,
) {
this.fcmDelay = Number(this.configService.getOrThrow<number>('FCM_DELAY'));
}

async getNoticeList(
getAllNoticeQueryDto: GetAllNoticeQueryDto,
Expand Down Expand Up @@ -126,6 +131,8 @@ export class NoticeService {
const createdNotice = await this.noticeRepository.createNotice(
createNoticeDto,
userUuid,
undefined,
new Date(new Date().getTime() + this.fcmDelay),
);

const notice = await this.getNotice(createdNotice.id, { isViewed: false });
Expand All @@ -136,7 +143,8 @@ export class NoticeService {
imageUrl: notice.imageUrls ? notice.imageUrls[0] : undefined,
};

await this.fcmService.postMessage(
await this.fcmService.postMessageWithDelay(
notice.id.toString(),
this.convertNotificationBodyToString(notification),
FcmTargetUser.All,
{
Expand All @@ -147,6 +155,34 @@ export class NoticeService {
return notice;
}

async sendNotice(id: number, userUuid: string): Promise<void> {
this.loggger.log(`Send notice ${id}`);
const notice = await this.getNotice(id, { isViewed: false });
if (notice.author.uuid !== userUuid) {
throw new ForbiddenException('not author of the notice');
}
if (notice.publishedAt === null || notice.publishedAt < new Date()) {
throw new ForbiddenException('a message already sent');
}
this.loggger.log(`Notice time ${notice.publishedAt} is not sent yet`);

const notification = {
title: notice.title,
body: notice.content,
imageUrl: notice.imageUrls ? notice.imageUrls[0] : undefined,
};

await this.fcmService.deleteMessageJobIdPattern(notice.id.toString());
await this.fcmService.postMessage(
this.convertNotificationBodyToString(notification),
FcmTargetUser.All,
{
path: `/notice/${id}`,
},
);
await this.noticeRepository.updatePublishedAt(id, new Date());
}

async addNoticeAdditional(
additionalNoticeDto: AdditionalNoticeDto,
id: number,
Expand Down Expand Up @@ -238,6 +274,7 @@ export class NoticeService {
}

async deleteNotice(id: number, userUuid: string): Promise<void> {
await this.fcmService.deleteMessageJobIdPattern(id.toString());
const notice = await this.noticeRepository.getNotice(id);
if (notice.author.uuid !== userUuid) {
throw new ForbiddenException();
Expand Down

0 comments on commit 19f0323

Please sign in to comment.