mirror of
https://github.com/ershisan99/flashcards-api.git
synced 2025-12-24 20:59:28 +00:00
add error handling for db calls
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Request,
|
||||
Res,
|
||||
@@ -24,6 +25,8 @@ import {
|
||||
LogoutCommand,
|
||||
RefreshTokenCommand,
|
||||
ResendVerificationEmailCommand,
|
||||
ResetPasswordCommand,
|
||||
SendPasswordRecoveryEmailCommand,
|
||||
VerifyEmailCommand,
|
||||
} from './use-cases'
|
||||
|
||||
@@ -97,4 +100,14 @@ export class AuthController {
|
||||
accessToken: newTokens.accessToken,
|
||||
}
|
||||
}
|
||||
|
||||
@Post('recover-password')
|
||||
async recoverPassword(@Body('email') email: string) {
|
||||
return await this.commandBus.execute(new SendPasswordRecoveryEmailCommand(email))
|
||||
}
|
||||
|
||||
@Post('reset-password/:token')
|
||||
async resetPassword(@Body('password') password: string, @Param('token') token: string) {
|
||||
return await this.commandBus.execute(new ResetPasswordCommand(token, password))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
LogoutHandler,
|
||||
RefreshTokenHandler,
|
||||
ResendVerificationEmailHandler,
|
||||
ResetPasswordHandler,
|
||||
SendPasswordRecoveryEmailHandler,
|
||||
VerifyEmailHandler,
|
||||
} from './use-cases'
|
||||
import { AuthRepository } from './infrastructure/auth.repository'
|
||||
@@ -20,6 +22,8 @@ const commandHandlers = [
|
||||
LogoutHandler,
|
||||
RefreshTokenHandler,
|
||||
ResendVerificationEmailHandler,
|
||||
ResetPasswordHandler,
|
||||
SendPasswordRecoveryEmailHandler,
|
||||
VerifyEmailHandler,
|
||||
]
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
async validate(email: string, password: string): Promise<any> {
|
||||
const newCredentials = await this.authService.checkCredentials(email, password)
|
||||
if (newCredentials.resultCode === 1) {
|
||||
throw new UnauthorizedException()
|
||||
throw new UnauthorizedException('Invalid credentials')
|
||||
}
|
||||
return newCredentials
|
||||
}
|
||||
|
||||
143
src/modules/auth/tests/integration/auth.e2e.spec.ts
Normal file
143
src/modules/auth/tests/integration/auth.e2e.spec.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import * as request from 'supertest'
|
||||
import { HttpStatus } from '@nestjs/common'
|
||||
import { AppModule } from '../../../../app.module'
|
||||
import { RegistrationDto } from '../../dto/registration.dto'
|
||||
|
||||
describe('AuthController (e2e)', () => {
|
||||
let app
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile()
|
||||
|
||||
app = moduleFixture.createNestApplication()
|
||||
await app.init()
|
||||
})
|
||||
|
||||
it('/POST sign-up', () => {
|
||||
const registrationData: RegistrationDto = {
|
||||
name: 'John',
|
||||
email: 'john@gmail.com',
|
||||
password: 'secret',
|
||||
}
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/sign-up')
|
||||
.send(registrationData)
|
||||
.expect(HttpStatus.CREATED)
|
||||
})
|
||||
|
||||
it('/POST sign-up (duplicate)', () => {
|
||||
const registrationData: RegistrationDto = {
|
||||
name: 'John',
|
||||
email: 'john@gmail.com',
|
||||
password: 'secret',
|
||||
}
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/sign-up')
|
||||
.send(registrationData)
|
||||
.expect(HttpStatus.BAD_REQUEST)
|
||||
})
|
||||
|
||||
it('/POST login', () => {
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/login')
|
||||
.send({ email: 'john@gmail.com', password: 'secret' })
|
||||
.expect(HttpStatus.OK)
|
||||
.then(response => {
|
||||
expect(response.body.accessToken).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
it('/POST login (invalid)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/login')
|
||||
.send({ email: 'john@gmail.com', password: 'wrong_password' })
|
||||
.expect(HttpStatus.UNAUTHORIZED)
|
||||
})
|
||||
|
||||
it('/GET me', async () => {
|
||||
const loginResponse = await request(app.getHttpServer())
|
||||
.post('/auth/login')
|
||||
.send({ email: 'john@gmail.com', password: 'secret' })
|
||||
.expect(HttpStatus.OK)
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.get('/auth/me')
|
||||
.set('Authorization', `Bearer ${loginResponse.body.accessToken}`)
|
||||
.expect(HttpStatus.OK)
|
||||
})
|
||||
it('/POST verify-email', async () => {
|
||||
// Assuming "john@gmail.com" has a verification code "123456"
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/verify-email')
|
||||
.send({ code: '123456' })
|
||||
.expect(HttpStatus.OK)
|
||||
})
|
||||
|
||||
it('/POST verify-email (invalid)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/verify-email')
|
||||
.send({ code: 'wrong_code' })
|
||||
.expect(HttpStatus.UNAUTHORIZED)
|
||||
})
|
||||
|
||||
it('/POST resend-verification-email', async () => {
|
||||
// Assuming "john@gmail.com" has a user ID "1"
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/resend-verification-email')
|
||||
.send({ userId: '1' })
|
||||
.expect(HttpStatus.OK)
|
||||
})
|
||||
|
||||
it('/POST logout', async () => {
|
||||
const loginResponse = await request(app.getHttpServer())
|
||||
.post('/auth/login')
|
||||
.send({ email: 'john@gmail.com', password: 'secret' })
|
||||
.expect(HttpStatus.OK)
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/logout')
|
||||
.set('Cookie', [`refreshToken=${loginResponse.body.refreshToken}`])
|
||||
.expect(HttpStatus.OK)
|
||||
})
|
||||
|
||||
it('/GET refresh-token', async () => {
|
||||
const loginResponse = await request(app.getHttpServer())
|
||||
.post('/auth/login')
|
||||
.send({ email: 'john@gmail.com', password: 'secret' })
|
||||
.expect(HttpStatus.OK)
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.get('/auth/refresh-token')
|
||||
.set('Cookie', [`refreshToken=${loginResponse.body.refreshToken}`])
|
||||
.expect(HttpStatus.OK)
|
||||
.then(response => {
|
||||
expect(response.body.accessToken).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
it('/POST recover-password', () => {
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/recover-password')
|
||||
.send({ email: 'john@gmail.com' })
|
||||
.expect(HttpStatus.OK)
|
||||
})
|
||||
|
||||
it('/POST reset-password/:token', () => {
|
||||
// Assuming "john@gmail.com" has a password reset token "abcdef"
|
||||
return request(app.getHttpServer())
|
||||
.post('/auth/reset-password/abcdef')
|
||||
.send({ password: 'new_password' })
|
||||
.expect(HttpStatus.OK)
|
||||
})
|
||||
|
||||
// Add more tests for the other endpoints in a similar way
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close()
|
||||
})
|
||||
})
|
||||
@@ -4,3 +4,5 @@ export * from './logout-use-case'
|
||||
export * from './resend-verification-email-use-case'
|
||||
export * from './refresh-token-use-case'
|
||||
export * from './verify-email-use-case'
|
||||
export * from './send-password-recovery-email-use-case'
|
||||
export * from './reset-password-use-case'
|
||||
|
||||
40
src/modules/auth/use-cases/reset-password-use-case.ts
Normal file
40
src/modules/auth/use-cases/reset-password-use-case.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common'
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
import { UsersService } from '../../users/services/users.service'
|
||||
|
||||
export class ResetPasswordCommand {
|
||||
constructor(public readonly resetPasswordToken: string, public readonly newPassword: string) {}
|
||||
}
|
||||
|
||||
@CommandHandler(ResetPasswordCommand)
|
||||
export class ResetPasswordHandler implements ICommandHandler<ResetPasswordCommand> {
|
||||
constructor(
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly usersService: UsersService
|
||||
) {}
|
||||
|
||||
async execute(command: ResetPasswordCommand) {
|
||||
const user = await this.usersRepository.findUserByPasswordResetToken(command.resetPasswordToken)
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('Incorrect or expired password reset token')
|
||||
}
|
||||
if (!command.newPassword) {
|
||||
throw new BadRequestException('Password is required')
|
||||
}
|
||||
|
||||
const newPasswordHash = await this.usersService.generateHash(command.newPassword)
|
||||
|
||||
const updatedUser = await this.usersRepository.resetPasswordAndDeleteToken(
|
||||
user.id,
|
||||
newPasswordHash
|
||||
)
|
||||
|
||||
if (!updatedUser) {
|
||||
throw new NotFoundException('Incorrect or expired password reset token')
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { NotFoundException } from '@nestjs/common'
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
import { UsersService } from '../../users/services/users.service'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export class SendPasswordRecoveryEmailCommand {
|
||||
constructor(public readonly email: string) {}
|
||||
}
|
||||
|
||||
@CommandHandler(SendPasswordRecoveryEmailCommand)
|
||||
export class SendPasswordRecoveryEmailHandler
|
||||
implements ICommandHandler<SendPasswordRecoveryEmailCommand>
|
||||
{
|
||||
constructor(
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly usersService: UsersService
|
||||
) {}
|
||||
|
||||
async execute(command: SendPasswordRecoveryEmailCommand) {
|
||||
const user = await this.usersRepository.findUserByEmail(command.email)
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found')
|
||||
}
|
||||
const token = uuidv4()
|
||||
const updatedUser = await this.usersRepository.createPasswordResetToken(user.id, token)
|
||||
|
||||
await this.usersService.sendPasswordRecoveryEmail({
|
||||
email: updatedUser.user.email,
|
||||
name: updatedUser.user.name,
|
||||
passwordRecoveryToken: updatedUser.resetPasswordToken,
|
||||
})
|
||||
if (!updatedUser) {
|
||||
throw new NotFoundException('User not found')
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user