mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
feat: handle http exceptions (#456)
This commit is contained in:
@@ -207,7 +207,6 @@ export default class AccountsController extends BaseController {
|
|||||||
tenantId,
|
tenantId,
|
||||||
accountDTO
|
accountDTO
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: account.id,
|
id: account.id,
|
||||||
message: 'The account has been created successfully.',
|
message: 'The account has been created successfully.',
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export class ExportController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Router constructor method.
|
* Router constructor method.
|
||||||
*/
|
*/
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
@@ -53,6 +53,7 @@ export class ExportController extends BaseController {
|
|||||||
query.resource,
|
query.resource,
|
||||||
acceptType === ACCEPT_TYPE.APPLICATION_XLSX ? 'xlsx' : 'csv'
|
acceptType === ACCEPT_TYPE.APPLICATION_XLSX ? 'xlsx' : 'csv'
|
||||||
);
|
);
|
||||||
|
// Retrieves the csv format.
|
||||||
if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
|
if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
res.setHeader('Content-Type', 'text/csv');
|
res.setHeader('Content-Type', 'text/csv');
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class ImportController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Router constructor method.
|
* Router constructor method.
|
||||||
*/
|
*/
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
@@ -240,11 +240,7 @@ export class ImportController extends BaseController {
|
|||||||
errors: [{ type: 'IMPORTED_FILE_EXTENSION_INVALID' }],
|
errors: [{ type: 'IMPORTED_FILE_EXTENSION_INVALID' }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: error.errorType }],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,14 +77,14 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Specific manual journal id param validation schema.
|
* Specific manual journal id param validation schema.
|
||||||
*/
|
*/
|
||||||
get manualJournalParamSchema() {
|
private get manualJournalParamSchema() {
|
||||||
return [param('id').exists().isNumeric().toInt()];
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manual journal DTO schema.
|
* Manual journal DTO schema.
|
||||||
*/
|
*/
|
||||||
get manualJournalValidationSchema() {
|
private get manualJournalValidationSchema() {
|
||||||
return [
|
return [
|
||||||
check('date').exists().isISO8601(),
|
check('date').exists().isISO8601(),
|
||||||
check('currency_code').optional(),
|
check('currency_code').optional(),
|
||||||
@@ -154,7 +154,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Manual journals list validation schema.
|
* Manual journals list validation schema.
|
||||||
*/
|
*/
|
||||||
get manualJournalsListSchema() {
|
private get manualJournalsListSchema() {
|
||||||
return [
|
return [
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
@@ -320,7 +320,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
getManualJournalsList = async (
|
private getManualJournalsList = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
|
|||||||
@@ -33,17 +33,17 @@ export default class OrganizationDashboardController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Detarmines whether the current authed organization to able to change its currency/.
|
||||||
* @param req
|
* @param {Request} req
|
||||||
* @param res
|
* @param {Response} res
|
||||||
* @param next
|
* @param {NextFunction} next
|
||||||
* @returns
|
* @returns {Response|void}
|
||||||
*/
|
*/
|
||||||
private async baseCurrencyMutateAbility(
|
private async baseCurrencyMutateAbility(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: Function
|
next: Function
|
||||||
) {
|
): Promise<Response|void> {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ export class ProjectsController extends BaseController {
|
|||||||
check('cost_estimate').exists().isDecimal(),
|
check('cost_estimate').exists().isDecimal(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.createProject.bind(this)),
|
asyncMiddleware(this.createProject.bind(this))
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id',
|
'/:id',
|
||||||
@@ -43,8 +42,7 @@ export class ProjectsController extends BaseController {
|
|||||||
check('cost_estimate').exists().isDecimal(),
|
check('cost_estimate').exists().isDecimal(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.editProject.bind(this)),
|
asyncMiddleware(this.editProject.bind(this))
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
);
|
||||||
router.patch(
|
router.patch(
|
||||||
'/:projectId/status',
|
'/:projectId/status',
|
||||||
@@ -56,16 +54,14 @@ export class ProjectsController extends BaseController {
|
|||||||
.isIn([IProjectStatus.InProgress, IProjectStatus.Closed]),
|
.isIn([IProjectStatus.InProgress, IProjectStatus.Closed]),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.editProject.bind(this)),
|
asyncMiddleware(this.editProject.bind(this))
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(ProjectAction.VIEW, AbilitySubject.Project),
|
CheckPolicies(ProjectAction.VIEW, AbilitySubject.Project),
|
||||||
[param('id').exists().isInt().toInt()],
|
[param('id').exists().isInt().toInt()],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getProject.bind(this)),
|
asyncMiddleware(this.getProject.bind(this))
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:projectId/billable/entries',
|
'/:projectId/billable/entries',
|
||||||
@@ -76,24 +72,21 @@ export class ProjectsController extends BaseController {
|
|||||||
query('to_date').optional().isISO8601(),
|
query('to_date').optional().isISO8601(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.projectBillableEntries.bind(this)),
|
asyncMiddleware(this.projectBillableEntries.bind(this))
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
CheckPolicies(ProjectAction.VIEW, AbilitySubject.Project),
|
CheckPolicies(ProjectAction.VIEW, AbilitySubject.Project),
|
||||||
[],
|
[],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getProjects.bind(this)),
|
asyncMiddleware(this.getProjects.bind(this))
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
);
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:id',
|
'/:id',
|
||||||
CheckPolicies(ProjectAction.DELETE, AbilitySubject.Project),
|
CheckPolicies(ProjectAction.DELETE, AbilitySubject.Project),
|
||||||
[param('id').exists().isInt().toInt()],
|
[param('id').exists().isInt().toInt()],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.deleteProject.bind(this)),
|
asyncMiddleware(this.deleteProject.bind(this))
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -252,22 +245,4 @@ export class ProjectsController extends BaseController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms service errors to response.
|
|
||||||
* @param {Error}
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {ServiceError} error
|
|
||||||
*/
|
|
||||||
private catchServiceErrors(
|
|
||||||
error,
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
if (error instanceof ServiceError) {
|
|
||||||
}
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ export default class UsersController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await this.usersService.getUser(tenantId, userId);
|
const user = await this.usersService.getUser(tenantId, userId);
|
||||||
|
|
||||||
return res.status(200).send({ user });
|
return res.status(200).send({ user });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -229,7 +230,7 @@ export default class UsersController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
catchServiceErrors(
|
private catchServiceErrors(
|
||||||
error: Error,
|
error: Error,
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class WarehousesController extends BaseController {
|
|||||||
*
|
*
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
|
|||||||
@@ -34,14 +34,21 @@ export class Webhooks extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @returns {Response}
|
* @returns {Response}
|
||||||
*/
|
*/
|
||||||
public async lemonWebhooks(req: Request, res: Response) {
|
public async lemonWebhooks(req: Request, res: Response, next: any) {
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
const signature = req.headers['x-signature'] ?? '';
|
const signature = req.headers['x-signature'] ?? '';
|
||||||
const rawBody = req.rawBody;
|
const rawBody = req.rawBody;
|
||||||
|
|
||||||
await this.lemonWebhooksService.handlePostWebhook(rawBody, data, signature);
|
try {
|
||||||
|
await this.lemonWebhooksService.handlePostWebhook(
|
||||||
return res.status(200).send();
|
rawBody,
|
||||||
|
data,
|
||||||
|
signature
|
||||||
|
);
|
||||||
|
return res.status(200).send();
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
20
packages/server/src/api/exceptions/GlobalErrorException.ts
Normal file
20
packages/server/src/api/exceptions/GlobalErrorException.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global error handler.
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
export function GlobalErrorException(
|
||||||
|
err: Error,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
console.error(err.stack);
|
||||||
|
|
||||||
|
res.status(500);
|
||||||
|
res.boom.badImplementation('', { stack: err.stack });
|
||||||
|
}
|
||||||
@@ -10,8 +10,14 @@ import {
|
|||||||
DataError,
|
DataError,
|
||||||
} from 'objection';
|
} from 'objection';
|
||||||
|
|
||||||
// In this example `res` is an express response object.
|
/**
|
||||||
export default function ObjectionErrorHandlerMiddleware(
|
* Handles the Objection error exception.
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
export function ObjectionErrorException(
|
||||||
err: Error,
|
err: Error,
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
@@ -108,6 +114,7 @@ export default function ObjectionErrorHandlerMiddleware(
|
|||||||
type: 'UnknownDatabaseError',
|
type: 'UnknownDatabaseError',
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
next(err);
|
||||||
}
|
}
|
||||||
next(err);
|
|
||||||
}
|
}
|
||||||
25
packages/server/src/api/exceptions/ServiceErrorException.ts
Normal file
25
packages/server/src/api/exceptions/ServiceErrorException.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles service error exception.
|
||||||
|
* @param {Error | ServiceError} err
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
export function ServiceErrorException(
|
||||||
|
err: Error | ServiceError,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
if (err instanceof ServiceError) {
|
||||||
|
res.boom.badRequest('', {
|
||||||
|
errors: [{ type: err.errorType, message: err.message }],
|
||||||
|
type: 'ServiceError',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,9 @@ import {
|
|||||||
} from '@/api/middleware/JSONResponseTransformer';
|
} from '@/api/middleware/JSONResponseTransformer';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import ObjectionErrorHandlerMiddleware from '@/api/middleware/ObjectionErrorHandlerMiddleware';
|
import { ObjectionErrorException } from '@/api/exceptions/ObjectionErrorException';
|
||||||
|
import { ServiceErrorException } from '@/api/exceptions/ServiceErrorException';
|
||||||
|
import { GlobalErrorException } from '@/api/exceptions/GlobalErrorException';
|
||||||
|
|
||||||
export default ({ app }) => {
|
export default ({ app }) => {
|
||||||
// Express configuration.
|
// Express configuration.
|
||||||
@@ -30,9 +32,6 @@ export default ({ app }) => {
|
|||||||
// Helmet helps you secure your Express apps by setting various HTTP headers.
|
// Helmet helps you secure your Express apps by setting various HTTP headers.
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
|
|
||||||
// Allow to full error stack traces and internal details
|
|
||||||
app.use(errorHandler());
|
|
||||||
|
|
||||||
// Boom response objects.
|
// Boom response objects.
|
||||||
app.use(boom());
|
app.use(boom());
|
||||||
|
|
||||||
@@ -65,8 +64,10 @@ export default ({ app }) => {
|
|||||||
// Agendash application load.
|
// Agendash application load.
|
||||||
app.use('/agendash', AgendashController.router());
|
app.use('/agendash', AgendashController.router());
|
||||||
|
|
||||||
// Handles objectionjs errors.
|
// Handles errors.
|
||||||
app.use(ObjectionErrorHandlerMiddleware);
|
app.use(ObjectionErrorException);
|
||||||
|
app.use(ServiceErrorException);
|
||||||
|
app.use(GlobalErrorException);
|
||||||
|
|
||||||
// catch 404 and forward to error handler
|
// catch 404 and forward to error handler
|
||||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from './utils';
|
} from './utils';
|
||||||
import { Plan } from '@/system/models';
|
import { Plan } from '@/system/models';
|
||||||
import { Subscription } from './Subscription';
|
import { Subscription } from './Subscription';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class LemonSqueezyWebhooks {
|
export class LemonSqueezyWebhooks {
|
||||||
@@ -32,6 +33,9 @@ export class LemonSqueezyWebhooks {
|
|||||||
if (!config.lemonSqueezy.webhookSecret) {
|
if (!config.lemonSqueezy.webhookSecret) {
|
||||||
throw new Error('Lemon Squeezy Webhook Secret not set in .env');
|
throw new Error('Lemon Squeezy Webhook Secret not set in .env');
|
||||||
}
|
}
|
||||||
|
if (!signature) {
|
||||||
|
throw new Error('Request signature is required.');
|
||||||
|
}
|
||||||
const secret = config.lemonSqueezy.webhookSecret;
|
const secret = config.lemonSqueezy.webhookSecret;
|
||||||
const hmacSignature = createHmacSignature(secret, rawData);
|
const hmacSignature = createHmacSignature(secret, rawData);
|
||||||
|
|
||||||
@@ -49,7 +53,7 @@ export class LemonSqueezyWebhooks {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This action will process a webhook event in the database.
|
* This action will process a webhook event in the database.
|
||||||
* @param {unknown} eventBody -
|
* @param {unknown} eventBody -
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
private async processWebhookEvent(eventBody): Promise<void> {
|
private async processWebhookEvent(eventBody): Promise<void> {
|
||||||
@@ -96,7 +100,7 @@ export class LemonSqueezyWebhooks {
|
|||||||
if (webhookEvent === 'subscription_created') {
|
if (webhookEvent === 'subscription_created') {
|
||||||
await this.subscriptionService.newSubscribtion(
|
await this.subscriptionService.newSubscribtion(
|
||||||
tenantId,
|
tenantId,
|
||||||
'early-adaptor',
|
'early-adaptor'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user