From 77c33347a0f8a4cf0c846c0383dbb4c5492f3690 Mon Sep 17 00:00:00 2001 From: Gyunseo Lee Date: Thu, 25 Jan 2024 14:21:32 +0900 Subject: [PATCH] feat(be): implement contest participant standings (#1233) * feat(be): implement participant rank * chore(be): add contest record seed script * fix(be): add rankings property when returning * test(be): add test code for service code * docs(be): fix bruno api docs * docs(be): fix bruno api docs * docs(be): fix bruno api docs * feat: add score field to contest record table to implement participant standings * fix: fix seed script * fix(be): fix service code * test(be): fix test code * docs(be): fix bruno api doc * docs(be): add login script for pre-request script * docs(be): fix pre request script --- .../src/contest/contest.service.spec.ts | 40 ++++++++++++++++-- .../client/src/contest/contest.service.ts | 42 ++++++++++++++++--- .../migration.sql | 2 + backend/prisma/schema.prisma | 1 + backend/prisma/seed.ts | 33 ++++++++++++++- .../Contest/Get contest by ID/Succeed.bru | 20 +++++++-- 6 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 backend/prisma/migrations/20240124143020_add_score_field_in_contest_record/migration.sql diff --git a/backend/apps/client/src/contest/contest.service.spec.ts b/backend/apps/client/src/contest/contest.service.spec.ts index 11a1878981..6b9ed6643c 100644 --- a/backend/apps/client/src/contest/contest.service.spec.ts +++ b/backend/apps/client/src/contest/contest.service.spec.ts @@ -183,10 +183,37 @@ const record: ContestRecord = { contestId, userId, acceptedProblemNum: 0, + score: 0, totalPenalty: 0, createTime: new Date(), updateTime: new Date() } +const sortedContestRecordsWithUserDetail = [ + { + user: { + id: 13, + username: 'user10' + }, + score: 36, + totalPenalty: 720 + }, + { + user: { + id: 12, + username: 'user09' + }, + score: 33, + totalPenalty: 660 + }, + { + user: { + id: 11, + username: 'user08' + }, + score: 30, + totalPenalty: 600 + } +] const mockPrismaService = { contest: { @@ -330,10 +357,17 @@ describe('ContestService', () => { it('should return contest', async () => { mockPrismaService.contest.findUniqueOrThrow.resolves(contestDetail) - - expect(await service.getContest(groupId, contestId)).to.deep.equal( - contestDetail + mockPrismaService.contestRecord.findMany.resolves( + sortedContestRecordsWithUserDetail ) + + expect(await service.getContest(groupId, contestId)).to.deep.equal({ + ...contestDetail, + standings: sortedContestRecordsWithUserDetail.map((record, index) => ({ + ...record, + standing: index + 1 + })) + }) }) }) diff --git a/backend/apps/client/src/contest/contest.service.ts b/backend/apps/client/src/contest/contest.service.ts index a8edb8168b..9e60c11e93 100644 --- a/backend/apps/client/src/contest/contest.service.ts +++ b/backend/apps/client/src/contest/contest.service.ts @@ -214,10 +214,7 @@ export class ContestService { return upcomingContest } - async getContest( - id: number, - groupId = OPEN_SPACE_ID - ): Promise> { + async getContest(id: number, groupId = OPEN_SPACE_ID) { const contest = await this.prisma.contest.findUniqueOrThrow({ where: { id, @@ -232,8 +229,43 @@ export class ContestService { description: true } }) + // get contest participants ranking using ContestRecord + const sortedContestRecordsWithUserDetail = + await this.prisma.contestRecord.findMany({ + where: { + contestId: id + }, + select: { + user: { + select: { + id: true, + username: true + } + }, + score: true, + totalPenalty: true + }, + orderBy: [ + { + score: 'desc' + }, + { + totalPenalty: 'asc' + } + ] + }) - return contest + const UsersWithStandingDetail = sortedContestRecordsWithUserDetail.map( + (contestRecord, index) => ({ + ...contestRecord, + standing: index + 1 + }) + ) + // combine contest and sortedContestRecordsWithUserDetail + return { + ...contest, + standings: UsersWithStandingDetail + } } async createContestRecord( diff --git a/backend/prisma/migrations/20240124143020_add_score_field_in_contest_record/migration.sql b/backend/prisma/migrations/20240124143020_add_score_field_in_contest_record/migration.sql new file mode 100644 index 0000000000..c1686b47ed --- /dev/null +++ b/backend/prisma/migrations/20240124143020_add_score_field_in_contest_record/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "contest_record" ADD COLUMN "score" INTEGER NOT NULL DEFAULT 0; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 2b91ae57d7..ead2fc66cb 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -295,6 +295,7 @@ model ContestRecord { user User? @relation(fields: [userId], references: [id], onDelete: SetNull) userId Int? @map("user_id") acceptedProblemNum Int @default(0) @map("accepted_problem_num") + score Int @default(0) totalPenalty Int @default(0) @map("total_penalty") createTime DateTime @default(now()) @map("create_time") updateTime DateTime @updatedAt @map("update_time") diff --git a/backend/prisma/seed.ts b/backend/prisma/seed.ts index f6cc3bb695..4833785c05 100644 --- a/backend/prisma/seed.ts +++ b/backend/prisma/seed.ts @@ -13,7 +13,8 @@ import { type Submission, type ProblemTestcase, type Announcement, - type CodeDraft + type CodeDraft, + ContestRecord } from '@prisma/client' import { hash } from 'argon2' import { readFile } from 'fs/promises' @@ -1562,6 +1563,35 @@ const createCodeDrafts = async () => { return codeDrafts } +const createContestRecords = async () => { + const contestRecords: ContestRecord[] = [] + let i = 0 + // group 1 users + const group1Users = await prisma.userGroup.findMany({ + where: { + groupId: 1 + } + }) + for (const user of group1Users) { + const contestRecord = await prisma.contestRecord.create({ + data: { + userId: user.userId, + contestId: 1, + acceptedProblemNum: user.userId, + // TODO: 아직 점수 계산 로직을 구현하지 않아서, + // 임시로 임의로 좀수와 페널티를 부여하도록 하였습니다. + // 점수 계산 로직을 구현하면 아래의 코드를 수정해주세요. + score: i < 3 ? 3 : i * 3, + totalPenalty: i * 60 + } + }) + contestRecords.push(contestRecord) + i++ + } + + return contestRecords +} + const main = async () => { await createUsers() await createGroups() @@ -1572,6 +1602,7 @@ const main = async () => { await createSubmissions() await createAnnouncements() await createCodeDrafts() + await createContestRecords() } main() diff --git a/collection/client/Contest/Get contest by ID/Succeed.bru b/collection/client/Contest/Get contest by ID/Succeed.bru index 7e8a4d55ee..733cac274a 100644 --- a/collection/client/Contest/Get contest by ID/Succeed.bru +++ b/collection/client/Contest/Get contest by ID/Succeed.bru @@ -5,13 +5,13 @@ meta { } get { - url: {{baseUrl}}/contest/13 + url: {{baseUrl}}/contest/1?groupId=1 body: none auth: none } query { - ~groupId: 2 + groupId: 1 } assert { @@ -24,12 +24,26 @@ assert { res("group.groupName"): isString res("description"): isString res("_count.contestRecord"): isNumber + res("standings[0].user.id"): isNumber + res("standings[0].user.username"): isString + res("standings[0].score"): isNumber + res("standings[0].totalPenalty"): isNumber + res("standings[0].standing"): isNumber +} + +script:pre-request { + await require("./login").loginUser(req); } docs { # Get Contest by ID - 하나의 대회 정보를 가져옵니다. + 하나의 대회 정보와 Contest 참여자 정보를 가져옵니다. + ## Path + + | 이름 | 타입 | 설명 | + |-----|-----|-----| + |id|Integer|Contest(대회) ID| ## Query