Skip to content

Commit

Permalink
[FEAT] 박사과정 관련 기능 구현 (#150)
Browse files Browse the repository at this point in the history
* fix: user type PHD 추가

* feat: createPhD API 구현

* fix: 학생 엑셀 업로드 양식에 박사과정 학생 등록 방식 설명 추가

* feat: createPhDExcel API 구현

* feat: PHD UserType에 일부 API 권한 부여

* feat: 연구실적 서비스 로직에 PHD UserType 추가

* fix: 연구실적 DB 구조 수정

* fix: 연구실적 등록 시 지도교수 최대 2명 설정 가능

* fix: 연구실적 엑셀 다운로드 시 학위과정, 지도교수 이름 포함
  • Loading branch information
yesjuhee authored Oct 18, 2024
1 parent edd8b72 commit cda0401
Show file tree
Hide file tree
Showing 11 changed files with 415 additions and 35 deletions.
47 changes: 27 additions & 20 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand All @@ -177,6 +183,7 @@ enum UserType {
STUDENT
PROFESSOR
ADMIN
PHD
}

enum Stage {
Expand Down
Binary file not shown.
1 change: 1 addition & 0 deletions src/common/enums/user-type.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum UserType {
STUDENT = "STUDENT",
PROFESSOR = "PROFESSOR",
ADMIN = "ADMIN",
PHD = "PHD",
}
10 changes: 5 additions & 5 deletions src/modules/achievements/achievements.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class AchievementsController {
summary: "논문실적 등록",
description: "논문실적 등록",
})
@UseUserTypeGuard([UserType.STUDENT, UserType.ADMIN])
@UseUserTypeGuard([UserType.STUDENT, UserType.ADMIN, UserType.PHD])
@Post()
@ApiCreatedResponse({
description: "논문 실적 등록 성공",
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
33 changes: 27 additions & 6 deletions src/modules/achievements/achievements.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,31 @@ 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: {
id: userId,
},
});
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,
Expand All @@ -33,6 +47,8 @@ export class AchievementsService {
publicationDate,
authorNumbers,
authorType,
professorId1: professorIds ? professorIds[0] : undefined,
professorId2: professorIds && professorIds.length == 2 ? professorIds[1] : undefined,
},
});
}
Expand All @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -147,6 +163,8 @@ export class AchievementsService {
department: true,
},
},
Professor1: true,
Professor2: true,
},
});
if (!achievements) throw new BadRequestException("검색된 논문 실적이 없습니다.");
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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({
Expand Down
22 changes: 21 additions & 1 deletion src/modules/achievements/dtos/create-achievements.dto.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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[];
}
43 changes: 43 additions & 0 deletions src/modules/students/dtos/create-phd.dto.ts
Original file line number Diff line number Diff line change
@@ -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;
}
30 changes: 30 additions & 0 deletions src/modules/students/dtos/phd.dto.ts
Original file line number Diff line number Diff line change
@@ -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<User> & { 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;
}
Loading

0 comments on commit cda0401

Please sign in to comment.