diff --git a/apps/backend/apps/admin/src/contest/contest.resolver.ts b/apps/backend/apps/admin/src/contest/contest.resolver.ts index 8db19415fe..7a1c48d016 100644 --- a/apps/backend/apps/admin/src/contest/contest.resolver.ts +++ b/apps/backend/apps/admin/src/contest/contest.resolver.ts @@ -2,10 +2,8 @@ import { ParseBoolPipe } from '@nestjs/common' import { Args, Context, Int, Mutation, Query, Resolver } from '@nestjs/graphql' import { Contest, ContestProblem } from '@generated' import { AuthenticatedRequest, UseRolesGuard } from '@libs/auth' -import { OPEN_SPACE_ID } from '@libs/constants' import { CursorValidationPipe, - GroupIDPipe, IDValidationPipe, RequiredIntPipe } from '@libs/pipe' @@ -33,16 +31,10 @@ export class ContestResolver { new RequiredIntPipe('take') ) take: number, - @Args( - 'groupId', - { defaultValue: OPEN_SPACE_ID, type: () => Int }, - GroupIDPipe - ) - groupId: number, @Args('cursor', { nullable: true, type: () => Int }, CursorValidationPipe) cursor: number | null ) { - return await this.contestService.getContests(take, groupId, cursor) + return await this.contestService.getContests(take, cursor) } @Query(() => ContestWithParticipants) @@ -56,35 +48,25 @@ export class ContestResolver { @Mutation(() => Contest) async createContest( @Args('input') input: CreateContestInput, - @Args( - 'groupId', - { defaultValue: OPEN_SPACE_ID, type: () => Int }, - GroupIDPipe - ) - groupId: number, @Context('req') req: AuthenticatedRequest ) { - return await this.contestService.createContest(groupId, req.user.id, input) + return await this.contestService.createContest(req.user.id, input) } @Mutation(() => Contest) - async updateContest( - @Args('groupId', { type: () => Int }, GroupIDPipe) groupId: number, - @Args('input') input: UpdateContestInput - ) { - return await this.contestService.updateContest(groupId, input) + async updateContest(@Args('input') input: UpdateContestInput) { + return await this.contestService.updateContest(input) } @Mutation(() => Contest) async deleteContest( - @Args('groupId', { type: () => Int }, GroupIDPipe) groupId: number, @Args('contestId', { type: () => Int }) contestId: number ) { - return await this.contestService.deleteContest(groupId, contestId) + return await this.contestService.deleteContest(contestId) } /** - * Contest의 소속 Group을 Open Space(groupId === 1)로 이동시키기 위한 요청(Publicizing Requests)들을 불러옵니다. + * Contest를 공개(Open Space)로 이동시키기 위한 요청(Publicizing Requests)들을 불러옵니다. * @returns Publicizing Request 배열 */ @Query(() => [PublicizingRequest]) @@ -94,24 +76,19 @@ export class ContestResolver { } /** - * Contest의 소속 Group을 Open Space(groupId === 1)로 이동시키기 위한 요청(Publicizing Request)를 생성합니다. - * @param groupId Contest가 속한 Group의 ID. 이미 Open Space(groupId === 1)이 아니어야 합니다. + * Contest를 공개(Open Space)로 이동시키기 위한 요청(Publicizing Request)을 생성합니다. * @param contestId Contest의 ID * @returns 생성된 Publicizing Request */ @Mutation(() => PublicizingRequest) async createPublicizingRequest( - @Args('groupId', { type: () => Int }, GroupIDPipe) groupId: number, @Args('contestId', { type: () => Int }) contestId: number ) { - return await this.contestService.createPublicizingRequest( - groupId, - contestId - ) + return await this.contestService.createPublicizingRequest(contestId) } /** - * Contest의 소속 Group을 Open Space(groupId === 1)로 이동시키기 위한 요청(Publicizing Request)을 처리합니다. + * Contest를 공개(Open Space)로 이동시키기 위한 요청(Publicizing Request)을 처리합니다. * @param contestId Publicizing Request를 생성한 contest의 Id * @param isAccepted 요청 수락 여부 * @returns @@ -130,13 +107,11 @@ export class ContestResolver { @Mutation(() => [ContestProblem]) async importProblemsToContest( - @Args('groupId', { type: () => Int }, GroupIDPipe) groupId: number, @Args('contestId', { type: () => Int }) contestId: number, @Args('problemIdsWithScore', { type: () => [ProblemScoreInput] }) problemIdsWithScore: ProblemScoreInput[] ) { return await this.contestService.importProblemsToContest( - groupId, contestId, problemIdsWithScore ) @@ -144,13 +119,11 @@ export class ContestResolver { @Mutation(() => [ContestProblem]) async removeProblemsFromContest( - @Args('groupId', { type: () => Int }, GroupIDPipe) groupId: number, @Args('contestId', { type: () => Int }) contestId: number, @Args('problemIds', { type: () => [Int] }) problemIds: number[] ) { return await this.contestService.removeProblemsFromContest( - groupId, contestId, problemIds ) @@ -188,16 +161,11 @@ export class ContestResolver { @Mutation(() => DuplicatedContestResponse) async duplicateContest( - @Args('groupId', { type: () => Int }, GroupIDPipe) groupId: number, @Args('contestId', { type: () => Int }) contestId: number, @Context('req') req: AuthenticatedRequest ) { - return await this.contestService.duplicateContest( - groupId, - contestId, - req.user.id - ) + return await this.contestService.duplicateContest(contestId, req.user.id) } /** diff --git a/apps/backend/apps/admin/src/contest/contest.service.spec.ts b/apps/backend/apps/admin/src/contest/contest.service.spec.ts index 6b92bc7f3d..4f213e14fb 100644 --- a/apps/backend/apps/admin/src/contest/contest.service.spec.ts +++ b/apps/backend/apps/admin/src/contest/contest.service.spec.ts @@ -1,6 +1,6 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager' import { Test, type TestingModule } from '@nestjs/testing' -import { ContestProblem, Group, ContestRecord } from '@generated' +import { ContestProblem, ContestRecord } from '@generated' import { Problem } from '@generated' import { Contest } from '@generated' import { faker } from '@faker-js/faker' @@ -20,7 +20,6 @@ import type { PublicizingRequest } from './model/publicizing-request.model' const contestId = 1 const userId = 1 -const groupId = 1 const problemId = 2 const startTime = faker.date.past() const endTime = faker.date.future() @@ -36,7 +35,6 @@ const problemIdsWithScore = { const contest: Contest = { id: contestId, createdById: userId, - groupId, title: 'title', description: 'description', penalty: 20, @@ -63,7 +61,6 @@ const contest: Contest = { const contestWithCount = { id: contestId, createdById: userId, - groupId, title: 'title', description: 'description', penalty: 20, @@ -93,7 +90,6 @@ const contestWithCount = { const contestWithParticipants: ContestWithParticipants = { id: contestId, createdById: userId, - groupId, title: 'title', description: 'description', penalty: 20, @@ -118,20 +114,6 @@ const contestWithParticipants: ContestWithParticipants = { benefits: 'benefits' } -const group: Group = { - id: groupId, - groupName: 'groupName', - description: 'description', - config: { - showOnList: true, - allowJoinFromSearch: true, - allowJoinWithURL: false, - requireApprovalBeforeJoin: true - }, - createTime: faker.date.past(), - updateTime: faker.date.past() -} - const problem: Problem = { id: problemId, createdById: 2, @@ -257,9 +239,6 @@ const db = { updateMany: stub().resolves([Problem]), findFirstOrThrow: stub().resolves(Problem) }, - group: { - findUnique: stub().resolves(Group) - }, submission: { findMany: stub().resolves([submissionsWithProblemTitleAndUsername]) }, @@ -310,7 +289,7 @@ describe('ContestService', () => { it('should return an array of contests', async () => { db.contest.findMany.resolves([contestWithCount]) - const res = await service.getContests(5, 2, 0) + const res = await service.getContests(5, 0) expect(res).to.deep.equal([contestWithParticipants]) }) }) @@ -328,9 +307,8 @@ describe('ContestService', () => { describe('createContest', () => { it('should return created contest', async () => { db.contest.create.resolves(contest) - db.group.findUnique.resolves(group) - const res = await service.createContest(groupId, userId, input) + const res = await service.createContest(userId, input) expect(res).to.deep.equal(contest) }) }) @@ -340,15 +318,9 @@ describe('ContestService', () => { db.contest.findFirst.resolves(contest) db.contest.update.resolves(contest) - const res = await service.updateContest(groupId, updateInput) + const res = await service.updateContest(updateInput) expect(res).to.deep.equal(contest) }) - - it('should throw error when groupId or contestId not exist', async () => { - expect(service.updateContest(1000, updateInput)).to.be.rejectedWith( - EntityNotExistException - ) - }) }) describe('deleteContest', () => { @@ -356,12 +328,12 @@ describe('ContestService', () => { db.contest.findFirst.resolves(contest) db.contest.delete.resolves(contest) - const res = await service.deleteContest(groupId, contestId) + const res = await service.deleteContest(contestId) expect(res).to.deep.equal(contest) }) - it('should throw error when groupId or contestId not exist', async () => { - expect(service.deleteContest(1000, 1000)).to.be.rejectedWith( + it('should throw error when contestId not exist', async () => { + expect(service.deleteContest(1000)).to.be.rejectedWith( EntityNotExistException ) }) @@ -381,7 +353,7 @@ describe('ContestService', () => { }) }) - it('should throw error when groupId or contestId not exist', async () => { + it('should throw error when contestId not exist', async () => { expect(service.handlePublicizingRequest(1000, true)).to.be.rejectedWith( EntityNotExistException ) @@ -407,9 +379,7 @@ describe('ContestService', () => { db.contestProblem.findFirst.resolves(null) const res = await Promise.all( - await service.importProblemsToContest(groupId, contestId, [ - problemIdsWithScore - ]) + await service.importProblemsToContest(contestId, [problemIdsWithScore]) ) expect(res).to.deep.equal([contestProblem]) @@ -420,7 +390,7 @@ describe('ContestService', () => { db.problem.update.resolves(problem) db.contestProblem.findFirst.resolves(ContestProblem) - const res = await service.importProblemsToContest(groupId, contestId, [ + const res = await service.importProblemsToContest(contestId, [ problemIdsWithScore ]) @@ -429,7 +399,7 @@ describe('ContestService', () => { it('should throw error when the contestId not exist', async () => { expect( - service.importProblemsToContest(groupId, 9999, [problemIdsWithScore]) + service.importProblemsToContest(9999, [problemIdsWithScore]) ).to.be.rejectedWith(EntityNotExistException) }) }) diff --git a/apps/backend/apps/admin/src/contest/contest.service.ts b/apps/backend/apps/admin/src/contest/contest.service.ts index d2a8ad0583..d95bca4205 100644 --- a/apps/backend/apps/admin/src/contest/contest.service.ts +++ b/apps/backend/apps/admin/src/contest/contest.service.ts @@ -1,14 +1,9 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager' -import { - Inject, - Injectable, - UnprocessableEntityException -} from '@nestjs/common' +import { Inject, Injectable } from '@nestjs/common' import { Contest, ResultStatus, Submission } from '@generated' import type { ContestProblem } from '@prisma/client' import { Cache } from 'cache-manager' import { - OPEN_SPACE_ID, PUBLICIZING_REQUEST_EXPIRE_TIME, PUBLICIZING_REQUEST_KEY, MIN_DATE, @@ -34,12 +29,11 @@ export class ContestService { @Inject(CACHE_MANAGER) private readonly cacheManager: Cache ) {} - async getContests(take: number, groupId: number, cursor: number | null) { + async getContests(take: number, cursor: number | null) { const paginator = this.prisma.getPaginator(cursor) const contests = await this.prisma.contest.findMany({ ...paginator, - where: { groupId }, take, include: { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -84,7 +78,6 @@ export class ContestService { } async createContest( - groupId: number, userId: number, contest: CreateContestInput ): Promise { @@ -94,20 +87,10 @@ export class ContestService { ) } - const group = await this.prisma.group.findUnique({ - where: { - id: groupId - } - }) - if (!group) { - throw new EntityNotExistException('Group') - } - try { return await this.prisma.contest.create({ data: { createdById: userId, - groupId, ...contest } }) @@ -116,14 +99,10 @@ export class ContestService { } } - async updateContest( - groupId: number, - contest: UpdateContestInput - ): Promise { + async updateContest(contest: UpdateContestInput): Promise { const contestFound = await this.prisma.contest.findFirst({ where: { - id: contest.id, - groupId + id: contest.id }, select: { startTime: true, @@ -214,11 +193,10 @@ export class ContestService { } } - async deleteContest(groupId: number, contestId: number) { + async deleteContest(contestId: number) { const contest = await this.prisma.contest.findFirst({ where: { - id: contestId, - groupId + id: contestId }, select: { contestProblem: { @@ -236,7 +214,7 @@ export class ContestService { (problem) => problem.problemId ) if (problemIds.length) { - await this.removeProblemsFromContest(groupId, contestId, problemIds) + await this.removeProblemsFromContest(contestId, problemIds) } try { @@ -299,9 +277,7 @@ export class ContestService { where: { id: contestId }, - data: { - groupId: OPEN_SPACE_ID - } + data: {} }) } catch (error) { throw new UnprocessableDataException(error.message) @@ -314,17 +290,10 @@ export class ContestService { } as PublicizingResponse } - async createPublicizingRequest(groupId: number, contestId: number) { - if (groupId == OPEN_SPACE_ID) { - throw new UnprocessableEntityException( - 'This contest is already publicized' - ) - } - + async createPublicizingRequest(contestId: number) { const contest = await this.prisma.contest.findFirst({ where: { - id: contestId, - groupId + id: contestId } }) if (!contest) { @@ -360,14 +329,12 @@ export class ContestService { } async importProblemsToContest( - groupId: number, contestId: number, problemIdsWithScore: ProblemScoreInput[] ) { const contest = await this.prisma.contest.findUnique({ where: { - id: contestId, - groupId + id: contestId }, include: { submission: { @@ -442,15 +409,10 @@ export class ContestService { return contestProblems } - async removeProblemsFromContest( - groupId: number, - contestId: number, - problemIds: number[] - ) { + async removeProblemsFromContest(contestId: number, problemIds: number[]) { const contest = await this.prisma.contest.findUnique({ where: { - id: contestId, - groupId + id: contestId }, include: { submission: { @@ -608,18 +570,16 @@ export class ContestService { /** * Duplicate contest with contest problems and users who participated in the contest * Not copied: submission - * @param groupId group to duplicate contest * @param contestId contest to duplicate * @param userId user who tries to duplicates the contest * @returns */ - async duplicateContest(groupId: number, contestId: number, userId: number) { + async duplicateContest(contestId: number, userId: number) { const [contestFound, contestProblemsFound, userContestRecords] = await Promise.all([ this.prisma.contest.findFirst({ where: { - id: contestId, - groupId + id: contestId } }), this.prisma.contestProblem.findMany({ @@ -658,7 +618,6 @@ export class ContestService { ...contestDataToCopy, title: 'Copy of ' + title, createdById: userId, - groupId, isVisible: newVisible } }) diff --git a/apps/backend/apps/admin/src/problem/mock/mock.ts b/apps/backend/apps/admin/src/problem/mock/mock.ts index a39bd61adc..c0d644672e 100644 --- a/apps/backend/apps/admin/src/problem/mock/mock.ts +++ b/apps/backend/apps/admin/src/problem/mock/mock.ts @@ -375,7 +375,6 @@ export const exampleContest: Contest = { description: 'example', penalty: 20, lastPenalty: false, - groupId: 1, createdById: 1, isVisible: true, isRankVisible: true, diff --git a/apps/backend/apps/admin/src/problem/problem.service.ts b/apps/backend/apps/admin/src/problem/problem.service.ts index 0043ff1e55..9063536f5b 100644 --- a/apps/backend/apps/admin/src/problem/problem.service.ts +++ b/apps/backend/apps/admin/src/problem/problem.service.ts @@ -605,7 +605,7 @@ export class ProblemService { contestId: number ): Promise[]> { await this.prisma.contest.findFirstOrThrow({ - where: { id: contestId, groupId } + where: { id: contestId } }) const contestProblems = await this.prisma.contestProblem.findMany({ where: { contestId } @@ -619,7 +619,7 @@ export class ProblemService { problemIdsWithScore: ProblemScoreInput[] ): Promise[]> { await this.prisma.contest.findFirstOrThrow({ - where: { id: contestId, groupId } + where: { id: contestId } }) const queries = problemIdsWithScore.map((record) => { @@ -644,7 +644,7 @@ export class ProblemService { orders: number[] ): Promise[]> { await this.prisma.contest.findFirstOrThrow({ - where: { id: contestId, groupId } + where: { id: contestId } }) const contestProblems = await this.prisma.contestProblem.findMany({ diff --git a/apps/backend/apps/client/src/announcement/announcement.controller.ts b/apps/backend/apps/client/src/announcement/announcement.controller.ts index 82038e4885..9bf96ba0dc 100644 --- a/apps/backend/apps/client/src/announcement/announcement.controller.ts +++ b/apps/backend/apps/client/src/announcement/announcement.controller.ts @@ -44,8 +44,7 @@ export class AnnouncementController { } else { if (contestId) { return await this.announcementService.getContestAnnouncements( - contestId, - groupId + contestId ) } else { return await this.announcementService.getAssignmentAnnouncements( diff --git a/apps/backend/apps/client/src/announcement/announcement.service.spec.ts b/apps/backend/apps/client/src/announcement/announcement.service.spec.ts index be12cf43fb..7c0102f1c6 100644 --- a/apps/backend/apps/client/src/announcement/announcement.service.spec.ts +++ b/apps/backend/apps/client/src/announcement/announcement.service.spec.ts @@ -70,7 +70,7 @@ describe('AnnouncementService', () => { describe('getContestAnnouncements', () => { it('should return multiple contest announcements', async () => { - const res = await service.getContestAnnouncements(1, 1) + const res = await service.getContestAnnouncements(1) expect(res) .excluding(['createTime', 'updateTime', 'content']) .to.deep.equal([ diff --git a/apps/backend/apps/client/src/announcement/announcement.service.ts b/apps/backend/apps/client/src/announcement/announcement.service.ts index 3f4818c707..679c8c3e1c 100644 --- a/apps/backend/apps/client/src/announcement/announcement.service.ts +++ b/apps/backend/apps/client/src/announcement/announcement.service.ts @@ -6,15 +6,11 @@ import { PrismaService } from '@libs/prisma' export class AnnouncementService { constructor(private readonly prisma: PrismaService) {} - async getContestAnnouncements( - contestId: number, - groupId: number - ): Promise { + async getContestAnnouncements(contestId: number): Promise { const { contestProblem, announcement } = await this.prisma.contest.findUniqueOrThrow({ where: { - id: contestId, - groupId + id: contestId }, select: { contestProblem: true, @@ -73,8 +69,7 @@ export class AnnouncementService { where: { problemId, contest: { - id: contestId, - groupId + id: contestId } }, orderBy: { updateTime: 'desc' } diff --git a/apps/backend/apps/client/src/contest/contest.controller.ts b/apps/backend/apps/client/src/contest/contest.controller.ts index b6c1e55ff4..c457b9614f 100644 --- a/apps/backend/apps/client/src/contest/contest.controller.ts +++ b/apps/backend/apps/client/src/contest/contest.controller.ts @@ -12,7 +12,7 @@ import { AuthNotNeededIfOpenSpace, UserNullWhenAuthFailedIfOpenSpace } from '@libs/auth' -import { GroupIDPipe, IDValidationPipe, RequiredIntPipe } from '@libs/pipe' +import { IDValidationPipe, RequiredIntPipe } from '@libs/pipe' import { ContestService } from './contest.service' @Controller('contest') @@ -37,24 +37,21 @@ export class ContestController { @UserNullWhenAuthFailedIfOpenSpace() async getContest( @Req() req: AuthenticatedRequest, - @Query('groupId', GroupIDPipe) groupId: number, @Param('id', new RequiredIntPipe('id')) id: number ) { - return await this.contestService.getContest(id, groupId, req.user?.id) + return await this.contestService.getContest(id, req.user?.id) } @Post(':id/participation') async createContestRecord( @Req() req: AuthenticatedRequest, - @Query('groupId', GroupIDPipe) groupId: number, @Param('id', IDValidationPipe) contestId: number, @Query('invitationCode') invitationCode?: string ) { return await this.contestService.createContestRecord({ contestId, userId: req.user.id, - invitationCode, - groupId + invitationCode }) } @@ -62,14 +59,9 @@ export class ContestController { @Delete(':id/participation') async deleteContestRecord( @Req() req: AuthenticatedRequest, - @Query('groupId', GroupIDPipe) groupId: number, @Param('id', IDValidationPipe) contestId: number ) { - return await this.contestService.deleteContestRecord( - contestId, - req.user.id, - groupId - ) + return await this.contestService.deleteContestRecord(contestId, req.user.id) } @Get(':id/leaderboard') diff --git a/apps/backend/apps/client/src/contest/contest.service.spec.ts b/apps/backend/apps/client/src/contest/contest.service.spec.ts index bd14c24e98..213ba0473f 100644 --- a/apps/backend/apps/client/src/contest/contest.service.spec.ts +++ b/apps/backend/apps/client/src/contest/contest.service.spec.ts @@ -1,12 +1,7 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager' import { ConfigService } from '@nestjs/config' import { Test, type TestingModule } from '@nestjs/testing' -import { - Prisma, - type Contest, - type Group, - type ContestRecord -} from '@prisma/client' +import { Prisma, type Contest, type ContestRecord } from '@prisma/client' import { expect } from 'chai' import * as dayjs from 'dayjs' import { @@ -23,14 +18,12 @@ import { ContestService, type ContestResult } from './contest.service' const contestId = 1 const user01Id = 4 -const groupId = 1 const now = dayjs() const contest = { id: contestId, createdById: 1, - groupId, title: 'title', description: 'description', penalty: 100, @@ -44,10 +37,6 @@ const contest = { enableCopyPaste: true, createTime: now.add(-1, 'day').toDate(), updateTime: now.add(-1, 'day').toDate(), - group: { - id: groupId, - groupName: 'group' - }, posterUrl: 'posterUrl', participationTarget: 'participationTarget', competitionMethod: 'competitionMethod', @@ -55,14 +44,11 @@ const contest = { problemFormat: 'problemFormat', benefits: 'benefits', invitationCode: '123456' -} satisfies Contest & { - group: Partial -} +} satisfies Contest const ongoingContests = [ { id: contest.id, - group: contest.group, title: contest.title, posterUrl: contest.posterUrl, participationTarget: contest.participationTarget, @@ -83,7 +69,6 @@ const ongoingContests = [ const upcomingContests = [ { id: contest.id + 6, - group: contest.group, title: contest.title, posterUrl: null, participationTarget: null, @@ -104,7 +89,6 @@ const upcomingContests = [ const finishedContests = [ { id: contest.id + 1, - group: contest.group, title: contest.title, posterUrl: contest.posterUrl, participationTarget: contest.participationTarget, @@ -122,12 +106,6 @@ const finishedContests = [ } ] satisfies Partial[] -const contests = [ - ...ongoingContests, - ...finishedContests, - ...upcomingContests -] satisfies Partial[] - describe('ContestService', () => { let service: ContestService let prisma: PrismaTestService @@ -183,7 +161,7 @@ describe('ContestService', () => { }) it('a contest should contain following fields when userId is undefined', async () => { - const contests = await service.getContests(groupId) + const contests = await service.getContests() expect(contests.ongoing[0]).to.have.property('title') expect(contests.ongoing[0]).to.have.property('startTime') expect(contests.ongoing[0]).to.have.property('endTime') @@ -223,13 +201,13 @@ describe('ContestService', () => { describe('getContest', () => { it('should throw error when contest does not exist', async () => { - await expect( - service.getContest(999, groupId, user01Id) - ).to.be.rejectedWith(EntityNotExistException) + await expect(service.getContest(999, user01Id)).to.be.rejectedWith( + EntityNotExistException + ) }) it('should return contest', async () => { - expect(await service.getContest(contestId, groupId, user01Id)).to.be.ok + expect(await service.getContest(contestId, user01Id)).to.be.ok }) it('should return optional fields if they exist', async () => { @@ -242,7 +220,7 @@ describe('ContestService', () => { }) it('should return prev and next contest information', async () => { - const contest = await service.getContest(contestId, groupId, user01Id) + const contest = await service.getContest(contestId, user01Id) if (contest.prev) { expect(contest.prev).to.have.property('id') expect(contest.prev.id).to.be.lessThan(contestId) @@ -284,9 +262,9 @@ describe('ContestService', () => { it('should throw error when user is participated in contest again', async () => { await expect( service.createContestRecord({ - contestId: contestId, + contestId, userId: user01Id, - invitationCode: invitationCode + invitationCode }) ).to.be.rejectedWith(ConflictFoundException) }) @@ -296,7 +274,7 @@ describe('ContestService', () => { service.createContestRecord({ contestId: 8, userId: user01Id, - invitationCode: invitationCode + invitationCode }) ).to.be.rejectedWith(ConflictFoundException) }) @@ -305,7 +283,7 @@ describe('ContestService', () => { const contestRecord = await service.createContestRecord({ contestId: 2, userId: user01Id, - invitationCode: invitationCode + invitationCode }) contestRecordId = contestRecord.id expect( diff --git a/apps/backend/apps/client/src/contest/contest.service.ts b/apps/backend/apps/client/src/contest/contest.service.ts index bf8c512962..cd9783acd2 100644 --- a/apps/backend/apps/client/src/contest/contest.service.ts +++ b/apps/backend/apps/client/src/contest/contest.service.ts @@ -1,6 +1,5 @@ import { Injectable } from '@nestjs/common' import { Prisma, Role, type Contest } from '@prisma/client' -import { OPEN_SPACE_ID } from '@libs/constants' import { ConflictFoundException, EntityNotExistException, @@ -13,7 +12,6 @@ const contestSelectOption = { title: true, startTime: true, endTime: true, - group: { select: { id: true, groupName: true } }, contestRecord: { select: { userId: true @@ -181,7 +179,7 @@ export class ContestService { } } - async getContest(id: number, groupId = OPEN_SPACE_ID, userId?: number) { + async getContest(id: number, userId?: number) { // check if the user has already registered this contest // initial value is false let isRegistered = false @@ -198,7 +196,6 @@ export class ContestService { contest = await this.prisma.contest.findUniqueOrThrow({ where: { id, - groupId, isVisible: true }, select: { @@ -263,7 +260,6 @@ export class ContestService { return { where: { id: options.compare, - groupId, isVisible: true }, orderBy: { @@ -288,20 +284,19 @@ export class ContestService { async createContestRecord({ contestId, userId, - invitationCode, - groupId = OPEN_SPACE_ID + invitationCode }: { contestId: number userId: number invitationCode?: string - groupId?: number }) { const contest = await this.prisma.contest.findUniqueOrThrow({ - where: { id: contestId, groupId }, + where: { + id: contestId + }, select: { startTime: true, endTime: true, - groupId: true, invitationCode: true } }) @@ -326,27 +321,27 @@ export class ContestService { }) } - async isVisible(contestId: number, groupId: number): Promise { + async isVisible(contestId: number): Promise { return !!(await this.prisma.contest.count({ where: { id: contestId, - isVisible: true, - groupId + isVisible: true } })) } - async deleteContestRecord( - contestId: number, - userId: number, - groupId = OPEN_SPACE_ID - ) { + async deleteContestRecord(contestId: number, userId: number) { const [contest, contestRecord] = await Promise.all([ this.prisma.contest.findUnique({ - where: { id: contestId, groupId } + where: { + id: contestId + } }), this.prisma.contestRecord.findFirst({ - where: { userId, contestId } + where: { + userId, + contestId + } }) ]) diff --git a/apps/backend/apps/client/src/problem/problem.service.ts b/apps/backend/apps/client/src/problem/problem.service.ts index 2b135d7d73..da1650c756 100644 --- a/apps/backend/apps/client/src/problem/problem.service.ts +++ b/apps/backend/apps/client/src/problem/problem.service.ts @@ -284,11 +284,7 @@ export class ContestProblemService { take: number groupId: number }) { - const contest = await this.contestService.getContest( - contestId, - groupId, - userId - ) + const contest = await this.contestService.getContest(contestId, userId) const now = new Date() if (contest.isRegistered && contest.startTime! > now) { throw new ForbiddenAccessException( @@ -414,11 +410,7 @@ export class ContestProblemService { userId: number groupId: number }) { - const contest = await this.contestService.getContest( - contestId, - groupId, - userId - ) + const contest = await this.contestService.getContest(contestId, userId) const now = new Date() if (contest.isRegistered) { if (now < contest.startTime!) { diff --git a/apps/backend/apps/client/src/submission/submission.controller.ts b/apps/backend/apps/client/src/submission/submission.controller.ts index a955b56ea8..70393dde86 100644 --- a/apps/backend/apps/client/src/submission/submission.controller.ts +++ b/apps/backend/apps/client/src/submission/submission.controller.ts @@ -67,8 +67,7 @@ export class SubmissionController { userIp, userId: req.user.id, problemId, - contestId, - groupId + contestId }) } else if (assignmentId) { return await this.submissionService.submitToAssignment({ diff --git a/apps/backend/apps/client/src/submission/submission.service.ts b/apps/backend/apps/client/src/submission/submission.service.ts index 2abd8dc7ca..178633fcbe 100644 --- a/apps/backend/apps/client/src/submission/submission.service.ts +++ b/apps/backend/apps/client/src/submission/submission.service.ts @@ -143,15 +143,13 @@ export class SubmissionService { userIp, userId, problemId, - contestId, - groupId = OPEN_SPACE_ID + contestId }: { submissionDto: CreateSubmissionDto userIp: string userId: number problemId: number contestId: number - groupId: number }) { const now = new Date() @@ -159,7 +157,6 @@ export class SubmissionService { const contest = await this.prisma.contest.findFirst({ where: { id: contestId, - groupId, startTime: { lte: now }, @@ -184,7 +181,6 @@ export class SubmissionService { select: { contest: { select: { - groupId: true, startTime: true, endTime: true } @@ -194,9 +190,7 @@ export class SubmissionService { if (!contestRecord) { throw new EntityNotExistException('ContestRecord') } - if (contestRecord.contest.groupId !== groupId) { - throw new EntityNotExistException('Contest') - } else if ( + if ( contestRecord.contest.startTime > now || contestRecord.contest.endTime <= now ) { @@ -957,7 +951,6 @@ export class SubmissionService { }) { const now = new Date() let contest: { - groupId: number startTime: Date endTime: Date isJudgeResultVisible: boolean @@ -982,7 +975,6 @@ export class SubmissionService { select: { contest: { select: { - groupId: true, startTime: true, endTime: true, isJudgeResultVisible: true @@ -993,9 +985,6 @@ export class SubmissionService { if (!contestRecord) { throw new EntityNotExistException('ContestRecord') } - if (contestRecord.contest.groupId !== groupId) { - throw new EntityNotExistException('Contest') - } contest = contestRecord.contest isJudgeResultVisible = contest.isJudgeResultVisible } else if (assignmentId) { diff --git a/apps/backend/apps/client/src/submission/test/submission.service.spec.ts b/apps/backend/apps/client/src/submission/test/submission.service.spec.ts index 2a5e1d3158..591438724a 100644 --- a/apps/backend/apps/client/src/submission/test/submission.service.spec.ts +++ b/apps/backend/apps/client/src/submission/test/submission.service.spec.ts @@ -84,7 +84,6 @@ const WORKBOOK_ID = 1 const mockContest: Contest = { id: CONTEST_ID, createdById: 1, - groupId: 1, title: 'SKKU Coding Platform 모의대회', description: 'test', penalty: 20, @@ -246,8 +245,7 @@ describe('SubmissionService', () => { userIp: USERIP, userId: submissions[0].userId, problemId: problems[0].id, - contestId: CONTEST_ID, - groupId: problems[0].groupId + contestId: CONTEST_ID }) expect(createSpy.calledOnce).to.be.true }) @@ -262,8 +260,7 @@ describe('SubmissionService', () => { userIp: USERIP, userId: submissions[0].userId, problemId: problems[0].id, - contestId: CONTEST_ID, - groupId: problems[0].groupId + contestId: CONTEST_ID }) ).to.be.rejectedWith(EntityNotExistException) expect(createSpy.called).to.be.false diff --git a/apps/backend/prisma/migrations/20250130102040_delete_group_from_contest/migration.sql b/apps/backend/prisma/migrations/20250130102040_delete_group_from_contest/migration.sql new file mode 100644 index 0000000000..a80ac62fe1 --- /dev/null +++ b/apps/backend/prisma/migrations/20250130102040_delete_group_from_contest/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `group_id` on the `contest` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "contest" DROP CONSTRAINT "contest_group_id_fkey"; + +-- AlterTable +ALTER TABLE "contest" DROP COLUMN "group_id"; diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma index 96814a6c2b..e8c59263a9 100644 --- a/apps/backend/prisma/schema.prisma +++ b/apps/backend/prisma/schema.prisma @@ -122,7 +122,6 @@ model Group { problem Problem[] assignment Assignment[] workbook Workbook[] - contest Contest[] @@map("group") } @@ -327,11 +326,9 @@ model AssignmentRecord { } model Contest { - id Int @id @default(autoincrement()) - createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull) - createdById Int? @map("created_by_id") - group Group @relation(fields: [groupId], references: [id]) - groupId Int @map("group_id") + id Int @id @default(autoincrement()) + createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull) + createdById Int? @map("created_by_id") title String description String // 대회의 페널티 (0 ≤ penalty ≤ 100), diff --git a/apps/backend/prisma/seed.ts b/apps/backend/prisma/seed.ts index 09b1219c02..d277373470 100644 --- a/apps/backend/prisma/seed.ts +++ b/apps/backend/prisma/seed.ts @@ -958,7 +958,6 @@ const createContests = async () => { title: string description: string createdById: number - groupId: number posterUrl: string | null participationTarget: string | null competitionMethod: string | null @@ -1008,7 +1007,6 @@ const createContests = async () => { 아니하고는 처벌·보안처분 또는 강제노역을 받지 아니한다.

`, createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: `https://skkuding.dev/open-graph.png`, participationTarget: '성균관대 재학생이라면 누구나', competitionMethod: '온라인으로 진행', @@ -1028,7 +1026,6 @@ const createContests = async () => { title: '24년도 소프트웨어학과 신입생 입학 테스트1', description: '

이 대회는 현재 진행 중입니다 !

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '성균관대학교 24학번 신입생', competitionMethod: '강의실에서 오프라인으로 진행', @@ -1048,7 +1045,6 @@ const createContests = async () => { title: '24년도 소프트웨어학과 신입생 입학 테스트2', description: '

이 대회는 현재 진행 중입니다 !

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: `https://skkuding.dev/open-graph.png`, participationTarget: '성균관대학교 24학번 신입생', competitionMethod: '강의실에서 오프라인으로 진행', @@ -1068,7 +1064,6 @@ const createContests = async () => { title: '24년도 소프트웨어학과 신입생 입학 테스트3', description: '

이 대회는 현재 진행 중입니다 !

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: `https://skkuding.dev/open-graph.png`, participationTarget: '성균관대학교 24학번 신입생', competitionMethod: '강의실에서 오프라인으로 진행', @@ -1088,7 +1083,6 @@ const createContests = async () => { title: '24년도 아늑배 스파게티 코드 만들기 대회', description: '

이 대회는 현재 진행 중입니다 ! (private group)

', createdById: superAdminUser.id, - groupId: privateGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '삼성학술정보관 지하1층에서 오프라인 진행', @@ -1109,7 +1103,6 @@ const createContests = async () => { title: 'Long Time Ago Assignment', description: '

이 대회는 오래 전에 끝났어요

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공생', competitionMethod: '온라인 진행', @@ -1129,7 +1122,6 @@ const createContests = async () => { title: '23년도 소프트웨어학과 신입생 입학 테스트', description: '

이 대회는 오래 전에 끝났어요

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 23학번', competitionMethod: '온라인 진행', @@ -1149,7 +1141,6 @@ const createContests = async () => { title: '소프트의 아침', description: '

이 대회는 오래 전에 끝났어요

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행', @@ -1169,7 +1160,6 @@ const createContests = async () => { title: '소프트의 낮', description: '

이 대회는 오래 전에 끝났어요

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행', @@ -1189,7 +1179,6 @@ const createContests = async () => { title: '소프트의 밤', description: '

이 대회는 오래 전에 끝났어요

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행', @@ -1209,7 +1198,6 @@ const createContests = async () => { title: '2023 SKKU 프로그래밍 대회', description: '

이 대회는 오래 전에 끝났어요

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행', @@ -1229,7 +1217,6 @@ const createContests = async () => { title: '소프트의 오전', description: '

이 대회는 오래 전에 끝났어요

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행', @@ -1249,7 +1236,6 @@ const createContests = async () => { title: '소프트의 오후', description: '

이 대회는 오래 전에 끝났어요

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행', @@ -1269,7 +1255,6 @@ const createContests = async () => { title: '23년도 아늑배 스파게티 코드 만들기 대회', description: '

이 대회는 오래 전에 끝났어요 (private group)

', createdById: superAdminUser.id, - groupId: privateGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행', @@ -1290,7 +1275,6 @@ const createContests = async () => { title: 'Future Assignment', description: '

이 대회는 언젠가 열리겠죠...?

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행 예정...?', @@ -1310,7 +1294,6 @@ const createContests = async () => { title: '2024 SKKU 프로그래밍 대회', description: '

이 대회는 언젠가 열리겠죠...?

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행 예정...?', @@ -1331,7 +1314,6 @@ const createContests = async () => { description: '

이 대회는 언젠가 열리겠죠...? isVisible이 false인 assignment입니다

', createdById: superAdminUser.id, - groupId: publicGroup.id, posterUrl: `https://skkuding.dev/open-graph.png`, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행 예정...?', @@ -1351,7 +1333,6 @@ const createContests = async () => { title: '25년도 아늑배 스파게티 코드 만들기 대회', description: '

이 대회는 언젠가 열리겠죠...? (private group)

', createdById: superAdminUser.id, - groupId: privateGroup.id, posterUrl: null, participationTarget: '소프트웨어학과 원전공/복수전공', competitionMethod: '온라인 진행 예정...?', @@ -2396,39 +2377,58 @@ const createAssignmentRecords = async () => { const createContestRecords = async () => { const contestRecords: ContestRecord[] = [] - // 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, + // all users + const users = await prisma.user.findMany() + for (const user of users) { + const existingRecord = await prisma.contestRecord.findFirst({ + where: { contestId: 1, - acceptedProblemNum: 0, - totalPenalty: 0 + userId: user.id } }) - contestRecords.push(contestRecord) - } - - // upcoming contest에 참가한 User 1의 contest register를 un-register하는 기능과, - // registered upcoming, ongoing, finished contest를 조회하는 기능을 확인하기 위함 - const user01Id = 4 - for (let contestId = 3; contestId <= contests.length; contestId += 2) { - contestRecords.push( - await prisma.contestRecord.create({ + if (!existingRecord) { + const contestRecord = await prisma.contestRecord.create({ data: { - userId: user01Id, - contestId, + userId: user.id, + contestId: 1, acceptedProblemNum: 0, - score: 0, totalPenalty: 0 } }) - ) + contestRecords.push(contestRecord) + } + } + + // upcoming contest에 참가한 User 1의 contest register를 un-register하는 기능과, + // registered upcoming, ongoing, finished contest를 조회하는 기능을 확인하기 위함 + const contests = await prisma.contest.findMany({ + select: { + id: true + } + }) + const user01Id = 4 + for (let i = 0; i < contests.length; i += 2) { + const contestId = contests[i].id + // eslint-disable-next-line @typescript-eslint/naming-convention + const existingRecord = await prisma.contestRecord.findFirst({ + where: { + contestId: contestId, + userId: user01Id + } + }) + if (!existingRecord) { + contestRecords.push( + await prisma.contestRecord.create({ + data: { + userId: user01Id, + contestId, + acceptedProblemNum: 0, + score: 0, + totalPenalty: 0 + } + }) + ) + } } return contestRecords