Skip to content

Commit

Permalink
feat(be): use cache for test submission
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaehyeon1020 committed Sep 8, 2024
1 parent 6200d16 commit 8e20860
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 46 deletions.
49 changes: 41 additions & 8 deletions apps/backend/apps/client/src/submission/submission-sub.service.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { Injectable, Logger, type OnModuleInit } from '@nestjs/common'
import { CACHE_MANAGER } from '@nestjs/cache-manager'
import { Inject, Injectable, Logger, type OnModuleInit } from '@nestjs/common'
import { Nack, AmqpConnection } from '@golevelup/nestjs-rabbitmq'
import {
ResultStatus,
type Submission,
type SubmissionResult
} from '@prisma/client'
import type { Cache } from 'cache-manager'
import { plainToInstance } from 'class-transformer'
import { validateOrReject, ValidationError } from 'class-validator'
import { Span } from 'nestjs-otel'
import { testKey } from '@libs/cache'
import {
CONSUME_CHANNEL,
EXCHANGE,
ORIGIN_HANDLER_NAME,
RESULT_KEY,
RESULT_QUEUE,
Status
RUN_MESSAGE_TYPE,
Status,
TEST_SUBMISSION_EXPIRE_TIME
} from '@libs/constants'
import { UnprocessableDataException } from '@libs/exception'
import { PrismaService } from '@libs/prisma'
Expand All @@ -26,14 +31,23 @@ export class SubmissionSubscriptionService implements OnModuleInit {

constructor(
private readonly prisma: PrismaService,
private readonly amqpConnection: AmqpConnection
private readonly amqpConnection: AmqpConnection,
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache
) {}

onModuleInit() {
this.amqpConnection.createSubscriber(
async (msg: object) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async (msg: object, raw: any) => {
try {
const res = await this.validateJudgerResponse(msg)

if (raw.properties.type === RUN_MESSAGE_TYPE) {
const testRequestedUserId = res.submissionId // test용 submissionId == test를 요청한 userId
await this.updateTestResult(res, testRequestedUserId)
return
}

await this.handleJudgerMessage(res)
} catch (error) {
if (
Expand Down Expand Up @@ -61,6 +75,28 @@ export class SubmissionSubscriptionService implements OnModuleInit {
)
}

async updateTestResult(msg: JudgerResponse, userId: number): Promise<void> {
const key = testKey(userId)
const status = Status(msg.resultCode)
const testcaseId = msg.judgeResult?.testcaseId

const testcases =
(await this.cacheManager.get<
{
id: number
result: ResultStatus
}[]
>(key)) ?? []

testcases.forEach((tc) => {
if (tc.id === testcaseId) {
tc.result = status
}
})

await this.cacheManager.set(key, testcases, TEST_SUBMISSION_EXPIRE_TIME)
}

async validateJudgerResponse(msg: object): Promise<JudgerResponse> {
const res: JudgerResponse = plainToInstance(JudgerResponse, msg)
await validateOrReject(res)
Expand Down Expand Up @@ -191,8 +227,7 @@ export class SubmissionSubscriptionService implements OnModuleInit {
select: {
result: true
}
},
isTest: true
}
}
})

Expand All @@ -214,8 +249,6 @@ export class SubmissionSubscriptionService implements OnModuleInit {
data: { result: submissionResult }
})

if (submission.isTest) return

if (submission.userId && submission.contestId)
await this.calculateSubmissionScore(submission, allAccepted)

Expand Down
53 changes: 42 additions & 11 deletions apps/backend/apps/client/src/submission/submission.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import {
Logger,
Query,
DefaultValuePipe,
Headers,
ParseBoolPipe
Headers
} from '@nestjs/common'
import { Prisma } from '@prisma/client'
import { AuthNotNeededIfOpenSpace, AuthenticatedRequest } from '@libs/auth'
Expand Down Expand Up @@ -43,9 +42,7 @@ export class SubmissionController {
@Query('problemId', new RequiredIntPipe('problemId')) problemId: number,
@Query('groupId', GroupIDPipe) groupId: number,
@Query('contestId', IDValidationPipe) contestId: number | null,
@Query('workbookId', IDValidationPipe) workbookId: number | null,
@Query('isTest', new ParseBoolPipe({ optional: true }))
isTest?: boolean
@Query('workbookId', IDValidationPipe) workbookId: number | null
) {
try {
if (!contestId && !workbookId) {
Expand All @@ -54,8 +51,7 @@ export class SubmissionController {
userIp,
req.user.id,
problemId,
groupId,
isTest || false
groupId
)
} else if (contestId) {
return await this.submissionService.submitToContest(
Expand All @@ -64,8 +60,7 @@ export class SubmissionController {
req.user.id,
problemId,
contestId,
groupId,
isTest || false
groupId
)
} else if (workbookId) {
return await this.submissionService.submitToWorkbook(
Expand All @@ -74,8 +69,7 @@ export class SubmissionController {
req.user.id,
problemId,
workbookId,
groupId,
isTest || false
groupId
)
}
} catch (error) {
Expand All @@ -94,6 +88,43 @@ export class SubmissionController {
}
}

/**
* Open Testcase에 대해 채점하는 Test를 요청합니다.
* 채점 결과는 Cache에 저장됩니다.
*/
@Post('test')
async requestTest(
@Req() req: AuthenticatedRequest,
@Query('problemId', new RequiredIntPipe('problemId')) problemId: number,
@Body() submissionDto: CreateSubmissionDto
) {
try {
return await this.submissionService.requestTest(
req.user.id,
problemId,
submissionDto
)
} catch (error) {
if (
error instanceof EntityNotExistException ||
error instanceof ConflictFoundException
) {
throw error.convert2HTTPException()
}
this.logger.error(error)
throw new InternalServerErrorException()
}
}

/**
* requestTest의 반환으로 받은 key를 통해 Test 결과를 조회합니다.
* @returns Testcase별 결과가 담겨있는 Object
*/
@Get('test')
async getTestResult(@Req() req: AuthenticatedRequest) {
return await this.submissionService.getTestResult(req.user.id)
}

@Get('delay-cause')
async checkDelay() {
return await this.submissionService.checkDelay()
Expand Down
Loading

0 comments on commit 8e20860

Please sign in to comment.