diff --git a/package.json b/package.json index 2e3bde2..d4be63a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "api-next", - "version": "1.1.0", + "version": "1.2.0", "repository": "https://github.com/imflavio/api-next", "author": "Flávio Carvalho ", "license": "MIT", diff --git a/src/hooks.ts b/src/hooks.ts index 139b8dd..46c5bef 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -17,6 +17,7 @@ export const connectToDatabase = ({ cachedDb[name] = true } } catch (err) { + console.error(err) throw new DatabaseConnectionError() } } diff --git a/src/mongoose.ts b/src/mongoose.ts index d5ce046..df32e5c 100644 --- a/src/mongoose.ts +++ b/src/mongoose.ts @@ -6,11 +6,11 @@ import { NotFoundError } from './errors/not-found-error' export const createMongooseMethods = ( Model: ReturnType, ): ServiceMethods => ({ - find: async () => Model.find(), + find: async (query) => Model.find(query), create: async (body) => Model.create(body), - get: async (pk) => Model.findById(pk), - update: async (pk, body) => { - const data = await Model.findById(pk) + get: async (pk, query) => Model.findOne({ _id: pk, ...query }), + update: async (pk, body, query) => { + const data = await Model.findOne({ _id: pk, ...query }) if (!data) throw new NotFoundError() data.set(body) diff --git a/src/services.ts b/src/services.ts index 22b96f5..db811b2 100644 --- a/src/services.ts +++ b/src/services.ts @@ -39,94 +39,106 @@ const getPk = (pk: Pk = {}, query: NextApiRequest['query']): string | null => { return pk?.cast && value ? pk.cast(value!) : value } -const getStatusCode = (type: keyof ServiceMethods) => { - switch (type) { - case 'create': - return Status.HTTP_201_CREATED - default: - return Status.HTTP_200_OK - } -} - -const runHandler = async ( - type: keyof ServiceMethods, - options: ServiceOptions, - req: NextApiRequest, - pk: string, -) => { - switch (type) { - case 'find': - return options.find!(req.query) - case 'create': - return options.create!(req.body) - case 'get': - return options.get!(pk, req.query) - case 'update': - return options.update!(pk, req.body, req.query) - case 'patch': - return options.patch!(pk, req.body, req.query) - case 'remove': - return options.remove!(pk) - } -} - -const handleService = async ( - type: keyof ServiceMethods, - options: ServiceOptions, +export const createService = (options: ServiceOptions) => async ( req: NextApiRequest, res: NextApiResponse, ) => { - const pk = getPk(options.pk, req.query) + logger(req, res, () => {}) - const runHooks = async (hooks: Hook[]) => { - for (let index = 0; index < hooks.length; index++) { - await hooks[index](req, res, () => {}) + try { + const pk = getPk(options.pk, req.query) + const method = req.method?.toUpperCase() + + const runHooks = async (hooks: Hook[]) => { + for (let index = 0; index < hooks.length; index++) { + await hooks[index](req, res, () => {}) + } } - } - const allBeforeHooks = options.hooks?.before?.all ?? [] - const beforeHooks = options.hooks?.before?.[type] ?? [] - await runHooks([...allBeforeHooks, ...beforeHooks]) + switch (true) { + case 'get' in options && pk && method === Method.GET: { + const allBeforeHooks = options.hooks?.before?.all ?? [] + const beforeHooks = options.hooks?.before?.get ?? [] + await runHooks([...allBeforeHooks, ...beforeHooks]) - const result = await runHandler(type, options, req, pk!) + const result = await options.get!(pk!, req.query) - const allAfterHooks = options.hooks?.after?.all ?? [] - const afterHooks = options.hooks?.after?.[type] ?? [] - await runHooks([...allAfterHooks, ...afterHooks]) + const allAfterHooks = options.hooks?.after?.all ?? [] + const afterHooks = options.hooks?.after?.get ?? [] + await runHooks([...allAfterHooks, ...afterHooks]) - const statusCode = getStatusCode(type) + return res.status(Status.HTTP_200_OK).json(result) + } - return res.status(statusCode).json(result) -} + case 'update' in options && pk && method === Method.PUT: { + const allBeforeHooks = options.hooks?.before?.all ?? [] + const beforeHooks = options.hooks?.before?.update ?? [] + await runHooks([...allBeforeHooks, ...beforeHooks]) -export const createService = (options: ServiceOptions) => async ( - req: NextApiRequest, - res: NextApiResponse, -) => { - logger(req, res, () => {}) + const result = await options.update!(pk!, req.body, req.query) - try { - const pk = getPk(options.pk, req.query) - const { method } = req + const allAfterHooks = options.hooks?.after?.all ?? [] + const afterHooks = options.hooks?.after?.update ?? [] + await runHooks([...allAfterHooks, ...afterHooks]) - switch (true) { - case options.get && pk && method === Method.GET: - return handleService('get', options, req, res) + return res.status(Status.HTTP_200_OK).json(result) + } + + case 'patch' in options && pk && method === Method.PATCH: { + const allBeforeHooks = options.hooks?.before?.all ?? [] + const beforeHooks = options.hooks?.before?.patch ?? [] + await runHooks([...allBeforeHooks, ...beforeHooks]) + + const result = await options.patch!(pk!, req.body, req.query) + + const allAfterHooks = options.hooks?.after?.all ?? [] + const afterHooks = options.hooks?.after?.patch ?? [] + await runHooks([...allAfterHooks, ...afterHooks]) + + return res.status(Status.HTTP_200_OK).json(result) + } + + case 'remove' in options && pk && method === Method.DELETE: { + const allBeforeHooks = options.hooks?.before?.all ?? [] + const beforeHooks = options.hooks?.before?.remove ?? [] + await runHooks([...allBeforeHooks, ...beforeHooks]) + + const result = await options.remove!(pk!) + + const allAfterHooks = options.hooks?.after?.all ?? [] + const afterHooks = options.hooks?.after?.remove ?? [] + await runHooks([...allAfterHooks, ...afterHooks]) + + return res.status(Status.HTTP_200_OK).json(result) + } + + case 'find' in options && !pk && method === Method.GET: { + const allBeforeHooks = options.hooks?.before?.all ?? [] + const beforeHooks = options.hooks?.before?.find ?? [] + await runHooks([...allBeforeHooks, ...beforeHooks]) + + const result = await options.find!(req.query) + + const allAfterHooks = options.hooks?.after?.all ?? [] + const afterHooks = options.hooks?.after?.find ?? [] + await runHooks([...allAfterHooks, ...afterHooks]) - case options.update && pk && method === Method.PUT: - return handleService('update', options, req, res) + return res.status(Status.HTTP_200_OK).json(result) + } - case options.patch && pk && method === Method.PATCH: - return handleService('patch', options, req, res) + case 'create' in options && !pk && method === Method.POST: { + const allBeforeHooks = options.hooks?.before?.all ?? [] + const beforeHooks = options.hooks?.before?.create ?? [] + await runHooks([...allBeforeHooks, ...beforeHooks]) - case options.remove && pk && method === Method.DELETE: - return handleService('remove', options, req, res) + const result = await options.create!(req.body) - case options.find && !pk && method === Method.GET: - return handleService('find', options, req, res) + const allAfterHooks = options.hooks?.after?.all ?? [] + const afterHooks = options.hooks?.after?.create ?? [] + await runHooks([...allAfterHooks, ...afterHooks]) - case options.create && !pk && method === Method.POST: - return handleService('create', options, req, res) + return res.status(Status.HTTP_201_CREATED).json(result) + } default: throw new NotFoundError() diff --git a/src/types.ts b/src/types.ts index f562bdc..f21f425 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,12 +9,12 @@ export type ApiNextQuery = NextApiRequest['query'] export type ApiNextBody = NextApiRequest['body'] export interface ServiceMethods { - find?(query: ApiNextQuery): Promise - create?: (body: ApiNextBody) => Promise get?: (pk: string, query: ApiNextQuery) => Promise update?: (pk: string, body: ApiNextBody, query: ApiNextQuery) => Promise patch?: (pk: string, body: ApiNextBody, query: ApiNextQuery) => Promise remove?: (pk: string) => Promise + find?(query: ApiNextQuery): Promise + create?: (body: ApiNextBody) => Promise } export type Hook = ( diff --git a/test/errors.test.ts b/test/errors.test.ts new file mode 100644 index 0000000..220f62b --- /dev/null +++ b/test/errors.test.ts @@ -0,0 +1,28 @@ +import { Method, Status } from 'simple-http-status' + +import { createService, testService, NotAuthorised } from '../src' + +describe('[errors/generic]', () => { + test('Should return error object', async () => { + const service = createService({ + hooks: { + before: { + get: [ + async () => { + throw new NotAuthorised() + }, + ], + }, + }, + get: async () => ({ + hello: 'there', + }), + }) + const { statusCode, data } = await testService(service, { + method: Method.GET, + query: { id: '1' }, + }) + expect(statusCode).toBe(Status.HTTP_401_UNAUTHORIZED) + expect(data).toEqual({ errors: [{ message: 'Not authorised' }] }) + }) +})