-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
316 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { | ||
createParamDecorator, | ||
ExecutionContext, | ||
HttpException, | ||
} from '@nestjs/common'; | ||
|
||
export const Auth = createParamDecorator( | ||
(data: unknown, context: ExecutionContext) => { | ||
const request = context.switchToHttp().getRequest(); | ||
const user = request.user; | ||
if (user) { | ||
return user; | ||
} else { | ||
throw new HttpException('Unauthorized', 401); | ||
} | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
export class RegisterUserRequest { | ||
username: string; | ||
password: string; | ||
first_name: string; | ||
last_name?: string; | ||
email?: string; | ||
phone?: string; | ||
} | ||
|
||
export class UserResponse { | ||
username: string; | ||
first_name: string; | ||
last_name?: string; | ||
email?: string; | ||
phone?: string; | ||
token?: string; | ||
} | ||
|
||
export class LoginUserRequest { | ||
username: string; | ||
password: string; | ||
} | ||
|
||
export class UpdateUserRequest { | ||
first_name?: string; | ||
password?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export class WebResponse<T> { | ||
data?: T; | ||
errors?: string; | ||
paging?: Paging; | ||
} | ||
|
||
export class Paging { | ||
size: number; | ||
total_page: number; | ||
current_page: number; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { | ||
Body, | ||
Controller, | ||
Delete, | ||
Get, | ||
HttpCode, | ||
Patch, | ||
Post, | ||
} from '@nestjs/common'; | ||
import { UserService } from './user.service'; | ||
import { WebResponse } from '../model/web.model'; | ||
import { | ||
LoginUserRequest, | ||
RegisterUserRequest, | ||
UpdateUserRequest, | ||
UserResponse, | ||
} from '../model/user.model'; | ||
import { Auth } from '../common/auth.decorator'; | ||
import { User } from '@prisma/client'; | ||
|
||
@Controller('/api/users') | ||
export class UserController { | ||
constructor(private userService: UserService) {} | ||
|
||
@Post() | ||
@HttpCode(200) | ||
async register( | ||
@Body() request: RegisterUserRequest, | ||
): Promise<WebResponse<UserResponse>> { | ||
const result = await this.userService.register(request); | ||
return { | ||
data: result, | ||
}; | ||
} | ||
|
||
@Post('/login') | ||
@HttpCode(200) | ||
async login( | ||
@Body() request: LoginUserRequest, | ||
): Promise<WebResponse<UserResponse>> { | ||
const result = await this.userService.login(request); | ||
return { | ||
data: result, | ||
}; | ||
} | ||
|
||
@Get('/current') | ||
@HttpCode(200) | ||
async get(@Auth() user: User): Promise<WebResponse<UserResponse>> { | ||
const result = await this.userService.get(user); | ||
return { | ||
data: result, | ||
}; | ||
} | ||
|
||
@Patch('/current') | ||
@HttpCode(200) | ||
async update( | ||
@Auth() user: User, | ||
@Body() request: UpdateUserRequest, | ||
): Promise<WebResponse<UserResponse>> { | ||
const result = await this.userService.update(user, request); | ||
return { | ||
data: result, | ||
}; | ||
} | ||
|
||
@Delete('/current') | ||
@HttpCode(200) | ||
async logout(@Auth() user: User): Promise<WebResponse<boolean>> { | ||
await this.userService.logout(user); | ||
return { | ||
data: true, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { UserService } from './user.service'; | ||
import { UserController } from './user.controller'; | ||
|
||
@Module({ | ||
providers: [UserService], | ||
controllers: [UserController], | ||
}) | ||
export class UserModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import { HttpException, Inject, Injectable } from '@nestjs/common'; | ||
import { | ||
RegisterUserRequest, | ||
UserResponse, | ||
LoginUserRequest, | ||
UpdateUserRequest, | ||
} from 'src/model/user.model'; | ||
import { ValidationService } from 'src/common/validation.service'; | ||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; | ||
import { Logger } from 'winston'; | ||
import { PrismaService } from 'src/common/prisma.service'; | ||
import { UserValidation } from './user.validation'; | ||
import * as bcrypt from 'bcrypt'; | ||
import { v4 as uuid } from 'uuid'; | ||
import { User } from '@prisma/client'; | ||
|
||
@Injectable() | ||
export class UserService { | ||
constructor( | ||
private validationService: ValidationService, | ||
@Inject(WINSTON_MODULE_PROVIDER) private logger: Logger, | ||
private prismaService: PrismaService, | ||
) {} | ||
|
||
async register(request: RegisterUserRequest): Promise<UserResponse> { | ||
this.logger.info(`Register new yser ${JSON.stringify(request)}`); | ||
const registerRequest: RegisterUserRequest = | ||
this.validationService.validate(UserValidation.REGISTER, request); | ||
|
||
const totalUserWithSameUsername = await this.prismaService.user.count({ | ||
where: { | ||
username: registerRequest.username, | ||
}, | ||
}); | ||
|
||
if (totalUserWithSameUsername != 0) { | ||
throw new HttpException('Username already exists', 400); | ||
} | ||
|
||
registerRequest.password = await bcrypt.hash(registerRequest.password, 10); | ||
|
||
const user = await this.prismaService.user.create({ | ||
data: registerRequest, | ||
}); | ||
|
||
return { | ||
username: user.username, | ||
first_name: user.first_name, | ||
}; | ||
} | ||
|
||
async login(request: LoginUserRequest): Promise<UserResponse> { | ||
this.logger.debug(`UserService.login(${JSON.stringify(request)})`); | ||
const loginRequest: LoginUserRequest = this.validationService.validate( | ||
UserValidation.LOGIN, | ||
request, | ||
); | ||
|
||
let user = await this.prismaService.user.findUnique({ | ||
where: { | ||
username: loginRequest.username, | ||
}, | ||
}); | ||
|
||
if (!user) { | ||
throw new HttpException('Username or password is invalid', 401); | ||
} | ||
|
||
const isPasswordValid = await bcrypt.compare( | ||
loginRequest.password, | ||
user.password, | ||
); | ||
|
||
if (!isPasswordValid) { | ||
throw new HttpException('Username or password is invalid', 401); | ||
} | ||
|
||
user = await this.prismaService.user.update({ | ||
where: { | ||
username: loginRequest.username, | ||
}, | ||
data: { | ||
token: uuid(), | ||
}, | ||
}); | ||
|
||
return { | ||
username: user.username, | ||
first_name: user.first_name, | ||
token: user.token, | ||
}; | ||
} | ||
|
||
async get(user: User): Promise<UserResponse> { | ||
return { | ||
username: user.username, | ||
first_name: user.first_name, | ||
}; | ||
} | ||
|
||
async update(user: User, request: UpdateUserRequest): Promise<UserResponse> { | ||
this.logger.debug( | ||
`UserService.update( ${JSON.stringify(user)} , ${JSON.stringify(request)} )`, | ||
); | ||
|
||
const updateRequest: UpdateUserRequest = this.validationService.validate( | ||
UserValidation.UPDATE, | ||
request, | ||
); | ||
|
||
if (updateRequest.first_name) { | ||
user.first_name = updateRequest.first_name; | ||
} | ||
|
||
if (updateRequest.password) { | ||
user.password = await bcrypt.hash(updateRequest.password, 10); | ||
} | ||
|
||
const result = await this.prismaService.user.update({ | ||
where: { | ||
username: user.username, | ||
}, | ||
data: user, | ||
}); | ||
|
||
return { | ||
first_name: result.first_name, | ||
username: result.username, | ||
}; | ||
} | ||
|
||
async logout(user: User): Promise<UserResponse> { | ||
const result = await this.prismaService.user.update({ | ||
where: { | ||
username: user.username, | ||
}, | ||
data: { | ||
token: null, | ||
}, | ||
}); | ||
|
||
return { | ||
username: result.username, | ||
first_name: result.first_name, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { z, ZodType } from 'zod'; | ||
|
||
export class UserValidation { | ||
static readonly REGISTER: ZodType = z.object({ | ||
username: z.string().min(1).max(100), | ||
password: z.string().min(1).max(100), | ||
first_name: z.string().min(1).max(100), | ||
last_name: z.string().min(1).max(100).optional(), | ||
email: z.string().min(1).max(100).optional(), | ||
phone: z.string().min(1).max(100).optional(), | ||
}); | ||
|
||
static readonly LOGIN: ZodType = z.object({ | ||
username: z.string().min(1).max(100), | ||
password: z.string().min(1).max(100), | ||
}); | ||
|
||
static readonly UPDATE: ZodType = z.object({ | ||
first_name: z.string().min(1).max(100).optional(), | ||
last_name: z.string().min(1).max(100).optional(), | ||
email: z.string().min(1).max(100).optional(), | ||
phone: z.string().min(1).max(100).optional(), | ||
name: z.string().min(1).max(100).optional(), | ||
password: z.string().min(1).max(100).optional(), | ||
}); | ||
} |