From 55233f3140715222d1be803194b5d030b6fff5e4 Mon Sep 17 00:00:00 2001 From: luizy Date: Mon, 4 Dec 2023 12:19:47 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=84=B8=EC=85=98=20=ED=86=B5=EC=8B=A0?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Intercepter 도입 - 이미 쿠키에 세션이 있었거나, 쿠키에 설정할 세션이 있으면 응답 종류 후에만 업데이트 [#224] --- .../backend/src/quizzes/quizzes.controller.ts | 3 ++ .../src/session/session-save.intercepter.ts | 35 ++++++++++++++++++ .../backend/src/session/session.controller.ts | 4 ++- .../backend/src/session/session.service.ts | 36 ++++++++++++------- 4 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 packages/backend/src/session/session-save.intercepter.ts diff --git a/packages/backend/src/quizzes/quizzes.controller.ts b/packages/backend/src/quizzes/quizzes.controller.ts index 2a23b25..d38bcb2 100644 --- a/packages/backend/src/quizzes/quizzes.controller.ts +++ b/packages/backend/src/quizzes/quizzes.controller.ts @@ -11,6 +11,7 @@ import { Delete, UseGuards, HttpCode, + UseInterceptors, } from '@nestjs/common'; import { ApiTags, @@ -39,9 +40,11 @@ import { QuizWizardService } from '../quiz-wizard/quiz-wizard.service'; import { Fail, SubmitDto, Success } from './dto/submit.dto'; import { preview } from '../common/util'; import { QuizGuard } from './quiz.guard'; +import { SessionUpdateInterceptor } from '../session/session-save.intercepter'; @ApiTags('quizzes') @Controller('api/v1/quizzes') +@UseInterceptors(SessionUpdateInterceptor) export class QuizzesController { constructor( private readonly quizService: QuizzesService, diff --git a/packages/backend/src/session/session-save.intercepter.ts b/packages/backend/src/session/session-save.intercepter.ts new file mode 100644 index 0000000..6d4b8b7 --- /dev/null +++ b/packages/backend/src/session/session-save.intercepter.ts @@ -0,0 +1,35 @@ +import { tap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { SessionService } from './session.service'; +import { Response } from 'express'; + +@Injectable() +export class SessionUpdateInterceptor implements NestInterceptor { + constructor(private sessionService: SessionService) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const response: Response = context.switchToHttp().getResponse(); + let sessionId = request.cookies.sessionId; // 세션 ID 추출 + + return next.handle().pipe( + tap(() => { + sessionId = + sessionId || this.extractSessionId(response.getHeader('Set-Cookie')); // 세션 ID가 없으면 쿠키에서 추출 + // 세션 업데이트 로직 + this.sessionService.saveSession(sessionId); + }), + ); + } + + private extractSessionId(cookieStr) { + const sessionIdMatch = /sessionId=([^;]+)/.exec(cookieStr); + return sessionIdMatch ? sessionIdMatch[1] : null; + } +} diff --git a/packages/backend/src/session/session.controller.ts b/packages/backend/src/session/session.controller.ts index 5c67cfc..2177880 100644 --- a/packages/backend/src/session/session.controller.ts +++ b/packages/backend/src/session/session.controller.ts @@ -1,11 +1,13 @@ -import { Controller, Delete, Res } from '@nestjs/common'; +import { Controller, Delete, Res, UseInterceptors } from '@nestjs/common'; import { SessionService } from './session.service'; import { SessionId } from './session.decorator'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { Response } from 'express'; +import { SessionUpdateInterceptor } from './session-save.intercepter'; @ApiTags('session') @Controller('api/v1/session') +@UseInterceptors(SessionUpdateInterceptor) export class SessionController { constructor(private readonly sessionService: SessionService) {} diff --git a/packages/backend/src/session/session.service.ts b/packages/backend/src/session/session.service.ts index 7b2278e..a6de17c 100644 --- a/packages/backend/src/session/session.service.ts +++ b/packages/backend/src/session/session.service.ts @@ -7,15 +7,11 @@ import { ObjectId } from 'typeorm'; @Injectable() export class SessionService { + private readonly sessionMap: Map = new Map(); constructor( @InjectModel(Session.name) private sessionModel: Model, @Inject('winston') private readonly logger: Logger, - ) { - const testSession = new this.sessionModel({ - problems: {}, - }); - testSession.save(); - } + ) {} async createSession(): Promise { const session = new this.sessionModel({ @@ -23,7 +19,9 @@ export class SessionService { }); return await session.save().then((session) => { this.logger.log('info', `session ${session._id as ObjectId} created`); - return (session._id as ObjectId).toString('hex'); + const sessionId = (session._id as ObjectId).toString('hex'); + this.sessionMap.set(sessionId, session); + return sessionId; }); } @@ -44,7 +42,6 @@ export class SessionService { 'info', `session's new quizId: ${problemId}, document created`, ); - await session.save(); } else { this.logger.log( 'info', @@ -68,7 +65,6 @@ export class SessionService { `setting ${sessionId}'s containerId as ${containerId}`, ); session.problems.get(problemId).containerId = containerId; - session.save(); } async getRecentLog( @@ -95,7 +91,6 @@ export class SessionService { throw new Error('problem not found'); } session.problems.get(problemId).logs.push(log); - session.save(); } async deleteCommandHistory( @@ -108,18 +103,33 @@ export class SessionService { } session.problems.get(problemId).logs = []; session.problems.get(problemId).containerId = ''; - session.save(); } private async getSessionById(id: string): Promise { - return await this.sessionModel.findById(id); + let session = this.sessionMap.get(id); + if (!session) { + session = await this.sessionModel.findById(id); + if (!session) { + throw new Error('session not found'); + } + this.sessionMap.set(id, session); + } + return session; + } + + async saveSession(sessionId: string): Promise { + // 세션 조회 및 저장 로직 + const session = this.sessionMap.get(sessionId); + if (session) { + this.sessionMap.delete(sessionId); + await session.save(); + } } async deleteSession(sessionId: string): Promise { //soft delete const session = await this.getSessionById(sessionId); session.deletedAt = new Date(); - session.save(); this.logger.log('info', `session ${session._id as ObjectId} deleted`); }