우버이츠클론코딩 #5
User
4.0 & 4.1
user Entity
-
id
-
createAt
-
updateAt
-
email
-
password
-
role(client/ /owner/ /delivery)
User CRUD
-
Create Account
-
Log In
-
See profile
-
Edit Profile
-
Verify Email
User module
-
nest g mo users (module 만들기)
-
app.module.ts 에 RestaurantModule, Restaurant Entity 삭제
-
users 파일에 entites 폴더 생성 & user.entity.ts 파일 생성
- User.entity.ts
- password
-
role(client owner delivery)
import { Column, Entity } from "typeorm"; type UserRole = "client" | "owner" | "delivery" @Entity() export class User { @Column() email: string; @Column() password: string; @Column() role: UserRole; }
- User.entity.ts
-
nest g mo common(common이라는 module 생성)
-
common 파일에 entites 폴더 생성 & core.entity.ts 파일 생성
-
core.entity.ts -> 여기에 app관련 모든 것을 넣을 예정
import { PrimaryGeneratedColumn } from "typeorm"; export class CoreEntity { @PrimaryGeneratedColumn() id:number; }
-
user.entity.ts
-> CoreEntity extends 해주기
import { CoreEntity } from "src/common/entities/core.entity"; import { Column, Entity } from "typeorm"; type UserRole = "client" | "owner" | "delivery" @Entity() export class User extends CoreEntity{ //CoreEntity로 extend -> 모든 entities는 core.entity.ts에서 extends됨 @Column() email: string; @Column() password: string; @Column() role: UserRole;
-
core.entity.ts
-> createdAt, updatedAt을 special column으로 만들기
import { CreateDateColumn, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; export class CoreEntity { @PrimaryGeneratedColumn() id:number; @CreateDateColumn() createdAt:Date; @UpdateDateColumn() updatedAt:Date; }
-
-
app.module의 entities부분에 User넣기
4.2
-
users 폴더에 users.resolver.ts 파일 & users.service.ts 생성
-
users.module.ts
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([User])] //service는 repository 필요로 하기 떄문에 }) export class UsersModule {}
-
users.service.ts
import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { User } from "./entities/user.entity"; @Injectable() export class UsersService{ constructor( @InjectRepository(User) private readonly users: Repository <User>//User entity의 InjectRepository 불러오기 & type이 repository이고 repository type은 user enitity ){} }
-
users.resolver.ts
-> constructor랑 Query
import { Resolver, Query } from "@nestjs/graphql"; import { User } from "./entities/user.entity"; import { UsersService } from "./users.service"; @Resolver(of => User) export class UsersResolver { constructor( private readonly usersService: UsersService ){} @Query(returns => Boolean)//graphQL 루트 만들기 hi(){ return true; } }
-
user.module.ts
-> provider 넣기
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; import { UsersResolver } from './users.resolver'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], //service는 repository 필요로 하기 떄문에 providers:[UsersResolver, UsersService], }) export class UsersModule {}
-
4.3
create Account 만들기
(mutation, dtos)
-
User graphQL object만들기
-
user.entity.ts
import { Field, InputType, ObjectType } from "@nestjs/graphql"; import { CoreEntity } from "src/common/entities/core.entity"; import { Column, Entity } from "typeorm"; type UserRole = "client" | "owner" | "delivery" @InputType({ isAbstract: true}) @ObjectType() @Entity() export class User extends CoreEntity{ //CoreEntity로 extend -> 모든 entities는 core.entity.ts에서 extends됨 @Column() @Field(type=>String) email: string; @Column() @Field(type=>String) password: string; @Column() @Field(type=>String) role: UserRole; }
-
core.entity
import { Field } from "@nestjs/graphql"; import { CreateDateColumn, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; export class CoreEntity { @PrimaryGeneratedColumn() @Field(type=>Number) //graphQL type만들기 id:number; @CreateDateColumn() @Field(type=>Date) createdAt:Date; @UpdateDateColumn() @Field(type=>Date) updatedAt:Date; }
-
-
Mutation 만들기
-
users.resolver.ts
import { Resolver, Query, Mutation, Args } from "@nestjs/graphql"; import { createAccountInput, CreateAccountOutput } from "./dtos/create-account.dto"; import { User } from "./entities/user.entity"; import { UsersService } from "./users.service"; @Resolver(of => User) export class UsersResolver { constructor( private readonly usersService: UsersService ){} @Query(returns => Boolean)//graphQL 루트 만들기 hi(){ return true; } @Mutation(returns =>CreateAccountOutput) createAccount(@Args("input") createAccountInput: createAccountInput ) {} //createAccountInput이라는 input type만듦 }
users에 dtos폴더 생성 & create-account 파일 생성
-
create-account.dto.ts
//dto 2개 -> create account의 입력과 출력 import { Field, InputType, ObjectType, PickType } from "@nestjs/graphql"; import { User } from "../entities/user.entity"; @InputType() export class createAccountInput extends PickType(User, [ "email", "password", "role" ]) {}//pickType의 class에 User와 우리가 가지고 싶은 거 @ObjectType() //graphQL Objecttype안에 만들어져야함 export class CreateAccountOutput{ @Field(type => String, {nullable:true}) error?: string; @Field(type => Boolean) ok: boolean; }
-
4.4 & 4.5
-
UserRole을 enum으로
-
user.entity.ts
import { Field, InputType, ObjectType, registerEnumType } from "@nestjs/graphql"; import { CoreEntity } from "src/common/entities/core.entity"; import { Column, Entity } from "typeorm"; enum UserRole { Client, //0 Owner, //1 Delivery, //2 } registerEnumType(UserRole, {name: "UserRole"}) //graphQL enum type만들기 @InputType({ isAbstract: true}) @ObjectType() @Entity() export class User extends CoreEntity{ //CoreEntity로 extend -> 모든 entities는 core.entity.ts에서 extends됨 @Column() @Field(type=>String) email: string; @Column() @Field(type=>String) password: string; @Column( { type: 'enum', enum : UserRole}) //데이터베이스에 type이 enum인 UserRole @Field(type=> UserRole) //type이 UserRole인 graphQL role: UserRole; }
-
users.service.ts -> id 검사
import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { createAccountInput } from "./dtos/create-account.dto"; import { User } from "./entities/user.entity"; @Injectable() export class UsersService{ constructor( @InjectRepository(User) private readonly users: Repository <User>//User entity의 InjectRepository 불러오기 & type이 repository이고 repository type은 user enitity ){} async createAccount({email, password, role}: createAccountInput): Promise<string | undefined>{ //check new user(that email does not exist) try { const exists = await this.users.findOne({email}) //findOne = 주어진 condition(환경)과 일치하는 첫 번째 entity 찾기 if(exists){ //make error return 'There is a uwer with that email already'; } await this.users.save(this.users.create({email, password, role})) //없다면 새로운 계정 create & save } catch(e){ return "Couldn't create account"; } // create user & hash the password } }
-
users.resolver.ts
-> 계정 만들기 확인
import { Resolver, Query, Mutation, Args} from "@nestjs/graphql"; import { createAccountInput, CreateAccountOutput } from "./dtos/create-account.dto"; import { User } from "./entities/user.entity"; import { UsersService } from "./users.service"; @Resolver(of => User) export class UsersResolver { constructor( private readonly usersService: UsersService ){} @Query(returns => Boolean)//graphQL 루트 만들기 hi(){ return true; } @Mutation(returns =>CreateAccountOutput) async createAccount(@Args("input") createAccountInput: createAccountInput, ): Promise <CreateAccountOutput>{ //createAccountInput이라는 input type만듦 try{ const error = await this.usersService.createAccount(createAccountInput); if (error){ // 계정이 있거나 만들 수 없다면 return { ok:false, error, }; } //에러가 없다면 return { ok: true, } } catch(e) { //에러 발생시 return{ error:e, ok: false } } } }
-
4.6
-
users.service.ts 에서 string이나 undefined대신 array 쓰는 방법
-> 해도 되고 안해도 됨
-
users.service.ts
import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { createAccountInput } from "./dtos/create-account.dto"; import { User } from "./entities/user.entity"; @Injectable() export class UsersService{ constructor( @InjectRepository(User) private readonly users: Repository <User>//User entity의 InjectRepository 불러오기 & type이 repository이고 repository type은 user enitity ){} async createAccount({email, password, role}: createAccountInput ): Promise <[boolean, string?]>{ //check new user(that email does not exist) try { const exists = await this.users.findOne({email}) //findOne = 주어진 condition(환경)과 일치하는 첫 번째 entity 찾기 if(exists){ //make error return [false, 'There is a uwer with that email already']; //boolean =false, error ="there~" } await this.users.save(this.users.create({email, password, role})) //없다면 새로운 계정 create & save return [true]; } catch(e){ return [false, "Couldn't create account"]; } // create user & hash the password } }
-
users.resolver.ts
import { Resolver, Query, Mutation, Args} from "@nestjs/graphql"; import { createAccountInput, CreateAccountOutput } from "./dtos/create-account.dto"; import { User } from "./entities/user.entity"; import { UsersService } from "./users.service"; @Resolver(of => User) export class UsersResolver { constructor( private readonly usersService: UsersService ){} @Query(returns => Boolean)//graphQL 루트 만들기 hi(){ return true; } @Mutation(returns =>CreateAccountOutput) async createAccount(@Args("input") createAccountInput: createAccountInput, ): Promise <CreateAccountOutput>{ //createAccountInput이라는 input type만듦 try{ const [ok, error] = await this.usersService.createAccount(createAccountInput); //에러가 없다면 return { ok, error, } } catch(error) { //에러 발생시 return{ error, ok: false } } } }
-
-
또는 object return 하기
-
users.service.ts
import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { createAccountInput } from "./dtos/create-account.dto"; import { User } from "./entities/user.entity"; @Injectable() export class UsersService{ constructor( @InjectRepository(User) private readonly users: Repository <User>//User entity의 InjectRepository 불러오기 & type이 repository이고 repository type은 user enitity ){} async createAccount({email, password, role}: createAccountInput ): Promise <{ok:boolean, error?: string }>{ //check new user(that email does not exist) try { const exists = await this.users.findOne({email}) //findOne = 주어진 condition(환경)과 일치하는 첫 번째 entity 찾기 if(exists){ //make error return {ok: false, error: 'There is a uwer with that email already'}; //boolean =false, error ="there~" } await this.users.save(this.users.create({email, password, role})) //없다면 새로운 계정 create & save return {ok: true}; } catch(e){ return {ok: false, error: "Couldn't create account"}; } // create user & hash the password } }
-
users.resolver.ts
import { Resolver, Query, Mutation, Args} from "@nestjs/graphql"; import { createAccountInput, CreateAccountOutput } from "./dtos/create-account.dto"; import { User } from "./entities/user.entity"; import { UsersService } from "./users.service"; @Resolver(of => User) export class UsersResolver { constructor( private readonly usersService: UsersService ){} @Query(returns => Boolean)//graphQL 루트 만들기 hi(){ return true; } @Mutation(returns =>CreateAccountOutput) async createAccount(@Args("input") createAccountInput: createAccountInput, ): Promise <CreateAccountOutput>{ //createAccountInput이라는 input type만듦 try{ const {ok, error} = await this.usersService.createAccount(createAccountInput); //에러가 없다면 return { ok, error, } } catch(error) { //에러 발생시 return{ error, ok: false } } } }
-> object로 해도 되고, array로 해도 됨
-
4.7
password hash
-> 데이터베이스에 password를 그대로 저장 X
-
hash: 단방향 함수
=>다시 되돌릴 수 없음
-> 데이터베이스엔 hash를 저장
-
BeforeInsert(Insert하기 전에 실행)
-
bcrypt(hash하고 hash 확인하는데 사용하는 module)
-> npm i bcrypt
-> npm i @type/bcrypt –dev-only
-
user.entity.ts
import { Field, InputType, ObjectType, registerEnumType } from "@nestjs/graphql"; import { CoreEntity } from "src/common/entities/core.entity"; import { BeforeInsert, Column, Entity } from "typeorm"; import * as bcrypt from "bcrypt"; import { InternalServerError } from "http-errors"; import { InternalServerErrorException } from "@nestjs/common"; enum UserRole { Client, //0 Owner, //1 Delivery, //2 } registerEnumType(UserRole, {name: "UserRole"}) //graphQL enum type만들기 @InputType({ isAbstract: true}) @ObjectType() @Entity() export class User extends CoreEntity{ //CoreEntity로 extend -> 모든 entities는 core.entity.ts에서 extends됨 @Column() @Field(type=>String) email: string; @Column() @Field(type=>String) password: string; @Column( { type: 'enum', enum : UserRole}) //데이터베이스에 type이 enum인 UserRole @Field(type=> UserRole) //type이 UserRole인 graphQL role: UserRole; @BeforeInsert()//DB에 저장하기 전에 password hash해주기 async hashPassword(): Promise<void> { try{ this.password = await bcrypt.hash(this.password, 10); //hash round는 10으로 } catch(e){ console.log(e); throw new InternalServerErrorException(); } } }
-
4.8
- login resolver와 service method 만들기
-
dto에 login.dto.ts 파일 생성 & common에 dtos 폴더 생성, output.dto.ts 파일 생성
-
login.dto.ts
import { Field, ObjectType } from "@nestjs/graphql"; @ObjectType() //graphQL Objecttype안에 만들어져야함 export class MutationOutput{ @Field(type => String, {nullable:true}) error?: string; @Field(type => Boolean) ok: boolean; }
-
create-account.dto.ts
-> extends해줌
//dto 2개 -> create account의 입력과 출력 import { Field, InputType, ObjectType, PickType } from "@nestjs/graphql"; import { MutationOutput } from "src/common/dtos/output.dto"; import { User } from "../entities/user.entity"; @InputType() export class createAccountInput extends PickType(User, [ "email", "password", "role" ]) {}//pickType의 class에 User와 우리가 가지고 싶은 거 @ObjectType() export class CreateAccountOutput extends MutationOutput{}
-
login.dto.ts
import { Field, InputType, ObjectType, PickType } from "@nestjs/graphql"; import { MutationOutput } from "src/common/dtos/output.dto"; import { User } from "../entities/user.entity"; @InputType() export class LoginInput extends PickType(User, ["email", "password"]){} @ObjectType() export class LoginOutput extends MutationOutput{ @Field(type => String) token:string; }
-
users.resolver.ts
-> mutation 만들기
import { Resolver, Query, Mutation, Args} from "@nestjs/graphql"; import { createAccountInput, CreateAccountOutput } from "./dtos/create-account.dto"; import { LoginInput, LoginOutput} from "./dtos/login.dto"; import { User } from "./entities/user.entity"; import { UsersService } from "./users.service"; @Resolver(of => User) export class UsersResolver { constructor( private readonly usersService: UsersService ){} @Query(returns => Boolean)//graphQL 루트 만들기 hi(){ return true; } @Mutation(returns =>CreateAccountOutput) async createAccount(@Args("input") createAccountInput: createAccountInput, ): Promise <CreateAccountOutput>{ //createAccountInput이라는 input type만듦 try{ const [ok, error] = await this.usersService.createAccount(createAccountInput); //에러가 없다면 return { ok, error, } } catch(error) { //에러 발생시 return{ error, ok: false } } } @Mutation(returns => LoginOutput) async login(@Args('input') loginInput: LoginInput ){}//input Arguments 필요 }
-
user.entity.ts
-> validation 해주기
-
email, role
import { Field, InputType, ObjectType, registerEnumType } from "@nestjs/graphql"; import { CoreEntity } from "src/common/entities/core.entity"; import { BeforeInsert, Column, Entity } from "typeorm"; import * as bcrypt from "bcrypt"; import { InternalServerError } from "http-errors"; import { InternalServerErrorException } from "@nestjs/common"; import { IsEmail, IsEnum, IsString } from "class-validator"; enum UserRole { Client, //0 Owner, //1 Delivery, //2 } registerEnumType(UserRole, {name: "UserRole"}) //graphQL enum type만들기 @InputType({ isAbstract: true}) @ObjectType() @Entity() export class User extends CoreEntity{ //CoreEntity로 extend -> 모든 entities는 core.entity.ts에서 extends됨 @Column() @Field(type=>String) @IsEmail() email: string; @Column() @Field(type=>String) password: string; @Column( { type: 'enum', enum : UserRole}) //데이터베이스에 type이 enum인 UserRole @Field(type=> UserRole) //type이 UserRole인 graphQL @IsEnum(UserRole) role: UserRole; @BeforeInsert()//DB에 저장하기 전에 password hash해주기 async hashPassword(): Promise<void> { try{ this.password = await bcrypt.hash(this.password, 10); //hash round는 10으로 } catch(e){ console.log(e); throw new InternalServerErrorException(); } } }
-
-
4.9
service에 login function만들기
- find the user with the email
- check if the password is correct
- make a JWT and give it to the user
-
users.service.ts
import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { createAccountInput } from "./dtos/create-account.dto"; import { LoginInput } from "./dtos/login.dto"; import { User } from "./entities/user.entity"; @Injectable() export class UsersService{ constructor( @InjectRepository(User) private readonly users: Repository <User>//User entity의 InjectRepository 불러오기 & type이 repository이고 repository type은 user enitity ){} async createAccount({email, password, role}: createAccountInput ): Promise <{ok: boolean, error? :string }>{ //check new user(that email does not exist) try { const exists = await this.users.findOne({email}) //findOne = 주어진 condition(환경)과 일치하는 첫 번째 entity 찾기 if(exists){ //make error return {ok: false, error: 'There is a uwer with that email already'}; //boolean =false, error ="there~" } await this.users.save(this.users.create({email, password, role})) //없다면 새로운 계정 create & save return {ok: true}; } catch(e){ return {ok: false, error: "Couldn't create account"}; } // create user & hash the password } async login({ email, password }: LoginInput): Promise<{ok: boolean; error?:string, token?: string}> { //make a JWT and give it to the user try{ // find the user with the email const user = await this.users.findOne({ email }); if(!user){ //user가 존재하지 않는다면 return { ok:false, error: 'User not found', } } //check if the password is correct //비밀번호를 hash 한후 데이터베이스에 있는 hash된 비번과 같은지 확인 const passwordCorrect = await user.checkPassword(password); //여기의 user와 위의 const user와는 다름.. 전자는 entity if (!passwordCorrect){ return{ ok:false, error:"Wrong password" } } return{ ok:true, token: 'llalaalalala', } }catch(error){ return{ ok: false, error, } } } }
-
user.entity.ts
-> password check
import { Field, InputType, ObjectType, registerEnumType } from "@nestjs/graphql"; import { CoreEntity } from "src/common/entities/core.entity"; import { BeforeInsert, Column, Entity } from "typeorm"; import * as bcrypt from "bcrypt"; import { InternalServerError } from "http-errors"; import { InternalServerErrorException } from "@nestjs/common"; import { IsEmail, IsEnum, IsString } from "class-validator"; enum UserRole { Client, //0 Owner, //1 Delivery, //2 } registerEnumType(UserRole, {name: "UserRole"}) //graphQL enum type만들기 @InputType({ isAbstract: true}) @ObjectType() @Entity() export class User extends CoreEntity{ //CoreEntity로 extend -> 모든 entities는 core.entity.ts에서 extends됨 @Column() @Field(type=>String) @IsEmail() email: string; @Column() @Field(type=>String) password: string; @Column( { type: 'enum', enum : UserRole}) //데이터베이스에 type이 enum인 UserRole @Field(type=> UserRole) //type이 UserRole인 graphQL @IsEnum(UserRole) role: UserRole; @BeforeInsert()//DB에 저장하기 전에 password hash해주기 async hashPassword(): Promise<void> { try{ this.password = await bcrypt.hash(this.password, 10); //hash round는 10으로 } catch(e){ console.log(e); throw new InternalServerErrorException(); } } async checkPassword(aPassword:string): Promise<boolean>{ try{ const ok = await bcrypt.compare(aPassword , this.password) //같은지 확인 return ok; }catch(e){ console.log(e); throw new InternalServerErrorException(); } } }
-
login.dto.ts
-> toke type에 nullable도
import { Field, InputType, ObjectType, PickType } from "@nestjs/graphql"; import { MutationOutput } from "src/common/dtos/output.dto"; import { User } from "../entities/user.entity"; @InputType() export class LoginInput extends PickType(User, ["email", "password"]){} @ObjectType() export class LoginOutput extends MutationOutput{ @Field(type => String, { nullable:true }) //token이 없을 수도 있으니 nullable token?:string; }
-
users.resolver.ts
-> loginOutput
import { Resolver, Query, Mutation, Args} from "@nestjs/graphql"; import { createAccountInput, CreateAccountOutput } from "./dtos/create-account.dto"; import { LoginInput, LoginOutput} from "./dtos/login.dto"; import { User } from "./entities/user.entity"; import { UsersService } from "./users.service"; @Resolver(of => User) export class UsersResolver { constructor( private readonly usersService: UsersService ){} @Query(returns => Boolean)//graphQL 루트 만들기 hi(){ return true; } @Mutation(returns =>CreateAccountOutput) async createAccount(@Args("input") createAccountInput: createAccountInput, ): Promise <CreateAccountOutput>{ //createAccountInput이라는 input type만듦 try{ return this.usersService.createAccount(createAccountInput); //에러가 없다면 } catch(error) { //에러 발생시 return{ error, ok: false, } } } @Mutation(returns => LoginOutput) async login(@Args('input') loginInput: LoginInput ): Promise<LoginOutput>{//input Arguments 필요 try { return this.usersService.login(loginInput) //loginInput 저장 } catch(error){ return{ ok: false, error, }; } } }