Skip to content
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

✨ 스트리밍기능 추가, 알림함 비우기 기능 + Refactor #116

Merged
merged 12 commits into from
Dec 19, 2024
1 change: 1 addition & 0 deletions src/app/_dto/notification/notification.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AnswerWithProfileDto } from '@/app/_dto/answers/Answers.dto';
export type NotificationPayloadTypes = [
{ notification_name: 'answer_on_my_question'; data: AnswerWithProfileDto; target: string },
{ notification_name: 'read_all_notifications'; data: null; target: string },
{ notification_name: 'delete_all_notifications'; data: null; target: string },
][number];

export class NotificationDto {
Expand Down
29 changes: 26 additions & 3 deletions src/app/api/_service/notification/notification.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export class NotificationService {
this.queueService = QueueService.get();
this.redisPubSub = RedisPubSubService.getInstance();
this.prisma = GetPrismaClient.getClient();
this.DeleteAnswerNotification = this.DeleteAnswerNotification.bind(this);
this.onDeleteAnswerNotification = this.onDeleteAnswerNotification.bind(this);
this.redisPubSub.sub<AnswerDeletedEvPayload>('answer-deleted-event', (data) => {
this.DeleteAnswerNotification(data);
this.onDeleteAnswerNotification(data);
});
}
public static getInstance() {
Expand Down Expand Up @@ -66,7 +66,7 @@ export class NotificationService {
});
}

public async DeleteAnswerNotification(data: AnswerDeletedEvPayload) {
private async onDeleteAnswerNotification(data: AnswerDeletedEvPayload) {
const key = `answer:${data.deleted_id}`;
try {
await this.prisma.notification.delete({ where: { notiKey: key } });
Expand Down Expand Up @@ -131,4 +131,27 @@ export class NotificationService {
return sendApiError(500, 'readAllNotificationsApi FAIL!');
}
}

@Auth()
@RateLimit({ bucket_time: 60, req_limit: 10 }, 'user')
public async deleteAllNotificationApi(
_req: NextRequest,
@JwtPayload tokenPayload?: jwtPayloadType,
): Promise<NextResponse> {
const handle = tokenPayload?.handle;
if (!handle) {
return sendApiError(401, 'Unauthorized');
}
try {
const deleted = await this.prisma.notification.deleteMany({ where: { userHandle: handle } });
this.redisPubSub.pub<NotificationPayloadTypes>('websocket-notification-event', {
notification_name: 'delete_all_notifications',
data: null,
target: handle,
});
return NextResponse.json({ message: `OK! ${deleted.count} notifications Deleted` });
} catch (err) {
return sendApiError(500, JSON.stringify(err));
}
}
}
9 changes: 8 additions & 1 deletion src/app/api/_service/queue/workers/AccountClean.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AnswerDeletedEvPayload } from '@/app/_dto/websocket-event/websocket-event.dto';
import { RedisPubSubService } from '@/app/api/_service/redis-pubsub/redis-event.service';
import { GetPrismaClient } from '@/app/api/_utils/getPrismaClient/get-prisma-client';
import { Logger } from '@/utils/logger/Logger';
import { Job, Queue, Worker } from 'bullmq';
Expand All @@ -11,12 +13,14 @@ const logger = new Logger('AccountCleanWork');
export class AccountCleanJob {
private cleanQueue;
private cleanWorker;
private redisPubsub: RedisPubSubService;

constructor(connection: Redis) {
this.redisPubsub = RedisPubSubService.getInstance();
this.cleanQueue = new Queue(accountClean, {
connection,
});
this.cleanWorker = new Worker(accountClean, this.process, {
this.cleanWorker = new Worker(accountClean, this.process.bind(this), {
connection,
concurrency: 10,
removeOnComplete: {
Expand Down Expand Up @@ -56,6 +60,9 @@ export class AccountCleanJob {
}
for (const a of parts) {
await prisma.answer.delete({ where: { id: a.id } });
await this.redisPubsub.pub<AnswerDeletedEvPayload>('answer-deleted-event', {
deleted_id: a.id,
});
}
counter += parts.length;
logger.debug(`답변 ${counter} 개 삭제됨`);
Expand Down
5 changes: 5 additions & 0 deletions src/app/api/user/notification/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ export async function GET(req: NextRequest) {
const notificationService = NotificationService.getInstance();
return await notificationService.getMyNotificationsApi(req);
}

export async function DELETE(req: NextRequest) {
const notificationService = NotificationService.getInstance();
return await notificationService.deleteAllNotificationApi(req);
}
63 changes: 53 additions & 10 deletions src/app/main/_events.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Logger } from '@/utils/logger/Logger';
import { questionDto } from '../_dto/questions/question.dto';
import { QuestionDeletedPayload } from '../_dto/websocket-event/websocket-event.dto';
import { AnswerDeletedEvPayload, QuestionDeletedPayload } from '../_dto/websocket-event/websocket-event.dto';
import { AnswerWithProfileDto } from '../_dto/answers/Answers.dto';
import { NotificationPayloadTypes } from '../_dto/notification/notification.dto';
import { userProfileMeDto } from '@/app/_dto/fetch-profile/Profile.dto';

const QuestionCreateEvent = 'QuestionCreateEvent';
const QuestionDeleteEvent = 'QuestionDeleteEvent';
Expand Down Expand Up @@ -46,7 +47,8 @@ export class MyQuestionEv {
}

const FetchMoreAnswerRequestEvent = 'FetchMoreAnswerRequestEvent';
const WebSocketAnswerEvent = 'WebSocketAnswerEvent';
const WebSocketAnswerCreatedEvent = 'WebSocketAnswerCreatedEvent';
const WebSocketAnswerDeletedEvent = 'WebSocketAnswerDeletedEvent';
export class AnswerEv {
private static logger = new Logger('AnswerEv', { noColor: true });
static addFetchMoreRequestEventListener(onEvent: (ev: CustomEvent<string | undefined>) => void) {
Expand All @@ -63,21 +65,35 @@ export class AnswerEv {
window.dispatchEvent(ev);
}

static addCreatedAnswerEventListener(onEvent: (ev: CustomEvent<AnswerWithProfileDto>) => void) {
AnswerEv.logger.debug('Added WebSocket Answer EventListener');
window.addEventListener(WebSocketAnswerEvent, onEvent as EventListener);
static addAnswerCreatedEventListener(onEvent: (ev: CustomEvent<AnswerWithProfileDto>) => void) {
AnswerEv.logger.debug('Added WebSocket AnswerCreated EventListener');
window.addEventListener(WebSocketAnswerCreatedEvent, onEvent as EventListener);
}

static removeCreatedAnswerEventListener(onEvent: (ev: CustomEvent<AnswerWithProfileDto>) => void) {
AnswerEv.logger.debug('Removed WebSocket Answer EventListener');
window.removeEventListener(WebSocketAnswerEvent, onEvent as EventListener);
static removeAnswerCreatedEventListener(onEvent: (ev: CustomEvent<AnswerWithProfileDto>) => void) {
AnswerEv.logger.debug('Removed WebSocket AnswerCreated EventListener');
window.removeEventListener(WebSocketAnswerCreatedEvent, onEvent as EventListener);
}

static sendCreatedAnswerEvent(data: AnswerWithProfileDto) {
const ev = new CustomEvent<AnswerWithProfileDto>(WebSocketAnswerEvent, { detail: data });
static sendAnswerCreatedEvent(data: AnswerWithProfileDto) {
const ev = new CustomEvent<AnswerWithProfileDto>(WebSocketAnswerCreatedEvent, { detail: data });
window.dispatchEvent(ev);
AnswerEv.logger.debug('New Answer Created', data);
}

static addAnswerDeletedEventListener(onEvent: (ev: CustomEvent<AnswerDeletedEvPayload>) => void) {
AnswerEv.logger.debug('Added WebSocket AnswerDeleted EventListener');
window.addEventListener(WebSocketAnswerDeletedEvent, onEvent as EventListener);
}
static removeAnswerDeletedEventListener(onEvent: (ev: CustomEvent<AnswerDeletedEvPayload>) => void) {
AnswerEv.logger.debug('Removed WebSocket AnswerDeleted EventListener');
window.removeEventListener(WebSocketAnswerDeletedEvent, onEvent as EventListener);
}
static sendAnswerDeletedEvent(data: AnswerDeletedEvPayload) {
const ev = new CustomEvent<AnswerDeletedEvPayload>(WebSocketAnswerDeletedEvent, { detail: data });
AnswerEv.logger.debug('Answer Deleted', data);
window.dispatchEvent(ev);
}
}

const NotificationEvent = 'NotificationEvent';
Expand All @@ -99,3 +115,30 @@ export class NotificationEv {
NotificationEv.logger.debug('Notification Event Sent', data);
}
}

const ProfileUpdateReqEvent = 'ProfileUpdateReqEvent';
type ProfileUpdateReqEvent = typeof ProfileUpdateReqEvent;
type ProfileUpdateReqData = Partial<userProfileMeDto>;
/**
* MyProfileContext 의 Update요청 Event들
*/
export class MyProfileEv {
private constructor() {}
private static logger = new Logger('UpdateMyProfileContext', { noColor: true });
static async SendUpdateReq(data: Partial<userProfileMeDto>) {
const ev = new CustomEvent<ProfileUpdateReqData>(ProfileUpdateReqEvent, { bubbles: true, detail: data });
window.dispatchEvent(ev);
MyProfileEv.logger.debug('Send My Profile Update Request Event...');
}

static addEventListener(onEvent: (ev: CustomEvent<ProfileUpdateReqData>) => void) {
MyProfileEv.logger.debug('add Profile Update EventListener');
window.addEventListener(ProfileUpdateReqEvent, onEvent as EventListener);
}

static removeEventListener(onEvent: (ev: CustomEvent<ProfileUpdateReqData>) => void) {
MyProfileEv.logger.debug('Remove Profile Update Req EventListener');
window.removeEventListener(ProfileUpdateReqEvent, onEvent as EventListener);
}
}

Loading
Loading