Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
Warnings:

- The primary key for the `assignment` table will be changed. If it partially fails, the table could be left without primary key constraint.

*/
-- DropForeignKey
ALTER TABLE "user_assignment" DROP CONSTRAINT "user_assignment_assignment_id_fkey";

-- AlterTable
ALTER TABLE "assignment" DROP CONSTRAINT "assignment_pkey",
ADD COLUMN "starts_at" TIMESTAMP(3),
ALTER COLUMN "id" SET DATA TYPE TEXT,
ALTER COLUMN "week" DROP NOT NULL,
ADD CONSTRAINT "assignment_pkey" PRIMARY KEY ("id");

-- AlterTable
ALTER TABLE "user_assignment" ALTER COLUMN "assignment_id" SET DATA TYPE TEXT;

-- AddForeignKey
ALTER TABLE "user_assignment" ADD CONSTRAINT "user_assignment_assignment_id_fkey" FOREIGN KEY ("assignment_id") REFERENCES "assignment"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
5 changes: 3 additions & 2 deletions prisma/schema/attendance/assignment.prisma
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
model Assignment {
id Int @id
id String @id @default(uuid())
courseId String @map("course_id")
courseOldId String? @map("course_old_id")
name String
week Int
week Int?
startsAt DateTime? @map("starts_at")
endsAt DateTime? @map("ends_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
Expand Down
2 changes: 1 addition & 1 deletion prisma/schema/attendance/user-assignment.prisma
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
model UserAssignment {
id Int @id @default(autoincrement())
studentId String @map("student_id")
assignmentId Int @map("assignment_id")
assignmentId String @map("assignment_id")
isDone Boolean @map("is_done")
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [studentId], references: [studentId])
Expand Down
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AppController } from './app.controller';
import { AppService } from './app.service';
import { configModule } from './modules/config.module';
import { BatchModule } from 'src/batch/batch.module';
import { AssignmentModule } from 'src/assignment/assignment.module';

@Module({
imports: [
Expand All @@ -25,6 +26,7 @@ import { BatchModule } from 'src/batch/batch.module';
GodokModule,
NoticeModule,
BatchModule,
AssignmentModule
],
controllers: [AppController],
providers: [AppService],
Expand Down
75 changes: 75 additions & 0 deletions src/assignment/assignment.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Body, Controller, Delete, Get, Param, Post, Put, UseGuards, Version } from "@nestjs/common";
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
import { JwtAuthGuard } from "src/auth/guard/jwt-auth.guard";
import { CreateUpdateAssignmentPayload } from "./payload/create-update-assignment.payload";
import { CurrentUser } from "src/auth/decorator/user.decorator";
import { UserInfo } from "src/auth/types/user-info.type";
import { AssignmentService } from "./assignment.service";
import { Assignment } from "@prisma/client";

@ApiTags("과제 API")
@Controller('assignment')
export class AssignmentController {
constructor(
private readonly assignmentService: AssignmentService,
) {}

@Version('1')
@ApiOperation({
summary: '과제 생성 API',
description: '과제를 생성합니다.',
})
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Post('')
async createAssignment(@CurrentUser() user: UserInfo, @Body() payload: CreateUpdateAssignmentPayload): Promise<void> {
console.log(payload)
return await this.assignmentService.createAssignment(user, payload);
}

@Version('1')
@ApiOperation({
summary: '과제 단일 조회 API',
description: '과제를 조회합니다.',
})
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':id')
async findAssignmentById(
@CurrentUser() user: UserInfo,
@Param('id') id: string,
): Promise<Assignment> {
return await this.assignmentService.findAssignmentById(user, id);
}

@Version('1')
@ApiOperation({
summary: '과제 수정 API',
description: '과제를 수정합니다.',
})
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Put(':id')
async updateAssignment(
@CurrentUser() user: UserInfo,
@Param('id') id: string,
@Body() payload: CreateUpdateAssignmentPayload,
): Promise<void> {
return await this.assignmentService.updateAssignment(user, id, payload);
}

@Version('1')
@ApiOperation({
summary: '과제 삭제 API',
description: '과제를 삭제합니다.',
})
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Delete(':id')
async deleteAssignment(
@CurrentUser() user: UserInfo,
@Param('id') id: string,
): Promise<void> {
return await this.assignmentService.deleteAssignment(user, id);
}
}
13 changes: 13 additions & 0 deletions src/assignment/assignment.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { AssignmentController } from './assignment.controller';
import { AssignmentRepository } from './assignment.repository';
import { AssignmentService } from './assignment.service';

@Module({
providers: [
AssignmentService,
AssignmentRepository,
],
controllers: [AssignmentController],
})
export class AssignmentModule {}
118 changes: 118 additions & 0 deletions src/assignment/assignment.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "src/common/services/prisma.service";
import { CreateUpdateAssignmentPayload } from "./payload/create-update-assignment.payload";
import { Assignment } from "@prisma/client";

@Injectable()
export class AssignmentRepository {
constructor(private readonly prisma: PrismaService) { }

async createAssignment(userId: string, payload: CreateUpdateAssignmentPayload): Promise<void> {
const course = await this.prisma.course.findFirst({
where: { id: payload.courseId },
});
if (!course) {
throw new NotFoundException(`해당 ID의 강의가 존재하지 않습니다.`);
}
const newAssignment = await this.prisma.assignment.create({
data: {
name: payload.name,
endsAt: payload.endsAt,
startsAt: payload.startsAt,
course: {
connect: {
id: payload.courseId
},
},
}
})

await this.prisma.userAssignment.create({
data: {
isDone: false,
studentId: userId,
assignmentId: newAssignment.id,
}
});
}

async findAssignmentById(userId: string, id: string): Promise<Assignment> {
const assignment = await this.prisma.assignment.findUnique({
where: { id },
});

if (!assignment) {
throw new NotFoundException(`해당 ID의 과제가 존재하지 않습니다.`);
}
const userAssignment = await this.prisma.userAssignment.findFirst({
where: {
studentId: userId,
assignmentId: id,
},
});

if (!userAssignment || userAssignment.studentId !== userId) {
throw new BadRequestException(`해당 과제를 조회할 권한이 없습니다.`);
}

return assignment;
}

async updateAssignment(userId: string, id: string, payload: CreateUpdateAssignmentPayload): Promise<void> {
const assignment = await this.prisma.assignment.findUnique({
where: { id },
});

if (!assignment) {
throw new NotFoundException(`해당 ID의 과제가 존재하지 않습니다.`);
}
const userAssignment = await this.prisma.userAssignment.findFirst({
where: {
studentId: userId,
assignmentId: id,
},
});
if (userAssignment.studentId !== userId) {
throw new BadRequestException(`해당 과제를 수정할 권한이 없습니다.`);
}


await this.prisma.assignment.update({
where: { id },
data: {
courseId: payload.courseId,
startsAt: payload.startsAt,
name: payload.name,
endsAt: payload.endsAt,
},
});
}

async deleteAssignment(userId: string, id: string): Promise<void> {
const assignment = await this.prisma.assignment.findUnique({
where: { id },
});

if (!assignment) {
throw new NotFoundException(`해당 ID의 과제가 존재하지 않습니다.`);
}
const userAssignment = await this.prisma.userAssignment.findFirst({
where: {
studentId: userId,
assignmentId: id,
},
});
if (!userAssignment || userAssignment.studentId !== userId) {
throw new BadRequestException(`해당 과제를 삭제할 권한이 없습니다.`);
}

await this.prisma.$transaction(async (tx) => {
await tx.userAssignment.delete({
where: { id: userAssignment.id },
});
await tx.assignment.delete({
where: { id },
});
});
}
}
28 changes: 28 additions & 0 deletions src/assignment/assignment.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Injectable } from "@nestjs/common";
import { CreateUpdateAssignmentPayload } from "./payload/create-update-assignment.payload";
import { UserInfo } from "src/auth/types/user-info.type";
import { AssignmentRepository } from "./assignment.repository";
import { Assignment } from "@prisma/client";

@Injectable()
export class AssignmentService {
constructor(
private readonly assignmentRepository: AssignmentRepository,
) {}

async createAssignment(user: UserInfo, payload: CreateUpdateAssignmentPayload): Promise<void> {
return await this.assignmentRepository.createAssignment(user.studentId, payload);
}

async findAssignmentById(user: UserInfo, id: string): Promise<Assignment> {
return await this.assignmentRepository.findAssignmentById(user.studentId, id);
}

async updateAssignment(user: UserInfo, id: string, payload: CreateUpdateAssignmentPayload): Promise<void> {
return await this.assignmentRepository.updateAssignment(user.studentId, id, payload);
}

async deleteAssignment(user: UserInfo, id: string): Promise<void> {
return await this.assignmentRepository.deleteAssignment(user.studentId, id);
}
}
39 changes: 39 additions & 0 deletions src/assignment/payload/create-update-assignment.payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsDate, IsString } from "class-validator";

export class CreateUpdateAssignmentPayload {
@ApiProperty({
description: '과제의 원본 강의 id',
example: '2a22cfb9-86b1-4279-8fa7-d7383ede0c3e',
type: String,
})
@IsString()
courseId?: string;

@ApiProperty({
description: '과제 제목',
example: 'Chapter 1 예제 풀이',
type: String,
})
@IsString()
name!: string;

@ApiProperty({
description: '과제 시작일',
example: '2024-01-31T10:13:09.004Z',
type: Date,
})
@Type(() => Date)
@IsDate()
startsAt!: Date;

@ApiProperty({
description: '과제 마감일',
example: '2024-02-28T10:13:09.004Z',
type: Date,
})
@Type(() => Date)
@IsDate()
endsAt!: Date;
}
1 change: 0 additions & 1 deletion src/attendance/attendance.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { EcampusService } from './ecampus.service';
EcampusService,
AttendanceService,
AttendanceRepository,
EcampusService,
],
controllers: [AttendanceController],
})
Expand Down
Loading