diff --git a/src/api/plugins/auth.ts b/src/api/plugins/auth.ts index 81ca1934..a05aba77 100644 --- a/src/api/plugins/auth.ts +++ b/src/api/plugins/auth.ts @@ -3,7 +3,6 @@ import fp from 'fastify-plugin' import { User } from '@prisma/client' import apiConfig from '../../config/api' -import { GarboAPIError } from '../../lib/garbo-api-error' import { prisma } from '../../lib/prisma' declare module 'fastify' { @@ -17,14 +16,18 @@ declare module 'fastify' { } } +const unauthorizedError = { + message: 'Unauthorized', +} + async function authPlugin(app: FastifyInstance) { app.decorateRequest('user') - app.addHook('onRequest', async (request) => { + app.addHook('onRequest', async (request, reply) => { try { const token = request.headers['authorization']?.replace('Bearer ', '') if (!token || !apiConfig.tokens?.includes(token)) { - throw GarboAPIError.unauthorized() + return reply.status(401).send(unauthorizedError) } const [username] = token.split(':') @@ -38,12 +41,12 @@ async function authPlugin(app: FastifyInstance) { }) if (!user?.id) { - throw GarboAPIError.unauthorized() + return reply.status(401).send(unauthorizedError) } request.user = user } catch (error) { - throw GarboAPIError.unauthorized() + return reply.status(401).send(unauthorizedError) } }) } diff --git a/src/api/plugins/errorhandler.ts b/src/api/plugins/errorhandler.ts index 503cd107..e5fc68cd 100644 --- a/src/api/plugins/errorhandler.ts +++ b/src/api/plugins/errorhandler.ts @@ -1,25 +1,37 @@ -import { FastifyReply, FastifyRequest } from 'fastify' +import { FastifyReply, FastifyRequest, FastifyError } from 'fastify' +import { Prisma } from '@prisma/client' -import { GarboAPIError } from '../../lib/garbo-api-error' +import apiConfig from '../../config/api' -export const errorHandler = ( +export function errorHandler( error: Error, request: FastifyRequest, reply: FastifyReply -) => { +) { request.log.error(error) - if (error instanceof GarboAPIError) { - request.log.error(error.original) - return reply.code(error.statusCode).send({ - error: error.message, - details: error.original, - help: 'Contact support if the problem persists', + if ((error as FastifyError)?.validation) { + const fastifyError = error as FastifyError + + reply.status(400).send({ + code: 'VALIDATION_ERROR', + message: fastifyError.message, + details: apiConfig.DEV ? fastifyError : fastifyError.validation, + }) + } else if ( + error instanceof Prisma.PrismaClientKnownRequestError && + error.code === 'P2025' + ) { + reply.status(404).send({ + code: 'NOT_FOUND', + message: 'The requested resource could not be found.', + details: apiConfig.DEV ? error : undefined, + }) + } else { + reply.status(500).send({ + code: 'INTERNAL_SERVER_ERROR', + message: apiConfig.DEV ? error.message : 'An unexpected error occurred.', + details: apiConfig.DEV ? error : undefined, }) } - - reply.code(500).send({ - error: 'Internal Server Error', - help: 'Contact support if the problem persists', - }) } diff --git a/src/api/routes/company.delete.ts b/src/api/routes/company.delete.ts index 56a1833a..4f9d6a02 100644 --- a/src/api/routes/company.delete.ts +++ b/src/api/routes/company.delete.ts @@ -10,6 +10,7 @@ import { wikidataIdParamSchema, emptyBodySchema, garboEntityIdSchema, + getErrorSchemas, } from '../schemas' import { getTags } from '../../config/openapi' import { GarboEntityId, WikidataIdParams } from '../types' @@ -20,11 +21,12 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete company', - description: 'Deletes a company by Wikidata ID', + description: 'Delete a company by Wikidata ID', tags: getTags('Companies'), params: wikidataIdParamSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -43,11 +45,12 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete a goal', - description: 'Deletes a goal by id', + description: 'Delete a goal by id', tags: getTags('Goals'), params: garboEntityIdSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -71,6 +74,7 @@ export async function companyDeleteRoutes(app: FastifyInstance) { params: garboEntityIdSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -89,11 +93,11 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete an initiative', - description: 'Deletes an initiative by id', + description: 'Delete an initiative by id', tags: getTags('Initiatives'), params: garboEntityIdSchema, response: { - 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -112,11 +116,12 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete a reporting period', - description: 'Deletes a reporting period by id', + description: 'Delete a reporting period by id', tags: getTags('ReportingPeriods'), params: garboEntityIdSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -135,11 +140,12 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete stated total emissions', - description: 'Deletes stated total emissions by id', + description: 'Delete stated total emissions by id', tags: getTags('Emissions'), params: garboEntityIdSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -158,11 +164,12 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete biogenic emissions', - description: 'Deletes biogenic emissions by id', + description: 'Delete biogenic emissions by id', tags: getTags('Emissions'), params: garboEntityIdSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -181,11 +188,12 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete Scope1', - description: 'Deletes the Scope1 emissions by id', + description: 'Delete the Scope1 emissions by id', tags: getTags('Emissions'), params: garboEntityIdSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -204,11 +212,12 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete scope1and2', - description: 'Deletes a scope1and2 by id', + description: 'Delete a scope1and2 by id', tags: getTags('Emissions'), params: garboEntityIdSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -227,11 +236,12 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete scope2', - description: 'Deletes a scope2 by id', + description: 'Delete a scope2 by id', tags: getTags('Emissions'), params: garboEntityIdSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -250,11 +260,12 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete scope3', - description: 'Deletes a scope3 by id', + description: 'Delete a scope3 by id', tags: getTags('Emissions'), params: garboEntityIdSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -273,11 +284,12 @@ export async function companyDeleteRoutes(app: FastifyInstance) { { schema: { summary: 'Delete a scope3 category', - description: 'Deletes a scope3 category by id', + description: 'Delete a scope3 category by id', tags: getTags('Emissions'), params: garboEntityIdSchema, response: { 204: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, diff --git a/src/api/routes/company.goals.ts b/src/api/routes/company.goals.ts index 1509426b..53bfc61f 100644 --- a/src/api/routes/company.goals.ts +++ b/src/api/routes/company.goals.ts @@ -1,7 +1,5 @@ import { FastifyInstance, AuthenticatedFastifyRequest } from 'fastify' -import { Prisma } from '@prisma/client' -import { GarboAPIError } from '../../lib/garbo-api-error' import { goalService } from '../services/goalService' import { wikidataIdParamSchema, @@ -9,6 +7,7 @@ import { postGoalsSchema, okResponseSchema, garboEntityIdSchema, + getErrorSchemas, } from '../schemas' import { PostGoalsBody, @@ -31,6 +30,7 @@ export async function companyGoalsRoutes(app: FastifyInstance) { body: postGoalsSchema, response: { 200: okResponseSchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -69,6 +69,7 @@ export async function companyGoalsRoutes(app: FastifyInstance) { body: postGoalSchema, response: { 200: okResponseSchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -87,20 +88,8 @@ export async function companyGoalsRoutes(app: FastifyInstance) { user: request.user, }) - await goalService - .updateGoal(id, { goal }, createdMetadata) - .catch((error) => { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('Goal not found', { - statusCode: 404, - original: error, - }) - } - throw error - }) + await goalService.updateGoal(id, { goal }, createdMetadata) + reply.send({ ok: true }) } ) diff --git a/src/api/routes/company.industry.ts b/src/api/routes/company.industry.ts index 61bf2501..2beca3fe 100644 --- a/src/api/routes/company.industry.ts +++ b/src/api/routes/company.industry.ts @@ -1,9 +1,8 @@ import { FastifyInstance, AuthenticatedFastifyRequest } from 'fastify' import { prisma } from '../../lib/prisma' -import { GarboAPIError } from '../../lib/garbo-api-error' import { industryService } from '../services/industryService' -import { postIndustrySchema } from '../schemas' +import { getErrorSchemas, postIndustrySchema } from '../schemas' import { metadataService } from '../services/metadataService' import { getTags } from '../../config/openapi' import { wikidataIdParamSchema, okResponseSchema } from '../schemas' @@ -22,6 +21,7 @@ export async function companyIndustryRoutes(app: FastifyInstance) { body: postIndustrySchema, response: { 200: okResponseSchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -38,7 +38,7 @@ export async function companyIndustryRoutes(app: FastifyInstance) { } = request.body const { wikidataId } = request.params - const current = await prisma.industry.findFirst({ + const current = await prisma.industry.findFirstOrThrow({ where: { companyWikidataId: wikidataId }, }) @@ -48,23 +48,17 @@ export async function companyIndustryRoutes(app: FastifyInstance) { }) if (current) { - await industryService - .updateIndustry(wikidataId, { subIndustryCode }, createdMetadata) - .catch((error) => { - throw new GarboAPIError('Failed to update industry', { - original: error, - statusCode: 500, - }) - }) + await industryService.updateIndustry( + wikidataId, + { subIndustryCode }, + createdMetadata + ) } else { - await industryService - .createIndustry(wikidataId, { subIndustryCode }, createdMetadata) - .catch((error) => { - throw new GarboAPIError('Failed to create industry', { - original: error, - statusCode: 500, - }) - }) + await industryService.createIndustry( + wikidataId, + { subIndustryCode }, + createdMetadata + ) } reply.send({ ok: true }) diff --git a/src/api/routes/company.initiatives.ts b/src/api/routes/company.initiatives.ts index bd2e6c4b..1a72ca3b 100644 --- a/src/api/routes/company.initiatives.ts +++ b/src/api/routes/company.initiatives.ts @@ -1,7 +1,5 @@ import { FastifyInstance, AuthenticatedFastifyRequest } from 'fastify' -import { Prisma } from '@prisma/client' -import { GarboAPIError } from '../../lib/garbo-api-error' import { getTags } from '../../config/openapi' import { metadataService } from '../services/metadataService' import { @@ -10,6 +8,7 @@ import { postInitiativeSchema, postInitiativesSchema, garboEntityIdSchema, + getErrorSchemas, } from '../schemas' import { initiativeService } from '../services/initiativeService' import { @@ -31,6 +30,7 @@ export async function companyInitiativesRoutes(app: FastifyInstance) { body: postInitiativesSchema, response: { 200: okResponseSchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -68,6 +68,7 @@ export async function companyInitiativesRoutes(app: FastifyInstance) { body: postInitiativeSchema, response: { 200: okResponseSchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -84,20 +85,7 @@ export async function companyInitiativesRoutes(app: FastifyInstance) { metadata, user: request.user, }) - await initiativeService - .updateInitiative(id, initiative, createdMetadata) - .catch((error) => { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('Initiative not found', { - statusCode: 404, - original: error, - }) - } - throw error - }) + await initiativeService.updateInitiative(id, initiative, createdMetadata) reply.send({ ok: true }) } diff --git a/src/api/routes/company.read.ts b/src/api/routes/company.read.ts index 1ee8c9f0..6db3abf9 100644 --- a/src/api/routes/company.read.ts +++ b/src/api/routes/company.read.ts @@ -1,17 +1,17 @@ import { FastifyInstance, FastifyRequest } from 'fastify' -import { Prisma } from '@prisma/client' import { getGics } from '../../lib/gics' -import { GarboAPIError } from '../../lib/garbo-api-error' import { prisma } from '../../lib/prisma' import { getTags } from '../../config/openapi' import { WikidataIdParams } from '../types' import { cachePlugin } from '../plugins/cache' import { companyListArgs, detailedCompanyArgs } from '../args' -import { CompanyList } from '../schemas' -import { wikidataIdParamSchema } from '../schemas' -import { CompanyDetails } from '../schemas' -import { emptyBodySchema } from '../schemas' +import { + CompanyList, + wikidataIdParamSchema, + CompanyDetails, + getErrorSchemas, +} from '../schemas' function isNumber(n: unknown): n is number { return Number.isFinite(n) @@ -133,20 +133,13 @@ export async function companyReadRoutes(app: FastifyInstance) { }, }, async (request, reply) => { - try { - const companies = await prisma.company.findMany(companyListArgs) + const companies = await prisma.company.findMany(companyListArgs) - const transformedCompanies = addCalculatedTotalEmissions( - companies.map(transformMetadata) - ) + const transformedCompanies = addCalculatedTotalEmissions( + companies.map(transformMetadata) + ) - reply.send(transformedCompanies) - } catch (error) { - throw new GarboAPIError('Failed to load companies', { - original: error, - statusCode: 500, - }) - } + reply.send(transformedCompanies) } ) @@ -161,57 +154,39 @@ export async function companyReadRoutes(app: FastifyInstance) { params: wikidataIdParamSchema, response: { 200: CompanyDetails, - 404: emptyBodySchema, + ...getErrorSchemas(400, 404), }, }, }, async (request: FastifyRequest<{ Params: WikidataIdParams }>, reply) => { - try { - const { wikidataId } = request.params - - const company = await prisma.company.findFirst({ - ...detailedCompanyArgs, - where: { - wikidataId, - }, - }) + const { wikidataId } = request.params - if (!company) { - return reply.status(404).send() - } + const company = await prisma.company.findFirstOrThrow({ + ...detailedCompanyArgs, + where: { + wikidataId, + }, + }) - const [transformedCompany] = addCalculatedTotalEmissions([ - transformMetadata(company), - ]) + const [transformedCompany] = addCalculatedTotalEmissions([ + transformMetadata(company), + ]) - reply.send({ - ...transformedCompany, - // Add translations for GICS data - industry: transformedCompany.industry - ? { - ...transformedCompany.industry, - industryGics: { - ...transformedCompany.industry.industryGics, - ...getGics( - transformedCompany.industry.industryGics.subIndustryCode - ), - }, - } - : null, - }) - } catch (error) { - if (error instanceof Prisma.PrismaClientKnownRequestError) { - throw new GarboAPIError('Database error while loading company', { - original: error, - statusCode: 500, - }) - } else { - throw new GarboAPIError('Failed to load company', { - original: error, - statusCode: 500, - }) - } - } + reply.send({ + ...transformedCompany, + // Add translations for GICS data + industry: transformedCompany.industry + ? { + ...transformedCompany.industry, + industryGics: { + ...transformedCompany.industry.industryGics, + ...getGics( + transformedCompany.industry.industryGics.subIndustryCode + ), + }, + } + : null, + }) } ) } diff --git a/src/api/routes/company.reportingPeriods.ts b/src/api/routes/company.reportingPeriods.ts index 4d3f307b..b160e3d6 100644 --- a/src/api/routes/company.reportingPeriods.ts +++ b/src/api/routes/company.reportingPeriods.ts @@ -1,10 +1,10 @@ import { FastifyInstance, AuthenticatedFastifyRequest } from 'fastify' -import { GarboAPIError } from '../../lib/garbo-api-error' import { emissionsService } from '../services/emissionsService' import { companyService } from '../services/companyService' import { reportingPeriodService } from '../services/reportingPeriodService' import { + getErrorSchemas, okResponseSchema, postReportingPeriodsSchema, wikidataIdParamSchema, @@ -26,6 +26,7 @@ export async function companyReportingPeriodsRoutes(app: FastifyInstance) { body: postReportingPeriodsSchema, response: { 200: okResponseSchema, + ...getErrorSchemas(400, 404), }, }, }, @@ -42,119 +43,112 @@ export async function companyReportingPeriodsRoutes(app: FastifyInstance) { const company = await companyService.getCompany(wikidataId) - try { - await Promise.allSettled( - reportingPeriods.map( - async ({ - emissions = {}, - economy = {}, - startDate, - endDate, - reportURL, - }) => { - const year = endDate.getFullYear().toString() - const createdMetadata = await metadataService.createMetadata({ - metadata, - user, - }) - const reportingPeriod = - await reportingPeriodService.upsertReportingPeriod( - company, - createdMetadata, - { - startDate, - endDate, - reportURL, - year, - } - ) - - const [dbEmissions, dbEconomy] = await Promise.all([ - emissionsService.upsertEmissions({ - emissionsId: reportingPeriod.emissions?.id ?? '', - reportingPeriodId: reportingPeriod.id, - }), - companyService.upsertEconomy({ - economyId: reportingPeriod.economy?.id ?? '', - reportingPeriodId: reportingPeriod.id, - }), - ]) + await Promise.allSettled( + reportingPeriods.map( + async ({ + emissions = {}, + economy = {}, + startDate, + endDate, + reportURL, + }) => { + const year = endDate.getFullYear().toString() + const createdMetadata = await metadataService.createMetadata({ + metadata, + user, + }) + const reportingPeriod = + await reportingPeriodService.upsertReportingPeriod( + company, + createdMetadata, + { + startDate, + endDate, + reportURL, + year, + } + ) - const { - scope1, - scope2, - scope3, - statedTotalEmissions, - biogenic, - scope1And2, - } = emissions - const { turnover, employees } = economy + const [dbEmissions, dbEconomy] = await Promise.all([ + emissionsService.upsertEmissions({ + emissionsId: reportingPeriod.emissions?.id ?? '', + reportingPeriodId: reportingPeriod.id, + }), + companyService.upsertEconomy({ + economyId: reportingPeriod.economy?.id ?? '', + reportingPeriodId: reportingPeriod.id, + }), + ]) - // Normalise currency - if (turnover?.currency) { - turnover.currency = turnover.currency.trim().toUpperCase() - } + const { + scope1, + scope2, + scope3, + statedTotalEmissions, + biogenic, + scope1And2, + } = emissions + const { turnover, employees } = economy - await Promise.allSettled([ - scope1 && - emissionsService.upsertScope1( - dbEmissions, - scope1, - createdMetadata - ), - scope2 && - emissionsService.upsertScope2( - dbEmissions, - scope2, - createdMetadata - ), - scope3 && - emissionsService.upsertScope3(dbEmissions, scope3, () => - metadataService.createMetadata({ - metadata, - user, - }) - ), - statedTotalEmissions !== undefined && - emissionsService.upsertStatedTotalEmissions( - dbEmissions, - createdMetadata, - statedTotalEmissions - ), - biogenic && - emissionsService.upsertBiogenic( - dbEmissions, - biogenic, - createdMetadata - ), - scope1And2 && - emissionsService.upsertScope1And2( - dbEmissions, - scope1And2, - createdMetadata - ), - turnover && - companyService.upsertTurnover({ - economy: dbEconomy, - metadata: createdMetadata, - turnover, - }), - employees && - companyService.upsertEmployees({ - economy: dbEconomy, - employees, - metadata: createdMetadata, - }), - ]) + // Normalise currency + if (turnover?.currency) { + turnover.currency = turnover.currency.trim().toUpperCase() } - ) + + await Promise.allSettled([ + scope1 && + emissionsService.upsertScope1( + dbEmissions, + scope1, + createdMetadata + ), + scope2 && + emissionsService.upsertScope2( + dbEmissions, + scope2, + createdMetadata + ), + scope3 && + emissionsService.upsertScope3(dbEmissions, scope3, () => + metadataService.createMetadata({ + metadata, + user, + }) + ), + statedTotalEmissions !== undefined && + emissionsService.upsertStatedTotalEmissions( + dbEmissions, + createdMetadata, + statedTotalEmissions + ), + biogenic && + emissionsService.upsertBiogenic( + dbEmissions, + biogenic, + createdMetadata + ), + scope1And2 && + emissionsService.upsertScope1And2( + dbEmissions, + scope1And2, + createdMetadata + ), + turnover && + companyService.upsertTurnover({ + economy: dbEconomy, + metadata: createdMetadata, + turnover, + }), + employees && + companyService.upsertEmployees({ + economy: dbEconomy, + employees, + metadata: createdMetadata, + }), + ]) + } ) - } catch (error) { - throw new GarboAPIError('Failed to update reporting periods', { - original: error, - statusCode: 500, - }) - } + ) reply.send({ ok: true }) } diff --git a/src/api/routes/company.update.ts b/src/api/routes/company.update.ts index 0bb14d2b..3ad6fb96 100644 --- a/src/api/routes/company.update.ts +++ b/src/api/routes/company.update.ts @@ -1,7 +1,11 @@ import { AuthenticatedFastifyRequest, FastifyInstance } from 'fastify' import { companyService } from '../services/companyService' -import { postCompanyBodySchema, okResponseSchema } from '../schemas' +import { + postCompanyBodySchema, + okResponseSchema, + getErrorSchemas, +} from '../schemas' import { getTags } from '../../config/openapi' import { PostCompanyBody, WikidataIdParams } from '../types' @@ -17,6 +21,7 @@ export async function companyUpdateRoutes(app: FastifyInstance) { body: postCompanyBodySchema, response: { 200: okResponseSchema, + ...getErrorSchemas(400, 404), }, }, }, diff --git a/src/api/schemas/common.ts b/src/api/schemas/common.ts index d9f44bf1..961a9498 100644 --- a/src/api/schemas/common.ts +++ b/src/api/schemas/common.ts @@ -1,4 +1,7 @@ import { z } from 'zod' +import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi' + +extendZodWithOpenApi(z) export const wikidataIdSchema = z.string().regex(/Q\d+/) @@ -12,3 +15,18 @@ export const garboEntityIdSchema = z.object({ id: z.string() }) export const yearSchema = z.string().regex(/\d{4}(?:-\d{4})?/) export const yearParamSchema = z.object({ year: yearSchema }) + +export const errorSchema = z.object({ + code: z.string().openapi('Error code'), + message: z.string().optional().openapi('Error message'), + details: z.any().optional(), +}) + +const errorCodes = [400, 404] as const + +export function getErrorSchemas(...codes: (typeof errorCodes)[number][]) { + return codes.reduce((acc, code) => { + acc[code] = errorSchema + return acc + }, {}) +} diff --git a/src/api/schemas/response.ts b/src/api/schemas/response.ts index 5d22b73b..e3d3b07b 100644 --- a/src/api/schemas/response.ts +++ b/src/api/schemas/response.ts @@ -31,14 +31,6 @@ export const MetadataSchema = z.object({ export const MinimalMetadataSchema = MetadataSchema.pick({ verifiedBy: true }) -export const ErrorSchema = z.object({ - error: z.string().openapi({ description: 'Error message' }), - details: z - .any() - .nullable() - .openapi({ description: 'Additional error details' }), -}) - const CompanyBaseSchema = z.object({ wikidataId: wikidataIdSchema, name: z.string(), diff --git a/src/api/services/companyService.ts b/src/api/services/companyService.ts index 28e0b274..92983341 100644 --- a/src/api/services/companyService.ts +++ b/src/api/services/companyService.ts @@ -3,18 +3,13 @@ import { Economy, Employees, Metadata, Turnover } from '@prisma/client' import { OptionalNullable } from '../../lib/type-utils' import { DefaultEconomyType } from '../types' import { prisma } from '../../lib/prisma' -import { GarboAPIError } from '../../lib/garbo-api-error' import { economyArgs } from '../args' class CompanyService { async getCompany(wikidataId: string) { - const company = await prisma.company.findFirst({ + return prisma.company.findFirstOrThrow({ where: { wikidataId }, }) - if (!company) { - throw new GarboAPIError('Company not found', { statusCode: 404 }) - } - return company } async upsertCompany({ @@ -46,8 +41,7 @@ class CompanyService { } async deleteCompany(wikidataId: string) { - const _company = await this.getCompany(wikidataId) - await prisma.company.delete({ where: { wikidataId } }) + return prisma.company.delete({ where: { wikidataId } }) } async upsertEconomy({ diff --git a/src/api/services/emissionsService.ts b/src/api/services/emissionsService.ts index f26a5f5a..dae1ce82 100644 --- a/src/api/services/emissionsService.ts +++ b/src/api/services/emissionsService.ts @@ -1,7 +1,6 @@ import { BiogenicEmissions, Metadata, - Prisma, Scope1, Scope1And2, Scope3, @@ -11,7 +10,6 @@ import { import { OptionalNullable } from '../../lib/type-utils' import { DefaultEmissions } from '../types' import { prisma } from '../../lib/prisma' -import { GarboAPIError } from '../../lib/garbo-api-error' import { emissionsArgs } from '../args' const TONNES_CO2_UNIT = 'tCO2e' @@ -78,19 +76,7 @@ class EmissionsService { } async deleteScope1(id: Scope1['id']) { - try { - return await prisma.scope1.delete({ where: { id } }) - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('Scope1 not found', { - statusCode: 404, - }) - } - throw error - } + return await prisma.scope1.delete({ where: { id } }) } async upsertScope2( @@ -137,19 +123,7 @@ class EmissionsService { } async deleteScope2(id: string) { - try { - return await prisma.scope2.delete({ where: { id } }) - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('Scope2 not found', { - statusCode: 404, - }) - } - throw error - } + return await prisma.scope2.delete({ where: { id } }) } async upsertScope1And2( @@ -192,19 +166,7 @@ class EmissionsService { } async deleteScope1And2(id: Scope1And2['id']) { - try { - return await prisma.scope1And2.delete({ where: { id } }) - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('Scope1And2 not found', { - statusCode: 404, - }) - } - throw error - } + return await prisma.scope1And2.delete({ where: { id } }) } async upsertScope3( @@ -302,35 +264,11 @@ class EmissionsService { } async deleteScope3(id: Scope3['id']) { - try { - return await prisma.scope3.delete({ where: { id } }) - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('Scope3 not found', { - statusCode: 404, - }) - } - throw error - } + return await prisma.scope3.delete({ where: { id } }) } async deleteScope3Category(id: Scope3Category['id']) { - try { - return await prisma.scope3Category.delete({ where: { id } }) - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('Scope3Category not found', { - statusCode: 404, - }) - } - throw error - } + return await prisma.scope3Category.delete({ where: { id } }) } async upsertBiogenic( @@ -377,19 +315,7 @@ class EmissionsService { } async deleteBiogenicEmissions(id: BiogenicEmissions['id']) { - try { - return await prisma.biogenicEmissions.delete({ where: { id } }) - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('BiogenicEmissions not found', { - statusCode: 404, - }) - } - throw error - } + return await prisma.biogenicEmissions.delete({ where: { id } }) } async upsertStatedTotalEmissions( @@ -435,19 +361,7 @@ class EmissionsService { } async deleteStatedTotalEmissions(id: StatedTotalEmissions['id']) { - try { - return await prisma.statedTotalEmissions.delete({ where: { id } }) - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('StatedTotalEmissions not found', { - statusCode: 404, - }) - } - throw error - } + return await prisma.statedTotalEmissions.delete({ where: { id } }) } } diff --git a/src/api/services/goalService.ts b/src/api/services/goalService.ts index 46d23d21..fc8a96f3 100644 --- a/src/api/services/goalService.ts +++ b/src/api/services/goalService.ts @@ -1,7 +1,6 @@ -import { Company, Goal, Metadata, Prisma } from '@prisma/client' +import { Company, Goal, Metadata } from '@prisma/client' import { prisma } from '../../lib/prisma' import { PostGoalBody, PostGoalsBody } from '../types' -import { GarboAPIError } from '../../lib/garbo-api-error' class GoalService { async createGoals( @@ -49,17 +48,7 @@ class GoalService { } async deleteGoal(id: Goal['id']) { - try { - return await prisma.goal.delete({ where: { id } }) - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('Goal not found', { statusCode: 404 }) - } - throw error - } + return prisma.goal.delete({ where: { id } }) } } export const goalService = new GoalService() diff --git a/src/api/services/industryService.ts b/src/api/services/industryService.ts index 07101495..a79ed939 100644 --- a/src/api/services/industryService.ts +++ b/src/api/services/industryService.ts @@ -1,6 +1,5 @@ import { Company, Metadata } from '@prisma/client' import { prisma } from '../../lib/prisma' -import { GarboAPIError } from '../../lib/garbo-api-error' class IndustryService { async createIndustry( @@ -52,15 +51,7 @@ class IndustryService { } async deleteIndustry(wikidataId: string) { - const industry = await prisma.industry.findFirst({ - where: { companyWikidataId: wikidataId }, - }) - - if (!industry) { - throw new GarboAPIError('Industry not found', { statusCode: 404 }) - } - - return prisma.industry.delete({ + return await prisma.industry.findFirstOrThrow({ where: { companyWikidataId: wikidataId }, }) } diff --git a/src/api/services/initiativeService.ts b/src/api/services/initiativeService.ts index 08fcb1c3..4d9e8dc1 100644 --- a/src/api/services/initiativeService.ts +++ b/src/api/services/initiativeService.ts @@ -1,7 +1,6 @@ -import { Company, Initiative, Metadata, Prisma } from '@prisma/client' +import { Company, Initiative, Metadata } from '@prisma/client' import { OptionalNullable } from '../../lib/type-utils' import { prisma } from '../../lib/prisma' -import { GarboAPIError } from '../../lib/garbo-api-error' class InitiativeService { async createInitiatives( @@ -57,17 +56,7 @@ class InitiativeService { } async deleteInitiative(id: Initiative['id']) { - try { - return await prisma.initiative.delete({ where: { id } }) - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('Initiative not found', { statusCode: 404 }) - } - throw error - } + return await prisma.initiative.delete({ where: { id } }) } } diff --git a/src/api/services/reportingPeriodService.ts b/src/api/services/reportingPeriodService.ts index 9dfd8393..0a36f527 100644 --- a/src/api/services/reportingPeriodService.ts +++ b/src/api/services/reportingPeriodService.ts @@ -1,6 +1,5 @@ -import { Company, Prisma, ReportingPeriod } from '@prisma/client' +import { Company, ReportingPeriod } from '@prisma/client' import { prisma } from '../../lib/prisma' -import { GarboAPIError } from '../../lib/garbo-api-error' import { reportingPeriodArgs } from '../args' class ReportingPeriodService { @@ -51,46 +50,8 @@ class ReportingPeriodService { }) } - async updateReportingPeriodReportURL( - company: Company, - year: string, - reportURL: string - ) { - const reportingPeriod = await prisma.reportingPeriod.findFirst({ - where: { - companyId: company.wikidataId, - year, - }, - }) - - if (!reportingPeriod) { - return false - } - - return prisma.reportingPeriod.update({ - where: { - id: reportingPeriod.id, - }, - data: { - reportURL, - }, - }) - } - async deleteReportingPeriod(id: ReportingPeriod['id']) { - try { - return await prisma.reportingPeriod.delete({ where: { id } }) - } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === 'P2025' - ) { - throw new GarboAPIError('ReportingPeriod not found', { - statusCode: 404, - }) - } - throw error - } + return await prisma.reportingPeriod.delete({ where: { id } }) } } diff --git a/src/config/api.ts b/src/config/api.ts index eda19bb3..0bf479cc 100644 --- a/src/config/api.ts +++ b/src/config/api.ts @@ -40,7 +40,9 @@ const baseLoggerOptions: FastifyServerOptions['logger'] = { redact: ['req.headers.authorization'], } -export default { +const DEV = env.NODE_ENV === 'development' + +const apiConfig = { cacheMaxAge: env.CACHE_MAX_AGE, authorizedUsers: { @@ -48,9 +50,9 @@ export default { alex: 'alex@klimatkollen.se', } as const, - corsAllowOrigins: - env.NODE_ENV === 'development' ? developmentOrigins : productionOrigins, + corsAllowOrigins: DEV ? developmentOrigins : productionOrigins, + DEV, tokens: env.API_TOKENS, frontendURL: env.FRONTEND_URL, baseURL: env.API_BASE_URL, @@ -59,7 +61,7 @@ export default { bullBoardBasePath: '/admin/queues', - logger: (env.NODE_ENV === 'development' && process.stdout.isTTY + logger: (DEV && process.stdout.isTTY ? { level: 'trace', transport: { target: 'pino-pretty' }, @@ -70,3 +72,5 @@ export default { ...baseLoggerOptions, }) as FastifyServerOptions['logger'], } + +export default apiConfig diff --git a/src/lib/garbo-api-error.ts b/src/lib/garbo-api-error.ts deleted file mode 100644 index ab1ee124..00000000 --- a/src/lib/garbo-api-error.ts +++ /dev/null @@ -1,23 +0,0 @@ -export class GarboAPIError extends Error { - statusCode: number - original?: Error - - constructor( - message: string, - options: { - statusCode?: number - original?: Error - } = {} - ) { - super(message) - - const { statusCode = 400, original } = options - - this.statusCode = statusCode - this.original = original - } - - static unauthorized() { - return new GarboAPIError('Unauthorized', { statusCode: 401 }) - } -}