mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
Merge branch 'master' of https://github.com/abouolia/Bigcapital into RegisterWizard
This commit is contained in:
@@ -43,6 +43,15 @@ export default class AccountsController extends BaseController{
|
||||
asyncMiddleware(this.inactivateAccount.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
);
|
||||
router.post(
|
||||
'/:id/close', [
|
||||
...this.accountParamSchema,
|
||||
...this.closingAccountSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.closeAccount.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
)
|
||||
router.post(
|
||||
'/:id', [
|
||||
...this.accountDTOSchema,
|
||||
@@ -127,18 +136,12 @@ export default class AccountsController extends BaseController{
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Account param schema validation.
|
||||
*/
|
||||
get accountParamSchema() {
|
||||
return [
|
||||
param('id').exists().isNumeric().toInt()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Accounts list schema validation.
|
||||
*/
|
||||
get accountsListSchema() {
|
||||
return [
|
||||
query('custom_view_id').optional().isNumeric().toInt(),
|
||||
@@ -149,9 +152,6 @@ export default class AccountsController extends BaseController{
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
get bulkSelectIdsQuerySchema() {
|
||||
return [
|
||||
query('ids').isArray({ min: 2 }),
|
||||
@@ -159,6 +159,13 @@ export default class AccountsController extends BaseController{
|
||||
];
|
||||
}
|
||||
|
||||
get closingAccountSchema() {
|
||||
return [
|
||||
check('to_account_id').exists().isNumeric().toInt(),
|
||||
check('delete_after_closing').exists().isBoolean(),
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new account.
|
||||
* @param {Request} req -
|
||||
@@ -328,8 +335,36 @@ export default class AccountsController extends BaseController{
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
try {
|
||||
const accounts = await this.accountsService.getAccountsList(tenantId, filter);
|
||||
return res.status(200).send({ accounts });
|
||||
const { accounts, filterMeta } = await this.accountsService.getAccountsList(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
accounts,
|
||||
filter_meta: this.transfromToResponse(filterMeta)
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the given account.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param next
|
||||
*/
|
||||
async closeAccount(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: accountId } = req.params;
|
||||
const closeAccountQuery = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
await this.accountsService.closeAccount(
|
||||
tenantId,
|
||||
accountId,
|
||||
closeAccountQuery.toAccountId,
|
||||
closeAccountQuery.deleteAfterClosing
|
||||
);
|
||||
return res.status(200).send({ id: accountId });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -358,9 +393,8 @@ export default class AccountsController extends BaseController{
|
||||
}
|
||||
if (error.errorType === 'account_type_not_found') {
|
||||
return res.boom.badRequest(
|
||||
'The given account type not found.', {
|
||||
errors: [{ type: 'ACCOUNT_TYPE_NOT_FOUND', code: 200 }]
|
||||
}
|
||||
'The given account type not found.',
|
||||
{ errors: [{ type: 'ACCOUNT_TYPE_NOT_FOUND', code: 200 }] }
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'account_type_not_allowed_to_changed') {
|
||||
@@ -417,6 +451,12 @@ export default class AccountsController extends BaseController{
|
||||
{ errors: [{ type: 'ACCOUNTS_PREDEFINED', code: 1100 }] }
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'close_account_and_to_account_not_same_type') {
|
||||
return res.boom.badRequest(
|
||||
'The close account has different root type with to account.',
|
||||
{ errors: [{ type: 'CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE', code: 1200 }] },
|
||||
);
|
||||
}
|
||||
}
|
||||
next(error)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@ import { Request, Response, Router } from 'express';
|
||||
import { check, ValidationChain } from 'express-validator';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import AuthenticationService from 'services/Authentication';
|
||||
import { IUserOTD, ISystemUser, IRegisterOTD } from 'interfaces';
|
||||
import { ILoginDTO, ISystemUser, IRegisterOTD } from 'interfaces';
|
||||
import { ServiceError, ServiceErrors } from "exceptions";
|
||||
|
||||
@Service()
|
||||
@@ -61,7 +60,6 @@ export default class AuthenticationController extends BaseController{
|
||||
*/
|
||||
get registerSchema(): ValidationChain[] {
|
||||
return [
|
||||
check('organization_name').exists().trim().escape(),
|
||||
check('first_name').exists().trim().escape(),
|
||||
check('last_name').exists().trim().escape(),
|
||||
check('email').exists().isEmail().trim().escape(),
|
||||
@@ -102,7 +100,7 @@ export default class AuthenticationController extends BaseController{
|
||||
* @param {Response} res
|
||||
*/
|
||||
async login(req: Request, res: Response, next: Function): Response {
|
||||
const userDTO: IUserOTD = this.matchedBodyData(req);
|
||||
const userDTO: ILoginDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const { token, user, tenant } = await this.authService.signIn(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Response, Request, NextFunction } from 'express';
|
||||
import { matchedData, validationResult } from "express-validator";
|
||||
import { camelCase, omit } from "lodash";
|
||||
import { camelCase, snakeCase, omit } from "lodash";
|
||||
import { mapKeysDeep } from 'utils'
|
||||
|
||||
export default class BaseController {
|
||||
@@ -55,4 +55,12 @@ export default class BaseController {
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the given data to response.
|
||||
* @param {any} data
|
||||
*/
|
||||
transfromToResponse(data: any) {
|
||||
return mapKeysDeep(data, (v, k) => snakeCase(k));
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,12 @@ export default class CustomersController extends ContactsController {
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteBulkCustomers.bind(this))
|
||||
);
|
||||
router.get('/', [
|
||||
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getCustomersList.bind(this))
|
||||
);
|
||||
router.get('/:id', [
|
||||
...this.specificContactSchema,
|
||||
],
|
||||
@@ -193,4 +199,15 @@ export default class CustomersController extends ContactsController {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getCustomersList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
await this.customersService.getCustomersList(tenantId)
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ export default class CurrenciesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
handlerServiceError(error, req, res, next) {
|
||||
handlerServiceError(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'currency_not_found') {
|
||||
return res.boom.badRequest(null, {
|
||||
|
||||
@@ -7,6 +7,7 @@ import ExpensesService from "services/Expenses/ExpensesService";
|
||||
import { IExpenseDTO } from 'interfaces';
|
||||
import { ServiceError } from "exceptions";
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import { takeWhile } from "lodash";
|
||||
|
||||
@Service()
|
||||
export default class ExpensesController extends BaseController {
|
||||
@@ -30,7 +31,8 @@ export default class ExpensesController extends BaseController {
|
||||
asyncMiddleware(this.newExpense.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
);
|
||||
router.post('/publish', [
|
||||
router.post(
|
||||
'/publish', [
|
||||
...this.bulkSelectSchema,
|
||||
],
|
||||
this.bulkPublishExpenses.bind(this),
|
||||
@@ -69,11 +71,22 @@ export default class ExpensesController extends BaseController {
|
||||
this.catchServiceErrors,
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
'/', [
|
||||
...this.expensesListSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getExpensesList.bind(this)),
|
||||
this.dynamicListService.handlerErrorsToResponse,
|
||||
this.catchServiceErrors,
|
||||
);
|
||||
router.get(
|
||||
'/:id', [
|
||||
this.expenseParamSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getExpense.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -89,6 +102,7 @@ export default class ExpensesController extends BaseController {
|
||||
check('currency_code').optional(),
|
||||
check('exchange_rate').optional().isNumeric().toFloat(),
|
||||
check('publish').optional().isBoolean().toBoolean(),
|
||||
|
||||
check('categories').exists().isArray({ min: 1 }),
|
||||
check('categories.*.index').exists().isNumeric().toInt(),
|
||||
check('categories.*.expense_account_id').exists().isNumeric().toInt(),
|
||||
@@ -120,6 +134,20 @@ export default class ExpensesController extends BaseController {
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
get expensesListSchema() {
|
||||
return [
|
||||
query('custom_view_id').optional().isNumeric().toInt(),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
|
||||
query('column_sort_by').optional(),
|
||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expense on
|
||||
* @param {Request} req
|
||||
@@ -240,12 +268,41 @@ export default class ExpensesController extends BaseController {
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const expenses = await this.expensesService.getExpensesList(tenantId, filter);
|
||||
return res.status(200).send({ expenses });
|
||||
const { expenses, pagination, filterMeta } = await this.expensesService.getExpensesList(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
expenses,
|
||||
pagination: this.transfromToResponse(pagination),
|
||||
filter_meta: this.transfromToResponse(filterMeta),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve expense details.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async getExpense(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: expenseId } = req.params;
|
||||
|
||||
try {
|
||||
const expense = await this.expensesService.getExpense(tenantId, expenseId);
|
||||
return res.status(200).send({ expense });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -259,29 +316,34 @@ export default class ExpensesController extends BaseController {
|
||||
catchServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'expense_not_found') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'EXPENSE_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
return res.boom.badRequest(
|
||||
'Expense not found.',
|
||||
{ errors: [{ type: 'EXPENSE_NOT_FOUND', code: 100 }] }
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'total_amount_equals_zero') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'TOTAL.AMOUNT.EQUALS.ZERO', code: 200 }],
|
||||
});
|
||||
return res.boom.badRequest(
|
||||
'Expense total should not equal zero.',
|
||||
{ errors: [{ type: 'TOTAL.AMOUNT.EQUALS.ZERO', code: 200 }] },
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'payment_account_not_found') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', code: 300 }],
|
||||
});
|
||||
return res.boom.badRequest(
|
||||
'Payment account not found.',
|
||||
{ errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', code: 300 }] },
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'some_expenses_not_found') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'SOME.EXPENSE.ACCOUNTS.NOT.FOUND', code: 400 }]
|
||||
})
|
||||
return res.boom.badRequest(
|
||||
'Some expense accounts not found.',
|
||||
{ errors: [{ type: 'SOME.EXPENSE.ACCOUNTS.NOT.FOUND', code: 400 }] },
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'payment_account_has_invalid_type') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'PAYMENT.ACCOUNT.HAS.INVALID.TYPE', code: 500 }],
|
||||
});
|
||||
return res.boom.badRequest(
|
||||
'Payment account has invalid type.',
|
||||
{ errors: [{ type: 'PAYMENT.ACCOUNT.HAS.INVALID.TYPE', code: 500 }], },
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'expenses_account_has_invalid_type') {
|
||||
return res.boom.badRequest(null, {
|
||||
|
||||
@@ -7,16 +7,19 @@ import {
|
||||
import ItemCategoriesService from 'services/ItemCategories/ItemCategoriesService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import { IItemCategoryOTD } from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
|
||||
@Service()
|
||||
export default class ItemsCategoriesController extends BaseController {
|
||||
@Inject()
|
||||
itemCategoriesService: ItemCategoriesService;
|
||||
|
||||
@Inject()
|
||||
dynamicListService: DynamicListingService;
|
||||
|
||||
/**
|
||||
* Router constructor method.
|
||||
*/
|
||||
@@ -27,44 +30,45 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
...this.categoryValidationSchema,
|
||||
...this.specificCategoryValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.editCategory.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.post('/', [
|
||||
...this.categoryValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.newCategory.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.delete('/', [
|
||||
...this.categoriesBulkValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.bulkDeleteCategories.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.delete('/:id', [
|
||||
...this.specificCategoryValidationSchema
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteItem.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.get('/:id', [
|
||||
...this.specificCategoryValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getCategory.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.get('/', [
|
||||
...this.categoriesListValidationSchema
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getList.bind(this)),
|
||||
this.handlerServiceError,
|
||||
this.dynamicListService.handlerErrorsToResponse,
|
||||
);
|
||||
return router;
|
||||
}
|
||||
@@ -113,8 +117,9 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
*/
|
||||
get categoriesListValidationSchema() {
|
||||
return [
|
||||
query('column_sort_order').optional().trim().escape(),
|
||||
query('column_sort_by').optional().trim().escape(),
|
||||
query('sort_order').optional().trim().escape().isIn(['desc', 'asc']),
|
||||
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
];
|
||||
}
|
||||
@@ -190,13 +195,21 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
*/
|
||||
async getList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId, user } = req;
|
||||
const itemCategoriesFilter = this.matchedQueryData(req);
|
||||
const itemCategoriesFilter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
|
||||
try {
|
||||
const itemCategories = await this.itemCategoriesService.getItemCategoriesList(
|
||||
const { itemCategories, filterMeta } = await this.itemCategoriesService.getItemCategoriesList(
|
||||
tenantId, itemCategoriesFilter, user,
|
||||
);
|
||||
return res.status(200).send({ item_categories: itemCategories });
|
||||
return res.status(200).send({
|
||||
item_categories: itemCategories,
|
||||
filter_meta: this.transfromToResponse(filterMeta),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ export default class ItemsController extends BaseController {
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/', [
|
||||
router.post('/', [
|
||||
...this.validateItemSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
@@ -150,10 +149,12 @@ export default class ItemsController extends BaseController {
|
||||
*/
|
||||
get validateListQuerySchema() {
|
||||
return [
|
||||
query('column_sort_order').optional().trim().escape(),
|
||||
query('column_sort_by').optional().trim().escape(),
|
||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
|
||||
query('custom_view_id').optional().isNumeric().toInt(),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
]
|
||||
@@ -240,14 +241,22 @@ export default class ItemsController extends BaseController {
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
try {
|
||||
const items = await this.itemsService.itemsList(tenantId, filter);
|
||||
return res.status(200).send({ items });
|
||||
const { items, pagination, filterMeta } = await this.itemsService.itemsList(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
items,
|
||||
pagination: this.transfromToResponse(pagination),
|
||||
filter_meta: this.transfromToResponse(filterMeta),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -345,6 +354,16 @@ export default class ItemsController extends BaseController {
|
||||
errors: [{ type: 'INVENTORY.ACCOUNT.NOT.CURRENT.ASSET', code: 300 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS') {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS', code: 310 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'ITEM_HAS_ASSOCIATED_TRANSACTINS') {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'ITEM_HAS_ASSOCIATED_TRANSACTINS', code: 320 }],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,17 +299,22 @@ export default class ManualJournalsController extends BaseController {
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
filterRoles: [],
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
}
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
try {
|
||||
const manualJournals = await this.manualJournalsService.getManualJournals(tenantId, filter);
|
||||
return res.status(200).send({ manualJournals });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
const { manualJournals, pagination, filterMeta } = await this.manualJournalsService.getManualJournals(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
manual_journals: manualJournals,
|
||||
pagination: this.transfromToResponse(pagination),
|
||||
filter_meta: this.transfromToResponse(filterMeta),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
|
||||
import express from 'express';
|
||||
import {
|
||||
param,
|
||||
query,
|
||||
validationResult,
|
||||
} from 'express-validator';
|
||||
import Container from 'typedi';
|
||||
import fs from 'fs';
|
||||
import { difference } from 'lodash';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
|
||||
const fsPromises = fs.promises;
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/upload',
|
||||
this.upload.validation,
|
||||
asyncMiddleware(this.upload.handler));
|
||||
|
||||
router.delete('/',
|
||||
this.delete.validation,
|
||||
asyncMiddleware(this.delete.handler));
|
||||
|
||||
router.get('/',
|
||||
this.get.validation,
|
||||
asyncMiddleware(this.get.handler));
|
||||
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve all or the given attachment ids.
|
||||
*/
|
||||
get: {
|
||||
validation: [
|
||||
query('ids'),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Media } = req.models;
|
||||
const media = await Media.query().onBuild((builder) => {
|
||||
|
||||
if (req.query.ids) {
|
||||
const ids = Array.isArray(req.query.ids) ? req.query.ids : [req.query.ids];
|
||||
builder.whereIn('id', ids);
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(200).send({ media });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Uploads the given attachment file.
|
||||
*/
|
||||
upload: {
|
||||
validation: [
|
||||
// check('attachment').exists(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const Logger = Container.get('logger');
|
||||
|
||||
if (!req.files.attachment) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'ATTACHMENT.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
const publicPath = 'storage/app/public/';
|
||||
const attachmentsMimes = ['image/png', 'image/jpeg'];
|
||||
const { attachment } = req.files;
|
||||
const { Media } = req.models;
|
||||
|
||||
const errorReasons = [];
|
||||
|
||||
// Validate the attachment.
|
||||
if (attachment && attachmentsMimes.indexOf(attachment.mimetype) === -1) {
|
||||
errorReasons.push({ type: 'ATTACHMENT.MINETYPE.NOT.SUPPORTED', code: 160 });
|
||||
}
|
||||
// Catch all error reasons to response 400.
|
||||
if (errorReasons.length > 0) {
|
||||
return res.status(400).send({ errors: errorReasons });
|
||||
}
|
||||
|
||||
try {
|
||||
await attachment.mv(`${publicPath}${req.organizationId}/${attachment.md5}.png`);
|
||||
Logger.info('[attachment] uploaded successfully');
|
||||
} catch (error) {
|
||||
Logger.info('[attachment] uploading failed.', { error });
|
||||
}
|
||||
|
||||
const media = await Media.query().insert({
|
||||
attachment_file: `${attachment.md5}.png`,
|
||||
});
|
||||
return res.status(200).send({ media });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes the given attachment ids from file system and database.
|
||||
*/
|
||||
delete: {
|
||||
validation: [
|
||||
query('ids').exists().isArray(),
|
||||
query('ids.*').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const Logger = Container.get('logger');
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Media, MediaLink } = req.models;
|
||||
const ids = Array.isArray(req.query.ids) ? req.query.ids : [req.query.ids];
|
||||
const media = await Media.query().whereIn('id', ids);
|
||||
const mediaIds = media.map((m) => m.id);
|
||||
const notFoundMedia = difference(ids, mediaIds);
|
||||
|
||||
if (notFoundMedia.length) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'MEDIA.IDS.NOT.FOUND', code: 200, ids: notFoundMedia }],
|
||||
});
|
||||
}
|
||||
const publicPath = 'storage/app/public/';
|
||||
const tenantPath = `${publicPath}${req.organizationId}`;
|
||||
const unlinkOpers = [];
|
||||
|
||||
media.forEach((mediaModel) => {
|
||||
const oper = fsPromises.unlink(`${tenantPath}/${mediaModel.attachmentFile}`);
|
||||
unlinkOpers.push(oper);
|
||||
});
|
||||
await Promise.all(unlinkOpers).then((resolved) => {
|
||||
resolved.forEach(() => {
|
||||
Logger.info('[attachment] file has been deleted.');
|
||||
});
|
||||
})
|
||||
.catch((errors) => {
|
||||
errors.forEach((error) => {
|
||||
Logger.info('[attachment] Delete item attachment file delete failed.', { error });
|
||||
})
|
||||
});
|
||||
|
||||
await MediaLink.query().whereIn('media_id', mediaIds).delete();
|
||||
await Media.query().whereIn('id', mediaIds).delete();
|
||||
|
||||
return res.status(200).send();
|
||||
},
|
||||
},
|
||||
};
|
||||
212
server/src/api/controllers/Media.ts
Normal file
212
server/src/api/controllers/Media.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import {
|
||||
param,
|
||||
query,
|
||||
check,
|
||||
} from 'express-validator';
|
||||
import { camelCase, upperFirst } from 'lodash';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IMediaLinkDTO } from 'interfaces';
|
||||
import fs from 'fs';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import BaseController from './BaseController';
|
||||
import MediaService from 'services/Media/MediaService';
|
||||
import { ServiceError } from 'exceptions';
|
||||
|
||||
const fsPromises = fs.promises;
|
||||
|
||||
@Service()
|
||||
export default class MediaController extends BaseController {
|
||||
@Inject()
|
||||
mediaService: MediaService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.post('/upload', [
|
||||
...this.uploadValidationSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.uploadMedia.bind(this)),
|
||||
this.handlerServiceErrors,
|
||||
);
|
||||
router.post('/:id/link', [
|
||||
...this.mediaIdParamSchema,
|
||||
...this.linkValidationSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.linkMedia.bind(this)),
|
||||
this.handlerServiceErrors,
|
||||
);
|
||||
router.delete('/', [
|
||||
...this.deleteValidationSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteMedia.bind(this)),
|
||||
this.handlerServiceErrors,
|
||||
);
|
||||
router.get('/:id', [
|
||||
...this.mediaIdParamSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getMedia.bind(this)),
|
||||
this.handlerServiceErrors,
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
get uploadValidationSchema() {
|
||||
return [
|
||||
// check('attachment'),
|
||||
check('model_name').optional().trim().escape(),
|
||||
check('model_id').optional().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
get linkValidationSchema() {
|
||||
return [
|
||||
check('model_name').exists().trim().escape(),
|
||||
check('model_id').exists().isNumeric().toInt(),
|
||||
]
|
||||
}
|
||||
|
||||
get deleteValidationSchema() {
|
||||
return [
|
||||
query('ids').exists().isArray(),
|
||||
query('ids.*').exists().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
get mediaIdParamSchema() {
|
||||
return [
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all or the given attachment ids.
|
||||
* @param {Request} req -
|
||||
* @param {Response} req -
|
||||
* @param {NextFunction} req -
|
||||
*/
|
||||
async getMedia(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: mediaId } = req.params;
|
||||
|
||||
try {
|
||||
const media = await this.mediaService.getMedia(tenantId, mediaId);
|
||||
return res.status(200).send({ media });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads media.
|
||||
* @param {Request} req -
|
||||
* @param {Response} req -
|
||||
* @param {NextFunction} req -
|
||||
*/
|
||||
async uploadMedia(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { attachment } = req.files
|
||||
|
||||
const linkMediaDTO: IMediaLinkDTO = this.matchedBodyData(req);
|
||||
const modelName = linkMediaDTO.modelName
|
||||
? upperFirst(camelCase(linkMediaDTO.modelName)) : '';
|
||||
|
||||
try {
|
||||
const media = await this.mediaService.upload(tenantId, attachment, modelName, linkMediaDTO.modelId);
|
||||
return res.status(200).send({ media_id: media.id });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given attachment ids from file system and database.
|
||||
* @param {Request} req -
|
||||
* @param {Response} req -
|
||||
* @param {NextFunction} req -
|
||||
*/
|
||||
async deleteMedia(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { ids: mediaIds } = req.query;
|
||||
|
||||
try {
|
||||
await this.mediaService.deleteMedia(tenantId, mediaIds);
|
||||
return res.status(200).send({
|
||||
media_ids: mediaIds
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Links the given media to the specific resource model.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async linkMedia(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: mediaId } = req.params;
|
||||
const linkMediaDTO: IMediaLinkDTO = this.matchedBodyData(req);
|
||||
const modelName = upperFirst(camelCase(linkMediaDTO.modelName));
|
||||
|
||||
try {
|
||||
await this.mediaService.linkMedia(tenantId, mediaId, linkMediaDTO.modelId, modelName);
|
||||
return res.status(200).send({ media_id: mediaId });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler service errors.
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
handlerServiceErrors(error, req: Request, res: Response, next: NextFunction) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'MINETYPE_NOT_SUPPORTED') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'MINETYPE_NOT_SUPPORTED', code: 100, }]
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'MEDIA_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'MEDIA_NOT_FOUND', code: 200 }]
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'MODEL_NAME_HAS_NO_MEDIA') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'MODEL_NAME_HAS_NO_MEDIA', code: 300 }]
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'MODEL_ID_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'MODEL_ID_NOT_FOUND', code: 400 }]
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'MEDIA_IDS_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'MEDIA_IDS_NOT_FOUND', code: 500 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'MEDIA_LINK_EXISTS') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'MEDIA_LINK_EXISTS', code: 600 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import { check, param, query, matchedData } from 'express-validator';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { difference } from 'lodash';
|
||||
import { BillOTD } from 'interfaces';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import BillsService from 'services/Purchases/Bills';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
@@ -30,7 +29,7 @@ export default class BillsController extends BaseController {
|
||||
router.post(
|
||||
'/',
|
||||
[...this.billValidationSchema],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateVendorExistance.bind(this)),
|
||||
asyncMiddleware(this.validateItemsIds.bind(this)),
|
||||
asyncMiddleware(this.validateBillNumberExists.bind(this)),
|
||||
@@ -40,7 +39,7 @@ export default class BillsController extends BaseController {
|
||||
router.post(
|
||||
'/:id',
|
||||
[...this.billValidationSchema, ...this.specificBillValidationSchema],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillExistance.bind(this)),
|
||||
asyncMiddleware(this.validateVendorExistance.bind(this)),
|
||||
asyncMiddleware(this.validateItemsIds.bind(this)),
|
||||
@@ -51,20 +50,20 @@ export default class BillsController extends BaseController {
|
||||
router.get(
|
||||
'/:id',
|
||||
[...this.specificBillValidationSchema],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillExistance.bind(this)),
|
||||
asyncMiddleware(this.getBill.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
[...this.billsListingValidationSchema],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.listingBills.bind(this))
|
||||
);
|
||||
router.delete(
|
||||
'/:id',
|
||||
[...this.specificBillValidationSchema],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillExistance.bind(this)),
|
||||
asyncMiddleware(this.deleteBill.bind(this))
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ export default class BillsPayments extends BaseController {
|
||||
router.post('/', [
|
||||
...this.billPaymentSchemaValidation,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillPaymentVendorExistance.bind(this)),
|
||||
asyncMiddleware(this.validatePaymentAccount.bind(this)),
|
||||
asyncMiddleware(this.validatePaymentNumber.bind(this)),
|
||||
@@ -42,7 +42,7 @@ export default class BillsPayments extends BaseController {
|
||||
...this.billPaymentSchemaValidation,
|
||||
...this.specificBillPaymentValidateSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillPaymentVendorExistance.bind(this)),
|
||||
asyncMiddleware(this.validatePaymentAccount.bind(this)),
|
||||
asyncMiddleware(this.validatePaymentNumber.bind(this)),
|
||||
@@ -53,19 +53,19 @@ export default class BillsPayments extends BaseController {
|
||||
)
|
||||
router.delete('/:id',
|
||||
this.specificBillPaymentValidateSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillPaymentExistance.bind(this)),
|
||||
asyncMiddleware(this.deleteBillPayment.bind(this)),
|
||||
);
|
||||
router.get('/:id',
|
||||
this.specificBillPaymentValidateSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillPaymentExistance.bind(this)),
|
||||
asyncMiddleware(this.getBillPayment.bind(this)),
|
||||
);
|
||||
router.get('/',
|
||||
this.listingValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getBillsPayments.bind(this))
|
||||
);
|
||||
return router;
|
||||
@@ -381,6 +381,20 @@ export default class BillsPayments extends BaseController {
|
||||
* @return {Response}
|
||||
*/
|
||||
async getBillsPayments(req: Request, res: Response) {
|
||||
const { tenantId } = req.params;
|
||||
const billPaymentsFilter = this.matchedQueryData(req);
|
||||
|
||||
try {
|
||||
const { billPayments, pagination, filterMeta } = await this.billPaymentService
|
||||
.listBillPayments(tenantId, billPaymentsFilter);
|
||||
|
||||
return res.status(200).send({
|
||||
bill_payments: billPayments,
|
||||
pagination: this.transfromToResponse(pagination),
|
||||
filter_meta: this.transfromToResponse(filterMeta)
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import express from 'express';
|
||||
import {
|
||||
param,
|
||||
query,
|
||||
} from 'express-validator';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/:resource_slug/data',
|
||||
this.resourceData.validation,
|
||||
asyncMiddleware(this.resourceData.handler));
|
||||
|
||||
router.get('/:resource_slug/columns',
|
||||
this.resourceColumns.validation,
|
||||
asyncMiddleware(this.resourceColumns.handler));
|
||||
|
||||
router.get('/:resource_slug/fields',
|
||||
this.resourceFields.validation,
|
||||
asyncMiddleware(this.resourceFields.handler));
|
||||
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve resource data of the given resource key/slug.
|
||||
*/
|
||||
resourceData: {
|
||||
validation: [
|
||||
param('resource_slug').trim().escape().exists(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { AccountType } = req.models;
|
||||
const { resource_slug: resourceSlug } = req.params;
|
||||
|
||||
const data = await AccountType.query();
|
||||
|
||||
return res.status(200).send({
|
||||
data,
|
||||
resource_slug: resourceSlug,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve resource columns of the given resource.
|
||||
*/
|
||||
resourceColumns: {
|
||||
validation: [
|
||||
param('resource_slug').trim().escape().exists(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { resource_slug: resourceSlug } = req.params;
|
||||
const { Resource } = req.models;
|
||||
|
||||
const resource = await Resource.query()
|
||||
.where('name', resourceSlug)
|
||||
.withGraphFetched('fields')
|
||||
.first();
|
||||
|
||||
if (!resource) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'RESOURCE.SLUG.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
const resourceFields = resource.fields
|
||||
.filter((field) => field.columnable)
|
||||
.map((field) => ({
|
||||
id: field.id,
|
||||
label: field.labelName,
|
||||
key: field.key,
|
||||
}));
|
||||
|
||||
return res.status(200).send({
|
||||
resource_columns: resourceFields,
|
||||
resource_slug: resourceSlug,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve resource fields of the given resource.
|
||||
*/
|
||||
resourceFields: {
|
||||
validation: [
|
||||
param('resource_slug').trim().escape().exists(),
|
||||
query('predefined').optional().isBoolean().toBoolean(),
|
||||
query('builtin').optional().isBoolean().toBoolean(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { resource_slug: resourceSlug } = req.params;
|
||||
const { Resource } = req.models;
|
||||
|
||||
const resource = await Resource.query()
|
||||
.where('name', resourceSlug)
|
||||
.withGraphFetched('fields')
|
||||
.first();
|
||||
|
||||
if (!resource) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'RESOURCE.SLUG.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
return res.status(200).send({
|
||||
resource_fields: resource.fields,
|
||||
resource_slug: resourceSlug,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
44
server/src/api/controllers/Resources.ts
Normal file
44
server/src/api/controllers/Resources.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import {
|
||||
param,
|
||||
query,
|
||||
} from 'express-validator';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import BaseController from './BaseController';
|
||||
import { Service } from 'typedi';
|
||||
import ResourceFieldsKeys from 'data/ResourceFieldsKeys';
|
||||
|
||||
@Service()
|
||||
export default class ResourceController extends BaseController{
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.get('/:resource_model/fields',
|
||||
this.resourceModelParamSchema,
|
||||
asyncMiddleware(this.resourceFields.bind(this))
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
get resourceModelParamSchema() {
|
||||
return [
|
||||
param('resource_model').exists().trim().escape(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve resource fields of the given resource.
|
||||
*/
|
||||
resourceFields(req: Request, res: Response, next: NextFunction) {
|
||||
const { resource_model: resourceModel } = req.params;
|
||||
|
||||
try {
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4,7 +4,6 @@ import { difference } from 'lodash';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IPaymentReceive, IPaymentReceiveOTD } from 'interfaces';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import PaymentReceiveService from 'services/Sales/PaymentsReceives';
|
||||
import SaleInvoiceService from 'services/Sales/SalesInvoices';
|
||||
@@ -34,7 +33,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
router.post(
|
||||
'/:id',
|
||||
this.editPaymentReceiveValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
|
||||
asyncMiddleware(this.validatePaymentReceiveNoExistance.bind(this)),
|
||||
asyncMiddleware(this.validateCustomerExistance.bind(this)),
|
||||
@@ -47,7 +46,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
router.post(
|
||||
'/',
|
||||
this.newPaymentReceiveValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePaymentReceiveNoExistance.bind(this)),
|
||||
asyncMiddleware(this.validateCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateDepositAccount.bind(this)),
|
||||
@@ -58,20 +57,20 @@ export default class PaymentReceivesController extends BaseController {
|
||||
router.get(
|
||||
'/:id',
|
||||
this.paymentReceiveValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
|
||||
asyncMiddleware(this.getPaymentReceive.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
this.validatePaymentReceiveList,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getPaymentReceiveList.bind(this)),
|
||||
);
|
||||
router.delete(
|
||||
'/:id',
|
||||
this.paymentReceiveValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
|
||||
asyncMiddleware(this.deletePaymentReceive.bind(this)),
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { check, param, query, matchedData } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISaleEstimate, ISaleEstimateOTD } from 'interfaces';
|
||||
import BaseController from 'api/controllers/BaseController'
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
@@ -25,7 +24,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
router.post(
|
||||
'/',
|
||||
this.estimateValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateEstimateCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateEstimateNumberExistance.bind(this)),
|
||||
asyncMiddleware(this.validateEstimateEntriesItemsExistance.bind(this)),
|
||||
@@ -36,7 +35,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
...this.validateSpecificEstimateSchema,
|
||||
...this.estimateValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
|
||||
asyncMiddleware(this.validateEstimateCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateEstimateNumberExistance.bind(this)),
|
||||
@@ -48,21 +47,21 @@ export default class SalesEstimatesController extends BaseController {
|
||||
'/:id', [
|
||||
this.validateSpecificEstimateSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
|
||||
asyncMiddleware(this.deleteEstimate.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/:id',
|
||||
this.validateSpecificEstimateSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
|
||||
asyncMiddleware(this.getEstimate.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
this.validateEstimateListSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getEstimates.bind(this))
|
||||
);
|
||||
return router;
|
||||
@@ -298,7 +297,21 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getEstimates(req: Request, res: Response) {
|
||||
|
||||
async getEstimates(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const estimatesFilter: ISalesEstimatesFilter = this.matchedQueryData(req);
|
||||
|
||||
try {
|
||||
const { salesEstimates, pagination, filterMeta } = await this.saleEstimateService
|
||||
.estimatesList(tenantId, estimatesFilter);
|
||||
|
||||
return res.status(200).send({
|
||||
sales_estimates: this.transfromToResponse(salesEstimates),
|
||||
pagination,
|
||||
filter_meta: this.transfromToResponse(filterMeta),
|
||||
})
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { check, param, query, matchedData } from 'express-validator';
|
||||
import { difference } from 'lodash';
|
||||
import { raw } from 'objection';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import BaseController from '../BaseController';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import SaleInvoiceService from 'services/Sales/SalesInvoices';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
import { ISaleInvoiceOTD } from 'interfaces';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import { ISaleInvoiceOTD, ISalesInvoicesFilter } from 'interfaces';
|
||||
|
||||
@Service()
|
||||
export default class SaleInvoicesController {
|
||||
export default class SaleInvoicesController extends BaseController{
|
||||
@Inject()
|
||||
itemsService: ItemsService;
|
||||
|
||||
@Inject()
|
||||
saleInvoiceService: SaleInvoiceService;
|
||||
|
||||
@Inject()
|
||||
dynamicListService: DynamicListingService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
@@ -26,7 +30,7 @@ export default class SaleInvoicesController {
|
||||
router.post(
|
||||
'/',
|
||||
this.saleInvoiceValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateInvoiceCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateInvoiceNumberUnique.bind(this)),
|
||||
asyncMiddleware(this.validateInvoiceItemsIdsExistance.bind(this)),
|
||||
@@ -39,7 +43,7 @@ export default class SaleInvoicesController {
|
||||
...this.saleInvoiceValidationSchema,
|
||||
...this.specificSaleInvoiceValidation,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateInvoiceExistance.bind(this)),
|
||||
asyncMiddleware(this.validateInvoiceCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateInvoiceNumberUnique.bind(this)),
|
||||
@@ -52,7 +56,7 @@ export default class SaleInvoicesController {
|
||||
router.delete(
|
||||
'/:id',
|
||||
this.specificSaleInvoiceValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateInvoiceExistance.bind(this)),
|
||||
asyncMiddleware(this.deleteSaleInvoice.bind(this))
|
||||
);
|
||||
@@ -64,13 +68,14 @@ export default class SaleInvoicesController {
|
||||
router.get(
|
||||
'/:id',
|
||||
this.specificSaleInvoiceValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateInvoiceExistance.bind(this)),
|
||||
asyncMiddleware(this.getSaleInvoice.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
this.saleInvoiceListValidationSchema,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getSalesInvoices.bind(this))
|
||||
)
|
||||
return router;
|
||||
@@ -411,7 +416,21 @@ export default class SaleInvoicesController {
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async getSalesInvoices(req, res) {
|
||||
|
||||
public async getSalesInvoices(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req.params;
|
||||
const salesInvoicesFilter: ISalesInvoicesFilter = req.query;
|
||||
|
||||
try {
|
||||
const { salesInvoices, filterMeta, pagination } = await this.saleInvoiceService.salesInvoicesList(
|
||||
tenantId, salesInvoicesFilter,
|
||||
);
|
||||
return res.status(200).send({
|
||||
sales_invoices: salesInvoices,
|
||||
pagination: this.transfromToResponse(pagination),
|
||||
filter_meta: this.transfromToResponse(filterMeta),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { check, param, query, matchedData } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import AccountsService from 'services/Accounts/AccountsService';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
import SaleReceiptService from 'services/Sales/SalesReceipts';
|
||||
import BaseController from '../BaseController';
|
||||
|
||||
@Service()
|
||||
export default class SalesReceiptsController {
|
||||
export default class SalesReceiptsController extends BaseController{
|
||||
@Inject()
|
||||
saleReceiptService: SaleReceiptService;
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class SalesReceiptsController {
|
||||
...this.specificReceiptValidationSchema,
|
||||
...this.salesReceiptsValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateSaleReceiptExistance.bind(this)),
|
||||
asyncMiddleware(this.validateReceiptCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateReceiptDepositAccountExistance.bind(this)),
|
||||
@@ -40,7 +40,7 @@ export default class SalesReceiptsController {
|
||||
router.post(
|
||||
'/',
|
||||
this.salesReceiptsValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateReceiptCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateReceiptDepositAccountExistance.bind(this)),
|
||||
asyncMiddleware(this.validateReceiptItemsIdsExistance.bind(this)),
|
||||
@@ -49,14 +49,14 @@ export default class SalesReceiptsController {
|
||||
router.delete(
|
||||
'/:id',
|
||||
this.specificReceiptValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateSaleReceiptExistance.bind(this)),
|
||||
asyncMiddleware(this.deleteSaleReceipt.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
this.listSalesReceiptsValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.listingSalesReceipts.bind(this))
|
||||
);
|
||||
return router;
|
||||
@@ -274,7 +274,6 @@ export default class SalesReceiptsController {
|
||||
|
||||
const { id: saleReceiptId } = req.params;
|
||||
const saleReceipt = { ...req.body };
|
||||
|
||||
const errorReasons = [];
|
||||
|
||||
// Handle all errors with reasons messages.
|
||||
@@ -296,7 +295,19 @@ export default class SalesReceiptsController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async listingSalesReceipts(req: Request, res: Response) {
|
||||
async getSalesReceipts(req: Request, res: Response) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
sortOrder: 'asc',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedBodyData(req),
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Service } from 'typedi';
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { body, query } from 'express-validator';
|
||||
import { pick } from 'lodash';
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
isDefinedOptionConfigurable,
|
||||
} from 'utils';
|
||||
|
||||
@Service()
|
||||
export default class SettingsController extends BaseController{
|
||||
/**
|
||||
* Router constructor.
|
||||
|
||||
@@ -6,7 +6,6 @@ import config from 'config';
|
||||
import { License, Plan } from 'system/models';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import LicenseService from 'services/Payment/License';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import { ILicensesFilter } from 'interfaces';
|
||||
|
||||
@@ -31,13 +30,13 @@ export default class LicensesController extends BaseController {
|
||||
router.post(
|
||||
'/generate',
|
||||
this.generateLicenseSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePlanExistance.bind(this)),
|
||||
asyncMiddleware(this.generateLicense.bind(this)),
|
||||
);
|
||||
router.post(
|
||||
'/disable/:licenseId',
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateLicenseExistance.bind(this)),
|
||||
asyncMiddleware(this.validateNotDisabledLicense.bind(this)),
|
||||
asyncMiddleware(this.disableLicense.bind(this)),
|
||||
@@ -45,7 +44,7 @@ export default class LicensesController extends BaseController {
|
||||
router.post(
|
||||
'/send',
|
||||
this.sendLicenseSchemaValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.sendLicense.bind(this)),
|
||||
);
|
||||
router.delete(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { check } from 'express-validator';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import PaymentMethodController from 'api/controllers/Subscription/PaymentMethod';
|
||||
import {
|
||||
@@ -26,7 +25,7 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
||||
router.post(
|
||||
'/payment',
|
||||
this.paymentViaLicenseSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePlanSlugExistance.bind(this)),
|
||||
asyncMiddleware(this.paymentViaLicense.bind(this)),
|
||||
);
|
||||
|
||||
@@ -222,19 +222,22 @@ export default class UsersController extends BaseController{
|
||||
}
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'user_not_found') {
|
||||
return res.status(404).send({
|
||||
errors: [{ type: 'USER.NOT.FOUND', code: 100 }],
|
||||
});
|
||||
return res.boom.badRequest(
|
||||
'User not found.',
|
||||
{ errors: [{ type: 'USER.NOT.FOUND', code: 100 }] }
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'user_already_active') {
|
||||
return res.status(404).send({
|
||||
errors: [{ type: 'USER.ALREADY.ACTIVE', code: 200 }],
|
||||
});
|
||||
return res.boom.badRequest(
|
||||
'User is already active.',
|
||||
{ errors: [{ type: 'USER.ALREADY.ACTIVE', code: 200 }] },
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'user_already_inactive') {
|
||||
return res.status(404).send({
|
||||
errors: [{ type: 'USER.ALREADY.INACTIVE', code: 200 }],
|
||||
});
|
||||
return res.boom.badRequest(
|
||||
'User is already inactive.',
|
||||
{ errors: [{ type: 'USER.ALREADY.INACTIVE', code: 200 }] },
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'user_same_the_authorized_user') {
|
||||
return res.boom.badRequest(
|
||||
|
||||
@@ -1,473 +0,0 @@
|
||||
import { difference, pick } from 'lodash';
|
||||
import express from 'express';
|
||||
import {
|
||||
check,
|
||||
query,
|
||||
param,
|
||||
oneOf,
|
||||
validationResult,
|
||||
} from 'express-validator';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import {
|
||||
validateViewRoles,
|
||||
} from 'lib/ViewRolesBuilder';
|
||||
|
||||
export default {
|
||||
resource: 'items',
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/',
|
||||
this.listViews.validation,
|
||||
asyncMiddleware(this.listViews.handler));
|
||||
|
||||
router.post('/',
|
||||
this.createView.validation,
|
||||
asyncMiddleware(this.createView.handler));
|
||||
|
||||
router.post('/:view_id',
|
||||
this.editView.validation,
|
||||
asyncMiddleware(this.editView.handler));
|
||||
|
||||
router.delete('/:view_id',
|
||||
this.deleteView.validation,
|
||||
asyncMiddleware(this.deleteView.handler));
|
||||
|
||||
router.get('/:view_id',
|
||||
asyncMiddleware(this.getView.handler));
|
||||
|
||||
router.get('/:view_id/resource',
|
||||
this.getViewResource.validation,
|
||||
asyncMiddleware(this.getViewResource.handler));
|
||||
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* List all views that associated with the given resource.
|
||||
*/
|
||||
listViews: {
|
||||
validation: [
|
||||
oneOf([
|
||||
query('resource_name').exists().trim().escape(),
|
||||
], [
|
||||
query('resource_id').exists().isNumeric().toInt(),
|
||||
]),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { Resource, View } = req.models;
|
||||
const filter = { ...req.query };
|
||||
|
||||
const resource = await Resource.query().onBuild((builder) => {
|
||||
if (filter.resource_id) {
|
||||
builder.where('id', filter.resource_id);
|
||||
}
|
||||
if (filter.resource_name) {
|
||||
builder.where('name', filter.resource_name);
|
||||
}
|
||||
builder.first();
|
||||
});
|
||||
|
||||
const views = await View.query().where('resource_id', resource.id);
|
||||
|
||||
return res.status(200).send({ views });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve view details of the given view id.
|
||||
*/
|
||||
getView: {
|
||||
validation: [
|
||||
param('view_id').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { view_id: viewId } = req.params;
|
||||
const { View } = req.models;
|
||||
|
||||
const view = await View.query()
|
||||
.where('id', viewId)
|
||||
.withGraphFetched('resource')
|
||||
.withGraphFetched('columns')
|
||||
.withGraphFetched('roles.field')
|
||||
.first();
|
||||
|
||||
if (!view) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'VIEW_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
return res.status(200).send({ view: view.toJSON() });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the given view of the resource.
|
||||
*/
|
||||
deleteView: {
|
||||
validation: [
|
||||
param('view_id').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { View } = req.models;
|
||||
const { view_id: viewId } = req.params;
|
||||
const view = await View.query().findById(viewId);
|
||||
|
||||
if (!view) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'VIEW_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
if (view.predefined) {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'PREDEFINED_VIEW', code: 200 }],
|
||||
});
|
||||
}
|
||||
await Promise.all([
|
||||
view.$relatedQuery('roles').delete(),
|
||||
view.$relatedQuery('columns').delete(),
|
||||
]);
|
||||
await View.query().where('id', view.id).delete();
|
||||
|
||||
return res.status(200).send({ id: view.id });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new view.
|
||||
*/
|
||||
createView: {
|
||||
validation: [
|
||||
check('resource_name').exists().escape().trim(),
|
||||
check('name').exists().escape().trim(),
|
||||
check('logic_expression').exists().trim().escape(),
|
||||
check('roles').isArray({ min: 1 }),
|
||||
check('roles.*.field_key').exists().escape().trim(),
|
||||
check('roles.*.comparator').exists(),
|
||||
check('roles.*.value').exists(),
|
||||
check('roles.*.index').exists().isNumeric().toInt(),
|
||||
check('columns').exists().isArray({ min: 1 }),
|
||||
check('columns.*.key').exists().escape().trim(),
|
||||
check('columns.*.index').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const {
|
||||
Resource,
|
||||
View,
|
||||
ViewColumn,
|
||||
ViewRole,
|
||||
} = req.models;
|
||||
const form = { roles: [], ...req.body };
|
||||
const resource = await Resource.query().where('name', form.resource_name).first();
|
||||
|
||||
if (!resource) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'RESOURCE_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
const errorReasons = [];
|
||||
const fieldsSlugs = form.roles.map((role) => role.field_key);
|
||||
|
||||
const resourceFields = await resource.$relatedQuery('fields');
|
||||
const resourceFieldsKeys = resourceFields.map((f) => f.key);
|
||||
const resourceFieldsKeysMap = new Map(resourceFields.map((field) => [field.key, field]));
|
||||
const columnsKeys = form.columns.map((c) => c.key);
|
||||
|
||||
// The difference between the stored resource fields and submit fields keys.
|
||||
const notFoundFields = difference(fieldsSlugs, resourceFieldsKeys);
|
||||
|
||||
if (notFoundFields.length > 0) {
|
||||
errorReasons.push({ type: 'RESOURCE_FIELDS_NOT_EXIST', code: 100, fields: notFoundFields });
|
||||
}
|
||||
// The difference between the stored resource fields and the submit columns keys.
|
||||
const notFoundColumns = difference(columnsKeys, resourceFieldsKeys);
|
||||
|
||||
if (notFoundColumns.length > 0) {
|
||||
errorReasons.push({ type: 'COLUMNS_NOT_EXIST', code: 200, columns: notFoundColumns });
|
||||
}
|
||||
// Validates the view conditional logic expression.
|
||||
if (!validateViewRoles(form.roles, form.logic_expression)) {
|
||||
errorReasons.push({ type: 'VIEW.ROLES.LOGIC.EXPRESSION.INVALID', code: 400 });
|
||||
}
|
||||
if (errorReasons.length > 0) {
|
||||
return res.boom.badRequest(null, { errors: errorReasons });
|
||||
}
|
||||
|
||||
// Save view details.
|
||||
const view = await View.query().insert({
|
||||
name: form.name,
|
||||
predefined: false,
|
||||
resource_id: resource.id,
|
||||
roles_logic_expression: form.logic_expression,
|
||||
});
|
||||
// Save view roles async operations.
|
||||
const saveViewRolesOpers = [];
|
||||
|
||||
form.roles.forEach((role) => {
|
||||
const fieldModel = resourceFieldsKeysMap.get(role.field_key);
|
||||
|
||||
const saveViewRoleOper = ViewRole.query().insert({
|
||||
...pick(role, ['comparator', 'value', 'index']),
|
||||
field_id: fieldModel.id,
|
||||
view_id: view.id,
|
||||
});
|
||||
saveViewRolesOpers.push(saveViewRoleOper);
|
||||
});
|
||||
|
||||
form.columns.forEach((column) => {
|
||||
const fieldModel = resourceFieldsKeysMap.get(column.key);
|
||||
|
||||
const saveViewColumnOper = ViewColumn.query().insert({
|
||||
field_id: fieldModel.id,
|
||||
view_id: view.id,
|
||||
index: column.index,
|
||||
});
|
||||
saveViewRolesOpers.push(saveViewColumnOper);
|
||||
});
|
||||
await Promise.all(saveViewRolesOpers);
|
||||
|
||||
return res.status(200).send({ id: view.id });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit the given custom view metadata.
|
||||
*/
|
||||
editView: {
|
||||
validation: [
|
||||
param('view_id').exists().isNumeric().toInt(),
|
||||
check('name').exists().escape().trim(),
|
||||
check('logic_expression').exists().trim().escape(),
|
||||
|
||||
check('columns').exists().isArray({ min: 1 }),
|
||||
|
||||
check('columns.*.id').optional().isNumeric().toInt(),
|
||||
check('columns.*.key').exists().escape().trim(),
|
||||
check('columns.*.index').exists().isNumeric().toInt(),
|
||||
|
||||
check('roles').isArray(),
|
||||
check('roles.*.id').optional().isNumeric().toInt(),
|
||||
check('roles.*.field_key').exists().escape().trim(),
|
||||
check('roles.*.comparator').exists(),
|
||||
check('roles.*.value').exists(),
|
||||
check('roles.*.index').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { view_id: viewId } = req.params;
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const {
|
||||
View, ViewRole, ViewColumn, Resource,
|
||||
} = req.models;
|
||||
const view = await View.query().where('id', viewId)
|
||||
.withGraphFetched('roles.field')
|
||||
.withGraphFetched('columns')
|
||||
.first();
|
||||
|
||||
if (!view) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'ROLE_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
const form = { ...req.body };
|
||||
const resource = await Resource.query()
|
||||
.where('id', view.resourceId)
|
||||
.withGraphFetched('fields')
|
||||
.withGraphFetched('views')
|
||||
.first();
|
||||
|
||||
const errorReasons = [];
|
||||
const fieldsSlugs = form.roles.map((role) => role.field_key);
|
||||
const resourceFieldsKeys = resource.fields.map((f) => f.key);
|
||||
const resourceFieldsKeysMap = new Map(resource.fields.map((field) => [field.key, field]));
|
||||
const columnsKeys = form.columns.map((c) => c.key);
|
||||
|
||||
// The difference between the stored resource fields and submit fields keys.
|
||||
const notFoundFields = difference(fieldsSlugs, resourceFieldsKeys);
|
||||
|
||||
// Validate not found resource fields keys.
|
||||
if (notFoundFields.length > 0) {
|
||||
errorReasons.push({
|
||||
type: 'RESOURCE_FIELDS_NOT_EXIST', code: 100, fields: notFoundFields,
|
||||
});
|
||||
}
|
||||
// The difference between the stored resource fields and the submit columns keys.
|
||||
const notFoundColumns = difference(columnsKeys, resourceFieldsKeys);
|
||||
|
||||
// Validate not found view columns.
|
||||
if (notFoundColumns.length > 0) {
|
||||
errorReasons.push({ type: 'RESOURCE_COLUMNS_NOT_EXIST', code: 200, columns: notFoundColumns });
|
||||
}
|
||||
// Validates the view conditional logic expression.
|
||||
if (!validateViewRoles(form.roles, form.logic_expression)) {
|
||||
errorReasons.push({ type: 'VIEW.ROLES.LOGIC.EXPRESSION.INVALID', code: 400 });
|
||||
}
|
||||
|
||||
const viewRolesIds = view.roles.map((r) => r.id);
|
||||
const viewColumnsIds = view.columns.map((c) => c.id);
|
||||
|
||||
const formUpdatedRoles = form.roles.filter((r) => r.id);
|
||||
const formInsertRoles = form.roles.filter((r) => !r.id);
|
||||
|
||||
const formRolesIds = formUpdatedRoles.map((r) => r.id);
|
||||
|
||||
const formUpdatedColumns = form.columns.filter((r) => r.id);
|
||||
const formInsertedColumns = form.columns.filter((r) => !r.id);
|
||||
const formColumnsIds = formUpdatedColumns.map((r) => r.id);
|
||||
|
||||
const rolesIdsShouldDeleted = difference(viewRolesIds, formRolesIds);
|
||||
const columnsIdsShouldDelete = difference(viewColumnsIds, formColumnsIds);
|
||||
|
||||
const notFoundViewRolesIds = difference(formRolesIds, viewRolesIds);
|
||||
const notFoundViewColumnsIds = difference(viewColumnsIds, viewColumnsIds);
|
||||
|
||||
// Validate the not found view roles ids.
|
||||
if (notFoundViewRolesIds.length) {
|
||||
errorReasons.push({ type: 'VIEW.ROLES.IDS.NOT.FOUND', code: 500, ids: notFoundViewRolesIds });
|
||||
}
|
||||
// Validate the not found view columns ids.
|
||||
if (notFoundViewColumnsIds.length) {
|
||||
errorReasons.push({ type: 'VIEW.COLUMNS.IDS.NOT.FOUND', code: 600, ids: notFoundViewColumnsIds });
|
||||
}
|
||||
if (errorReasons.length > 0) {
|
||||
return res.status(400).send({ errors: errorReasons });
|
||||
}
|
||||
const asyncOpers = [];
|
||||
|
||||
// Save view details.
|
||||
await View.query()
|
||||
.where('id', view.id)
|
||||
.patch({
|
||||
name: form.name,
|
||||
roles_logic_expression: form.logic_expression,
|
||||
});
|
||||
|
||||
// Update view roles.
|
||||
if (formUpdatedRoles.length > 0) {
|
||||
formUpdatedRoles.forEach((role) => {
|
||||
const fieldModel = resourceFieldsKeysMap.get(role.field_key);
|
||||
const updateOper = ViewRole.query()
|
||||
.where('id', role.id)
|
||||
.update({
|
||||
...pick(role, ['comparator', 'value', 'index']),
|
||||
field_id: fieldModel.id,
|
||||
});
|
||||
asyncOpers.push(updateOper);
|
||||
});
|
||||
}
|
||||
// Insert a new view roles.
|
||||
if (formInsertRoles.length > 0) {
|
||||
formInsertRoles.forEach((role) => {
|
||||
const fieldModel = resourceFieldsKeysMap.get(role.field_key);
|
||||
const insertOper = ViewRole.query()
|
||||
.insert({
|
||||
...pick(role, ['comparator', 'value', 'index']),
|
||||
field_id: fieldModel.id,
|
||||
view_id: view.id,
|
||||
});
|
||||
asyncOpers.push(insertOper);
|
||||
});
|
||||
}
|
||||
// Delete view roles.
|
||||
if (rolesIdsShouldDeleted.length > 0) {
|
||||
const deleteOper = ViewRole.query()
|
||||
.whereIn('id', rolesIdsShouldDeleted)
|
||||
.delete();
|
||||
asyncOpers.push(deleteOper);
|
||||
}
|
||||
// Insert a new view columns to the storage.
|
||||
if (formInsertedColumns.length > 0) {
|
||||
formInsertedColumns.forEach((column) => {
|
||||
const fieldModel = resourceFieldsKeysMap.get(column.key);
|
||||
const insertOper = ViewColumn.query()
|
||||
.insert({
|
||||
field_id: fieldModel.id,
|
||||
index: column.index,
|
||||
view_id: view.id,
|
||||
});
|
||||
asyncOpers.push(insertOper);
|
||||
});
|
||||
}
|
||||
// Update the view columns on the storage.
|
||||
if (formUpdatedColumns.length > 0) {
|
||||
formUpdatedColumns.forEach((column) => {
|
||||
const updateOper = ViewColumn.query()
|
||||
.where('id', column.id)
|
||||
.update({
|
||||
index: column.index,
|
||||
});
|
||||
asyncOpers.push(updateOper);
|
||||
});
|
||||
}
|
||||
// Delete the view columns from the storage.
|
||||
if (columnsIdsShouldDelete.length > 0) {
|
||||
const deleteOper = ViewColumn.query()
|
||||
.whereIn('id', columnsIdsShouldDelete)
|
||||
.delete();
|
||||
asyncOpers.push(deleteOper);
|
||||
}
|
||||
await Promise.all(asyncOpers);
|
||||
|
||||
return res.status(200).send();
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve resource columns that associated to the given custom view.
|
||||
*/
|
||||
getViewResource: {
|
||||
validation: [
|
||||
param('view_id').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { view_id: viewId } = req.params;
|
||||
const { View } = req.models;
|
||||
|
||||
const view = await View.query()
|
||||
.where('id', viewId)
|
||||
.withGraphFetched('resource.fields')
|
||||
.first();
|
||||
|
||||
if (!view) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'VIEW.NOT.FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
if (!view.resource) {
|
||||
return res.boom.badData(null, {
|
||||
errors: [{ type: 'VIEW.HAS.NOT.ASSOCIATED.RESOURCE', code: 200 }],
|
||||
});
|
||||
}
|
||||
|
||||
const resourceColumns = view.resource.fields
|
||||
.filter((field) => field.columnable)
|
||||
.map((field) => ({
|
||||
id: field.id,
|
||||
label: field.labelName,
|
||||
key: field.key,
|
||||
}));
|
||||
|
||||
return res.status(200).send({
|
||||
resource_slug: view.resource.name,
|
||||
resource_columns: resourceColumns,
|
||||
resource_fields: view.resource.fields,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -1,19 +1,10 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Router, Request, NextFunction, Response } from 'express';
|
||||
import {
|
||||
check,
|
||||
query,
|
||||
param,
|
||||
oneOf,
|
||||
validationResult,
|
||||
} from 'express-validator';
|
||||
import { check, param } from 'express-validator';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import {
|
||||
validateViewRoles,
|
||||
} from 'lib/ViewRolesBuilder';
|
||||
import ViewsService from 'services/Views/ViewsService';
|
||||
import BaseController from './BaseController';
|
||||
import { IViewDTO } from 'interfaces';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import { IViewDTO, IViewEditDTO } from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
|
||||
@Service()
|
||||
@@ -27,66 +18,96 @@ export default class ViewsController extends BaseController{
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.get('/', [
|
||||
...this.viewDTOSchemaValidation,
|
||||
router.get('/resource/:resource_model', [
|
||||
...this.viewsListSchemaValidation,
|
||||
],
|
||||
asyncMiddleware(this.listViews)
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.listResourceViews.bind(this)),
|
||||
this.handlerServiceErrors,
|
||||
);
|
||||
router.post('/', [
|
||||
...this.viewDTOSchemaValidation,
|
||||
],
|
||||
asyncMiddleware(this.createView)
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.createView.bind(this)),
|
||||
this.handlerServiceErrors
|
||||
);
|
||||
|
||||
router.post('/:view_id', [
|
||||
...this.viewDTOSchemaValidation,
|
||||
router.post('/:id', [
|
||||
...this.viewParamSchemaValidation,
|
||||
...this.viewEditDTOSchemaValidation,
|
||||
],
|
||||
asyncMiddleware(this.editView)
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.editView.bind(this)),
|
||||
this.handlerServiceErrors,
|
||||
);
|
||||
|
||||
router.delete('/:view_id', [
|
||||
router.delete('/:id', [
|
||||
...this.viewParamSchemaValidation
|
||||
],
|
||||
asyncMiddleware(this.deleteView));
|
||||
|
||||
router.get('/:view_id', [
|
||||
...this.viewParamSchemaValidation
|
||||
]
|
||||
asyncMiddleware(this.getView)
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteView.bind(this)),
|
||||
this.handlerServiceErrors,
|
||||
);
|
||||
|
||||
router.get('/:view_id/resource', [
|
||||
router.get('/:id', [
|
||||
...this.viewParamSchemaValidation
|
||||
],
|
||||
asyncMiddleware(this.getViewResource)
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getView.bind(this)),
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* New view DTO schema validation.
|
||||
*/
|
||||
get viewDTOSchemaValidation() {
|
||||
return [
|
||||
check('resource_name').exists().escape().trim(),
|
||||
check('resource_model').exists().escape().trim(),
|
||||
check('name').exists().escape().trim(),
|
||||
check('logic_expression').exists().trim().escape(),
|
||||
|
||||
check('roles').isArray({ min: 1 }),
|
||||
check('roles.*.field_key').exists().escape().trim(),
|
||||
check('roles.*.comparator').exists(),
|
||||
check('roles.*.value').exists(),
|
||||
check('roles.*.index').exists().isNumeric().toInt(),
|
||||
|
||||
check('columns').exists().isArray({ min: 1 }),
|
||||
check('columns.*.key').exists().escape().trim(),
|
||||
check('columns.*.field_key').exists().escape().trim(),
|
||||
check('columns.*.index').exists().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit view DTO schema validation.
|
||||
*/
|
||||
get viewEditDTOSchemaValidation() {
|
||||
return [
|
||||
check('name').exists().escape().trim(),
|
||||
check('logic_expression').exists().trim().escape(),
|
||||
|
||||
check('roles').isArray({ min: 1 }),
|
||||
check('roles.*.field_key').exists().escape().trim(),
|
||||
check('roles.*.comparator').exists(),
|
||||
check('roles.*.value').exists(),
|
||||
check('roles.*.index').exists().isNumeric().toInt(),
|
||||
|
||||
check('columns').exists().isArray({ min: 1 }),
|
||||
check('columns.*.field_key').exists().escape().trim(),
|
||||
check('columns.*.index').exists().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
get viewParamSchemaValidation() {
|
||||
return [
|
||||
|
||||
]
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
get viewsListSchemaValidation() {
|
||||
return [
|
||||
param('resource_model').exists().trim().escape(),
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* List all views that associated with the given resource.
|
||||
@@ -94,12 +115,12 @@ export default class ViewsController extends BaseController{
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
listViews(req: Request, res: Response, next: NextFunction) {
|
||||
async listResourceViews(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = req.query;
|
||||
const { resource_model: resourceModel } = req.params;
|
||||
|
||||
try {
|
||||
const views = this.viewsService.listViews(tenantId, filter);
|
||||
const views = await this.viewsService.listResourceViews(tenantId, resourceModel);
|
||||
return res.status(200).send({ views });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -107,16 +128,17 @@ export default class ViewsController extends BaseController{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Retrieve view details with assocaited roles and columns.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
getView(req: Request, res: Response, next: NextFunction) {
|
||||
async getView(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: viewId } = req.params;
|
||||
|
||||
try {
|
||||
const view = this.viewsService.getView(tenantId, viewId);
|
||||
const view = await this.viewsService.getView(tenantId, viewId);
|
||||
return res.status(200).send({ view });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -129,13 +151,13 @@ export default class ViewsController extends BaseController{
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
createView(req: Request, res: Response, next: NextFunction) {
|
||||
async createView(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const viewDTO: IViewDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
await this.viewsService.newView(tenantId, viewDTO);
|
||||
return res.status(200).send({ id: 1 });
|
||||
const view = await this.viewsService.newView(tenantId, viewDTO);
|
||||
return res.status(200).send({ id: view.id });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -147,10 +169,10 @@ export default class ViewsController extends BaseController{
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
editView(req: Request, res: Response, next: NextFunction) {
|
||||
async editView(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: viewId } = req.params;
|
||||
const { body: viewEditDTO } = req;
|
||||
const viewEditDTO: IViewEditDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
await this.viewsService.editView(tenantId, viewId, viewEditDTO);
|
||||
@@ -166,7 +188,7 @@ export default class ViewsController extends BaseController{
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
deleteView(req: Request, res: Response, next: NextFunction) {
|
||||
async deleteView(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: viewId } = req.params;
|
||||
|
||||
@@ -187,6 +209,16 @@ export default class ViewsController extends BaseController{
|
||||
*/
|
||||
handlerServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'VIEW_NAME_NOT_UNIQUE') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'VIEW_NAME_NOT_UNIQUE', code: 110 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'RESOURCE_MODEL_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'RESOURCE_MODEL_NOT_FOUND', code: 150, }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'INVALID_LOGIC_EXPRESSION') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'VIEW.ROLES.LOGIC.EXPRESSION.INVALID', code: 400 }],
|
||||
@@ -212,6 +244,17 @@ export default class ViewsController extends BaseController{
|
||||
errors: [{ type: 'PREDEFINED_VIEW', code: 200 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'RESOURCE_FIELDS_KEYS_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'RESOURCE_FIELDS_KEYS_NOT_FOUND', code: 300 }],
|
||||
})
|
||||
}
|
||||
if (error.errorType === 'RESOURCE_COLUMNS_KEYS_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'RESOURCE_COLUMNS_KEYS_NOT_FOUND', code: 310 }],
|
||||
})
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -87,18 +87,18 @@ export default () => {
|
||||
dashboard.use('/accounts', Container.get(Accounts).router());
|
||||
dashboard.use('/account_types', Container.get(AccountTypes).router());
|
||||
dashboard.use('/manual-journals', Container.get(ManualJournals).router());
|
||||
dashboard.use('/views', Views.router());
|
||||
dashboard.use('/views', Container.get(Views).router());
|
||||
dashboard.use('/items', Container.get(Items).router());
|
||||
dashboard.use('/item_categories', Container.get(ItemCategories).router());
|
||||
dashboard.use('/expenses', Container.get(Expenses).router());
|
||||
dashboard.use('/financial_statements', FinancialStatements.router());
|
||||
dashboard.use('/sales', Container.get(Sales).router());
|
||||
dashboard.use('/customers', Container.get(Customers).router());
|
||||
dashboard.use('/vendors', Container.get(Vendors).router());
|
||||
dashboard.use('/purchases', Container.get(Purchases).router());
|
||||
dashboard.use('/resources', Resources.router());
|
||||
// dashboard.use('/sales', Container.get(Sales).router());
|
||||
// dashboard.use('/purchases', Container.get(Purchases).router());
|
||||
dashboard.use('/resources', Container.get(Resources).router());
|
||||
dashboard.use('/exchange_rates', Container.get(ExchangeRates).router());
|
||||
dashboard.use('/media', Media.router());
|
||||
dashboard.use('/media', Container.get(Media).router());
|
||||
|
||||
app.use('/', dashboard);
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ export default (req: Request, tenant: ITenant) => {
|
||||
const repositories = tenantServices.repositories(tenantId)
|
||||
const cacheInstance = tenantServices.cache(tenantId);
|
||||
|
||||
tenantServices.setI18nLocals(tenantId, { __: req.__ });
|
||||
|
||||
req.knex = knexInstance;
|
||||
req.organizationId = organizationId;
|
||||
req.tenant = tenant;
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { validationResult } from 'express-validator';
|
||||
|
||||
export default (req, res, next) => {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error',
|
||||
...validationErrors,
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
Reference in New Issue
Block a user