From b618d5f7991b32e5f1db0a25d6388a2999a785a7 Mon Sep 17 00:00:00 2001 From: Daehyun Kim Date: Sat, 9 Mar 2024 02:01:58 +0900 Subject: [PATCH 1/2] =?UTF-8?q?querybuilder=EB=A5=BC=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=9C,=20=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EB=90=9C=20ranking?= =?UTF-8?q?=20api=20=EA=B5=AC=ED=98=84,=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=99=84=EB=A3=8C.=20=EC=95=84=EC=A7=81=EC=9D=80?= =?UTF-8?q?=20=EB=AF=B8=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/room/room-code/room-code.pipe.spec.ts | 7 ++++ server/src/room/room-code/room-code.pipe.ts | 19 ++++++++++ server/src/room/room.controller.ts | 7 ++++ server/src/room/room.service.ts | 37 ++++++++++++++++++- 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 server/src/room/room-code/room-code.pipe.spec.ts create mode 100644 server/src/room/room-code/room-code.pipe.ts diff --git a/server/src/room/room-code/room-code.pipe.spec.ts b/server/src/room/room-code/room-code.pipe.spec.ts new file mode 100644 index 0000000..99b4ac5 --- /dev/null +++ b/server/src/room/room-code/room-code.pipe.spec.ts @@ -0,0 +1,7 @@ +import { RoomCodePipe } from './room-code.pipe'; + +describe('RoomCodePipe', () => { + it('should be defined', () => { + expect(new RoomCodePipe()).toBeDefined(); + }); +}); diff --git a/server/src/room/room-code/room-code.pipe.ts b/server/src/room/room-code/room-code.pipe.ts new file mode 100644 index 0000000..781d5a9 --- /dev/null +++ b/server/src/room/room-code/room-code.pipe.ts @@ -0,0 +1,19 @@ +import { + ArgumentMetadata, + BadRequestException, + Injectable, + PipeTransform, +} from '@nestjs/common'; + +@Injectable() +export class RoomCodePipe implements PipeTransform { + transform(value: any, _metadata: ArgumentMetadata) { + const hexRegExp = /^[0-9a-fA-F]{6}$/; + + if (!hexRegExp.test(value)) { + throw new BadRequestException('Invalid room code'); + } + + return value; + } +} diff --git a/server/src/room/room.controller.ts b/server/src/room/room.controller.ts index 62fc72b..4e7d742 100644 --- a/server/src/room/room.controller.ts +++ b/server/src/room/room.controller.ts @@ -15,6 +15,7 @@ import { Request } from 'express'; import { SessionAuthGuard } from '../auth/auth.guard'; import User from '../entities/user.entity'; import { RoomUserService } from '../room-user/room-user.service'; +import { RoomCodePipe } from './room-code/room-code.pipe'; import { RoomService } from './room.service'; @Controller('room') @@ -69,4 +70,10 @@ export class RoomController { async getRoomUsers(@Param('code') code: string) { return await this.roomUserService.findUsersByRoomCode(code); } + + @Get('/:code/rankings') + @HttpCode(HttpStatus.OK) + async getRoomRankings(@Param('code', RoomCodePipe) code: string) { + return await this.roomService.getRoomRankings(code); + } } diff --git a/server/src/room/room.service.ts b/server/src/room/room.service.ts index 9c9003c..2833f95 100644 --- a/server/src/room/room.service.ts +++ b/server/src/room/room.service.ts @@ -1,17 +1,19 @@ import { BadRequestException, + forwardRef, Inject, Injectable, InternalServerErrorException, Logger, - forwardRef, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import * as crypto from 'crypto'; import { isNil } from 'src/common/utils'; import { SubmissionService } from 'src/submission/submission.service'; import { Repository } from 'typeorm'; +import { Status } from '../const/boj-results'; import Room from '../entities/room.entity'; +import Submission from '../entities/submission.entity'; import User from '../entities/user.entity'; import RoomUser from '../room-user/room-user.entity'; import { RoomUserService } from '../room-user/room-user.service'; @@ -130,4 +132,37 @@ export class RoomService { } return room; } + + /** + * returns an array of user id, username, and the number of accepted problems of the user, sorted by the number of accepted problems + * @param code + */ + async getRoomRankings(code: string) { + Submission; + User; + const qb = this.roomRepository + .createQueryBuilder('room') + .where('room.code = :code', { code }) + .innerJoin('room.joinedUsers', 'roomUser') + .innerJoin('roomUser.user', 'user') + .innerJoin( + 'room.submissions', + 'submission', + 'submission.status = :status AND submission.user_id = user.id', + { + status: Status.ACCEPTED, + }, + ) + .innerJoin('submission.problem', 'problem') + .select('user.id', 'userId') + .addSelect('user.username', 'username') + .addSelect('COUNT(DISTINCT submission.problem)', 'acceptedCount') + .addSelect('MAX(submission.submittedAt)', 'lastAcceptedAt') + .groupBy('user.id') + .orderBy('acceptedCount', 'DESC') + .addOrderBy('lastAcceptedAt', 'ASC'); + + const data = await qb.getRawMany(); + return { data }; + } } From 3ac2c1159538d370b9d87dc959f9a63ca8bcd4df Mon Sep 17 00:00:00 2001 From: Daehyun Kim Date: Sat, 9 Mar 2024 02:16:35 +0900 Subject: [PATCH 2/2] filter out duplicate AC submissions --- server/src/entities/submission.entity.ts | 5 ++++- server/src/room/room.service.ts | 5 ++--- server/src/submission/submission.service.ts | 10 ++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/server/src/entities/submission.entity.ts b/server/src/entities/submission.entity.ts index ca49ce5..f0f4486 100644 --- a/server/src/entities/submission.entity.ts +++ b/server/src/entities/submission.entity.ts @@ -1,4 +1,3 @@ -import { Status } from '../const/boj-results'; import { BaseEntity, Column, @@ -6,6 +5,7 @@ import { ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; +import { Status } from '../const/boj-results'; import Problem from './problem.entity'; import Room from './room.entity'; import User from './user.entity'; @@ -37,4 +37,7 @@ export default class Submission extends BaseEntity { @ManyToOne(() => Problem, (problem) => problem.submissions, { cascade: true }) problem?: Problem; + + @Column() + alreadyAccepted!: boolean; } diff --git a/server/src/room/room.service.ts b/server/src/room/room.service.ts index 2833f95..82f2c8a 100644 --- a/server/src/room/room.service.ts +++ b/server/src/room/room.service.ts @@ -148,15 +148,14 @@ export class RoomService { .innerJoin( 'room.submissions', 'submission', - 'submission.status = :status AND submission.user_id = user.id', + 'submission.status = :status AND submission.user_id = user.id AND submission.alreadyAccepted = false', { status: Status.ACCEPTED, }, ) - .innerJoin('submission.problem', 'problem') .select('user.id', 'userId') .addSelect('user.username', 'username') - .addSelect('COUNT(DISTINCT submission.problem)', 'acceptedCount') + .addSelect('COUNT(submission.id)', 'acceptedCount') .addSelect('MAX(submission.submittedAt)', 'lastAcceptedAt') .groupBy('user.id') .orderBy('acceptedCount', 'DESC') diff --git a/server/src/submission/submission.service.ts b/server/src/submission/submission.service.ts index b181801..8129169 100644 --- a/server/src/submission/submission.service.ts +++ b/server/src/submission/submission.service.ts @@ -105,6 +105,15 @@ export class SubmissionService { status, ); + const alreadyAccepted = await this.submissionRepository.exist({ + where: { + user: { id: user.id }, + problem: { id: problem.id }, + room: { id: room.id }, + status: Status.ACCEPTED, + }, + }); + return await this.submissionRepository .create({ status, @@ -112,6 +121,7 @@ export class SubmissionService { room, problem, submittedAt, + alreadyAccepted, }) .save(); }