feat: remove path alias.

feat: remove Webpack and depend on nodemon.
feat: refactoring expenses.
feat: optimize system users with caching.
feat: architecture tenant optimize.
This commit is contained in:
Ahmed Bouhuolia
2020-09-15 00:51:39 +02:00
parent ad00f140d1
commit a22c8395f3
293 changed files with 3391 additions and 1637 deletions

View File

@@ -26,5 +26,5 @@
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.insertSpaces": true, "editor.insertSpaces": true,
"git.ignoreLimitWarning": true, "git.ignoreLimitWarning": true,
"god.tsconfig": "./tsconfig.json",
} }

View File

@@ -1,15 +1,15 @@
const commander = require('commander'); import commander from 'commander';
const color = require('colorette'); import color from 'colorette';
const argv = require('getopts')(process.argv.slice(2)); import argv from 'getopts'
const config = require('../config/config'); import config from '../src/config';
const { import {
initSystemKnex, initSystemKnex,
getAllSystemTenants, getAllSystemTenants,
initTenantKnex, initTenantKnex,
exit, exit,
success, success,
log, log,
} = require('./utils'); } from './utils';
// - bigcapital system:migrate:latest // - bigcapital system:migrate:latest
// - bigcapital system:migrate:rollback // - bigcapital system:migrate:rollback

View File

@@ -1,13 +1,26 @@
const Knex = require('knex'); import Knex from 'knex';
const { knexSnakeCaseMappers } = require('objection'); import { knexSnakeCaseMappers } from 'objection';
const color = require('colorette'); import color from 'colorette';
const config = require('../config/config'); import config from '../src/config';
const systemConfig = require('../config/systemKnexfile'); // import { systemKnexConfig } from '../src/config/knexConfig';
function initSystemKnex() { function initSystemKnex() {
return Knex({ return Knex({
...systemConfig['production'], client: config.system.db_client,
connection: {
host: config.system.db_host,
user: config.system.db_user,
password: config.system.db_password,
database: config.system.db_name,
charset: 'utf8',
},
migrations: {
directory: config.system.migrations_dir,
},
seeds: {
directory: config.system.seeds_dir,
},
pool: { min: 0, max: 7 },
...knexSnakeCaseMappers({ upperCase: true }), ...knexSnakeCaseMappers({ upperCase: true }),
}); });
} }
@@ -70,7 +83,7 @@ function getDeepValue(prop, obj) {
}, []); }, []);
} }
module.exports = { export {
initTenantKnex, initTenantKnex,
initSystemKnex, initSystemKnex,
getAllSystemTenants, getAllSystemTenants,

View File

@@ -1,6 +0,0 @@
import path from 'path';
import dotenv from 'dotenv';
dotenv.config({
path: path.resolve(process.cwd(), '.env.test'),
});

View File

@@ -1,24 +0,0 @@
const config = require('./config');
const configEnv = {
client: config.system.db_client,
connection: {
host: config.system.db_host,
user: config.system.db_user,
password: config.system.db_password,
database: config.system.db_name,
charset: 'utf8',
},
migrations: {
directory: config.system.migrations_dir,
},
seeds: {
directory: config.system.seeds_dir,
},
pool: { min: 0, max: 7 },
};
module.exports = {
development: configEnv,
production: configEnv,
};

11
server/nodemon.json Normal file
View File

@@ -0,0 +1,11 @@
{
"watch": [
"src",
".env"
],
"ext": "js,ts,json",
"ignore": [
"src/**/*.spec.ts"
],
"exec": "ts-node --transpile-only ./src/server.ts"
}

View File

@@ -1,15 +1,12 @@
{ {
"name": "bigcapital-server", "name": "bigcapital-server",
"version": "1.0.0", "version": "0.0.1",
"description": "", "description": "",
"main": "index.js", "main": "src/server.ts",
"scripts": { "scripts": {
"build": "webpack", "build": "webpack",
"start": "npm-run-all --parallel watch:server watch:build", "start": "cross-env NODE_PATH=./src nodemon",
"watch:build": "webpack --watch", "inspect": "cross-env NODE_PATH=./src nodemon --inspect src/server.ts"
"watch:server": "nodemon --inspect=\"9229\" \"./dist/bundle.js\" --watch \"./dist\" ",
"test": "cross-env NODE_ENV=test mocha-webpack --webpack-config webpack.config.js \"tests/**/*.test.js\"",
"test:watch": "cross-env NODE_ENV=test mocha-webpack --watch --webpack-config webpack.config.js --timeout=30000 tests/**/*.test.js"
}, },
"author": "Ahmed Bouhuolia, <a.bouhuolia@gmail.com>", "author": "Ahmed Bouhuolia, <a.bouhuolia@gmail.com>",
"license": "ISC", "license": "ISC",
@@ -29,10 +26,12 @@
"bookshelf-json-columns": "^2.1.1", "bookshelf-json-columns": "^2.1.1",
"bookshelf-modelbase": "^2.10.4", "bookshelf-modelbase": "^2.10.4",
"bookshelf-paranoia": "^0.13.1", "bookshelf-paranoia": "^0.13.1",
"compression": "^1.7.4",
"crypto-random-string": "^3.2.0", "crypto-random-string": "^3.2.0",
"csurf": "^1.10.0", "csurf": "^1.10.0",
"dotenv": "^8.1.0", "dotenv": "^8.1.0",
"errorhandler": "^1.5.1", "errorhandler": "^1.5.1",
"esm": "^3.2.25",
"event-dispatch": "^0.4.1", "event-dispatch": "^0.4.1",
"express": "^4.17.1", "express": "^4.17.1",
"express-basic-auth": "^1.2.0", "express-basic-auth": "^1.2.0",
@@ -66,14 +65,7 @@
"winston": "^3.2.1" "winston": "^3.2.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.5.5",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/polyfill": "^7.4.4",
"@babel/preset-env": "^7.5.5",
"@babel/runtime": "^7.5.5",
"@types/lodash": "^4.14.158", "@types/lodash": "^4.14.158",
"babel-loader": "^8.0.6",
"chai": "^4.2.0", "chai": "^4.2.0",
"chai-http": "^4.3.0", "chai-http": "^4.3.0",
"chai-things": "^0.2.0", "chai-things": "^0.2.0",
@@ -90,15 +82,11 @@
"getopts": "^2.2.5", "getopts": "^2.2.5",
"knex-factory": "0.0.6", "knex-factory": "0.0.6",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"mocha-webpack": "^2.0.0-beta.0", "module-alias": "^2.2.2",
"npm-run-all": "^4.1.5",
"nyc": "^14.1.1", "nyc": "^14.1.1",
"sinon": "^7.4.2", "sinon": "^7.4.2",
"ts-loader": "^8.0.1", "ts-node": "^9.0.0",
"typedi": "^0.8.0", "typedi": "^0.8.0",
"typescript": "^3.9.7", "typescript": "^3.9.7"
"webpack": "^4.0.0",
"webpack-cli": "^3.3.7",
"webpack-node-externals": "^1.7.2"
} }
} }

View File

@@ -1,7 +1,7 @@
import { Service } from 'typedi'; import { Service } from 'typedi';
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
@Service() @Service()
export default class AccountsTypesController extends BaseController{ export default class AccountsTypesController extends BaseController{

View File

@@ -2,19 +2,19 @@ import { check, query, validationResult, param } from 'express-validator';
import express from 'express'; import express from 'express';
import { difference } from 'lodash'; import { difference } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry'; import JournalEntry from 'services/Accounting/JournalEntry';
import { import {
mapViewRolesToConditionals, mapViewRolesToConditionals,
mapFilterRolesToDynamicFilter, mapFilterRolesToDynamicFilter,
} from '@/lib/ViewRolesBuilder'; } from 'lib/ViewRolesBuilder';
import { import {
DynamicFilter, DynamicFilter,
DynamicFilterSortBy, DynamicFilterSortBy,
DynamicFilterViews, DynamicFilterViews,
DynamicFilterFilterRoles, DynamicFilterFilterRoles,
} from '@/lib/DynamicFilter'; } from 'lib/DynamicFilter';
export default { export default {
/** /**

View File

@@ -1,22 +1,22 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { check, validationResult, param, query } from 'express-validator'; import { check, validationResult, param, query } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import { import {
mapViewRolesToConditionals, mapViewRolesToConditionals,
mapFilterRolesToDynamicFilter, mapFilterRolesToDynamicFilter,
} from '@/lib/ViewRolesBuilder'; } from 'lib/ViewRolesBuilder';
import { import {
DynamicFilter, DynamicFilter,
DynamicFilterSortBy, DynamicFilterSortBy,
DynamicFilterViews, DynamicFilterViews,
DynamicFilterFilterRoles, DynamicFilterFilterRoles,
} from '@/lib/DynamicFilter'; } from 'lib/DynamicFilter';
import BaseController from './BaseController'; import BaseController from './BaseController';
import { IAccountDTO, IAccount } from '@/interfaces'; import { IAccountDTO, IAccount } from 'interfaces';
import { ServiceError } from '@/exceptions'; import { ServiceError } from 'exceptions';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from 'services/Accounts/AccountsService';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
@Service() @Service()
@@ -265,8 +265,6 @@ export default class AccountsController extends BaseController{
await this.accountsService.activateAccount(tenantId, accountId, true); await this.accountsService.activateAccount(tenantId, accountId, true);
return res.status(200).send({ id: accountId }); return res.status(200).send({ id: accountId });
} catch (error) { } catch (error) {
console.log(error);
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
this.transformServiceErrorToResponse(res, error); this.transformServiceErrorToResponse(res, error);
} }

View File

@@ -3,7 +3,7 @@ import { Router } from 'express'
import basicAuth from 'express-basic-auth'; import basicAuth from 'express-basic-auth';
import agendash from 'agendash' import agendash from 'agendash'
import { Container } from 'typedi' import { Container } from 'typedi'
import config from '@/../config/config' import config from 'config'
export default class AgendashController { export default class AgendashController {

View File

@@ -1,12 +1,12 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import { check, ValidationChain } from 'express-validator'; import { check, ValidationChain } from 'express-validator';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import AuthenticationService from '@/services/Authentication'; import AuthenticationService from 'services/Authentication';
import { IUserOTD, ISystemUser, IRegisterOTD } from '@/interfaces'; import { IUserOTD, ISystemUser, IRegisterOTD } from 'interfaces';
import { ServiceError, ServiceErrors } from "@/exceptions"; import { ServiceError, ServiceErrors } from "exceptions";
@Service() @Service()
export default class AuthenticationController extends BaseController{ export default class AuthenticationController extends BaseController{
@@ -22,25 +22,25 @@ export default class AuthenticationController extends BaseController{
router.post( router.post(
'/login', '/login',
this.loginSchema, this.loginSchema,
validateMiddleware, this.validationResult,
asyncMiddleware(this.login.bind(this)) asyncMiddleware(this.login.bind(this))
); );
router.post( router.post(
'/register', '/register',
this.registerSchema, this.registerSchema,
validateMiddleware, this.validationResult,
asyncMiddleware(this.register.bind(this)) asyncMiddleware(this.register.bind(this))
); );
router.post( router.post(
'/send_reset_password', '/send_reset_password',
this.sendResetPasswordSchema, this.sendResetPasswordSchema,
validateMiddleware, this.validationResult,
asyncMiddleware(this.sendResetPassword.bind(this)) asyncMiddleware(this.sendResetPassword.bind(this))
); );
router.post( router.post(
'/reset/:token', '/reset/:token',
this.resetPasswordSchema, this.resetPasswordSchema,
validateMiddleware, this.validationResult,
asyncMiddleware(this.resetPassword.bind(this)) asyncMiddleware(this.resetPassword.bind(this))
); );
return router; return router;
@@ -64,7 +64,7 @@ export default class AuthenticationController extends BaseController{
check('organization_name').exists().trim().escape(), check('organization_name').exists().trim().escape(),
check('first_name').exists().trim().escape(), check('first_name').exists().trim().escape(),
check('last_name').exists().trim().escape(), check('last_name').exists().trim().escape(),
check('email').exists().trim().escape(), check('email').exists().isEmail().trim().escape(),
check('phone_number').exists().trim().escape(), check('phone_number').exists().trim().escape(),
check('password').exists().trim().escape(), check('password').exists().trim().escape(),
check('country').exists().trim().escape(), check('country').exists().trim().escape(),
@@ -105,11 +105,11 @@ export default class AuthenticationController extends BaseController{
const userDTO: IUserOTD = this.matchedBodyData(req); const userDTO: IUserOTD = this.matchedBodyData(req);
try { try {
const { token, user } = await this.authService.signIn( const { token, user, tenant } = await this.authService.signIn(
userDTO.crediential, userDTO.crediential,
userDTO.password userDTO.password
); );
return res.status(200).send({ token, user }); return res.status(200).send({ token, user, tenant });
} catch (error) { } catch (error) {
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
if (['invalid_details', 'invalid_password'].indexOf(error.errorType) !== -1) { if (['invalid_details', 'invalid_password'].indexOf(error.errorType) !== -1) {
@@ -123,7 +123,7 @@ export default class AuthenticationController extends BaseController{
}); });
} }
} }
next(); next(error);
} }
} }
@@ -157,7 +157,7 @@ export default class AuthenticationController extends BaseController{
return res.boom.badRequest(null, { errors: errorReasons }); return res.boom.badRequest(null, { errors: errorReasons });
} }
} }
next(); next(error);
} }
} }
@@ -183,7 +183,7 @@ export default class AuthenticationController extends BaseController{
}); });
} }
} }
next(); next(error);
} }
} }
@@ -192,7 +192,7 @@ export default class AuthenticationController extends BaseController{
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
async resetPassword(req: Request, res: Response) { async resetPassword(req: Request, res: Response, next: Function) {
const { token } = req.params; const { token } = req.params;
const { password } = req.body; const { password } = req.body;
@@ -214,7 +214,8 @@ export default class AuthenticationController extends BaseController{
errors: [{ type: 'USER_NOT_FOUND', code: 120 }], errors: [{ type: 'USER_NOT_FOUND', code: 120 }],
}); });
} }
} }
next(error);
} }
} }
}; };

View File

@@ -1,16 +1,17 @@
import { Response, Request } from 'express'; import { Response, Request } from 'express';
import { matchedData, validationResult } from "express-validator"; import { matchedData, validationResult } from "express-validator";
import { mapKeys, camelCase, omit } from "lodash"; import { camelCase, omit } from "lodash";
import { mapKeysDeep } from 'utils'
export default class BaseController { export default class BaseController {
matchedBodyData(req: Request, options: any) { matchedBodyData(req: Request, options: any = {}) {
const data = matchedData(req, { const data = matchedData(req, {
locations: ['body'], locations: ['body'],
includeOptionals: true, includeOptionals: true,
...omit(options, ['locations']), // override any propery except locations. ...omit(options, ['locations']), // override any propery except locations.
}); });
return mapKeys(data, (v, k) => camelCase(k)); return mapKeysDeep(data, (v, k) => camelCase(k));
} }
validationResult(req: Request, res: Response, next: NextFunction) { validationResult(req: Request, res: Response, next: NextFunction) {

View File

@@ -1,5 +1,5 @@
import { check, param, query } from 'express-validator'; import { check, param, query } from 'express-validator';
import BaseController from "@/http/controllers/BaseController"; import BaseController from "api/controllers/BaseController";
export default class ContactsController extends BaseController { export default class ContactsController extends BaseController {

View File

@@ -1,11 +1,11 @@
import { Request, Response, Router, NextFunction } from 'express'; import { Request, Response, Router, NextFunction } from 'express';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { check } from 'express-validator'; import { check } from 'express-validator';
import ContactsController from '@/http/controllers/Contacts/Contacts'; import ContactsController from 'api/controllers/Contacts/Contacts';
import CustomersService from '@/services/Contacts/CustomersService'; import CustomersService from 'services/Contacts/CustomersService';
import { ServiceError } from '@/exceptions'; import { ServiceError } from 'exceptions';
import { ICustomerNewDTO, ICustomerEditDTO } from '@/interfaces'; import { ICustomerNewDTO, ICustomerEditDTO } from 'interfaces';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
@Service() @Service()
export default class CustomersController extends ContactsController { export default class CustomersController extends ContactsController {
@@ -95,7 +95,7 @@ export default class CustomersController extends ContactsController {
const { id: contactId } = req.params; const { id: contactId } = req.params;
try { try {
await this.customersService.editCustomer(tenantId, contactId, contactDTO;) await this.customersService.editCustomer(tenantId, contactId, contactDTO);
return res.status(200).send({ id: contactId }); return res.status(200).send({ id: contactId });
} catch (error) { } catch (error) {
if (error instanceof ServiceError) { if (error instanceof ServiceError) {

View File

@@ -1,11 +1,11 @@
import { Request, Response, Router, NextFunction } from 'express'; import { Request, Response, Router, NextFunction } from 'express';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { check } from 'express-validator'; import { check } from 'express-validator';
import ContactsController from '@/http/controllers/Contacts/Contacts'; import ContactsController from 'api/controllers/Contacts/Contacts';
import VendorsService from '@/services/Contacts/VendorsService'; import VendorsService from 'services/Contacts/VendorsService';
import { ServiceError } from '@/exceptions'; import { ServiceError } from 'exceptions';
import { IVendorNewDTO, IVendorEditDTO } from '@/interfaces'; import { IVendorNewDTO, IVendorEditDTO } from 'interfaces';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
@Service() @Service()
export default class VendorsController extends ContactsController { export default class VendorsController extends ContactsController {
@@ -94,7 +94,7 @@ export default class VendorsController extends ContactsController {
const { id: contactId } = req.params; const { id: contactId } = req.params;
try { try {
await this.vendorsService.editVendor(tenantId, contactId, contactDTO;) await this.vendorsService.editVendor(tenantId, contactId, contactDTO);
return res.status(200).send({ id: contactId }); return res.status(200).send({ id: contactId });
} catch (error) { } catch (error) {
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
@@ -146,11 +146,11 @@ export default class VendorsController extends ContactsController {
*/ */
async getVendor(req: Request, res: Response, next: NextFunction) { async getVendor(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const { id: contactId } = req.params; const { id: vendorId } = req.params;
try { try {
const contact = await this.vendorsService.getVendor(tenantId, contactId) const vendor = await this.vendorsService.getVendor(tenantId, vendorId)
return res.status(200).send({ contact }); return res.status(200).send({ vendor });
} catch (error) { } catch (error) {
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
if (error.errorType === 'contact_not_found') { if (error.errorType === 'contact_not_found') {

View File

@@ -1,6 +1,6 @@
import express from 'express'; import express from 'express';
import { check, param, validationResult } from 'express-validator'; import { check, param, validationResult } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
export default { export default {
/** /**

View File

@@ -7,7 +7,7 @@ import {
} from 'express-validator'; } from 'express-validator';
import moment from 'moment'; import moment from 'moment';
import { difference } from 'lodash'; import { difference } from 'lodash';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
export default { export default {
/** /**

View File

@@ -0,0 +1,265 @@
import { Inject, Service } from "typedi";
import { check, param, query } from 'express-validator';
import { Router, Request, Response, NextFunction } from 'express';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
import BaseController from "api/controllers/BaseController";
import ExpensesService from "services/Expenses/ExpensesService";
import { IExpenseDTO } from 'interfaces';
import { ServiceError } from "exceptions";
@Service()
export default class ExpensesController extends BaseController {
@Inject()
expensesService: ExpensesService;
/**
* Express router.
*/
router() {
const router = Router();
router.post(
'/', [
...this.expenseDTOSchema,
],
this.validationResult,
asyncMiddleware(this.newExpense.bind(this))
);
router.post('/publish', [
...this.bulkSelectSchema,
],
this.bulkPublishExpenses.bind(this)
);
router.post(
'/:id/publish', [
...this.expenseParamSchema,
],
this.validationResult,
asyncMiddleware(this.publishExpense.bind(this))
);
router.post(
'/:id', [
...this.expenseDTOSchema,
...this.expenseParamSchema,
],
this.validationResult,
asyncMiddleware(this.editExpense.bind(this)),
);
router.delete(
'/:id', [
...this.expenseParamSchema,
],
this.validationResult,
asyncMiddleware(this.deleteExpense.bind(this))
);
router.delete('/', [
...this.bulkSelectSchema,
],
this.validationResult,
asyncMiddleware(this.bulkDeleteExpenses.bind(this))
);
return router;
}
/**
* Expense DTO schema.
*/
get expenseDTOSchema() {
return [
check('reference_no').optional().trim().escape().isLength({ max: 255 }),
check('payment_date').exists().isISO8601(),
check('payment_account_id').exists().isNumeric().toInt(),
check('description').optional(),
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(),
check('categories.*.amount')
.optional({ nullable: true })
.isNumeric()
.isDecimal()
.isFloat({ max: 9999999999.999 }) // 13, 3
.toFloat(),
check('categories.*.description').optional().trim().escape().isLength({
max: 255,
}),
];
}
/**
* Expense param schema.
*/
get expenseParamSchema() {
return [
param('id').exists().isNumeric().toInt(),
];
}
get bulkSelectSchema() {
return [
query('ids').isArray({ min: 1 }),
query('ids.*').isNumeric().toInt(),
];
}
/**
* Creates a new expense on
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async newExpense(req: Request, res: Response, next: NextFunction) {
const expenseDTO: IExpenseDTO = this.matchedBodyData(req);
const { tenantId, user } = req;
try {
const expense = await this.expensesService.newExpense(tenantId, expenseDTO, user);
return res.status(200).send({ id: expense.id });
} catch (error) {
if (error instanceof ServiceError) {
this.serviceErrorsTransformer(res, error);
}
next(error);
}
}
/**
* Edits details of the given expense.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async editExpense(req: Request, res: Response, next: NextFunction) {
const { id: expenseId } = req.params;
const expenseDTO: IExpenseDTO = this.matchedBodyData(req);
const { tenantId, user } = req;
try {
await this.expensesService.editExpense(tenantId, expenseId, expenseDTO, user);
return res.status(200).send({ id: expenseId });
} catch (error) {
if (error instanceof ServiceError) {
this.serviceErrorsTransformer(res, error);
}
next(error)
}
}
/**
* Deletes the given expense.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async deleteExpense(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: expenseId } = req.params;
try {
await this.expensesService.deleteExpense(tenantId, expenseId)
return res.status(200).send({ id: expenseId });
} catch (error) {
if (error instanceof ServiceError) {
this.serviceErrorsTransformer(res, error);
}
next(error)
}
}
/**
* Publishs the given expense.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async publishExpense(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: expenseId } = req.params;
try {
await this.expensesService.publishExpense(tenantId, expenseId)
return res.status(200).send({ });
} catch (error) {
if (error instanceof ServiceError) {
this.serviceErrorsTransformer(req, error);
}
next(error);
}
}
/**
* Deletes the expenses in bulk.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async bulkDeleteExpenses(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { ids: expensesIds } = req.params;
try {
await this.expensesService.deleteBulkExpenses(tenantId, expensesIds);
return res.status(200).send({ ids: expensesIds });
} catch (error) {
if (error instanceof ServiceError) {
this.serviceErrorsTransformer(req, error);
}
next(error);
}
}
async bulkPublishExpenses(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
try {
await this.expensesService.publishBulkExpenses(tenantId,);
return res.status(200).send({});
} catch (error) {
if (error instanceof ServiceError) {
this.serviceErrorsTransformer(req, error);
}
next(error);
}
}
/**
* Transform service errors to api response errors.
* @param {Response} res
* @param {ServiceError} error
*/
serviceErrorsTransformer(res, error: ServiceError) {
if (error.errorType === 'expense_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'EXPENSE_NOT_FOUND' }],
});
}
if (error.errorType === 'total_amount_equals_zero') {
return res.boom.badRequest(null, {
errors: [{ type: 'TOTAL.AMOUNT.EQUALS.ZERO' }],
});
}
if (error.errorType === 'payment_account_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', }],
});
}
if (error.errorType === 'some_expenses_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'SOME.EXPENSE.ACCOUNTS.NOT.FOUND', code: 200 }]
})
}
if (error.errorType === 'payment_account_has_invalid_type') {
return res.boom.badRequest(null, {
errors: [{ type: 'PAYMENT.ACCOUNT.HAS.INVALID.TYPE' }],
});
}
if (error.errorType === 'expenses_account_has_invalid_type') {
return res.boom.badRequest(null, {
errors: [{ type: 'EXPENSES.ACCOUNT.HAS.INVALID.TYPE' }]
});
}
}
}

View File

@@ -1,7 +1,7 @@
import express from 'express'; import express from 'express';
import { check, param, validationResult } from 'express-validator'; import { check, param, validationResult } from 'express-validator';
import ResourceField from '@/models/ResourceField'; import ResourceField from 'models/ResourceField';
import Resource from '@/models/Resource'; import Resource from 'models/Resource';
import asyncMiddleware from '../middleware/asyncMiddleware'; import asyncMiddleware from '../middleware/asyncMiddleware';
/** /**

View File

@@ -1,7 +1,7 @@
import moment from 'moment'; import moment from 'moment';
import { validationResult } from 'express-validator'; import { validationResult } from 'express-validator';
import { omit, reverse } from 'lodash'; import { omit, reverse } from 'lodash';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
export default class AgingReport extends BaseController{ export default class AgingReport extends BaseController{

View File

@@ -2,11 +2,11 @@ import express from 'express';
import { query, validationResult } from 'express-validator'; import { query, validationResult } from 'express-validator';
import moment from 'moment'; import moment from 'moment';
import { pick, omit, sumBy } from 'lodash'; import { pick, omit, sumBy } from 'lodash';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import { dateRangeCollection, itemsStartWith, getTotalDeep } from '@/utils'; import { dateRangeCollection, itemsStartWith, getTotalDeep } from 'utils';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import { formatNumberClosure } from './FinancialStatementMixin'; import { formatNumberClosure } from './FinancialStatementMixin';
import BalanceSheetStructure from '@/data/BalanceSheetStructure'; import BalanceSheetStructure from 'data/BalanceSheetStructure';
export default { export default {
/** /**

View File

@@ -2,10 +2,10 @@ import express from 'express';
import { query, validationResult } from 'express-validator'; import { query, validationResult } from 'express-validator';
import moment from 'moment'; import moment from 'moment';
import { pick, difference } from 'lodash'; import { pick, difference } from 'lodash';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import { formatNumberClosure } from './FinancialStatementMixin'; import { formatNumberClosure } from './FinancialStatementMixin';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import DependencyGraph from '@/lib/DependencyGraph'; import DependencyGraph from 'lib/DependencyGraph';
export default { export default {
/** /**

View File

@@ -2,8 +2,8 @@ import express from 'express';
import { query, oneOf, validationResult } from 'express-validator'; import { query, oneOf, validationResult } from 'express-validator';
import moment from 'moment'; import moment from 'moment';
import { groupBy } from 'lodash'; import { groupBy } from 'lodash';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import { formatNumberClosure } from './FinancialStatementMixin'; import { formatNumberClosure } from './FinancialStatementMixin';

View File

@@ -1,9 +1,9 @@
import express from 'express'; import express from 'express';
import { query } from 'express-validator'; import { query } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import AgingReport from '@/http/controllers/FinancialStatements/AgingReport'; import AgingReport from 'api/controllers/FinancialStatements/AgingReport';
import moment from 'moment'; import moment from 'moment';
export default class PayableAgingSummary extends AgingReport { export default class PayableAgingSummary extends AgingReport {

View File

@@ -2,9 +2,9 @@ import express from 'express';
import { query, oneOf, validationResult } from 'express-validator'; import { query, oneOf, validationResult } from 'express-validator';
import moment from 'moment'; import moment from 'moment';
import { pick, sumBy } from 'lodash'; import { pick, sumBy } from 'lodash';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import { dateRangeCollection } from '@/utils'; import { dateRangeCollection } from 'utils';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import { formatNumberClosure } from './FinancialStatementMixin'; import { formatNumberClosure } from './FinancialStatementMixin';
export default { export default {

View File

@@ -1,9 +1,9 @@
import express from 'express'; import express from 'express';
import { query, oneOf } from 'express-validator'; import { query, oneOf } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import AgingReport from '@/http/controllers/FinancialStatements/AgingReport'; import AgingReport from 'api/controllers/FinancialStatements/AgingReport';
import moment from 'moment'; import moment from 'moment';
export default class ReceivableAgingSummary extends AgingReport { export default class ReceivableAgingSummary extends AgingReport {

View File

@@ -1,9 +1,9 @@
import express from 'express'; import express from 'express';
import { query, validationResult } from 'express-validator'; import { query, validationResult } from 'express-validator';
import moment from 'moment'; import moment from 'moment';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import DependencyGraph from '@/lib/DependencyGraph'; import DependencyGraph from 'lib/DependencyGraph';
import { formatNumberClosure }from './FinancialStatementMixin'; import { formatNumberClosure }from './FinancialStatementMixin';
export default { export default {

View File

@@ -5,9 +5,9 @@ import {
body, body,
param, param,
} from 'express-validator'; } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import InviteUserService from '@/services/InviteUsers'; import InviteUserService from 'services/InviteUsers';
import { ServiceErrors, ServiceError } from '@/exceptions'; import { ServiceErrors, ServiceError } from 'exceptions';
import BaseController from './BaseController'; import BaseController from './BaseController';
@Service() @Service()
@@ -24,6 +24,7 @@ export default class InviteUsersController extends BaseController {
router.post('/send', [ router.post('/send', [
body('email').exists().trim().escape(), body('email').exists().trim().escape(),
], ],
this.validationResult,
asyncMiddleware(this.sendInvite.bind(this)), asyncMiddleware(this.sendInvite.bind(this)),
); );
return router; return router;
@@ -38,12 +39,15 @@ export default class InviteUsersController extends BaseController {
router.post('/accept/:token', [ router.post('/accept/:token', [
...this.inviteUserDTO, ...this.inviteUserDTO,
], ],
asyncMiddleware(this.accept.bind(this))); this.validationResult,
asyncMiddleware(this.accept.bind(this))
);
router.get('/invited/:token', [ router.get('/invited/:token', [
param('token').exists().trim().escape(), param('token').exists().trim().escape(),
], ],
asyncMiddleware(this.invited.bind(this))); this.validationResult,
asyncMiddleware(this.invited.bind(this))
);
return router; return router;
} }
@@ -80,7 +84,6 @@ export default class InviteUsersController extends BaseController {
message: 'The invite has been sent to the given email.', message: 'The invite has been sent to the given email.',
}) })
} catch (error) { } catch (error) {
console.log(error);
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
if (error.errorType === 'email_already_invited') { if (error.errorType === 'email_already_invited') {
return res.status(400).send({ return res.status(400).send({

View File

@@ -6,18 +6,18 @@ import {
} from 'express-validator'; } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import { Service } from 'typedi'; import { Service } from 'typedi';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import { import {
DynamicFilter, DynamicFilter,
DynamicFilterSortBy, DynamicFilterSortBy,
DynamicFilterFilterRoles, DynamicFilterFilterRoles,
} from '@/lib/DynamicFilter'; } from 'lib/DynamicFilter';
import { import {
mapFilterRolesToDynamicFilter, mapFilterRolesToDynamicFilter,
} from '@/lib/ViewRolesBuilder'; } from 'lib/ViewRolesBuilder';
import { IItemCategory, IItemCategoryOTD } from '@/interfaces'; import { IItemCategory, IItemCategoryOTD } from 'interfaces';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
@Service() @Service()
export default class ItemsCategoriesController extends BaseController { export default class ItemsCategoriesController extends BaseController {

View File

@@ -1,12 +1,12 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { Router, Request, Response } from 'express'; import { Router, Request, Response } from 'express';
import { check, param, query, ValidationChain, matchedData } from 'express-validator'; import { check, param, query, ValidationChain, matchedData } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import ItemsService from '@/services/Items/ItemsService'; import ItemsService from 'services/Items/ItemsService';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from 'services/DynamicListing/DynamicListing';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from 'services/DynamicListing/DynamicListingBuilder';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing'; import { dynamicListingErrorsToResponse } from 'services/DynamicListing/hasDynamicListing';
@Service() @Service()
export default class ItemsController { export default class ItemsController {

View File

@@ -8,7 +8,7 @@ import {
import Container from 'typedi'; import Container from 'typedi';
import fs from 'fs'; import fs from 'fs';
import { difference } from 'lodash'; import { difference } from 'lodash';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
const fsPromises = fs.promises; const fsPromises = fs.promises;

View File

@@ -0,0 +1,114 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response } from 'express';
import asyncMiddleware from "api/middleware/asyncMiddleware";
import JWTAuth from 'api/middleware/jwtAuth';
import TenancyMiddleware from 'api/middleware/TenancyMiddleware';
import SubscriptionMiddleware from 'api/middleware/SubscriptionMiddleware';
import AttachCurrentTenantUser from 'api/middleware/AttachCurrentTenantUser';
import OrganizationService from 'services/Organization';
import { ServiceError } from 'exceptions';
import BaseController from 'api/controllers/BaseController';
@Service()
export default class OrganizationController extends BaseController{
@Inject()
organizationService: OrganizationService;
/**
* Router constructor.
*/
router() {
const router = Router();
// Should before build tenant database the user be authorized and
// most important than that, should be subscribed to any plan.
router.use(JWTAuth);
router.use(AttachCurrentTenantUser);
router.use(TenancyMiddleware);
router.use(SubscriptionMiddleware('main'));
router.post(
'/build',
asyncMiddleware(this.build.bind(this))
);
router.post(
'/seed',
asyncMiddleware(this.seed.bind(this)),
);
return router;
}
/**
* Builds tenant database and migrate database schema.
* @param {Request} req - Express request.
* @param {Response} res - Express response.
* @param {NextFunction} next
*/
async build(req: Request, res: Response, next: Function) {
const { organizationId } = req.tenant;
try {
await this.organizationService.build(organizationId);
return res.status(200).send({
type: 'success',
code: 'ORGANIZATION.DATABASE.INITIALIZED',
message: 'The organization database has been initialized.'
});
} catch (error) {
if (error instanceof ServiceError) {
if (error.errorType === 'tenant_not_found') {
return res.status(400).send({
errors: [{ type: 'TENANT.NOT.FOUND', code: 100 }],
});
}
if (error.errorType === 'tenant_already_initialized') {
return res.status(400).send({
errors: [{ type: 'TENANT.DATABASE.ALREADY.BUILT', code: 200 }],
});
}
}
console.log(error);
next(error);
}
}
/**
* Seeds initial data to tenant database.
* @param req
* @param res
* @param next
*/
async seed(req: Request, res: Response, next: Function) {
const { organizationId } = req.tenant;
try {
await this.organizationService.seed(organizationId);
return res.status(200).send({
type: 'success',
code: 'ORGANIZATION.DATABASE.SEED',
message: 'The organization database has been seeded.'
});
} catch (error) {
if (error instanceof ServiceError) {
if (error.errorType === 'tenant_not_found') {
return res.status(400).send({
errors: [{ type: 'TENANT.NOT.FOUND', code: 100 }],
});
}
if (error.errorType === 'tenant_seeded') {
return res.status(400).send({
errors: [{ type: 'TENANT.DATABASE.ALREADY.SEEDED', code: 200 }],
});
}
if (error.errorType === 'tenant_db_not_built') {
return res.status(400).send({
errors: [{ type: 'TENANT.DATABASE.NOT.BUILT', code: 300 }],
});
}
}
next(error);
}
}
}

View File

@@ -1,4 +1,6 @@
import { Router, Request, Response } from 'express'; import { Router, Request, Response } from 'express';
import MomentFormat from 'lib/MomentFormats';
import moment from 'moment';
export default class Ping { export default class Ping {
/** /**

View File

@@ -2,16 +2,16 @@ import { Router, Request, Response } from 'express';
import { check, param, query, matchedData } from 'express-validator'; import { check, param, query, matchedData } from 'express-validator';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { difference } from 'lodash'; import { difference } from 'lodash';
import { BillOTD } from '@/interfaces'; import { BillOTD } from 'interfaces';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import BillsService from '@/services/Purchases/Bills'; import BillsService from 'services/Purchases/Bills';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
import ItemsService from '@/services/Items/ItemsService'; import ItemsService from 'services/Items/ItemsService';
import TenancyService from '@/services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from 'services/DynamicListing/DynamicListingBuilder';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from 'services/DynamicListing/DynamicListing';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/HasDynamicListing'; import { dynamicListingErrorsToResponse } from 'services/DynamicListing/HasDynamicListing';
@Service() @Service()
export default class BillsController extends BaseController { export default class BillsController extends BaseController {

View File

@@ -3,14 +3,14 @@ import { Router } from 'express';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { check, param, query, ValidationChain, matchedData } from 'express-validator'; import { check, param, query, ValidationChain, matchedData } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
import BillPaymentsService from '@/services/Purchases/BillPayments'; import BillPaymentsService from 'services/Purchases/BillPayments';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from 'services/Accounts/AccountsService';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from 'services/DynamicListing/DynamicListingBuilder';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from 'services/DynamicListing/DynamicListing';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing'; import { dynamicListingErrorsToResponse } from 'services/DynamicListing/hasDynamicListing';
/** /**
* Bills payments controller. * Bills payments controller.

View File

@@ -1,7 +1,7 @@
import express from 'express'; import express from 'express';
import { Container } from 'typedi'; import { Container } from 'typedi';
import Bills from '@/http/controllers/Purchases/Bills' import Bills from 'api/controllers/Purchases/Bills'
import BillPayments from '@/http/controllers/Purchases/BillsPayments'; import BillPayments from 'api/controllers/Purchases/BillsPayments';
export default { export default {

View File

@@ -3,7 +3,7 @@ import {
param, param,
query, query,
} from 'express-validator'; } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
export default { export default {
/** /**

View File

@@ -2,17 +2,16 @@ import { Router, Request, Response } from 'express';
import { check, param, query, ValidationChain, matchedData } from 'express-validator'; import { check, param, query, ValidationChain, matchedData } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { IPaymentReceive, IPaymentReceiveOTD } from '@/interfaces'; import { IPaymentReceive, IPaymentReceiveOTD } from 'interfaces';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import PaymentReceiveService from '@/services/Sales/PaymentsReceives'; import PaymentReceiveService from 'services/Sales/PaymentsReceives';
import CustomersService from '@/services/Customers/CustomersService'; import SaleInvoiceService from 'services/Sales/SalesInvoices';
import SaleInvoiceService from '@/services/Sales/SalesInvoices'; import AccountsService from 'services/Accounts/AccountsService';
import AccountsService from '@/services/Accounts/AccountsService'; import DynamicListing from 'services/DynamicListing/DynamicListing';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListingBuilder from 'services/DynamicListing/DynamicListingBuilder';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import { dynamicListingErrorsToResponse } from 'services/DynamicListing/hasDynamicListing';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing';
/** /**
* Payments receives controller. * Payments receives controller.
@@ -23,9 +22,6 @@ export default class PaymentReceivesController extends BaseController {
@Inject() @Inject()
paymentReceiveService: PaymentReceiveService; paymentReceiveService: PaymentReceiveService;
@Inject()
customersService: CustomersService;
@Inject() @Inject()
accountsService: AccountsService; accountsService: AccountsService;

View File

@@ -1,15 +1,14 @@
import { Router, Request, Response } from 'express'; import { Router, Request, Response } from 'express';
import { check, param, query, matchedData } from 'express-validator'; import { check, param, query, matchedData } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { ISaleEstimate, ISaleEstimateOTD } from '@/interfaces'; import { ISaleEstimate, ISaleEstimateOTD } from 'interfaces';
import BaseController from '@/http/controllers/BaseController' import BaseController from 'api/controllers/BaseController'
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import CustomersService from '@/services/Customers/CustomersService'; import SaleEstimateService from 'services/Sales/SalesEstimate';
import SaleEstimateService from '@/services/Sales/SalesEstimate'; import ItemsService from 'services/Items/ItemsService';
import ItemsService from '@/services/Items/ItemsService'; import DynamicListingBuilder from 'services/DynamicListing/DynamicListingBuilder';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListing from 'services/DynamicListing/DynamicListing';
import DynamicListing from '@/services/DynamicListing/DynamicListing';
@Service() @Service()
export default class SalesEstimatesController extends BaseController { export default class SalesEstimatesController extends BaseController {
@@ -19,9 +18,6 @@ export default class SalesEstimatesController extends BaseController {
@Inject() @Inject()
itemsService: ItemsService; itemsService: ItemsService;
@Inject()
customersService: CustomersService;
/** /**
* Router constructor. * Router constructor.
*/ */

View File

@@ -3,14 +3,14 @@ import { check, param, query, matchedData } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import { raw } from 'objection'; import { raw } from 'objection';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import SaleInvoiceService from '@/services/Sales/SalesInvoices'; import SaleInvoiceService from 'services/Sales/SalesInvoices';
import ItemsService from '@/services/Items/ItemsService'; import ItemsService from 'services/Items/ItemsService';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from 'services/DynamicListing/DynamicListing';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from 'services/DynamicListing/DynamicListingBuilder';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing'; import { dynamicListingErrorsToResponse } from 'services/DynamicListing/hasDynamicListing';
import { ISaleInvoiceOTD } from '@/interfaces'; import { ISaleInvoiceOTD } from 'interfaces';
@Service() @Service()
export default class SaleInvoicesController { export default class SaleInvoicesController {

View File

@@ -1,16 +1,16 @@
import { Router, Request, Response } from 'express'; import { Router, Request, Response } from 'express';
import { check, param, query, matchedData } from 'express-validator'; import { check, param, query, matchedData } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from 'services/Accounts/AccountsService';
import ItemsService from '@/services/Items/ItemsService'; import ItemsService from 'services/Items/ItemsService';
import SaleReceiptService from '@/services/Sales/SalesReceipts'; import SaleReceiptService from 'services/Sales/SalesReceipts';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from 'services/DynamicListing/DynamicListingBuilder';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from 'services/DynamicListing/DynamicListing';
import { import {
dynamicListingErrorsToResponse dynamicListingErrorsToResponse
} from '@/services/DynamicListing/HasDynamicListing'; } from 'services/DynamicListing/HasDynamicListing';
@Service() @Service()
export default class SalesReceiptsController { export default class SalesReceiptsController {

View File

@@ -1,9 +1,9 @@
import { Router, Request, Response } from 'express'; import { Router, Request, Response } from 'express';
import { body, query, validationResult } from 'express-validator'; import { body, query, validationResult } from 'express-validator';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { IOptionDTO, IOptionsDTO } from '@/interfaces'; import { IOptionDTO, IOptionsDTO } from 'interfaces';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
export default class SettingsController extends BaseController{ export default class SettingsController extends BaseController{
/** /**

View File

@@ -2,13 +2,13 @@ import { Service, Inject } from 'typedi';
import { Router, Request, Response } from 'express' import { Router, Request, Response } from 'express'
import { check, oneOf, ValidationChain } from 'express-validator'; import { check, oneOf, ValidationChain } from 'express-validator';
import basicAuth from 'express-basic-auth'; import basicAuth from 'express-basic-auth';
import config from '@/../config/config'; import config from 'config';
import { License, Plan } from '@/system/models'; import { License, Plan } from 'system/models';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
import LicenseService from '@/services/Payment/License'; import LicenseService from 'services/Payment/License';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import { ILicensesFilter } from '@/interfaces'; import { ILicensesFilter } from 'interfaces';
@Service() @Service()
export default class LicensesController extends BaseController { export default class LicensesController extends BaseController {

View File

@@ -1,7 +1,7 @@
import { Inject } from 'typedi'; import { Inject } from 'typedi';
import { Plan } from '@/system/models'; import { Plan } from 'system/models';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
import SubscriptionService from '@/services/Subscription/SubscriptionService'; import SubscriptionService from 'services/Subscription/SubscriptionService';
export default class PaymentMethodController extends BaseController { export default class PaymentMethodController extends BaseController {
@Inject() @Inject()

View File

@@ -1,16 +1,16 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { Router, Request, Response } from 'express'; import { Router, Request, Response } from 'express';
import { check } from 'express-validator'; import { check } from 'express-validator';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import PaymentMethodController from '@/http/controllers/Subscription/PaymentMethod'; import PaymentMethodController from 'api/controllers/Subscription/PaymentMethod';
import { import {
NotAllowedChangeSubscriptionPlan, NotAllowedChangeSubscriptionPlan,
NoPaymentModelWithPricedPlan, NoPaymentModelWithPricedPlan,
PaymentAmountInvalidWithPlan, PaymentAmountInvalidWithPlan,
PaymentInputInvalid, PaymentInputInvalid,
} from '@/exceptions'; } from 'exceptions';
import { ILicensePaymentModel } from '@/interfaces'; import { ILicensePaymentModel } from 'interfaces';
@Service() @Service()
export default class PaymentViaLicenseController extends PaymentMethodController { export default class PaymentViaLicenseController extends PaymentMethodController {

View File

@@ -1,9 +1,9 @@
import { Router } from 'express' import { Router } from 'express'
import { Container, Service } from 'typedi'; import { Container, Service } from 'typedi';
import JWTAuth from '@/http/middleware/jwtAuth'; import JWTAuth from 'api/middleware/jwtAuth';
import TenancyMiddleware from '@/http/middleware/TenancyMiddleware'; import TenancyMiddleware from 'api/middleware/TenancyMiddleware';
import AttachCurrentTenantUser from '@/http/middleware/AttachCurrentTenantUser'; import AttachCurrentTenantUser from 'api/middleware/AttachCurrentTenantUser';
import PaymentViaLicenseController from '@/http/controllers/Subscription/PaymentViaLicense'; import PaymentViaLicenseController from 'api/controllers/Subscription/PaymentViaLicense';
@Service() @Service()
export default class SubscriptionController { export default class SubscriptionController {

View File

@@ -5,11 +5,11 @@ import {
query, query,
param, param,
} from 'express-validator'; } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import BaseController from '@/http/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
import UsersService from '@/services/Users/UsersService'; import UsersService from 'services/Users/UsersService';
import { ServiceError, ServiceErrors } from '@/exceptions'; import { ServiceError, ServiceErrors } from 'exceptions';
import { ISystemUserDTO } from '@/interfaces'; import { ISystemUserDTO } from 'interfaces';
@Service() @Service()
export default class UsersController extends BaseController{ export default class UsersController extends BaseController{
@@ -101,8 +101,6 @@ export default class UsersController extends BaseController{
await this.usersService.editUser(tenantId, userId, userDTO); await this.usersService.editUser(tenantId, userId, userDTO);
return res.status(200).send({ id: userId }); return res.status(200).send({ id: userId });
} catch (error) { } catch (error) {
console.log(error);
if (error instanceof ServiceErrors) { if (error instanceof ServiceErrors) {
const errorReasons = []; const errorReasons = [];
@@ -116,6 +114,7 @@ export default class UsersController extends BaseController{
return res.status(400).send({ errors: errorReasons }); return res.status(400).send({ errors: errorReasons });
} }
} }
next(error);
} }
} }

View File

@@ -7,10 +7,10 @@ import {
oneOf, oneOf,
validationResult, validationResult,
} from 'express-validator'; } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import { import {
validateViewRoles, validateViewRoles,
} from '@/lib/ViewRolesBuilder'; } from 'lib/ViewRolesBuilder';
export default { export default {
resource: 'items', resource: 'items',

View File

@@ -2,17 +2,17 @@ import express from 'express';
import { check, param, query, validationResult } from 'express-validator'; import { check, param, query, validationResult } from 'express-validator';
import moment from 'moment'; import moment from 'moment';
import { difference, sumBy, omit } from 'lodash'; import { difference, sumBy, omit } from 'lodash';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry'; import JournalEntry from 'services/Accounting/JournalEntry';
import JWTAuth from '@/http/middleware/jwtAuth'; import JWTAuth from 'api/middleware/jwtAuth';
import { mapViewRolesToConditionals } from '@/lib/ViewRolesBuilder'; import { mapViewRolesToConditionals } from 'lib/ViewRolesBuilder';
import { import {
DynamicFilter, DynamicFilter,
DynamicFilterSortBy, DynamicFilterSortBy,
DynamicFilterViews, DynamicFilterViews,
DynamicFilterFilterRoles, DynamicFilterFilterRoles,
} from '@/lib/DynamicFilter'; } from 'lib/DynamicFilter';
export default { export default {
/** /**

View File

@@ -2,39 +2,39 @@ import { Router } from 'express';
import { Container } from 'typedi'; import { Container } from 'typedi';
// Middlewares // Middlewares
import JWTAuth from '@/http/middleware/jwtAuth'; import JWTAuth from 'api/middleware/jwtAuth';
import AttachCurrentTenantUser from '@/http/middleware/AttachCurrentTenantUser'; import AttachCurrentTenantUser from 'api/middleware/AttachCurrentTenantUser';
import SubscriptionMiddleware from '@/http/middleware/SubscriptionMiddleware'; import SubscriptionMiddleware from 'api/middleware/SubscriptionMiddleware';
import TenancyMiddleware from '@/http/middleware/TenancyMiddleware'; import TenancyMiddleware from 'api/middleware/TenancyMiddleware';
import EnsureTenantIsInitialized from '@/http/middleware/EnsureTenantIsInitialized'; import EnsureTenantIsInitialized from 'api/middleware/EnsureTenantIsInitialized';
import SettingsMiddleware from '@/http/middleware/SettingsMiddleware'; import SettingsMiddleware from 'api/middleware/SettingsMiddleware';
import I18nMiddleware from '@/http/middleware/I18nMiddleware'; import I18nMiddleware from 'api/middleware/I18nMiddleware';
// Routes // Routes
import Authentication from '@/http/controllers/Authentication'; import Authentication from 'api/controllers/Authentication';
import InviteUsers from '@/http/controllers/InviteUsers'; import InviteUsers from 'api/controllers/InviteUsers';
import Organization from '@/http/controllers/Organization'; import Organization from 'api/controllers/Organization';
import Users from '@/http/controllers/Users'; import Users from 'api/controllers/Users';
import Items from '@/http/controllers/Items'; import Items from 'api/controllers/Items';
import ItemCategories from '@/http/controllers/ItemCategories'; import ItemCategories from 'api/controllers/ItemCategories';
import Accounts from '@/http/controllers/Accounts'; import Accounts from 'api/controllers/Accounts';
import AccountTypes from '@/http/controllers/AccountTypes'; import AccountTypes from 'api/controllers/AccountTypes';
import Views from '@/http/controllers/Views'; import Views from 'api/controllers/Views';
import Accounting from '@/http/controllers/Accounting'; import Accounting from 'api/controllers/Accounting';
import FinancialStatements from '@/http/controllers/FinancialStatements'; import FinancialStatements from 'api/controllers/FinancialStatements';
import Expenses from '@/http/controllers/Expenses'; import Expenses from 'api/controllers/Expenses';
import Settings from '@/http/controllers/Settings'; import Settings from 'api/controllers/Settings';
import Currencies from '@/http/controllers/Currencies'; import Currencies from 'api/controllers/Currencies';
import Customers from '@/http/controllers/Contacts/Customers'; import Customers from 'api/controllers/Contacts/Customers';
import Vendors from '@/http/controllers/Contacts/Vendors'; import Vendors from 'api/controllers/Contacts/Vendors';
import Sales from '@/http/controllers/Sales' import Sales from 'api/controllers/Sales'
import Purchases from '@/http/controllers/Purchases'; import Purchases from 'api/controllers/Purchases';
import Resources from './controllers/Resources'; import Resources from './controllers/Resources';
import ExchangeRates from '@/http/controllers/ExchangeRates'; import ExchangeRates from 'api/controllers/ExchangeRates';
import Media from '@/http/controllers/Media'; import Media from 'api/controllers/Media';
import Ping from '@/http/controllers/Ping'; import Ping from 'api/controllers/Ping';
import Subscription from '@/http/controllers/Subscription'; import Subscription from 'api/controllers/Subscription';
import Licenses from '@/http/controllers/Subscription/Licenses'; import Licenses from 'api/controllers/Subscription/Licenses';
export default () => { export default () => {
const app = Router(); const app = Router();
@@ -67,7 +67,7 @@ export default () => {
dashboard.use('/views', Views.router()); dashboard.use('/views', Views.router());
dashboard.use('/items', Container.get(Items).router()); dashboard.use('/items', Container.get(Items).router());
dashboard.use('/item_categories', Container.get(ItemCategories).router()); dashboard.use('/item_categories', Container.get(ItemCategories).router());
dashboard.use('/expenses', Expenses.router()); dashboard.use('/expenses', Container.get(Expenses).router());
dashboard.use('/financial_statements', FinancialStatements.router()); dashboard.use('/financial_statements', FinancialStatements.router());
dashboard.use('/settings', Container.get(Settings).router()); dashboard.use('/settings', Container.get(Settings).router());
dashboard.use('/sales', Sales.router()); dashboard.use('/sales', Sales.router());

View File

@@ -1,5 +1,5 @@
import { Container } from 'typedi'; import { Container } from 'typedi';
import { SystemUser } from '@/system/models'; import { Request, Response } from 'express';
/** /**
* Attach user to req.currentUser * Attach user to req.currentUser
@@ -9,10 +9,11 @@ import { SystemUser } from '@/system/models';
*/ */
const attachCurrentUser = async (req: Request, res: Response, next: Function) => { const attachCurrentUser = async (req: Request, res: Response, next: Function) => {
const Logger = Container.get('logger'); const Logger = Container.get('logger');
const { systemUserRepository } = Container.get('repositories');
try { try {
Logger.info('[attach_user_middleware] finding system user by id.'); Logger.info('[attach_user_middleware] finding system user by id.');
const user = await SystemUser.query().findById(req.token.id); const user = await systemUserRepository.getById(req.token.id);
if (!user) { if (!user) {
Logger.info('[attach_user_middleware] the system user not found.'); Logger.info('[attach_user_middleware] the system user not found.');

View File

@@ -0,0 +1,27 @@
import { Container } from 'typedi';
import { Request, Response } from 'express';
export default (req: Request, res: Response, next: Function) => {
const Logger = Container.get('logger');
if (!req.tenant) {
Logger.info('[ensure_tenant_intialized_middleware] no tenant model.');
throw new Error('Should load this middleware after `TenancyMiddleware`.');
}
if (!req.tenant.initializedAt) {
Logger.info('[ensure_tenant_initialized_middleware] tenant database not initalized.');
return res.boom.badRequest(
'Tenant database is not migrated with application schema yut.',
{ errors: [{ type: 'TENANT.DATABASE.NOT.INITALIZED' }] },
);
}
if (!req.tenant.seededAt) {
Logger.info('[ensure_tenant_initialized_middleware] tenant databae not seeded.');
return res.boom.badRequest(
'Tenant database is not seeded with initial data yet.',
{ errors: [{ type: 'TENANT.DATABASE.NOT.SEED' }] },
);
}
next();
};

View File

@@ -1,11 +1,13 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { Container } from 'typedi'; import { Container } from 'typedi';
import SettingsStore from '@/services/Settings/SettingsStore'; import SettingsStore from 'services/Settings/SettingsStore';
export default async (req: Request, res: Response, next: NextFunction) => { export default async (req: Request, res: Response, next: NextFunction) => {
const { tenantId } = req.user; const { tenantId } = req.user;
const { knex } = req; const { knex } = req;
console.log(knex);
const Logger = Container.get('logger'); const Logger = Container.get('logger');
const tenantContainer = Container.of(`tenant-${tenantId}`); const tenantContainer = Container.of(`tenant-${tenantId}`);

View File

@@ -4,15 +4,13 @@ import { Container } from 'typedi';
export default (subscriptionSlug = 'main') => async (req: Request, res: Response, next: NextFunction) => { export default (subscriptionSlug = 'main') => async (req: Request, res: Response, next: NextFunction) => {
const { tenant, tenantId } = req; const { tenant, tenantId } = req;
const Logger = Container.get('logger'); const Logger = Container.get('logger');
const { subscriptionRepository } = Container.get('repositories');
if (!tenant) { if (!tenant) {
throw new Error('Should load `TenancyMiddlware` before this middleware.'); throw new Error('Should load `TenancyMiddlware` before this middleware.');
} }
Logger.info('[subscription_middleware] trying get tenant main subscription.'); Logger.info('[subscription_middleware] trying get tenant main subscription.');
const subscription = await tenant const subscription = await subscriptionRepository.getBySlugInTenant(subscriptionSlug, tenantId);
.$relatedQuery('subscriptions')
.modify('subscriptionBySlug', subscriptionSlug)
.first();
// Validate in case there is no any already subscription. // Validate in case there is no any already subscription.
if (!subscription) { if (!subscription) {

View File

@@ -0,0 +1,36 @@
import { Container } from 'typedi';
import { Request, Response, NextFunction } from 'express';
import tenantDependencyInjection from 'api/middleware/TenantDependencyInjection'
export default async (req: Request, res: Response, next: NextFunction) => {
const Logger = Container.get('logger');
const organizationId = req.headers['organization-id'] || req.query.organization;
const notFoundOrganization = () => {
Logger.info('[tenancy_middleware] organization id not found.');
return res.boom.unauthorized(
'Organization identication not found.',
{ errors: [{ type: 'ORGANIZATION.ID.NOT.FOUND', code: 100 }] },
);
}
// In case the given organization not found.
if (!organizationId) {
return notFoundOrganization();
}
const { tenantRepository } = Container.get('repositories');
Logger.info('[tenancy_middleware] trying get tenant by org. id from storage.');
const tenant = await tenantRepository.getByOrgId(organizationId);
// When the given organization id not found on the system storage.
if (!tenant) {
return notFoundOrganization();
}
// When user tenant not match the given organization id.
if (tenant.id !== req.user.tenantId) {
Logger.info('[tenancy_middleware] authorized user not match org. tenant.');
return res.boom.unauthorized();
}
tenantDependencyInjection(req, tenant);
next();
}

View File

@@ -0,0 +1,27 @@
import { Container } from 'typedi';
import { ITenant } from 'interfaces';
import { Request } from 'express';
import TenancyService from 'services/Tenancy/TenancyService';
import TenantsManagerService from 'services/Tenancy/TenantsManager';
export default (req: Request, tenant: ITenant) => {
const { id: tenantId, organizationId } = tenant;
const tenantServices = Container.get(TenancyService);
const tenantsManager = Container.get(TenantsManagerService);
// Initialize the knex instance.
tenantsManager.setupKnexInstance(tenant);
const knexInstance = tenantServices.knex(tenantId);
const models = tenantServices.models(tenantId);
const repositories = tenantServices.repositories(tenantId)
const cacheInstance = tenantServices.cache(tenantId);
req.knex = knexInstance;
req.organizationId = organizationId;
req.tenant = tenant;
req.tenantId = tenant.id;
req.models = models;
req.repositories = repositories;
req.cache = cacheInstance;
}

View File

@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { Container } from 'typedi'; import { Container } from 'typedi';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import config from '@/../config/config'; import config from 'config';
const authMiddleware = (req: Request, res: Response, next: NextFunction) => { const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
const Logger = Container.get('logger'); const Logger = Container.get('logger');

8
server/src/before.ts Normal file
View File

@@ -0,0 +1,8 @@
import path from 'path';
import moment from 'moment';
global.__root = path.resolve(__dirname);
moment.prototype.toMySqlDateTime = function () {
return this.format('YYYY-MM-DD HH:mm:ss');
};

View File

@@ -1,5 +1,5 @@
import MetableCollection from '@/lib/Metable/MetableCollection'; import MetableCollection from 'lib/Metable/MetableCollection';
import ResourceFieldMetadata from '@/models/ResourceFieldMetadata'; import ResourceFieldMetadata from 'models/ResourceFieldMetadata';
export default class ResourceFieldMetadataCollection extends MetableCollection { export default class ResourceFieldMetadataCollection extends MetableCollection {
/** /**

View File

@@ -1,5 +1,15 @@
import dotenv from 'dotenv';
module.exports = { // Set the NODE_ENV to 'development' by default
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
const envFound = dotenv.config();
if (envFound.error) {
// This error should crash whole process
throw new Error("⚠️ Couldn't find .env file ⚠️");
}
export default {
/** /**
* Your favorite port * Your favorite port
*/ */
@@ -22,7 +32,8 @@ module.exports = {
db_password: 'root', db_password: 'root',
charset: 'utf8', charset: 'utf8',
migrations_dir: 'src/database/migrations', migrations_dir: 'src/database/migrations',
seeds_dir: 'src/database/seeds', seeds_dir: 'src/database/seeds/core',
seeds_table_name: 'seeds_versioning',
}, },
manager: { manager: {
superUser: 'root', superUser: 'root',
@@ -85,5 +96,5 @@ module.exports = {
licensesAuth: { licensesAuth: {
user: 'admin', user: 'admin',
password: 'admin', password: 'admin',
} },
}; };

View File

@@ -0,0 +1,58 @@
import config from 'config';
import { ITenant } from 'interfaces';
export const tenantKnexConfig = (tenant: ITenant) => {
const { organizationId, id } = tenant;
return {
client: config.tenant.db_client,
connection: {
host: config.tenant.db_host,
user: config.tenant.db_user,
password: config.tenant.db_password,
database: `${config.tenant.db_name_prefix}${organizationId}`,
charset: config.tenant.charset,
},
migrations: {
directory: config.tenant.migrations_dir,
},
seeds: {
directory: config.tenant.seeds_dir,
},
pool: { min: 0, max: 5 },
userParams: {
tenantId: id,
organizationId
}
};
};
export const systemKnexConfig = {
client: config.system.db_client,
connection: {
host: config.system.db_host,
user: config.system.db_user,
password: config.system.db_password,
database: config.system.db_name,
charset: 'utf8',
},
migrations: {
directory: config.system.migrations_dir,
},
seeds: {
directory: config.system.seeds_dir,
},
pool: { min: 0, max: 7 },
};
export const systemDbManager = {
collate: [],
superUser: config.manager.superUser,
superPassword: config.manager.superPassword,
};
export const tenantSeedConfig = (tenant: ITenant) => {
return {
directory: config.tenant.seeds_dir,
};
}

View File

@@ -1,6 +1,6 @@
import KnexFactory from '@/lib/KnexFactory'; import KnexFactory from 'lib/KnexFactory';
import faker from 'faker'; import faker from 'faker';
import { hashPassword } from '@/utils'; import { hashPassword } from 'utils';
export default (tenantDb) => { export default (tenantDb) => {

View File

@@ -1,5 +1,5 @@
import KnexFactory from '@/lib/KnexFactory'; import KnexFactory from 'lib/KnexFactory';
import systemDb from '@/database/knex'; import systemDb from 'database/knex';
import faker from 'faker'; import faker from 'faker';
export default () => { export default () => {

View File

@@ -1,14 +0,0 @@
import knexManager from 'knex-db-manager';
import knexfile from '@/../config/systemKnexfile';
import config from '@/../config/config';
const knexConfig = knexfile[process.env.NODE_ENV];
export default () => knexManager.databaseManagerFactory({
knex: knexConfig,
dbManager: {
collate: [],
superUser: config.manager.superUser,
superPassword: config.manager.superPassword,
},
});

View File

@@ -3,10 +3,6 @@ exports.up = function (knex) {
return knex.schema.createTable('resources', (table) => { return knex.schema.createTable('resources', (table) => {
table.increments(); table.increments();
table.string('name'); table.string('name');
}).then(() => {
return knex.seed.run({
specific: 'seed_resources.js',
});
}); });
}; };

View File

@@ -14,11 +14,7 @@ exports.up = function (knex) {
table.decimal('amount', 15, 5); table.decimal('amount', 15, 5);
table.string('currency_code', 3); table.string('currency_code', 3);
table.timestamps(); table.timestamps();
}).raw('ALTER TABLE `ACCOUNTS` AUTO_INCREMENT = 1000').then(() => { }).raw('ALTER TABLE `ACCOUNTS` AUTO_INCREMENT = 1000');
return knex.seed.run({
specific: 'seed_accounts.js',
});
});
}; };
exports.down = (knex) => knex.schema.dropTableIfExists('accounts'); exports.down = (knex) => knex.schema.dropTableIfExists('accounts');

View File

@@ -9,11 +9,7 @@ exports.up = (knex) => {
table.string('child_type'); table.string('child_type');
table.boolean('balance_sheet'); table.boolean('balance_sheet');
table.boolean('income_sheet'); table.boolean('income_sheet');
}).raw('ALTER TABLE `ACCOUNT_TYPES` AUTO_INCREMENT = 1000').then(() => { }).raw('ALTER TABLE `ACCOUNT_TYPES` AUTO_INCREMENT = 1000');
return knex.seed.run({
specific: 'seed_account_types.js',
});
});
}; };
exports.down = (knex) => knex.schema.dropTableIfExists('account_types'); exports.down = (knex) => knex.schema.dropTableIfExists('account_types');

View File

@@ -15,11 +15,7 @@ exports.up = function (knex) {
table.string('data_resource'); table.string('data_resource');
table.json('options'); table.json('options');
table.integer('resource_id').unsigned(); table.integer('resource_id').unsigned();
}).raw('ALTER TABLE `RESOURCE_FIELDS` AUTO_INCREMENT = 1000').then(() => { }).raw('ALTER TABLE `RESOURCE_FIELDS` AUTO_INCREMENT = 1000');
return knex.seed.run({
specific: 'seed_resources_fields.js',
});
});
}; };
exports.down = (knex) => knex.schema.dropTableIfExists('resource_fields'); exports.down = (knex) => knex.schema.dropTableIfExists('resource_fields');

View File

@@ -5,9 +5,7 @@ exports.up = function (knex) {
table.integer('view_id').unsigned(); table.integer('view_id').unsigned();
table.integer('field_id').unsigned(); table.integer('field_id').unsigned();
table.integer('index').unsigned(); table.integer('index').unsigned();
}).raw('ALTER TABLE `ITEMS_CATEGORIES` AUTO_INCREMENT = 1000').then(() => { }).raw('ALTER TABLE `ITEMS_CATEGORIES` AUTO_INCREMENT = 1000');
});
}; };
exports.down = (knex) => knex.schema.dropTableIfExists('view_has_columns'); exports.down = (knex) => knex.schema.dropTableIfExists('view_has_columns');

View File

@@ -8,11 +8,7 @@ exports.up = function (knex) {
table.boolean('favourite'); table.boolean('favourite');
table.string('roles_logic_expression'); table.string('roles_logic_expression');
table.timestamps(); table.timestamps();
}).raw('ALTER TABLE `VIEWS` AUTO_INCREMENT = 1000').then(() => { }).raw('ALTER TABLE `VIEWS` AUTO_INCREMENT = 1000');
return knex.seed.run({
specific: 'seed_views.js',
});
});
}; };
exports.down = (knex) => knex.schema.dropTableIfExists('views'); exports.down = (knex) => knex.schema.dropTableIfExists('views');

View File

@@ -1,49 +0,0 @@
exports.up = function(knex) {
return knex.schema.createTable('contacts', table => {
table.increments();
table.string('contact_service');
table.string('contact_type');
table.decimal('balance', 13, 3).defaultTo(0);
table.decimal('opening_balance', 13, 3).defaultTo(0);
table.string('first_name').nullable();
table.string('last_name').nullable();
table.string('company_name').nullable();
table.string('display_name');
table.string('email').nullable();
table.string('work_phone').nullable();
table.string('personal_phone').nullable();
table.string('billing_address_1').nullable();
table.string('billing_address_2').nullable();
table.string('billing_address_city').nullable();
table.string('billing_address_country').nullable();
table.string('billing_address_email').nullable();
table.string('billing_address_zipcode').nullable();
table.string('billing_address_phone').nullable();
table.string('billing_address_state').nullable(),
table.string('shipping_address_1').nullable();
table.string('shipping_address_2').nullable();
table.string('shipping_address_city').nullable();
table.string('shipping_address_country').nullable();
table.string('shipping_address_email').nullable();
table.string('shipping_address_zipcode').nullable();
table.string('shipping_address_phone').nullable();
table.string('shipping_address_state').nullable();
table.text('note');
table.boolean('active').defaultTo(true);
table.timestamps();
});
};
exports.down = function(knex) {
return knex.schema.dropTableIfExists('contacts');
};

View File

@@ -1,46 +0,0 @@
exports.up = function(knex) {
return knex.schema.createTable('customers', table => {
table.increments();
table.string('customer_type');
table.decimal('balance', 13, 3).defaultTo(0);
table.string('first_name').nullable();
table.string('last_name').nullable();
table.string('company_name').nullable();
table.string('display_name');
table.string('email').nullable();
table.string('work_phone').nullable();
table.string('personal_phone').nullable();
table.string('billing_address_1').nullable();
table.string('billing_address_2').nullable();
table.string('billing_address_city').nullable();
table.string('billing_address_country').nullable();
table.string('billing_address_email').nullable();
table.string('billing_address_zipcode').nullable();
table.string('billing_address_phone').nullable();
table.string('billing_address_state').nullable(),
table.string('shipping_address_1').nullable();
table.string('shipping_address_2').nullable();
table.string('shipping_address_city').nullable();
table.string('shipping_address_country').nullable();
table.string('shipping_address_email').nullable();
table.string('shipping_address_zipcode').nullable();
table.string('shipping_address_phone').nullable();
table.string('shipping_address_state').nullable();
table.text('note');
table.boolean('active').defaultTo(true);
table.timestamps();
});
};
exports.down = function(knex) {
return knex.schema.dropTableIfExists('customers');
};

View File

@@ -0,0 +1,182 @@
import TenancyService from 'services/Tenancy/TenancyService'
import Container from 'typedi';
exports.up = function (knex) {
const tenancyService = Container.get(TenancyService);
const i18n = tenancyService.i18n(knex.userParams.tenantId);
return knex('accounts').then(() => {
// Inserts seed entries
return knex('accounts').insert([
{
id: 1,
name: 'Petty Cash',
slug: 'petty-cash',
account_type_id: 2,
parent_account_id: null,
code: '1000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 2,
name: 'Bank',
slug: 'bank',
account_type_id: 2,
parent_account_id: null,
code: '2000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 3,
name: 'Other Income',
slug: 'other-income',
account_type_id: 7,
parent_account_id: null,
code: '1000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 4,
name: 'Interest Income',
slug: 'interest-income',
account_type_id: 7,
parent_account_id: null,
code: '1000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 5,
name: 'Opening Balance',
slug: 'opening-balance',
account_type_id: 5,
parent_account_id: null,
code: '1000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 6,
name: 'Depreciation Expense',
slug: 'depreciation-expense',
account_type_id: 6,
parent_account_id: null,
code: '1000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 7,
name: 'Interest Expense',
slug: 'interest-expense',
account_type_id: 6,
parent_account_id: null,
code: '1000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 8,
name: 'Payroll Expenses',
slug: 'payroll-expenses',
account_type_id: 6,
parent_account_id: null,
code: '1000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 9,
name: 'Other Expenses',
slug: 'other-expenses',
account_type_id: 6,
parent_account_id: null,
code: '1000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 10,
name: 'Accounts Receivable',
slug: 'accounts-receivable',
account_type_id: 8,
parent_account_id: null,
code: '1000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 11,
name: 'Accounts Payable',
slug: 'accounts-payable',
account_type_id: 9,
parent_account_id: null,
code: '1000',
description: '',
active: 1,
index: 1,
predefined: 1,
},
{
id: 12,
name: 'Cost of Goods Sold (COGS)',
slug: 'cost-of-goods-sold',
account_type_id: 12,
predefined: 1,
parent_account_id: null,
index: 1,
active: 1,
description: 1,
},
{
id: 13,
name: 'Inventory Asset',
slug: 'inventory-asset',
account_type_id: 14,
predefined: 1,
parent_account_id: null,
index: 1,
active: 1,
description: '',
},
{
id: 14,
name: 'Sales of Product Income',
slug: 'sales-of-product-income',
account_type_id: 7,
predefined: 1,
parent_account_id: null,
index: 1,
active: 1,
description: '',
}
]);
});
};
exports.down = function (knex) {
};

View File

@@ -0,0 +1,148 @@
exports.up = function (knex) {
return knex('account_types').insert([
{
id: 1,
name: 'Fixed Asset',
key: 'fixed_asset',
normal: 'debit',
root_type: 'asset',
child_type: 'fixed_asset',
balance_sheet: true,
income_sheet: false,
},
{
id: 2,
name: 'Current Asset',
key: 'current_asset',
normal: 'debit',
root_type: 'asset',
child_type: 'current_asset',
balance_sheet: true,
income_sheet: false,
},
{
id: 14,
name: 'Other Asset',
key: 'other_asset',
normal: 'debit',
root_type: 'asset',
child_type: 'other_asset',
balance_sheet: true,
income_sheet: false,
},
{
id: 3,
name: 'Long Term Liability',
key: 'long_term_liability',
normal: 'credit',
root_type: 'liability',
child_type: 'long_term_liability',
balance_sheet: false,
income_sheet: true,
},
{
id: 4,
name: 'Current Liability',
key: 'current_liability',
normal: 'credit',
root_type: 'liability',
child_type: 'current_liability',
balance_sheet: false,
income_sheet: true,
},
{
id: 13,
name: 'Other Liability',
key: 'other_liability',
normal: 'credit',
root_type: 'liability',
child_type: 'other_liability',
balance_sheet: false,
income_sheet: true,
},
{
id: 5,
name: 'Equity',
key: 'equity',
normal: 'credit',
root_type: 'equity',
child_type: 'equity',
balance_sheet: true,
income_sheet: false,
},
{
id: 6,
name: 'Expense',
key: 'expense',
normal: 'debit',
root_type: 'expense',
child_type: 'expense',
balance_sheet: false,
income_sheet: true,
},
{
id: 10,
name: 'Other Expense',
key: 'other_expense',
normal: 'debit',
root_type: 'expense',
balance_sheet: false,
income_sheet: true,
},
{
id: 7,
name: 'Income',
key: 'income',
normal: 'credit',
root_type: 'income',
child_type: 'income',
balance_sheet: false,
income_sheet: true,
},
{
id: 11,
name: 'Other Income',
key: 'other_income',
normal: 'credit',
root_type: 'income',
child_type: 'other_income',
balance_sheet: false,
income_sheet: true,
},
{
id: 12,
name: 'Cost of Goods Sold (COGS)',
key: 'cost_of_goods_sold',
normal: 'debit',
root_type: 'expenses',
child_type: 'expenses',
balance_sheet: true,
income_sheet: false,
},
{
id: 8,
name: 'Accounts Receivable (A/R)',
key: 'accounts_receivable',
normal: 'debit',
root_type: 'asset',
child_type: 'current_asset',
balance_sheet: true,
income_sheet: false,
},
{
id: 9,
name: 'Accounts Payable (A/P)',
key: 'accounts_payable',
normal: 'credit',
root_type: 'liability',
child_type: 'current_liability',
balance_sheet: true,
income_sheet: false,
},
]);
};
exports.down = function(knex) {
}

View File

@@ -1,149 +0,0 @@
exports.seed = (knex) => {
// Deletes ALL existing entries
return knex('account_types').del()
.then(() => {
// Inserts seed entries
return knex('account_types').insert([
{
id: 1,
name: 'Fixed Asset',
key: 'fixed_asset',
normal: 'debit',
root_type: 'asset',
child_type: 'fixed_asset',
balance_sheet: true,
income_sheet: false,
},
{
id: 2,
name: 'Current Asset',
key: 'current_asset',
normal: 'debit',
root_type: 'asset',
child_type: 'current_asset',
balance_sheet: true,
income_sheet: false,
},
{
id: 14,
name: 'Other Asset',
key: 'other_asset',
normal: 'debit',
root_type: 'asset',
child_type: 'other_asset',
balance_sheet: true,
income_sheet: false,
},
{
id: 3,
name: 'Long Term Liability',
key: 'long_term_liability',
normal: 'credit',
root_type: 'liability',
child_type: 'long_term_liability',
balance_sheet: false,
income_sheet: true,
},
{
id: 4,
name: 'Current Liability',
key: 'current_liability',
normal: 'credit',
root_type: 'liability',
child_type: 'current_liability',
balance_sheet: false,
income_sheet: true,
},
{
id: 13,
name: 'Other Liability',
key: 'other_liability',
normal: 'credit',
root_type: 'liability',
child_type: 'other_liability',
balance_sheet: false,
income_sheet: true,
},
{
id: 5,
name: 'Equity',
key: 'equity',
normal: 'credit',
root_type: 'equity',
child_type: 'equity',
balance_sheet: true,
income_sheet: false,
},
{
id: 6,
name: 'Expense',
key: 'expense',
normal: 'debit',
root_type: 'expense',
child_type: 'expense',
balance_sheet: false,
income_sheet: true,
},
{
id: 10,
name: 'Other Expense',
key: 'other_expense',
normal: 'debit',
root_type: 'expense',
balance_sheet: false,
income_sheet: true,
},
{
id: 7,
name: 'Income',
key: 'income',
normal: 'credit',
root_type: 'income',
child_type: 'income',
balance_sheet: false,
income_sheet: true,
},
{
id: 11,
name: 'Other Income',
key: 'other_income',
normal: 'credit',
root_type: 'income',
child_type: 'other_income',
balance_sheet: false,
income_sheet: true,
},
{
id: 12,
name: 'Cost of Goods Sold (COGS)',
key: 'cost_of_goods_sold',
normal: 'debit',
root_type: 'expenses',
child_type: 'expenses',
balance_sheet: true,
income_sheet: false,
},
{
id: 8,
name: 'Accounts Receivable (A/R)',
key: 'accounts_receivable',
normal: 'debit',
root_type: 'asset',
child_type: 'current_asset',
balance_sheet: true,
income_sheet: false,
},
{
id: 9,
name: 'Accounts Payable (A/P)',
key: 'accounts_payable',
normal: 'credit',
root_type: 'liability',
child_type: 'current_liability',
balance_sheet: true,
income_sheet: false,
},
]);
});
};

View File

@@ -1,5 +1,7 @@
exports.seed = (knex) => { exports.seed = (knex) => {
console.log(knex.tenantId);
// Deletes ALL existing entries // Deletes ALL existing entries
return knex('accounts').del() return knex('accounts').del()
.then(() => { .then(() => {

View File

@@ -0,0 +1,7 @@
export default class TenantAlreadyInitialized {
constructor() {
}
}

View File

@@ -0,0 +1,9 @@
export default class TenantAlreadySeeded {
constructor() {
}
}

View File

@@ -0,0 +1,9 @@
export default class TenantDBAlreadyExists {
constructor() {
}
}

View File

@@ -0,0 +1,7 @@
export default class TenantDatabaseNotBuilt {
constructor() {
}
}

View File

@@ -4,6 +4,10 @@ import ServiceErrors from './ServiceErrors';
import NoPaymentModelWithPricedPlan from './NoPaymentModelWithPricedPlan'; import NoPaymentModelWithPricedPlan from './NoPaymentModelWithPricedPlan';
import PaymentInputInvalid from './PaymentInputInvalid'; import PaymentInputInvalid from './PaymentInputInvalid';
import PaymentAmountInvalidWithPlan from './PaymentAmountInvalidWithPlan'; import PaymentAmountInvalidWithPlan from './PaymentAmountInvalidWithPlan';
import TenantAlreadyInitialized from './TenantAlreadyInitialized';
import TenantAlreadySeeded from './TenantAlreadySeeded';
import TenantDBAlreadyExists from './TenantDBAlreadyExists';
import TenantDatabaseNotBuilt from './TenantDatabaseNotBuilt';
export { export {
NotAllowedChangeSubscriptionPlan, NotAllowedChangeSubscriptionPlan,
@@ -11,5 +15,9 @@ export {
PaymentAmountInvalidWithPlan, PaymentAmountInvalidWithPlan,
ServiceError, ServiceError,
ServiceErrors, ServiceErrors,
PaymentInputInvalid PaymentInputInvalid,
TenantAlreadyInitialized,
TenantAlreadySeeded,
TenantDBAlreadyExists,
TenantDatabaseNotBuilt,
}; };

View File

@@ -1,74 +0,0 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response } from 'express';
import { check } from 'express-validator';
import asyncMiddleware from "@/http/middleware/asyncMiddleware";
import JWTAuth from '@/http/middleware/jwtAuth';
import TenancyMiddleware from '@/http/middleware/TenancyMiddleware';
import SubscriptionMiddleware from '@/http/middleware/SubscriptionMiddleware';
import AttachCurrentTenantUser from '@/http/middleware/AttachCurrentTenantUser';
import OrganizationService from '@/services/Organization';
import { ServiceError } from '@/exceptions';
import BaseController from '@/http/controllers/BaseController';
@Service()
export default class OrganizationController extends BaseController{
@Inject()
organizationService: OrganizationService;
/**
* Router constructor.
*/
router() {
const router = Router();
// Should before build tenant database the user be authorized and
// most important than that, should be subscribed to any plan.
router.use(JWTAuth);
router.use(AttachCurrentTenantUser);
router.use(TenancyMiddleware);
router.use(SubscriptionMiddleware('main'));
router.post(
'/build', [
check('organization_id').exists().trim().escape(),
],
this.validationResult,
asyncMiddleware(this.build.bind(this))
);
return router;
}
/**
* Builds tenant database and seed initial data.
* @param {Request} req - Express request.
* @param {Response} res - Express response.
* @param {NextFunction} next
*/
async build(req: Request, res: Response, next: Function) {
const buildOTD = this.matchedBodyData(req);
try {
await this.organizationService.build(buildOTD.organizationId);
return res.status(200).send({
type: 'success',
code: 'ORGANIZATION.DATABASE.INITIALIZED',
message: 'The organization database has been initialized.'
});
} catch (error) {
if (error instanceof ServiceError) {
if (error.errorType === 'tenant_not_found') {
return res.status(400).send({
errors: [{ type: 'TENANT.NOT.FOUND', code: 100 }],
});
}
if (error.errorType === 'tenant_initialized') {
return res.status(400).send({
errors: [{ type: 'TENANT.DATABASE.ALREADY.BUILT', code: 200 }],
});
}
}
next(error);
}
}
}

View File

@@ -1,17 +0,0 @@
import { Container } from 'typedi';
export default (req: Request, res: Response, next: Function) => {
const Logger = Container.get('logger');
if (!req.tenant) {
Logger.info('[ensure_tenant_intialized_middleware] no tenant model.');
throw new Error('Should load this middleware after `TenancyMiddleware`.');
}
if (!req.tenant.initialized) {
Logger.info('[ensure_tenant_intialized_middleware] tenant database not initalized.');
return res.status(400).send({
errors: [{ type: 'TENANT.DATABASE.NOT.INITALIZED' }],
});
}
next();
};

View File

@@ -1,68 +0,0 @@
import { Container } from 'typedi';
import { Request, Response, NextFunction } from 'express';
import TenantsManager from '@/system/TenantsManager';
import tenantModelsLoader from '@/loaders/tenantModels';
import tenantRepositoriesLoader from '@/loaders/tenantRepositories';
import Cache from '@/services/Cache';
export default async (req: Request, res: Response, next: NextFunction) => {
const Logger = Container.get('logger');
const organizationId = req.headers['organization-id'] || req.query.organization;
const notFoundOrganization = () => {
Logger.info('[tenancy_middleware] organization id not found.');
return res.boom.unauthorized(
'Organization identication not found.',
{ errors: [{ type: 'ORGANIZATION.ID.NOT.FOUND', code: 100 }] },
);
}
// In case the given organization not found.
if (!organizationId) {
return notFoundOrganization();
}
const tenantsManager = Container.get(TenantsManager);
Logger.info('[tenancy_middleware] trying get tenant by org. id from storage.');
const tenant = await tenantsManager.getTenant(organizationId);
Logger.info('[tenancy_middleware] initializing tenant knex instance.');
const tenantKnex = tenantsManager.knexInstance(organizationId);
// When the given organization id not found on the system storage.
if (!tenant) {
return notFoundOrganization();
}
// When user tenant not match the given organization id.
if (tenant.id !== req.user.tenantId) {
Logger.info('[tenancy_middleware] authorized user not match org. tenant.');
return res.boom.unauthorized();
}
const models = tenantModelsLoader(tenantKnex);
req.knex = tenantKnex;
req.organizationId = organizationId;
req.tenant = tenant;
req.tenantId = tenant.id;
req.models = models;
const tenantContainer = Container.of(`tenant-${tenant.id}`);
const cacheInstance = new Cache();
tenantContainer.set('models', models);
tenantContainer.set('knex', tenantKnex);
tenantContainer.set('tenant', tenant);
tenantContainer.set('cache', cacheInstance);
const repositories = tenantRepositoriesLoader(tenant.id)
tenantContainer.set('repositories', repositories);
req.repositories = repositories;
Logger.info('[tenancy_middleware] tenant dependencies injected to container.');
if (res.locals) {
tenantContainer.set('i18n', res.locals);
Logger.info('[tenancy_middleware] i18n locals injected.');
}
next();
}

View File

@@ -0,0 +1,55 @@
export interface IExpense {
id: number,
totalAmount: number,
currencyCode: string,
description?: string,
paymentAccountId: number,
peyeeId?: number,
referenceNo?: string,
published: boolean,
userId: number,
paymentDate: Date,
categories: IExpenseCategory[],
}
export interface IExpenseCategory {
expenseAccountId: number,
index: number,
description: string,
expenseId: number,
amount: number,
}
export interface IExpenseDTO {
currencyCode: string,
description?: string,
paymentAccountId: number,
peyeeId?: number,
referenceNo?: string,
published: boolean,
userId: number,
paymentDate: Date,
categories: IExpenseCategoryDTO[],
}
export interface IExpenseCategoryDTO {
expenseAccountId: number,
index: number,
description?: string,
expenseId: number,
};
export interface IExpensesService {
newExpense(tenantid: number, expenseDTO: IExpenseDTO): Promise<IExpense>;
editExpense(tenantid: number, expenseId: number, expenseDTO: IExpenseDTO): void;
publishExpense(tenantId: number, expenseId: number): Promise<void>;
deleteExpense(tenantId: number, expenseId: number): Promise<void>;
deleteBulkExpenses(tenantId: number, expensesIds: number[]): Promise<void>;
publishBulkExpenses(tenantId: number, expensesIds: number[]): Promise<void>;
}

View File

@@ -0,0 +1,49 @@
import { Knex } from 'knex';
export interface ITenant {
id: number,
organizationId: string,
initializedAt: Date|null,
seededAt: Date|null,
createdAt: Date|null,
}
export interface ITenantDBManager {
constructor();
databaseExists(tenant: ITenant): Promise<boolean>;
createDatabase(tenant: ITenant): Promise<void>;
migrate(tenant: ITenant): Promise<void>;
seed(tenant: ITenant): Promise<void>;
setupKnexInstance(tenant: ITenant): Knex;
getKnexInstance(tenantId: number): Knex;
}
export interface ITenantManager {
tenantDBManager: ITenantDBManager;
tenant: ITenant;
constructor(): void;
createTenant(): Promise<ITenant>;
createDatabase(tenant: ITenant): Promise<void>;
hasDatabase(tenant: ITenant): Promise<boolean>;
dropTenant(tenant: ITenant): Promise<void>;
migrateTenant(tenant: ITenant): Promise<void>;
seedTenant(tenant: ITenant): Promise<void>;
setupKnexInstance(tenant: ITenant): Knex;
getKnexInstance(tenantId: number): Knex;
}
export interface ISystemService {
cache();
repositories();
knex();
dbManager();
}

View File

@@ -1,13 +1,33 @@
export interface ISystemUser { export interface ISystemUser {
id: number,
firstName: string,
lastName: string,
active: boolean,
password: string,
email: string,
phoneNumber: string,
roleId: number,
tenantId: number,
inviteAcceptAt: Date,
lastLoginAt: Date,
} }
export interface ISystemUserDTO { export interface ISystemUserDTO {
firstName: string,
lastName: string,
password: string,
phoneNumber: string,
active: boolean,
email: string,
} }
export interface IInviteUserInput { export interface IInviteUserInput {
firstName: string,
lastName: string,
phoneNumber: string,
password: string,
} }

View File

@@ -66,6 +66,19 @@ import {
ICustomerNewDTO, ICustomerNewDTO,
ICustomerEditDTO, ICustomerEditDTO,
} from './Contact'; } from './Contact';
import {
IExpense,
IExpenseCategory,
IExpenseDTO,
IExpenseCategoryDTO,
IExpensesService,
} from './Expenses';
import {
ITenant,
ITenantDBManager,
ITenantManager,
ISystemService,
} from './Tenancy';
export { export {
IAccount, IAccount,
@@ -126,4 +139,15 @@ export {
ICustomer, ICustomer,
ICustomerNewDTO, ICustomerNewDTO,
ICustomerEditDTO, ICustomerEditDTO,
IExpense,
IExpenseCategory,
IExpenseDTO,
IExpenseCategoryDTO,
IExpensesService,
ITenant,
ITenantDBManager,
ITenantManager,
ISystemService,
}; };

View File

@@ -1,7 +1,7 @@
import { Container } from 'typedi'; import { Container } from 'typedi';
import moment from 'moment'; import moment from 'moment';
import InventoryService from '@/services/Inventory/Inventory'; import InventoryService from 'services/Inventory/Inventory';
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost'; import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
export default class ComputeItemCostJob { export default class ComputeItemCostJob {
depends: number; depends: number;

View File

@@ -1,5 +1,5 @@
import Container from 'typedi'; import Container from 'typedi';
import SubscriptionService from '@/services/Subscription/Subscription'; import SubscriptionService from 'services/Subscription/Subscription';
export default class MailNotificationSubscribeEnd { export default class MailNotificationSubscribeEnd {
/** /**

Some files were not shown because too many files have changed in this diff Show More