Skip to content

Commit d80f36d

Browse files
authored
Merge branch 'main' into t2240-modify-InviteRegsiterModal
2 parents 04cd886 + 6bcded1 commit d80f36d

42 files changed

Lines changed: 2783 additions & 2111 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CODE_OF_CONDUCT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ representative at an online or offline event.
6060

6161
Instances of abusive, harassing, or otherwise unacceptable behavior may be
6262
reported to the community leaders responsible for enforcement at
63-
skkucodingplatform@gmail.com.
63+
skkuding@gmail.com.
6464
All complaints will be reviewed and investigated promptly and fairly.
6565

6666
All community leaders are obligated to respect the privacy and security of the

apps/backend/apps/admin/src/contest/contest-problem.resolver.ts

Lines changed: 0 additions & 66 deletions
This file was deleted.

apps/backend/apps/admin/src/contest/contest-problem.service.ts

Lines changed: 191 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { Injectable } from '@nestjs/common'
2-
import type { ContestProblem } from '@prisma/client'
3-
import { UnprocessableDataException } from '@libs/exception'
2+
import { Prisma, type ContestProblem } from '@prisma/client'
3+
import { MAX_DATE, MIN_DATE } from '@libs/constants'
4+
import {
5+
EntityNotExistException,
6+
UnprocessableDataException
7+
} from '@libs/exception'
48
import { PrismaService } from '@libs/prisma'
59
import type { ProblemScoreInput } from './model/problem-score.input'
610

@@ -20,6 +24,191 @@ export class ContestProblemService {
2024
return contestProblems
2125
}
2226

27+
/**
28+
* 대회에 여러 문제를 일괄적으로 추가합니다.
29+
* 이미 추가된 문제는 건너뛰며, 'visibleLockTime'를 업데이트합니다.
30+
*
31+
* @param {number} contestId 대회 ID
32+
* @param {ProblemScoreInput[]} problemIdsWithScore 추가할 문제 ID와 배점
33+
* @returns 'ContestProblem' 정보
34+
*/
35+
async importProblemsToContest(
36+
contestId: number,
37+
problemIdsWithScore: ProblemScoreInput[]
38+
) {
39+
const [contest, maxOrderResult, existingProblems] = await Promise.all([
40+
this.prisma.contest.findUniqueOrThrow({
41+
where: { id: contestId },
42+
select: { endTime: true }
43+
}),
44+
this.prisma.contestProblem.aggregate({
45+
where: { contestId },
46+
// eslint-disable-next-line @typescript-eslint/naming-convention
47+
_max: { order: true }
48+
}),
49+
this.prisma.contestProblem.findMany({
50+
where: { contestId },
51+
select: { problemId: true }
52+
})
53+
])
54+
55+
let maxOrder = maxOrderResult._max?.order ?? -1
56+
const existingProblemIds = new Set(existingProblems.map((p) => p.problemId))
57+
58+
const contestProblems: ContestProblem[] = []
59+
60+
for (const { problemId, score } of problemIdsWithScore) {
61+
if (existingProblemIds.has(problemId)) {
62+
continue
63+
}
64+
65+
try {
66+
const [contestProblem] = await this.prisma.$transaction([
67+
this.prisma.contestProblem.create({
68+
data: {
69+
order: ++maxOrder,
70+
contestId,
71+
problemId,
72+
score
73+
}
74+
}),
75+
this.prisma.problem.updateMany({
76+
where: {
77+
id: problemId,
78+
OR: [
79+
{ visibleLockTime: { equals: MIN_DATE } },
80+
{ visibleLockTime: { equals: MAX_DATE } },
81+
{ visibleLockTime: { lte: contest.endTime } }
82+
]
83+
},
84+
data: {
85+
visibleLockTime: contest.endTime
86+
}
87+
})
88+
])
89+
contestProblems.push(contestProblem)
90+
} catch (error) {
91+
if (error instanceof Prisma.PrismaClientKnownRequestError) {
92+
if (error.code === 'P2003') {
93+
throw new EntityNotExistException(`Problem ${problemId}`)
94+
}
95+
}
96+
throw new UnprocessableDataException((error as Error).message)
97+
}
98+
}
99+
100+
return contestProblems
101+
}
102+
103+
/**
104+
* 대회에서 특정 문제들을 제거합니다.
105+
*
106+
* @param {number} contestId 대회 ID
107+
* @param {number[]} problemIds 제거할 문제 ID 배열
108+
* @returns 삭제된 `ContestProblem` 정보
109+
*/
110+
async removeProblemsFromContest(contestId: number, problemIds: number[]) {
111+
const contest = await this.prisma.contest.findUnique({
112+
where: {
113+
id: contestId
114+
},
115+
select: { endTime: true }
116+
})
117+
if (!contest) {
118+
throw new EntityNotExistException('Contest')
119+
}
120+
121+
const contestProblems: ContestProblem[] = []
122+
123+
for (const problemId of problemIds) {
124+
const [otherContestIds, removeContestProblem] = await Promise.all([
125+
this.prisma.contestProblem
126+
.findMany({
127+
where: {
128+
problemId,
129+
contestId: { not: contestId }
130+
},
131+
select: { contestId: true }
132+
})
133+
.then((cps) => cps.map((cp) => cp.contestId)),
134+
135+
this.prisma.contestProblem.findUniqueOrThrow({
136+
where: {
137+
// eslint-disable-next-line @typescript-eslint/naming-convention
138+
contestId_problemId: {
139+
contestId,
140+
problemId
141+
}
142+
},
143+
select: { order: true }
144+
})
145+
])
146+
147+
// 문제가 포함된 대회 중 가장 늦게 끝나는 대회의 종료시각으로 visibleLockTime 설정 (없을시 비공개 전환)
148+
let visibleLockTime = MAX_DATE
149+
150+
if (otherContestIds.length) {
151+
const latestContest = await this.prisma.contest.findFirst({
152+
where: {
153+
id: { in: otherContestIds }
154+
},
155+
orderBy: { endTime: 'desc' },
156+
select: { endTime: true }
157+
})
158+
visibleLockTime = latestContest!.endTime
159+
}
160+
161+
try {
162+
const [, contestProblem] = await this.prisma.$transaction([
163+
this.prisma.problem.updateMany({
164+
where: {
165+
id: problemId,
166+
visibleLockTime: {
167+
lte: contest.endTime
168+
}
169+
},
170+
data: {
171+
visibleLockTime
172+
}
173+
}),
174+
this.prisma.contestProblem.delete({
175+
where: {
176+
// eslint-disable-next-line @typescript-eslint/naming-convention
177+
contestId_problemId: {
178+
contestId,
179+
problemId
180+
}
181+
}
182+
}),
183+
this.prisma.contestProblem.updateMany({
184+
where: {
185+
contestId,
186+
order: {
187+
gt: removeContestProblem.order
188+
}
189+
},
190+
data: {
191+
order: {
192+
decrement: 1
193+
}
194+
}
195+
})
196+
])
197+
198+
contestProblems.push(contestProblem)
199+
} catch (error) {
200+
if (error instanceof Prisma.PrismaClientKnownRequestError) {
201+
if (error.code === 'P2025') {
202+
throw new EntityNotExistException('ContestProblem')
203+
}
204+
}
205+
throw new UnprocessableDataException((error as Error).message)
206+
}
207+
}
208+
209+
return contestProblems
210+
}
211+
23212
async updateContestProblemsScore(
24213
contestId: number,
25214
problemIdsWithScore: ProblemScoreInput[]

0 commit comments

Comments
 (0)