mirror of
https://github.com/ershisan99/flashcards-api.git
synced 2025-12-17 05:09:26 +00:00
add /auth/patch endpoint
This commit is contained in:
@@ -5,17 +5,22 @@ import {
|
|||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
Param,
|
Param,
|
||||||
|
Patch,
|
||||||
Post,
|
Post,
|
||||||
Request,
|
Request,
|
||||||
Res,
|
Res,
|
||||||
Response,
|
Response,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
|
UploadedFiles,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
|
UseInterceptors,
|
||||||
} from '@nestjs/common'
|
} from '@nestjs/common'
|
||||||
import { CommandBus } from '@nestjs/cqrs'
|
import { CommandBus } from '@nestjs/cqrs'
|
||||||
|
import { FileFieldsInterceptor } from '@nestjs/platform-express'
|
||||||
import {
|
import {
|
||||||
ApiBadRequestResponse,
|
ApiBadRequestResponse,
|
||||||
ApiBody,
|
ApiBody,
|
||||||
|
ApiConsumes,
|
||||||
ApiNoContentResponse,
|
ApiNoContentResponse,
|
||||||
ApiNotFoundResponse,
|
ApiNotFoundResponse,
|
||||||
ApiOperation,
|
ApiOperation,
|
||||||
@@ -34,6 +39,7 @@ import {
|
|||||||
ResendVerificationEmailDto,
|
ResendVerificationEmailDto,
|
||||||
} from './dto'
|
} from './dto'
|
||||||
import { ResetPasswordDto } from './dto/reset-password.dto'
|
import { ResetPasswordDto } from './dto/reset-password.dto'
|
||||||
|
import { UpdateUserDataDto } from './dto/update-user-data.dto'
|
||||||
import { LoginResponse, UserEntity } from './entities/auth.entity'
|
import { LoginResponse, UserEntity } from './entities/auth.entity'
|
||||||
import { JwtAuthGuard, JwtRefreshGuard, LocalAuthGuard } from './guards'
|
import { JwtAuthGuard, JwtRefreshGuard, LocalAuthGuard } from './guards'
|
||||||
import {
|
import {
|
||||||
@@ -45,6 +51,7 @@ import {
|
|||||||
ResetPasswordCommand,
|
ResetPasswordCommand,
|
||||||
SendPasswordRecoveryEmailCommand,
|
SendPasswordRecoveryEmailCommand,
|
||||||
VerifyEmailCommand,
|
VerifyEmailCommand,
|
||||||
|
UpdateUserCommand,
|
||||||
} from './use-cases'
|
} from './use-cases'
|
||||||
|
|
||||||
@ApiTags('Auth')
|
@ApiTags('Auth')
|
||||||
@@ -63,6 +70,23 @@ export class AuthController {
|
|||||||
return await this.commandBus.execute(new GetCurrentUserDataCommand(userId))
|
return await this.commandBus.execute(new GetCurrentUserDataCommand(userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiConsumes('multipart/form-data')
|
||||||
|
@ApiOperation({ description: 'Update current user data.', summary: 'Update user data' })
|
||||||
|
@ApiUnauthorizedResponse({ description: 'Not logged in' })
|
||||||
|
@ApiBadRequestResponse({ description: 'User not found' })
|
||||||
|
@UseInterceptors(FileFieldsInterceptor([{ name: 'avatar', maxCount: 1 }]))
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Patch('me')
|
||||||
|
async updateUserData(
|
||||||
|
@Request() req,
|
||||||
|
@UploadedFiles()
|
||||||
|
files: { avatar: Express.Multer.File[] },
|
||||||
|
@Body() body: UpdateUserDataDto
|
||||||
|
): Promise<UserEntity> {
|
||||||
|
const userId = req.user.id
|
||||||
|
|
||||||
|
return await this.commandBus.execute(new UpdateUserCommand(userId, body, files.avatar?.[0]))
|
||||||
|
}
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
description: 'Sign in using email and password. Must have an account to do so.',
|
description: 'Sign in using email and password. Must have an account to do so.',
|
||||||
summary: 'Sign in using email and password. Must have an account to do so.',
|
summary: 'Sign in using email and password. Must have an account to do so.',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Module } from '@nestjs/common'
|
import { Module } from '@nestjs/common'
|
||||||
import { CqrsModule } from '@nestjs/cqrs'
|
import { CqrsModule } from '@nestjs/cqrs'
|
||||||
|
|
||||||
|
import { FileUploadService } from '../../infrastructure/file-upload-service/file-upload.service'
|
||||||
import { UsersModule } from '../users/users.module'
|
import { UsersModule } from '../users/users.module'
|
||||||
|
|
||||||
import { AuthController } from './auth.controller'
|
import { AuthController } from './auth.controller'
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
ResetPasswordHandler,
|
ResetPasswordHandler,
|
||||||
SendPasswordRecoveryEmailHandler,
|
SendPasswordRecoveryEmailHandler,
|
||||||
VerifyEmailHandler,
|
VerifyEmailHandler,
|
||||||
|
UpdateUserHandler,
|
||||||
} from './use-cases'
|
} from './use-cases'
|
||||||
|
|
||||||
const commandHandlers = [
|
const commandHandlers = [
|
||||||
@@ -27,12 +29,13 @@ const commandHandlers = [
|
|||||||
ResetPasswordHandler,
|
ResetPasswordHandler,
|
||||||
SendPasswordRecoveryEmailHandler,
|
SendPasswordRecoveryEmailHandler,
|
||||||
VerifyEmailHandler,
|
VerifyEmailHandler,
|
||||||
|
UpdateUserHandler,
|
||||||
]
|
]
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [UsersModule, CqrsModule],
|
imports: [UsersModule, CqrsModule],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthService, LocalStrategy, AuthRepository, ...commandHandlers],
|
providers: [AuthService, LocalStrategy, AuthRepository, FileUploadService, ...commandHandlers],
|
||||||
exports: [AuthService, CqrsModule],
|
exports: [AuthService, CqrsModule],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
15
src/modules/auth/dto/update-user-data.dto.ts
Normal file
15
src/modules/auth/dto/update-user-data.dto.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { PickType } from '@nestjs/swagger'
|
||||||
|
import { IsEmail, IsOptional } from 'class-validator'
|
||||||
|
|
||||||
|
import { User } from '../entities/auth.entity'
|
||||||
|
|
||||||
|
export class UpdateUserDataDto extends PickType(User, ['name', 'email', 'avatar'] as const) {
|
||||||
|
avatar: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEmail()
|
||||||
|
email: string
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { OmitType } from '@nestjs/swagger'
|
import { ApiProperty, OmitType } from '@nestjs/swagger'
|
||||||
|
|
||||||
export class User {
|
export class User {
|
||||||
id: string
|
id: string
|
||||||
@@ -6,9 +6,10 @@ export class User {
|
|||||||
password: string
|
password: string
|
||||||
isEmailVerified: boolean
|
isEmailVerified: boolean
|
||||||
name: string
|
name: string
|
||||||
|
@ApiProperty({ type: 'string', format: 'binary' })
|
||||||
avatar: string
|
avatar: string
|
||||||
created: string
|
created: Date
|
||||||
updated: string
|
updated: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LoginResponse {
|
export class LoginResponse {
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ export * from './resend-verification-email-use-case'
|
|||||||
export * from './reset-password-use-case'
|
export * from './reset-password-use-case'
|
||||||
export * from './refresh-token-use-case'
|
export * from './refresh-token-use-case'
|
||||||
export * from './send-password-recovery-email-use-case'
|
export * from './send-password-recovery-email-use-case'
|
||||||
|
export * from './update-user-use-case'
|
||||||
export * from './verify-email-use-case'
|
export * from './verify-email-use-case'
|
||||||
|
|||||||
61
src/modules/auth/use-cases/update-user-use-case.ts
Normal file
61
src/modules/auth/use-cases/update-user-use-case.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||||
|
import { pick } from 'remeda'
|
||||||
|
|
||||||
|
import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service'
|
||||||
|
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||||
|
import { UsersService } from '../../users/services/users.service'
|
||||||
|
import { UpdateUserDataDto } from '../dto/update-user-data.dto'
|
||||||
|
import { UserEntity } from '../entities/auth.entity'
|
||||||
|
|
||||||
|
export class UpdateUserCommand {
|
||||||
|
constructor(
|
||||||
|
public readonly userId: string,
|
||||||
|
public readonly user: UpdateUserDataDto,
|
||||||
|
public readonly avatar: Express.Multer.File
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CommandHandler(UpdateUserCommand)
|
||||||
|
export class UpdateUserHandler implements ICommandHandler<UpdateUserCommand> {
|
||||||
|
constructor(
|
||||||
|
private readonly usersRepository: UsersRepository,
|
||||||
|
private readonly usersService: UsersService,
|
||||||
|
private readonly fileUploadService: FileUploadService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(command: UpdateUserCommand): Promise<UserEntity | null> {
|
||||||
|
let avatar
|
||||||
|
|
||||||
|
if (command.avatar) {
|
||||||
|
const addAvatarImagePromise = this.fileUploadService.uploadFile(
|
||||||
|
command.avatar?.buffer,
|
||||||
|
command.avatar?.originalname
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await addAvatarImagePromise
|
||||||
|
|
||||||
|
avatar = result.fileUrl
|
||||||
|
} else if (command.user.avatar === '') {
|
||||||
|
avatar = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUser = await this.usersRepository.updateUser(command.userId, {
|
||||||
|
...command.user,
|
||||||
|
avatar,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!updatedUser) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return pick(updatedUser, [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'isEmailVerified',
|
||||||
|
'avatar',
|
||||||
|
'created',
|
||||||
|
'updated',
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,6 +95,17 @@ export class UsersRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateUser(id: string, data: Prisma.userUpdateInput): Promise<User> {
|
||||||
|
try {
|
||||||
|
return await this.prisma.user.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(e?.message || e)
|
||||||
|
throw new InternalServerErrorException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
async deleteUserById(id: string): Promise<boolean> {
|
async deleteUserById(id: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const result = await this.prisma.user.delete({
|
const result = await this.prisma.user.delete({
|
||||||
|
|||||||
Reference in New Issue
Block a user