Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f678673
refactor: catch error in handleApiRequest
vbetsch Aug 3, 2025
fa8d1a7
refactor: move handleApiRequest in api folder
vbetsch Aug 3, 2025
64447ff
refactor: re-create errors architecture
vbetsch Aug 3, 2025
b7c1b4c
refactor: create BusinessError
vbetsch Aug 3, 2025
73ac171
refactor: handle BusinessError in handleApiRequest
vbetsch Aug 3, 2025
98cba0c
fix: HttpErrorDto api doc
vbetsch Aug 3, 2025
32d8e73
refactor: Move request service to services folder
vbetsch Aug 3, 2025
f9cb3ac
feat: handle BusinessError in LockliteApiRequestService
vbetsch Aug 3, 2025
fe5bee9
refactor: Move abstract errors in shared module
vbetsch Aug 3, 2025
4698cd3
refactor: Use HttpError & BusinessError in frontend
vbetsch Aug 3, 2025
6570827
refactor: Add toString methods in errors
vbetsch Aug 3, 2025
f01581f
fix: toString methods in errors
vbetsch Aug 3, 2025
742621e
refactor: LockliteApiRequestService - create handleNoDataCase
vbetsch Aug 3, 2025
0c286c6
refactor: LockliteApiRequestService - create _errorMessage
vbetsch Aug 3, 2025
812b398
refactor: LockliteApiRequestService - create _parseData & _handlePars…
vbetsch Aug 3, 2025
ceaa870
fix: LockliteApiRequestService - create _parseData & _handleParseError
vbetsch Aug 3, 2025
0201167
refactor: LockliteApiRequestService - create _handleNoData
vbetsch Aug 3, 2025
af559b2
refactor: LockliteApiRequestService - rename methods
vbetsch Aug 3, 2025
10c56cf
refactor: Request services - move errorMessage in abstract
vbetsch Aug 3, 2025
203b1ce
refactor: Request services - create _handleResponseNotOk
vbetsch Aug 3, 2025
db38e4d
fix: Request services - create _handleResponseNotOk
vbetsch Aug 3, 2025
9928a1c
fix: Request services - rename _fetch to _request
vbetsch Aug 3, 2025
c59dd3e
fix: Request services - create _fetch
vbetsch Aug 3, 2025
94d6b93
fix: Request services - protected naming
vbetsch Aug 3, 2025
4bc23cf
refactor: move VaultAlreadyExistsError in vaults folder
vbetsch Aug 3, 2025
5f6e1d4
feat: create VaultLabelTooLongError
vbetsch Aug 3, 2025
de5393b
fix: create BusinessErrorCodeEnumDto.VAULT_LABEL_TOO_LONG
vbetsch Aug 3, 2025
4a2c92a
fix: add VaultAlreadyExistsError in api doc
vbetsch Aug 3, 2025
5eb1d89
fix: tests of handleApiRequest
vbetsch Aug 3, 2025
2a6ca27
refactor: move http error test
vbetsch Aug 3, 2025
3775a60
refactor: move handle-api-request.test.ts in api folder
vbetsch Aug 3, 2025
0efaabc
fix: move request.service.test.ts in services folder
vbetsch Aug 3, 2025
4c27de3
fix: Type error: Variable 'vaultCreated' is used before being assigned.
vbetsch Aug 3, 2025
a4d8021
fix: use uri instead of url
vbetsch Aug 3, 2025
e908e2b
fix: typo
vbetsch Aug 3, 2025
a4687bc
fix: Property 'data' does not exist on type 'HttpResponseDto<Data>'
vbetsch Aug 3, 2025
5295e4b
chore(linter): add @typescript-eslint/no-use-before-define & @typescr…
vbetsch Aug 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ export default tseslint.config(
],

/* TypeScript strictness */
'@typescript-eslint/no-use-before-define': ['error'],
'@typescript-eslint/no-unnecessary-condition': ['error'],
'@typescript-eslint/adjacent-overload-signatures': 'warn',
'@typescript-eslint/class-literal-property-style': ['warn', 'fields'],
'@typescript-eslint/consistent-type-imports': [
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { JSX } from 'react';
import React from 'react';
import type { Metadata } from 'next';
import type { SharedLayoutProps } from '@shared/types/props/SharedLayoutProps';
import type { SharedLayoutProps } from '@shared/props/SharedLayoutProps';

export const metadata: Metadata = {
title: 'LockLite API',
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/vaults/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'reflect-metadata';
import type { NextRequest, NextResponse } from 'next/server';
import { container } from 'tsyringe';
import { handleApiRequest } from '@api/helpers/handle-api-request';
import { handleApiRequest } from '@api/helpers/api/handle-api-request';
import { StatusCodes } from 'http-status-codes';
import { DeleteVaultUseCase } from '@api/usecases/vaults/delete-vault.usecase';
import type { CreateVaultParams } from '@shared/dto/input/params/create-vault.params';
Expand Down
12 changes: 9 additions & 3 deletions src/app/api/vaults/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'reflect-metadata';
import type { NextRequest, NextResponse } from 'next/server';
import { container } from 'tsyringe';
import { handleApiRequest } from '@api/helpers/handle-api-request';
import { handleApiRequest } from '@api/helpers/api/handle-api-request';
import type { VaultModelDto } from '@shared/dto/models/vault.model.dto';
import { CreateVaultUseCase } from '@api/usecases/vaults/create-vault.usecase';
import { GetMyVaultsUseCase } from '@api/usecases/vaults/get-my-vaults.usecase';
Expand Down Expand Up @@ -64,8 +64,14 @@ export async function GET(): Promise<
* application/json:
* schema:
* $ref: '#/components/schemas/CreateVaultBodyDto'
* 400:
* description: One of the requested values is too long
* 409:
* description: Vault already exists
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/HttpErrorDto'
* 422:
* description: The vault label must not exceed 255 characters
* content:
* application/json:
* schema:
Expand Down
2 changes: 1 addition & 1 deletion src/app/ui/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from 'react';
import ThemeRegistry from '@ui/providers/ThemeRegistry';
import { AppBar, Container, Toolbar, Typography } from '@mui/material';
import PageContainer from '@ui/components/common/PageContainer';
import type { SharedLayoutProps } from '@shared/types/props/SharedLayoutProps';
import type { SharedLayoutProps } from '@shared/props/SharedLayoutProps';

export const metadata: Metadata = {
title: {
Expand Down
2 changes: 1 addition & 1 deletion src/app/ui/workspace/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import type { JSX } from 'react';
import type { SharedLayoutProps } from '@shared/types/props/SharedLayoutProps';
import type { SharedLayoutProps } from '@shared/props/SharedLayoutProps';

export default function WorkspaceLayout(props: SharedLayoutProps): JSX.Element {
return <>{props.children}</>;
Expand Down
9 changes: 0 additions & 9 deletions src/modules/api/errors/abstract/http-error.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { StatusCodes } from 'http-status-codes';
import { BusinessError } from '@shared/errors/business-error';
import { BusinessErrorCodeEnumDto } from '@shared/dto/output/errors/business-error-code.enum.dto';

export class VaultAlreadyExistsError extends BusinessError {
public constructor(label: string) {
super(
`Vault with label '${label}' already exists`,
StatusCodes.CONFLICT,
BusinessErrorCodeEnumDto.VAULT_ALREADY_EXISTS
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { StatusCodes } from 'http-status-codes';
import { BusinessError } from '@shared/errors/business-error';
import { BusinessErrorCodeEnumDto } from '@shared/dto/output/errors/business-error-code.enum.dto';

export class VaultLabelTooLongError extends BusinessError {
public constructor() {
super(
`The vault label must not exceed 255 characters`,
StatusCodes.UNPROCESSABLE_ENTITY,
BusinessErrorCodeEnumDto.VAULT_LABEL_TOO_LONG
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpError } from '@api/errors/abstract/http-error';
import { HttpError } from '@shared/errors/http-error';
import { StatusCodes } from 'http-status-codes';

export class InternalServerError extends HttpError {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpError } from '@api/errors/abstract/http-error';
import { HttpError } from '@shared/errors/http-error';
import { StatusCodes } from 'http-status-codes';

export class InvalidRequestDataError extends HttpError {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpError } from '@api/errors/abstract/http-error';
import { HttpError } from '@shared/errors/http-error';
import { StatusCodes } from 'http-status-codes';

export class RequestedValueTooLongError extends HttpError {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpError } from '@api/errors/abstract/http-error';
import { HttpError } from '@shared/errors/http-error';
import { StatusCodes } from 'http-status-codes';

export class ResourceAlreadyExistsError extends HttpError {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpError } from '@api/errors/abstract/http-error';
import { HttpError } from '@shared/errors/http-error';
import { StatusCodes } from 'http-status-codes';

export class ResourceNotFoundError extends HttpError {
Expand Down
8 changes: 0 additions & 8 deletions src/modules/api/errors/vault-already-exists.error.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { HttpError } from '@api/errors/abstract/http-error';
import { HttpError } from '@shared/errors/http-error';
import { StatusCodes } from 'http-status-codes';
import { NextResponse } from 'next/server';
import type { HttpResponseDto } from '@shared/dto/output/responses/abstract/http.response.dto';
import { ApiLogger } from '@api/logs/api.logger';
import { InternalServerError } from '@api/errors/http/internal-server.error';
import { BusinessError } from '@shared/errors/business-error';

export async function handleApiRequest<Data>(
callback: () => Promise<Data>,
Expand All @@ -22,17 +24,22 @@ export async function handleApiRequest<Data>(
}
);
} catch (error: unknown) {
let httpError: HttpError;
if (error instanceof HttpError) {
return NextResponse.json(
{ error: error.message },
{ status: error.status }
);
httpError = error;
if (error instanceof BusinessError) {
return NextResponse.json(
{ error: { message: httpError.message, code: error.code } },
{ status: httpError.status }
);
}
} else {
ApiLogger.error('Error while handling API errors: ', error);
httpError = new InternalServerError();
}

ApiLogger.error('Error while handling API errors: ', error);
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: StatusCodes.INTERNAL_SERVER_ERROR }
{ error: { message: httpError.message } },
{ status: httpError.status }
);
}
}
12 changes: 6 additions & 6 deletions src/modules/api/helpers/prisma/handle-prisma-errors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { PrismaErrorLike } from '@api/types/prisma-error-like.type';
import type { HttpError } from '@api/errors/abstract/http-error';
import { InternalServerError } from '@api/errors/internal-server.error';
import { ResourceAlreadyExistsError } from '@api/errors/prisma/resource-already-exists.error';
import { ResourceNotFoundError } from '@api/errors/prisma/resource-not-found.error';
import { RequestedValueTooLongError } from '@api/errors/prisma/requested-value-too-long.error';
import { InvalidRequestDataError } from '@api/errors/prisma/invalid-request-data.error';
import type { HttpError } from '@shared/errors/http-error';
import { InternalServerError } from '@api/errors/http/internal-server.error';
import { ResourceAlreadyExistsError } from '@api/errors/http/prisma/resource-already-exists.error';
import { ResourceNotFoundError } from '@api/errors/http/prisma/resource-not-found.error';
import { RequestedValueTooLongError } from '@api/errors/http/prisma/requested-value-too-long.error';
import { InvalidRequestDataError } from '@api/errors/http/prisma/invalid-request-data.error';
import { ApiLogger } from '@api/logs/api.logger';

export function handlePrismaError(error: PrismaErrorLike): HttpError {
Expand Down
13 changes: 11 additions & 2 deletions src/modules/api/usecases/vaults/create-vault.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Vault } from '@prisma/generated';
import { VaultAdapter } from '@api/adapters/vault.adapter';
import { VaultsRepository } from '@api/repositories/vaults.repository';
import { CreateVaultRequestDto } from '@shared/dto/input/requests/create-vault.request.dto';
import { VaultAlreadyExistsError } from '@api/errors/vault-already-exists.error';
import { VaultAlreadyExistsError } from '@api/errors/business/vaults/vault-already-exists.error';
import { RequestedValueTooLongError } from '@api/errors/http/prisma/requested-value-too-long.error';
import { VaultLabelTooLongError } from '@api/errors/business/vaults/vault-label-too-long.error';

@injectable()
export class CreateVaultUseCase
Expand All @@ -25,7 +27,14 @@ export class CreateVaultUseCase
if (vaultsFound > 0) {
throw new VaultAlreadyExistsError(input.label);
}
const vaultCreated: Vault = await this._vaultsRepository.create(input);
let vaultCreated: Vault;
try {
vaultCreated = await this._vaultsRepository.create(input);
} catch (error: unknown) {
if (error instanceof RequestedValueTooLongError)
throw new VaultLabelTooLongError();
throw error;
}
return this._vaultAdapter.getDtoFromEntity(vaultCreated);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum BusinessErrorCodeEnumDto {
VAULT_ALREADY_EXISTS = 'L-409-VAE',
VAULT_LABEL_TOO_LONG = 'L-422-VLTL',
}
14 changes: 12 additions & 2 deletions src/modules/shared/dto/output/errors/http.error.dto.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { BusinessErrorCodeEnumDto } from '@shared/dto/output/errors/business-error-code.enum.dto';

/**
* @swagger
* components:
Expand All @@ -6,8 +8,16 @@
* type: object
* properties:
* error:
* type: string
* type: object
* properties:
* message:
* type: string
* code:
* type: string
*/
export type HttpErrorDto = {
error: string;
error: {
message: string;
code?: BusinessErrorCodeEnumDto;
};
};
20 changes: 20 additions & 0 deletions src/modules/shared/errors/business-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { HttpError } from '@shared/errors/http-error';
import type { BusinessErrorCodeEnumDto } from '@shared/dto/output/errors/business-error-code.enum.dto';

export class BusinessError extends HttpError {
public readonly code: BusinessErrorCodeEnumDto;

public constructor(
message: string,
status: number,
code: BusinessErrorCodeEnumDto
) {
super(message, status);
this.code = code;
this.name = this.constructor.name;
}

public override toString(): string {
return `[${this.code}] ${this.message}`;
}
}
13 changes: 13 additions & 0 deletions src/modules/shared/errors/http-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class HttpError extends Error {
public readonly status: number;

public constructor(message: string, status: number) {
super(message);
this.status = status;
this.name = this.constructor.name;
}

public override toString(): string {
return `(${this.status}) ${this.message}`;
}
}
3 changes: 3 additions & 0 deletions src/modules/shared/props/SharedLayoutProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { SharedChildrenProps } from '@shared/props/SharedChildrenProps';

export type SharedLayoutProps = SharedChildrenProps;
62 changes: 0 additions & 62 deletions src/modules/shared/services/abstract/request.service.ts

This file was deleted.

Loading