Skip to content

Commit

Permalink
refactor: better handling of errors
Browse files Browse the repository at this point in the history
  • Loading branch information
RoccoCocco committed Aug 12, 2023
1 parent 49d4b62 commit 314bf35
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 12 deletions.
11 changes: 11 additions & 0 deletions src/core/exceptions/conflict.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class Conflict extends Error {
constructor(reason?: string) {
super(reason);
}
}

export class ConflictExceptionFactory {
static usernameTaken() {
return new Conflict('Username taken');
}
}
1 change: 1 addition & 0 deletions src/core/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './conflict.exception';
export * from './forbidden.exception';
export * from './not-found.exception';
export * from './unauthorized.exception';
2 changes: 1 addition & 1 deletion src/core/interfaces/generic.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface IGenericRepository<T> {

getAll(queryOptions?: QueryOptions<T>): Promise<GenericList<T>>;

getById(id: string): Promise<T>;
getById(id: string): Promise<T | null>;

update(id: string, data: Partial<T>): Promise<void>;
}
4 changes: 3 additions & 1 deletion src/http/filters/exception.filter.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import {
ArgumentsHost,
Catch,
ConflictException,
ForbiddenException,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

import { Forbidden, NotFound, Unauthorized } from '@/core';
import { Conflict, Forbidden, NotFound, Unauthorized } from '@/core';

const exceptionMapping = [
[Forbidden, ForbiddenException],
[Unauthorized, UnauthorizedException],
[NotFound, NotFoundException],
[Conflict, ConflictException],
] as const;

@Catch()
Expand Down
4 changes: 3 additions & 1 deletion src/persistence/typeorm/entities/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';

import { User, UserRoleEnum, UserStatusEnum } from '../../../core';

export const UQ_USERNAME = 'UQ_user_username';

@Entity({ name: 'users' })
@Unique('UQ_user_username', ['username'])
@Unique(UQ_USERNAME, ['username'])
export class UserEntity implements User {
@PrimaryGeneratedColumn('uuid', { primaryKeyConstraintName: 'PK_user_id' })
@Factory(() => faker.string.uuid())
Expand Down
2 changes: 1 addition & 1 deletion src/persistence/typeorm/repositories/book.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class TypeOrmBookEntityRepository implements IBookRepository {
}

async getById(id: string) {
return this.repository.findOneOrFail({ where: { id } });
return this.repository.findOne({ where: { id } });
}

async getAll(queryOptions?: QueryOptions<Book>): Promise<BookList> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export class TypeOrmPasswordVaultRepository
throw new Error('Not implemented');
}

async getById(id: string): Promise<PasswordVault> {
return this.repository.findOneOrFail({ where: { id } });
async getById(id: string) {
return this.repository.findOne({ where: { id } });
}

async getAll(): Promise<never> {
Expand Down
28 changes: 23 additions & 5 deletions src/persistence/typeorm/repositories/user.repository.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { QueryFailedError, Repository } from 'typeorm';

import { IUserRepository, QueryOptions, User, UserList } from '@/core';
import {
ConflictExceptionFactory,
IUserRepository,
QueryOptions,
User,
UserList,
} from '@/core';

import { UserEntity } from '../entities';
import { UQ_USERNAME, UserEntity } from '../entities';

@Injectable()
export class TypeOrmUserEntityRepository implements IUserRepository {
Expand All @@ -15,7 +21,7 @@ export class TypeOrmUserEntityRepository implements IUserRepository {

async create(data: User) {
const userEntity = this.repository.create(data);
await this.repository.save(userEntity, { reload: true });
await this.handleError(this.repository.save(userEntity, { reload: true }));

return userEntity.id;
}
Expand All @@ -29,7 +35,7 @@ export class TypeOrmUserEntityRepository implements IUserRepository {
}

async getById(id: string) {
return this.repository.findOneOrFail({ where: { id } });
return this.repository.findOne({ where: { id } });
}

async getAll(queryOptions?: QueryOptions<User>): Promise<UserList> {
Expand All @@ -50,4 +56,16 @@ export class TypeOrmUserEntityRepository implements IUserRepository {
async getOneByUsername(username: string): Promise<User | null> {
return this.repository.findOne({ where: { username } });
}

async handleError<T>(promise: Promise<T>): Promise<T> {
return promise.catch((error) => {
if (
error instanceof QueryFailedError &&
error.message.includes(UQ_USERNAME)
) {
throw ConflictExceptionFactory.usernameTaken();
}
throw error;
});
}
}
4 changes: 4 additions & 0 deletions src/usecase/services/authentication.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export class AuthenticationService {

const passwordVault = await this.dataService.passwordVault.getById(user.id);

if (!passwordVault) {
throw new Unauthorized();
}

const matches = await compare(
signInDto.password,
passwordVault.passwordHash,
Expand Down
16 changes: 15 additions & 1 deletion src/usecase/services/book.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
BookQueryDto,
BookUpdateDto,
IDataService,
NotFoundFactory,
} from '@/core';

import { BookDtoFactory, BookFactory } from '../factories';
Expand All @@ -24,6 +25,11 @@ export class BookService {

async get(id: string): Promise<BookDto> {
const book = await this.dataService.book.getById(id);

if (book === null) {
throw NotFoundFactory.forResource({ type: 'book', id });
}

return BookDtoFactory.toDto(book);
}

Expand All @@ -48,8 +54,12 @@ export class BookService {

async delete(requester: AuthenticatedUserDto, id: string): Promise<void> {
const book = await this.dataService.book.getById(id);
new BookPermission(requester).canDelete(book);

if (book === null) {
throw NotFoundFactory.forResource({ type: 'book', id });
}

new BookPermission(requester).canDelete(book);
await this.dataService.book.delete(id);
}

Expand All @@ -62,6 +72,10 @@ export class BookService {

const book = await this.dataService.book.getById(requester.id);

if (book === null) {
throw NotFoundFactory.forResource({ type: 'book', id });
}

new BookPermission(requester).canUpdate(book);

const updateData = BookFactory.update(dto);
Expand Down
10 changes: 10 additions & 0 deletions src/usecase/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { validateOrReject } from 'class-validator';
import {
AuthenticatedUserDto,
IDataService,
NotFoundFactory,
UserCreateDto,
UserDto,
UserListDto,
Expand All @@ -26,6 +27,10 @@ export class UserService {
async get(id: string): Promise<UserDto> {
const user = await this.repository.user.getById(id);

if (user === null) {
throw NotFoundFactory.forResource({ type: 'user', id });
}

return UserDtoFactory.toDto(user);
}

Expand All @@ -49,6 +54,11 @@ export class UserService {

async delete(requester: AuthenticatedUserDto, id: string): Promise<void> {
const user = await this.repository.user.getById(id);

if (user === null) {
throw NotFoundFactory.forResource({ type: 'user', id });
}

new UserPermission(requester).canDelete(user);
await this.repository.user.delete(id);
}
Expand Down

0 comments on commit 314bf35

Please sign in to comment.