From 25bae8d5a3b29388d42667a245175c8a8648e023 Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:08:44 +0200 Subject: [PATCH 01/17] refactor: Replace Response by NextResponse --- src/app/api/vaults/route.ts | 6 +- src/modules/api/utils/handle-api-request.ts | 12 ++-- .../api/utils/handle-api-request.test.ts | 55 ++++++++++--------- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/app/api/vaults/route.ts b/src/app/api/vaults/route.ts index 814b5b5d..d9b1edc5 100644 --- a/src/app/api/vaults/route.ts +++ b/src/app/api/vaults/route.ts @@ -1,5 +1,5 @@ import 'reflect-metadata'; -import type { NextRequest } from 'next/server'; +import type { NextRequest, NextResponse } from 'next/server'; import { container } from 'tsyringe'; import { handleApiRequest } from '@api/utils/handle-api-request'; import type { CreateVaultParamsDto } from '@shared/dto/params/create-vault.params.dto'; @@ -30,7 +30,7 @@ import { GetMyVaultsUseCase } from '@api/usecases/vaults/get-my-vaults.usecase'; * schema: * $ref: '#/components/schemas/HttpResponseDto' */ -export async function GET(): Promise { +export async function GET(): Promise { const getMyVaultsUseCase: GetMyVaultsUseCase = container.resolve(GetMyVaultsUseCase); return await handleApiRequest(async () => { @@ -67,7 +67,7 @@ export async function GET(): Promise { * schema: * $ref: '#/components/schemas/HttpResponseDto' */ -export async function POST(request: NextRequest): Promise { +export async function POST(request: NextRequest): Promise { const params: CreateVaultParamsDto = await request.json(); const createVaultUseCase: CreateVaultUseCase = container.resolve(CreateVaultUseCase); diff --git a/src/modules/api/utils/handle-api-request.ts b/src/modules/api/utils/handle-api-request.ts index 9c2f9d0a..fc4b0d11 100644 --- a/src/modules/api/utils/handle-api-request.ts +++ b/src/modules/api/utils/handle-api-request.ts @@ -1,18 +1,22 @@ import { HttpError } from '@api/errors/abstract/http-error'; import { StatusCodes } from 'http-status-codes'; +import { NextResponse } from 'next/server'; export async function handleApiRequest( callback: () => Promise -): Promise { +): Promise { try { const data: Awaited = await callback(); - return Response.json(data, { status: StatusCodes.OK }); + return NextResponse.json(data, { status: StatusCodes.OK }); } catch (error) { if (error instanceof HttpError) { - return Response.json({ error: error.message }, { status: error.status }); + return NextResponse.json( + { error: error.message }, + { status: error.status } + ); } console.error(error); - return Response.json( + return NextResponse.json( { error: 'Internal Server Error' }, { status: StatusCodes.INTERNAL_SERVER_ERROR } ); diff --git a/tests/units/modules/api/utils/handle-api-request.test.ts b/tests/units/modules/api/utils/handle-api-request.test.ts index 6d0311bb..7889bba3 100644 --- a/tests/units/modules/api/utils/handle-api-request.test.ts +++ b/tests/units/modules/api/utils/handle-api-request.test.ts @@ -1,38 +1,43 @@ +jest.mock('next/server', (): any => { + return { + NextResponse: { + json: jest.fn( + ( + body: T, + init: { status: number } + ): { body: T; status: number } => ({ + body, + status: init.status, + }) + ), + }, + }; +}); + +import { handleApiRequest } from '@api/utils/handle-api-request'; import { HttpError } from '@api/errors/abstract/http-error'; import { StatusCodes } from 'http-status-codes'; -import { handleApiRequest } from '@api/utils/handle-api-request'; +import { NextResponse } from 'next/server'; interface IJsonResponse { body: T; status: number; } -type ResponseJson = (body: T, init: { status: number }) => IJsonResponse; - -declare global { - var Response: { - json: ResponseJson; - }; -} +type NextResponseJson = ( + body: T, + init: { status: number } +) => IJsonResponse; describe('handleApiRequest', () => { - let jsonMock: jest.MockedFunction; + let jsonMock: jest.MockedFunction; beforeEach((): void => { - jsonMock = jest.fn( - (body: T, init: { status: number }): IJsonResponse => ({ - body, - status: init.status, - }) - ); - globalThis.Response = { json: jsonMock }; - }); - - afterEach((): void => { - jest.clearAllMocks(); + jsonMock = NextResponse.json as jest.MockedFunction; + jsonMock.mockClear(); }); - it('should return 200 and data when callback resolves', async (): Promise => { + it('returns 200 and data when callback resolves', async (): Promise => { const data: { foo: string } = { foo: 'bar' }; const callback: () => Promise<{ foo: string }> = jest.fn( (): Promise<{ foo: string }> => Promise.resolve(data) @@ -46,7 +51,7 @@ describe('handleApiRequest', () => { expect(callback).toHaveBeenCalled(); }); - it('should return error message and status when HttpError is thrown', async (): Promise => { + it('returns error message and status when HttpError is thrown', async (): Promise => { const error: HttpError = new HttpError('Not Found', StatusCodes.NOT_FOUND); const callback: () => Promise = jest.fn( (): Promise => Promise.reject(error) @@ -66,16 +71,14 @@ describe('handleApiRequest', () => { expect(callback).toHaveBeenCalled(); }); - it('should log error and return 500 when non-HttpError is thrown', async (): Promise => { + it('logs error and returns 500 when non-HttpError is thrown', async (): Promise => { const error: Error = new Error('Unexpected'); const callback: () => Promise = jest.fn( (): Promise => Promise.reject(error) ); const consoleErrorSpy: jest.SpyInstance = jest .spyOn(console, 'error') - .mockImplementation((): void => { - /* ignore error */ - }); + .mockImplementation((): void => {}); const response: IJsonResponse<{ error: string }> = await handleApiRequest(callback); From 8b75d4bca6ad9a96dc03f9a5de3f2c94a3736bae Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:09:57 +0200 Subject: [PATCH 02/17] refactor: Replace Response by NextResponse --- src/app/api/swagger/route.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/api/swagger/route.ts b/src/app/api/swagger/route.ts index d3f8412a..878ce4d3 100644 --- a/src/app/api/swagger/route.ts +++ b/src/app/api/swagger/route.ts @@ -1,6 +1,7 @@ import { createSwaggerSpec } from 'next-swagger-doc'; +import { NextResponse } from 'next/server'; -export function GET(): Response { +export function GET(): NextResponse { const spec: object = createSwaggerSpec({ apiFolder: 'src/app/api', schemaFolders: ['src/modules/shared/dto'], @@ -13,5 +14,5 @@ export function GET(): Response { }, }); - return Response.json(spec); + return NextResponse.json(spec); } From c9065237c545b9199b08021d406cef3386be22e0 Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:12:27 +0200 Subject: [PATCH 03/17] refactor: Move CreateVaultParamsDto to requests --- src/app/api/vaults/route.ts | 6 +++--- src/modules/api/repositories/vaults.repository.ts | 4 ++-- src/modules/api/usecases/vaults/create-vault.usecase.ts | 6 +++--- src/modules/shared/dto/params/.gitkeep | 0 .../create-vault.request.dto.ts} | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 src/modules/shared/dto/params/.gitkeep rename src/modules/shared/dto/{params/create-vault.params.dto.ts => requests/create-vault.request.dto.ts} (85%) diff --git a/src/app/api/vaults/route.ts b/src/app/api/vaults/route.ts index d9b1edc5..403ac100 100644 --- a/src/app/api/vaults/route.ts +++ b/src/app/api/vaults/route.ts @@ -2,12 +2,12 @@ import 'reflect-metadata'; import type { NextRequest, NextResponse } from 'next/server'; import { container } from 'tsyringe'; import { handleApiRequest } from '@api/utils/handle-api-request'; -import type { CreateVaultParamsDto } from '@shared/dto/params/create-vault.params.dto'; import type { VaultModelDto } from '@shared/dto/models/vault.model.dto'; import type { CreateVaultResponseDto } from '@shared/dto/responses/create-vault.response.dto'; import { CreateVaultUseCase } from '@api/usecases/vaults/create-vault.usecase'; import type { GetMyVaultsResponseDto } from '@shared/dto/responses/get-my-vaults.response.dto'; import { GetMyVaultsUseCase } from '@api/usecases/vaults/get-my-vaults.usecase'; +import type { CreateVaultRequestDto } from '@shared/dto/requests/create-vault.request.dto'; /** * @swagger @@ -52,7 +52,7 @@ export async function GET(): Promise { * content: * application/json: * schema: - * $ref: '#/components/schemas/CreateVaultParamsDto' + * $ref: '#/components/schemas/CreateVaultRequestDto' * responses: * 200: * description: Returns the vault created @@ -68,7 +68,7 @@ export async function GET(): Promise { * $ref: '#/components/schemas/HttpResponseDto' */ export async function POST(request: NextRequest): Promise { - const params: CreateVaultParamsDto = await request.json(); + const params: CreateVaultRequestDto = await request.json(); const createVaultUseCase: CreateVaultUseCase = container.resolve(CreateVaultUseCase); return await handleApiRequest(async () => { diff --git a/src/modules/api/repositories/vaults.repository.ts b/src/modules/api/repositories/vaults.repository.ts index b7833fe3..20dfe45a 100644 --- a/src/modules/api/repositories/vaults.repository.ts +++ b/src/modules/api/repositories/vaults.repository.ts @@ -1,7 +1,7 @@ import { injectable } from 'tsyringe'; import { Vault } from '@prisma/generated'; import prisma from '@lib/prisma'; -import type { CreateVaultParamsDto } from '@shared/dto/params/create-vault.params.dto'; +import { CreateVaultRequestDto } from '@shared/dto/requests/create-vault.request.dto'; @injectable() export class VaultsRepository { @@ -9,7 +9,7 @@ export class VaultsRepository { return await prisma.vault.findMany(); } - public async create(params: CreateVaultParamsDto): Promise { + public async create(params: CreateVaultRequestDto): Promise { return await prisma.vault.create({ data: params }); } } diff --git a/src/modules/api/usecases/vaults/create-vault.usecase.ts b/src/modules/api/usecases/vaults/create-vault.usecase.ts index f12f0336..b353681c 100644 --- a/src/modules/api/usecases/vaults/create-vault.usecase.ts +++ b/src/modules/api/usecases/vaults/create-vault.usecase.ts @@ -1,14 +1,14 @@ import { inject, injectable } from 'tsyringe'; import type { VaultModelDto } from '@shared/dto/models/vault.model.dto'; import { IUseCaseWithInput } from '@api/usecases/abstract/usecase.with-input.interface'; -import type { CreateVaultParamsDto } from '@shared/dto/params/create-vault.params.dto'; import { Vault } from '@prisma/generated'; import { VaultAdapter } from '@api/adapters/vault.adapter'; import { VaultsRepository } from '@api/repositories/vaults.repository'; +import { CreateVaultRequestDto } from '@shared/dto/requests/create-vault.request.dto'; @injectable() export class CreateVaultUseCase - implements IUseCaseWithInput + implements IUseCaseWithInput { public constructor( @inject(VaultsRepository) @@ -17,7 +17,7 @@ export class CreateVaultUseCase private readonly _vaultAdapter: VaultAdapter ) {} - public async handle(params: CreateVaultParamsDto): Promise { + public async handle(params: CreateVaultRequestDto): Promise { const vaultCreated: Vault = await this._vaultsRepository.create(params); return this._vaultAdapter.getDtoFromModel(vaultCreated); } diff --git a/src/modules/shared/dto/params/.gitkeep b/src/modules/shared/dto/params/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/shared/dto/params/create-vault.params.dto.ts b/src/modules/shared/dto/requests/create-vault.request.dto.ts similarity index 85% rename from src/modules/shared/dto/params/create-vault.params.dto.ts rename to src/modules/shared/dto/requests/create-vault.request.dto.ts index edec22fc..abd950e0 100644 --- a/src/modules/shared/dto/params/create-vault.params.dto.ts +++ b/src/modules/shared/dto/requests/create-vault.request.dto.ts @@ -2,7 +2,7 @@ * @swagger * components: * schemas: - * CreateVaultParamsDto: + * CreateVaultRequestDto: * type: object * required: * - label @@ -15,7 +15,7 @@ * type: string * description: Password, token, or other sensitive string to store */ -export type CreateVaultParamsDto = { +export type CreateVaultRequestDto = { label: string; secret: string; }; From 32b2b2a0513c59286eb2e22eb1f4f6074aae11c5 Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:19:08 +0200 Subject: [PATCH 04/17] feat: Create params --- src/modules/shared/dto/params/abstract/next-params.ts | 3 +++ src/modules/shared/dto/params/id.param.ts | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 src/modules/shared/dto/params/abstract/next-params.ts create mode 100644 src/modules/shared/dto/params/id.param.ts diff --git a/src/modules/shared/dto/params/abstract/next-params.ts b/src/modules/shared/dto/params/abstract/next-params.ts new file mode 100644 index 00000000..bbf3c154 --- /dev/null +++ b/src/modules/shared/dto/params/abstract/next-params.ts @@ -0,0 +1,3 @@ +export type NextParams = { + params: T; +}; diff --git a/src/modules/shared/dto/params/id.param.ts b/src/modules/shared/dto/params/id.param.ts new file mode 100644 index 00000000..6cdfcf70 --- /dev/null +++ b/src/modules/shared/dto/params/id.param.ts @@ -0,0 +1,5 @@ +import type { NextParams } from '@shared/dto/params/abstract/next-params'; + +export type IdParam = NextParams<{ + id: string; +}>; From 22c6d36613da42799297c319879365e4aade336f Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:23:29 +0200 Subject: [PATCH 05/17] refactor: Delete .gitkeep --- src/modules/shared/dto/params/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/modules/shared/dto/params/.gitkeep diff --git a/src/modules/shared/dto/params/.gitkeep b/src/modules/shared/dto/params/.gitkeep deleted file mode 100644 index e69de29b..00000000 From 3a8c9ccf976ba5a09245ed2c5475e7836157cdfd Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:29:21 +0200 Subject: [PATCH 06/17] refactor: Add successStatusCode in handleApiRequest --- src/app/api/vaults/route.ts | 5 +++-- src/modules/api/utils/handle-api-request.ts | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/api/vaults/route.ts b/src/app/api/vaults/route.ts index 403ac100..19ac3a88 100644 --- a/src/app/api/vaults/route.ts +++ b/src/app/api/vaults/route.ts @@ -8,6 +8,7 @@ import { CreateVaultUseCase } from '@api/usecases/vaults/create-vault.usecase'; import type { GetMyVaultsResponseDto } from '@shared/dto/responses/get-my-vaults.response.dto'; import { GetMyVaultsUseCase } from '@api/usecases/vaults/get-my-vaults.usecase'; import type { CreateVaultRequestDto } from '@shared/dto/requests/create-vault.request.dto'; +import { StatusCodes } from 'http-status-codes'; /** * @swagger @@ -54,7 +55,7 @@ export async function GET(): Promise { * schema: * $ref: '#/components/schemas/CreateVaultRequestDto' * responses: - * 200: + * 201: * description: Returns the vault created * content: * application/json: @@ -75,5 +76,5 @@ export async function POST(request: NextRequest): Promise { const vaultCreated: VaultModelDto = await createVaultUseCase.handle(params); const response: CreateVaultResponseDto = { vaultCreated }; return response; - }); + }, StatusCodes.CREATED); } diff --git a/src/modules/api/utils/handle-api-request.ts b/src/modules/api/utils/handle-api-request.ts index fc4b0d11..89a5bdbd 100644 --- a/src/modules/api/utils/handle-api-request.ts +++ b/src/modules/api/utils/handle-api-request.ts @@ -3,11 +3,14 @@ import { StatusCodes } from 'http-status-codes'; import { NextResponse } from 'next/server'; export async function handleApiRequest( - callback: () => Promise + callback: () => Promise, + successStatusCode?: StatusCodes ): Promise { try { const data: Awaited = await callback(); - return NextResponse.json(data, { status: StatusCodes.OK }); + return NextResponse.json(data, { + status: successStatusCode || StatusCodes.OK, + }); } catch (error) { if (error instanceof HttpError) { return NextResponse.json( From ad0840c254a89bac25f7b70925fa2f19cd80301e Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:29:38 +0200 Subject: [PATCH 07/17] feat: Create DELETE /vaults/[id] --- src/app/api/vaults/[id]/route.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/app/api/vaults/[id]/route.ts diff --git a/src/app/api/vaults/[id]/route.ts b/src/app/api/vaults/[id]/route.ts new file mode 100644 index 00000000..8fcd3c1c --- /dev/null +++ b/src/app/api/vaults/[id]/route.ts @@ -0,0 +1,31 @@ +import type { NextResponse } from 'next/server'; +import type { IdParam } from '@shared/dto/params/id.param'; +import { container } from 'tsyringe'; +import { handleApiRequest } from '@api/utils/handle-api-request'; +import { StatusCodes } from 'http-status-codes'; + +/** + * @swagger + * /api/vaults/{id}: + * delete: + * tags: + * - Vaults + * description: Delete a vault by id + * responses: + * 204: + * description: The vault has been successfully deleted + * 500: + * description: Internal Server Error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/HttpResponseDto' + */ +export async function DELETE(params: IdParam): Promise { + const deleteVaultUseCase: DeleteVaultUseCase = + container.resolve(DeleteVaultUseCase); + return await handleApiRequest(async () => { + await deleteVaultUseCase.handle(params); + return null; + }, StatusCodes.NO_CONTENT); +} From 6eb1fabfa216d0342d93b2ce91f718754e2490c7 Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:36:58 +0200 Subject: [PATCH 08/17] refactor: Rename params to input in IUseCaseWithInput --- .../api/usecases/abstract/usecase.with-input.interface.ts | 2 +- src/modules/api/usecases/vaults/create-vault.usecase.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/api/usecases/abstract/usecase.with-input.interface.ts b/src/modules/api/usecases/abstract/usecase.with-input.interface.ts index 5894ab0d..ea4a4775 100644 --- a/src/modules/api/usecases/abstract/usecase.with-input.interface.ts +++ b/src/modules/api/usecases/abstract/usecase.with-input.interface.ts @@ -1,3 +1,3 @@ export interface IUseCaseWithInput { - handle(params: Input): Promise; + handle(input: Input): Promise; } diff --git a/src/modules/api/usecases/vaults/create-vault.usecase.ts b/src/modules/api/usecases/vaults/create-vault.usecase.ts index b353681c..523d1eb3 100644 --- a/src/modules/api/usecases/vaults/create-vault.usecase.ts +++ b/src/modules/api/usecases/vaults/create-vault.usecase.ts @@ -17,8 +17,8 @@ export class CreateVaultUseCase private readonly _vaultAdapter: VaultAdapter ) {} - public async handle(params: CreateVaultRequestDto): Promise { - const vaultCreated: Vault = await this._vaultsRepository.create(params); + public async handle(input: CreateVaultRequestDto): Promise { + const vaultCreated: Vault = await this._vaultsRepository.create(input); return this._vaultAdapter.getDtoFromModel(vaultCreated); } } From 1046a78dfd2af9dd5b3cd9e91836a1af081a37d1 Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:37:09 +0200 Subject: [PATCH 09/17] feat: Create DeleteVaultUseCase --- src/app/api/vaults/[id]/route.ts | 1 + .../api/usecases/vaults/delete-vault.usecase.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/modules/api/usecases/vaults/delete-vault.usecase.ts diff --git a/src/app/api/vaults/[id]/route.ts b/src/app/api/vaults/[id]/route.ts index 8fcd3c1c..8771d41c 100644 --- a/src/app/api/vaults/[id]/route.ts +++ b/src/app/api/vaults/[id]/route.ts @@ -3,6 +3,7 @@ import type { IdParam } from '@shared/dto/params/id.param'; import { container } from 'tsyringe'; import { handleApiRequest } from '@api/utils/handle-api-request'; import { StatusCodes } from 'http-status-codes'; +import { DeleteVaultUseCase } from '@api/usecases/vaults/delete-vault.usecase'; /** * @swagger diff --git a/src/modules/api/usecases/vaults/delete-vault.usecase.ts b/src/modules/api/usecases/vaults/delete-vault.usecase.ts new file mode 100644 index 00000000..eb7f626e --- /dev/null +++ b/src/modules/api/usecases/vaults/delete-vault.usecase.ts @@ -0,0 +1,16 @@ +import { inject, injectable } from 'tsyringe'; +import { IUseCaseWithInput } from '@api/usecases/abstract/usecase.with-input.interface'; +import { IdParam } from '@shared/dto/params/id.param'; +import { VaultsRepository } from '@api/repositories/vaults.repository'; + +@injectable() +export class DeleteVaultUseCase implements IUseCaseWithInput { + public constructor( + @inject(VaultsRepository) + private readonly _vaultsRepository: VaultsRepository + ) {} + + public async handle(input: IdParam): Promise { + await this._vaultsRepository.delete(input.params.id); + } +} From 554a1d52230c6fa618ec793d4b3249c7802912d3 Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:38:52 +0200 Subject: [PATCH 10/17] feat: Add delete method in VaultsRepository --- src/modules/api/repositories/vaults.repository.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/api/repositories/vaults.repository.ts b/src/modules/api/repositories/vaults.repository.ts index 20dfe45a..c3303c8c 100644 --- a/src/modules/api/repositories/vaults.repository.ts +++ b/src/modules/api/repositories/vaults.repository.ts @@ -12,4 +12,8 @@ export class VaultsRepository { public async create(params: CreateVaultRequestDto): Promise { return await prisma.vault.create({ data: params }); } + + public async delete(id: string): Promise { + await prisma.vault.delete({ where: { id: id } }); + } } From 255a6db140124b572018d984a6e80ff0a4de028c Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:40:51 +0200 Subject: [PATCH 11/17] fix: Fix lint --- tests/units/modules/api/utils/handle-api-request.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/units/modules/api/utils/handle-api-request.test.ts b/tests/units/modules/api/utils/handle-api-request.test.ts index 7889bba3..ea4a2384 100644 --- a/tests/units/modules/api/utils/handle-api-request.test.ts +++ b/tests/units/modules/api/utils/handle-api-request.test.ts @@ -1,4 +1,4 @@ -jest.mock('next/server', (): any => { +jest.mock('next/server', (): unknown => { return { NextResponse: { json: jest.fn( @@ -78,7 +78,9 @@ describe('handleApiRequest', () => { ); const consoleErrorSpy: jest.SpyInstance = jest .spyOn(console, 'error') - .mockImplementation((): void => {}); + .mockImplementation((): void => { + return; + }); const response: IJsonResponse<{ error: string }> = await handleApiRequest(callback); From 621d645af965ce161bb74ade1ac8377f658d3a57 Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:48:28 +0200 Subject: [PATCH 12/17] docs: Add path param in delete route --- src/app/api/vaults/[id]/route.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/app/api/vaults/[id]/route.ts b/src/app/api/vaults/[id]/route.ts index 8771d41c..ba1973e6 100644 --- a/src/app/api/vaults/[id]/route.ts +++ b/src/app/api/vaults/[id]/route.ts @@ -12,6 +12,13 @@ import { DeleteVaultUseCase } from '@api/usecases/vaults/delete-vault.usecase'; * tags: * - Vaults * description: Delete a vault by id + * parameters: + * - in: path + * name: id + * required: true + * description: id of vault to delete + * schema: + * type: string * responses: * 204: * description: The vault has been successfully deleted From 9efc7ccfa49a4c8096b361884fa56f07b1136995 Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 21:48:44 +0200 Subject: [PATCH 13/17] fix: Import reflect-metadata --- src/app/api/vaults/[id]/route.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/api/vaults/[id]/route.ts b/src/app/api/vaults/[id]/route.ts index ba1973e6..272fa977 100644 --- a/src/app/api/vaults/[id]/route.ts +++ b/src/app/api/vaults/[id]/route.ts @@ -1,3 +1,4 @@ +import 'reflect-metadata'; import type { NextResponse } from 'next/server'; import type { IdParam } from '@shared/dto/params/id.param'; import { container } from 'tsyringe'; From 5a3c87c1b5672deab21e65fa8185836867dff14c Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 22:00:01 +0200 Subject: [PATCH 14/17] fix: Fix params --- src/app/api/vaults/[id]/route.ts | 7 +++++-- src/modules/api/usecases/vaults/delete-vault.usecase.ts | 3 ++- src/modules/shared/dto/params/abstract/next-params.ts | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/api/vaults/[id]/route.ts b/src/app/api/vaults/[id]/route.ts index 272fa977..a20c4b51 100644 --- a/src/app/api/vaults/[id]/route.ts +++ b/src/app/api/vaults/[id]/route.ts @@ -1,5 +1,5 @@ import 'reflect-metadata'; -import type { NextResponse } from 'next/server'; +import type { NextRequest, NextResponse } from 'next/server'; import type { IdParam } from '@shared/dto/params/id.param'; import { container } from 'tsyringe'; import { handleApiRequest } from '@api/utils/handle-api-request'; @@ -30,7 +30,10 @@ import { DeleteVaultUseCase } from '@api/usecases/vaults/delete-vault.usecase'; * schema: * $ref: '#/components/schemas/HttpResponseDto' */ -export async function DELETE(params: IdParam): Promise { +export async function DELETE( + request: NextRequest, + params: IdParam +): Promise { const deleteVaultUseCase: DeleteVaultUseCase = container.resolve(DeleteVaultUseCase); return await handleApiRequest(async () => { diff --git a/src/modules/api/usecases/vaults/delete-vault.usecase.ts b/src/modules/api/usecases/vaults/delete-vault.usecase.ts index eb7f626e..e51adec6 100644 --- a/src/modules/api/usecases/vaults/delete-vault.usecase.ts +++ b/src/modules/api/usecases/vaults/delete-vault.usecase.ts @@ -11,6 +11,7 @@ export class DeleteVaultUseCase implements IUseCaseWithInput { ) {} public async handle(input: IdParam): Promise { - await this._vaultsRepository.delete(input.params.id); + const vaultId: string = (await input.params)?.id; + await this._vaultsRepository.delete(vaultId); } } diff --git a/src/modules/shared/dto/params/abstract/next-params.ts b/src/modules/shared/dto/params/abstract/next-params.ts index bbf3c154..7b4fc0a5 100644 --- a/src/modules/shared/dto/params/abstract/next-params.ts +++ b/src/modules/shared/dto/params/abstract/next-params.ts @@ -1,3 +1,3 @@ export type NextParams = { - params: T; + params: Promise; }; From f4c57c74cf5974b2f80a03624f181c17c277b13d Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 22:00:16 +0200 Subject: [PATCH 15/17] feat: Create NoVaultFoundError --- src/app/api/vaults/[id]/route.ts | 6 ++++-- src/modules/api/errors/no-vault-found.error.ts | 8 ++++++++ src/modules/api/repositories/vaults.repository.ts | 8 +++++++- 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 src/modules/api/errors/no-vault-found.error.ts diff --git a/src/app/api/vaults/[id]/route.ts b/src/app/api/vaults/[id]/route.ts index a20c4b51..5e43fb82 100644 --- a/src/app/api/vaults/[id]/route.ts +++ b/src/app/api/vaults/[id]/route.ts @@ -12,17 +12,19 @@ import { DeleteVaultUseCase } from '@api/usecases/vaults/delete-vault.usecase'; * delete: * tags: * - Vaults - * description: Delete a vault by id + * description: Delete a vault by ID * parameters: * - in: path * name: id * required: true - * description: id of vault to delete + * description: ID of vault to delete * schema: * type: string * responses: * 204: * description: The vault has been successfully deleted + * 404: + * description: No vault found with the entered ID * 500: * description: Internal Server Error * content: diff --git a/src/modules/api/errors/no-vault-found.error.ts b/src/modules/api/errors/no-vault-found.error.ts new file mode 100644 index 00000000..5a805ef3 --- /dev/null +++ b/src/modules/api/errors/no-vault-found.error.ts @@ -0,0 +1,8 @@ +import { HttpError } from '@api/errors/abstract/http-error'; +import { StatusCodes } from 'http-status-codes'; + +export class NoVaultFoundError extends HttpError { + public constructor() { + super('Vault not found', StatusCodes.NOT_FOUND); + } +} diff --git a/src/modules/api/repositories/vaults.repository.ts b/src/modules/api/repositories/vaults.repository.ts index c3303c8c..eac29b89 100644 --- a/src/modules/api/repositories/vaults.repository.ts +++ b/src/modules/api/repositories/vaults.repository.ts @@ -2,6 +2,7 @@ import { injectable } from 'tsyringe'; import { Vault } from '@prisma/generated'; import prisma from '@lib/prisma'; import { CreateVaultRequestDto } from '@shared/dto/requests/create-vault.request.dto'; +import { NoVaultFoundError } from '@api/errors/no-vault-found.error'; @injectable() export class VaultsRepository { @@ -14,6 +15,11 @@ export class VaultsRepository { } public async delete(id: string): Promise { - await prisma.vault.delete({ where: { id: id } }); + try { + await prisma.vault.delete({ where: { id: id } }); + } catch (error: unknown) { + console.error(error); + throw new NoVaultFoundError(); + } } } From 008b488ba45a5880366848a6d2dd25bdf09fcaa4 Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 22:01:51 +0200 Subject: [PATCH 16/17] docs: Fix --- src/app/api/vaults/[id]/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/vaults/[id]/route.ts b/src/app/api/vaults/[id]/route.ts index 5e43fb82..6104bd6f 100644 --- a/src/app/api/vaults/[id]/route.ts +++ b/src/app/api/vaults/[id]/route.ts @@ -24,7 +24,7 @@ import { DeleteVaultUseCase } from '@api/usecases/vaults/delete-vault.usecase'; * 204: * description: The vault has been successfully deleted * 404: - * description: No vault found with the entered ID + * description: Vault not found * 500: * description: Internal Server Error * content: From b58ce42880397c96806d5b06516f02215880d891 Mon Sep 17 00:00:00 2001 From: vbetsch Date: Tue, 29 Jul 2025 22:08:27 +0200 Subject: [PATCH 17/17] fix: return no content --- src/app/api/vaults/[id]/route.ts | 8 ++++---- src/modules/api/utils/handle-api-request.ts | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/api/vaults/[id]/route.ts b/src/app/api/vaults/[id]/route.ts index 6104bd6f..9a87071a 100644 --- a/src/app/api/vaults/[id]/route.ts +++ b/src/app/api/vaults/[id]/route.ts @@ -38,8 +38,8 @@ export async function DELETE( ): Promise { const deleteVaultUseCase: DeleteVaultUseCase = container.resolve(DeleteVaultUseCase); - return await handleApiRequest(async () => { - await deleteVaultUseCase.handle(params); - return null; - }, StatusCodes.NO_CONTENT); + return await handleApiRequest( + () => deleteVaultUseCase.handle(params), + StatusCodes.NO_CONTENT + ); } diff --git a/src/modules/api/utils/handle-api-request.ts b/src/modules/api/utils/handle-api-request.ts index 89a5bdbd..ac3b8b2e 100644 --- a/src/modules/api/utils/handle-api-request.ts +++ b/src/modules/api/utils/handle-api-request.ts @@ -8,6 +8,11 @@ export async function handleApiRequest( ): Promise { try { const data: Awaited = await callback(); + + if (successStatusCode === StatusCodes.NO_CONTENT) { + return new NextResponse(null, { status: StatusCodes.NO_CONTENT }); + } + return NextResponse.json(data, { status: successStatusCode || StatusCodes.OK, });