View
목표는 1번 Sentence의 Voice파일 1개를 생성하는 기능구현을 하는 것이었다.
먼저 prisma를 통하여 다음과 같이 모델링을 하였다.
(User는 다른 데이터베이스에 들어가야하지만, 아직 같은 데이터 베이스 안에서 데이터를 넣는 것부터도 시도해보지 않은 상태여서,
데이터베이스 분리는 나중에 해보기로 결정하였다. )
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Voice {
id Int @default(autoincrement()) @id
fileSize Decimal @db.Decimal(8,3)
url String @db.VarChar(500)
user User @relation(fields: [userId], references: [id])
userId Int
sentence Sentence @relation(fields: [sentenceId], references: [id])
sentenceId Int
dateOfCreated DateTime @default(now())
dateOfUpdated DateTime? @updatedAt
voiceStatus VoiceStatus[]
@@map("voice")
}
model VoiceStatus {
id Int @default(autoincrement()) @id
voice Voice @relation(fields: [voiceId], references: [id])
voiceId Int
downloadCount Int @default(0)
clientId Int?
verifyStatus String @db.VarChar(50) @default("None")
user User? @relation(fields: [userId], references: [id])
userId Int?
playCount Int @default(0)
dateOfCreated DateTime @db.Timestamptz(3) @default(now())
dateOfUpdated DateTime? @db.Timestamptz(3) @updatedAt
@@map("voice_statuse")
}
model Script {
id Int @default(autoincrement()) @id
title String @db.VarChar(50) @unique
url String @db.VarChar(500)
fileSize Decimal @db.Decimal(8,3)
remarks String @db.VarChar(1000)
client Client? @relation(fields: [clientId], references:[id])
clientId Int?
dateOfCreated DateTime @db.Timestamptz(3) @default(now())
dateOfUpdated DateTime? @db.Timestamptz(3) @updatedAt
scriptStatus ScriptStatus[]
sentence Sentence[]
@@map("script")
}
model ScriptStatus {
id Int @default(autoincrement()) @id
script Script @relation(fields: [scriptId], references: [id])
scriptId Int @db.Integer
post Boolean @default(false)
deadline Boolean @default(false)
downloadCount Int @default(0)
client Client? @relation(fields: [clientId], references:[id])
clientId Int?
downloadMax Int? @default(1)
downloadPreiodFrom DateTime? @db.Date
downloadPreiodTo DateTime? @db.Date
dateOfCreated DateTime @db.Timestamptz(3) @default(now())
dateOfUpdated DateTime? @db.Timestamptz(3) @updatedAt
@@map("script_status")
}
model SentenceStatus {
id Int @default(autoincrement()) @id
sentence Sentence @relation(fields: [sentenceId], references: [id])
sentenceId Int
deadlineStatus Boolean @default(false)
dateOfCreated DateTime @db.Timestamptz(3) @default(now())
dateOfUpdated DateTime? @db.Timestamptz(3) @updatedAt
@@map("sentence_status")
}
model Sentence {
id Int @default(autoincrement()) @id
script Script @relation(fields: [scriptId], references: [id])
scriptId Int
context String @db.VarChar(5000)
language String @db.VarChar(50)
age Int?
gender String? @db.VarChar(50)
dateOfCreated DateTime @db.Timestamptz(3) @default(now())
dateOfUpdated DateTime? @db.Timestamptz(3) @updatedAt
voice Voice[]
sentenceStatus SentenceStatus[]
@@map("sentence")
}
model Client {
id Int @id @default(autoincrement())
loginId String @unique
clientName String
ramarks String? @db.Text
dateOfCreated DateTime @default(now())
dateOfUpdated DateTime @updatedAt
password String
role Role @default(CLIENT)
script Script[]
scriptStatus ScriptStatus[]
@@map("client")
}
model User {
id Int @id @default(autoincrement())
loginId String @unique
firstName String
middleName String?
lastName String
gender Gender?
dateOfCreated DateTime @default(now())
dateOdUpdated DateTime @updatedAt
status Boolean @default(false)
birthday String
password String
role Role? @default(USER)
languages String
voice Voice[]
voiceStatus VoiceStatus[]
@@map("user")
}
enum Gender {
MALE
FEMALE
NEUTRAL
}
enum Role {
ADMIN
CLIENT
USER
}
dateOfUpdated필드의 ?는 update는 나중에 되므로 처음 데이터가 생성될때 null값을 허용한다는 의미에서 붙였다.
모델링 후 다음과 같이 VoiceDTO를 작성하였다.
export class VoiceDTO {
@IsOptional()
@IsNumber()
id
@IsOptional()
@IsNumber()
fileSize
@IsUrl()
@IsString()
url
@IsNumber()
user
@IsOptional()
@IsNumber()
sentence
@IsDate()
dateOfCreated
@IsOptional()
@IsDateString()
dateOfUpdated
}
DTO작성할때, dateOfCreated, dateOfUpdated 를 작성한 것을 잘 봐두자...
이후 Voice폴더 안에 controller, service, module을 생성하였으며,
main.ts에서 먼저 app.module을 통해 접속이 이루어지고, app.module에 있는 controller를 통해 service의 코드들이 작동하는 원리이다. 먼저 연습을 위해 아래와 같이 app.module을 작성하였고, voice.module로 endpoint가 잘 접속되는지 확인해보고자 하였다.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { VoiceController } from './voice/voice.controller';
import { VoiceModule } from './voice/voice.module';
import { ScriptController } from './script/script.controller';
import { ScriptModule } from './script/script.module';
import { PrismaService } from './prisma/prisma.service';
import { PrismaModule } from './prisma/prisma.module';
import { VoiceService } from './voice/voice.service';
import { ScriptService } from './script/script.service';
@Module({
imports: [VoiceModule, ScriptModule, PrismaModule],
controllers: [AppController, VoiceController, ScriptController],
providers: [AppService, VoiceService, PrismaService, ScriptService],
})
export class AppModule {}
AppController와 AppService는 다음과 같이 비워두었고,
import {
Controller,
Get,
Param,
Post,
Body,
Put,
Delete,
} from '@nestjs/common';
import { VoiceService } from './voice/voice.service';
import { ScriptService } from './script/script.service';
import { Voice as VoiceModel, Script as ScriptModel } from '@prisma/client';
import { user } from './dto/user.dto';
@Controller('')
export class AppController {
constructor(
private readonly scriptService : ScriptService,
private readonly voiceService : VoiceService,
) {}
}
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
VoiceController에 부터 코드 작성을 시작하였다.
구현할 endpoint는 http://localhost:3000/user/voice/submit?stentence_id=1이며, 필요한 parameter는 stentence_id라는 파라미터 이다.
url의 파라미터를 받아야하는데, 어떻게 받을지 먼저 고민을 하였고, 처음에 javascript와 typescript에서 url의 파라미터를 받는 방법을 찾아보다가, 이와 다른 표현방식을 사용한 블로그의 코드를 발견하게 되어, 이 방식은 NestJS에서 파라미터를 방는 방법임을 알게되었다.
그리하여, NestJS공식문서를 통해 controller에서 url의 파라미터를 받는 방법을 찾아보았고,
https://docs.nestjs.com/controllers
쿼리 파라미터는 @Query('')로 , path 파라미터는 @Param('')으로, body의 데이터는 @Body로 받게 되는 것을 알게 되었다.
자 그럼 이제 방법을 알았으니, 적용해볼 차례이다. 일단 내가 작성한 아래의 코드로 데이터가 잘 받아지는지를 먼저 확인하기 위해
import { Body, Controller, Post, Param, Query } from '@nestjs/common';
import { VoiceDTO } from '../dto/voice.dto';
import { VoiceService } from './voice.service';
import { Voice } from '@prisma/client';
@Controller('user/voice')
export class VoiceController {
constructor(private readonly voiceServicve: VoiceService) {}
@Post('submit')
createVoice(@Query('sentence_id') sentence_id: string, @Body() voiceData:VoiceDTO):Promise<boolean> {
return this.voiceServicve.createVoice(sentence_id, voiceData);
}
}
VoiceService를 아래와 같은 방법으로 작성해 보았다.
아래 코드는 단순히 @Body를 통해 받은 데이터를 콘솔에 찍어주는 함수이다.
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { VoiceDTO } from '../dto/voice.dto';
import {
Voice,
Prisma
} from '@prisma/client';
@Injectable()
export class VoiceService {
constructor(private prisma: PrismaService) {}
async createVoice(id: string, voiceData: Prisma.VoiceCreateInput): Promise<boolean> {
console.log(voiceData)
return true;
}
}
이전에 작성해 놓은 아래 VoiceDto를 기반으로 테스트를 진행하였다.
export class VoiceDTO {
@IsOptional()
@IsNumber()
id
@IsOptional()
@IsNumber()
fileSize
@IsUrl()
@IsString()
url
@IsNumber()
user
@IsOptional()
@IsNumber()
sentence
@IsDate()
dateOfCreated
@IsOptional()
@IsDateString()
dateOfUpdated
}
테스트용 바디는 아래와 같다.
{
"fileSize" : 3214,
"url" : "http://adfsdff.com",
"user" : 1
}
하지만 결과는 아래와 같았다...ㅠㅠ
"dateOfCreated must be a Date instance" : dateOfCreated가 date객체어야 한다는 말인데,
dateOfCreated에 문제라는 말에 처음에는 model의 dateOfCreated의 타입에 null을 허용해서 DateTime?도 줘보았다.
또한, 바디안에 dateOfCreated도 데이터로 넣어 전송시켜봤으나, 해결되지 않았다.
그리고 본론으로 돌아와서........
내가 짜 놓은 모델은 dateOfCreated가 default(now())로 설정되어있어서 dateOfCreated값이 body로 전송될 필요가 없다는 것을 깨달은 뒤 그렇다면, 데이터 전송객체인 DTO가 문제였다는 것을 깨닫게 되었고, 아래와 같이 필드값이 생성되는 동시에 자동으로 default가 생성되는 dateOfCreated을 제거해 보기로 하였다.
그렇다면, 왜 dateOfUpdated에 대해서는 오류가 안떴을까???
그 이유는 바로 아래와 같이 해당값이 들어와도 되고, 안들어와도 되는 @IsOptional()을 걸어놓았기 때문이다.. ^^
export class VoiceDTO {
@IsOptional()
@IsNumber()
id
@IsOptional()
@IsNumber()
fileSize
@IsUrl()
@IsString()
url
@IsNumber()
user
@IsOptional()
@IsNumber()
sentence
@IsDate()
dateOfCreated
@IsOptional()
@IsDateString()
dateOfUpdated
}
그리하여 바로 즉시 VoiceDTO에서 dateOfCreated, dateOfUpdated를 삭제해주었다.
그리고 요청을 보낸 결과 아래과 같이 예쁜 True를 얻을 수 있었다.
그리고 덤으로 Body에 담아 보낸 데이터들이 콘솔에도 예쁘게 찍힌 것을 볼수있었다!!!!!!!!
여기서 뽀인트는
Body를 통해 받은 데이터들이 VoiceDTO의 형식을 잘 지킨 값들이라고 앞쪽에 VoiceDTO로 표시해주고있다는 것이다..ㅎㅎ
내가 놓쳤던 것은 model을 보고 DTO를 작성하다보니,
실제로 body로 넘겨받지 않는 데이터에 대해서도 DTO가 작성되었다는 것이었다.ㅠㅠ
이를 유의하여 앞으로의 DTO들도 잘 짜야겠다...!!
자, 그럼 이제 다음 블로깅에서는 받아온 값들을 Voice 테이블에 넣어보도록 하자!
body의 값을 받아오는 또다른 방법
@Controller('user/voice')
export class VoiceController {
constructor(private readonly voiceServicve: VoiceService) {}
@Post('submit')
createVoice(@Body() voiceData:{fileSize, url} ): boolean {
const {fileSize, url} = voiceData;
return this.voiceServicve.createVoice({
fileSize,
url,
user:{connect : {id : 1}},
sentence :{connect : {id : 1}}
});
}
}
@Injectable()
export class VoiceService {
constructor(private prisma: PrismaService) {}
createVoice(voiceData: Prisma.VoiceCreateInput): boolean {
console.log(voiceData)
this.prisma.voice.create({data:voiceData});
return true;
}
}
중요한 것은 DTO와 모두 변수를 일치시켜야한다.
'NestJS' 카테고리의 다른 글
TIL | now(), @updateAt의 @db.Timestamptz(3)의 유무에 따른 시간 표시 (0) | 2021.10.18 |
---|---|
TIL | 데이터 베이스에 데이터를 저장 (0) | 2021.10.18 |
TIL | NestJS_REST_API_Module (0) | 2021.10.11 |
TIL | NestJS_REST_API_Validation (0) | 2021.10.11 |
TIL | NestJS_REST_API_Service (0) | 2021.10.11 |