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

@@ -1,7 +1,7 @@
import { Service } from 'typedi';
import { Request, Response, Router } from 'express';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import BaseController from '@/http/controllers/BaseController';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
import BaseController from 'api/controllers/BaseController';
@Service()
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 { difference } from 'lodash';
import moment from 'moment';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
import JournalPoster from 'services/Accounting/JournalPoster';
import JournalEntry from 'services/Accounting/JournalEntry';
import {
mapViewRolesToConditionals,
mapFilterRolesToDynamicFilter,
} from '@/lib/ViewRolesBuilder';
} from 'lib/ViewRolesBuilder';
import {
DynamicFilter,
DynamicFilterSortBy,
DynamicFilterViews,
DynamicFilterFilterRoles,
} from '@/lib/DynamicFilter';
} from 'lib/DynamicFilter';
export default {
/**

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,17 @@
import { Response, Request } from 'express';
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 {
matchedBodyData(req: Request, options: any) {
matchedBodyData(req: Request, options: any = {}) {
const data = matchedData(req, {
locations: ['body'],
includeOptionals: true,
...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) {

View File

@@ -1,5 +1,5 @@
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 {

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ import {
} from 'express-validator';
import moment from 'moment';
import { difference } from 'lodash';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
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 { check, param, validationResult } from 'express-validator';
import ResourceField from '@/models/ResourceField';
import Resource from '@/models/Resource';
import ResourceField from 'models/ResourceField';
import Resource from 'models/Resource';
import asyncMiddleware from '../middleware/asyncMiddleware';
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ import {
import Container from 'typedi';
import fs from 'fs';
import { difference } from 'lodash';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
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 MomentFormat from 'lib/MomentFormats';
import moment from 'moment';
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 { Service, Inject } from 'typedi';
import { difference } from 'lodash';
import { BillOTD } from '@/interfaces';
import validateMiddleware from '@/http/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import BillsService from '@/services/Purchases/Bills';
import BaseController from '@/http/controllers/BaseController';
import ItemsService from '@/services/Items/ItemsService';
import TenancyService from '@/services/Tenancy/TenancyService';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import DynamicListing from '@/services/DynamicListing/DynamicListing';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/HasDynamicListing';
import { BillOTD } from 'interfaces';
import validateMiddleware from 'api/middleware/validateMiddleware';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
import BillsService from 'services/Purchases/Bills';
import BaseController from 'api/controllers/BaseController';
import ItemsService from 'services/Items/ItemsService';
import TenancyService from 'services/Tenancy/TenancyService';
import DynamicListingBuilder from 'services/DynamicListing/DynamicListingBuilder';
import DynamicListing from 'services/DynamicListing/DynamicListing';
import { dynamicListingErrorsToResponse } from 'services/DynamicListing/HasDynamicListing';
@Service()
export default class BillsController extends BaseController {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { Container } from 'typedi';
import { SystemUser } from '@/system/models';
import { Request, Response } from 'express';
/**
* Attach user to req.currentUser
@@ -9,10 +9,11 @@ import { SystemUser } from '@/system/models';
*/
const attachCurrentUser = async (req: Request, res: Response, next: Function) => {
const Logger = Container.get('logger');
const { systemUserRepository } = Container.get('repositories');
try {
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) {
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 { Container } from 'typedi';
import SettingsStore from '@/services/Settings/SettingsStore';
import SettingsStore from 'services/Settings/SettingsStore';
export default async (req: Request, res: Response, next: NextFunction) => {
const { tenantId } = req.user;
const { knex } = req;
console.log(knex);
const Logger = Container.get('logger');
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) => {
const { tenant, tenantId } = req;
const Logger = Container.get('logger');
const { subscriptionRepository } = Container.get('repositories');
if (!tenant) {
throw new Error('Should load `TenancyMiddlware` before this middleware.');
}
Logger.info('[subscription_middleware] trying get tenant main subscription.');
const subscription = await tenant
.$relatedQuery('subscriptions')
.modify('subscriptionBySlug', subscriptionSlug)
.first();
const subscription = await subscriptionRepository.getBySlugInTenant(subscriptionSlug, tenantId);
// Validate in case there is no any already 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 { Container } from 'typedi';
import jwt from 'jsonwebtoken';
import config from '@/../config/config';
import config from 'config';
const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
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 ResourceFieldMetadata from '@/models/ResourceFieldMetadata';
import MetableCollection from 'lib/Metable/MetableCollection';
import ResourceFieldMetadata from 'models/ResourceFieldMetadata';
export default class ResourceFieldMetadataCollection extends MetableCollection {
/**

100
server/src/config/index.js Normal file
View File

@@ -0,0 +1,100 @@
import dotenv from 'dotenv';
// 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
*/
port: parseInt(process.env.PORT, 10),
system: {
db_client: 'mysql',
db_host: '127.0.0.1',
db_user: 'root',
db_password: 'root',
db_name: 'bigcapital_system',
migrations_dir: './src/system/migrations',
seeds_dir: './src/system/seeds',
},
tenant: {
db_client: 'mysql',
db_name_prefix: 'bigcapital_tenant_',
db_host: '127.0.0.1',
db_user: 'root',
db_password: 'root',
charset: 'utf8',
migrations_dir: 'src/database/migrations',
seeds_dir: 'src/database/seeds/core',
seeds_table_name: 'seeds_versioning',
},
manager: {
superUser: 'root',
superPassword: 'root',
},
mail: {
host: 'smtp.mailtrap.io',
port: 587,
secure: false,
username: '842f331d3dc005',
password: '172f97b34f1a17',
},
mongoDb: {
/**
* That long string from mlab
*/
databaseURL: 'mongodb://localhost/bigcapital',
},
/**
* Agenda.js stuff
*/
agenda: {
dbCollection: process.env.AGENDA_DB_COLLECTION,
pooltime: process.env.AGENDA_POOL_TIME,
concurrency: parseInt(process.env.AGENDA_CONCURRENCY, 10),
},
/**
* Agendash config
*/
agendash: {
user: 'agendash',
password: '123456'
},
/**
* Subscription config.
*/
subscription: {
user: 'root',
password: 'root',
},
SMSGateway: {
type: '',
endpoint: '',
},
easySMSGateway: {
api_key: 'b0JDZW56RnV6aEthb0RGPXVEcUI'
},
jwtSecret: 'b0JDZW56RnV6aEthb0RGPXVEcUI',
contactUsMail: 'support@bigcapital.ly',
baseURL: 'https://bigcapital.ly',
api: {
prefix: '/api'
},
resetPasswordSeconds: 600,
licensesAuth: {
user: '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 { hashPassword } from '@/utils';
import { hashPassword } from 'utils';
export default (tenantDb) => {

View File

@@ -1,5 +1,5 @@
import KnexFactory from '@/lib/KnexFactory';
import systemDb from '@/database/knex';
import KnexFactory from 'lib/KnexFactory';
import systemDb from 'database/knex';
import faker from 'faker';
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) => {
table.increments();
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.string('currency_code', 3);
table.timestamps();
}).raw('ALTER TABLE `ACCOUNTS` AUTO_INCREMENT = 1000').then(() => {
return knex.seed.run({
specific: 'seed_accounts.js',
});
});
}).raw('ALTER TABLE `ACCOUNTS` AUTO_INCREMENT = 1000');
};
exports.down = (knex) => knex.schema.dropTableIfExists('accounts');

View File

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

View File

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

View File

@@ -8,11 +8,7 @@ exports.up = function (knex) {
table.boolean('favourite');
table.string('roles_logic_expression');
table.timestamps();
}).raw('ALTER TABLE `VIEWS` AUTO_INCREMENT = 1000').then(() => {
return knex.seed.run({
specific: 'seed_views.js',
});
});
}).raw('ALTER TABLE `VIEWS` AUTO_INCREMENT = 1000');
};
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) => {
console.log(knex.tenantId);
// Deletes ALL existing entries
return knex('accounts').del()
.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 PaymentInputInvalid from './PaymentInputInvalid';
import PaymentAmountInvalidWithPlan from './PaymentAmountInvalidWithPlan';
import TenantAlreadyInitialized from './TenantAlreadyInitialized';
import TenantAlreadySeeded from './TenantAlreadySeeded';
import TenantDBAlreadyExists from './TenantDBAlreadyExists';
import TenantDatabaseNotBuilt from './TenantDatabaseNotBuilt';
export {
NotAllowedChangeSubscriptionPlan,
@@ -11,5 +15,9 @@ export {
PaymentAmountInvalidWithPlan,
ServiceError,
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 {
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 {
firstName: string,
lastName: string,
password: string,
phoneNumber: string,
active: boolean,
email: string,
}
export interface IInviteUserInput {
firstName: string,
lastName: string,
phoneNumber: string,
password: string,
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { Container, Inject } from 'typedi';
import AuthenticationService from '@/services/Authentication';
import AuthenticationService from 'services/Authentication';
export default class WelcomeEmailJob {
/**

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { Container } from 'typedi';
import LicenseService from '@/services/Payment/License';
import LicenseService from 'services/Payment/License';
export default class SendLicenseViaEmailJob {
public async handler(job, done: Function): Promise<void> {

View File

@@ -1,5 +1,5 @@
import { Container } from 'typedi';
import LicenseService from '@/services/Payment/License';
import LicenseService from 'services/Payment/License';
export default class SendLicenseViaPhoneJob {
public async handler(job, done: Function): Promise<void> {

View File

@@ -1,5 +1,5 @@
import { Container, Inject } from 'typedi';
import InviteUserService from '@/services/InviteUsers';
import InviteUserService from 'services/InviteUsers';
export default class UserInviteMailJob {
@Inject()

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