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`); }