Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge: 토익 문제 수정 및 삭제 적용 #43

Merged
merged 8 commits into from
Feb 25, 2024
119 changes: 119 additions & 0 deletions packages/backend/src/toeic/dto/patch-quesion.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
IsUUID,
MaxLength,
MinLength,
ValidateNested,
} from 'class-validator';
import {
PatchQuestionToEntity,
QuestionWithId,
ToeicReqBodyProps,
} from '../toeic.interface';

export class PatchToeicWithQuestion implements ToeicReqBodyProps {
@ApiProperty({
description: '토익 문제 제목입니다.',
example: '해커스 토익 실전 1회',
})
@IsString()
@MinLength(2)
@MaxLength(32)
@IsOptional()
@Type(() => String)
title: string;

@ApiProperty({
description: '토익 문제 공개 여부입니다.',
})
@IsBoolean()
@Type(() => Boolean)
@IsOptional()
is_public: boolean;

@ApiProperty({
description: '토익 문제입니다.',
})
@IsArray()
@ValidateNested({ each: true })
@Type(() => PatchQuestion)
questions: PatchQuestion[];

toEntity(): PatchQuestionToEntity {
return {
title: this.title,
is_public: this.is_public,
questions: {
update: this.questions.map((question) => ({
where: { id: question.id },
data: {
question_number: question.question_number,
answer: question.answer,
content: question.content,
choice: question.choice,
translation: question.translation,
},
})),
},
};
}
}

export class PatchQuestion implements QuestionWithId {
@ApiProperty({
description: '문제 DB 고유 ID입니다.',
example: 'uuid',
})
@IsString()
@IsNotEmpty()
@IsUUID()
id: string;

@ApiProperty({
description: '문제 번호입니다.',
example: 1,
})
@Type(() => Number)
@IsNumber()
question_number: number;

@ApiProperty({
description: '정답입니다.',
example: 'A',
})
@IsString()
@MaxLength(1)
@IsOptional()
answer: string;

@ApiProperty({
description: '문제입니다.',
example: '문제입니다.',
})
@IsString()
@IsOptional()
content: string;

@ApiProperty({
description: '선택지입니다.',
example: '(A) --- (B) --- (C) --- (D) ---.',
})
@IsString()
@IsOptional()
choice: string;

@ApiProperty({
description: '문제 한글 번역입니다.',
example: '문제 한글 번역입니다.',
})
@IsString()
@IsOptional()
translation: string;
}
55 changes: 49 additions & 6 deletions packages/backend/src/toeic/toeic.controller.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,63 @@
import { Controller, Get, Param } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
} from '@nestjs/common';
import { ToeicService } from './toeic.service';
import { ApiSwagger } from '../common/swagger/api.decorator';
import { ResponseEntity } from '../common/entity/response.entity';
import { PatchToeicWithQuestion } from './dto/patch-quesion.dto';
import { ApiTags } from '@nestjs/swagger';

@ApiTags('토익 문제')
@Controller('toeic')
export class ToeicController {
constructor(private readonly toeicService: ToeicService) {}

@Get()
@ApiSwagger({ name: '토익 문제 조회' })
findAll() {
return this.toeicService.findAll();
async findAll() {
const result = await this.toeicService.findAll();
return ResponseEntity.OK_WITH(
`Successfully find ${result.length} questsions`,
result,
);
}

@Get('/:id')
@ApiSwagger({ name: '토익 문제 조회' })
findOne(@Param('id') id: number) {
return this.toeicService.findOne(+id);
@ApiSwagger({ name: '토익 문제 상세 조회' })
async findOne(@Param('id', ParseIntPipe) id: number) {
const result = await this.toeicService.findOne(+id);
return ResponseEntity.OK_WITH(
`Successfully find question id: ${id}.`,
result,
);
}

@Patch('/:id')
@ApiSwagger({ name: '토익 문제 수정' })
updateOne(
@Param('id', ParseIntPipe) id: number,
@Body() request: PatchToeicWithQuestion,
) {
const result = this.toeicService.updateOne(+id, request.toEntity());
return ResponseEntity.OK_WITH(
`Successfully update question id: ${id}.`,
result,
);
}

@Delete('/:id')
@ApiSwagger({ name: '토익 문제 수정' })
deleteOne(@Param('id', ParseIntPipe) id: number) {
const result = this.toeicService.deleteOne(+id);
return ResponseEntity.OK_WITH(
`Successfully delete question id: ${id}.`,
result,
);
}
}
29 changes: 29 additions & 0 deletions packages/backend/src/toeic/toeic.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Question } from '../upload/upload.interface';

export interface ToeicReqBodyProps {
title: string;
is_public: boolean;
questions: QuestionWithId[];
}

export interface QuestionWithId extends Question {
id: string;
}

export interface PatchQuestionToEntity
extends Omit<ToeicReqBodyProps, 'questions'> {
questions: {
update: {
where: {
id: string;
};
data: {
question_number: number;
answer: string;
content: string;
choice: string;
translation: string;
};
}[];
};
}
17 changes: 8 additions & 9 deletions packages/backend/src/toeic/toeic.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Injectable } from '@nestjs/common';
import { UploadedSheetData } from '../upload/dto/upload.dto';
import { UploadedQuestionInSheet } from '../upload/dto/upload.dto';
import { PrismaService } from '../prisma/prisma.service';
import { PatchQuestionToEntity } from './toeic.interface';

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

async create(filename: string, sheetData: UploadedSheetData) {
async create(filename: string, questionInSheet: UploadedQuestionInSheet) {
return this.prisma.toeic.create({
data: {
filename,
title: sheetData.title,
title: questionInSheet.title,
questions: {
create: sheetData.data,
create: questionInSheet.data,
},
},
});
Expand All @@ -23,7 +24,7 @@ export class ToeicService {
}

async findOne(id: number) {
return this.prisma.toeic.findUnique({
return this.prisma.toeic.findUniqueOrThrow({
where: {
id,
},
Expand All @@ -44,14 +45,12 @@ export class ToeicService {
});
}

async updateOne(id: number) {
async updateOne(id: number, data: PatchQuestionToEntity) {
return this.prisma.toeic.update({
where: {
id,
},
data: {
is_public: false,
},
data,
});
}
}
4 changes: 2 additions & 2 deletions packages/backend/src/upload/dto/upload.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { Question, UploadedQuestionSheet } from '../upload.interface';
import { Question, UploadedSheetProps } from '../upload.interface';

export class UploadedSheetData implements UploadedQuestionSheet {
export class UploadedQuestionInSheet implements UploadedSheetProps {
@ApiProperty({
description: '엑셀파일 시트 이름입니다.',
required: true,
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/upload/upload.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Database } from 'src/supabase/schema/database.schema';

export interface UploadedQuestionSheet {
export interface UploadedSheetProps {
title: Database['public']['Tables']['toeic']['Row']['title'];
data: Question[];
}
Expand All @@ -17,5 +17,5 @@ export interface QuestionInFile {
size: number;
name: string;
questionAmount: number;
sheets: UploadedQuestionSheet[];
sheets: UploadedSheetProps[];
}
6 changes: 3 additions & 3 deletions packages/backend/src/upload/upload.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Injectable } from '@nestjs/common';
import { WorkBook, utils } from 'xlsx';
import { UploadedSheetData } from './dto/upload.dto';
import { UploadedQuestionInSheet } from './dto/upload.dto';

@Injectable()
export class UploadService {
sheetToQuestions(workbook: WorkBook): UploadedSheetData[] {
sheetToQuestions(workbook: WorkBook): UploadedQuestionInSheet[] {
return workbook.SheetNames.map(
(title) =>
new UploadedSheetData(
new UploadedQuestionInSheet(
title,
utils.sheet_to_json(workbook.Sheets[title]),
),
Expand Down