From 33df47af95b98bc4690206a6884f23fa9f02fc0c Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:27:07 +0100 Subject: [PATCH 1/5] Extract owner and pet routes to plugins. --- requests.http | 5 ++ src/controller/app.ts | 77 ++----------------- src/controller/routes/owner/owner.routes.ts | 57 ++++++++++++++ .../{ => routes/owner}/owner.schemas.ts | 0 src/controller/routes/pet/pet.routes.ts | 45 +++++++++++ .../{ => routes/pet}/pet.schemas.ts | 0 6 files changed, 114 insertions(+), 70 deletions(-) create mode 100644 src/controller/routes/owner/owner.routes.ts rename src/controller/{ => routes/owner}/owner.schemas.ts (100%) create mode 100644 src/controller/routes/pet/pet.routes.ts rename src/controller/{ => routes/pet}/pet.schemas.ts (100%) diff --git a/requests.http b/requests.http index 5615710..666e2a4 100644 --- a/requests.http +++ b/requests.http @@ -6,6 +6,11 @@ GET {{host}}/api/pets ### +GET {{host}}/api/pets/3 + +### + + POST {{host}}/api/pets Content-Type: application/json diff --git a/src/controller/app.ts b/src/controller/app.ts index 49c6f64..c33a9f7 100644 --- a/src/controller/app.ts +++ b/src/controller/app.ts @@ -3,10 +3,12 @@ import { PetService } from '../service/pet.service'; import { PetRepository } from '../repository/pet.repository'; import { DbClient } from '../db'; import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' -import { getPetByIdSchema, getPetsSchema, postPetsSchema, putPetsToOwnersSchema } from './pet.schemas'; +import { getPetByIdSchema, getPetsSchema, postPetsSchema, putPetsToOwnersSchema } from './routes/pet/pet.schemas'; import { OwnerRepository } from '../repository/owner.repository'; import { OwnerService } from '../service/owner.service'; -import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './owner.schemas'; +import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './routes/owner/owner.schemas'; +import { createPetRoutes } from './routes/pet/pet.routes'; +import { createOwnerRoutes } from './routes/owner/owner.routes'; type Dependencies = { dbClient: DbClient; @@ -20,75 +22,10 @@ export default function createApp(options = {}, dependencies: Dependencies) { const ownerRepository = new OwnerRepository(dbClient); const ownerService = new OwnerService(ownerRepository); - const app = fastify(options) - .withTypeProvider() + const app = fastify(options); - app.get( - '/api/pets', - { schema: getPetsSchema }, - async () => { - const pets = await petService.getAll(); - return pets; - }) - - app.get( - '/api/pets/:id', - { schema: getPetByIdSchema }, - async (request) => { - const { id } = request.params; - const pets = await petService.getById(id); - return pets; - }) - - - app.post( - '/api/pets', - { schema: postPetsSchema }, - async (request, reply) => { - const { body: petToCreate } = request; - - const created = await petService.create(petToCreate); - reply.status(201); - return created; - }) - - app.put( - '/api/owners/:ownerId/pets/:petId', - { schema: putPetsToOwnersSchema }, - async (request) => { - const { petId, ownerId } = request.params; - const updated = await petService.adopt(petId, ownerId); - return updated; - } - ) - - app.get( - '/api/owners', - { schema: getOwnersSchema }, - async () => { - return await ownerService.getAll(); - } - ) - - app.get( - '/api/owners/:id', - { schema: getOwnerByIdSchema }, - async (request) => { - const { id } = request.params; - return await ownerService.getById(id); - } - ) - - app.post( - '/api/owners', - { schema: postOwnerSchema }, - async (request, reply) => { - const ownerProps = request.body; - const created = await ownerService.create(ownerProps); - reply.status(201); - return created; - } - ) + app.register(createPetRoutes, { petService, prefix: '/api/pets' }) + app.register(createOwnerRoutes, { petService, ownerService }) return app; } \ No newline at end of file diff --git a/src/controller/routes/owner/owner.routes.ts b/src/controller/routes/owner/owner.routes.ts new file mode 100644 index 0000000..15b3f10 --- /dev/null +++ b/src/controller/routes/owner/owner.routes.ts @@ -0,0 +1,57 @@ +import { FastifyPluginAsync } from 'fastify' +import { PetService } from '../../../service/pet.service'; +import { OwnerService } from '../../../service/owner.service'; +import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' +import { putPetsToOwnersSchema } from '../pet/pet.schemas'; +import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './owner.schemas'; + +type PluginOption = { + petService: PetService, + ownerService: OwnerService +} + +export const createOwnerRoutes: FastifyPluginAsync = async ( + app, + {petService, ownerService} +) => { + const appWithProvider = app.withTypeProvider() + + appWithProvider.put( + '/api/owners/:ownerId/pets/:petId', + { schema: putPetsToOwnersSchema }, + async (request) => { + const { petId, ownerId } = request.params; + const updated = await petService.adopt(petId, ownerId); + return updated; + } + ) + + appWithProvider.get( + '/api/owners', + { schema: getOwnersSchema }, + async () => { + return await ownerService.getAll(); + } + ) + + appWithProvider.get( + '/api/owners/:id', + { schema: getOwnerByIdSchema }, + async (request) => { + const { id } = request.params; + return await ownerService.getById(id); + } + ) + + appWithProvider.post( + '/api/owners', + { schema: postOwnerSchema }, + async (request, reply) => { + const ownerProps = request.body; + const created = await ownerService.create(ownerProps); + reply.status(201); + return created; + } + ) + +} \ No newline at end of file diff --git a/src/controller/owner.schemas.ts b/src/controller/routes/owner/owner.schemas.ts similarity index 100% rename from src/controller/owner.schemas.ts rename to src/controller/routes/owner/owner.schemas.ts diff --git a/src/controller/routes/pet/pet.routes.ts b/src/controller/routes/pet/pet.routes.ts new file mode 100644 index 0000000..66e993c --- /dev/null +++ b/src/controller/routes/pet/pet.routes.ts @@ -0,0 +1,45 @@ +import { FastifyPluginAsync } from 'fastify' +import { getPetByIdSchema, getPetsSchema, postPetsSchema } from './pet.schemas'; +import { PetService } from '../../../service/pet.service'; +import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' + +type PluginOption = { + petService: PetService +} + +export const createPetRoutes: FastifyPluginAsync = async ( + app, + { petService } +) => { + + const appWithProvider = app.withTypeProvider() + + appWithProvider.get( + '/', + { schema: getPetsSchema }, + async () => { + const pets = await petService.getAll(); + return pets; + }) + + appWithProvider.get( + '/:id', + { schema: getPetByIdSchema }, + async (request) => { + const { id } = request.params; + const pets = await petService.getById(id); + return pets; + }) + + appWithProvider.post( + '/', + { schema: postPetsSchema }, + async (request, reply) => { + const { body: petToCreate } = request; + + const created = await petService.create(petToCreate); + reply.status(201); + return created; + }) + +} \ No newline at end of file diff --git a/src/controller/pet.schemas.ts b/src/controller/routes/pet/pet.schemas.ts similarity index 100% rename from src/controller/pet.schemas.ts rename to src/controller/routes/pet/pet.schemas.ts From ef5a980378aa351531d775d3394e1a9ac7ec3721 Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:27:38 +0100 Subject: [PATCH 2/5] Declare a plugin. --- src/controller/app.ts | 13 +++++++++++-- src/controller/routes/pet/pet.routes.ts | 14 ++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/controller/app.ts b/src/controller/app.ts index c33a9f7..a3b558c 100644 --- a/src/controller/app.ts +++ b/src/controller/app.ts @@ -14,6 +14,13 @@ type Dependencies = { dbClient: DbClient; } +declare module 'fastify' { + interface FastifyInstance { + petService: PetService + } +} + + export default function createApp(options = {}, dependencies: Dependencies) { const { dbClient } = dependencies; @@ -24,8 +31,10 @@ export default function createApp(options = {}, dependencies: Dependencies) { const app = fastify(options); - app.register(createPetRoutes, { petService, prefix: '/api/pets' }) - app.register(createOwnerRoutes, { petService, ownerService }) + app.decorate('petService', petService); + + app.register(createPetRoutes, { prefix: '/api/pets' }); + app.register(createOwnerRoutes, { petService, ownerService }); return app; } \ No newline at end of file diff --git a/src/controller/routes/pet/pet.routes.ts b/src/controller/routes/pet/pet.routes.ts index 66e993c..9533d0d 100644 --- a/src/controller/routes/pet/pet.routes.ts +++ b/src/controller/routes/pet/pet.routes.ts @@ -1,15 +1,9 @@ import { FastifyPluginAsync } from 'fastify' import { getPetByIdSchema, getPetsSchema, postPetsSchema } from './pet.schemas'; -import { PetService } from '../../../service/pet.service'; import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' -type PluginOption = { - petService: PetService -} - -export const createPetRoutes: FastifyPluginAsync = async ( +export const createPetRoutes: FastifyPluginAsync = async ( app, - { petService } ) => { const appWithProvider = app.withTypeProvider() @@ -18,7 +12,7 @@ export const createPetRoutes: FastifyPluginAsync = async ( '/', { schema: getPetsSchema }, async () => { - const pets = await petService.getAll(); + const pets = await app.petService.getAll(); return pets; }) @@ -27,7 +21,7 @@ export const createPetRoutes: FastifyPluginAsync = async ( { schema: getPetByIdSchema }, async (request) => { const { id } = request.params; - const pets = await petService.getById(id); + const pets = await app.petService.getById(id); return pets; }) @@ -37,7 +31,7 @@ export const createPetRoutes: FastifyPluginAsync = async ( async (request, reply) => { const { body: petToCreate } = request; - const created = await petService.create(petToCreate); + const created = await app.petService.create(petToCreate); reply.status(201); return created; }) From f28a9b7dcede5ce384d4fd46eb5d59156d5cb29f Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:45:48 +0100 Subject: [PATCH 3/5] Create ownerService. --- src/controller/app.ts | 12 +++++----- src/controller/routes/owner/owner.routes.ts | 26 +++++++-------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/controller/app.ts b/src/controller/app.ts index a3b558c..168d7c2 100644 --- a/src/controller/app.ts +++ b/src/controller/app.ts @@ -2,11 +2,8 @@ import fastify from 'fastify'; import { PetService } from '../service/pet.service'; import { PetRepository } from '../repository/pet.repository'; import { DbClient } from '../db'; -import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' -import { getPetByIdSchema, getPetsSchema, postPetsSchema, putPetsToOwnersSchema } from './routes/pet/pet.schemas'; import { OwnerRepository } from '../repository/owner.repository'; import { OwnerService } from '../service/owner.service'; -import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './routes/owner/owner.schemas'; import { createPetRoutes } from './routes/pet/pet.routes'; import { createOwnerRoutes } from './routes/owner/owner.routes'; @@ -16,8 +13,9 @@ type Dependencies = { declare module 'fastify' { interface FastifyInstance { - petService: PetService - } + petService: PetService, + ownerService: OwnerService + } } @@ -32,9 +30,11 @@ export default function createApp(options = {}, dependencies: Dependencies) { const app = fastify(options); app.decorate('petService', petService); + app.decorate('ownerService', ownerService) app.register(createPetRoutes, { prefix: '/api/pets' }); - app.register(createOwnerRoutes, { petService, ownerService }); + app.register(createOwnerRoutes, { prefix: '/api/owners' }); + // app.register(createMessengerPlugin, {message: 'hjksdhjkashdkja'}) return app; } \ No newline at end of file diff --git a/src/controller/routes/owner/owner.routes.ts b/src/controller/routes/owner/owner.routes.ts index 15b3f10..d1c615e 100644 --- a/src/controller/routes/owner/owner.routes.ts +++ b/src/controller/routes/owner/owner.routes.ts @@ -1,54 +1,46 @@ import { FastifyPluginAsync } from 'fastify' -import { PetService } from '../../../service/pet.service'; -import { OwnerService } from '../../../service/owner.service'; import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' import { putPetsToOwnersSchema } from '../pet/pet.schemas'; import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './owner.schemas'; -type PluginOption = { - petService: PetService, - ownerService: OwnerService -} - -export const createOwnerRoutes: FastifyPluginAsync = async ( +export const createOwnerRoutes: FastifyPluginAsync = async ( app, - {petService, ownerService} ) => { const appWithProvider = app.withTypeProvider() appWithProvider.put( - '/api/owners/:ownerId/pets/:petId', + '/:ownerId/pets/:petId', { schema: putPetsToOwnersSchema }, async (request) => { const { petId, ownerId } = request.params; - const updated = await petService.adopt(petId, ownerId); + const updated = await app.petService.adopt(petId, ownerId); return updated; } ) appWithProvider.get( - '/api/owners', + '/', { schema: getOwnersSchema }, async () => { - return await ownerService.getAll(); + return await app.ownerService.getAll(); } ) appWithProvider.get( - '/api/owners/:id', + '/:id', { schema: getOwnerByIdSchema }, async (request) => { const { id } = request.params; - return await ownerService.getById(id); + return await app.ownerService.getById(id); } ) appWithProvider.post( - '/api/owners', + '/', { schema: postOwnerSchema }, async (request, reply) => { const ownerProps = request.body; - const created = await ownerService.create(ownerProps); + const created = await app.ownerService.create(ownerProps); reply.status(201); return created; } From ab9eecc607758d905c6c0607af0def4698dd66f5 Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Fri, 15 Nov 2024 00:30:59 +0100 Subject: [PATCH 4/5] Add a greeter plugin. --- package-lock.json | 7 +++++++ package.json | 1 + src/controller/app.ts | 9 +++++---- src/controller/plugins/greeter.ts | 30 ++++++++++++++++++++++++++++++ src/server.ts | 20 ++++++++++++-------- 5 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 src/controller/plugins/greeter.ts diff --git a/package-lock.json b/package-lock.json index 020009d..afce7e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@fastify/type-provider-json-schema-to-ts": "^4.0.1", "dotenv": "^16.4.5", "fastify": "^5.0.0", + "fastify-plugin": "^5.0.1", "pg": "^8.13.1", "pg-format": "^1.0.4" }, @@ -2549,6 +2550,12 @@ "toad-cache": "^3.7.0" } }, + "node_modules/fastify-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.0.1.tgz", + "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==", + "license": "MIT" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", diff --git a/package.json b/package.json index dfe7a81..fd1de69 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@fastify/type-provider-json-schema-to-ts": "^4.0.1", "dotenv": "^16.4.5", "fastify": "^5.0.0", + "fastify-plugin": "^5.0.1", "pg": "^8.13.1", "pg-format": "^1.0.4" }, diff --git a/src/controller/app.ts b/src/controller/app.ts index 168d7c2..b244062 100644 --- a/src/controller/app.ts +++ b/src/controller/app.ts @@ -6,6 +6,7 @@ import { OwnerRepository } from '../repository/owner.repository'; import { OwnerService } from '../service/owner.service'; import { createPetRoutes } from './routes/pet/pet.routes'; import { createOwnerRoutes } from './routes/owner/owner.routes'; +import createGreeterPlugin from './plugins/greeter'; type Dependencies = { dbClient: DbClient; @@ -19,7 +20,7 @@ declare module 'fastify' { } -export default function createApp(options = {}, dependencies: Dependencies) { +export default async function createApp(options = {}, dependencies: Dependencies) { const { dbClient } = dependencies; const petRepository = new PetRepository(dbClient); @@ -32,9 +33,9 @@ export default function createApp(options = {}, dependencies: Dependencies) { app.decorate('petService', petService); app.decorate('ownerService', ownerService) - app.register(createPetRoutes, { prefix: '/api/pets' }); - app.register(createOwnerRoutes, { prefix: '/api/owners' }); - // app.register(createMessengerPlugin, {message: 'hjksdhjkashdkja'}) + await app.register(createGreeterPlugin, { message: 'Hi' }) + await app.register(createPetRoutes, { prefix: '/api/pets' }); + await app.register(createOwnerRoutes, { prefix: '/api/owners' }); return app; } \ No newline at end of file diff --git a/src/controller/plugins/greeter.ts b/src/controller/plugins/greeter.ts new file mode 100644 index 0000000..bc6552f --- /dev/null +++ b/src/controller/plugins/greeter.ts @@ -0,0 +1,30 @@ +import { FastifyPluginAsync } from 'fastify'; +import fastifyPlugin from 'fastify-plugin'; + +type PluginOptions = { + message: string +} + +declare module 'fastify' { + interface FastifyRequest { + message: string + } +} + +const createGreeterPlugin: FastifyPluginAsync = async ( + app, + options +) => { + app.decorateRequest('message', '') + + app.addHook('onRequest', async (request) => { + const { message } = options; + request.message = message + }) + + app.addHook('onResponse', async (request) => { + console.log(`Message: ${request.message}`) + }) +} + +export default fastifyPlugin(createGreeterPlugin) \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 2c6a37b..c27b680 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,12 +16,16 @@ const options = { }, }; -const app = createApp(options, { dbClient }); +async function main() { + const app = await createApp(options, { dbClient }) + + app.listen({ port: PORT }, (error, address) => { + if (error) { + app.log.error(error); + process.exit(1); + } + app.log.info(`Server is started successfully.`) + }); +} -app.listen({ port: PORT }, (error, address) => { - if (error) { - app.log.error(error); - process.exit(1); - } - app.log.info(`Server is started successfully.`) -}); \ No newline at end of file +main(); From b0e5110cae01fb7a9e9146c618b80cb5304bd751 Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:31:04 +0100 Subject: [PATCH 5/5] Add error handling. --- package-lock.json | 136 +++++++++++++++++++- package.json | 1 + requests.http | 4 +- src/controller/app.ts | 14 +- src/controller/routes/owner/owner.routes.ts | 8 +- src/service/pet.service.ts | 12 +- 6 files changed, 164 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index afce7e8..2851a25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@fastify/sensible": "^6.0.1", "@fastify/type-provider-json-schema-to-ts": "^4.0.1", "dotenv": "^16.4.5", "fastify": "^5.0.0", @@ -729,6 +730,21 @@ "fast-deep-equal": "^3.1.3" } }, + "node_modules/@fastify/sensible": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@fastify/sensible/-/sensible-6.0.1.tgz", + "integrity": "sha512-D0rN0kMeZKP23f4w9MoCI9e4+n7vASFAsGoRNn9bondSbplLeIfR2HcjCbyElAM04jGrPRLi/edyThEPOyC9cQ==", + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.2", + "dequal": "^2.0.3", + "fastify-plugin": "^5.0.0-pre.fv5.1", + "forwarded": "^0.2.0", + "http-errors": "^2.0.0", + "type-is": "^1.6.18", + "vary": "^1.1.2" + } + }, "node_modules/@fastify/type-provider-json-schema-to-ts": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@fastify/type-provider-json-schema-to-ts/-/type-provider-json-schema-to-ts-4.0.1.tgz", @@ -1213,6 +1229,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -2225,6 +2250,24 @@ "node": ">=0.10.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2852,6 +2895,22 @@ "dev": true, "license": "MIT" }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -2935,7 +2994,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/ipaddr.js": { @@ -3938,6 +3996,15 @@ "tmpl": "1.0.5" } }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3959,6 +4026,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -4942,6 +5030,12 @@ "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==", "license": "MIT" }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5061,6 +5155,15 @@ "node": ">=10" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5264,6 +5367,15 @@ "node": ">=12" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/touch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", @@ -5396,6 +5508,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", @@ -5477,6 +5602,15 @@ "node": ">=10.12.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index fd1de69..deb3638 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "license": "ISC", "description": "", "dependencies": { + "@fastify/sensible": "^6.0.1", "@fastify/type-provider-json-schema-to-ts": "^4.0.1", "dotenv": "^16.4.5", "fastify": "^5.0.0", diff --git a/requests.http b/requests.http index 666e2a4..72c983f 100644 --- a/requests.http +++ b/requests.http @@ -6,7 +6,7 @@ GET {{host}}/api/pets ### -GET {{host}}/api/pets/3 +GET {{host}}/api/pets/399 ### @@ -18,7 +18,7 @@ Content-Type: application/json ### -PUT {{host}}/api/owners/1/pets/24 +PUT {{host}}/api/owners/1/pets/3999 ### diff --git a/src/controller/app.ts b/src/controller/app.ts index b244062..343ad23 100644 --- a/src/controller/app.ts +++ b/src/controller/app.ts @@ -1,5 +1,5 @@ import fastify from 'fastify'; -import { PetService } from '../service/pet.service'; +import { PetNotFoundError, PetService, PetTakenError } from '../service/pet.service'; import { PetRepository } from '../repository/pet.repository'; import { DbClient } from '../db'; import { OwnerRepository } from '../repository/owner.repository'; @@ -7,6 +7,8 @@ import { OwnerService } from '../service/owner.service'; import { createPetRoutes } from './routes/pet/pet.routes'; import { createOwnerRoutes } from './routes/owner/owner.routes'; import createGreeterPlugin from './plugins/greeter'; +import { httpErrors } from '@fastify/sensible'; +import { log } from 'console'; type Dependencies = { dbClient: DbClient; @@ -32,6 +34,16 @@ export default async function createApp(options = {}, dependencies: Dependencies app.decorate('petService', petService); app.decorate('ownerService', ownerService) + app.setErrorHandler((error) => { + app.log.error(error); + if(error instanceof PetNotFoundError) { + return httpErrors.notFound('Pet not found.') + } else if (error instanceof PetTakenError) { + return httpErrors.badRequest('Pet has been already taken.') + } else { + return httpErrors.internalServerError('Something went wrong. 😥') + } + }) await app.register(createGreeterPlugin, { message: 'Hi' }) await app.register(createPetRoutes, { prefix: '/api/pets' }); diff --git a/src/controller/routes/owner/owner.routes.ts b/src/controller/routes/owner/owner.routes.ts index d1c615e..3eac839 100644 --- a/src/controller/routes/owner/owner.routes.ts +++ b/src/controller/routes/owner/owner.routes.ts @@ -2,6 +2,8 @@ import { FastifyPluginAsync } from 'fastify' import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' import { putPetsToOwnersSchema } from '../pet/pet.schemas'; import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './owner.schemas'; +import { PetNotFoundError, PetTakenError } from '../../../service/pet.service'; +import { httpErrors } from '@fastify/sensible'; export const createOwnerRoutes: FastifyPluginAsync = async ( app, @@ -12,9 +14,9 @@ export const createOwnerRoutes: FastifyPluginAsync = async ( '/:ownerId/pets/:petId', { schema: putPetsToOwnersSchema }, async (request) => { - const { petId, ownerId } = request.params; - const updated = await app.petService.adopt(petId, ownerId); - return updated; + const { petId, ownerId } = request.params; + const updated = await app.petService.adopt(petId, ownerId); + return updated; } ) diff --git a/src/service/pet.service.ts b/src/service/pet.service.ts index f9134d0..dbc6c10 100644 --- a/src/service/pet.service.ts +++ b/src/service/pet.service.ts @@ -1,6 +1,10 @@ import { PetToCreate } from "../entity/pet.type"; import { PetRepository } from "../repository/pet.repository" + +export class PetNotFoundError extends Error {}; +export class PetTakenError extends Error {}; + export class PetService { private readonly repository; @@ -15,7 +19,7 @@ export class PetService { async getById(id: number) { const pet = await this.repository.readById(id); if(!pet) { - throw new Error('Pet not found'); + throw new PetNotFoundError(); } return pet; } @@ -27,14 +31,14 @@ export class PetService { async adopt(petId: number, ownerId: number) { const pet = await this.repository.readById(petId); if (!pet) { - throw new Error('Pet does not exists.'); + throw new PetNotFoundError(); } if(pet.ownerId !== null) { - throw new Error('Pet has already have an owner.') + throw new PetTakenError(); } const adopted = await this.repository.update(petId, { ownerId }) if (!adopted) { - throw new Error('Pet could not be adopted, because it is disappeared.'); + throw new PetNotFoundError(); } return adopted; }