diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e687efd..f8ce7e6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -9,25 +9,27 @@ datasource db { /// 사용자 model User { - id Int @id @default(autoincrement()) - loginId String @unique @db.VarChar(255) - password String @db.VarChar(255) - name String @db.VarChar(255) - email String? @unique @db.VarChar(255) - phone String? @db.VarChar(255) - type UserType - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - deletedAt DateTime? - deptId Int? - signId String? @unique - headReviewProcesses Process[] @relation("headReviewer") - studentProcess Process? @relation("student") - reviews Review[] - reviewers Reviewer[] - department Department? @relation(fields: [deptId], references: [id], onDelete: SetNull) - signFile File? @relation(fields: [signId], references: [uuid]) - Achievements Achievements[] + id Int @id @default(autoincrement()) + loginId String @unique @db.VarChar(255) + password String @db.VarChar(255) + name String @db.VarChar(255) + email String? @unique @db.VarChar(255) + phone String? @db.VarChar(255) + type UserType + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + deletedAt DateTime? + deptId Int? + signId String? @unique + headReviewProcesses Process[] @relation("headReviewer") + studentProcess Process? @relation("student") + reviews Review[] + reviewers Reviewer[] + department Department? @relation(fields: [deptId], references: [id], onDelete: SetNull) + signFile File? @relation(fields: [signId], references: [uuid]) + studentAchivements Achievements[] @relation("student") + profesosr1Achivements Achievements[] @relation("professor1") + profesor2Achivements Achievements[] @relation("professor2") @@index([deptId], map: "user_deptId_fkey") @@map("user") @@ -167,7 +169,11 @@ model Achievements { authorType AuthorType authorNumbers Int userId Int? - User User? @relation(fields: [userId], references: [id], onDelete: Cascade) + professorId1 Int? + professorId2 Int? + User User? @relation("student", fields: [userId], references: [id], onDelete: Cascade) + Professor1 User? @relation("professor1", fields: [professorId1], references: [id], onDelete: SetNull) + Professor2 User? @relation("professor2", fields: [professorId2], references: [id], onDelete: SetNull) @@index([userId], map: "achievements_userId_fkey") @@map("achievements") @@ -177,6 +183,7 @@ enum UserType { STUDENT PROFESSOR ADMIN + PHD } enum Stage { diff --git "a/resources/excel/\354\227\260\352\265\254\353\205\274\353\254\270\354\236\221\355\222\210\354\213\234\354\212\244\355\205\234_\355\225\231\354\203\235_\354\235\274\352\264\204\353\223\261\353\241\235_\354\226\221\354\213\235.xlsx" "b/resources/excel/\354\227\260\352\265\254\353\205\274\353\254\270\354\236\221\355\222\210\354\213\234\354\212\244\355\205\234_\355\225\231\354\203\235_\354\235\274\352\264\204\353\223\261\353\241\235_\354\226\221\354\213\235.xlsx" index e6d0113..c64d3bd 100644 Binary files "a/resources/excel/\354\227\260\352\265\254\353\205\274\353\254\270\354\236\221\355\222\210\354\213\234\354\212\244\355\205\234_\355\225\231\354\203\235_\354\235\274\352\264\204\353\223\261\353\241\235_\354\226\221\354\213\235.xlsx" and "b/resources/excel/\354\227\260\352\265\254\353\205\274\353\254\270\354\236\221\355\222\210\354\213\234\354\212\244\355\205\234_\355\225\231\354\203\235_\354\235\274\352\264\204\353\223\261\353\241\235_\354\226\221\354\213\235.xlsx" differ diff --git a/src/common/enums/user-type.enum.ts b/src/common/enums/user-type.enum.ts index 50a10ce..2407f63 100644 --- a/src/common/enums/user-type.enum.ts +++ b/src/common/enums/user-type.enum.ts @@ -2,4 +2,5 @@ export enum UserType { STUDENT = "STUDENT", PROFESSOR = "PROFESSOR", ADMIN = "ADMIN", + PHD = "PHD", } diff --git a/src/modules/achievements/achievements.controller.ts b/src/modules/achievements/achievements.controller.ts index 6aad4ef..81c319b 100644 --- a/src/modules/achievements/achievements.controller.ts +++ b/src/modules/achievements/achievements.controller.ts @@ -35,7 +35,7 @@ export class AchievementsController { summary: "논문실적 등록", description: "논문실적 등록", }) - @UseUserTypeGuard([UserType.STUDENT, UserType.ADMIN]) + @UseUserTypeGuard([UserType.STUDENT, UserType.ADMIN, UserType.PHD]) @Post() @ApiCreatedResponse({ description: "논문 실적 등록 성공", @@ -71,7 +71,7 @@ export class AchievementsController { @ApiInternalServerErrorResponse({ description: "논문 조회 실패", }) - @UseUserTypeGuard([UserType.ADMIN, UserType.STUDENT]) + @UseUserTypeGuard([UserType.ADMIN, UserType.STUDENT, UserType.PHD]) @Get() async getAchievements(@Query() achievementsQuery: AchievementsSearchQuery, @CurrentUser() currentUser: User) { const { achievements, counts } = await this.achievemenstService.getAchievements(currentUser, achievementsQuery); @@ -94,7 +94,7 @@ export class AchievementsController { description: "논문실적 수정 성공", type: CommonResponseDto, }) - @UseUserTypeGuard([UserType.STUDENT, UserType.ADMIN]) + @UseUserTypeGuard([UserType.STUDENT, UserType.ADMIN, UserType.PHD]) @Put(":id") async updateAchievement( @Param("id", PositiveIntPipe) id: number, @@ -130,7 +130,7 @@ export class AchievementsController { }) @ApiInternalServerErrorResponse({ description: "논문 실적 조회 실패" }) @ApiUnauthorizedResponse({ description: "학생은 본인 논문실적만 조회 허용" }) - @UseUserTypeGuard([UserType.ADMIN, UserType.STUDENT]) + @UseUserTypeGuard([UserType.ADMIN, UserType.STUDENT, UserType.PHD]) @Get(":id") async getAchievement(@Param("id", PositiveIntPipe) id: number, @CurrentUser() user: User) { const achievement = await this.achievemenstService.getAchievement(id, user); @@ -146,7 +146,7 @@ export class AchievementsController { }) @ApiInternalServerErrorResponse({ description: "논문 실적 삭제 실패" }) @ApiUnauthorizedResponse({ description: "학생은 본인 논문실적만 삭제 허용" }) - @UseUserTypeGuard([UserType.ADMIN, UserType.STUDENT]) + @UseUserTypeGuard([UserType.ADMIN, UserType.STUDENT, UserType.PHD]) @Delete(":id") async deleteAchievement(@Param("id", PositiveIntPipe) id: number, @CurrentUser() user: User) { await this.achievemenstService.deleteAchievement(id, user); diff --git a/src/modules/achievements/achievements.service.ts b/src/modules/achievements/achievements.service.ts index 5e6cf98..256d9cd 100644 --- a/src/modules/achievements/achievements.service.ts +++ b/src/modules/achievements/achievements.service.ts @@ -12,10 +12,10 @@ export class AchievementsService { constructor(private readonly prismaService: PrismaService) {} async createAchievement(userId: number, user: User, createAchievementsDto: CreateAchievementsDto) { - const { performance, paperTitle, journalName, ISSN, publicationDate, authorType, authorNumbers } = + const { performance, paperTitle, journalName, ISSN, publicationDate, authorType, authorNumbers, professorIds } = createAchievementsDto; - if (user.type === UserType.STUDENT && userId !== user.id) + if ((user.type === UserType.STUDENT || user.type === UserType.PHD) && userId !== user.id) throw new UnauthorizedException("본인 논문실적만 등록 가능합니다."); const foundUser = await this.prismaService.user.findUnique({ where: { @@ -23,6 +23,20 @@ export class AchievementsService { }, }); if (!foundUser) throw new BadRequestException("해당 유저가 존재하지 않습니다."); + + // 교수 아이디 확인 + if (professorIds && professorIds.length !== 0) { + const foundProfessors = await this.prismaService.user.findMany({ + where: { + id: { in: professorIds }, + type: UserType.PROFESSOR, + }, + }); + const foundIds = foundProfessors.map((user) => user.id); + const missingIds = professorIds.filter((id) => !foundIds.includes(id)); + if (missingIds.length !== 0) throw new BadRequestException(`ID:[${missingIds}]에 해당하는 교수가 없습니다.`); + } + return await this.prismaService.achievements.create({ data: { userId, @@ -33,6 +47,8 @@ export class AchievementsService { publicationDate, authorNumbers, authorType, + professorId1: professorIds ? professorIds[0] : undefined, + professorId2: professorIds && professorIds.length == 2 ? professorIds[1] : undefined, }, }); } @@ -44,7 +60,7 @@ export class AchievementsService { }, }); if (!foundUser) throw new BadRequestException("해당 논문실적은 존재하지 않습니다."); - if (user.type === UserType.STUDENT && foundUser.userId != user.id) + if ((user.type === UserType.STUDENT || user.type === UserType.PHD) && foundUser.userId != user.id) throw new BadRequestException("다른 학생의 논문실적은 수정할수 없습니다."); const { performance, paperTitle, journalName, ISSN, publicationDate, authorType, authorNumbers } = updateAchievementDto; @@ -69,7 +85,7 @@ export class AchievementsService { } async getAchievements(currentUser: User, achievementsQuery: AchievementsSearchQuery) { - if (currentUser.type == UserType.STUDENT) { + if (currentUser.type === UserType.STUDENT || currentUser.type === UserType.PHD) { const studentQuery = { where: { userId: currentUser.id, @@ -147,6 +163,8 @@ export class AchievementsService { department: true, }, }, + Professor1: true, + Professor2: true, }, }); if (!achievements) throw new BadRequestException("검색된 논문 실적이 없습니다."); @@ -158,6 +176,9 @@ export class AchievementsService { record["학번"] = student.loginId; record["이름"] = student.name; record["학과"] = dept.name; + record["학위과정"] = achievement.User.type === UserType.STUDENT ? "석사" : "박사"; + record["지도교수1"] = achievement.Professor1 ? achievement.Professor1.name : null; + record["지도교수2"] = achievement.Professor2 ? achievement.Professor2.name : null; record["실적 구분"] = this.PerformanceToFullname(achievement.performance); record["학술지 또는 학술대회명"] = achievement.journalName; @@ -236,7 +257,7 @@ export class AchievementsService { }, }); if (!achievement) throw new BadRequestException("해당 id의 논문실적이 존재하지 않습니다."); - if (user.type === UserType.STUDENT && achievement.userId !== user.id) + if ((user.type === UserType.STUDENT || user.type === UserType.PHD) && achievement.userId !== user.id) throw new UnauthorizedException("학생의 경우 본인 논문 실적만 조회가 가능합니다."); return achievement; } @@ -248,7 +269,7 @@ export class AchievementsService { }, }); if (!achievement) throw new BadRequestException("해당 id의 논문실적이 존재하지 않습니다."); - if (user.type === UserType.STUDENT && achievement.userId !== user.id) + if ((user.type === UserType.STUDENT || user.type === UserType.PHD) && achievement.userId !== user.id) throw new UnauthorizedException("학생의 경우 본인 논문 실적만 조회가 가능합니다."); try { await this.prismaService.achievements.delete({ diff --git a/src/modules/achievements/dtos/create-achievements.dto.ts b/src/modules/achievements/dtos/create-achievements.dto.ts index 327f1b9..3d13249 100644 --- a/src/modules/achievements/dtos/create-achievements.dto.ts +++ b/src/modules/achievements/dtos/create-achievements.dto.ts @@ -1,4 +1,15 @@ -import { IsDate, IsEnum, IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from "class-validator"; +import { + ArrayMaxSize, + ArrayMinSize, + IsArray, + IsDate, + IsEnum, + IsInt, + IsNotEmpty, + IsOptional, + IsPositive, + IsString, +} from "class-validator"; import { Performance } from "../../../common/enums/performance.enum"; import { Author } from "../../../common/enums/author.enum"; import { ApiProperty } from "@nestjs/swagger"; @@ -69,4 +80,13 @@ export class CreateAchievementsDto { @IsInt() @IsPositive() authorNumbers: number; + + @ApiProperty({ description: "지도교수 아이디 리스트", type: [Number], example: "[3, 4]" }) + @IsArray() + @Type(() => Number) + @IsInt({ each: true }) + @IsPositive({ each: true }) + @ArrayMinSize(0) + @ArrayMaxSize(2) + professorIds: number[]; } diff --git a/src/modules/students/dtos/create-phd.dto.ts b/src/modules/students/dtos/create-phd.dto.ts new file mode 100644 index 0000000..3469c2d --- /dev/null +++ b/src/modules/students/dtos/create-phd.dto.ts @@ -0,0 +1,43 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Type } from "class-transformer"; +import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from "class-validator"; +import { IsKoreanPhoneNumber } from "src/common/decorators/is-kr-phone-number.decorator"; + +export class CreatePhDDto { + // 사용자 정보 + @ApiProperty({ description: "로그인 아이디(학번)", example: "2022000000" }) + @IsNotEmpty() + @IsString() + loginId: string; + + @ApiProperty({ description: "비밀번호(생년월일)", example: "010101" }) + @IsNotEmpty() + @IsString() + password: string; + + @ApiProperty({ description: "학생 이름", example: "홍길동" }) + @IsNotEmpty() + @IsString() + name: string; + + @ApiProperty({ description: "학생 이메일", example: "abc@gmail.com", required: false }) + @IsOptional() + @IsNotEmpty() + @IsString() + @IsEmail() + email: string; + + @ApiProperty({ description: "학생 전화번호", example: "010-1111-1222", required: false }) + @IsOptional() + @IsNotEmpty() + @IsString() + @IsKoreanPhoneNumber() + phone: string; + + @ApiProperty({ description: "학과 아이디", example: "1" }) + @IsNotEmpty() + @Type(() => Number) + @IsPositive() + @IsInt() + deptId: number; +} diff --git a/src/modules/students/dtos/phd.dto.ts b/src/modules/students/dtos/phd.dto.ts new file mode 100644 index 0000000..c21b119 --- /dev/null +++ b/src/modules/students/dtos/phd.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Department, User, UserType } from "@prisma/client"; +import { DepartmentDto } from "src/modules/departments/dtos/department.dto"; + +export class PhDDto { + constructor(phdData: Partial & { department: Department }) { + this.id = phdData.id; + this.loginId = phdData.loginId; + this.name = phdData.name; + this.email = phdData.email; + this.phone = phdData.phone; + this.type = phdData.type; + this.department = phdData.department ? new DepartmentDto(phdData.department) : undefined; + } + + @ApiProperty({ description: "아이디" }) + id: number; + @ApiProperty({ description: "로그인 아이디" }) + loginId: string; + @ApiProperty({ description: "이름" }) + name: string; + @ApiProperty({ description: "이메일" }) + email?: string; + @ApiProperty({ description: "연락처" }) + phone?: string; + @ApiProperty({ description: "사용자 유형", enum: UserType, example: UserType.PHD }) + type: UserType; + @ApiProperty({ description: "학과" }) + department: DepartmentDto; +} diff --git a/src/modules/students/students.controller.ts b/src/modules/students/students.controller.ts index 7a4d709..61e81d2 100644 --- a/src/modules/students/students.controller.ts +++ b/src/modules/students/students.controller.ts @@ -1,3 +1,4 @@ +import { CreatePhDDto } from "./dtos/create-phd.dto"; import { StudentsService } from "./students.service"; import { Body, @@ -50,11 +51,12 @@ import { ReviewersDto } from "./dtos/reviewers.dto"; import { FileInterceptor } from "@nestjs/platform-express"; import { ExcelFilter } from "src/common/pipes/excel.filter"; import { UpdateReviewerQueryDto } from "./dtos/update-reviewer-query-dto"; +import { PhDDto } from "./dtos/phd.dto"; @Controller("students") @UseGuards(JwtGuard) @ApiTags("학생 API") -@ApiExtraModels(UserDto, CommonResponseDto, SystemDto, ThesisInfoDto, ReviewersDto) +@ApiExtraModels(UserDto, CommonResponseDto, SystemDto, ThesisInfoDto, ReviewersDto, PhDDto) @ApiInternalServerErrorResponse({ description: "서버 내부 오류" }) @ApiBearerAuth("access-token") export class StudentsController { @@ -82,6 +84,26 @@ export class StudentsController { return new CommonResponseDto(userDto); } + @Post("/phd") + @UseUserTypeGuard([UserType.ADMIN]) + @ApiOperation({ + summary: "박사과정 학생 생성 API", + description: "박사과정 학생의 기본 회원정보를 받아서 생성한다. 학생의 회원 가입 역할을 한다.", + }) + @ApiUnauthorizedResponse({ description: "[관리자] 로그인 후 접근 가능" }) + @ApiBadRequestResponse({ description: "요청 양식 오류" }) + @ApiCreatedResponse({ + description: "박사과정 학생 생성 성공", + schema: { + allOf: [{ $ref: getSchemaPath(CommonResponseDto) }, { $ref: getSchemaPath(PhDDto) }], + }, + }) + async createPhD(@Body() createPhdDto: CreatePhDDto) { + const phd = await this.studentsService.createPhD(createPhdDto); + const userDto = new PhDDto(phd); + return new CommonResponseDto(userDto); + } + @Post("/excel") @UseUserTypeGuard([UserType.ADMIN]) @UseInterceptors(FileInterceptor("file", { fileFilter: ExcelFilter })) @@ -127,6 +149,51 @@ export class StudentsController { return new CommonResponseDto(pageDto); } + @Post("/excel/phd") + @UseUserTypeGuard([UserType.ADMIN]) + @UseInterceptors(FileInterceptor("file", { fileFilter: ExcelFilter })) + @ApiConsumes("multipart/form-data") + @ApiBody({ + schema: { + type: "object", + properties: { + file: { + type: "string", + format: "binary", + }, + }, + }, + }) + @ApiOperation({ + summary: "박사과정 학생 엑셀 업로드 API", + description: "엑셀을 업로드하여 박사과정 학생을 생성한다. 학번 기준 기존 학생인 경우 업데이트를 진행한다.", + }) + @ApiUnauthorizedResponse({ description: "[관리자] 로그인 후 접근 가능" }) + @ApiBadRequestResponse({ description: "엑셀 양식 오류" }) + @ApiCreatedResponse({ + description: "생성 및 업데이트 성공", + schema: { + allOf: [ + { $ref: getSchemaPath(CommonResponseDto) }, + { $ref: getSchemaPath(PageDto) }, + { + properties: { + content: { + type: "array", + items: { $ref: getSchemaPath(PhDDto) }, + }, + }, + }, + ], + }, + }) + async createPhDExcel(@UploadedFile() excelFile: Express.Multer.File) { + const students = await this.studentsService.createPhDExcel(excelFile); + const contents = students.map((student) => new PhDDto(student)); + const pageDto = new PageDto(0, 0, contents.length, contents); + return new CommonResponseDto(pageDto); + } + // 학생 대량 조회 API @Get("/excel") @UseUserTypeGuard([UserType.ADMIN]) diff --git a/src/modules/students/students.service.ts b/src/modules/students/students.service.ts index 62b7a64..fe75832 100644 --- a/src/modules/students/students.service.ts +++ b/src/modules/students/students.service.ts @@ -24,6 +24,7 @@ import { validate } from "class-validator"; import { UpdateThesisInfoDto } from "./dtos/update-thesis-info.dto"; import { Readable } from "stream"; import { UpdateSystemDto } from "./dtos/update-system.dto"; +import { CreatePhDDto } from "./dtos/create-phd.dto"; @Injectable() export class StudentsService { @@ -244,6 +245,56 @@ export class StudentsService { } } + async createPhD(createPhDDto: CreatePhDDto) { + const { loginId, password, name, email, phone, deptId } = createPhDDto; + + // 로그인 아이디, 이메일 존재 여부 확인 + const foundId = await this.prismaService.user.findUnique({ + where: { + loginId, + deletedAt: null, + }, + }); + if (foundId) + throw new BadRequestException("이미 존재하는 아이디입니다. 기존 학생 수정은 학생 수정 페이지를 이용해주세요."); + if (email) { + const foundEmail = await this.prismaService.user.findUnique({ + where: { + email, + deletedAt: null, + }, + }); + if (foundEmail) throw new BadRequestException("이미 존재하는 이메일입니다."); + } + + // deptId 올바른지 확인 + const foundDept = await this.prismaService.department.findUnique({ + where: { id: deptId }, + }); + if (!foundDept) throw new BadRequestException("해당하는 학과가 없습니다."); + + try { + return await this.prismaService.$transaction(async (tx) => { + // 사용자(user) 생성 + return await tx.user.create({ + data: { + deptId, + loginId, + email, + password: this.authService.createHash(password), + name, + phone, + type: UserType.PHD, + }, + include: { department: true }, + }); + }); + } catch (error) { + console.log(error); + throw new InternalServerErrorException("학생 생성 실패"); + } + } + async createStudentExcel(excelFile: Express.Multer.File) { if (!excelFile) throw new BadRequestException("파일을 업로드해주세요."); const workBook = XLSX.read(excelFile.buffer, { type: "buffer" }); @@ -1035,6 +1086,146 @@ export class StudentsService { ); } + async createPhDExcel(excelFile: Express.Multer.File) { + if (!excelFile) throw new BadRequestException("파일을 업로드해주세요."); + const workBook = XLSX.read(excelFile.buffer, { type: "buffer" }); + const sheetName = workBook.SheetNames[0]; + const sheet = workBook.Sheets[sheetName]; + const studentRecords = XLSX.utils.sheet_to_json(sheet); + + return await this.prismaService.$transaction( + async (tx) => { + const students = []; + for (const [index, studentRecord] of studentRecords.entries()) { + try { + // 모든 항목 값 받아오기 + const studentNumber = studentRecord["학번"]?.toString(); + const password = studentRecord["비밀번호"]?.toString(); + const name = studentRecord["이름"]; + const email = studentRecord["이메일"]; + const phone = studentRecord["연락처"]?.toString(); + const major = studentRecord["학과"]; + + // 학과 찾기 + let deptId: number, foundDept: Department; + if (major) { + foundDept = await this.prismaService.department.findFirst({ + where: { name: major }, + }); + if (!foundDept) throw new BadRequestException(`${index + 2} 행의 학과 명을 확인해주세요.`); + deptId = foundDept.id; + } + + // 학번으로 기존 학생 여부 판단 + if (!studentNumber) throw new BadRequestException(`${index + 2}번째 행의 [학번] 항목을 다시 확인해주세요.`); + const foundStudent = await this.prismaService.user.findUnique({ + where: { + loginId: studentNumber, + type: UserType.PHD, + deletedAt: null, + }, + }); + + // 학생 업데이트 + if (foundStudent) { + // 학생 기본 정보 업데이트 (학번, 비밀번호, 이름, 이메일, 연락처, 학과) + const updateStudentDto = new UpdateStudentDto(); + updateStudentDto.password = password; + updateStudentDto.name = name; + updateStudentDto.email = email; + updateStudentDto.phone = phone; + updateStudentDto.deptId = deptId; + // validation + const validationErrors = await validate(updateStudentDto); + if (validationErrors.length > 0) { + validationErrors.map((error) => console.log(error.constraints)); + throw new BadRequestException(`${index + 2} 행의 데이터 입력 형식이 잘못되었습니다.`); + } + + // 이메일 중복 여부 확인 + if (updateStudentDto.email) { + const foundEmail = await this.prismaService.user.findUnique({ + where: { email: updateStudentDto.email, deletedAt: null }, + }); + if (foundEmail && foundEmail.id !== foundStudent.id) + throw new BadRequestException(`${index + 2}행 : 이미 존재하는 이메일로는 변경할 수 없습니다.`); + } + + // 업데이트 + const updatePhD = await tx.user.update({ + where: { id: foundStudent.id, deletedAt: null }, + data: { + loginId: updateStudentDto.loginId, + password: updateStudentDto.password + ? this.authService.createHash(updateStudentDto.password) + : undefined, + name: updateStudentDto.name, + email: updateStudentDto.email, + phone: updateStudentDto.phone, + deptId: updateStudentDto.deptId, + }, + include: { department: true }, + }); + + students.push(updatePhD); + } + // 신규 학생 생성 + else { + const createPhDDto = new CreatePhDDto(); + createPhDDto.loginId = studentNumber; + createPhDDto.password = password; + createPhDDto.name = name; + createPhDDto.email = email; + createPhDDto.phone = phone; + createPhDDto.deptId = deptId; + + // DTO 이용한 validation + const validationErrors = await validate(createPhDDto); + if (validationErrors.length > 0) { + validationErrors.map((error) => console.log(error.constraints)); + throw new BadRequestException(`${index + 2} 행의 데이터 입력 형식이 잘못되었습니다.`); + } + + // 이메일 존재 여부 확인 + if (createPhDDto.email) { + const foundEmail = await this.prismaService.user.findUnique({ + where: { email: createPhDDto.email }, + }); + if (foundEmail) throw new BadRequestException(`${index + 2}행 : 이미 존재하는 이메일입니다.`); + } + + // 신규 학생 생성 + const newPhD = await tx.user.create({ + data: { + deptId: createPhDDto.deptId, + loginId: createPhDDto.loginId, + email: createPhDDto.email, + password: this.authService.createHash(createPhDDto.password), + name: createPhDDto.name, + phone: createPhDDto.phone, + type: UserType.PHD, + }, + include: { department: true }, + }); + + students.push(newPhD); + } + } catch (error) { + console.log(error); + if (error.status === 400) { + throw new BadRequestException(error.message); + } + throw new InternalServerErrorException(`${index + 2} 행에서 에러 발생`); + } + } + return students; + }, + { + timeout: 4000, + } + ); + } + // 학생 대량 조회 API async getStudentList(studentSearchPageQuery: StudentSearchPageQuery) { const { studentNumber, name, email, phone, departmentId } = studentSearchPageQuery; diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 10f9e6b..d13c771 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -29,7 +29,7 @@ export class UsersController { constructor(private readonly usersService: UsersService) {} @Get("/me") - @UseUserTypeGuard([UserType.ADMIN, UserType.STUDENT, UserType.PROFESSOR]) + @UseUserTypeGuard([UserType.ADMIN, UserType.STUDENT, UserType.PROFESSOR, UserType.PHD]) @ApiOperation({ summary: "로그인 유저 정보 조회 API", description: "로그인된 유저의 회원 정보를 조회할 수 있다.", @@ -46,7 +46,7 @@ export class UsersController { } @Put("/me") - @UseUserTypeGuard([UserType.ADMIN, UserType.PROFESSOR, UserType.STUDENT]) + @UseUserTypeGuard([UserType.ADMIN, UserType.PROFESSOR, UserType.STUDENT, UserType.PHD]) @ApiOperation({ summary: "로그인 유저 정보 수정 API", description: "로그인된 유저의 회원 정보 중 '비밀번호', '연락처', '이메일' 필드를 수정할 수 있다.",