mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
feat: Payment system with voucher cards.
feat: Design with inversion dependency injection architecture. feat: Prettier http middleware. feat: Re-write items categories with preferred accounts.
This commit is contained in:
@@ -1,327 +0,0 @@
|
||||
import express from 'express';
|
||||
import {
|
||||
check,
|
||||
param,
|
||||
validationResult,
|
||||
query,
|
||||
} from 'express-validator';
|
||||
import { difference } from 'lodash';
|
||||
import asyncMiddleware from '../middleware/asyncMiddleware';
|
||||
import {
|
||||
DynamicFilter,
|
||||
DynamicFilterSortBy,
|
||||
DynamicFilterFilterRoles,
|
||||
} from '@/lib/DynamicFilter';
|
||||
import {
|
||||
mapFilterRolesToDynamicFilter,
|
||||
} from '@/lib/ViewRolesBuilder';
|
||||
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Router constructor method.
|
||||
*/
|
||||
router() {
|
||||
const router = express.Router();
|
||||
// const permit = Authorization('items_categories');
|
||||
|
||||
router.post('/:id',
|
||||
this.editCategory.validation,
|
||||
asyncMiddleware(this.editCategory.handler));
|
||||
|
||||
router.post('/',
|
||||
this.newCategory.validation,
|
||||
asyncMiddleware(this.newCategory.handler));
|
||||
|
||||
router.delete('/bulk',
|
||||
this.bulkDeleteCategories.validation,
|
||||
asyncMiddleware(this.bulkDeleteCategories.handler));
|
||||
|
||||
router.delete('/:id',
|
||||
this.deleteItem.validation,
|
||||
asyncMiddleware(this.deleteItem.handler));
|
||||
|
||||
router.get('/:id',
|
||||
this.getCategory.validation,
|
||||
asyncMiddleware(this.getCategory.handler));
|
||||
|
||||
router.get('/',
|
||||
this.getList.validation,
|
||||
asyncMiddleware(this.getList.handler));
|
||||
|
||||
|
||||
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new item category.
|
||||
*/
|
||||
newCategory: {
|
||||
validation: [
|
||||
check('name').exists().trim().escape(),
|
||||
check('parent_category_id')
|
||||
.optional({ nullable: true, checkFalsy: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
check('description')
|
||||
.optional()
|
||||
.trim()
|
||||
.escape(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { user } = req;
|
||||
const form = { ...req.body };
|
||||
const { ItemCategory } = req.models;
|
||||
|
||||
if (form.parent_category_id) {
|
||||
const foundParentCategory = await ItemCategory.query()
|
||||
.where('id', form.parent_category_id)
|
||||
.first();
|
||||
|
||||
if (!foundParentCategory) {
|
||||
return res.boom.notFound('The parent category ID is not found.', {
|
||||
errors: [{ type: 'PARENT_CATEGORY_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
const category = await ItemCategory.query().insert({
|
||||
...form,
|
||||
user_id: user.id,
|
||||
});
|
||||
return res.status(200).send({ category });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit details of the given category item.
|
||||
*/
|
||||
editCategory: {
|
||||
validation: [
|
||||
param('id').toInt(),
|
||||
check('name').exists().trim().escape(),
|
||||
check('parent_category_id')
|
||||
.optional({ nullable: true, checkFalsy: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
check('description').optional().trim().escape(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error',
|
||||
...validationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
const form = { ...req.body };
|
||||
const { ItemCategory } = req.models;
|
||||
const itemCategory = await ItemCategory.query()
|
||||
.where('id', id)
|
||||
.first();
|
||||
|
||||
if (!itemCategory) {
|
||||
return res.boom.notFound({
|
||||
errors: [{ type: 'ITEM_CATEGORY.NOT.FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
if (
|
||||
form.parent_category_id
|
||||
&& form.parent_category_id !== itemCategory.parent_category_id
|
||||
) {
|
||||
const foundParentCategory = await ItemCategory.query()
|
||||
.where('id', form.parent_category_id)
|
||||
.first();
|
||||
|
||||
if (!foundParentCategory) {
|
||||
return res.boom.notFound('The parent category ID is not found.', {
|
||||
errors: [{ type: 'PARENT_CATEGORY_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
const updateItemCategory = await ItemCategory.query()
|
||||
.where('id', id)
|
||||
.update({ ...form });
|
||||
|
||||
return res.status(200).send({ id });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the give item category.
|
||||
*/
|
||||
deleteItem: {
|
||||
validation: [
|
||||
param('id').exists().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const { ItemCategory } = req.models;
|
||||
const itemCategory = await ItemCategory.query()
|
||||
.where('id', id)
|
||||
.first();
|
||||
|
||||
if (!itemCategory) {
|
||||
return res.boom.notFound();
|
||||
}
|
||||
await ItemCategory.query()
|
||||
.where('id', itemCategory.id)
|
||||
.delete();
|
||||
|
||||
return res.status(200).send();
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the list of items.
|
||||
*/
|
||||
getList: {
|
||||
validation: [
|
||||
query('column_sort_order').optional().trim().escape(),
|
||||
query('sort_order').optional().trim().escape().isIn(['desc', 'asc']),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
const { Resource, ItemCategory } = req.models;
|
||||
const categoriesResource = await Resource.query()
|
||||
.where('name', 'items_categories')
|
||||
.withGraphFetched('fields')
|
||||
.first();
|
||||
|
||||
if (!categoriesResource) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'ITEMS.CATEGORIES.RESOURCE.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
|
||||
const filter = {
|
||||
column_sort_order: '',
|
||||
sort_order: '',
|
||||
filter_roles: [],
|
||||
...req.query,
|
||||
};
|
||||
if (filter.stringified_filter_roles) {
|
||||
filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
|
||||
}
|
||||
const errorReasons = [];
|
||||
const resourceFieldsKeys = categoriesResource.fields.map((c) => c.key);
|
||||
const dynamicFilter = new DynamicFilter(ItemCategory.tableName);
|
||||
|
||||
// Dynamic filter with filter roles.
|
||||
if (filter.filter_roles.length > 0) {
|
||||
// Validate the accounts resource fields.
|
||||
const filterRoles = new DynamicFilterFilterRoles(
|
||||
mapFilterRolesToDynamicFilter(filter.filter_roles),
|
||||
categoriesResource.fields,
|
||||
);
|
||||
categoriesResource.setFilter(filterRoles);
|
||||
|
||||
if (filterRoles.validateFilterRoles().length > 0) {
|
||||
errorReasons.push({ type: 'ITEMS.RESOURCE.HAS.NO.FIELDS', code: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic filter with column sort order.
|
||||
if (filter.column_sort_order) {
|
||||
if (resourceFieldsKeys.indexOf(filter.column_sort_order) === -1) {
|
||||
errorReasons.push({ type: 'COLUMN.SORT.ORDER.NOT.FOUND', code: 300 });
|
||||
}
|
||||
const sortByFilter = new DynamicFilterSortBy(
|
||||
filter.column_sort_order,
|
||||
filter.sort_order,
|
||||
);
|
||||
dynamicFilter.setFilter(sortByFilter);
|
||||
}
|
||||
if (errorReasons.length > 0) {
|
||||
return res.status(400).send({ errors: errorReasons });
|
||||
}
|
||||
|
||||
const categories = await ItemCategory.query().onBuild((builder) => {
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
|
||||
builder.select([
|
||||
'*',
|
||||
ItemCategory.relatedQuery('items').count().as('count'),
|
||||
]);
|
||||
});
|
||||
|
||||
return res.status(200).send({ categories });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve details of the given category.
|
||||
*/
|
||||
getCategory: {
|
||||
validation: [param('category_id').toInt()],
|
||||
async handler(req, res) {
|
||||
const { category_id: categoryId } = req.params;
|
||||
const { ItemCategory } = req.models;
|
||||
const item = await ItemCategory.where('id', categoryId).fetch();
|
||||
|
||||
if (!item) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'CATEGORY_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
return res.status(200).send({ category: item.toJSON() });
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Bulk delete the given item categories.
|
||||
*/
|
||||
bulkDeleteCategories: {
|
||||
validation: [
|
||||
query('ids').isArray({ min: 2 }),
|
||||
query('ids.*').isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const filter = {
|
||||
ids: [],
|
||||
...req.query,
|
||||
};
|
||||
const { ItemCategory } = req.models;
|
||||
|
||||
const itemCategories = await ItemCategory.query().whereIn('id', filter.ids);
|
||||
const itemCategoriesIds = itemCategories.map((category) => category.id);
|
||||
const notFoundCategories = difference(filter.ids, itemCategoriesIds);
|
||||
|
||||
if (notFoundCategories.length > 0) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'ITEM.CATEGORIES.IDS.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
|
||||
await ItemCategory.query().whereIn('id', filter.ids).delete();
|
||||
|
||||
return res.status(200).send({ ids: filter.ids });
|
||||
},
|
||||
},
|
||||
};
|
||||
437
server/src/http/controllers/ItemCategories.ts
Normal file
437
server/src/http/controllers/ItemCategories.ts
Normal file
@@ -0,0 +1,437 @@
|
||||
import express from 'express';
|
||||
import {
|
||||
check,
|
||||
param,
|
||||
query,
|
||||
} from 'express-validator';
|
||||
import { difference } from 'lodash';
|
||||
import { Service } from 'typedi';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import validateMiddleware from '@/http/middleware/validateMiddleware';
|
||||
import {
|
||||
DynamicFilter,
|
||||
DynamicFilterSortBy,
|
||||
DynamicFilterFilterRoles,
|
||||
} from '@/lib/DynamicFilter';
|
||||
import {
|
||||
mapFilterRolesToDynamicFilter,
|
||||
} from '@/lib/ViewRolesBuilder';
|
||||
import { IItemCategory, IItemCategoryOTD } from '@/interfaces';
|
||||
import PrettierMiddleware from '@/http/middleware/PrettierMiddleware';
|
||||
|
||||
@Service()
|
||||
export default class ItemsCategoriesController {
|
||||
/**
|
||||
* Router constructor method.
|
||||
*/
|
||||
constructor() {
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/:id', [
|
||||
...this.categoryValidationSchema,
|
||||
...this.specificCategoryValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.validateParentCategoryExistance),
|
||||
asyncMiddleware(this.validateSellAccountExistance),
|
||||
asyncMiddleware(this.validateCostAccountExistance),
|
||||
asyncMiddleware(this.validateInventoryAccountExistance),
|
||||
asyncMiddleware(this.editCategory)
|
||||
);
|
||||
router.post('/',
|
||||
this.categoryValidationSchema,
|
||||
validateMiddleware,
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.validateParentCategoryExistance),
|
||||
asyncMiddleware(this.validateSellAccountExistance),
|
||||
asyncMiddleware(this.validateCostAccountExistance),
|
||||
asyncMiddleware(this.validateInventoryAccountExistance),
|
||||
asyncMiddleware(this.newCategory),
|
||||
);
|
||||
router.delete('/bulk',
|
||||
this.categoriesBulkValidationSchema,
|
||||
validateMiddleware,
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.validateCategoriesIdsExistance),
|
||||
asyncMiddleware(this.bulkDeleteCategories),
|
||||
);
|
||||
router.delete('/:id',
|
||||
this.specificCategoryValidationSchema,
|
||||
validateMiddleware,
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.validateItemCategoryExistance),
|
||||
asyncMiddleware(this.deleteItem),
|
||||
);
|
||||
router.get('/:id',
|
||||
this.specificCategoryValidationSchema,
|
||||
validateMiddleware,
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.validateItemCategoryExistance),
|
||||
asyncMiddleware(this.getCategory)
|
||||
);
|
||||
router.get('/',
|
||||
this.categoriesListValidationSchema,
|
||||
validateMiddleware,
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.getList)
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Item category validation schema.
|
||||
*/
|
||||
get categoryValidationSchema() {
|
||||
return [
|
||||
check('name').exists().trim().escape(),
|
||||
check('parent_category_id')
|
||||
.optional({ nullable: true, checkFalsy: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
check('description')
|
||||
.optional()
|
||||
.trim()
|
||||
.escape(),
|
||||
check('sell_account_id')
|
||||
.optional({ nullable: true, checkFalsy: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
check('cost_account_id')
|
||||
.optional()
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
check('inventory_account_id')
|
||||
.optional()
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate items categories bulk actions.
|
||||
*/
|
||||
get categoriesBulkValidationSchema() {
|
||||
return [
|
||||
query('ids').isArray({ min: 2 }),
|
||||
query('ids.*').isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate items categories schema.
|
||||
*/
|
||||
get categoriesListValidationSchema() {
|
||||
return [
|
||||
query('column_sort_order').optional().trim().escape(),
|
||||
query('sort_order').optional().trim().escape().isIn(['desc', 'asc']),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate specific item category schema.
|
||||
*/
|
||||
get specificCategoryValidationSchema() {
|
||||
return [
|
||||
param('id').exists().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the item category existance.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async validateItemCategoryExistance(req: Request, res: Response, next: Function) {
|
||||
const categoryId: number = req.params.id;
|
||||
const { ItemCategory } = req.models;
|
||||
const category = await ItemCategory.query().findById(categoryId);
|
||||
|
||||
if (!category) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'ITEM_CATEGORY_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate wether the given cost account exists on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validateCostAccountExistance(req: Request, res: Response, next: Function) {
|
||||
const { Account, AccountType } = req.models;
|
||||
const category: IItemCategoryOTD = { ...req.body };
|
||||
|
||||
if (category.costAccountId) {
|
||||
const COGSType = await AccountType.query().findOne('key', 'cost_of_goods_sold');
|
||||
const foundAccount = await Account.query().findById(category.costAccountId)
|
||||
|
||||
if (!foundAccount) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'COST.ACCOUNT.NOT.FOUND', code: 120 }],
|
||||
});
|
||||
} else if (foundAccount.accountTypeId !== COGSType.id) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'COST.ACCOUNT.NOT.COGS.TYPE', code: 220 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate wether the given sell account exists on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async validateSellAccountExistance(req: Request, res: Response, next: Function) {
|
||||
const { Account, AccountType } = req.models;
|
||||
const category: IItemCategoryOTD = { ...req.body };
|
||||
|
||||
if (category.sellAccountId) {
|
||||
const incomeType = await AccountType.query().findOne('key', 'income');
|
||||
const foundAccount = await Account.query().findById(category.sellAccountId);
|
||||
|
||||
if (!foundAccount) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'SELL.ACCOUNT.NOT.FOUND', code: 130 }],
|
||||
});
|
||||
} else if (foundAccount.accountTypeId !== incomeType.id) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'SELL.ACCOUNT.NOT.INCOME.TYPE', code: 230 }],
|
||||
})
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates wether the given inventory account exists on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async validateInventoryAccountExistance(req: Request, res: Response, next: Function) {
|
||||
const { Account, AccountType } = req.models;
|
||||
const category: IItemCategoryOTD = { ...req.body };
|
||||
|
||||
if (category.inventoryAccountId) {
|
||||
const otherAsset = await AccountType.query().findOne('key', 'other_asset');
|
||||
const foundAccount = await Account.query().findById(category.inventoryAccountId);
|
||||
|
||||
if (!foundAccount) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'INVENTORY.ACCOUNT.NOT.FOUND', code: 200}],
|
||||
});
|
||||
} else if (otherAsset.id !== foundAccount.accountTypeId) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'INVENTORY.ACCOUNT.NOT.CURRENT.ASSET', code: 300 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the item category parent category whether exists on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validateParentCategoryExistance(req: Request, res: Response, next: Function) {
|
||||
const category: IItemCategory = { ...req.body };
|
||||
const { ItemCategory } = req.models;
|
||||
|
||||
if (category.parentCategoryId) {
|
||||
const foundParentCategory = await ItemCategory.query()
|
||||
.where('id', category.parentCategoryId)
|
||||
.first();
|
||||
|
||||
if (!foundParentCategory) {
|
||||
return res.boom.notFound('The parent category ID is not found.', {
|
||||
errors: [{ type: 'PARENT_CATEGORY_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate item categories ids existance.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validateCategoriesIdsExistance(req: Request, res: Response, next: Function) {
|
||||
const ids: number[] = (req.query?.ids || []);
|
||||
const { ItemCategory } = req.models;
|
||||
|
||||
const itemCategories = await ItemCategory.query().whereIn('id', ids);
|
||||
const itemCategoriesIds = itemCategories.map((category: IItemCategory) => category.id);
|
||||
const notFoundCategories = difference(ids, itemCategoriesIds);
|
||||
|
||||
if (notFoundCategories.length > 0) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'ITEM.CATEGORIES.IDS.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new item category.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async newCategory(req: Request, res: Response) {
|
||||
const { user } = req;
|
||||
const category: IItemCategory = { ...req.body };
|
||||
const { ItemCategory } = req.models;
|
||||
|
||||
const storedCategory = await ItemCategory.query().insert({
|
||||
...category,
|
||||
user_id: user.id,
|
||||
});
|
||||
return res.status(200).send({ category: storedCategory });
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit details of the given category item.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async editCategory(req: Request, res: Response) {
|
||||
const { id } = req.params;
|
||||
const category: IItemCategory = { ...req.body };
|
||||
const { ItemCategory } = req.models;
|
||||
|
||||
const updateItemCategory = await ItemCategory.query()
|
||||
.where('id', id)
|
||||
.update({ ...category });
|
||||
|
||||
return res.status(200).send({ id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the give item category.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async deleteItem(req: Request, res: Response) {
|
||||
const { id } = req.params;
|
||||
const { ItemCategory } = req.models;
|
||||
|
||||
await ItemCategory.query()
|
||||
.where('id', id)
|
||||
.delete();
|
||||
|
||||
return res.status(200).send({ id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the list of items.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async getList(req: Request, res: Response) {
|
||||
const { Resource, ItemCategory } = req.models;
|
||||
const categoriesResource = await Resource.query()
|
||||
.where('name', 'items_categories')
|
||||
.withGraphFetched('fields')
|
||||
.first();
|
||||
|
||||
if (!categoriesResource) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'ITEMS.CATEGORIES.RESOURCE.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
const filter = {
|
||||
column_sort_order: '',
|
||||
sort_order: '',
|
||||
filter_roles: [],
|
||||
...req.query,
|
||||
};
|
||||
if (filter.stringified_filter_roles) {
|
||||
filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
|
||||
}
|
||||
const errorReasons = [];
|
||||
const resourceFieldsKeys = categoriesResource.fields.map((c) => c.key);
|
||||
const dynamicFilter = new DynamicFilter(ItemCategory.tableName);
|
||||
|
||||
// Dynamic filter with filter roles.
|
||||
if (filter.filter_roles.length > 0) {
|
||||
// Validate the accounts resource fields.
|
||||
const filterRoles = new DynamicFilterFilterRoles(
|
||||
mapFilterRolesToDynamicFilter(filter.filter_roles),
|
||||
categoriesResource.fields,
|
||||
);
|
||||
categoriesResource.setFilter(filterRoles);
|
||||
|
||||
if (filterRoles.validateFilterRoles().length > 0) {
|
||||
errorReasons.push({ type: 'ITEMS.RESOURCE.HAS.NO.FIELDS', code: 500 });
|
||||
}
|
||||
}
|
||||
// Dynamic filter with column sort order.
|
||||
if (filter.column_sort_order) {
|
||||
if (resourceFieldsKeys.indexOf(filter.column_sort_order) === -1) {
|
||||
errorReasons.push({ type: 'COLUMN.SORT.ORDER.NOT.FOUND', code: 300 });
|
||||
}
|
||||
const sortByFilter = new DynamicFilterSortBy(
|
||||
filter.column_sort_order,
|
||||
filter.sort_order,
|
||||
);
|
||||
dynamicFilter.setFilter(sortByFilter);
|
||||
}
|
||||
if (errorReasons.length > 0) {
|
||||
return res.status(400).send({ errors: errorReasons });
|
||||
}
|
||||
const categories = await ItemCategory.query().onBuild((builder) => {
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
|
||||
builder.select([
|
||||
'*',
|
||||
ItemCategory.relatedQuery('items').count().as('count'),
|
||||
]);
|
||||
});
|
||||
|
||||
return res.status(200).send({ categories });
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve details of the given category.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async getCategory(req: Request, res: Response) {
|
||||
const itemCategoryId: number = req.params.id;
|
||||
const { ItemCategory } = req.models;
|
||||
|
||||
const itemCategory = await ItemCategory.query().findById(itemCategoryId);
|
||||
|
||||
return res.status(200).send({ category: itemCategory });
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk delete the given item categories.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async bulkDeleteCategories(req: Request, res: Response) {
|
||||
const ids = req.query.ids;
|
||||
const { ItemCategory } = req.models;
|
||||
|
||||
await ItemCategory.query().whereIn('id', ids).delete();
|
||||
|
||||
return res.status(200).send({ ids: filter.ids });
|
||||
}
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'reflect-metadata';
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
|
||||
export default class Ping {
|
||||
/**
|
||||
* Router constur
|
||||
*/
|
||||
static router() {
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
@@ -20,7 +20,7 @@ export default class Ping {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
static async ping(req: Request, res: Response)
|
||||
async ping(req: Request, res: Response)
|
||||
{
|
||||
return res.status(200).send({
|
||||
server: true,
|
||||
|
||||
29
server/src/http/controllers/Subscription/PaymentMethod.ts
Normal file
29
server/src/http/controllers/Subscription/PaymentMethod.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Inject } from 'typedi';
|
||||
import { Plan } from '@/system/models';
|
||||
import SubscriptionService from '@/services/Subscription/SubscriptionService';
|
||||
|
||||
export default class PaymentMethodController {
|
||||
@Inject()
|
||||
subscriptionService: SubscriptionService;
|
||||
|
||||
/**
|
||||
* Validate the given plan slug exists on the storage.
|
||||
*
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*
|
||||
* @return {Response|void}
|
||||
*/
|
||||
async validatePlanSlugExistance(req: Request, res: Response, next: Function) {
|
||||
const { planSlug } = req.body;
|
||||
const foundPlan = await Plan.query().where('slug', planSlug).first();
|
||||
|
||||
if (!foundPlan) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'PLAN.SLUG.NOT.EXISTS', code: 110 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
}
|
||||
118
server/src/http/controllers/Subscription/PaymentViaVoucher.ts
Normal file
118
server/src/http/controllers/Subscription/PaymentViaVoucher.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { Container, Service } from 'typedi';
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { check, param, query, ValidationSchema } from 'express-validator';
|
||||
import { Voucher, Plan } from '@/system/models';
|
||||
import validateMiddleware from '@/http/middleware/validateMiddleware';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import PaymentMethodController from '@/http/controllers/Subscription/PaymentMethod';
|
||||
import PrettierMiddleware from '@/http/middleware/PrettierMiddleware';
|
||||
import {
|
||||
NotAllowedChangeSubscriptionPlan
|
||||
} from '@/exceptions';
|
||||
|
||||
@Service()
|
||||
export default class PaymentViaVoucherController extends PaymentMethodController {
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/payment',
|
||||
this.paymentViaVoucherSchema,
|
||||
validateMiddleware,
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.validateVoucherCodeExistance.bind(this)),
|
||||
asyncMiddleware(this.validatePlanSlugExistance.bind(this)),
|
||||
asyncMiddleware(this.validateVoucherAndPlan.bind(this)),
|
||||
asyncMiddleware(this.paymentViaVoucher.bind(this)),
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Payment via voucher validation schema.
|
||||
*/
|
||||
get paymentViaVoucherSchema() {
|
||||
return [
|
||||
check('plan_slug').exists().trim().escape(),
|
||||
check('voucher_code').exists().trim().escape(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given voucher code exists on the storage.
|
||||
* @async
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async validateVoucherCodeExistance(req: Request, res: Response, next: Function) {
|
||||
const { voucherCode } = req.body;
|
||||
|
||||
const foundVoucher = await Voucher.query()
|
||||
.modify('filterActiveVoucher')
|
||||
.where('voucher_code', voucherCode)
|
||||
.first();
|
||||
|
||||
if (!foundVoucher) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'VOUCHER.CODE.IS.INVALID', code: 120 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the voucher period and plan period.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validateVoucherAndPlan(req: Request, res: Response, next: Function) {
|
||||
const { planSlug, voucherCode } = req.body;
|
||||
|
||||
const voucher = await Voucher.query().findOne('voucher_code', voucherCode);
|
||||
const plan = await Plan.query().findOne('slug', planSlug);
|
||||
|
||||
if (voucher.planId !== plan.id) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'VOUCHER.NOT.FOR.GIVEN.PLAN' }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the subscription payment via voucher code.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
async paymentViaVoucher(req: Request, res: Response, next: Function) {
|
||||
const { planSlug, voucherCode } = req.body;
|
||||
const { tenant } = req;
|
||||
|
||||
try {
|
||||
await this.subscriptionService.subscriptionViaVoucher(tenant.id, planSlug, voucherCode);
|
||||
|
||||
return res.status(200).send({
|
||||
type: 'PAYMENT.SUCCESSFULLY.MADE',
|
||||
code: 100,
|
||||
});
|
||||
} catch (exception) {
|
||||
const errorReasons = [];
|
||||
|
||||
if (exception.name === 'NotAllowedChangeSubscriptionPlan') {
|
||||
errorReasons.push({
|
||||
type: 'NOT.ALLOWED.RENEW.SUBSCRIPTION.WHILE.ACTIVE',
|
||||
code: 120,
|
||||
});
|
||||
}
|
||||
if (errorReasons.length > 0) {
|
||||
return res.status(400).send({ errors: errorReasons });
|
||||
}
|
||||
next(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
261
server/src/http/controllers/Subscription/Vouchers.ts
Normal file
261
server/src/http/controllers/Subscription/Vouchers.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import { Router, Request, Response } from 'express'
|
||||
import { repeat, times, orderBy } from 'lodash';
|
||||
import { check, oneOf, param, query, ValidationChain } from 'express-validator';
|
||||
import { Container, Service, Inject } from 'typedi';
|
||||
import { Voucher, Plan } from '@/system/models';
|
||||
import VoucherService from '@/services/Payment/Voucher';
|
||||
import validateMiddleware from '@/http/middleware/validateMiddleware';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import PrettierMiddleware from '@/http/middleware/prettierMiddleware';
|
||||
import { IVouchersFilter } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class VouchersController {
|
||||
@Inject()
|
||||
voucherService: VoucherService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/generate',
|
||||
this.generateVoucherSchema,
|
||||
validateMiddleware,
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.validatePlanExistance),
|
||||
asyncMiddleware(this.generateVoucher.bind(this)),
|
||||
);
|
||||
router.post(
|
||||
'/disable/:voucherId',
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.validateVoucherExistance),
|
||||
asyncMiddleware(this.validateNotDisabledVoucher),
|
||||
asyncMiddleware(this.disableVoucher.bind(this)),
|
||||
);
|
||||
router.post(
|
||||
'/send',
|
||||
this.sendVoucherSchemaValidation,
|
||||
validateMiddleware,
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.sendVoucher.bind(this)),
|
||||
);
|
||||
router.delete(
|
||||
'/:voucherId',
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.validateVoucherExistance),
|
||||
asyncMiddleware(this.deleteVoucher.bind(this)),
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
PrettierMiddleware,
|
||||
asyncMiddleware(this.listVouchers.bind(this)),
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate voucher validation schema.
|
||||
*/
|
||||
get generateVoucherSchema(): ValidationChain[] {
|
||||
return [
|
||||
check('loop').exists().isNumeric().toInt(),
|
||||
check('period').exists().isNumeric().toInt(),
|
||||
check('period_interval').exists().isIn([
|
||||
'month', 'months', 'year', 'years', 'day', 'days'
|
||||
]),
|
||||
check('plan_id').exists().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific voucher validation schema.
|
||||
*/
|
||||
get specificVoucherSchema(): ValidationChain[] {
|
||||
return [
|
||||
oneOf([
|
||||
check('voucher_id').exists().isNumeric().toInt(),
|
||||
], [
|
||||
check('voucher_code').exists().isNumeric().toInt(),
|
||||
])
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Send voucher validation schema.
|
||||
*/
|
||||
get sendVoucherSchemaValidation(): ValidationChain[] {
|
||||
return [
|
||||
check('period').exists().isNumeric(),
|
||||
check('period_interval').exists().trim().escape(),
|
||||
check('plan_id').exists().isNumeric().toInt(),
|
||||
oneOf([
|
||||
check('phone_number').exists().trim().escape(),
|
||||
check('email').exists().trim().escape(),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the plan existance on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validatePlanExistance(req: Request, res: Response, next: Function) {
|
||||
const planId: number = req.body.planId || req.params.planId;
|
||||
const foundPlan = await Plan.query().findById(planId);
|
||||
|
||||
if (!foundPlan) {
|
||||
return res.status(400).send({
|
||||
erorrs: [{ type: 'PLAN.NOT.FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Valdiate the voucher existance on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function}
|
||||
*/
|
||||
async validateVoucherExistance(req: Request, res: Response, next: Function) {
|
||||
const voucherId = req.body.voucherId || req.params.voucherId;
|
||||
const foundVoucher = await Voucher.query().findById(voucherId);
|
||||
|
||||
if (!foundVoucher) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'VOUCHER.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the voucher id is disabled.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validateNotDisabledVoucher(req: Request, res: Response, next: Function) {
|
||||
const voucherId = req.params.voucherId || req.query.voucherId;
|
||||
const foundVoucher = await Voucher.query().findById(voucherId);
|
||||
|
||||
if (foundVoucher.disabled) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'VOUCHER.ALREADY.DISABLED', code: 200 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate vouchers codes with given period in bulk.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
async generateVoucher(req: Request, res: Response) {
|
||||
const { loop = 10, period, periodInterval, planId } = req.body;
|
||||
const generatedVouchers: string[] = [];
|
||||
const asyncOpers = [];
|
||||
|
||||
times(loop, () => {
|
||||
const generateOper = this.voucherService
|
||||
.generateVoucher(period, periodInterval, planId)
|
||||
.then((generatedVoucher: any) => {
|
||||
generatedVouchers.push(generatedVoucher)
|
||||
});
|
||||
asyncOpers.push(generateOper);
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
vouchers: generatedVouchers,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the given voucher on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
async disableVoucher(req: Request, res: Response) {
|
||||
const { voucherId } = req.params;
|
||||
|
||||
await this.voucherService.disableVoucher(voucherId);
|
||||
|
||||
return res.status(200).send({ voucher_id: voucherId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given voucher code on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
async deleteVoucher(req: Request, res: Response) {
|
||||
const { voucherId } = req.params;
|
||||
|
||||
await this.voucherService.deleteVoucher(voucherId);
|
||||
|
||||
return res.status(200).send({ voucher_id: voucherId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Send voucher code in the given period to the customer via email or phone number
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
async sendVoucher(req: Request, res: Response) {
|
||||
const { phoneNumber, email, period, periodInterval, planId } = req.body;
|
||||
|
||||
const voucher = await Voucher.query()
|
||||
.modify('filterActiveVoucher')
|
||||
.where('voucher_period', period)
|
||||
.where('period_interval', periodInterval)
|
||||
.where('plan_id', planId)
|
||||
.first();
|
||||
|
||||
if (!voucher) {
|
||||
return res.status(400).send({
|
||||
status: 110,
|
||||
message: 'There is no vouchers availiable right now with the given period and plan.',
|
||||
code: 'NO.AVALIABLE.VOUCHER.CODE',
|
||||
});
|
||||
}
|
||||
await this.voucherService.sendVoucherToCustomer(
|
||||
voucher.voucherCode, phoneNumber, email,
|
||||
);
|
||||
return res.status(200).send({
|
||||
status: 100,
|
||||
code: 'VOUCHER.CODE.SENT',
|
||||
message: 'The voucher has been sent to the given customer.',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Listing vouchers.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async listVouchers(req: Request, res: Response) {
|
||||
const filter: IVouchersFilter = {
|
||||
disabled: false,
|
||||
used: false,
|
||||
sent: false,
|
||||
active: false,
|
||||
...req.query,
|
||||
};
|
||||
const vouchers = await Voucher.query()
|
||||
.onBuild((builder) => {
|
||||
builder.modify('filter', filter);
|
||||
builder.orderBy('createdAt', 'ASC');
|
||||
});
|
||||
return res.status(200).send({ vouchers });
|
||||
}
|
||||
}
|
||||
22
server/src/http/controllers/Subscription/index.ts
Normal file
22
server/src/http/controllers/Subscription/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Router } from 'express'
|
||||
import { Container, Service } from 'typedi';
|
||||
import JWTAuth from '@/http/middleware/jwtAuth';
|
||||
import TenancyMiddleware from '@/http/middleware/TenancyMiddleware';
|
||||
import PaymentViaVoucherController from '@/http/controllers/Subscription/PaymentViaVoucher';
|
||||
|
||||
@Service()
|
||||
export default class SubscriptionController {
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.use(JWTAuth);
|
||||
router.use(TenancyMiddleware);
|
||||
|
||||
router.use('/voucher', Container.get(PaymentViaVoucherController).router());
|
||||
|
||||
return router;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// import OAuth2 from '@/http/controllers/OAuth2';
|
||||
import express from 'express';
|
||||
import { Container } from 'typedi';
|
||||
import Authentication from '@/http/controllers/Authentication';
|
||||
import InviteUsers from '@/http/controllers/InviteUsers';
|
||||
import Users from '@/http/controllers/Users';
|
||||
@@ -24,15 +24,24 @@ import JWTAuth from '@/http/middleware/jwtAuth';
|
||||
import TenancyMiddleware from '@/http/middleware/TenancyMiddleware';
|
||||
import Ping from '@/http/controllers/Ping';
|
||||
import Agendash from '@/http/controllers/Agendash';
|
||||
import Subscription from '@/http/controllers/Subscription';
|
||||
import VouchersController from '@/http/controllers/Subscription/Vouchers';
|
||||
|
||||
import TenantDependencyInjection from '@/http/middleware/TenantDependencyInjection';
|
||||
import SubscriptionMiddleware from '@/http/middleware/SubscriptionMiddleware';
|
||||
|
||||
export default (app) => {
|
||||
app.use('/api/auth', Authentication.router());
|
||||
app.use('/api/invite', InviteUsers.router());
|
||||
|
||||
app.use('/api/vouchers', Container.get(VouchersController).router());
|
||||
app.use('/api/subscription', Container.get(Subscription).router());
|
||||
app.use('/api/ping', Container.get(Ping).router());
|
||||
|
||||
const dashboard = express.Router();
|
||||
|
||||
dashboard.use(JWTAuth);
|
||||
dashboard.use(TenancyMiddleware);
|
||||
dashboard.use(SubscriptionMiddleware('main'));
|
||||
|
||||
dashboard.use('/api/currencies', Currencies.router());
|
||||
dashboard.use('/api/users', Users.router());
|
||||
@@ -41,7 +50,7 @@ export default (app) => {
|
||||
dashboard.use('/api/accounting', Accounting.router());
|
||||
dashboard.use('/api/views', Views.router());
|
||||
dashboard.use('/api/items', Items.router());
|
||||
dashboard.use('/api/item_categories', ItemCategories.router());
|
||||
dashboard.use('/api/item_categories', Container.get(ItemCategories));
|
||||
dashboard.use('/api/expenses', Expenses.router());
|
||||
dashboard.use('/api/financial_statements', FinancialStatements.router());
|
||||
dashboard.use('/api/options', Options.router());
|
||||
@@ -52,8 +61,7 @@ export default (app) => {
|
||||
dashboard.use('/api/resources', Resources.router());
|
||||
dashboard.use('/api/exchange_rates', ExchangeRates.router());
|
||||
dashboard.use('/api/media', Media.router());
|
||||
dashboard.use('/api/ping', Ping.router());
|
||||
|
||||
|
||||
app.use('/agendash', Agendash.router());
|
||||
app.use('/', dashboard);
|
||||
};
|
||||
|
||||
26
server/src/http/middleware/SubscriptionMiddleware.js
Normal file
26
server/src/http/middleware/SubscriptionMiddleware.js
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
export default (subscriptionSlug = 'main') => async (req, res, next) => {
|
||||
const { tenant } = req;
|
||||
|
||||
if (!tenant) {
|
||||
throw new Error('Should load `TenancyMiddlware` before this middleware.');
|
||||
}
|
||||
const subscription = await tenant
|
||||
.$relatedQuery('subscriptions')
|
||||
.modify('subscriptionBySlug', subscriptionSlug)
|
||||
.first();
|
||||
|
||||
// Validate in case there is no any already subscription.
|
||||
if (!subscription) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }],
|
||||
});
|
||||
}
|
||||
// Validate in case the subscription is inactive.
|
||||
else if (subscription.inactive()) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
|
||||
|
||||
const subscriptionObserver = (req, res, next) => {
|
||||
|
||||
};
|
||||
|
||||
export default subscriptionObserver;
|
||||
@@ -44,6 +44,7 @@ export default async (req, res, next) => {
|
||||
|
||||
req.knex = knex;
|
||||
req.organizationId = organizationId;
|
||||
req.tenant = tenant;
|
||||
req.models = {
|
||||
...Object.values(models).reduce((acc, model) => {
|
||||
if (typeof model.resource.default !== 'undefined' &&
|
||||
|
||||
13
server/src/http/middleware/TenantDependencyInjection.ts
Normal file
13
server/src/http/middleware/TenantDependencyInjection.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
|
||||
export default async (req: Request, res: Response, next: Function) => {
|
||||
const { organizationId, knex } = req;
|
||||
|
||||
if (!organizationId || !knex) {
|
||||
throw new Error('Should load `TenancyMiddleware` before this middleware.');
|
||||
}
|
||||
Container.of(`tenant-${organizationId}`).set('knex', knex);
|
||||
|
||||
next();
|
||||
};
|
||||
@@ -1,35 +1,34 @@
|
||||
import { camelCase, snakeCase } from 'lodash';
|
||||
import { Request, Response } from 'express';
|
||||
import { camelCase, snakeCase, mapKeys } from 'lodash';
|
||||
|
||||
/**
|
||||
* create a middleware to change json format from snake case to camelcase in request
|
||||
* then change back to snake case in response
|
||||
*
|
||||
*/
|
||||
export default function createMiddleware() {
|
||||
return function (req, res, next) {
|
||||
/**
|
||||
* camelize req.body
|
||||
*/
|
||||
if (req.body && typeof req.body === 'object') {
|
||||
req.body = camelCase(req.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* camelize req.query
|
||||
*/
|
||||
if (req.query && typeof req.query === 'object') {
|
||||
req.query = camelCase(req.query);
|
||||
}
|
||||
|
||||
/**
|
||||
* wrap res.json()
|
||||
*/
|
||||
const sendJson = res.json;
|
||||
|
||||
res.json = (data) => {
|
||||
return sendJson.call(res, snakeCase(data));
|
||||
}
|
||||
|
||||
return next();
|
||||
export default (req: Request, res: Response, next: Function) => {
|
||||
/**
|
||||
* camelize `req.body`
|
||||
*/
|
||||
if (req.body && typeof req.body === 'object') {
|
||||
req.body = mapKeys(req.body, (value: any, key: string) => camelCase(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* camelize `req.query`
|
||||
*/
|
||||
if (req.query && typeof req.query === 'object') {
|
||||
req.query = mapKeys(req.query, (value: any, key: string) => camelCase(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* wrap `res.json()`
|
||||
*/
|
||||
const sendJson = res.json;
|
||||
|
||||
res.json = (data: any) => {
|
||||
const mapped = mapKeys(data, (value: any, key: string) => snakeCase(key));
|
||||
return sendJson.call(res, mapped);
|
||||
};
|
||||
return next();
|
||||
};
|
||||
Reference in New Issue
Block a user