Skip to content

Commit

Permalink
Merge pull request #116 from serafuku/yunochi/develop
Browse files Browse the repository at this point in the history
✨ 스트리밍기능 추가, 알림함 비우기 기능 + Refactor
  • Loading branch information
yunochi authored Dec 19, 2024
2 parents 6ee6134 + 13e3bbd commit 4309059
Show file tree
Hide file tree
Showing 16 changed files with 475 additions and 280 deletions.
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

0 comments on commit 4309059

Please sign in to comment.