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-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..82f2c8a 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,36 @@ 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 AND submission.alreadyAccepted = false', + { + status: Status.ACCEPTED, + }, + ) + .select('user.id', 'userId') + .addSelect('user.username', 'username') + .addSelect('COUNT(submission.id)', 'acceptedCount') + .addSelect('MAX(submission.submittedAt)', 'lastAcceptedAt') + .groupBy('user.id') + .orderBy('acceptedCount', 'DESC') + .addOrderBy('lastAcceptedAt', 'ASC'); + + const data = await qb.getRawMany(); + return { data }; + } } 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(); }