Skip to content
Draft
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
3 changes: 3 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
},
"dependencies": {
"@aws-sdk/client-s3": "^3.731.1",
"@nestjs/bull": "^11.0.2",
"@nestjs/cache-manager": "^2.3.0",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.3.0",
Expand All @@ -38,8 +39,10 @@
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^8.1.0",
"@prisma/client": "^6.0.1",
"@types/bull": "^4.10.4",
"@types/nodemailer": "^6.4.17",
"axios": "^1.7.9",
"bull": "^4.16.5",
"cache-manager-redis-yet": "^5.1.5",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
Expand Down
13 changes: 13 additions & 0 deletions packages/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { CharacterModule } from './character/character.module';
import { ProofModule } from './proof/proof.module';
import { DuplicateRequestMiddleware } from './common/duplicate-request.middleware';
import { NotificationModule } from './notification/notification.module';
import { QueueModule } from './queue/queue.module';
import { BullModule } from '@nestjs/bull';

@Module({
imports: [
Expand All @@ -25,6 +27,16 @@ import { NotificationModule } from './notification/notification.module';
load: [configuration],
isGlobal: true,
}),
BullModule.forRootAsync({
inject: [ConfigService],
useFactory: async (config: ConfigService) => ({
redis: {
host: config.get('redis.host'),
port: config.get('redis.port'),
password: config.get('redis.password'),
},
}),
}),
CacheModule.registerAsync({
isGlobal: true,
inject: [ConfigService],
Expand All @@ -48,6 +60,7 @@ import { NotificationModule } from './notification/notification.module';
CharacterModule,
ProofModule,
NotificationModule,
QueueModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
6 changes: 6 additions & 0 deletions packages/backend/src/group/group.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ import { GetGalleryParam } from './dtos/get-gallery-param.dto';
import { GetGalleryRes } from './dtos/get-gallery-res.dto';
import { UpdateGroupQuery } from './dtos/update-group-query.dto';
import { LeaveGroupHelper } from './utils/leave-group.helper';
import { InjectQueue } from '@nestjs/bull';
import { NOTIFICATION_PROCESSOR } from '@/queue/utils/constants';
import { Queue } from 'bull';

@Injectable()
export class GroupService {
Expand All @@ -74,6 +77,8 @@ export class GroupService {
private readonly prisma: PrismaService,
private readonly s3: S3Service,
private readonly characterService: CharacterRewardService,
@InjectQueue(NOTIFICATION_PROCESSOR.QUEUE)
private readonly notiQueue: Queue,
) {}

/**
Expand Down Expand Up @@ -591,6 +596,7 @@ export class GroupService {
group,
wallet,
this.prisma,
this.notiQueue,
);

return join.joinRole === JoinRole.HOST
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/group/utils/leave-group.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as utc from 'dayjs/plugin/utc';
import * as timezone from 'dayjs/plugin/timezone';
import { PrismaService } from '@/prisma/prisma.service';
import { VisibleForTesting } from '@/common/visible-for-testing.decorator';
import { Queue } from 'bull';

dayjs.extend(utc);
dayjs.extend(timezone);
Expand All @@ -31,12 +32,14 @@ export class LeaveGroupHelper {
// leave group
private readonly joinId: number;
private readonly myWallet: Wallet;
private readonly notiQueue: Queue;

constructor(
join: JoinForLeave,
group: GroupForLeave,
wallet: Wallet,
prisma: PrismaService,
notiQueue: Queue,
) {
this.groupDates = group.groupDate
.map((d) => dayjs(d.date).tz('Asia/Seoul'))
Expand All @@ -53,6 +56,7 @@ export class LeaveGroupHelper {
this.joinId = join.id;
this.myWallet = wallet;
this.now = dayjs().tz('Asia/Seoul');
this.notiQueue = notiQueue;
}

/**
Expand Down Expand Up @@ -211,6 +215,10 @@ export class LeaveGroupHelper {
if (this.existMyProof) return false;
if (this.now.diff(this.joinedAt, 'hour') < 1) return true;

if (this.groupDates.length === 0) {
throw new UnprocessableEntityException('그룹 날짜가 없습니다.');
}

const earliestGroupDate = this.groupDates[0];

const isBeforeGroup = this.now.isBefore(earliestGroupDate.startOf('day'));
Expand Down
24 changes: 0 additions & 24 deletions packages/backend/src/notification/notification-store.service.ts

This file was deleted.

7 changes: 2 additions & 5 deletions packages/backend/src/notification/notification.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { Global, Module } from '@nestjs/common';
import { Module } from '@nestjs/common';
import { NotificationService } from './notification.service';
import { NotificationController } from './notification.controller';
import { NotificationStoreService } from './notification-store.service';

@Global()
@Module({
controllers: [NotificationController],
providers: [NotificationService, NotificationStoreService],
exports: [NotificationStoreService],
providers: [NotificationService],
})
export class NotificationModule {}
55 changes: 55 additions & 0 deletions packages/backend/src/queue/noti-queue.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
Injectable,
InternalServerErrorException,
Logger,
} from '@nestjs/common';
import { Process, Processor } from '@nestjs/bull';
import { Job } from 'bull';
import { NOTIFICATION_PROCESSOR } from './utils/constants';
import { CreateDeletedGroupNotiParams } from './utils/types';
import { PrismaService } from '@/prisma/prisma.service';
import { NotificationType } from '@prisma/client';

@Injectable()
@Processor(NOTIFICATION_PROCESSOR.QUEUE)
export class NotiQueueService {
constructor(private readonly prisma: PrismaService) {}

private readonly logger = new Logger(NotiQueueService.name, {
timestamp: true,
});

@Process(NOTIFICATION_PROCESSOR.MISSION.DELETED_ROOM)
async processDeletedGroupNotification(
job: Job<CreateDeletedGroupNotiParams>,
) {
const { data } = job;
const { userId, groupId, refundAmount, groupName } = data;

this.logger.debug(
`${groupName} 모임이 삭제되어 참여한 사용자 (userId: ${userId})에게 알림을 보냅니다.`,
);

return await this.prisma.notification
.create({
data: {
userId,
groupId,
moneyChange: refundAmount,
type: NotificationType.DELETED_ROOM,
title: `${groupName} 모임이 종료되었습니다.`,
content: `방을 만든 사람이 방을 폭파했습니다. 총 ${refundAmount}원을 환불 받았습니다.`,
},
})
.catch((err) => {
this.logger.error(
`알림 저장 실패 (userId: ${userId}, groupId: ${groupId})`,
err,
);

throw new InternalServerErrorException(
`${groupName} 모임 폭파에 대한 사용자 (userId: ${userId})에게 노출될 알림을 저장할 수 없습니다.`,
);
});
}
}
16 changes: 16 additions & 0 deletions packages/backend/src/queue/queue.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Global, Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bull';
import { NotiQueueService } from './noti-queue.service';
import { NOTIFICATION_PROCESSOR } from './utils/constants';

@Global()
@Module({
imports: [
BullModule.registerQueue({
name: NOTIFICATION_PROCESSOR.QUEUE,
}),
],
providers: [NotiQueueService],
exports: [BullModule],
})
export class QueueModule {}
17 changes: 17 additions & 0 deletions packages/backend/src/queue/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NotificationType } from '@prisma/client';

interface ProcessorDefinition<T> {
QUEUE: string;
MISSION: {
[key in keyof T]: T[key];
};
}

export const NOTIFICATION_PROCESSOR: ProcessorDefinition<
typeof NotificationType
> = {
QUEUE: 'notification',
MISSION: {
...NotificationType,
},
};
Loading