diff --git a/.vscode/settings.json b/.vscode/settings.json index fa780b6a1..16d3c53ab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,5 +26,5 @@ "editor.tabSize": 2, "editor.insertSpaces": true, "git.ignoreLimitWarning": true, - + "god.tsconfig": "./tsconfig.json", } \ No newline at end of file diff --git a/server/bin/bigcapital.js b/server/bin/bigcapital.js index 0bc3d39a3..54a7c6014 100644 --- a/server/bin/bigcapital.js +++ b/server/bin/bigcapital.js @@ -1,15 +1,15 @@ -const commander = require('commander'); -const color = require('colorette'); -const argv = require('getopts')(process.argv.slice(2)); -const config = require('../config/config'); -const { +import commander from 'commander'; +import color from 'colorette'; +import argv from 'getopts' +import config from '../src/config'; +import { initSystemKnex, getAllSystemTenants, initTenantKnex, exit, success, log, -} = require('./utils'); +} from './utils'; // - bigcapital system:migrate:latest // - bigcapital system:migrate:rollback diff --git a/server/bin/utils.js b/server/bin/utils.js index eaf5ae4e8..3c3dff0e2 100644 --- a/server/bin/utils.js +++ b/server/bin/utils.js @@ -1,13 +1,26 @@ -const Knex = require('knex'); -const { knexSnakeCaseMappers } = require('objection'); -const color = require('colorette'); -const config = require('../config/config'); -const systemConfig = require('../config/systemKnexfile'); - +import Knex from 'knex'; +import { knexSnakeCaseMappers } from 'objection'; +import color from 'colorette'; +import config from '../src/config'; +// import { systemKnexConfig } from '../src/config/knexConfig'; function initSystemKnex() { 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 }), }); } @@ -70,7 +83,7 @@ function getDeepValue(prop, obj) { }, []); } -module.exports = { +export { initTenantKnex, initSystemKnex, getAllSystemTenants, diff --git a/server/config/index.js b/server/config/index.js deleted file mode 100644 index 308213312..000000000 --- a/server/config/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import path from 'path'; -import dotenv from 'dotenv'; - -dotenv.config({ - path: path.resolve(process.cwd(), '.env.test'), -}); diff --git a/server/config/systemKnexfile.js b/server/config/systemKnexfile.js deleted file mode 100644 index 7df56e7cc..000000000 --- a/server/config/systemKnexfile.js +++ /dev/null @@ -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, -}; diff --git a/server/nodemon.json b/server/nodemon.json new file mode 100644 index 000000000..b4bc4681a --- /dev/null +++ b/server/nodemon.json @@ -0,0 +1,11 @@ +{ + "watch": [ + "src", + ".env" + ], + "ext": "js,ts,json", + "ignore": [ + "src/**/*.spec.ts" + ], + "exec": "ts-node --transpile-only ./src/server.ts" +} \ No newline at end of file diff --git a/server/package.json b/server/package.json index c4f8d0ea4..3a7aa61a6 100644 --- a/server/package.json +++ b/server/package.json @@ -1,15 +1,12 @@ { "name": "bigcapital-server", - "version": "1.0.0", + "version": "0.0.1", "description": "", - "main": "index.js", + "main": "src/server.ts", "scripts": { "build": "webpack", - "start": "npm-run-all --parallel watch:server watch:build", - "watch:build": "webpack --watch", - "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" + "start": "cross-env NODE_PATH=./src nodemon", + "inspect": "cross-env NODE_PATH=./src nodemon --inspect src/server.ts" }, "author": "Ahmed Bouhuolia, ", "license": "ISC", @@ -29,10 +26,12 @@ "bookshelf-json-columns": "^2.1.1", "bookshelf-modelbase": "^2.10.4", "bookshelf-paranoia": "^0.13.1", + "compression": "^1.7.4", "crypto-random-string": "^3.2.0", "csurf": "^1.10.0", "dotenv": "^8.1.0", "errorhandler": "^1.5.1", + "esm": "^3.2.25", "event-dispatch": "^0.4.1", "express": "^4.17.1", "express-basic-auth": "^1.2.0", @@ -66,14 +65,7 @@ "winston": "^3.2.1" }, "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", - "babel-loader": "^8.0.6", "chai": "^4.2.0", "chai-http": "^4.3.0", "chai-things": "^0.2.0", @@ -90,15 +82,11 @@ "getopts": "^2.2.5", "knex-factory": "0.0.6", "mocha": "^5.2.0", - "mocha-webpack": "^2.0.0-beta.0", - "npm-run-all": "^4.1.5", + "module-alias": "^2.2.2", "nyc": "^14.1.1", "sinon": "^7.4.2", - "ts-loader": "^8.0.1", + "ts-node": "^9.0.0", "typedi": "^0.8.0", - "typescript": "^3.9.7", - "webpack": "^4.0.0", - "webpack-cli": "^3.3.7", - "webpack-node-externals": "^1.7.2" + "typescript": "^3.9.7" } } diff --git a/server/src/http/controllers/AccountTypes.ts b/server/src/api/controllers/AccountTypes.ts similarity index 82% rename from server/src/http/controllers/AccountTypes.ts rename to server/src/api/controllers/AccountTypes.ts index a2886b7aa..34e658c11 100644 --- a/server/src/http/controllers/AccountTypes.ts +++ b/server/src/api/controllers/AccountTypes.ts @@ -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{ diff --git a/server/src/http/controllers/Accounting.js b/server/src/api/controllers/Accounting.js similarity index 99% rename from server/src/http/controllers/Accounting.js rename to server/src/api/controllers/Accounting.js index 60f63dedb..0314e4318 100644 --- a/server/src/http/controllers/Accounting.js +++ b/server/src/api/controllers/Accounting.js @@ -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 { /** diff --git a/server/src/http/controllers/Accounts.ts b/server/src/api/controllers/Accounts.ts similarity index 97% rename from server/src/http/controllers/Accounts.ts rename to server/src/api/controllers/Accounts.ts index e0e23fbe1..8ae7d2561 100644 --- a/server/src/http/controllers/Accounts.ts +++ b/server/src/api/controllers/Accounts.ts @@ -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); } diff --git a/server/src/http/controllers/Agendash.ts b/server/src/api/controllers/Agendash.ts similarity index 92% rename from server/src/http/controllers/Agendash.ts rename to server/src/api/controllers/Agendash.ts index 4365d3ae4..ba566e83f 100644 --- a/server/src/http/controllers/Agendash.ts +++ b/server/src/api/controllers/Agendash.ts @@ -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 { diff --git a/server/src/http/controllers/Authentication.ts b/server/src/api/controllers/Authentication.ts similarity index 86% rename from server/src/http/controllers/Authentication.ts rename to server/src/api/controllers/Authentication.ts index 1cbbe3cf2..1285574e0 100644 --- a/server/src/http/controllers/Authentication.ts +++ b/server/src/api/controllers/Authentication.ts @@ -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); } } }; diff --git a/server/src/http/controllers/BaseController.ts b/server/src/api/controllers/BaseController.ts similarity index 77% rename from server/src/http/controllers/BaseController.ts rename to server/src/api/controllers/BaseController.ts index 834f877e4..73b5ced36 100644 --- a/server/src/http/controllers/BaseController.ts +++ b/server/src/api/controllers/BaseController.ts @@ -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) { diff --git a/server/src/http/controllers/Contacts/Contacts.ts b/server/src/api/controllers/Contacts/Contacts.ts similarity index 96% rename from server/src/http/controllers/Contacts/Contacts.ts rename to server/src/api/controllers/Contacts/Contacts.ts index 533278f45..ccde5c555 100644 --- a/server/src/http/controllers/Contacts/Contacts.ts +++ b/server/src/api/controllers/Contacts/Contacts.ts @@ -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 { diff --git a/server/src/http/controllers/Contacts/Customers.ts b/server/src/api/controllers/Contacts/Customers.ts similarity index 94% rename from server/src/http/controllers/Contacts/Customers.ts rename to server/src/api/controllers/Contacts/Customers.ts index 0b8aa1f4f..8db9af516 100644 --- a/server/src/http/controllers/Contacts/Customers.ts +++ b/server/src/api/controllers/Contacts/Customers.ts @@ -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) { diff --git a/server/src/http/controllers/Contacts/Vendors.ts b/server/src/api/controllers/Contacts/Vendors.ts similarity index 91% rename from server/src/http/controllers/Contacts/Vendors.ts rename to server/src/api/controllers/Contacts/Vendors.ts index d4166e53e..9e49ad7ea 100644 --- a/server/src/http/controllers/Contacts/Vendors.ts +++ b/server/src/api/controllers/Contacts/Vendors.ts @@ -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') { diff --git a/server/src/http/controllers/Currencies.js b/server/src/api/controllers/Currencies.js similarity index 98% rename from server/src/http/controllers/Currencies.js rename to server/src/api/controllers/Currencies.js index b58a6514e..ddafd824c 100644 --- a/server/src/http/controllers/Currencies.js +++ b/server/src/api/controllers/Currencies.js @@ -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 { /** diff --git a/server/src/http/controllers/ExchangeRates.js b/server/src/api/controllers/ExchangeRates.js similarity index 98% rename from server/src/http/controllers/ExchangeRates.js rename to server/src/api/controllers/ExchangeRates.js index 7d955da7c..157eec63c 100644 --- a/server/src/http/controllers/ExchangeRates.js +++ b/server/src/api/controllers/ExchangeRates.js @@ -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 { /** diff --git a/server/src/api/controllers/Expenses.ts b/server/src/api/controllers/Expenses.ts new file mode 100644 index 000000000..f0f3d4a8c --- /dev/null +++ b/server/src/api/controllers/Expenses.ts @@ -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' }] + }); + } + } +} \ No newline at end of file diff --git a/server/src/http/controllers/Fields.js b/server/src/api/controllers/Fields.js similarity index 98% rename from server/src/http/controllers/Fields.js rename to server/src/api/controllers/Fields.js index 606396755..442fb074f 100644 --- a/server/src/http/controllers/Fields.js +++ b/server/src/api/controllers/Fields.js @@ -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'; /** diff --git a/server/src/http/controllers/FinancialStatements.js b/server/src/api/controllers/FinancialStatements.js similarity index 100% rename from server/src/http/controllers/FinancialStatements.js rename to server/src/api/controllers/FinancialStatements.js diff --git a/server/src/http/controllers/FinancialStatements/AgingReport.js b/server/src/api/controllers/FinancialStatements/AgingReport.js similarity index 97% rename from server/src/http/controllers/FinancialStatements/AgingReport.js rename to server/src/api/controllers/FinancialStatements/AgingReport.js index fa31d2275..41cb37eab 100644 --- a/server/src/http/controllers/FinancialStatements/AgingReport.js +++ b/server/src/api/controllers/FinancialStatements/AgingReport.js @@ -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{ diff --git a/server/src/http/controllers/FinancialStatements/BalanceSheet.js b/server/src/api/controllers/FinancialStatements/BalanceSheet.js similarity index 97% rename from server/src/http/controllers/FinancialStatements/BalanceSheet.js rename to server/src/api/controllers/FinancialStatements/BalanceSheet.js index 397e5cd7c..9948ab3a1 100644 --- a/server/src/http/controllers/FinancialStatements/BalanceSheet.js +++ b/server/src/api/controllers/FinancialStatements/BalanceSheet.js @@ -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 { /** diff --git a/server/src/http/controllers/FinancialStatements/FinancialStatementMixin.js b/server/src/api/controllers/FinancialStatements/FinancialStatementMixin.js similarity index 100% rename from server/src/http/controllers/FinancialStatements/FinancialStatementMixin.js rename to server/src/api/controllers/FinancialStatements/FinancialStatementMixin.js diff --git a/server/src/http/controllers/FinancialStatements/GeneralLedger.js b/server/src/api/controllers/FinancialStatements/GeneralLedger.js similarity index 96% rename from server/src/http/controllers/FinancialStatements/GeneralLedger.js rename to server/src/api/controllers/FinancialStatements/GeneralLedger.js index 0a3d7202f..fb90a3010 100644 --- a/server/src/http/controllers/FinancialStatements/GeneralLedger.js +++ b/server/src/api/controllers/FinancialStatements/GeneralLedger.js @@ -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 { /** diff --git a/server/src/http/controllers/FinancialStatements/InventoryValuationSummary.js b/server/src/api/controllers/FinancialStatements/InventoryValuationSummary.js similarity index 100% rename from server/src/http/controllers/FinancialStatements/InventoryValuationSummary.js rename to server/src/api/controllers/FinancialStatements/InventoryValuationSummary.js diff --git a/server/src/http/controllers/FinancialStatements/JournalSheet.js b/server/src/api/controllers/FinancialStatements/JournalSheet.js similarity index 96% rename from server/src/http/controllers/FinancialStatements/JournalSheet.js rename to server/src/api/controllers/FinancialStatements/JournalSheet.js index 1b2460784..bf76acc74 100644 --- a/server/src/http/controllers/FinancialStatements/JournalSheet.js +++ b/server/src/api/controllers/FinancialStatements/JournalSheet.js @@ -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'; diff --git a/server/src/http/controllers/FinancialStatements/PayableAgingSummary.js b/server/src/api/controllers/FinancialStatements/PayableAgingSummary.js similarity index 96% rename from server/src/http/controllers/FinancialStatements/PayableAgingSummary.js rename to server/src/api/controllers/FinancialStatements/PayableAgingSummary.js index 196a3d14b..be5142b93 100644 --- a/server/src/http/controllers/FinancialStatements/PayableAgingSummary.js +++ b/server/src/api/controllers/FinancialStatements/PayableAgingSummary.js @@ -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 { diff --git a/server/src/http/controllers/FinancialStatements/ProfitLossSheet.js b/server/src/api/controllers/FinancialStatements/ProfitLossSheet.js similarity index 97% rename from server/src/http/controllers/FinancialStatements/ProfitLossSheet.js rename to server/src/api/controllers/FinancialStatements/ProfitLossSheet.js index 9b70ea808..82a5fa1be 100644 --- a/server/src/http/controllers/FinancialStatements/ProfitLossSheet.js +++ b/server/src/api/controllers/FinancialStatements/ProfitLossSheet.js @@ -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 { diff --git a/server/src/http/controllers/FinancialStatements/ReceivableAgingSummary.js b/server/src/api/controllers/FinancialStatements/ReceivableAgingSummary.js similarity index 96% rename from server/src/http/controllers/FinancialStatements/ReceivableAgingSummary.js rename to server/src/api/controllers/FinancialStatements/ReceivableAgingSummary.js index e26a89bb0..991fd6823 100644 --- a/server/src/http/controllers/FinancialStatements/ReceivableAgingSummary.js +++ b/server/src/api/controllers/FinancialStatements/ReceivableAgingSummary.js @@ -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 { diff --git a/server/src/http/controllers/FinancialStatements/TrialBalanceSheet.js b/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.js similarity index 95% rename from server/src/http/controllers/FinancialStatements/TrialBalanceSheet.js rename to server/src/api/controllers/FinancialStatements/TrialBalanceSheet.js index a83aa0986..8df585262 100644 --- a/server/src/http/controllers/FinancialStatements/TrialBalanceSheet.js +++ b/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.js @@ -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 { diff --git a/server/src/http/controllers/InviteUsers.ts b/server/src/api/controllers/InviteUsers.ts similarity index 91% rename from server/src/http/controllers/InviteUsers.ts rename to server/src/api/controllers/InviteUsers.ts index 04ced44ea..1ffc8a065 100644 --- a/server/src/http/controllers/InviteUsers.ts +++ b/server/src/api/controllers/InviteUsers.ts @@ -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({ diff --git a/server/src/http/controllers/ItemCategories.ts b/server/src/api/controllers/ItemCategories.ts similarity index 97% rename from server/src/http/controllers/ItemCategories.ts rename to server/src/api/controllers/ItemCategories.ts index 2542fe68f..f56a93073 100644 --- a/server/src/http/controllers/ItemCategories.ts +++ b/server/src/api/controllers/ItemCategories.ts @@ -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 { diff --git a/server/src/http/controllers/Items.ts b/server/src/api/controllers/Items.ts similarity index 96% rename from server/src/http/controllers/Items.ts rename to server/src/api/controllers/Items.ts index 7a9b5accf..092a53489 100644 --- a/server/src/http/controllers/Items.ts +++ b/server/src/api/controllers/Items.ts @@ -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 { diff --git a/server/src/http/controllers/Media.js b/server/src/api/controllers/Media.js similarity index 98% rename from server/src/http/controllers/Media.js rename to server/src/api/controllers/Media.js index bddf125b8..94631e804 100644 --- a/server/src/http/controllers/Media.js +++ b/server/src/api/controllers/Media.js @@ -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; diff --git a/server/src/api/controllers/Organization.ts b/server/src/api/controllers/Organization.ts new file mode 100644 index 000000000..9ff614029 --- /dev/null +++ b/server/src/api/controllers/Organization.ts @@ -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); + } + } +} \ No newline at end of file diff --git a/server/src/http/controllers/Ping.ts b/server/src/api/controllers/Ping.ts similarity index 85% rename from server/src/http/controllers/Ping.ts rename to server/src/api/controllers/Ping.ts index 0dd89f3f0..bf7a9b608 100644 --- a/server/src/http/controllers/Ping.ts +++ b/server/src/api/controllers/Ping.ts @@ -1,4 +1,6 @@ import { Router, Request, Response } from 'express'; +import MomentFormat from 'lib/MomentFormats'; +import moment from 'moment'; export default class Ping { /** diff --git a/server/src/http/controllers/Purchases/Bills.ts b/server/src/api/controllers/Purchases/Bills.ts similarity index 94% rename from server/src/http/controllers/Purchases/Bills.ts rename to server/src/api/controllers/Purchases/Bills.ts index 1a659601d..aa8d6cbef 100644 --- a/server/src/http/controllers/Purchases/Bills.ts +++ b/server/src/api/controllers/Purchases/Bills.ts @@ -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 { diff --git a/server/src/http/controllers/Purchases/BillsPayments.ts b/server/src/api/controllers/Purchases/BillsPayments.ts similarity index 95% rename from server/src/http/controllers/Purchases/BillsPayments.ts rename to server/src/api/controllers/Purchases/BillsPayments.ts index bb583eead..c9f395fc0 100644 --- a/server/src/http/controllers/Purchases/BillsPayments.ts +++ b/server/src/api/controllers/Purchases/BillsPayments.ts @@ -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. diff --git a/server/src/http/controllers/Purchases/index.ts b/server/src/api/controllers/Purchases/index.ts similarity index 69% rename from server/src/http/controllers/Purchases/index.ts rename to server/src/api/controllers/Purchases/index.ts index 9660dda47..5333b2f84 100644 --- a/server/src/http/controllers/Purchases/index.ts +++ b/server/src/api/controllers/Purchases/index.ts @@ -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 { diff --git a/server/src/http/controllers/Resources.js b/server/src/api/controllers/Resources.js similarity index 97% rename from server/src/http/controllers/Resources.js rename to server/src/api/controllers/Resources.js index 1826091c8..bf1063a41 100644 --- a/server/src/http/controllers/Resources.js +++ b/server/src/api/controllers/Resources.js @@ -3,7 +3,7 @@ import { param, query, } from 'express-validator'; -import asyncMiddleware from '@/http/middleware/asyncMiddleware'; +import asyncMiddleware from 'api/middleware/asyncMiddleware'; export default { /** diff --git a/server/src/http/controllers/Sales/PaymentReceives.ts b/server/src/api/controllers/Sales/PaymentReceives.ts similarity index 94% rename from server/src/http/controllers/Sales/PaymentReceives.ts rename to server/src/api/controllers/Sales/PaymentReceives.ts index f54a7af60..51302fc37 100644 --- a/server/src/http/controllers/Sales/PaymentReceives.ts +++ b/server/src/api/controllers/Sales/PaymentReceives.ts @@ -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; diff --git a/server/src/http/controllers/Sales/SalesEstimates.ts b/server/src/api/controllers/Sales/SalesEstimates.ts similarity index 94% rename from server/src/http/controllers/Sales/SalesEstimates.ts rename to server/src/api/controllers/Sales/SalesEstimates.ts index 2fbb83405..514a5311e 100644 --- a/server/src/http/controllers/Sales/SalesEstimates.ts +++ b/server/src/api/controllers/Sales/SalesEstimates.ts @@ -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. */ diff --git a/server/src/http/controllers/Sales/SalesInvoices.ts b/server/src/api/controllers/Sales/SalesInvoices.ts similarity index 96% rename from server/src/http/controllers/Sales/SalesInvoices.ts rename to server/src/api/controllers/Sales/SalesInvoices.ts index b0ebb2196..1230fa329 100644 --- a/server/src/http/controllers/Sales/SalesInvoices.ts +++ b/server/src/api/controllers/Sales/SalesInvoices.ts @@ -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 { diff --git a/server/src/http/controllers/Sales/SalesReceipts.ts b/server/src/api/controllers/Sales/SalesReceipts.ts similarity index 95% rename from server/src/http/controllers/Sales/SalesReceipts.ts rename to server/src/api/controllers/Sales/SalesReceipts.ts index b592778bb..25d286cb6 100644 --- a/server/src/http/controllers/Sales/SalesReceipts.ts +++ b/server/src/api/controllers/Sales/SalesReceipts.ts @@ -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 { diff --git a/server/src/http/controllers/Sales/index.ts b/server/src/api/controllers/Sales/index.ts similarity index 100% rename from server/src/http/controllers/Sales/index.ts rename to server/src/api/controllers/Sales/index.ts diff --git a/server/src/http/controllers/Settings.ts b/server/src/api/controllers/Settings.ts similarity index 92% rename from server/src/http/controllers/Settings.ts rename to server/src/api/controllers/Settings.ts index 90c8b8676..276d25199 100644 --- a/server/src/http/controllers/Settings.ts +++ b/server/src/api/controllers/Settings.ts @@ -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{ /** diff --git a/server/src/http/controllers/Subscription/Licenses.ts b/server/src/api/controllers/Subscription/Licenses.ts similarity index 94% rename from server/src/http/controllers/Subscription/Licenses.ts rename to server/src/api/controllers/Subscription/Licenses.ts index ae0604447..ea9a3e1a0 100644 --- a/server/src/http/controllers/Subscription/Licenses.ts +++ b/server/src/api/controllers/Subscription/Licenses.ts @@ -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 { diff --git a/server/src/http/controllers/Subscription/PaymentMethod.ts b/server/src/api/controllers/Subscription/PaymentMethod.ts similarity index 79% rename from server/src/http/controllers/Subscription/PaymentMethod.ts rename to server/src/api/controllers/Subscription/PaymentMethod.ts index fb01e7fc3..fdc1b0c53 100644 --- a/server/src/http/controllers/Subscription/PaymentMethod.ts +++ b/server/src/api/controllers/Subscription/PaymentMethod.ts @@ -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() diff --git a/server/src/http/controllers/Subscription/PaymentViaLicense.ts b/server/src/api/controllers/Subscription/PaymentViaLicense.ts similarity index 89% rename from server/src/http/controllers/Subscription/PaymentViaLicense.ts rename to server/src/api/controllers/Subscription/PaymentViaLicense.ts index 0c99b5505..56ceb1345 100644 --- a/server/src/http/controllers/Subscription/PaymentViaLicense.ts +++ b/server/src/api/controllers/Subscription/PaymentViaLicense.ts @@ -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 { diff --git a/server/src/http/controllers/Subscription/index.ts b/server/src/api/controllers/Subscription/index.ts similarity index 58% rename from server/src/http/controllers/Subscription/index.ts rename to server/src/api/controllers/Subscription/index.ts index 683eb39a4..1154de6a7 100644 --- a/server/src/http/controllers/Subscription/index.ts +++ b/server/src/api/controllers/Subscription/index.ts @@ -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 { diff --git a/server/src/http/controllers/Users.ts b/server/src/api/controllers/Users.ts similarity index 95% rename from server/src/http/controllers/Users.ts rename to server/src/api/controllers/Users.ts index 4783a1cdb..ec223be12 100644 --- a/server/src/http/controllers/Users.ts +++ b/server/src/api/controllers/Users.ts @@ -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); } } diff --git a/server/src/http/controllers/Views.js b/server/src/api/controllers/Views.js similarity index 99% rename from server/src/http/controllers/Views.js rename to server/src/api/controllers/Views.js index b5fee01e7..68063e77f 100644 --- a/server/src/http/controllers/Views.js +++ b/server/src/api/controllers/Views.js @@ -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', diff --git a/server/src/http/controllers/Expenses.js b/server/src/api/controllers/_Expenses.js similarity index 98% rename from server/src/http/controllers/Expenses.js rename to server/src/api/controllers/_Expenses.js index 70a05022b..ab9a211b9 100644 --- a/server/src/http/controllers/Expenses.js +++ b/server/src/api/controllers/_Expenses.js @@ -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 { /** diff --git a/server/src/http/index.ts b/server/src/api/index.ts similarity index 52% rename from server/src/http/index.ts rename to server/src/api/index.ts index 2035983d3..6895803ce 100644 --- a/server/src/http/index.ts +++ b/server/src/api/index.ts @@ -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()); diff --git a/server/src/http/middleware/AttachCurrentTenantUser.ts b/server/src/api/middleware/AttachCurrentTenantUser.ts similarity index 82% rename from server/src/http/middleware/AttachCurrentTenantUser.ts rename to server/src/api/middleware/AttachCurrentTenantUser.ts index f2da447d2..20c626e82 100644 --- a/server/src/http/middleware/AttachCurrentTenantUser.ts +++ b/server/src/api/middleware/AttachCurrentTenantUser.ts @@ -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.'); diff --git a/server/src/http/middleware/ConfiguredMiddleware.ts b/server/src/api/middleware/ConfiguredMiddleware.ts similarity index 100% rename from server/src/http/middleware/ConfiguredMiddleware.ts rename to server/src/api/middleware/ConfiguredMiddleware.ts diff --git a/server/src/api/middleware/EnsureTenantIsInitialized.ts b/server/src/api/middleware/EnsureTenantIsInitialized.ts new file mode 100644 index 000000000..189bb8013 --- /dev/null +++ b/server/src/api/middleware/EnsureTenantIsInitialized.ts @@ -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(); +}; \ No newline at end of file diff --git a/server/src/http/middleware/I18nMiddleware.ts b/server/src/api/middleware/I18nMiddleware.ts similarity index 100% rename from server/src/http/middleware/I18nMiddleware.ts rename to server/src/api/middleware/I18nMiddleware.ts diff --git a/server/src/http/middleware/LoggerMiddleware.ts b/server/src/api/middleware/LoggerMiddleware.ts similarity index 100% rename from server/src/http/middleware/LoggerMiddleware.ts rename to server/src/api/middleware/LoggerMiddleware.ts diff --git a/server/src/http/middleware/SettingsMiddleware.ts b/server/src/api/middleware/SettingsMiddleware.ts similarity index 91% rename from server/src/http/middleware/SettingsMiddleware.ts rename to server/src/api/middleware/SettingsMiddleware.ts index 250761eb9..39f4437fb 100644 --- a/server/src/http/middleware/SettingsMiddleware.ts +++ b/server/src/api/middleware/SettingsMiddleware.ts @@ -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}`); diff --git a/server/src/http/middleware/SubscriptionMiddleware.ts b/server/src/api/middleware/SubscriptionMiddleware.ts similarity index 86% rename from server/src/http/middleware/SubscriptionMiddleware.ts rename to server/src/api/middleware/SubscriptionMiddleware.ts index 0abde5237..17e2ee5c8 100644 --- a/server/src/http/middleware/SubscriptionMiddleware.ts +++ b/server/src/api/middleware/SubscriptionMiddleware.ts @@ -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) { diff --git a/server/src/api/middleware/TenancyMiddleware.ts b/server/src/api/middleware/TenancyMiddleware.ts new file mode 100644 index 000000000..96f390f4f --- /dev/null +++ b/server/src/api/middleware/TenancyMiddleware.ts @@ -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(); +} diff --git a/server/src/api/middleware/TenantDependencyInjection.ts b/server/src/api/middleware/TenantDependencyInjection.ts new file mode 100644 index 000000000..fed2b4219 --- /dev/null +++ b/server/src/api/middleware/TenantDependencyInjection.ts @@ -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; +} \ No newline at end of file diff --git a/server/src/http/middleware/asyncMiddleware.ts b/server/src/api/middleware/asyncMiddleware.ts similarity index 100% rename from server/src/http/middleware/asyncMiddleware.ts rename to server/src/api/middleware/asyncMiddleware.ts diff --git a/server/src/http/middleware/jwtAuth.ts b/server/src/api/middleware/jwtAuth.ts similarity index 95% rename from server/src/http/middleware/jwtAuth.ts rename to server/src/api/middleware/jwtAuth.ts index c77709c01..8189c9b2a 100644 --- a/server/src/http/middleware/jwtAuth.ts +++ b/server/src/api/middleware/jwtAuth.ts @@ -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'); diff --git a/server/src/http/middleware/validateMiddleware.js b/server/src/api/middleware/validateMiddleware.js similarity index 100% rename from server/src/http/middleware/validateMiddleware.js rename to server/src/api/middleware/validateMiddleware.js diff --git a/server/src/before.ts b/server/src/before.ts new file mode 100644 index 000000000..9d753e36a --- /dev/null +++ b/server/src/before.ts @@ -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'); +}; diff --git a/server/src/collection/ResourceFieldMetadataCollection.js b/server/src/collection/ResourceFieldMetadataCollection.js index fc48fd9b8..b1d460111 100644 --- a/server/src/collection/ResourceFieldMetadataCollection.js +++ b/server/src/collection/ResourceFieldMetadataCollection.js @@ -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 { /** diff --git a/server/config/config.js b/server/src/config/index.js similarity index 80% rename from server/config/config.js rename to server/src/config/index.js index 5ad33c6ed..9010e73e2 100644 --- a/server/config/config.js +++ b/server/src/config/index.js @@ -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 */ @@ -22,7 +32,8 @@ module.exports = { db_password: 'root', charset: 'utf8', migrations_dir: 'src/database/migrations', - seeds_dir: 'src/database/seeds', + seeds_dir: 'src/database/seeds/core', + seeds_table_name: 'seeds_versioning', }, manager: { superUser: 'root', @@ -85,5 +96,5 @@ module.exports = { licensesAuth: { user: 'admin', password: 'admin', - } + }, }; diff --git a/server/src/config/knexConfig.ts b/server/src/config/knexConfig.ts new file mode 100644 index 000000000..a0b6dd27f --- /dev/null +++ b/server/src/config/knexConfig.ts @@ -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, + }; +} \ No newline at end of file diff --git a/server/src/database/factories/index.js b/server/src/database/factories/index.js index 070c58d21..34a49fce6 100644 --- a/server/src/database/factories/index.js +++ b/server/src/database/factories/index.js @@ -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) => { diff --git a/server/src/database/factories/system.js b/server/src/database/factories/system.js index ccde0f943..890202827 100644 --- a/server/src/database/factories/system.js +++ b/server/src/database/factories/system.js @@ -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 () => { diff --git a/server/src/database/manager.js b/server/src/database/manager.js deleted file mode 100644 index 9a002432e..000000000 --- a/server/src/database/manager.js +++ /dev/null @@ -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, - }, -}); \ No newline at end of file diff --git a/server/src/database/migrations/20190423085240_create_resources_table.js b/server/src/database/migrations/20190423085240_create_resources_table.js index 21070b573..1f9b99d40 100644 --- a/server/src/database/migrations/20190423085240_create_resources_table.js +++ b/server/src/database/migrations/20190423085240_create_resources_table.js @@ -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', - }); }); }; diff --git a/server/src/database/migrations/20190822214304_create_accounts_table.js b/server/src/database/migrations/20190822214304_create_accounts_table.js index fe188a06b..038116275 100644 --- a/server/src/database/migrations/20190822214304_create_accounts_table.js +++ b/server/src/database/migrations/20190822214304_create_accounts_table.js @@ -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'); diff --git a/server/src/database/migrations/20190822214904_create_account_types_table.js b/server/src/database/migrations/20190822214904_create_account_types_table.js index 234e45830..c580efe83 100644 --- a/server/src/database/migrations/20190822214904_create_account_types_table.js +++ b/server/src/database/migrations/20190822214904_create_account_types_table.js @@ -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'); diff --git a/server/src/database/migrations/20190822214905_create_resource_fields_table.js b/server/src/database/migrations/20190822214905_create_resource_fields_table.js index 1796ffe63..4087dde75 100644 --- a/server/src/database/migrations/20190822214905_create_resource_fields_table.js +++ b/server/src/database/migrations/20190822214905_create_resource_fields_table.js @@ -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'); diff --git a/server/src/database/migrations/20190822214905_create_views_columns.js b/server/src/database/migrations/20190822214905_create_views_columns.js index 500fbc27d..1fddbaca1 100644 --- a/server/src/database/migrations/20190822214905_create_views_columns.js +++ b/server/src/database/migrations/20190822214905_create_views_columns.js @@ -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'); diff --git a/server/src/database/migrations/20190822214905_create_views_table.js b/server/src/database/migrations/20190822214905_create_views_table.js index c4e796c90..26deaef69 100644 --- a/server/src/database/migrations/20190822214905_create_views_table.js +++ b/server/src/database/migrations/20190822214905_create_views_table.js @@ -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'); diff --git a/server/src/database/migrations/20200607212203_create_contacts_table.js b/server/src/database/migrations/20200607212203_create_contacts_table.js deleted file mode 100644 index ccc7e13f0..000000000 --- a/server/src/database/migrations/20200607212203_create_contacts_table.js +++ /dev/null @@ -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'); -}; diff --git a/server/src/database/migrations/20200607212203_create_customers_table.js b/server/src/database/migrations/20200607212203_create_customers_table.js deleted file mode 100644 index 4ea255665..000000000 --- a/server/src/database/migrations/20200607212203_create_customers_table.js +++ /dev/null @@ -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'); -}; diff --git a/server/src/database/seeds/core/20190423085240_seed_accounts.js b/server/src/database/seeds/core/20190423085240_seed_accounts.js new file mode 100644 index 000000000..cce8e6d27 --- /dev/null +++ b/server/src/database/seeds/core/20190423085240_seed_accounts.js @@ -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) { + +}; diff --git a/server/src/database/seeds/core/20190423085241_seed_accounts_types.js b/server/src/database/seeds/core/20190423085241_seed_accounts_types.js new file mode 100644 index 000000000..fd03d2f7d --- /dev/null +++ b/server/src/database/seeds/core/20190423085241_seed_accounts_types.js @@ -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) { + +} \ No newline at end of file diff --git a/server/src/database/seeds/seed_account_types.js b/server/src/database/seeds/seed_account_types.js deleted file mode 100644 index a306d9167..000000000 --- a/server/src/database/seeds/seed_account_types.js +++ /dev/null @@ -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, - }, - ]); - }); -}; diff --git a/server/src/database/seeds/seed_accounts.js b/server/src/database/seeds/seed_accounts.js index 44ca2ed86..9189dc4b0 100644 --- a/server/src/database/seeds/seed_accounts.js +++ b/server/src/database/seeds/seed_accounts.js @@ -1,5 +1,7 @@ exports.seed = (knex) => { + console.log(knex.tenantId); + // Deletes ALL existing entries return knex('accounts').del() .then(() => { diff --git a/server/src/exceptions/TenantAlreadyInitialized.ts b/server/src/exceptions/TenantAlreadyInitialized.ts new file mode 100644 index 000000000..72c11f810 --- /dev/null +++ b/server/src/exceptions/TenantAlreadyInitialized.ts @@ -0,0 +1,7 @@ + + +export default class TenantAlreadyInitialized { + constructor() { + + } +} \ No newline at end of file diff --git a/server/src/exceptions/TenantAlreadySeeded.ts b/server/src/exceptions/TenantAlreadySeeded.ts new file mode 100644 index 000000000..b4fac0bb0 --- /dev/null +++ b/server/src/exceptions/TenantAlreadySeeded.ts @@ -0,0 +1,9 @@ + + + + +export default class TenantAlreadySeeded { + constructor() { + + } +} \ No newline at end of file diff --git a/server/src/exceptions/TenantDBAlreadyExists.ts b/server/src/exceptions/TenantDBAlreadyExists.ts new file mode 100644 index 000000000..72c51890d --- /dev/null +++ b/server/src/exceptions/TenantDBAlreadyExists.ts @@ -0,0 +1,9 @@ + + + + +export default class TenantDBAlreadyExists { + constructor() { + + } +} \ No newline at end of file diff --git a/server/src/exceptions/TenantDatabaseNotBuilt.ts b/server/src/exceptions/TenantDatabaseNotBuilt.ts new file mode 100644 index 000000000..8b655e10a --- /dev/null +++ b/server/src/exceptions/TenantDatabaseNotBuilt.ts @@ -0,0 +1,7 @@ + + +export default class TenantDatabaseNotBuilt { + constructor() { + + } +} \ No newline at end of file diff --git a/server/src/exceptions/index.ts b/server/src/exceptions/index.ts index 2fe026caa..0ec59b132 100644 --- a/server/src/exceptions/index.ts +++ b/server/src/exceptions/index.ts @@ -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, }; \ No newline at end of file diff --git a/server/src/http/controllers/Organization.ts b/server/src/http/controllers/Organization.ts deleted file mode 100644 index 2536dd175..000000000 --- a/server/src/http/controllers/Organization.ts +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/server/src/http/middleware/EnsureTenantIsInitialized.ts b/server/src/http/middleware/EnsureTenantIsInitialized.ts deleted file mode 100644 index 086bfd8fc..000000000 --- a/server/src/http/middleware/EnsureTenantIsInitialized.ts +++ /dev/null @@ -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(); -}; \ No newline at end of file diff --git a/server/src/http/middleware/TenancyMiddleware.ts b/server/src/http/middleware/TenancyMiddleware.ts deleted file mode 100644 index 3cf4f1191..000000000 --- a/server/src/http/middleware/TenancyMiddleware.ts +++ /dev/null @@ -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(); -} diff --git a/server/src/interfaces/Expenses.ts b/server/src/interfaces/Expenses.ts new file mode 100644 index 000000000..b32109d46 --- /dev/null +++ b/server/src/interfaces/Expenses.ts @@ -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; + editExpense(tenantid: number, expenseId: number, expenseDTO: IExpenseDTO): void; + + publishExpense(tenantId: number, expenseId: number): Promise; + + deleteExpense(tenantId: number, expenseId: number): Promise; + deleteBulkExpenses(tenantId: number, expensesIds: number[]): Promise; + + publishBulkExpenses(tenantId: number, expensesIds: number[]): Promise; +} \ No newline at end of file diff --git a/server/src/interfaces/Tenancy.ts b/server/src/interfaces/Tenancy.ts new file mode 100644 index 000000000..e7b1e354a --- /dev/null +++ b/server/src/interfaces/Tenancy.ts @@ -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; + createDatabase(tenant: ITenant): Promise; + + migrate(tenant: ITenant): Promise; + seed(tenant: ITenant): Promise; + + setupKnexInstance(tenant: ITenant): Knex; + getKnexInstance(tenantId: number): Knex; +} + +export interface ITenantManager { + tenantDBManager: ITenantDBManager; + tenant: ITenant; + + constructor(): void; + + createTenant(): Promise; + createDatabase(tenant: ITenant): Promise; + hasDatabase(tenant: ITenant): Promise; + + dropTenant(tenant: ITenant): Promise; + + migrateTenant(tenant: ITenant): Promise; + seedTenant(tenant: ITenant): Promise; + + setupKnexInstance(tenant: ITenant): Knex; + getKnexInstance(tenantId: number): Knex; +} + +export interface ISystemService { + cache(); + repositories(); + knex(); + dbManager(); +} \ No newline at end of file diff --git a/server/src/interfaces/User.ts b/server/src/interfaces/User.ts index 8e693f6fc..bbbd0475e 100644 --- a/server/src/interfaces/User.ts +++ b/server/src/interfaces/User.ts @@ -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, } \ No newline at end of file diff --git a/server/src/interfaces/index.ts b/server/src/interfaces/index.ts index ad0bfc76f..ddd1b086a 100644 --- a/server/src/interfaces/index.ts +++ b/server/src/interfaces/index.ts @@ -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, }; \ No newline at end of file diff --git a/server/src/jobs/ComputeItemCost.ts b/server/src/jobs/ComputeItemCost.ts index f844b25bc..9d9458a73 100644 --- a/server/src/jobs/ComputeItemCost.ts +++ b/server/src/jobs/ComputeItemCost.ts @@ -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; diff --git a/server/src/jobs/MailNotificationSubscribeEnd.ts b/server/src/jobs/MailNotificationSubscribeEnd.ts index be79266cd..4f7c9372e 100644 --- a/server/src/jobs/MailNotificationSubscribeEnd.ts +++ b/server/src/jobs/MailNotificationSubscribeEnd.ts @@ -1,5 +1,5 @@ import Container from 'typedi'; -import SubscriptionService from '@/services/Subscription/Subscription'; +import SubscriptionService from 'services/Subscription/Subscription'; export default class MailNotificationSubscribeEnd { /** diff --git a/server/src/jobs/MailNotificationTrialEnd.ts b/server/src/jobs/MailNotificationTrialEnd.ts index 89162db5d..27eec67f8 100644 --- a/server/src/jobs/MailNotificationTrialEnd.ts +++ b/server/src/jobs/MailNotificationTrialEnd.ts @@ -1,5 +1,5 @@ import Container from 'typedi'; -import SubscriptionService from '@/services/Subscription/Subscription'; +import SubscriptionService from 'services/Subscription/Subscription'; export default class MailNotificationTrialEnd { /** diff --git a/server/src/jobs/ResetPasswordMail.ts b/server/src/jobs/ResetPasswordMail.ts index cd33e49b3..60be22500 100644 --- a/server/src/jobs/ResetPasswordMail.ts +++ b/server/src/jobs/ResetPasswordMail.ts @@ -1,5 +1,5 @@ import { Container, Inject } from 'typedi'; -import AuthenticationService from '@/services/Authentication'; +import AuthenticationService from 'services/Authentication'; export default class WelcomeEmailJob { /** diff --git a/server/src/jobs/SMSNotificationSubscribeEnd.ts b/server/src/jobs/SMSNotificationSubscribeEnd.ts index d203c1d6b..ee43ab0c5 100644 --- a/server/src/jobs/SMSNotificationSubscribeEnd.ts +++ b/server/src/jobs/SMSNotificationSubscribeEnd.ts @@ -1,5 +1,5 @@ import Container from 'typedi'; -import SubscriptionService from '@/services/Subscription/Subscription'; +import SubscriptionService from 'services/Subscription/Subscription'; export default class SMSNotificationSubscribeEnd { diff --git a/server/src/jobs/SMSNotificationTrialEnd.ts b/server/src/jobs/SMSNotificationTrialEnd.ts index a3e5c5420..72796ea50 100644 --- a/server/src/jobs/SMSNotificationTrialEnd.ts +++ b/server/src/jobs/SMSNotificationTrialEnd.ts @@ -1,5 +1,5 @@ import Container from 'typedi'; -import SubscriptionService from '@/services/Subscription/Subscription'; +import SubscriptionService from 'services/Subscription/Subscription'; export default class SMSNotificationTrialEnd { diff --git a/server/src/jobs/SendLicenseEmail.ts b/server/src/jobs/SendLicenseEmail.ts index 48cc9733a..41410a680 100644 --- a/server/src/jobs/SendLicenseEmail.ts +++ b/server/src/jobs/SendLicenseEmail.ts @@ -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 { diff --git a/server/src/jobs/SendLicensePhone.ts b/server/src/jobs/SendLicensePhone.ts index 2129b9c9b..359087848 100644 --- a/server/src/jobs/SendLicensePhone.ts +++ b/server/src/jobs/SendLicensePhone.ts @@ -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 { diff --git a/server/src/jobs/UserInviteMail.ts b/server/src/jobs/UserInviteMail.ts index f97703b78..7b261c407 100644 --- a/server/src/jobs/UserInviteMail.ts +++ b/server/src/jobs/UserInviteMail.ts @@ -1,5 +1,5 @@ import { Container, Inject } from 'typedi'; -import InviteUserService from '@/services/InviteUsers'; +import InviteUserService from 'services/InviteUsers'; export default class UserInviteMailJob { @Inject() diff --git a/server/src/jobs/WelcomeSMS.ts b/server/src/jobs/WelcomeSMS.ts index 4dc135db7..469607c6a 100644 --- a/server/src/jobs/WelcomeSMS.ts +++ b/server/src/jobs/WelcomeSMS.ts @@ -1,5 +1,5 @@ import { Container, Inject } from 'typedi'; -import AuthenticationService from '@/services/Authentication'; +import AuthenticationService from 'services/Authentication'; export default class WelcomeSMSJob { /** diff --git a/server/src/jobs/welcomeEmail.ts b/server/src/jobs/welcomeEmail.ts index 8c741c82f..8b37baaf2 100644 --- a/server/src/jobs/welcomeEmail.ts +++ b/server/src/jobs/welcomeEmail.ts @@ -1,5 +1,5 @@ import { Container, Inject } from 'typedi'; -import AuthenticationService from '@/services/Authentication'; +import AuthenticationService from 'services/Authentication'; export default class WelcomeEmailJob { /** @@ -22,7 +22,7 @@ export default class WelcomeEmailJob { */ public async handler(job, done: Function): Promise { const { organizationName, user } = job.attrs.data; - const Logger = Container.get('logger'); + const Logger: any = Container.get('logger'); const authService = Container.get(AuthenticationService); Logger.info(`[welcome_mail] send welcome mail message - started: ${job.attrs.data}`); diff --git a/server/src/jobs/writeInvoicesJEntries.ts b/server/src/jobs/writeInvoicesJEntries.ts index ff91368b0..5648819aa 100644 --- a/server/src/jobs/writeInvoicesJEntries.ts +++ b/server/src/jobs/writeInvoicesJEntries.ts @@ -1,5 +1,5 @@ import { Container } from 'typedi'; -import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost'; +import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost'; export default class WriteInvoicesJournalEntries { diff --git a/server/src/lib/Cachable/CachableModel.js b/server/src/lib/Cachable/CachableModel.js index d2a95f5ca..19c9fd239 100644 --- a/server/src/lib/Cachable/CachableModel.js +++ b/server/src/lib/Cachable/CachableModel.js @@ -1,5 +1,5 @@ -import BaseModel from '@/models/Model'; -import CacheService from '@/services/Cache'; +import BaseModel from 'models/Model'; +import CacheService from 'services/Cache'; export default (Model) => { return class CachableModel extends Model{ diff --git a/server/src/lib/Cachable/CachableQueryBuilder.js b/server/src/lib/Cachable/CachableQueryBuilder.js index 41fa8fbea..34493c084 100644 --- a/server/src/lib/Cachable/CachableQueryBuilder.js +++ b/server/src/lib/Cachable/CachableQueryBuilder.js @@ -1,6 +1,6 @@ import { QueryBuilder } from 'objection'; import crypto from 'crypto'; -import CacheService from '@/services/Cache'; +import CacheService from 'services/Cache'; export default class CachableQueryBuilder extends QueryBuilder{ diff --git a/server/src/lib/DynamicFilter/DynamicFilter.js b/server/src/lib/DynamicFilter/DynamicFilter.js index be7df1ee7..62000f11f 100644 --- a/server/src/lib/DynamicFilter/DynamicFilter.js +++ b/server/src/lib/DynamicFilter/DynamicFilter.js @@ -1,7 +1,7 @@ import { uniqBy } from 'lodash'; import { buildFilterRolesJoins, -} from '@/lib/ViewRolesBuilder'; +} from 'lib/ViewRolesBuilder'; export default class DynamicFilter { /** diff --git a/server/src/lib/DynamicFilter/DynamicFilterFilterRoles.js b/server/src/lib/DynamicFilter/DynamicFilterFilterRoles.js index 015c7f009..88a870a61 100644 --- a/server/src/lib/DynamicFilter/DynamicFilterFilterRoles.js +++ b/server/src/lib/DynamicFilter/DynamicFilterFilterRoles.js @@ -1,8 +1,8 @@ import { difference } from 'lodash'; -import DynamicFilterRoleAbstructor from '@/lib/DynamicFilter/DynamicFilterRoleAbstructor'; +import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor'; import { buildFilterQuery, -} from '@/lib/ViewRolesBuilder'; +} from 'lib/ViewRolesBuilder'; export default class FilterRoles extends DynamicFilterRoleAbstructor { /** diff --git a/server/src/lib/DynamicFilter/DynamicFilterSortBy.js b/server/src/lib/DynamicFilter/DynamicFilterSortBy.js index 00598cb0e..a463045f8 100644 --- a/server/src/lib/DynamicFilter/DynamicFilterSortBy.js +++ b/server/src/lib/DynamicFilter/DynamicFilterSortBy.js @@ -1,5 +1,5 @@ -import DynamicFilterRoleAbstructor from '@/lib/DynamicFilter/DynamicFilterRoleAbstructor'; -import { getRoleFieldColumn } from '@/lib/ViewRolesBuilder'; +import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor'; +import { getRoleFieldColumn } from 'lib/ViewRolesBuilder'; export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor { constructor(sortByFieldKey, sortDirection) { diff --git a/server/src/lib/DynamicFilter/DynamicFilterViews.js b/server/src/lib/DynamicFilter/DynamicFilterViews.js index 3fa6e493b..bbc3a2cce 100644 --- a/server/src/lib/DynamicFilter/DynamicFilterViews.js +++ b/server/src/lib/DynamicFilter/DynamicFilterViews.js @@ -1,8 +1,8 @@ -import DynamicFilterRoleAbstructor from '@/lib/DynamicFilter/DynamicFilterRoleAbstructor'; +import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor'; import { validateViewRoles, buildFilterQuery, -} from '@/lib/ViewRolesBuilder'; +} from 'lib/ViewRolesBuilder'; export default class DynamicFilterViews extends DynamicFilterRoleAbstructor { /** diff --git a/server/src/lib/Metable/MetableStore.ts b/server/src/lib/Metable/MetableStore.ts index b79dda503..ad1569797 100644 --- a/server/src/lib/Metable/MetableStore.ts +++ b/server/src/lib/Metable/MetableStore.ts @@ -4,8 +4,8 @@ import { IMetadata, IMetaQuery, IMetableStore, -} from '@/interfaces'; -import { itemsStartWith } from '@/utils'; +} from 'interfaces'; +import { itemsStartWith } from 'utils'; export default class MetableStore implements IMetableStore{ metadata: IMetadata[]; diff --git a/server/src/lib/Metable/MetableStoreDB.ts b/server/src/lib/Metable/MetableStoreDB.ts index 5e7dba2a5..8c3658ebe 100644 --- a/server/src/lib/Metable/MetableStoreDB.ts +++ b/server/src/lib/Metable/MetableStoreDB.ts @@ -2,7 +2,7 @@ import { Model } from 'objection'; import { IMetadata, IMetableStoreStorage, -} from '@/interfaces'; +} from 'interfaces'; import MetableStore from './MetableStore'; export default class MetableDBStore extends MetableStore implements IMetableStoreStorage{ diff --git a/server/src/lib/MomentFormats/index.ts b/server/src/lib/MomentFormats/index.ts new file mode 100644 index 000000000..4b3e7103f --- /dev/null +++ b/server/src/lib/MomentFormats/index.ts @@ -0,0 +1,48 @@ +import moment from 'moment'; + +moment.prototype.toMySqlDateTime = function () { + return this.format('YYYY-MM-DD HH:mm:ss'); +}; + +// moment.fn.businessDiff = function (param) { +// param = moment(param); +// var signal = param.unix() < this.unix() ? 1 : -1; +// var start = moment.min(param, this).clone(); +// var end = moment.max(param, this).clone(); +// var start_offset = start.day() - 7; +// var end_offset = end.day(); + +// var end_sunday = end.clone().subtract('d', end_offset); +// var start_sunday = start.clone().subtract('d', start_offset); +// var weeks = end_sunday.diff(start_sunday, 'days') / 7; + +// start_offset = Math.abs(start_offset); +// if (start_offset == 7) +// start_offset = 5; +// else if (start_offset == 1) +// start_offset = 0; +// else +// start_offset -= 2; + +// if (end_offset == 6) +// end_offset--; + +// return signal * (weeks * 5 + start_offset + end_offset); +// }; + +// moment.fn.businessAdd = function (days) { +// var signal = days < 0 ? -1 : 1; +// days = Math.abs(days); +// var d = this.clone().add(Math.floor(days / 5) * 7 * signal, 'd'); +// var remaining = days % 5; +// while (remaining) { +// d.add(signal, 'd'); +// if (d.day() !== 0 && d.day() !== 6) +// remaining--; +// } +// return d; +// }; + +// moment.fn.businessSubtract = function (days) { +// return this.businessAdd(-days); +// }; diff --git a/server/src/lib/ViewRolesBuilder/FilterRolesDynamicFilter.js b/server/src/lib/ViewRolesBuilder/FilterRolesDynamicFilter.js index 978abb53d..17bd8166f 100644 --- a/server/src/lib/ViewRolesBuilder/FilterRolesDynamicFilter.js +++ b/server/src/lib/ViewRolesBuilder/FilterRolesDynamicFilter.js @@ -1,8 +1,8 @@ -import DynamicFilterRoleAbstructor from '@/lib/DynamicFilter/DynamicFilterRoleAbstructor'; +import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor'; import { validateViewRoles, buildFilterQuery, -} from '@/lib/ViewRolesBuilder'; +} from 'lib/ViewRolesBuilder'; export default class ViewRolesDynamicFilter extends DynamicFilterRoleAbstructor { /** diff --git a/server/src/lib/ViewRolesBuilder/index.js b/server/src/lib/ViewRolesBuilder/index.js index 9228f6ecd..4b1e10d06 100644 --- a/server/src/lib/ViewRolesBuilder/index.js +++ b/server/src/lib/ViewRolesBuilder/index.js @@ -1,9 +1,9 @@ import { difference } from 'lodash'; import moment from 'moment'; -import { Lexer } from '@/lib/LogicEvaluation/Lexer'; -import Parser from '@/lib/LogicEvaluation/Parser'; -import QueryParser from '@/lib/LogicEvaluation/QueryParser'; -import resourceFieldsKeys from '@/data/ResourceFieldsKeys'; +import { Lexer } from 'lib/LogicEvaluation/Lexer'; +import Parser from 'lib/LogicEvaluation/Parser'; +import QueryParser from 'lib/LogicEvaluation/QueryParser'; +import resourceFieldsKeys from 'data/ResourceFieldsKeys'; // const role = { // compatotor: String, diff --git a/server/src/loaders/agenda.ts b/server/src/loaders/agenda.ts index 225008097..9959571d1 100644 --- a/server/src/loaders/agenda.ts +++ b/server/src/loaders/agenda.ts @@ -1,5 +1,5 @@ import Agenda from 'agenda'; -import config from '@/../config/config'; +import config from 'config'; export default ({ mongoConnection }) => { return new Agenda({ diff --git a/server/src/database/knex.js b/server/src/loaders/database.ts similarity index 60% rename from server/src/database/knex.js rename to server/src/loaders/database.ts index fb16d69cc..a6e8bce65 100644 --- a/server/src/database/knex.js +++ b/server/src/loaders/database.ts @@ -1,12 +1,10 @@ import Knex from 'knex'; import { knexSnakeCaseMappers } from 'objection'; -import knexfile from '@/../config/systemKnexfile'; - -const config = knexfile[process.env.NODE_ENV]; +import { systemKnexConfig } from 'config/knexConfig'; export default () => { return Knex({ - ...config, + ...systemKnexConfig, ...knexSnakeCaseMappers({ upperCase: true }), }); }; \ No newline at end of file diff --git a/server/src/loaders/dbManager.ts b/server/src/loaders/dbManager.ts index 9a002432e..6aaf31ed2 100644 --- a/server/src/loaders/dbManager.ts +++ b/server/src/loaders/dbManager.ts @@ -1,14 +1,7 @@ import knexManager from 'knex-db-manager'; -import knexfile from '@/../config/systemKnexfile'; -import config from '@/../config/config'; - -const knexConfig = knexfile[process.env.NODE_ENV]; +import { systemKnexConfig, systemDbManager } from 'config/knexConfig'; export default () => knexManager.databaseManagerFactory({ - knex: knexConfig, - dbManager: { - collate: [], - superUser: config.manager.superUser, - superPassword: config.manager.superPassword, - }, + knex: systemKnexConfig, + dbManager: systemDbManager, }); \ No newline at end of file diff --git a/server/src/loaders/dependencyInjector.ts b/server/src/loaders/dependencyInjector.ts index 80f9e3a29..562fcf5bd 100644 --- a/server/src/loaders/dependencyInjector.ts +++ b/server/src/loaders/dependencyInjector.ts @@ -1,37 +1,46 @@ import { Container } from 'typedi'; -import LoggerInstance from '@/loaders/Logger'; -import agendaFactory from '@/loaders/agenda'; -import SmsClientLoader from '@/loaders/smsClient'; -import mailInstance from '@/loaders/mail'; -import dbManagerFactory from '@/loaders/dbManager'; -import i18n from '@/loaders/i18n'; +import LoggerInstance from 'loaders/logger'; +import agendaFactory from 'loaders/agenda'; +import SmsClientLoader from 'loaders/smsClient'; +import mailInstance from 'loaders/mail'; +import dbManagerFactory from 'loaders/dbManager'; +import i18n from 'loaders/i18n'; +import repositoriesLoader from 'loaders/systemRepositories'; +import Cache from 'services/Cache'; export default ({ mongoConnection, knex }) => { try { const agendaInstance = agendaFactory({ mongoConnection }); const smsClientInstance = SmsClientLoader(); - const dbManager = dbManagerFactory(); + const dbManager = dbManagerFactory(knex); + const cacheInstance = new Cache(); Container.set('logger', LoggerInstance) - LoggerInstance.info('Logger instance has been injected into container'); + LoggerInstance.info('[DI] Logger instance has been injected into container'); Container.set('knex', knex); - LoggerInstance.info('Knex instance has been injected into container'); + LoggerInstance.info('[DI] Knex instance has been injected into container'); Container.set('SMSClient', smsClientInstance); - LoggerInstance.info('SMS client has been injected into container'); + LoggerInstance.info('[DI] SMS client has been injected into container'); Container.set('mail', mailInstance); - LoggerInstance.info('Mail instance has been injected into container'); + LoggerInstance.info('[DI] Mail instance has been injected into container'); Container.set('dbManager', dbManager); - LoggerInstance.info('Database manager has been injected into container.'); + LoggerInstance.info('[DI] Database manager has been injected into container.'); Container.set('agenda', agendaInstance); - LoggerInstance.info('Agenda has been injected into container'); + LoggerInstance.info('[DI] Agenda has been injected into container'); Container.set('i18n', i18n); - LoggerInstance.info('i18n has been injected into container'); + LoggerInstance.info('[DI] i18n has been injected into container'); + + Container.set('cache', cacheInstance); + LoggerInstance.info('[DI] cache has been injected into container'); + + Container.set('repositories', repositoriesLoader()); + LoggerInstance.info('[DI] repositories has been injected into container'); return { agenda: agendaInstance }; } catch (e) { diff --git a/server/src/loaders/events.ts b/server/src/loaders/events.ts index 0e3726d07..e46ce2d8e 100644 --- a/server/src/loaders/events.ts +++ b/server/src/loaders/events.ts @@ -1,3 +1,3 @@ // Here we import all events. -import '@/subscribers/authentication'; -import '@/subscribers/organization'; +import 'subscribers/authentication'; +import 'subscribers/organization'; diff --git a/server/src/loaders/express.ts b/server/src/loaders/express.ts index c7ee429db..3968e5457 100644 --- a/server/src/loaders/express.ts +++ b/server/src/loaders/express.ts @@ -4,10 +4,10 @@ import boom from 'express-boom'; import errorHandler from 'errorhandler'; import fileUpload from 'express-fileupload'; import i18n from 'i18n'; -import routes from '@/http'; -import LoggerMiddleware from '@/http/middleware/LoggerMiddleware'; -import AgendashController from '@/http/controllers/Agendash'; -import config from '@/../config/config'; +import routes from 'api'; +import LoggerMiddleware from 'api/middleware/LoggerMiddleware'; +import AgendashController from 'api/controllers/Agendash'; +import config from 'config'; export default ({ app }) => { // Express configuration. diff --git a/server/src/loaders/i18n.ts b/server/src/loaders/i18n.ts index b6622b00d..b87823dd2 100644 --- a/server/src/loaders/i18n.ts +++ b/server/src/loaders/i18n.ts @@ -4,6 +4,6 @@ import path from 'path'; export default () => i18n.configure({ locales: ['en', 'ar'], register: global, - directory: path.join(global.rootPath, 'src/locales'), + directory: path.join(global.__root, 'src/locales'), updateFiles: false }) \ No newline at end of file diff --git a/server/src/loaders/index.ts b/server/src/loaders/index.ts index 3eee701b4..363adb00f 100644 --- a/server/src/loaders/index.ts +++ b/server/src/loaders/index.ts @@ -1,18 +1,18 @@ -import Logger from '@/loaders/Logger'; -import mongooseLoader from '@/loaders/mongoose'; -import jobsLoader from '@/loaders/jobs'; -import expressLoader from '@/loaders/express'; -import databaseLoader from '@/database/knex'; -import dependencyInjectorLoader from '@/loaders/dependencyInjector'; -import objectionLoader from '@/database/objection'; -import i18nConfig from '@/loaders/i18n'; +import Logger from 'loaders/logger'; +import mongooseLoader from 'loaders/mongoose'; +import jobsLoader from 'loaders/jobs'; +import expressLoader from 'loaders/express'; +import databaseLoader from 'loaders/database'; +import dependencyInjectorLoader from 'loaders/dependencyInjector'; +import objectionLoader from 'database/objection'; +import i18nConfig from 'loaders/i18n'; // We have to import at least all the events once so they can be triggered -import '@/loaders/events'; +import 'loaders/events'; export default async ({ expressApp }) => { const mongoConnection = await mongooseLoader(); - Logger.info('MongoDB loaded and connected!'); + Logger.info('[init] MongoDB loaded and connected!'); // Initialize the system database once app started. const knex = databaseLoader(); @@ -26,11 +26,11 @@ export default async ({ expressApp }) => { knex, }); await jobsLoader({ agenda }); - Logger.info('Jobs loaded'); + Logger.info('[init] Jobs loaded'); expressLoader({ app: expressApp }); - Logger.info('Express loaded'); + Logger.info('[init] Express loaded'); i18nConfig(); - Logger.info('I18n node configured.'); + Logger.info('[init] I18n node configured.'); }; diff --git a/server/src/loaders/jobs.ts b/server/src/loaders/jobs.ts index c5c5b543a..324cfb7cc 100644 --- a/server/src/loaders/jobs.ts +++ b/server/src/loaders/jobs.ts @@ -1,16 +1,16 @@ import Agenda from 'agenda'; -import WelcomeEmailJob from '@/jobs/WelcomeEmail'; -import WelcomeSMSJob from '@/jobs/WelcomeSMS'; -import ResetPasswordMailJob from '@/jobs/ResetPasswordMail'; -import ComputeItemCost from '@/jobs/ComputeItemCost'; -import RewriteInvoicesJournalEntries from '@/jobs/writeInvoicesJEntries'; -import SendLicenseViaPhoneJob from '@/jobs/SendLicensePhone'; -import SendLicenseViaEmailJob from '@/jobs/SendLicenseEmail'; -import SendSMSNotificationSubscribeEnd from '@/jobs/SMSNotificationSubscribeEnd'; -import SendSMSNotificationTrialEnd from '@/jobs/SMSNotificationTrialEnd'; -import SendMailNotificationSubscribeEnd from '@/jobs/MailNotificationSubscribeEnd'; -import SendMailNotificationTrialEnd from '@/jobs/MailNotificationTrialEnd'; -import UserInviteMailJob from '@/jobs/UserInviteMail'; +import WelcomeEmailJob from 'jobs/WelcomeEmail'; +import WelcomeSMSJob from 'jobs/WelcomeSMS'; +import ResetPasswordMailJob from 'jobs/ResetPasswordMail'; +import ComputeItemCost from 'jobs/ComputeItemCost'; +import RewriteInvoicesJournalEntries from 'jobs/writeInvoicesJEntries'; +import SendLicenseViaPhoneJob from 'jobs/SendLicensePhone'; +import SendLicenseViaEmailJob from 'jobs/SendLicenseEmail'; +import SendSMSNotificationSubscribeEnd from 'jobs/SMSNotificationSubscribeEnd'; +import SendSMSNotificationTrialEnd from 'jobs/SMSNotificationTrialEnd'; +import SendMailNotificationSubscribeEnd from 'jobs/MailNotificationSubscribeEnd'; +import SendMailNotificationTrialEnd from 'jobs/MailNotificationTrialEnd'; +import UserInviteMailJob from 'jobs/UserInviteMail'; export default ({ agenda }: { agenda: Agenda }) => { new WelcomeEmailJob(agenda); @@ -31,7 +31,7 @@ export default ({ agenda }: { agenda: Agenda }) => { agenda.define( 'rewrite-invoices-journal-entries', { priority: 'normal', concurrency: 1, }, - new RewriteInvoicesJournalEntries(agenda).handler, + new RewriteInvoicesJournalEntries().handler, ); agenda.define( 'send-license-via-phone', diff --git a/server/src/loaders/mail.ts b/server/src/loaders/mail.ts index 3a7988e39..0826267ee 100644 --- a/server/src/loaders/mail.ts +++ b/server/src/loaders/mail.ts @@ -1,5 +1,5 @@ import nodemailer from 'nodemailer'; -import config from '@/../config/config'; +import config from 'config'; // create reusable transporter object using the default SMTP transport const transporter = nodemailer.createTransport({ diff --git a/server/src/loaders/mongoose.ts b/server/src/loaders/mongoose.ts index 1bc2ea9ac..3ed3d8236 100644 --- a/server/src/loaders/mongoose.ts +++ b/server/src/loaders/mongoose.ts @@ -1,6 +1,6 @@ import mongoose from 'mongoose'; import { Db } from 'mongodb'; -import config from '@/../config/config'; +import config from 'config'; export default async (): Promise => { const connection = await mongoose.connect( diff --git a/server/src/loaders/smsClient.ts b/server/src/loaders/smsClient.ts index bbb8b0895..bc2c031a0 100644 --- a/server/src/loaders/smsClient.ts +++ b/server/src/loaders/smsClient.ts @@ -1,5 +1,5 @@ -import SMSClient from '@/services/SMSClient'; -import EasySMSGateway from '@/services/SMSClient/EasySMSClient'; +import SMSClient from 'services/SMSClient'; +import EasySMSGateway from 'services/SMSClient/EasySMSClient'; export default () => { const easySmsGateway = new EasySMSGateway(); diff --git a/server/src/loaders/systemRepositories.ts b/server/src/loaders/systemRepositories.ts new file mode 100644 index 000000000..8fbf410e9 --- /dev/null +++ b/server/src/loaders/systemRepositories.ts @@ -0,0 +1,14 @@ +import Container from 'typedi'; +import { + SystemUserRepository, + SubscriptionRepository, + TenantRepository, +} from 'system/repositories'; + +export default () => { + return { + systemUserRepository: Container.get(SystemUserRepository), + subscriptionRepository: Container.get(SubscriptionRepository), + tenantRepository: Container.get(TenantRepository), + }; +} \ No newline at end of file diff --git a/server/src/loaders/tenantCache.ts b/server/src/loaders/tenantCache.ts new file mode 100644 index 000000000..a06e3570c --- /dev/null +++ b/server/src/loaders/tenantCache.ts @@ -0,0 +1,8 @@ +import { Container } from 'typedi'; +import Cache from 'services/Cache'; + +export default (tenantId: number) => { + const cacheInstance = new Cache(); + + return cacheInstance; +}; \ No newline at end of file diff --git a/server/src/loaders/tenantModels.ts b/server/src/loaders/tenantModels.ts index 3ce016b45..4db503ad8 100644 --- a/server/src/loaders/tenantModels.ts +++ b/server/src/loaders/tenantModels.ts @@ -1,41 +1,41 @@ import { mapValues } from 'lodash'; -import Account from '@/models/Account'; -import AccountTransaction from '@/models/AccountTransaction'; -import AccountType from '@/models/AccountType'; -import Item from '@/models/Item'; -import ItemEntry from '@/models/ItemEntry'; -import Bill from '@/models/Bill'; -import BillPayment from '@/models/BillPayment'; -import BillPaymentEntry from '@/models/BillPaymentEntry'; -import Currency from '@/models/Currency'; -import Customer from '@/models/Customer'; -import Contact from '@/models/Contact'; -import Vendor from '@/models/Vendor'; -import ExchangeRate from '@/models/ExchangeRate'; -import Expense from '@/models/Expense'; -import ExpenseCategory from '@/models/ExpenseCategory'; -import View from '@/models/View'; -import ViewRole from '@/models/ViewRole'; -import ViewColumn from '@/models/ViewColumn'; -import Setting from '@/models/Setting'; -import SaleInvoice from '@/models/SaleInvoice'; -import SaleInvoiceEntry from '@/models/SaleInvoiceEntry'; -import SaleReceipt from '@/models/SaleReceipt'; -import SaleReceiptEntry from '@/models/SaleReceiptEntry'; -import SaleEstimate from '@/models/SaleEstimate'; -import SaleEstimateEntry from '@/models/SaleEstimateEntry'; -import PaymentReceive from '@/models/PaymentReceive'; -import PaymentReceiveEntry from '@/models/PaymentReceiveEntry'; -import Option from '@/models/Option'; -import Resource from '@/models/Resource'; -import InventoryCostLotTracker from '@/models/InventoryCostLotTracker'; -import InventoryTransaction from '@/models/InventoryTransaction'; -import ResourceField from '@/models/ResourceField'; -import ResourceFieldMetadata from '@/models/ResourceFieldMetadata'; -import ManualJournal from '@/models/ManualJournal'; -import Media from '@/models/Media'; -import MediaLink from '@/models/MediaLink'; +import Account from 'models/Account'; +import AccountTransaction from 'models/AccountTransaction'; +import AccountType from 'models/AccountType'; +import Item from 'models/Item'; +import ItemEntry from 'models/ItemEntry'; +import Bill from 'models/Bill'; +import BillPayment from 'models/BillPayment'; +import BillPaymentEntry from 'models/BillPaymentEntry'; +import Currency from 'models/Currency'; +import Customer from 'models/Customer'; +import Contact from 'models/Contact'; +import Vendor from 'models/Vendor'; +import ExchangeRate from 'models/ExchangeRate'; +import Expense from 'models/Expense'; +import ExpenseCategory from 'models/ExpenseCategory'; +import View from 'models/View'; +import ViewRole from 'models/ViewRole'; +import ViewColumn from 'models/ViewColumn'; +import Setting from 'models/Setting'; +import SaleInvoice from 'models/SaleInvoice'; +import SaleInvoiceEntry from 'models/SaleInvoiceEntry'; +import SaleReceipt from 'models/SaleReceipt'; +import SaleReceiptEntry from 'models/SaleReceiptEntry'; +import SaleEstimate from 'models/SaleEstimate'; +import SaleEstimateEntry from 'models/SaleEstimateEntry'; +import PaymentReceive from 'models/PaymentReceive'; +import PaymentReceiveEntry from 'models/PaymentReceiveEntry'; +import Option from 'models/Option'; +import Resource from 'models/Resource'; +import InventoryCostLotTracker from 'models/InventoryCostLotTracker'; +import InventoryTransaction from 'models/InventoryTransaction'; +import ResourceField from 'models/ResourceField'; +import ResourceFieldMetadata from 'models/ResourceFieldMetadata'; +import ManualJournal from 'models/ManualJournal'; +import Media from 'models/Media'; +import MediaLink from 'models/MediaLink'; export default (knex) => { const models = { diff --git a/server/src/loaders/tenantRepositories.ts b/server/src/loaders/tenantRepositories.ts index ca793ca70..756a99278 100644 --- a/server/src/loaders/tenantRepositories.ts +++ b/server/src/loaders/tenantRepositories.ts @@ -1,8 +1,8 @@ -import AccountRepository from '@/repositories/AccountRepository'; -import AccountTypeRepository from '@/repositories/AccountTypeRepository'; -import VendorRepository from '@/repositories/VendorRepository'; -import CustomerRepository from '@/repositories/CustomerRepository'; - +import AccountRepository from 'repositories/AccountRepository'; +import AccountTypeRepository from 'repositories/AccountTypeRepository'; +import VendorRepository from 'repositories/VendorRepository'; +import CustomerRepository from 'repositories/CustomerRepository'; +import ExpenseRepository from 'repositories/ExpenseRepository'; export default (tenantId: number) => { return { @@ -10,5 +10,6 @@ export default (tenantId: number) => { accountTypeRepository: new AccountTypeRepository(tenantId), customerRepository: new CustomerRepository(tenantId), vendorRepository: new VendorRepository(tenantId), + expenseRepository: new ExpenseRepository(tenantId), }; }; \ No newline at end of file diff --git a/server/src/models/Account.js b/server/src/models/Account.js index bd6351177..30355b864 100644 --- a/server/src/models/Account.js +++ b/server/src/models/Account.js @@ -1,13 +1,13 @@ /* eslint-disable global-require */ import { Model } from 'objection'; import { flatten } from 'lodash'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; import { buildFilterQuery, buildSortColumnQuery, -} from '@/lib/ViewRolesBuilder'; -import { flatToNestedArray } from '@/utils'; -import DependencyGraph from '@/lib/DependencyGraph'; +} from 'lib/ViewRolesBuilder'; +import { flatToNestedArray } from 'utils'; +import DependencyGraph from 'lib/DependencyGraph'; export default class Account extends TenantModel { /** @@ -54,8 +54,8 @@ export default class Account extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const AccountType = require('@/models/AccountType'); - const AccountTransaction = require('@/models/AccountTransaction'); + const AccountType = require('models/AccountType'); + const AccountTransaction = require('models/AccountTransaction'); return { /** diff --git a/server/src/models/AccountTransaction.js b/server/src/models/AccountTransaction.js index 14ccf4db3..c2df7e32b 100644 --- a/server/src/models/AccountTransaction.js +++ b/server/src/models/AccountTransaction.js @@ -1,6 +1,6 @@ import { Model } from 'objection'; import moment from 'moment'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class AccountTransaction extends TenantModel { /** @@ -83,7 +83,7 @@ export default class AccountTransaction extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Account = require('@/models/Account'); + const Account = require('models/Account'); return { account: { diff --git a/server/src/models/AccountType.js b/server/src/models/AccountType.js index 03e75839e..6e6b46e9e 100644 --- a/server/src/models/AccountType.js +++ b/server/src/models/AccountType.js @@ -1,6 +1,6 @@ // import path from 'path'; import { Model, mixin } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class AccountType extends TenantModel { /** @@ -14,7 +14,7 @@ export default class AccountType extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Account = require('@/models/Account'); + const Account = require('models/Account'); return { /** diff --git a/server/src/models/Bill.js b/server/src/models/Bill.js index 310bc051d..5644a4909 100644 --- a/server/src/models/Bill.js +++ b/server/src/models/Bill.js @@ -1,6 +1,6 @@ import { Model } from 'objection'; import { difference } from 'lodash'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class Bill extends TenantModel { /** @@ -36,8 +36,8 @@ export default class Bill extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Vendor = require('@/models/Vendor'); - const ItemEntry = require('@/models/ItemEntry'); + const Vendor = require('models/Vendor'); + const ItemEntry = require('models/ItemEntry'); return { vendor: { diff --git a/server/src/models/BillPayment.js b/server/src/models/BillPayment.js index a43463596..eeb1655e2 100644 --- a/server/src/models/BillPayment.js +++ b/server/src/models/BillPayment.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class BillPayment extends TenantModel { /** @@ -20,10 +20,10 @@ export default class BillPayment extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const BillPaymentEntry = require('@/models/BillPaymentEntry'); - const AccountTransaction = require('@/models/AccountTransaction'); - const Vendor = require('@/models/Vendor'); - const Account = require('@/models/Account'); + const BillPaymentEntry = require('models/BillPaymentEntry'); + const AccountTransaction = require('models/AccountTransaction'); + const Vendor = require('models/Vendor'); + const Account = require('models/Account'); return { entries: { diff --git a/server/src/models/BillPaymentEntry.js b/server/src/models/BillPaymentEntry.js index de1bbe6a1..88414bdb4 100644 --- a/server/src/models/BillPaymentEntry.js +++ b/server/src/models/BillPaymentEntry.js @@ -1,5 +1,5 @@ import { mixin } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class BillPaymentEntry extends TenantModel { /** diff --git a/server/src/models/Contact.js b/server/src/models/Contact.js index a3cfdbedf..92064e416 100644 --- a/server/src/models/Contact.js +++ b/server/src/models/Contact.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class Contact extends TenantModel { /** @@ -39,8 +39,8 @@ export default class Contact extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const SaleInvoice = require('@/models/SaleInvoice'); - const Bill = require('@/models/Bill'); + const SaleInvoice = require('models/SaleInvoice'); + const Bill = require('models/Bill'); return { salesInvoices: { diff --git a/server/src/models/Currency.js b/server/src/models/Currency.js index 7a05a03b2..e1c6dbacb 100644 --- a/server/src/models/Currency.js +++ b/server/src/models/Currency.js @@ -1,4 +1,4 @@ -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class Currency extends TenantModel { /** diff --git a/server/src/models/Customer.js b/server/src/models/Customer.js index 89daf8152..5d0590667 100644 --- a/server/src/models/Customer.js +++ b/server/src/models/Customer.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class Customer extends TenantModel { /** diff --git a/server/src/models/ExchangeRate.js b/server/src/models/ExchangeRate.js index 08820091f..48340f8ed 100644 --- a/server/src/models/ExchangeRate.js +++ b/server/src/models/ExchangeRate.js @@ -1,6 +1,6 @@ import bcrypt from 'bcryptjs'; import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class ExchangeRate extends TenantModel { /** diff --git a/server/src/models/Expense.js b/server/src/models/Expense.js index b731e221d..a5b0b4e41 100644 --- a/server/src/models/Expense.js +++ b/server/src/models/Expense.js @@ -1,6 +1,6 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; -import { viewRolesBuilder } from '@/lib/ViewRolesBuilder'; +import TenantModel from 'models/TenantModel'; +import { viewRolesBuilder } from 'lib/ViewRolesBuilder'; export default class Expense extends TenantModel { /** @@ -70,9 +70,9 @@ export default class Expense extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Account = require('@/models/Account'); - const User = require('@/models/TenantUser'); - const ExpenseCategory = require('@/models/ExpenseCategory'); + const Account = require('models/Account'); + const User = require('models/TenantUser'); + const ExpenseCategory = require('models/ExpenseCategory'); return { paymentAccount: { diff --git a/server/src/models/ExpenseCategory.js b/server/src/models/ExpenseCategory.js index 382c300af..54b150111 100644 --- a/server/src/models/ExpenseCategory.js +++ b/server/src/models/ExpenseCategory.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class ExpenseCategory extends TenantModel { /** @@ -13,7 +13,7 @@ export default class ExpenseCategory extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Account = require('@/models/Account'); + const Account = require('models/Account'); return { expenseAccount: { diff --git a/server/src/models/InventoryCostLotTracker.js b/server/src/models/InventoryCostLotTracker.js index 31a3b3dea..3a2a477d9 100644 --- a/server/src/models/InventoryCostLotTracker.js +++ b/server/src/models/InventoryCostLotTracker.js @@ -1,6 +1,6 @@ import { Model } from 'objection'; import moment from 'moment'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class InventoryCostLotTracker extends TenantModel { /** @@ -52,7 +52,7 @@ export default class InventoryCostLotTracker extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Item = require('@/models/Item'); + const Item = require('models/Item'); return { item: { diff --git a/server/src/models/InventoryTransaction.js b/server/src/models/InventoryTransaction.js index 37dd84e4d..26e388ce4 100644 --- a/server/src/models/InventoryTransaction.js +++ b/server/src/models/InventoryTransaction.js @@ -1,6 +1,6 @@ import { Model } from 'objection'; import moment from 'moment'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class InventoryTransaction extends TenantModel { /** @@ -41,7 +41,7 @@ export default class InventoryTransaction extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Item = require('@/models/Item'); + const Item = require('models/Item'); return { item: { diff --git a/server/src/models/Item.js b/server/src/models/Item.js index a6dd638af..3a262e238 100644 --- a/server/src/models/Item.js +++ b/server/src/models/Item.js @@ -1,8 +1,8 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; import { buildFilterQuery, -} from '@/lib/ViewRolesBuilder'; +} from 'lib/ViewRolesBuilder'; export default class Item extends TenantModel { /** @@ -39,9 +39,9 @@ export default class Item extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Media = require('@/models/Media'); - const Account = require('@/models/Account'); - const ItemCategory = require('@/models/ItemCategory'); + const Media = require('models/Media'); + const Account = require('models/Account'); + const ItemCategory = require('models/ItemCategory'); return { /** diff --git a/server/src/models/ItemCategory.js b/server/src/models/ItemCategory.js index 1ca3ed806..e363adbc6 100644 --- a/server/src/models/ItemCategory.js +++ b/server/src/models/ItemCategory.js @@ -1,6 +1,6 @@ import path from 'path'; import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class ItemCategory extends TenantModel { /** @@ -14,7 +14,7 @@ export default class ItemCategory extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Item = require('@/models/Item'); + const Item = require('models/Item'); return { /** diff --git a/server/src/models/ItemEntry.js b/server/src/models/ItemEntry.js index b4cffec90..271a5cf43 100644 --- a/server/src/models/ItemEntry.js +++ b/server/src/models/ItemEntry.js @@ -1,6 +1,6 @@ import path from 'path'; import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class ItemEntry extends TenantModel { /** @@ -33,7 +33,7 @@ export default class ItemEntry extends TenantModel { } static get relationMappings() { - const Item = require('@/models/Item'); + const Item = require('models/Item'); return { item: { diff --git a/server/src/models/ManualJournal.js b/server/src/models/ManualJournal.js index 3419c2b30..b798dd7a9 100644 --- a/server/src/models/ManualJournal.js +++ b/server/src/models/ManualJournal.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class ManualJournal extends TenantModel { /** @@ -20,7 +20,7 @@ export default class ManualJournal extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Media = require('@/models/Media'); + const Media = require('models/Media'); return { media: { diff --git a/server/src/models/Media.js b/server/src/models/Media.js index c814d4655..acf421451 100644 --- a/server/src/models/Media.js +++ b/server/src/models/Media.js @@ -1,4 +1,4 @@ -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class Media extends TenantModel { /** diff --git a/server/src/models/MediaLink.js b/server/src/models/MediaLink.js index 53c20ad77..78f9d4888 100644 --- a/server/src/models/MediaLink.js +++ b/server/src/models/MediaLink.js @@ -1,4 +1,4 @@ -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class MediaLink extends TenantModel { /** diff --git a/server/src/models/Metable.js b/server/src/models/Metable.js index 8522120fe..42b870d12 100644 --- a/server/src/models/Metable.js +++ b/server/src/models/Metable.js @@ -1,4 +1,4 @@ -import knex from '@/database/knex'; +import knex from 'database/knex'; // import cache from 'memory-cache'; // Metadata diff --git a/server/src/models/Model.js b/server/src/models/Model.js index 86971811d..c67fb9684 100644 --- a/server/src/models/Model.js +++ b/server/src/models/Model.js @@ -1,8 +1,8 @@ import { Model, mixin } from 'objection'; import { snakeCase, each } from 'lodash'; -import { mapKeysDeep } from '@/utils'; -import PaginationQueryBuilder from '@/models/Pagination'; -import DateSession from '@/models/DateSession'; +import { mapKeysDeep } from 'utils'; +import PaginationQueryBuilder from 'models/Pagination'; +import DateSession from 'models/DateSession'; export default class ModelBase extends mixin(Model, [DateSession]) { diff --git a/server/src/models/Option.js b/server/src/models/Option.js index ebe39a0e9..2ba2793ba 100644 --- a/server/src/models/Option.js +++ b/server/src/models/Option.js @@ -1,5 +1,5 @@ -import TenantModel from '@/models/TenantModel'; -import definedOptions from '@/data/options'; +import TenantModel from 'models/TenantModel'; +import definedOptions from 'data/options'; export default class Option extends TenantModel { diff --git a/server/src/models/PaymentReceive.js b/server/src/models/PaymentReceive.js index ea0b7bd19..6d2b73bb9 100644 --- a/server/src/models/PaymentReceive.js +++ b/server/src/models/PaymentReceive.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class PaymentReceive extends TenantModel { /** @@ -20,10 +20,10 @@ export default class PaymentReceive extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const PaymentReceiveEntry = require('@/models/PaymentReceiveEntry'); - const AccountTransaction = require('@/models/AccountTransaction'); - const Customer = require('@/models/Customer'); - const Account = require('@/models/Account'); + const PaymentReceiveEntry = require('models/PaymentReceiveEntry'); + const AccountTransaction = require('models/AccountTransaction'); + const Customer = require('models/Customer'); + const Account = require('models/Account'); return { customer: { diff --git a/server/src/models/PaymentReceiveEntry.js b/server/src/models/PaymentReceiveEntry.js index ed0e4397a..5b9152afe 100644 --- a/server/src/models/PaymentReceiveEntry.js +++ b/server/src/models/PaymentReceiveEntry.js @@ -1,5 +1,5 @@ import { Model, mixin } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class PaymentReceiveEntry extends TenantModel { /** @@ -20,8 +20,8 @@ export default class PaymentReceiveEntry extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const PaymentReceive = require('@/models/PaymentReceive'); - const SaleInvoice = require('@/models/SaleInvoice'); + const PaymentReceive = require('models/PaymentReceive'); + const SaleInvoice = require('models/SaleInvoice'); return { /** diff --git a/server/src/models/Resource.js b/server/src/models/Resource.js index 40a9d18ff..123bde984 100644 --- a/server/src/models/Resource.js +++ b/server/src/models/Resource.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class Resource extends TenantModel { /** @@ -20,8 +20,8 @@ export default class Resource extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const View = require('@/models/View'); - const ResourceField = require('@/models/ResourceField'); + const View = require('models/View'); + const ResourceField = require('models/ResourceField'); return { /** diff --git a/server/src/models/ResourceField.js b/server/src/models/ResourceField.js index 32083dfef..76ef71ed7 100644 --- a/server/src/models/ResourceField.js +++ b/server/src/models/ResourceField.js @@ -1,7 +1,7 @@ import { snakeCase } from 'lodash'; import { Model } from 'objection'; import path from 'path'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class ResourceField extends TenantModel { /** @@ -51,7 +51,7 @@ export default class ResourceField extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Resource = require('@/models/Resource'); + const Resource = require('models/Resource'); return { /** diff --git a/server/src/models/ResourceFieldMetadata.js b/server/src/models/ResourceFieldMetadata.js index 8b3a54c84..6a9eb152a 100644 --- a/server/src/models/ResourceFieldMetadata.js +++ b/server/src/models/ResourceFieldMetadata.js @@ -1,4 +1,4 @@ -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class ResourceFieldMetadata extends TenantModel { /** diff --git a/server/src/models/SaleEstimate.js b/server/src/models/SaleEstimate.js index 55b043a45..463ab015b 100644 --- a/server/src/models/SaleEstimate.js +++ b/server/src/models/SaleEstimate.js @@ -1,5 +1,5 @@ import { Model, mixin } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class SaleEstimate extends TenantModel { /** @@ -20,8 +20,8 @@ export default class SaleEstimate extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const ItemEntry = require('@/models/ItemEntry'); - const Customer = require('@/models/Customer'); + const ItemEntry = require('models/ItemEntry'); + const Customer = require('models/Customer'); return { customer: { diff --git a/server/src/models/SaleEstimateEntry.js b/server/src/models/SaleEstimateEntry.js index cf1866588..a67eaea56 100644 --- a/server/src/models/SaleEstimateEntry.js +++ b/server/src/models/SaleEstimateEntry.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class SaleEstimateEntry extends TenantModel { @@ -14,7 +14,7 @@ export default class SaleEstimateEntry extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const SaleEstimate = require('@/models/SaleEstimate'); + const SaleEstimate = require('models/SaleEstimate'); return { estimate: { diff --git a/server/src/models/SaleInvoice.js b/server/src/models/SaleInvoice.js index fe92e554d..fa904c89c 100644 --- a/server/src/models/SaleInvoice.js +++ b/server/src/models/SaleInvoice.js @@ -1,6 +1,6 @@ import { Model, mixin } from 'objection'; import moment from 'moment'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class SaleInvoice extends TenantModel { /** @@ -55,10 +55,10 @@ export default class SaleInvoice extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const AccountTransaction = require('@/models/AccountTransaction'); - const ItemEntry = require('@/models/ItemEntry'); - const Customer = require('@/models/Customer'); - const InventoryCostLotTracker = require('@/models/InventoryCostLotTracker'); + const AccountTransaction = require('models/AccountTransaction'); + const ItemEntry = require('models/ItemEntry'); + const Customer = require('models/Customer'); + const InventoryCostLotTracker = require('models/InventoryCostLotTracker'); return { entries: { diff --git a/server/src/models/SaleInvoiceEntry.js b/server/src/models/SaleInvoiceEntry.js index 2bcb483c7..6d258b45f 100644 --- a/server/src/models/SaleInvoiceEntry.js +++ b/server/src/models/SaleInvoiceEntry.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class SaleInvoiceEntry extends TenantModel { /** @@ -13,7 +13,7 @@ export default class SaleInvoiceEntry extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const SaleInvoice = require('@/models/SaleInvoice'); + const SaleInvoice = require('models/SaleInvoice'); return { saleInvoice: { diff --git a/server/src/models/SaleReceipt.js b/server/src/models/SaleReceipt.js index 60f3d930b..732636851 100644 --- a/server/src/models/SaleReceipt.js +++ b/server/src/models/SaleReceipt.js @@ -1,5 +1,5 @@ import { Model, mixin } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class SaleReceipt extends TenantModel { /** @@ -20,10 +20,10 @@ export default class SaleReceipt extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Customer = require('@/models/Customer'); - const Account = require('@/models/Account'); - const AccountTransaction = require('@/models/AccountTransaction'); - const ItemEntry = require('@/models/ItemEntry'); + const Customer = require('models/Customer'); + const Account = require('models/Account'); + const AccountTransaction = require('models/AccountTransaction'); + const ItemEntry = require('models/ItemEntry'); return { customer: { diff --git a/server/src/models/SaleReceiptEntry.js b/server/src/models/SaleReceiptEntry.js index abc71cd2b..ce08e0159 100644 --- a/server/src/models/SaleReceiptEntry.js +++ b/server/src/models/SaleReceiptEntry.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class SaleReceiptEntry extends TenantModel { /** @@ -13,7 +13,7 @@ export default class SaleReceiptEntry extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const SaleReceipt = require('@/models/SaleReceipt'); + const SaleReceipt = require('models/SaleReceipt'); return { saleReceipt: { diff --git a/server/src/models/Setting.js b/server/src/models/Setting.js index 86ef5c15b..471fccb66 100644 --- a/server/src/models/Setting.js +++ b/server/src/models/Setting.js @@ -1,4 +1,4 @@ -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; import Auth from './Auth'; export default class Setting extends TenantModel { diff --git a/server/src/models/TenantModel.js b/server/src/models/TenantModel.js index ae3a6d56e..383921ece 100644 --- a/server/src/models/TenantModel.js +++ b/server/src/models/TenantModel.js @@ -1,4 +1,4 @@ -import BaseModel from '@/models/Model'; +import BaseModel from 'models/Model'; export default class TenantModel extends BaseModel { diff --git a/server/src/models/TenantUser.js b/server/src/models/TenantUser.js index 343260076..c9e362855 100644 --- a/server/src/models/TenantUser.js +++ b/server/src/models/TenantUser.js @@ -1,5 +1,5 @@ import bcrypt from 'bcryptjs'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class TenantUser extends TenantModel { /** diff --git a/server/src/models/Vendor.js b/server/src/models/Vendor.js index 68f21b963..636121624 100644 --- a/server/src/models/Vendor.js +++ b/server/src/models/Vendor.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class Vendor extends TenantModel { /** diff --git a/server/src/models/View.js b/server/src/models/View.js index 02bdc54e0..27e2cb7d0 100644 --- a/server/src/models/View.js +++ b/server/src/models/View.js @@ -1,5 +1,5 @@ import { Model, mixin } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class View extends TenantModel { /** @@ -40,9 +40,9 @@ export default class View extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Resource = require('@/models/Resource'); - const ViewColumn = require('@/models/ViewColumn'); - const ViewRole = require('@/models/ViewRole'); + const Resource = require('models/Resource'); + const ViewColumn = require('models/ViewColumn'); + const ViewRole = require('models/ViewRole'); return { /** diff --git a/server/src/models/ViewColumn.js b/server/src/models/ViewColumn.js index 0ad44bcb9..c216602c8 100644 --- a/server/src/models/ViewColumn.js +++ b/server/src/models/ViewColumn.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class ViewColumn extends TenantModel { /** @@ -13,7 +13,7 @@ export default class ViewColumn extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const ResourceField = require('@/models/ResourceField'); + const ResourceField = require('models/ResourceField'); return { /** diff --git a/server/src/models/ViewRole.js b/server/src/models/ViewRole.js index 6e0c89989..b7f1b37f9 100644 --- a/server/src/models/ViewRole.js +++ b/server/src/models/ViewRole.js @@ -1,5 +1,5 @@ import { Model } from 'objection'; -import TenantModel from '@/models/TenantModel'; +import TenantModel from 'models/TenantModel'; export default class ViewRole extends TenantModel { @@ -27,8 +27,8 @@ export default class ViewRole extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const ResourceField = require('@/models/ResourceField'); - const View = require('@/models/View'); + const ResourceField = require('models/ResourceField'); + const View = require('models/View'); return { /** diff --git a/server/src/repositories/AccountRepository.ts b/server/src/repositories/AccountRepository.ts index 396d8d893..a8afd678b 100644 --- a/server/src/repositories/AccountRepository.ts +++ b/server/src/repositories/AccountRepository.ts @@ -1,4 +1,4 @@ -import TenantRepository from '@/repositories/TenantRepository'; +import TenantRepository from 'repositories/TenantRepository'; export default class AccountRepository extends TenantRepository { models: any; @@ -35,7 +35,7 @@ export default class AccountRepository extends TenantRepository { * Retrieve all accounts on the storage. * @return {} */ - async allAccounts() { + allAccounts() { const { Account } = this.models; return this.cache.get('accounts', async () => { return Account.query(); @@ -46,10 +46,22 @@ export default class AccountRepository extends TenantRepository { * Retrieve account of the given account slug. * @param {string} slug */ - async getBySlug(slug: string) { + getBySlug(slug: string) { const { Account } = this.models; return this.cache.get(`accounts.slug.${slug}`, () => { return Account.query().findOne('slug', slug); }); } + + /** + * Retrieve the account by the given id. + * @param {number} id - Account id. + */ + getById(id: number) { + const { Account } = this.models; + return this.cache.get(`accounts.id.${id}`, () => { + return Account.query().findById(id); + }); + } + } \ No newline at end of file diff --git a/server/src/repositories/AccountTypeRepository.ts b/server/src/repositories/AccountTypeRepository.ts index ec47bbdcc..0b8f424b5 100644 --- a/server/src/repositories/AccountTypeRepository.ts +++ b/server/src/repositories/AccountTypeRepository.ts @@ -1,4 +1,4 @@ -import TenantRepository from '@/repositories/TenantRepository'; +import TenantRepository from 'repositories/TenantRepository'; export default class AccountTypeRepository extends TenantRepository { cache: any; @@ -27,4 +27,19 @@ export default class AccountTypeRepository extends TenantRepository { return AccountType.query().findById(accountTypeId); }); } + + getByKeys(keys: string[]) { + const { AccountType } = this.models; + return AccountType.query().whereIn('key', keys); + } + + getByKey(key: string) { + const { AccountType } = this.models; + return AccountType.query().findOne('key', key); + } + + getByRootType(rootType: string) { + const { AccountType } = this.models; + return AccountType.query().where('root_type', rootType); + } } \ No newline at end of file diff --git a/server/src/repositories/BaseModelRepository.js b/server/src/repositories/BaseModelRepository.ts similarity index 100% rename from server/src/repositories/BaseModelRepository.js rename to server/src/repositories/BaseModelRepository.ts diff --git a/server/src/repositories/CustomerRepository.js b/server/src/repositories/CustomerRepository.js index aace6b3b4..166476a45 100644 --- a/server/src/repositories/CustomerRepository.js +++ b/server/src/repositories/CustomerRepository.js @@ -1,4 +1,4 @@ -import { Customer } from '@/models'; +import { Customer } from 'models'; export default class CustomerRepository { diff --git a/server/src/repositories/ExpenseRepository.ts b/server/src/repositories/ExpenseRepository.ts new file mode 100644 index 000000000..861097814 --- /dev/null +++ b/server/src/repositories/ExpenseRepository.ts @@ -0,0 +1,46 @@ +import TenantRepository from "./TenantRepository"; +import { IExpense } from 'interfaces'; + +export default class ExpenseRepository extends TenantRepository { + models: any; + repositories: any; + cache: any; + + constructor(tenantId: number) { + super(tenantId); + + this.models = this.tenancy.models(tenantId); + this.cache = this.tenancy.cache(tenantId); + } + + getById(expenseId: number) { + const { Expense } = this.models; + return this.cache.get(`expense.id.${expenseId}`, () => { + return Expense.query().findById(expenseId); + }) + } + + create(expense: IExpense) { + const { Expense } = this.models; + return Expense.query().insert({ ...expense }); + } + + update(expenseId: number, expense: IExpense) { + const { Expense } = this.models; + return Expense.query().patchAndFetchById(expenseId, { ...expense }); + } + + publish(expenseId: number) { + + } + + delete(expenseId: number) { + const { Expense } = this.models; + return Expense.query().findById(expenseId).delete(); + } + + bulkDelete(expensesIds: number[]) { + const { Expense } = this.models; + return Expense.query().whereIn('id', expensesIds).delete(); + } +} \ No newline at end of file diff --git a/server/src/repositories/PaymentReceiveEntryRepository.js b/server/src/repositories/PaymentReceiveEntryRepository.js index f7503c6fe..eb2935c2b 100644 --- a/server/src/repositories/PaymentReceiveEntryRepository.js +++ b/server/src/repositories/PaymentReceiveEntryRepository.js @@ -1,6 +1,6 @@ import { omit } from 'lodash'; -import BaseModelRepository from '@/repositories/BaseModelRepository'; -import { PaymentReceiveEntry } from '@/models'; +import BaseModelRepository from 'repositories/BaseModelRepository'; +import { PaymentReceiveEntry } from 'models'; export default class PaymentReceiveEntryRepository extends BaseModelRepository { /** diff --git a/server/src/repositories/PaymentReceiveRepository.js b/server/src/repositories/PaymentReceiveRepository.js index 71680a4fd..833d07050 100644 --- a/server/src/repositories/PaymentReceiveRepository.js +++ b/server/src/repositories/PaymentReceiveRepository.js @@ -1,6 +1,6 @@ import { omit } from 'lodash'; -import { PaymentReceiveEntry } from '@/models'; -import BaseModelRepository from '@/repositories/BaseModelRepository'; +import { PaymentReceiveEntry } from 'models'; +import BaseModelRepository from 'repositories/BaseModelRepository'; export default class PaymentReceiveRepository extends BaseModelRepository { diff --git a/server/src/repositories/ResourceRepository.js b/server/src/repositories/ResourceRepository.js index 762ccd77c..33e4a131f 100644 --- a/server/src/repositories/ResourceRepository.js +++ b/server/src/repositories/ResourceRepository.js @@ -1,5 +1,5 @@ -import { Resource } from '@/models'; -import BaseModelRepository from '@/repositories/BaseModelRepository'; +import { Resource } from 'models'; +import BaseModelRepository from 'repositories/BaseModelRepository'; export default class ResourceRepository extends BaseModelRepository{ diff --git a/server/src/repositories/TenantRepository.ts b/server/src/repositories/TenantRepository.ts index 076dcf677..390229028 100644 --- a/server/src/repositories/TenantRepository.ts +++ b/server/src/repositories/TenantRepository.ts @@ -1,5 +1,5 @@ import { Container } from 'typedi'; -import TenancyService from '@/services/Tenancy/TenancyService'; +import TenancyService from 'services/Tenancy/TenancyService'; export default class TenantRepository { tenantId: number; diff --git a/server/src/server.js b/server/src/server.ts similarity index 61% rename from server/src/server.js rename to server/src/server.ts index 2dbff50ec..53b9bb9d0 100644 --- a/server/src/server.js +++ b/server/src/server.ts @@ -1,14 +1,10 @@ import 'reflect-metadata'; // We need this in order to use @Decorators -import moment from 'moment'; -moment.prototype.toMySqlDateTime = function () { - return this.format('YYYY-MM-DD HH:mm:ss'); -}; -import express from 'express'; -import rootPath from 'app-root-path'; -import loadersFactory from '@/loaders'; -import '../config'; +import 'module-alias/register'; +import 'config'; +import './before'; -global.rootPath = rootPath.path; +import express from 'express'; +import loadersFactory from 'loaders'; async function startServer() { const app = express(); @@ -24,7 +20,7 @@ async function startServer() { } console.log(` ################################################ - 🛡️ Server listening on port: ${app.get('port')} 🛡️ + Server listening on port: ${app.get('port')} ################################################ `); }); diff --git a/server/src/services/Accounting/JournalCommands.ts b/server/src/services/Accounting/JournalCommands.ts index 6c880abea..8f7f893df 100644 --- a/server/src/services/Accounting/JournalCommands.ts +++ b/server/src/services/Accounting/JournalCommands.ts @@ -1,8 +1,8 @@ import { sumBy, chain } from 'lodash'; import JournalPoster from "./JournalPoster"; import JournalEntry from "./JournalEntry"; -import { AccountTransaction } from '@/models'; -import { IInventoryTransaction } from '@/interfaces'; +import { AccountTransaction } from 'models'; +import { IInventoryTransaction } from 'interfaces'; import AccountsService from '../Accounts/AccountsService'; import { IInventoryTransaction, IInventoryTransaction } from '../../interfaces'; @@ -120,6 +120,21 @@ export default class JournalCommands{ this.journal.credit(creditEntry); } + async revertJournalEntries( + referenceId: number|number[], + referenceType: string + ) { + const { AccountTransaction } = this.models; + + const transactions = await AccountTransaction.query() + .where('reference_type', referenceType) + .whereIn('reference_id', Array.isArray(referenceId) ? referenceId : [referenceId]) + .withGraphFetched('account.type'); + + this.journal.loadEntries(transactions); + this.journal.removeEntries(); + } + /** * Removes and revert accounts balance journal entries that associated * to the given inventory transactions. diff --git a/server/src/services/Accounting/JournalPoster.ts b/server/src/services/Accounting/JournalPoster.ts index 5e6a62a13..bb75616be 100644 --- a/server/src/services/Accounting/JournalPoster.ts +++ b/server/src/services/Accounting/JournalPoster.ts @@ -1,14 +1,14 @@ import { omit } from 'lodash'; import { Container } from 'typedi'; -import JournalEntry from '@/services/Accounting/JournalEntry'; -import TenancyService from '@/services/Tenancy/TenancyService'; +import JournalEntry from 'services/Accounting/JournalEntry'; +import TenancyService from 'services/Tenancy/TenancyService'; import { IJournalEntry, IJournalPoster, IAccountChange, IAccountsChange, TEntryType, -} from '@/interfaces'; +} from 'interfaces'; export default class JournalPoster implements IJournalPoster { tenantId: number; diff --git a/server/src/services/Accounts/AccountsService.ts b/server/src/services/Accounts/AccountsService.ts index e1195aaba..32c0cf7d7 100644 --- a/server/src/services/Accounts/AccountsService.ts +++ b/server/src/services/Accounts/AccountsService.ts @@ -1,8 +1,8 @@ import { Inject, Service } from 'typedi'; import { kebabCase } from 'lodash' -import TenancyService from '@/services/Tenancy/TenancyService'; -import { ServiceError } from '@/exceptions'; -import { IAccountDTO, IAccount } from '@/interfaces'; +import TenancyService from 'services/Tenancy/TenancyService'; +import { ServiceError } from 'exceptions'; +import { IAccountDTO, IAccount } from 'interfaces'; import { difference } from 'lodash'; import { Account } from 'src/models'; diff --git a/server/src/services/Authentication/AuthenticationMailMessages.ts b/server/src/services/Authentication/AuthenticationMailMessages.ts index 3116ca33c..5703f12f3 100644 --- a/server/src/services/Authentication/AuthenticationMailMessages.ts +++ b/server/src/services/Authentication/AuthenticationMailMessages.ts @@ -2,8 +2,8 @@ import fs from 'fs'; import { Service, Container } from "typedi"; import Mustache from 'mustache'; import path from 'path'; -import { ISystemUser } from '@/interfaces'; -import config from '@/../config/config'; +import { ISystemUser } from 'interfaces'; +import config from 'config'; @Service() export default class AuthenticationMailMesssages { @@ -16,7 +16,7 @@ export default class AuthenticationMailMesssages { sendWelcomeMessage(user: ISystemUser, organizationName: string): Promise { const Mail = Container.get('mail'); - const filePath = path.join(global.rootPath, 'views/mail/Welcome.html'); + const filePath = path.join(global.__root, 'views/mail/Welcome.html'); const template = fs.readFileSync(filePath, 'utf8'); const rendered = Mustache.render(template, { email: user.email, @@ -50,7 +50,7 @@ export default class AuthenticationMailMesssages { sendResetPasswordMessage(user: ISystemUser, token: string): Promise { const Mail = Container.get('mail'); - const filePath = path.join(global.rootPath, 'views/mail/ResetPassword.html'); + const filePath = path.join(global.__root, 'views/mail/ResetPassword.html'); const template = fs.readFileSync(filePath, 'utf8'); const rendered = Mustache.render(template, { resetPasswordUrl: `${config.baseURL}/reset/${token}`, diff --git a/server/src/services/Authentication/AuthenticationSMSMessages.ts b/server/src/services/Authentication/AuthenticationSMSMessages.ts index 29bd69b72..c9a062ffb 100644 --- a/server/src/services/Authentication/AuthenticationSMSMessages.ts +++ b/server/src/services/Authentication/AuthenticationSMSMessages.ts @@ -1,5 +1,5 @@ import { Service, Inject } from "typedi"; -import { ISystemUser, ITenant } from "@/interfaces"; +import { ISystemUser, ITenant } from "interfaces"; @Service() export default class AuthenticationSMSMessages { diff --git a/server/src/services/Authentication/index.ts b/server/src/services/Authentication/index.ts index 5fbee41ad..565863480 100644 --- a/server/src/services/Authentication/index.ts +++ b/server/src/services/Authentication/index.ts @@ -6,34 +6,27 @@ import moment from "moment"; import { EventDispatcher, EventDispatcherInterface, -} from '@/decorators/eventDispatcher'; -import { - SystemUser, - PasswordReset, - Tenant, -} from '@/system/models'; +} from 'decorators/eventDispatcher'; +import { SystemUser, PasswordReset } from 'system/models'; import { IRegisterDTO, ITenant, ISystemUser, IPasswordReset, -} from '@/interfaces'; -import TenantsManager from "@/system/TenantsManager"; -import { hashPassword } from '@/utils'; -import { ServiceError, ServiceErrors } from "@/exceptions"; -import config from '@/../config/config'; -import events from '@/subscribers/events'; -import AuthenticationMailMessages from '@/services/Authentication/AuthenticationMailMessages'; -import AuthenticationSMSMessages from '@/services/Authentication/AuthenticationSMSMessages'; +} from 'interfaces'; +import { hashPassword } from 'utils'; +import { ServiceError, ServiceErrors } from 'exceptions'; +import config from 'config'; +import events from 'subscribers/events'; +import AuthenticationMailMessages from 'services/Authentication/AuthenticationMailMessages'; +import AuthenticationSMSMessages from 'services/Authentication/AuthenticationSMSMessages'; +import TenantsManager from 'services/Tenancy/TenantsManager'; @Service() export default class AuthenticationService { @Inject('logger') logger: any; - @Inject() - tenantsManager: TenantsManager; - @EventDispatcher() eventDispatcher: EventDispatcherInterface; @@ -43,6 +36,12 @@ export default class AuthenticationService { @Inject() mailMessages: AuthenticationMailMessages; + @Inject('repositories') + sysRepositories: any; + + @Inject() + tenantsManager: TenantsManager; + /** * Signin and generates JWT token. * @throws {ServiceError} @@ -50,20 +49,16 @@ export default class AuthenticationService { * @param {string} password - Password. * @return {Promise<{user: IUser, token: string}>} */ - async signIn(emailOrPhone: string, password: string): Promise<{user: IUser, token: string }> { + async signIn(emailOrPhone: string, password: string): Promise<{user: IUser, token: string, tenant: ITenant }> { this.logger.info('[login] Someone trying to login.', { emailOrPhone, password }); - const user = await SystemUser.query() - .where('email', emailOrPhone) - .orWhere('phone_number', emailOrPhone) - .withGraphFetched('tenant') - .first(); + const { systemUserRepository } = this.sysRepositories; + const user = await systemUserRepository.findByCrediential(emailOrPhone); if (!user) { this.logger.info('[login] invalid data'); throw new ServiceError('invalid_details'); } - this.logger.info('[login] check password validation.'); if (!user.verifyPassword(password)) { throw new ServiceError('invalid_password'); @@ -78,9 +73,7 @@ export default class AuthenticationService { const token = this.generateToken(user); this.logger.info('[login] updating user last login at.'); - await SystemUser.query() - .where('id', user.id) - .patch({ last_login_at: moment().toMySqlDateTime() }); + await systemUserRepository.patchLastLoginAt(user.id); this.logger.info('[login] Logging success.', { user, token }); @@ -88,11 +81,15 @@ export default class AuthenticationService { this.eventDispatcher.dispatch(events.auth.login, { emailOrPhone, password, }); + const tenant = await user.$relatedQuery('tenant'); // Remove password property from user object. Reflect.deleteProperty(user, 'password'); - return { user, token }; + // Remove id property from tenant object. + Reflect.deleteProperty(tenant, 'id'); + + return { user, token, tenant }; } /** @@ -101,18 +98,17 @@ export default class AuthenticationService { * @param {IRegisterDTO} registerDTO - Register data object. */ private async validateEmailAndPhoneUniqiness(registerDTO: IRegisterDTO) { - const user: ISystemUser = await SystemUser.query() - .where('email', registerDTO.email) - .orWhere('phone_number', registerDTO.phoneNumber) - .first(); + const { systemUserRepository } = this.sysRepositories; + const isEmailExists = await systemUserRepository.getByEmail(registerDTO.email); + const isPhoneExists = await systemUserRepository.getByPhoneNumber(registerDTO.phoneNumber); - const errorReasons: ServiceErrors[] = []; + const errorReasons: ServiceError[] = []; - if (user && user.phoneNumber === registerDTO.phoneNumber) { + if (isPhoneExists) { this.logger.info('[register] phone number exists on the storage.'); errorReasons.push(new ServiceError('phone_number_exists')); } - if (user && user.email === registerDTO.email) { + if (isEmailExists) { this.logger.info('[register] email exists on the storage.'); errorReasons.push(new ServiceError('email_exists')); } @@ -136,13 +132,13 @@ export default class AuthenticationService { this.logger.info('[register] Trying hashing the password.') const hashedPassword = await hashPassword(registerDTO.password); - const registeredUser = await SystemUser.query().insert({ + const { systemUserRepository } = this.sysRepositories; + const registeredUser = await systemUserRepository.create({ ...omit(registerDTO, 'country', 'organizationName'), active: true, password: hashedPassword, tenant_id: tenant.id, }); - // Triggers `onRegister` event. this.eventDispatcher.dispatch(events.auth.register, { registerDTO, user: registeredUser @@ -156,30 +152,7 @@ export default class AuthenticationService { * @return {Promise} */ private async newTenantOrganization(): Promise { - const organizationId = uniqid(); - const tenantOrganization = await Tenant.query().insert({ - organization_id: organizationId, - }); - return tenantOrganization; - } - - /** - * Initialize tenant database. - * @param {number} tenantId - The given tenant id. - * @return {void} - */ - async initializeTenant(tenantId: number): Promise { - const dbManager = Container.get('dbManager'); - - const tenant = await Tenant.query().findById(tenantId); - - this.logger.info('[tenant_init] Tenant DB creating.', { tenant }); - await dbManager.createDb(`bigcapital_tenant_${tenant.organizationId}`); - - const tenantDb = this.tenantsManager.knexInstance(tenant.organizationId); - - this.logger.info('[tenant_init] Tenant DB migrating to latest version.', { tenant }); - await tenantDb.migrate.latest(); + return this.tenantsManager.createTenant(); } /** @@ -188,12 +161,14 @@ export default class AuthenticationService { * @param {string} email - email address. */ private async validateEmailExistance(email: string) { - const foundEmail = await SystemUser.query().findOne('email', email); + const { systemUserRepository } = this.sysRepositories; + const userByEmail = await systemUserRepository.getByEmail(email); - if (!foundEmail) { + if (!userByEmail) { this.logger.info('[send_reset_password] The given email not found.'); throw new ServiceError('email_not_found'); } + return userByEmail; } /** @@ -203,7 +178,7 @@ export default class AuthenticationService { */ async sendResetPassword(email: string): Promise { this.logger.info('[send_reset_password] Trying to send reset password.'); - await this.validateEmailExistance(email); + const user = await this.validateEmailExistance(email); // Delete all stored tokens of reset password that associate to the give email. this.logger.info('[send_reset_password] trying to delete all tokens by email.'); @@ -213,7 +188,6 @@ export default class AuthenticationService { this.logger.info('[send_reset_password] insert the generated token.'); const passwordReset = await PasswordReset.query().insert({ email, token }); - const user = await SystemUser.query().findOne('email', email); // Triggers `onSendResetPassword` event. this.eventDispatcher.dispatch(events.auth.sendResetPassword, { user, token }); @@ -228,7 +202,8 @@ export default class AuthenticationService { * @return {Promise} */ async resetPassword(token: string, password: string): Promise { - const tokenModel = await PasswordReset.query().findOne('token', token) + const { systemUserRepository } = this.sysRepositories; + const tokenModel = await PasswordReset.query().findOne('token', token); if (!tokenModel) { this.logger.info('[reset_password] token invalid.'); @@ -242,7 +217,7 @@ export default class AuthenticationService { await this.deletePasswordResetToken(tokenModel.email); throw new ServiceError('token_expired'); } - const user = await SystemUser.query().findOne('email', tokenModel.email) + const user = await systemUserRepository.getByEmail(tokenModel.email); if (!user) { throw new ServiceError('user_not_found'); @@ -250,10 +225,8 @@ export default class AuthenticationService { const hashedPassword = await hashPassword(password); this.logger.info('[reset_password] saving a new hashed password.'); - await SystemUser.query() - .where('email', tokenModel.email) - .update({ password: hashedPassword }); - + await systemUserRepository.edit(user.id, { password: hashedPassword }); + // Deletes the used token. await this.deletePasswordResetToken(tokenModel.email); diff --git a/server/src/services/Contacts/ContactsService.ts b/server/src/services/Contacts/ContactsService.ts index ff5e9090e..633f46e6e 100644 --- a/server/src/services/Contacts/ContactsService.ts +++ b/server/src/services/Contacts/ContactsService.ts @@ -1,12 +1,13 @@ import { Inject, Service } from 'typedi'; -import { difference } from 'lodash'; -import { ServiceError } from "@/exceptions"; -import TenancyService from '@/services/Tenancy/TenancyService'; +import { difference, upperFirst } from 'lodash'; +import { ServiceError } from "exceptions"; +import TenancyService from 'services/Tenancy/TenancyService'; import { IContact, IContactNewDTO, IContactEditDTO, - } from "@/interfaces"; + } from "interfaces"; +import JournalPoster from '../Accounting/JournalPoster'; type TContactService = 'customer' | 'vendor'; @@ -128,4 +129,31 @@ export default class ContactsService { await Contact.query().whereIn('id', contactsIds).delete(); } + + /** + * Reverts journal entries of the given contacts. + * @param {number} tenantId + * @param {number[]} contactsIds + * @param {TContactService} contactService + */ + async revertJEntriesContactsOpeningBalance( + tenantId: number, + contactsIds: number[], + contactService: TContactService + ) { + const { AccountTransaction } = this.tenancy.models(tenantId); + const journal = new JournalPoster(tenantId); + + const contactsTransactions = await AccountTransaction.query() + .whereIn('reference_id', contactsIds) + .where('reference_type', `${upperFirst(contactService)}OpeningBalance`); + + journal.loadEntries(contactsTransactions); + journal.removeEntries(); + + await Promise.all([ + journal.saveBalance(), + journal.deleteEntries(), + ]); + } } \ No newline at end of file diff --git a/server/src/services/Contacts/CustomersService.ts b/server/src/services/Contacts/CustomersService.ts index 6d52baa77..97f85c927 100644 --- a/server/src/services/Contacts/CustomersService.ts +++ b/server/src/services/Contacts/CustomersService.ts @@ -1,14 +1,14 @@ import { Inject, Service } from 'typedi'; import { omit, difference } from 'lodash'; -import JournalPoster from "@/services/Accounting/JournalPoster"; -import JournalCommands from "@/services/Accounting/JournalCommands"; -import ContactsService from '@/services/Contacts/ContactsService'; +import JournalPoster from "services/Accounting/JournalPoster"; +import JournalCommands from "services/Accounting/JournalCommands"; +import ContactsService from 'services/Contacts/ContactsService'; import { ICustomerNewDTO, ICustomerEditDTO, - } from '@/interfaces'; -import { ServiceError } from '@/exceptions'; -import TenancyService from '@/services/Tenancy/TenancyService'; + } from 'interfaces'; +import { ServiceError } from 'exceptions'; +import TenancyService from 'services/Tenancy/TenancyService'; import { ICustomer } from 'src/interfaces'; @Service() @@ -44,7 +44,7 @@ export default class CustomersService { const customer = await this.contactService.newContact(tenantId, contactDTO, 'customer'); // Writes the customer opening balance journal entries. - if (customer.openingBalance) { + if (customer.openingBalance) { await this.writeCustomerOpeningBalanceJournal( tenantId, customer.id, @@ -71,8 +71,16 @@ export default class CustomersService { * @return {Promise} */ async deleteCustomer(tenantId: number, customerId: number) { + const { Contact } = this.tenancy.models(tenantId); + + await this.getCustomerByIdOrThrowError(tenantId, customerId); await this.customerHasNoInvoicesOrThrowError(tenantId, customerId); - return this.contactService.deleteContact(tenantId, customerId, 'customer'); + + await Contact.query().findById(customerId).delete(); + + await this.contactService.revertJEntriesContactsOpeningBalance( + tenantId, [customerId], 'customer', + ); } /** @@ -107,6 +115,15 @@ export default class CustomersService { ]); } + /** + * Retrieve the given customer by id or throw not found. + * @param {number} tenantId + * @param {number} customerId + */ + getCustomerByIdOrThrowError(tenantId: number, customerId: number) { + return this.contactService.getContactByIdOrThrowError(tenantId, customerId, 'customer'); + } + /** * Retrieve the given customers or throw error if one of them not found. * @param {numebr} tenantId @@ -129,6 +146,12 @@ export default class CustomersService { await this.customersHaveNoInvoicesOrThrowError(tenantId, customersIds); await Contact.query().whereIn('id', customersIds).delete(); + + await this.contactService.revertJEntriesContactsOpeningBalance( + tenantId, + customersIds, + 'Customer' + ); } /** diff --git a/server/src/services/Contacts/VendorsService.ts b/server/src/services/Contacts/VendorsService.ts index ecf7240c5..34c914799 100644 --- a/server/src/services/Contacts/VendorsService.ts +++ b/server/src/services/Contacts/VendorsService.ts @@ -1,15 +1,15 @@ import { Inject, Service } from 'typedi'; import { difference } from 'lodash'; -import JournalPoster from "@/services/Accounting/JournalPoster"; -import JournalCommands from "@/services/Accounting/JournalCommands"; -import ContactsService from '@/services/Contacts/ContactsService'; +import JournalPoster from "services/Accounting/JournalPoster"; +import JournalCommands from "services/Accounting/JournalCommands"; +import ContactsService from 'services/Contacts/ContactsService'; import { IVendorNewDTO, IVendorEditDTO, IVendor - } from '@/interfaces'; -import { ServiceError } from '@/exceptions'; -import TenancyService from '@/services/Tenancy/TenancyService'; + } from 'interfaces'; +import { ServiceError } from 'exceptions'; +import TenancyService from 'services/Tenancy/TenancyService'; @Service() export default class VendorsService { @@ -39,7 +39,8 @@ export default class VendorsService { * @return {Promise} */ async newVendor(tenantId: number, vendorDTO: IVendorNewDTO) { - const contactDTO = this.vendorToContactDTO(vendorDTO) + const contactDTO = this.vendorToContactDTO(vendorDTO); + const vendor = await this.contactService.newContact(tenantId, contactDTO, 'vendor'); // Writes the vendor opening balance journal entries. @@ -63,6 +64,15 @@ export default class VendorsService { return this.contactService.editContact(tenantId, vendorId, contactDTO, 'vendor'); } + /** + * Retrieve the given vendor details by id or throw not found. + * @param {number} tenantId + * @param {number} customerId + */ + getVendorByIdOrThrowError(tenantId: number, customerId: number) { + return this.contactService.getContactByIdOrThrowError(tenantId, customerId, 'vendor'); + } + /** * Deletes the given vendor from the storage. * @param {number} tenantId @@ -70,8 +80,16 @@ export default class VendorsService { * @return {Promise} */ async deleteVendor(tenantId: number, vendorId: number) { + const { Contact } = this.tenancy.models(tenantId); + + await this.getVendorByIdOrThrowError(tenantId, vendorId); await this.vendorHasNoBillsOrThrowError(tenantId, vendorId); - return this.contactService.deleteContact(tenantId, vendorId, 'vendor'); + + await Contact.query().findById(vendorId).delete(); + + await this.contactService.revertJEntriesContactsOpeningBalance( + tenantId, [vendorId], 'vendor', + ); } /** @@ -128,6 +146,10 @@ export default class VendorsService { await this.vendorsHaveNoBillsOrThrowError(tenantId, vendorsIds); await Contact.query().whereIn('id', vendorsIds).delete(); + + await this.contactService.revertJEntriesContactsOpeningBalance( + tenantId, vendorsIds, 'vendor', + ); } /** @@ -139,7 +161,7 @@ export default class VendorsService { const { vendorRepository } = this.tenancy.repositories(tenantId); const bills = await vendorRepository.getBills(vendorId); - if (bills) { + if (bills.length > 0) { throw new ServiceError('vendor_has_bills') } } diff --git a/server/src/services/CustomFields/ResourceCustomFieldRepository.js b/server/src/services/CustomFields/ResourceCustomFieldRepository.js index 73cca8b73..9c99dd48c 100644 --- a/server/src/services/CustomFields/ResourceCustomFieldRepository.js +++ b/server/src/services/CustomFields/ResourceCustomFieldRepository.js @@ -1,7 +1,7 @@ -import Resource from '@/models/Resource'; -import ResourceField from '@/models/ResourceField'; -import ResourceFieldMetadata from '@/models/ResourceFieldMetadata'; -import ResourceFieldMetadataCollection from '@/collection/ResourceFieldMetadataCollection'; +import Resource from 'models/Resource'; +import ResourceField from 'models/ResourceField'; +import ResourceFieldMetadata from 'models/ResourceFieldMetadata'; +import ResourceFieldMetadataCollection from 'collection/ResourceFieldMetadataCollection'; export default class ResourceCustomFieldRepository { /** diff --git a/server/src/services/DynamicListing/DynamicListing.js b/server/src/services/DynamicListing/DynamicListing.js index cad2ee990..b81ab1456 100644 --- a/server/src/services/DynamicListing/DynamicListing.js +++ b/server/src/services/DynamicListing/DynamicListing.js @@ -4,11 +4,11 @@ import { DynamicFilterSortBy, DynamicFilterViews, DynamicFilterFilterRoles, -} from '@/lib/DynamicFilter'; +} from 'lib/DynamicFilter'; import { mapViewRolesToConditionals, mapFilterRolesToDynamicFilter, -} from '@/lib/ViewRolesBuilder'; +} from 'lib/ViewRolesBuilder'; export const DYNAMIC_LISTING_ERRORS = { LOGIC_INVALID: 'VIEW.LOGIC.EXPRESSION.INVALID', diff --git a/server/src/services/DynamicListing/HasDynamicListing.js b/server/src/services/DynamicListing/HasDynamicListing.js index 0ce6a5c50..286fb0ab9 100644 --- a/server/src/services/DynamicListing/HasDynamicListing.js +++ b/server/src/services/DynamicListing/HasDynamicListing.js @@ -1,4 +1,4 @@ -import { DYNAMIC_LISTING_ERRORS } from '@/services/DynamicListing/DynamicListing'; +import { DYNAMIC_LISTING_ERRORS } from 'services/DynamicListing/DynamicListing'; export const dynamicListingErrorsToResponse = (error) => { let _errors; diff --git a/server/src/services/Expenses/ExpensesService.ts b/server/src/services/Expenses/ExpensesService.ts new file mode 100644 index 000000000..1d29cec6b --- /dev/null +++ b/server/src/services/Expenses/ExpensesService.ts @@ -0,0 +1,432 @@ +import { Service, Inject } from "typedi"; +import { difference, sumBy } from 'lodash'; +import moment from "moment"; +import { ServiceError } from "exceptions"; +import TenancyService from 'services/Tenancy/TenancyService'; +import JournalPoster from 'services/Accounting/JournalPoster'; +import JournalEntry from 'services/Accounting/JournalEntry'; +import JournalCommands from 'services/Accounting/JournalCommands'; +import { IExpense, IAccount, IExpenseDTO, IExpenseCategory, IExpensesService, ISystemUser } from 'interfaces'; + +const ERRORS = { + EXPENSE_NOT_FOUND: 'expense_not_found', + PAYMENT_ACCOUNT_NOT_FOUND: 'payment_account_not_found', + SOME_ACCOUNTS_NOT_FOUND: 'some_expenses_not_found', + TOTAL_AMOUNT_EQUALS_ZERO: 'total_amount_equals_zero', + PAYMENT_ACCOUNT_HAS_INVALID_TYPE: 'payment_account_has_invalid_type', + EXPENSES_ACCOUNT_HAS_INVALID_TYPE: 'expenses_account_has_invalid_type', + EXPENSE_ACCOUNT_ALREADY_PUBLISED: 'expense_already_published', +}; + +@Service() +export default class ExpensesService implements IExpensesService { + @Inject() + tenancy: TenancyService; + + @Inject('logger') + logger: any; + + /** + * Retrieve the payment account details or returns not found server error in case the + * given account not found on the storage. + * @param {number} tenantId + * @param {number} paymentAccountId + * @returns {Promise} + */ + async getPaymentAccountOrThrowError(tenantId: number, paymentAccountId: number) { + this.logger.info('[expenses] trying to get the given payment account.', { tenantId, paymentAccountId }); + + const { accountRepository } = this.tenancy.repositories(tenantId); + const paymentAccount = await accountRepository.getById(paymentAccountId) + + if (!paymentAccount) { + this.logger.info('[expenses] the given payment account not found.', { tenantId, paymentAccountId }); + throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_FOUND); + } + return paymentAccount; + } + + /** + * Retrieve expense accounts or throw error in case one of the given accounts + * not found not the storage. + * @param {number} tenantId + * @param {number} expenseAccountsIds + * @throws {ServiceError} + * @returns {Promise} + */ + async getExpensesAccountsOrThrowError(tenantId: number, expenseAccountsIds: number[]) { + this.logger.info('[expenses] trying to get expenses accounts.', { tenantId, expenseAccountsIds }); + + const { Account } = this.tenancy.models(tenantId); + const storedExpenseAccounts = await Account.query().whereIn( + 'id', expenseAccountsIds, + ); + const storedExpenseAccountsIds = storedExpenseAccounts.map((a: IAccount) => a.id); + const notStoredAccountsIds = difference( + expenseAccountsIds, + storedExpenseAccountsIds + ); + if (notStoredAccountsIds.length > 0) { + this.logger.info('[expenses] some of expense accounts not found.', { tenantId, expenseAccountsIds }); + throw new ServiceError(ERRORS.SOME_ACCOUNTS_NOT_FOUND); + } + return storedExpenseAccounts; + } + + /** + * Validates expense categories not equals zero. + * @param {IExpenseDTO|ServiceError} expenseDTO + * @throws {ServiceError} + */ + validateCategoriesNotEqualZero(expenseDTO: IExpenseDTO) { + this.logger.info('[expenses] validate the expenses categoires not equal zero.', { expenseDTO }); + const totalAmount = sumBy(expenseDTO.categories, 'amount') || 0; + + if (totalAmount <= 0) { + this.logger.info('[expenses] the given expense categories equal zero.', { expenseDTO }); + throw new ServiceError(ERRORS.TOTAL_AMOUNT_EQUALS_ZERO); + } + } + + /** + * Validate expenses accounts type. + * @param {number} tenantId + * @param {number[]} expensesAccountsIds + */ + async validateExpensesAccountsType(tenantId: number, expensesAccounts: number[]) { + this.logger.info('[expenses] trying to validate expenses accounts type.', { tenantId, expensesAccounts }); + + const { accountTypeRepository } = this.tenancy.repositories(tenantId); + const expensesTypes = await accountTypeRepository.getByRootType('expense'); + const expensesTypesIds = expensesTypes.map(t => t.id); + const invalidExpenseAccounts: number[] = []; + + expensesAccounts.forEach((expenseAccount) => { + if (expensesTypesIds.indexOf(expenseAccount.accountTypeId) === -1) { + invalidExpenseAccounts.push(expenseAccount.id); + } + }); + if (invalidExpenseAccounts.length > 0) { + throw new ServiceError(ERRORS.EXPENSES_ACCOUNT_HAS_INVALID_TYPE); + } + } + + /** + * Validates payment account type in case has invalid type throws errors. + * @param {number} tenantId + * @param {number} paymentAccountId + * @throws {ServiceError} + */ + async validatePaymentAccountType(tenantId: number, paymentAccount: number[]) { + this.logger.info('[expenses] trying to validate payment account type.', { tenantId, paymentAccount }); + + const { accountTypeRepository } = this.tenancy.repositories(tenantId); + const validAccountsType = await accountTypeRepository.getByKeys([ + 'current_asset', 'fixed_asset', + ]); + const validAccountsTypeIds = validAccountsType.map(t => t.id); + + if (validAccountsTypeIds.indexOf(paymentAccount.accountTypeId) === -1) { + this.logger.info('[expenses] the given payment account has invalid type', { tenantId, paymentAccount }); + throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_HAS_INVALID_TYPE); + } + } + + + async revertJournalEntries( + tenantId: number, + expenseId: number|number[], + ) { + const journal = new JournalPoster(tenantId); + const journalCommands = new JournalCommands(journal); + + if (revertOld) { + await journalCommands.revertJournalEntries(expenseId, 'Expense'); + } + return Promise.all([ + journal.saveBalance(), + journal.deleteEntries(), + ]); + } + + /** + * Writes expense journal entries. + * @param {number} tenantId + * @param {IExpense} expense + * @param {IUser} authorizedUser + */ + async writeJournalEntries( + tenantId: number, + expense: IExpense, + revertOld: boolean, + authorizedUser: ISystemUser + ) { + this.logger.info('[expense[ trying to write expense journal entries.', { tenantId, expense }); + const journal = new JournalPoster(tenantId); + const journalCommands = new JournalCommands(journal); + + if (revertOld) { + await journalCommands.revertJournalEntries(expense.id, 'Expense'); + } + const mixinEntry = { + referenceType: 'Expense', + referenceId: expense.id, + date: expense.paymentDate, + userId: authorizedUser.id, + draft: !expense.publish, + }; + const paymentJournalEntry = new JournalEntry({ + credit: expense.totalAmount, + account: expense.paymentAccountId, + ...mixinEntry, + }); + journal.credit(paymentJournalEntry); + + expense.categories.forEach((category: IExpenseCategory) => { + const expenseJournalEntry = new JournalEntry({ + account: category.expenseAccountId, + debit: category.amount, + note: category.description, + ...mixinEntry, + }); + journal.debit(expenseJournalEntry); + }); + return Promise.all([ + journal.saveBalance(), + journal.saveEntries(), + journal.deleteEntries(), + ]); + } + + /** + * Retrieve the given expenses or throw not found error. + * @param {number} tenantId + * @param {number} expenseId + * @returns {IExpense|ServiceError} + */ + async getExpenseOrThrowError(tenantId: number, expenseId: number) { + const { expenseRepository } = this.tenancy.repositories(tenantId); + + this.logger.info('[expense] trying to get the given expense.', { tenantId, expenseId }); + const expense = await expenseRepository.getById(expenseId); + + if (!expense) { + this.logger.info('[expense] the given expense not found.', { tenantId, expenseId }); + throw new ServiceError(ERRORS.EXPENSE_NOT_FOUND); + } + return expense; + } + + async getExpensesOrThrowError(tenantId: number, expensesIds: number[]) { + + } + + /** + * Validates expenses is not already published before. + * @param {IExpense} expense + */ + validateExpenseIsNotPublished(expense: IExpense) { + if (expense.published) { + throw new ServiceError(ERRORS.EXPENSE_ACCOUNT_ALREADY_PUBLISED); + } + } + + /** + * Mapping expense DTO to model. + * @param {IExpenseDTO} expenseDTO + * @return {IExpense} + */ + expenseDTOToModel(expenseDTO: IExpenseDTO) { + const totalAmount = sumBy(expenseDTO.categories, 'amount'); + + return { + published: false, + categories: [], + ...expenseDTO, + totalAmount, + paymentDate: moment(expenseDTO.paymentDate).toMySqlDateTime(), + } + } + + /** + * Mapping the expenses accounts ids from expense DTO. + * @param {IExpenseDTO} expenseDTO + * @return {number[]} + */ + mapExpensesAccountsIdsFromDTO(expenseDTO: IExpenseDTO) { + return expenseDTO.categories.map((category) => category.expenseAccountId); + } + + /** + * Precedures. + * --------- + * 1. Validate expense existance. + * 2. Validate payment account existance on the storage. + * 3. Validate expense accounts exist on the storage. + * 4. Validate payment account type. + * 5. Validate expenses accounts type. + * 6. Validate the given expense categories not equal zero. + * 7. Stores the expense to the storage. + * --------- + * @param {number} tenantId + * @param {number} expenseId + * @param {IExpenseDTO} expenseDTO + */ + async editExpense( + tenantId: number, + expenseId: number, + expenseDTO: IExpenseDTO, + authorizedUser: ISystemUser + ): Promise { + const { expenseRepository } = this.tenancy.repositories(tenantId); + const expense = await this.getExpenseOrThrowError(tenantId, expenseId); + + // 1. Validate payment account existance on the storage. + const paymentAccount = await this.getPaymentAccountOrThrowError( + tenantId, + expenseDTO.paymentAccountId, + ); + // 2. Validate expense accounts exist on the storage. + const expensesAccounts = await this.getExpensesAccountsOrThrowError( + tenantId, + this.mapExpensesAccountsIdsFromDTO(expenseDTO), + ); + // 3. Validate payment account type. + await this.validatePaymentAccountType(tenantId, paymentAccount); + + // 4. Validate expenses accounts type. + await this.validateExpensesAccountsType(tenantId, expensesAccounts); + + // 5. Validate the given expense categories not equal zero. + this.validateCategoriesNotEqualZero(expenseDTO); + + // 6. Update the expense on the storage. + const expenseObj = this.expenseDTOToModel(expenseDTO); + const expenseModel = await expenseRepository.update(expenseId, expenseObj, null); + + // 7. In case expense published, write journal entries. + if (expenseObj.published) { + await this.writeJournalEntries(tenantId, expenseModel, true, authorizedUser); + } + this.logger.info('[expense] the expense updated on the storage successfully.', { tenantId, expenseDTO }); + return expenseModel; + } + + /** + * Precedures. + * --------- + * 1. Validate payment account existance on the storage. + * 2. Validate expense accounts exist on the storage. + * 3. Validate payment account type. + * 4. Validate expenses accounts type. + * 5. Validate the given expense categories not equal zero. + * 6. Stores the expense to the storage. + * --------- + * @param {number} tenantId + * @param {IExpenseDTO} expenseDTO + */ + async newExpense(tenantId: number, expenseDTO: IExpenseDTO, authorizedUser: ISystemUser): Promise { + const { expenseRepository } = this.tenancy.repositories(tenantId); + + // 1. Validate payment account existance on the storage. + const paymentAccount = await this.getPaymentAccountOrThrowError( + tenantId, + expenseDTO.paymentAccountId, + ); + // 2. Validate expense accounts exist on the storage. + const expensesAccounts = await this.getExpensesAccountsOrThrowError( + tenantId, + this.mapExpensesAccountsIdsFromDTO(expenseDTO), + ); + // 3. Validate payment account type. + await this.validatePaymentAccountType(tenantId, paymentAccount); + + // 4. Validate expenses accounts type. + await this.validateExpensesAccountsType(tenantId, expensesAccounts); + + // 5. Validate the given expense categories not equal zero. + this.validateCategoriesNotEqualZero(expenseDTO); + + // 6. Save the expense to the storage. + const expenseObj = this.expenseDTOToModel(expenseDTO); + const expenseModel = await expenseRepository.create(expenseObj); + + // 7. In case expense published, write journal entries. + if (expenseObj.published) { + await this.writeJournalEntries(tenantId, expenseModel, false, authorizedUser); + } + this.logger.info('[expense] the expense stored to the storage successfully.', { tenantId, expenseDTO }); + + return expenseModel; + } + + /** + * Publish the given expense. + * @param {number} tenantId + * @param {number} expenseId + * @return {Promise} + */ + async publishExpense(tenantId: number, expenseId: number) { + const { expenseRepository } = this.tenancy.repositories(tenantId); + const expense = await this.getExpenseOrThrowError(tenantId, expenseId); + + if (expense instanceof ServiceError) { + throw expense; + } + this.validateExpenseIsNotPublished(expense); + + this.logger.info('[expense] trying to publish the expense.', { tenantId, expenseId }); + await expenseRepository.publish(expenseId); + + this.logger.info('[expense] the expense published successfully.', { tenantId, expenseId }); + } + + /** + * Deletes the given expense. + * @param {number} tenantId + * @param {number} expenseId + */ + async deleteExpense(tenantId: number, expenseId: number) { + const expense = await this.getExpenseOrThrowError(tenantId, expenseId); + const { expenseRepository } = this.tenancy.repositories(tenantId); + + this.logger.info('[expense] trying to delete the expense.', { tenantId, expenseId }); + await expenseRepository.delete(expenseId); + + if (expense.published) { + await this.revertJournalEntries(tenantId, expenseId); + } + this.logger.info('[expense] the expense deleted successfully.', { tenantId, expenseId }); + } + + /** + * Deletes the given expenses in bulk. + * @param {number} tenantId + * @param {number[]} expensesIds + */ + async deleteBulkExpenses(tenantId: number, expensesIds: number[]) { + const expenses = await this.getExpensesOrThrowError(tenantId, expensesIds); + const { expenseRepository } = this.tenancy.repositories(tenantId); + + this.logger.info('[expense] trying to delete the given expenses.', { tenantId, expensesIds }); + await expenseRepository.bulkDelete(expensesIds); + await this.revertJournalEntries(tenantId, expensesIds); + + this.logger.info('[expense] the given expenses deleted successfully.', { tenantId, expensesIds }); + } + + /** + * Deletes the given expenses in bulk. + * @param {number} tenantId + * @param {number[]} expensesIds + */ + async publishBulkExpenses(tenantId: number, expensesIds: number[]) { + const expenses = await this.getExpensesOrThrowError(tenantId, expensesIds); + const { expenseRepository } = this.tenancy.repositories(tenantId); + + this.logger.info('[expense] trying to publish the given expenses.', { tenantId, expensesIds }); + await expenseRepository.publishBulk(expensesIds); + + this.logger.info('[expense] the given expenses ids published successfully.', { tenantId, expensesIds }); + } +} \ No newline at end of file diff --git a/server/src/services/Inventory/Inventory.ts b/server/src/services/Inventory/Inventory.ts index 68ad44bd3..54655992a 100644 --- a/server/src/services/Inventory/Inventory.ts +++ b/server/src/services/Inventory/Inventory.ts @@ -1,7 +1,7 @@ import { Container, Service, Inject } from 'typedi'; -import InventoryAverageCost from '@/services/Inventory/InventoryAverageCost'; -import InventoryCostLotTracker from '@/services/Inventory/InventoryCostLotTracker'; -import TenancyService from '@/services/Tenancy/TenancyService'; +import InventoryAverageCost from 'services/Inventory/InventoryAverageCost'; +import InventoryCostLotTracker from 'services/Inventory/InventoryCostLotTracker'; +import TenancyService from 'services/Tenancy/TenancyService'; type TCostMethod = 'FIFO' | 'LIFO' | 'AVG'; diff --git a/server/src/services/Inventory/InventoryAverageCost.ts b/server/src/services/Inventory/InventoryAverageCost.ts index 36e0cb9c1..8724eec09 100644 --- a/server/src/services/Inventory/InventoryAverageCost.ts +++ b/server/src/services/Inventory/InventoryAverageCost.ts @@ -1,6 +1,6 @@ import { pick } from 'lodash'; -import { IInventoryTransaction } from '@/interfaces'; -import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod'; +import { IInventoryTransaction } from 'interfaces'; +import InventoryCostMethod from 'services/Inventory/InventoryCostMethod'; export default class InventoryAverageCostMethod extends InventoryCostMethod implements IInventoryCostMethod { startingDate: Date; diff --git a/server/src/services/Inventory/InventoryCostLotTracker.ts b/server/src/services/Inventory/InventoryCostLotTracker.ts index 03e94d950..4e7aeee4a 100644 --- a/server/src/services/Inventory/InventoryCostLotTracker.ts +++ b/server/src/services/Inventory/InventoryCostLotTracker.ts @@ -1,7 +1,7 @@ import { pick, chain } from 'lodash'; import moment from 'moment'; -import { IInventoryLotCost, IInventoryTransaction } from "@/interfaces"; -import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod'; +import { IInventoryLotCost, IInventoryTransaction } from "interfaces"; +import InventoryCostMethod from 'services/Inventory/InventoryCostMethod'; type TCostMethod = 'FIFO' | 'LIFO'; diff --git a/server/src/services/Inventory/InventoryCostMethod.ts b/server/src/services/Inventory/InventoryCostMethod.ts index 9d8297daa..a24345bf6 100644 --- a/server/src/services/Inventory/InventoryCostMethod.ts +++ b/server/src/services/Inventory/InventoryCostMethod.ts @@ -1,7 +1,7 @@ import { omit } from 'lodash'; import { Inject } from 'typedi'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import { IInventoryLotCost } from '@/interfaces'; +import TenancyService from 'services/Tenancy/TenancyService'; +import { IInventoryLotCost } from 'interfaces'; export default class InventoryCostMethod { @Inject() diff --git a/server/src/services/InviteUsers/InviteUsersMailMessages.ts b/server/src/services/InviteUsers/InviteUsersMailMessages.ts index bb37ff4ba..9730c813e 100644 --- a/server/src/services/InviteUsers/InviteUsersMailMessages.ts +++ b/server/src/services/InviteUsers/InviteUsersMailMessages.ts @@ -4,7 +4,7 @@ import { Service } from "typedi"; export default class InviteUsersMailMessages { sendInviteMail() { - const filePath = path.join(global.rootPath, 'views/mail/UserInvite.html'); + const filePath = path.join(global.__root, 'views/mail/UserInvite.html'); const template = fs.readFileSync(filePath, 'utf8'); const rendered = Mustache.render(template, { diff --git a/server/src/services/InviteUsers/index.ts b/server/src/services/InviteUsers/index.ts index 6e1f61939..be2ea602b 100644 --- a/server/src/services/InviteUsers/index.ts +++ b/server/src/services/InviteUsers/index.ts @@ -4,19 +4,18 @@ import moment from 'moment'; import { EventDispatcher, EventDispatcherInterface, -} from '@/decorators/eventDispatcher'; -import { ServiceError } from "@/exceptions"; -import { SystemUser, Invite, Tenant } from "@/system/models"; -import { Option } from '@/models'; -import { hashPassword } from '@/utils'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import TenantsManager from "@/system/TenantsManager"; -import InviteUsersMailMessages from "@/services/InviteUsers/InviteUsersMailMessages"; -import events from '@/subscribers/events'; +} from 'decorators/eventDispatcher'; +import { ServiceError } from "exceptions"; +import { Invite, Tenant } from "system/models"; +import { Option } from 'models'; +import { hashPassword } from 'utils'; +import TenancyService from 'services/Tenancy/TenancyService'; +import InviteUsersMailMessages from "services/InviteUsers/InviteUsersMailMessages"; +import events from 'subscribers/events'; import { ISystemUser, IInviteUserInput, -} from '@/interfaces'; +} from 'interfaces'; @Service() export default class InviteUserService { @@ -26,15 +25,15 @@ export default class InviteUserService { @Inject() tenancy: TenancyService; - @Inject() - tenantsManager: TenantsManager; - @Inject('logger') logger: any; @Inject() mailMessages: InviteUsersMailMessages; + @Inject('repositories') + sysRepositories: any; + /** * Accept the received invite. * @param {string} token @@ -50,24 +49,26 @@ export default class InviteUserService { const hashedPassword = await hashPassword(inviteUserInput.password); this.logger.info('[accept_invite] trying to update user details.'); - const updateUserOper = SystemUser.query() - .where('email', inviteToken.email) - .patch({ - ...inviteUserInput, - active: 1, - invite_accepted_at: moment().format('YYYY-MM-DD'), - password: hashedPassword, - }); + const { systemUserRepository } = this.sysRepositories; + + const user = await systemUserRepository.getByEmail(inviteToken.email); + + const updateUserOper = systemUserRepository.edit(user.id, { + ...inviteUserInput, + active: 1, + invite_accepted_at: moment().format('YYYY-MM-DD'), + password: hashedPassword, + }); this.logger.info('[accept_invite] trying to delete the given token.'); const deleteInviteTokenOper = Invite.query().where('token', inviteToken.token).delete(); // Await all async operations. - const [user] = await Promise.all([updateUserOper, deleteInviteTokenOper]); + const [updatedUser] = await Promise.all([updateUserOper, deleteInviteTokenOper]); // Triggers `onUserAcceptInvite` event. this.eventDispatcher.dispatch(events.inviteUser.acceptInvite, { - inviteToken, user, + inviteToken, user: updatedUser, }); } @@ -79,7 +80,7 @@ export default class InviteUserService { * * @return {Promise} */ - public async sendInvite(tenantId: number, email: string, authorizedUser: ISystemUser): Promise { + public async sendInvite(tenantId: number, email: string, authorizedUser: ISystemUser): Promise<{ invite: IInvite, user: ISystemUser }> { await this.throwErrorIfUserEmailExists(email); this.logger.info('[send_invite] trying to store invite token.'); @@ -90,11 +91,12 @@ export default class InviteUserService { }); this.logger.info('[send_invite] trying to store user with email and tenant.'); - const user = await SystemUser.query().insert({ + const { systemUserRepository } = this.sysRepositories; + const user = await systemUserRepository.create({ email, tenant_id: authorizedUser.tenantId, active: 1, - }) + }); // Triggers `onUserSendInvite` event. this.eventDispatcher.dispatch(events.inviteUser.sendInvite, { @@ -130,12 +132,14 @@ export default class InviteUserService { * Throws error in case the given user email not exists on the storage. * @param {string} email */ - private async throwErrorIfUserEmailExists(email: string) { - const foundUser = await SystemUser.query().findOne('email', email); + private async throwErrorIfUserEmailExists(email: string): Promise { + const { systemUserRepository } = this.sysRepositories; + const foundUser = await systemUserRepository.getByEmail(email); if (foundUser) { throw new ServiceError('email_already_invited'); } + return foundUser; } /** @@ -158,12 +162,9 @@ export default class InviteUserService { * Validate the given user email and phone number uniquine. * @param {IInviteUserInput} inviteUserInput */ - private async validateUserPhoneNumber(inviteUserInput: IInviteUserInput) { - const foundUser = await SystemUser.query() - .onBuild(query => { - query.where('phone_number', inviteUserInput.phoneNumber); - query.first(); - }); + private async validateUserPhoneNumber(inviteUserInput: IInviteUserInput): Promise { + const { systemUserRepository } = this.sysRepositories; + const foundUser = await systemUserRepository.getByPhoneNumber(inviteUserInput.phoneNumber) if (foundUser) { throw new ServiceError('phone_number_exists'); diff --git a/server/src/services/Items/ItemsService.ts b/server/src/services/Items/ItemsService.ts index 94f5bc4fd..9961a3ab3 100644 --- a/server/src/services/Items/ItemsService.ts +++ b/server/src/services/Items/ItemsService.ts @@ -1,6 +1,6 @@ import { difference } from "lodash"; import { Service, Inject } from "typedi"; -import TenancyService from '@/services/Tenancy/TenancyService'; +import TenancyService from 'services/Tenancy/TenancyService'; @Service() export default class ItemsService { diff --git a/server/src/services/Organization/index.ts b/server/src/services/Organization/index.ts index 095fbb5f4..88ef7515a 100644 --- a/server/src/services/Organization/index.ts +++ b/server/src/services/Organization/index.ts @@ -1,54 +1,94 @@ -import { Service, Inject, Container } from 'typedi'; -import { Tenant, SystemUser } from '@/system/models'; -import TenantsManager from '@/system/TenantsManager'; -import { ServiceError } from '@/exceptions'; -import { ITenant } from '@/interfaces'; +import { Service, Inject } from 'typedi'; +import { ServiceError } from 'exceptions'; +import { ITenant } from 'interfaces'; import { EventDispatcher, EventDispatcherInterface, -} from '@/decorators/eventDispatcher'; -import events from '@/subscribers/events'; +} from 'decorators/eventDispatcher'; +import events from 'subscribers/events'; +import { + TenantAlreadyInitialized, + TenantAlreadySeeded, + TenantDatabaseNotBuilt +} from 'exceptions'; +import TenantsManager from 'services/Tenancy/TenantsManager'; +const ERRORS = { + TENANT_NOT_FOUND: 'tenant_not_found', + TENANT_ALREADY_INITIALIZED: 'tenant_already_initialized', + TENANT_ALREADY_SEEDED: 'tenant_already_seeded', + TENANT_DB_NOT_BUILT: 'tenant_db_not_built', +}; @Service() export default class OrganizationService { @EventDispatcher() eventDispatcher: EventDispatcherInterface; - @Inject() - tenantsManager: TenantsManager; - - @Inject('dbManager') - dbManager: any; - @Inject('logger') logger: any; + @Inject('repositories') + sysRepositories: any; + + @Inject() + tenantsManager: TenantsManager; + /** * Builds the database schema and seed data of the given organization id. - * @param {srting} organizationId + * @param {srting} organizationId * @return {Promise} */ async build(organizationId: string): Promise { - const tenant = await Tenant.query().findOne('organization_id', organizationId); - this.throwIfTenantNotExists(tenant); + const tenant = await this.getTenantByOrgIdOrThrowError(organizationId); this.throwIfTenantInitizalized(tenant); - this.logger.info('[tenant_db_build] tenant DB creating.', { tenant }); - await this.dbManager.createDb(`bigcapital_tenant_${tenant.organizationId}`); - - const tenantDb = this.tenantsManager.knexInstance(tenant.organizationId); + const tenantHasDB = await this.tenantsManager.hasDatabase(tenant); - this.logger.info('[tenant_db_build] tenant DB migrating to latest version.', { tenant }); - await tenantDb.migrate.latest(); + try { + if (!tenantHasDB) { + this.logger.info('[organization] trying to create tenant database.', { organizationId }); + await this.tenantsManager.createDatabase(tenant); + } + this.logger.info('[organization] trying to migrate tenant database.', { organizationId }); + await this.tenantsManager.migrateTenant(tenant); - this.logger.info('[tenant_db_build] mark tenant as initialized.', { tenant }); - await tenant.$query().update({ initialized: true }); + // Throws `onOrganizationBuild` event. + this.eventDispatcher.dispatch(events.organization.build, { tenant }); - // Retrieve the tenant system user. - const user = await SystemUser.query().findOne('tenant_id', tenant.id); + } catch (error) { + if (error instanceof TenantAlreadyInitialized) { + throw new ServiceError(ERRORS.TENANT_ALREADY_INITIALIZED); + } else { + throw error; + } + } + } - // Throws `onOrganizationBuild` event. - this.eventDispatcher.dispatch(events.organization.build, { tenant, user }); + /** + * Seeds initial core data to the given organization tenant. + * @param {number} organizationId + * @return {Promise} + */ + async seed(organizationId: string): Promise { + const tenant = await this.getTenantByOrgIdOrThrowError(organizationId); + this.throwIfTenantSeeded(tenant); + + try { + this.logger.info('[organization] trying to seed tenant database.', { organizationId }); + await this.tenantsManager.seedTenant(tenant); + + // Throws `onOrganizationBuild` event. + this.eventDispatcher.dispatch(events.organization.seeded, { tenant }); + + } catch (error) { + if (error instanceof TenantAlreadySeeded) { + throw new ServiceError(ERRORS.TENANT_ALREADY_SEEDED); + } else if (error instanceof TenantDatabaseNotBuilt) { + throw new ServiceError(ERRORS.TENANT_DB_NOT_BUILT); + } else { + throw error; + } + } } /** @@ -58,7 +98,7 @@ export default class OrganizationService { private throwIfTenantNotExists(tenant: ITenant) { if (!tenant) { this.logger.info('[tenant_db_build] organization id not found.'); - throw new ServiceError('tenant_not_found'); + throw new ServiceError(ERRORS.TENANT_NOT_FOUND); } } @@ -67,8 +107,32 @@ export default class OrganizationService { * @param {ITenant} tenant */ private throwIfTenantInitizalized(tenant: ITenant) { - if (tenant.initialized) { - throw new ServiceError('tenant_initialized'); + if (tenant.initializedAt) { + throw new ServiceError(ERRORS.TENANT_ALREADY_INITIALIZED); } } + + /** + * Throws service if the tenant already seeded. + * @param {ITenant} tenant + */ + private throwIfTenantSeeded(tenant: ITenant) { + if (tenant.seededAt) { + throw new ServiceError(ERRORS.TENANT_ALREADY_SEEDED); + } + } + + /** + * Retrieve tenant model by the given organization id or throw not found + * error if the tenant not exists on the storage. + * @param {string} organizationId + * @return {ITenant} + */ + private async getTenantByOrgIdOrThrowError(organizationId: string) { + const { tenantRepository } = this.sysRepositories; + const tenant = await tenantRepository.getByOrgId(organizationId); + this.throwIfTenantNotExists(tenant); + + return tenant; + } } \ No newline at end of file diff --git a/server/src/services/Payment/License.ts b/server/src/services/Payment/License.ts index c2530b8dd..1e7c8a337 100644 --- a/server/src/services/Payment/License.ts +++ b/server/src/services/Payment/License.ts @@ -1,10 +1,10 @@ import { Service, Container, Inject } from 'typedi'; import cryptoRandomString from 'crypto-random-string'; import { times } from 'lodash'; -import { License } from "@/system/models"; -import { ILicense } from '@/interfaces'; -import LicenseMailMessages from '@/services/Payment/LicenseMailMessages'; -import LicenseSMSMessages from '@/services/Payment/LicenseSMSMessages'; +import { License } from "system/models"; +import { ILicense } from 'interfaces'; +import LicenseMailMessages from 'services/Payment/LicenseMailMessages'; +import LicenseSMSMessages from 'services/Payment/LicenseSMSMessages'; @Service() export default class LicenseService { @@ -27,8 +27,6 @@ export default class LicenseService { let licenseCode: string; let repeat: boolean = true; - console.log(License); - while(repeat) { licenseCode = cryptoRandomString({ length: 10, type: 'numeric' }); const foundLicenses = await License.query().where('license_code', licenseCode); diff --git a/server/src/services/Payment/LicenseMailMessages.ts b/server/src/services/Payment/LicenseMailMessages.ts index a6e56e54f..fce87b5b6 100644 --- a/server/src/services/Payment/LicenseMailMessages.ts +++ b/server/src/services/Payment/LicenseMailMessages.ts @@ -13,7 +13,7 @@ export default class SubscriptionMailMessages { const Logger = Container.get('logger'); const Mail = Container.get('mail'); - const filePath = path.join(global.rootPath, 'views/mail/LicenseReceive.html'); + const filePath = path.join(global.__root, 'views/mail/LicenseReceive.html'); const template = fs.readFileSync(filePath, 'utf8'); const rendered = Mustache.render(template, { licenseCode }); diff --git a/server/src/services/Payment/LicensePaymentMethod.ts b/server/src/services/Payment/LicensePaymentMethod.ts index 101156a53..88e428892 100644 --- a/server/src/services/Payment/LicensePaymentMethod.ts +++ b/server/src/services/Payment/LicensePaymentMethod.ts @@ -1,9 +1,9 @@ -import { License } from "@/system/models"; -import PaymentMethod from '@/services/Payment/PaymentMethod'; -import { Plan } from '@/system/models'; -import { IPaymentMethod, ILicensePaymentModel } from '@/interfaces'; -import { ILicensePaymentModel } from "@/interfaces"; -import { PaymentInputInvalid, PaymentAmountInvalidWithPlan } from '@/exceptions'; +import { License } from "system/models"; +import PaymentMethod from 'services/Payment/PaymentMethod'; +import { Plan } from 'system/models'; +import { IPaymentMethod, ILicensePaymentModel } from 'interfaces'; +import { ILicensePaymentModel } from "interfaces"; +import { PaymentInputInvalid, PaymentAmountInvalidWithPlan } from 'exceptions'; export default class LicensePaymentMethod extends PaymentMethod implements IPaymentMethod { /** diff --git a/server/src/services/Payment/LicenseSMSMessages.ts b/server/src/services/Payment/LicenseSMSMessages.ts index 207e6a9a8..70ed40a9e 100644 --- a/server/src/services/Payment/LicenseSMSMessages.ts +++ b/server/src/services/Payment/LicenseSMSMessages.ts @@ -1,5 +1,5 @@ import { Container, Inject } from 'typedi'; -import SMSClient from '@/services/SMSClient'; +import SMSClient from 'services/SMSClient'; export default class SubscriptionSMSMessages { @Inject('SMSClient') diff --git a/server/src/services/Payment/PaymentMethod.ts b/server/src/services/Payment/PaymentMethod.ts index a0c1072f0..1f6347d43 100644 --- a/server/src/services/Payment/PaymentMethod.ts +++ b/server/src/services/Payment/PaymentMethod.ts @@ -1,5 +1,5 @@ import moment from 'moment'; -import { IPaymentModel } from '@/interfaces'; +import { IPaymentModel } from 'interfaces'; export default class PaymentMethod implements IPaymentModel { diff --git a/server/src/services/Payment/index.ts b/server/src/services/Payment/index.ts index e963748ed..e40d8d7db 100644 --- a/server/src/services/Payment/index.ts +++ b/server/src/services/Payment/index.ts @@ -1,5 +1,5 @@ -import { IPaymentMethod, IPaymentContext } from "@/interfaces"; -import { Plan } from '@/system/models'; +import { IPaymentMethod, IPaymentContext } from "interfaces"; +import { Plan } from 'system/models'; export default class PaymentContext implements IPaymentContext{ paymentMethod: IPaymentMethod; diff --git a/server/src/services/Purchases/BillPayments.ts b/server/src/services/Purchases/BillPayments.ts index a42608ee3..4389f2267 100644 --- a/server/src/services/Purchases/BillPayments.ts +++ b/server/src/services/Purchases/BillPayments.ts @@ -1,14 +1,14 @@ import { Inject, Service } from 'typedi'; import { omit, sumBy } from 'lodash'; import moment from 'moment'; -import { IBillPaymentOTD, IBillPayment } from '@/interfaces'; -import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries'; -import AccountsService from '@/services/Accounts/AccountsService'; -import JournalPoster from '@/services/Accounting/JournalPoster'; -import JournalEntry from '@/services/Accounting/JournalEntry'; -import JournalPosterService from '@/services/Sales/JournalPosterService'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import { formatDateFields } from '@/utils'; +import { IBillPaymentOTD, IBillPayment } from 'interfaces'; +import ServiceItemsEntries from 'services/Sales/ServiceItemsEntries'; +import AccountsService from 'services/Accounts/AccountsService'; +import JournalPoster from 'services/Accounting/JournalPoster'; +import JournalEntry from 'services/Accounting/JournalEntry'; +import JournalPosterService from 'services/Sales/JournalPosterService'; +import TenancyService from 'services/Tenancy/TenancyService'; +import { formatDateFields } from 'utils'; /** * Bill payments service. diff --git a/server/src/services/Purchases/Bills.ts b/server/src/services/Purchases/Bills.ts index 5d0636e16..0cbb66903 100644 --- a/server/src/services/Purchases/Bills.ts +++ b/server/src/services/Purchases/Bills.ts @@ -1,16 +1,16 @@ import { omit, sumBy, pick } from 'lodash'; import moment from 'moment'; import { Inject, Service } from 'typedi'; -import JournalPoster from '@/services/Accounting/JournalPoster'; -import JournalEntry from '@/services/Accounting/JournalEntry'; -import AccountsService from '@/services/Accounts/AccountsService'; -import JournalPosterService from '@/services/Sales/JournalPosterService'; -import InventoryService from '@/services/Inventory/Inventory'; -import HasItemsEntries from '@/services/Sales/HasItemsEntries'; -import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import { formatDateFields } from '@/utils'; -import{ IBillOTD, IBill, IItem } from '@/interfaces'; +import JournalPoster from 'services/Accounting/JournalPoster'; +import JournalEntry from 'services/Accounting/JournalEntry'; +import AccountsService from 'services/Accounts/AccountsService'; +import JournalPosterService from 'services/Sales/JournalPosterService'; +import InventoryService from 'services/Inventory/Inventory'; +import HasItemsEntries from 'services/Sales/HasItemsEntries'; +import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost'; +import TenancyService from 'services/Tenancy/TenancyService'; +import { formatDateFields } from 'utils'; +import{ IBillOTD, IBill, IItem } from 'interfaces'; /** * Vendor bills services. diff --git a/server/src/services/SMSClient/EasySmsClient.ts b/server/src/services/SMSClient/EasySmsClient.ts index 1391268c0..85bfea4b6 100644 --- a/server/src/services/SMSClient/EasySmsClient.ts +++ b/server/src/services/SMSClient/EasySmsClient.ts @@ -1,6 +1,6 @@ import axios from 'axios'; -import SMSClientInterface from '@/services/SMSClient/SMSClientInterfaces'; -import config from '@/../config/config'; +import SMSClientInterface from 'services/SMSClient/SMSClientInterfaces'; +import config from 'config'; export default class EasySMSClient implements SMSClientInterface { clientName: string = 'easysms'; diff --git a/server/src/services/SMSClient/SMSAPI.ts b/server/src/services/SMSClient/SMSAPI.ts index 9a57cc595..6fc1d21a4 100644 --- a/server/src/services/SMSClient/SMSAPI.ts +++ b/server/src/services/SMSClient/SMSAPI.ts @@ -1,4 +1,4 @@ -import SMSClientInterface from '@/services/SMSClient/SMSClientInterface'; +import SMSClientInterface from 'services/SMSClient/SMSClientInterface'; export default class SMSAPI { smsClient: SMSClientInterface; diff --git a/server/src/services/Sales/HasItemsEntries.ts b/server/src/services/Sales/HasItemsEntries.ts index 59c3b338f..2754bfd60 100644 --- a/server/src/services/Sales/HasItemsEntries.ts +++ b/server/src/services/Sales/HasItemsEntries.ts @@ -1,7 +1,7 @@ import { difference, omit } from 'lodash'; import { Service, Inject } from 'typedi'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import { ItemEntry } from '@/models'; +import TenancyService from 'services/Tenancy/TenancyService'; +import { ItemEntry } from 'models'; @Service() export default class HasItemEntries { diff --git a/server/src/services/Sales/JournalPosterService.ts b/server/src/services/Sales/JournalPosterService.ts index 48dbf4954..000bb17a4 100644 --- a/server/src/services/Sales/JournalPosterService.ts +++ b/server/src/services/Sales/JournalPosterService.ts @@ -1,6 +1,6 @@ import { Service, Inject } from 'typedi'; -import JournalPoster from '@/services/Accounting/JournalPoster'; -import TenancyService from '@/services/Tenancy/TenancyService'; +import JournalPoster from 'services/Accounting/JournalPoster'; +import TenancyService from 'services/Tenancy/TenancyService'; @Service() export default class JournalPosterService { diff --git a/server/src/services/Sales/PaymentsReceives.ts b/server/src/services/Sales/PaymentsReceives.ts index b6be2a26b..57d85c3b7 100644 --- a/server/src/services/Sales/PaymentsReceives.ts +++ b/server/src/services/Sales/PaymentsReceives.ts @@ -1,16 +1,16 @@ import { omit, sumBy, chain } from 'lodash'; import moment from 'moment'; import { Service, Inject } from 'typedi'; -import { IPaymentReceiveOTD } from '@/interfaces'; -import AccountsService from '@/services/Accounts/AccountsService'; -import JournalPoster from '@/services/Accounting/JournalPoster'; -import JournalEntry from '@/services/Accounting/JournalEntry'; -import JournalPosterService from '@/services/Sales/JournalPosterService'; -import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries'; -import PaymentReceiveEntryRepository from '@/repositories/PaymentReceiveEntryRepository'; -import CustomerRepository from '@/repositories/CustomerRepository'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import { formatDateFields } from '@/utils'; +import { IPaymentReceiveOTD } from 'interfaces'; +import AccountsService from 'services/Accounts/AccountsService'; +import JournalPoster from 'services/Accounting/JournalPoster'; +import JournalEntry from 'services/Accounting/JournalEntry'; +import JournalPosterService from 'services/Sales/JournalPosterService'; +import ServiceItemsEntries from 'services/Sales/ServiceItemsEntries'; +import PaymentReceiveEntryRepository from 'repositories/PaymentReceiveEntryRepository'; +import CustomerRepository from 'repositories/CustomerRepository'; +import TenancyService from 'services/Tenancy/TenancyService'; +import { formatDateFields } from 'utils'; /** * Payment receive service. diff --git a/server/src/services/Sales/SalesEstimate.ts b/server/src/services/Sales/SalesEstimate.ts index a0649751a..ac00708c1 100644 --- a/server/src/services/Sales/SalesEstimate.ts +++ b/server/src/services/Sales/SalesEstimate.ts @@ -1,8 +1,8 @@ import { omit, difference, sumBy, mixin } from 'lodash'; import { Service, Inject } from 'typedi'; -import HasItemsEntries from '@/services/Sales/HasItemsEntries'; -import { formatDateFields } from '@/utils'; -import TenancyService from '@/services/Tenancy/TenancyService'; +import HasItemsEntries from 'services/Sales/HasItemsEntries'; +import { formatDateFields } from 'utils'; +import TenancyService from 'services/Tenancy/TenancyService'; /** * Sale estimate service. diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts index 0ae82d20f..877b0bf01 100644 --- a/server/src/services/Sales/SalesInvoices.ts +++ b/server/src/services/Sales/SalesInvoices.ts @@ -1,12 +1,12 @@ import { Service, Inject } from 'typedi'; import { omit, sumBy, difference, pick, chain } from 'lodash'; -import { ISaleInvoice, ISaleInvoiceOTD, IItemEntry } from '@/interfaces'; -import JournalPoster from '@/services/Accounting/JournalPoster'; -import HasItemsEntries from '@/services/Sales/HasItemsEntries'; -import InventoryService from '@/services/Inventory/Inventory'; -import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import { formatDateFields } from '@/utils'; +import { ISaleInvoice, ISaleInvoiceOTD, IItemEntry } from 'interfaces'; +import JournalPoster from 'services/Accounting/JournalPoster'; +import HasItemsEntries from 'services/Sales/HasItemsEntries'; +import InventoryService from 'services/Inventory/Inventory'; +import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost'; +import TenancyService from 'services/Tenancy/TenancyService'; +import { formatDateFields } from 'utils'; /** * Sales invoices service diff --git a/server/src/services/Sales/SalesInvoicesCost.ts b/server/src/services/Sales/SalesInvoicesCost.ts index 49b795632..73152122a 100644 --- a/server/src/services/Sales/SalesInvoicesCost.ts +++ b/server/src/services/Sales/SalesInvoicesCost.ts @@ -1,9 +1,9 @@ import { Container, Service, Inject } from 'typedi'; -import JournalPoster from '@/services/Accounting/JournalPoster'; -import JournalEntry from '@/services/Accounting/JournalEntry'; -import InventoryService from '@/services/Inventory/Inventory'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import { ISaleInvoice, IItemEntry } from '@/interfaces'; +import JournalPoster from 'services/Accounting/JournalPoster'; +import JournalEntry from 'services/Accounting/JournalEntry'; +import InventoryService from 'services/Inventory/Inventory'; +import TenancyService from 'services/Tenancy/TenancyService'; +import { ISaleInvoice, IItemEntry } from 'interfaces'; @Service() export default class SaleInvoicesCost { diff --git a/server/src/services/Sales/SalesReceipts.ts b/server/src/services/Sales/SalesReceipts.ts index 13da6e207..df726b1d2 100644 --- a/server/src/services/Sales/SalesReceipts.ts +++ b/server/src/services/Sales/SalesReceipts.ts @@ -1,9 +1,9 @@ import { omit, difference, sumBy } from 'lodash'; import { Service, Inject } from 'typedi'; -import JournalPosterService from '@/services/Sales/JournalPosterService'; -import HasItemEntries from '@/services/Sales/HasItemsEntries'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import { formatDateFields } from '@/utils'; +import JournalPosterService from 'services/Sales/JournalPosterService'; +import HasItemEntries from 'services/Sales/HasItemsEntries'; +import TenancyService from 'services/Tenancy/TenancyService'; +import { formatDateFields } from 'utils'; @Service() export default class SalesReceiptService { diff --git a/server/src/services/SessionModel/SessionQueryBuilder.js b/server/src/services/SessionModel/SessionQueryBuilder.js index 74c642fbe..31ec831d8 100644 --- a/server/src/services/SessionModel/SessionQueryBuilder.js +++ b/server/src/services/SessionModel/SessionQueryBuilder.js @@ -1,4 +1,4 @@ -import SessionModel from '@/services/SessionModel'; +import SessionModel from 'services/SessionModel'; export default class SessionQueryBuilder extends SessionModel.QueryBuilder { /** diff --git a/server/src/services/SessionModel/index.js b/server/src/services/SessionModel/index.js index e99fee588..368345e6e 100644 --- a/server/src/services/SessionModel/index.js +++ b/server/src/services/SessionModel/index.js @@ -1,4 +1,4 @@ -import SessionQueryBuilder from '@/services/SessionModel/SessionQueryBuilder'; +import SessionQueryBuilder from 'services/SessionModel/SessionQueryBuilder'; export default class SessionModel { /** diff --git a/server/src/services/Settings/SettingsStore.ts b/server/src/services/Settings/SettingsStore.ts index 66eb0dda5..0f5889dcb 100644 --- a/server/src/services/Settings/SettingsStore.ts +++ b/server/src/services/Settings/SettingsStore.ts @@ -1,6 +1,6 @@ import Knex from 'knex'; -import MetableStoreDB from '@/lib/Metable/MetableStoreDB'; -import Setting from '@/models/Setting'; +import MetableStoreDB from 'lib/Metable/MetableStoreDB'; +import Setting from 'models/Setting'; export default class SettingsStore extends MetableStoreDB { /** diff --git a/server/src/services/Subscription/SMSMessages.ts b/server/src/services/Subscription/SMSMessages.ts index 5af0a4909..3fa34afe9 100644 --- a/server/src/services/Subscription/SMSMessages.ts +++ b/server/src/services/Subscription/SMSMessages.ts @@ -1,5 +1,5 @@ import { Service, Inject } from 'typedi'; -import SMSClient from '@/services/SMSClient'; +import SMSClient from 'services/SMSClient'; @Service() export default class SubscriptionSMSMessages { diff --git a/server/src/services/Subscription/Subscription.ts b/server/src/services/Subscription/Subscription.ts index 2454d76fd..3bfa31f6e 100644 --- a/server/src/services/Subscription/Subscription.ts +++ b/server/src/services/Subscription/Subscription.ts @@ -1,8 +1,8 @@ import { Inject, Service } from 'typedi'; -import { Tenant, Plan } from '@/system/models'; -import { IPaymentContext } from '@/interfaces'; -import { NotAllowedChangeSubscriptionPlan } from '@/exceptions'; -import { NoPaymentModelWithPricedPlan } from '@/exceptions'; +import { Tenant, Plan } from 'system/models'; +import { IPaymentContext } from 'interfaces'; +import { NotAllowedChangeSubscriptionPlan } from 'exceptions'; +import { NoPaymentModelWithPricedPlan } from 'exceptions'; export default class Subscription { paymentContext: IPaymentContext|null; diff --git a/server/src/services/Subscription/SubscriptionService.ts b/server/src/services/Subscription/SubscriptionService.ts index a300f2506..695a0687c 100644 --- a/server/src/services/Subscription/SubscriptionService.ts +++ b/server/src/services/Subscription/SubscriptionService.ts @@ -1,11 +1,11 @@ import { Service, Inject } from 'typedi'; -import { Plan, Tenant } from '@/system/models'; -import Subscription from '@/services/Subscription/Subscription'; -import LicensePaymentMethod from '@/services/Payment/LicensePaymentMethod'; -import PaymentContext from '@/services/Payment'; -import SubscriptionSMSMessages from '@/services/Subscription/SMSMessages'; -import SubscriptionMailMessages from '@/services/Subscription/MailMessages'; -import { ILicensePaymentModel } from '@/interfaces'; +import { Plan, Tenant } from 'system/models'; +import Subscription from 'services/Subscription/Subscription'; +import LicensePaymentMethod from 'services/Payment/LicensePaymentMethod'; +import PaymentContext from 'services/Payment'; +import SubscriptionSMSMessages from 'services/Subscription/SMSMessages'; +import SubscriptionMailMessages from 'services/Subscription/MailMessages'; +import { ILicensePaymentModel } from 'interfaces'; @Service() export default class SubscriptionService { @@ -18,6 +18,9 @@ export default class SubscriptionService { @Inject('logger') logger: any; + @Inject('repositories') + sysRepositories: any; + /** * Handles the payment process via license code and than subscribe to * the given tenant. @@ -35,8 +38,10 @@ export default class SubscriptionService { this.logger.info('[subscription_via_license] try to subscribe via given license.', { tenantId, paymentModel }); + const { tenantRepository } = this.sysRepositories; + const plan = await Plan.query().findOne('slug', planSlug); - const tenant = await Tenant.query().findById(tenantId); + const tenant = await tenantRepository.getById(tenantId); const paymentViaLicense = new LicensePaymentMethod(); const paymentContext = new PaymentContext(paymentViaLicense); diff --git a/server/src/services/Tenancy/SystemService.ts b/server/src/services/Tenancy/SystemService.ts new file mode 100644 index 000000000..15159d6e5 --- /dev/null +++ b/server/src/services/Tenancy/SystemService.ts @@ -0,0 +1,26 @@ +import Container from "typedi" +import { Service, Inject } from 'typedi'; + +@Service() +export default class HasSystemService implements SystemService{ + + private container(key: string) { + return Container.get(key); + } + + knex() { + return this.container('knex'); + } + + repositories() { + return this.container('repositories'); + } + + cache() { + return this.container('cache'); + } + + dbManager() { + return this.container('dbManager'); + } +} \ No newline at end of file diff --git a/server/src/services/Tenancy/TenancyService.ts b/server/src/services/Tenancy/TenancyService.ts index d4f604057..915ebcf4b 100644 --- a/server/src/services/Tenancy/TenancyService.ts +++ b/server/src/services/Tenancy/TenancyService.ts @@ -1,22 +1,55 @@ -import { Container, Service } from 'typedi'; +import { Container, Service, Inject } from 'typedi'; +import TenantsManagerService from 'services/Tenancy/TenantsManager'; +import tenantModelsLoader from 'loaders/tenantModels'; +import tenantRepositoriesLoader from 'loaders/tenantRepositories'; +import tenantCacheLoader from 'loaders/tenantCache'; @Service() export default class HasTenancyService { + @Inject() + tenantsManager: TenantsManagerService; + /** * Retrieve the given tenant container. - * @param {number} tenantId + * @param {number} tenantId * @return {Container} */ tenantContainer(tenantId: number) { return Container.of(`tenant-${tenantId}`); } + /** + * Singleton tenant service. + * @param {number} tenantId - Tenant id. + * @param {string} key - Service key. + * @param {Function} callback + */ + singletonService(tenantId: number, key: string, callback: Function) { + const container = this.tenantContainer(tenantId); + const Logger = Container.get('logger'); + + const hasServiceInstnace = container.has(key); + + if (!hasServiceInstnace) { + const serviceInstance = callback(); + + container.set(key, serviceInstance); + Logger.info(`[tenant_DI] ${key} injected to tenant container.`, { tenantId, key }); + + return serviceInstance; + } else { + return container.get(key); + } + } + /** * Retrieve knex instance of the given tenant id. * @param {number} tenantId */ knex(tenantId: number) { - return this.tenantContainer(tenantId).get('knex'); + return this.singletonService(tenantId, 'tenantManager', () => { + return this.tenantsManager.getKnexInstance(tenantId); + }); } /** @@ -24,30 +57,39 @@ export default class HasTenancyService { * @param {number} tenantId - The tenant id. */ models(tenantId: number) { - return this.tenantContainer(tenantId).get('models'); + const knexInstance = this.knex(tenantId); + + return this.singletonService(tenantId, 'models', () => { + return tenantModelsLoader(knexInstance); + }); } /** * Retrieve repositories of the given tenant id. - * @param {number} tenantId + * @param {number} tenantId - Tenant id. */ repositories(tenantId: number) { - return this.tenantContainer(tenantId).get('repositories'); + return this.singletonService(tenantId, 'repositories', () => { + return tenantRepositoriesLoader(tenantId); + }) } /** * Retrieve i18n locales methods. - * @param {number} tenantId + * @param {number} tenantId - Tenant id. */ i18n(tenantId: number) { - return this.tenantContainer(tenantId).get('i18n'); + return this.singletonService(tenantId, 'i18n', () => { + }); } /** * Retrieve tenant cache instance. - * @param {number} tenantId - + * @param {number} tenantId - Tenant id. */ cache(tenantId: number) { - return this.tenantContainer(tenantId).get('cache'); + return this.singletonService(tenantId, 'cache', () => { + return tenantCacheLoader(tenantId); + }); } } \ No newline at end of file diff --git a/server/src/services/Tenancy/TenantDBManager.ts b/server/src/services/Tenancy/TenantDBManager.ts new file mode 100644 index 000000000..a0e2d7786 --- /dev/null +++ b/server/src/services/Tenancy/TenantDBManager.ts @@ -0,0 +1,123 @@ +import { Container } from 'typedi'; +import Knex from 'knex'; +import { knexSnakeCaseMappers } from 'objection'; +import config from 'config'; +import { ITenant, ITenantDBManager, ISystemService } from 'interfaces'; +import SystemService from 'services/Tenancy/SystemService'; +import { TenantDBAlreadyExists } from 'exceptions'; +import { tenantKnexConfig, tenantSeedConfig } from 'config/knexConfig'; + +export default class TenantDBManager implements ITenantDBManager{ + static knexCache: { [key: string]: Knex; } = {}; + + // System database manager. + dbManager: any; + + // System knex instance. + sysKnex: Knex; + + /** + * Constructor method. + * @param {ITenant} tenant + */ + constructor() { + const systemService = Container.get(SystemService); + + this.dbManager = systemService.dbManager(); + this.sysKnex = systemService.knex(); + } + + /** + * Retrieve the tenant database name. + * @return {string} + */ + private getDatabaseName(tenant: ITenant) { + return `${config.tenant.db_name_prefix}${tenant.organizationId}`; + } + + /** + * Detarmines the tenant database weather exists. + * @return {Promise} + */ + public async databaseExists(tenant: ITenant) { + const databaseName = this.getDatabaseName(tenant); + const results = await this.sysKnex + .raw('SELECT * FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = "?"', databaseName); + + return results[0].length > 0; + } + + /** + * Creates a tenant database. + * @throws {TenantAlreadyInitialized} + * @return {Promise} + */ + public async createDatabase(tenant: ITenant): Promise { + await this.throwErrorIfTenantDBExists(tenant); + + const databaseName = this.getDatabaseName(tenant); + await this.dbManager.createDb(databaseName); + } + + /** + * Migrate tenant database schema to the latest version. + * @return {Promise} + */ + public async migrate(tenant: ITenant): Promise { + const knex = this.setupKnexInstance(tenant); + + await knex.migrate.latest(); + } + + /** + * Seeds initial data to the tenant database. + * @return {Promise} + */ + public async seed(tenant: ITenant): Promise { + const knex = this.setupKnexInstance(tenant); + + await knex.migrate.latest({ + ...tenantSeedConfig(tenant), + disableMigrationsListValidation: true, + }); + } + + /** + * Retrieve the knex instance of tenant. + * @return {Knex} + */ + public setupKnexInstance(tenant: ITenant) { + const key: string = `${tenant.id}`; + let knexInstance = TenantDBManager.knexCache[key]; + + if (!knexInstance) { + knexInstance = Knex({ + ...tenantKnexConfig(tenant), + ...knexSnakeCaseMappers({ upperCase: true }), + }); + TenantDBManager.knexCache[key] = knexInstance; + } + return knexInstance; + } + + public getKnexInstance(tenantId: number) { + const key: string = `${tenantId}`; + let knexInstance = TenantDBManager.knexCache[key]; + + if (!knexInstance) { + throw new Error('Knex instance is not initialized yut.'); + } + return knexInstance; + } + + /** + * Throws error if the tenant database already exists. + * @return {Promise} + */ + async throwErrorIfTenantDBExists(tenant: ITenant) { + const isExists = await this.databaseExists(tenant); + if (isExists) { + throw new TenantDBAlreadyExists(); + } + } +} \ No newline at end of file diff --git a/server/src/services/Tenancy/TenantService.ts b/server/src/services/Tenancy/TenantService.ts new file mode 100644 index 000000000..e69de29bb diff --git a/server/src/services/Tenancy/TenantsManager.ts b/server/src/services/Tenancy/TenantsManager.ts new file mode 100644 index 000000000..d83ab7638 --- /dev/null +++ b/server/src/services/Tenancy/TenantsManager.ts @@ -0,0 +1,158 @@ +import { Container, Inject, Service } from 'typedi'; +import { ServiceError } from 'exceptions'; +import { + ITenantManager, + ITenant, + ITenantDBManager, +} from 'interfaces'; +import { + EventDispatcherInterface, + EventDispatcher, +} from 'decorators/eventDispatcher'; +import { TenantAlreadyInitialized, TenantAlreadySeeded, TenantDatabaseNotBuilt } from 'exceptions'; +import TenantDBManager from 'services/Tenancy/TenantDBManager'; +import events from 'subscribers/events'; + +const ERRORS = { + TENANT_ALREADY_CREATED: 'TENANT_ALREADY_CREATED', + TENANT_NOT_EXISTS: 'TENANT_NOT_EXISTS' +}; + +// Tenants manager service. +@Service() +export default class TenantsManagerService implements ITenantManager{ + static instances: { [key: number]: ITenantManager } = {}; + + @EventDispatcher() + private eventDispatcher: EventDispatcherInterface; + + @Inject('repositories') + private sysRepositories: any; + + private tenantDBManager: ITenantDBManager; + + /** + * Constructor method. + */ + constructor() { + this.tenantDBManager = new TenantDBManager(); + } + + /** + * Creates a new teant with unique organization id. + * @param {ITenant} tenant + * @return {Promise} + */ + public async createTenant(): Promise { + const { tenantRepository } = this.sysRepositories; + const tenant = await tenantRepository.newTenantWithUniqueOrgId(); + + return tenant; + } + + /** + * Creates a new tenant database. + * @param {ITenant} tenant - + * @return {Promise} + */ + public async createDatabase(tenant: ITenant): Promise { + this.throwErrorIfTenantAlreadyInitialized(tenant); + + await this.tenantDBManager.createDatabase(tenant); + + this.eventDispatcher.dispatch(events.tenantManager.databaseCreated); + } + + /** + * Detarmines the tenant has database. + * @param {ITenant} tenant + * @returns {Promise} + */ + public async hasDatabase(tenant: ITenant): Promise { + return this.tenantDBManager.databaseExists(tenant); + } + + /** + * Migrates the tenant database. + * @param {ITenant} tenant + * @return {Promise} + */ + public async migrateTenant(tenant: ITenant) { + this.throwErrorIfTenantAlreadyInitialized(tenant); + + const { tenantRepository } = this.sysRepositories; + + await this.tenantDBManager.migrate(tenant); + await tenantRepository.markAsInitialized(tenant.id); + + this.eventDispatcher.dispatch(events.tenantManager.tenantMigrated, { tenant }); + } + + /** + * Seeds the tenant database. + * @param {ITenant} tenant + * @return {Promise} + */ + public async seedTenant(tenant: ITenant) { + this.throwErrorIfTenantNotBuilt(tenant); + this.throwErrorIfTenantAlreadySeeded(tenant); + + const { tenantRepository } = this.sysRepositories; + + // Seed the tenant database. + await this.tenantDBManager.seed(tenant); + + // Mark the tenant as seeded in specific date. + await tenantRepository.markAsSeeded(tenant.id); + + this.eventDispatcher.dispatch(events.tenantManager.tenantSeeded); + } + + /** + * Initialize knex instance or retrieve the instance of cache map. + * @param {ITenant} tenant + * @returns {Knex} + */ + public setupKnexInstance(tenant: ITenant) { + return this.tenantDBManager.setupKnexInstance(tenant); + } + + /** + * Retrieve tenant knex instance or throw error in case was not initialized. + * @param {number} tenantId + * @returns {Knex} + */ + public getKnexInstance(tenantId: number) { + return this.tenantDBManager.getKnexInstance(tenantId); + } + + /** + * Throws error if the tenant already seeded. + * @throws {TenantAlreadySeeded} + */ + private throwErrorIfTenantAlreadySeeded(tenant: ITenant) { + if (tenant.seededAt) { + throw new TenantAlreadySeeded(); + } + } + + /** + * Throws error if the tenant database is not built yut. + * @param tenant + */ + private throwErrorIfTenantNotBuilt(tenant: ITenant) { + if (!tenant.initializedAt) { + throw new TenantDatabaseNotBuilt(); + } + } + + /** + * Throws error if the tenant already migrated. + * @throws {TenantAlreadyInitialized} + */ + private throwErrorIfTenantAlreadyInitialized(tenant: ITenant) { + if (tenant.initializedAt) { + throw new TenantAlreadyInitialized(); + } + } +} diff --git a/server/src/services/Users/UsersService.ts b/server/src/services/Users/UsersService.ts index c7010f5c0..953ad00c2 100644 --- a/server/src/services/Users/UsersService.ts +++ b/server/src/services/Users/UsersService.ts @@ -1,8 +1,9 @@ import { Inject, Service } from "typedi"; -import TenancyService from '@/services/Tenancy/TenancyService'; -import { SystemUser } from "@/system/models"; -import { ServiceError, ServiceErrors } from "@/exceptions"; -import { ISystemUser, ISystemUserDTO } from "@/interfaces"; +import TenancyService from 'services/Tenancy/TenancyService'; +import { SystemUser } from "system/models"; +import { ServiceError, ServiceErrors } from "exceptions"; +import { ISystemUser, ISystemUserDTO } from "interfaces"; +import systemRepositories from "loaders/systemRepositories"; @Service() export default class UsersService { @@ -12,6 +13,9 @@ export default class UsersService { @Inject('logger') logger: any; + @Inject('repositories') + repositories: any; + /** * Creates a new user. * @param {number} tenantId @@ -20,26 +24,17 @@ export default class UsersService { * @return {Promise} */ async editUser(tenantId: number, userId: number, userDTO: ISystemUserDTO): Promise { - const foundUsers = await SystemUser.query() - .whereNot('id', userId) - .andWhere((query) => { - query.where('email', userDTO.email); - query.orWhere('phone_number', userDTO.phoneNumber); - }) - .where('tenant_id', tenantId); + const { systemUserRepository } = this.repositories; - const sameUserEmail = foundUsers - .some((u: ISystemUser) => u.email === userDTO.email); - - const samePhoneNumber = foundUsers - .some((u: ISystemUser) => u.phoneNumber === userDTO.phone_number); + const isEmailExists = await systemUserRepository.isEmailExists(userDTO.email, userId); + const isPhoneNumberExists = await systemUserRepository.isPhoneNumberExists(userDTO.phoneNumber, userId); const serviceErrors: ServiceError[] = []; - if (sameUserEmail) { + if (isEmailExists) { serviceErrors.push(new ServiceError('email_already_exists')); } - if (samePhoneNumber) { + if (isPhoneNumberExists) { serviceErrors.push(new ServiceError('phone_number_already_exist')); } if (serviceErrors.length > 0) { @@ -47,9 +42,8 @@ export default class UsersService { } const updateSystemUser = await SystemUser.query() .where('id', userId) - .update({ - ...userDTO, - }); + .update({ ...userDTO }); + return updateSystemUser; } @@ -60,9 +54,9 @@ export default class UsersService { * @returns {ISystemUser} */ async getUserOrThrowError(tenantId: number, userId: number): void { - const user = await SystemUser.query().findOne({ - id: userId, tenant_id: tenantId, - }); + const { systemUserRepository } = this.repositories; + const user = await systemUserRepository.getByIdAndTenant(userId, tenantId); + if (!user) { this.logger.info('[users] the given user not found.', { tenantId, userId }); throw new ServiceError('user_not_found'); @@ -76,11 +70,11 @@ export default class UsersService { * @param {number} userId */ async deleteUser(tenantId: number, userId: number): Promise { + const { systemUserRepository } = this.repositories; await this.getUserOrThrowError(tenantId, userId); this.logger.info('[users] trying to delete the given user.', { tenantId, userId }); - await SystemUser.query().where('tenant_id', tenantId) - .where('id', userId).delete(); + await systemUserRepository.deleteById(userId); this.logger.info('[users] the given user deleted successfully.', { tenantId, userId }); } @@ -91,12 +85,14 @@ export default class UsersService { * @param {number} userId */ async activateUser(tenantId: number, userId: number): Promise { + const { systemUserRepository } = this.repositories; + const user = await this.getUserOrThrowError(tenantId, userId); this.throwErrorIfUserActive(user); - await SystemUser.query().findById(userId).update({ active: true }); + await systemUserRepository.activateUser(userId); } - + /** * Inactivate the given user id. * @param {number} tenantId @@ -104,10 +100,11 @@ export default class UsersService { * @return {Promise} */ async inactivateUser(tenantId: number, userId: number): Promise { + const { systemUserRepository } = this.repositories; const user = await this.getUserOrThrowError(tenantId, userId); this.throwErrorIfUserInactive(user); - await SystemUser.query().findById(userId).update({ active: false }); + await systemUserRepository.inactivateById(userId); } /** diff --git a/server/src/subscribers/authentication.ts b/server/src/subscribers/authentication.ts index 26b0d468e..8402b7036 100644 --- a/server/src/subscribers/authentication.ts +++ b/server/src/subscribers/authentication.ts @@ -1,7 +1,7 @@ import { Container } from 'typedi'; import { pick } from 'lodash'; import { EventSubscriber, On } from 'event-dispatch'; -import events from '@/subscribers/events'; +import events from 'subscribers/events'; @EventSubscriber() export class AuthenticationSubscriber { diff --git a/server/src/subscribers/events.ts b/server/src/subscribers/events.ts index baf05327b..2b82f1f05 100644 --- a/server/src/subscribers/events.ts +++ b/server/src/subscribers/events.ts @@ -16,5 +16,12 @@ export default { organization: { build: 'onOrganizationBuild', + seeded: 'onOrganizationSeeded', + }, + + tenantManager: { + databaseCreated: 'onDatabaseCreated', + tenantMigrated: 'onTenantMigrated', + tenantSeeded: 'onTenantSeeded', } } diff --git a/server/src/subscribers/inviteUser.ts b/server/src/subscribers/inviteUser.ts index a4be953db..1abd7d2f6 100644 --- a/server/src/subscribers/inviteUser.ts +++ b/server/src/subscribers/inviteUser.ts @@ -1,6 +1,6 @@ import { Container } from 'typedi'; import { EventSubscriber, On } from 'event-dispatch'; -import events from '@/subscribers/events'; +import events from 'subscribers/events'; @EventSubscriber() export class InviteUserSubscriber { diff --git a/server/src/subscribers/organization.ts b/server/src/subscribers/organization.ts index 40427bcb3..8e54d2cab 100644 --- a/server/src/subscribers/organization.ts +++ b/server/src/subscribers/organization.ts @@ -1,6 +1,6 @@ import { Container } from 'typedi'; import { On, EventSubscriber } from "event-dispatch"; -import events from '@/subscribers/events'; +import events from 'subscribers/events'; @EventSubscriber() export class OrganizationSubscriber { diff --git a/server/src/subscribers/tenantManager.ts b/server/src/subscribers/tenantManager.ts new file mode 100644 index 000000000..e830aced1 --- /dev/null +++ b/server/src/subscribers/tenantManager.ts @@ -0,0 +1,22 @@ +import { Container } from 'typedi'; +import { On, EventSubscriber } from "event-dispatch"; +import events from 'subscribers/events'; + +@EventSubscriber() +export default class TenantManagerSubscriber { + + @On(events.tenantManager.databaseCreated) + onDatabaseCreated() { + + } + + @On(events.tenantManager.tenantMigrated) + onTenantMigrated() { + + } + + @On(events.tenantManager.tenantSeeded) + onTenantSeeded() { + + } +} \ No newline at end of file diff --git a/server/src/system/TenantsManager.ts b/server/src/system/TenantsManager.ts deleted file mode 100644 index cb83f90d6..000000000 --- a/server/src/system/TenantsManager.ts +++ /dev/null @@ -1,138 +0,0 @@ -import Knex from 'knex'; -import { knexSnakeCaseMappers } from 'objection'; -import { Service } from 'typedi'; -import Tenant from '@/system/models/Tenant'; -import config from '@/../config/config'; -import TenantModel from '@/models/TenantModel'; -import uniqid from 'uniqid'; -import dbManager from '@/database/manager'; -import { omit } from 'lodash'; - -import SystemUser from '@/system/models/SystemUser'; -import TenantUser from '@/models/TenantUser'; -// import TenantModel from '@/models/TenantModel'; - -// const TenantWebsite: { -// tenantDb: Knex, -// tenantId: Number, -// tenantOrganizationId: String, -// } - -@Service() -export default class TenantsManager { - - constructor() { - this.knexCache = new Map(); - } - - async getTenant(organizationId) { - const tenant = await Tenant.query() - .where('organization_id', organizationId).first(); - - return tenant; - } - - /** - * Creates a new tenant database. - * @param {Integer} uniqId - * @return {TenantWebsite} - */ - async createTenant(uniqId) { - const organizationId = uniqId || uniqid(); - const tenantOrganization = await Tenant.query().insert({ - organization_id: organizationId, - }); - - const tenantDbName = `bigcapital_tenant_${organizationId}`; - await dbManager.createDb(tenantDbName); - - const tenantDb = TenantsManager.knexInstance(organizationId); - await tenantDb.migrate.latest(); - - return { - tenantDb, - tenantId: tenantOrganization.id, - organizationId, - }; - } - - /** - * Drop tenant database of the given tenant website. - * @param {TenantWebsite} tenantWebsite - */ - async dropTenant(tenantWebsite) { - const tenantDbName = `bigcapital_tenant_${tenantWebsite.organizationId}`; - await dbManager.dropDb(tenantDbName); - - await SystemUser.query() - .where('tenant_id', tenantWebsite.tenantId); - } - - /** - * Creates a user that associate to the given tenant. - */ - async createTenantUser(tenantWebsite, user) { - const userInsert = { ...user }; - - const systemUser = await SystemUser.query().insert({ - ...user, - tenant_id: tenantWebsite.tenantId, - }); - TenantModel.knexBinded = tenantWebsite.tenantDb; - - const tenantUser = await TenantUser.bindKnex(tenantWebsite.tenantDb) - .query() - .insert({ - ...omit(userInsert, ['password']), - }); - return { - ...tenantUser, - ...systemUser - }; - } - - /** - * Retrieve all tenants metadata from system storage. - */ - getAllTenants() { - return Tenant.query(); - } - - /** - * Retrieve the given organization id knex configuration. - * @param {String} organizationId - - */ - getTenantKnexConfig(organizationId) { - 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 }, - }; - } - - knexInstance(organizationId) { - const knexCache = new Map(); - let knex = knexCache.get(organizationId); - - if (!knex) { - knex = Knex({ - ...this.getTenantKnexConfig(organizationId), - ...knexSnakeCaseMappers({ upperCase: true }), - }); - knexCache.set(organizationId, knex); - } - return knex; - } -} \ No newline at end of file diff --git a/server/src/system/migrations/20200420134631_create_tenants_table.js b/server/src/system/migrations/20200420134631_create_tenants_table.js index 51bfa3bf2..1d73550fb 100644 --- a/server/src/system/migrations/20200420134631_create_tenants_table.js +++ b/server/src/system/migrations/20200420134631_create_tenants_table.js @@ -3,7 +3,10 @@ exports.up = function(knex) { return knex.schema.createTable('tenants', (table) => { table.bigIncrements(); table.string('organization_id'); - table.boolean('initialized').defaultTo(false); + + table.dateTime('under_maintenance_since').nullable(); + table.dateTime('initialized_at').nullable(); + table.dateTime('seeded_at').nullable(); table.timestamps(); }); }; diff --git a/server/src/system/models/Invite.js b/server/src/system/models/Invite.js index daaa91ab7..bb226f88b 100644 --- a/server/src/system/models/Invite.js +++ b/server/src/system/models/Invite.js @@ -1,4 +1,4 @@ -import SystemModel from '@/system/models/SystemModel'; +import SystemModel from 'system/models/SystemModel'; export default class UserInvite extends SystemModel { /** diff --git a/server/src/system/models/PasswordReset.js b/server/src/system/models/PasswordReset.js index b72b0aeef..12aa1e54d 100644 --- a/server/src/system/models/PasswordReset.js +++ b/server/src/system/models/PasswordReset.js @@ -1,4 +1,4 @@ -import SystemModel from '@/system/models/SystemModel'; +import SystemModel from 'system/models/SystemModel'; export default class PasswordResets extends SystemModel { /** diff --git a/server/src/system/models/Subscriptions/License.js b/server/src/system/models/Subscriptions/License.js index d2492afe7..3bbc76e31 100644 --- a/server/src/system/models/Subscriptions/License.js +++ b/server/src/system/models/Subscriptions/License.js @@ -1,7 +1,7 @@ import { Model, mixin } from 'objection'; import moment from 'moment'; -import SystemModel from '@/system/models/SystemModel'; -import { ILicensesFilter } from '@/interfaces'; +import SystemModel from 'system/models/SystemModel'; +import { ILicensesFilter } from 'interfaces'; export default class License extends SystemModel { /** @@ -61,7 +61,7 @@ export default class License extends SystemModel { * Relationship mapping. */ static get relationMappings() { - const Plan = require('@/system/models/Subscriptions/Plan'); + const Plan = require('system/models/Subscriptions/Plan'); return { plan: { diff --git a/server/src/system/models/Subscriptions/Plan.js b/server/src/system/models/Subscriptions/Plan.js index 5842c1408..20cfd046c 100644 --- a/server/src/system/models/Subscriptions/Plan.js +++ b/server/src/system/models/Subscriptions/Plan.js @@ -1,5 +1,5 @@ import { Model, mixin } from 'objection'; -import SystemModel from '@/system/models/SystemModel'; +import SystemModel from 'system/models/SystemModel'; import { PlanSubscription } from '..'; export default class Plan extends mixin(SystemModel) { @@ -39,7 +39,7 @@ export default class Plan extends mixin(SystemModel) { * Relationship mapping. */ static get relationMappings() { - const PlanFeature = require('@/system/models/Subscriptions/PlanFeature'); + const PlanFeature = require('system/models/Subscriptions/PlanFeature'); return { /** diff --git a/server/src/system/models/Subscriptions/PlanFeature.js b/server/src/system/models/Subscriptions/PlanFeature.js index 0fe03bcbc..75dc6d29b 100644 --- a/server/src/system/models/Subscriptions/PlanFeature.js +++ b/server/src/system/models/Subscriptions/PlanFeature.js @@ -1,5 +1,5 @@ import { Model, mixin } from 'objection'; -import SystemModel from '@/system/models/SystemModel'; +import SystemModel from 'system/models/SystemModel'; export default class PlanFeature extends mixin(SystemModel) { /** @@ -20,7 +20,7 @@ export default class PlanFeature extends mixin(SystemModel) { * Relationship mapping. */ static get relationMappings() { - const Plan = require('@/system/models/Subscriptions/Plan'); + const Plan = require('system/models/Subscriptions/Plan'); return { plan: { diff --git a/server/src/system/models/Subscriptions/PlanSubscription.js b/server/src/system/models/Subscriptions/PlanSubscription.js index 638826e74..dbbbaa8ce 100644 --- a/server/src/system/models/Subscriptions/PlanSubscription.js +++ b/server/src/system/models/Subscriptions/PlanSubscription.js @@ -1,7 +1,7 @@ import { Model, mixin } from 'objection'; -import SystemModel from '@/system/models/SystemModel'; +import SystemModel from 'system/models/SystemModel'; import moment from 'moment'; -import SubscriptionPeriod from '@/services/Subscription/SubscriptionPeriod'; +import SubscriptionPeriod from 'services/Subscription/SubscriptionPeriod'; export default class PlanSubscription extends mixin(SystemModel) { /** @@ -67,8 +67,8 @@ export default class PlanSubscription extends mixin(SystemModel) { * Relations mappings. */ static get relationMappings() { - const Tenant = require('@/system/Models/Tenant'); - const Plan = require('@/system/Models/Subscriptions/Plan'); + const Tenant = require('system/Models/Tenant'); + const Plan = require('system/Models/Subscriptions/Plan'); return { /** diff --git a/server/src/system/models/SystemModel.js b/server/src/system/models/SystemModel.js index 35b17d61f..5e599b6be 100644 --- a/server/src/system/models/SystemModel.js +++ b/server/src/system/models/SystemModel.js @@ -1,4 +1,4 @@ -import BaseModel from '@/models/Model'; +import BaseModel from 'models/Model'; export default class SystemModel extends BaseModel{ diff --git a/server/src/system/models/SystemUser.js b/server/src/system/models/SystemUser.js index d6f0c5cff..a5e1b3229 100644 --- a/server/src/system/models/SystemUser.js +++ b/server/src/system/models/SystemUser.js @@ -1,7 +1,7 @@ import { Model, mixin } from 'objection'; import bcrypt from 'bcryptjs'; import SoftDelete from 'objection-soft-delete'; -import SystemModel from '@/system/models/SystemModel'; +import SystemModel from 'system/models/SystemModel'; import moment from 'moment'; export default class SystemUser extends mixin(SystemModel, [SoftDelete({ @@ -27,7 +27,7 @@ export default class SystemUser extends mixin(SystemModel, [SoftDelete({ * Relationship mapping. */ static get relationMappings() { - const Tenant = require('@/system/models/Tenant'); + const Tenant = require('system/models/Tenant'); return { /** diff --git a/server/src/system/models/Tenant.js b/server/src/system/models/Tenant.js index 0d17eac63..dfc738387 100644 --- a/server/src/system/models/Tenant.js +++ b/server/src/system/models/Tenant.js @@ -1,6 +1,6 @@ -import BaseModel from '@/models/Model'; +import BaseModel from 'models/Model'; import { Model } from 'objection'; -import SubscriptionPeriod from '@/services/Subscription/SubscriptionPeriod'; +import SubscriptionPeriod from 'services/Subscription/SubscriptionPeriod'; export default class Tenant extends BaseModel { /** diff --git a/server/src/system/repositories/SubscriptionRepository.ts b/server/src/system/repositories/SubscriptionRepository.ts new file mode 100644 index 000000000..6be38c802 --- /dev/null +++ b/server/src/system/repositories/SubscriptionRepository.ts @@ -0,0 +1,21 @@ +import { Service, Inject } from 'typedi'; +import SystemRepository from "system/repositories/SystemRepository"; +import { PlanSubscription } from 'system/models' + +@Service() +export default class SubscriptionRepository extends SystemRepository{ + @Inject('cache') + cache: any; + + /** + * Retrieve subscription from a given slug in specific tenant. + * @param {string} slug + * @param {number] tenantId + */ + getBySlugInTenant(slug: string, tenantId: number) { + const key = `subscription.slug.${slug}.tenant.${tenantId}`; + return this.cache.get(key, () => { + return PlanSubscription.query().findOne('slug', slug).where('tenant_id', tenantId); + }); + } +} \ No newline at end of file diff --git a/server/src/system/repositories/SystemRepository.ts b/server/src/system/repositories/SystemRepository.ts new file mode 100644 index 000000000..843157471 --- /dev/null +++ b/server/src/system/repositories/SystemRepository.ts @@ -0,0 +1,5 @@ + + +export default class SystemRepository { + +} \ No newline at end of file diff --git a/server/src/system/repositories/SystemUserRepository.ts b/server/src/system/repositories/SystemUserRepository.ts new file mode 100644 index 000000000..5e90f86ad --- /dev/null +++ b/server/src/system/repositories/SystemUserRepository.ts @@ -0,0 +1,137 @@ +import { Service, Inject } from 'typedi'; +import moment from 'moment'; +import SystemRepository from "system/repositories/SystemRepository"; +import { SystemUser } from "system/models"; +import { ISystemUser } from 'interfaces'; + +@Service() +export default class SystemUserRepository extends SystemRepository { + @Inject('cache') + cache: any; + + /** + * Patches the last login date to the given system user. + * @param {number} userId + */ + async patchLastLoginAt(userId: number) { + const user = await SystemUser.query().patchAndFetchById(userId, { + last_login_at: moment().toMySqlDateTime() + }); + this.flushUserCache(user); + return user; + } + + /** + * Finds system user by crediential. + * @param {string} crediential - Phone number or email. + * @return {ISystemUser} + */ + findByCrediential(crediential: string) { + return SystemUser.query().whereNotDeleted() + .findOne('email', crediential) + .orWhere('phone_number', crediential); + } + + /** + * Retrieve system user details of the given id. + * @param {number} userId + */ + getById(userId: number) { + return this.cache.get(`systemUser.id.${userId}`, () => { + return SystemUser.query().whereNotDeleted().findById(userId); + }); + } + + /** + * Retrieve user by id and tenant id. + * @param {number} userId + * @param {number} tenantId + */ + getByIdAndTenant(userId: number, tenantId: number) { + return this.cache.get(`systemUser.id.${userId}.tenant.${tenantId}`, () => { + return SystemUser.query().whereNotDeleted() + .findOne({ id: userId, tenant_id: tenantId }); + }); + } + + /** + * Retrieve system user details by the given email. + * @param {string} email + */ + getByEmail(email: string) { + return this.cache.get(`systemUser.email.${email}`, () => { + return SystemUser.query().whereNotDeleted().findOne('email', email); + }); + } + + /** + * Retrieve user by phone number. + * @param {string} phoneNumber + */ + getByPhoneNumber(phoneNumber: string) { + return this.cache.get(`systemUser.phoneNumber.${phoneNumber}`, () => { + return SystemUser.query().whereNotDeleted().findOne('phoneNumber', phoneNumber); + }); + } + + /** + * Edits details. + * @param {number} userId + * @param {number} user + */ + edit(userId: number, userInput: ISystemUser) { + const user = SystemUser.query().patchAndFetchById(userId, { ...userInput }); + this.flushUserCache(user); + return user; + } + + /** + * Creates a new user. + * @param {IUser} userInput + */ + create(userInput: ISystemUser) { + return SystemUser.query().insert({ ...userInput }); + } + + /** + * Deletes user by the given id. + * @param {number} userId + */ + async deleteById(userId: number) { + const user = this.getById(userId); + await SystemUser.query().where('id', userId).delete(); + this.flushUserCache(user); + } + + /** + * Activate user by the given id. + * @param {number} userId + */ + async activateById(userId: number) { + const user = await SystemUser.query().patchAndFetchById(userId, { active: 1 }); + this.flushUserCache(user); + return user; + } + + /** + * Inactivate user by the given id. + * @param {number} userId + */ + async inactivateById(userId: number) { + const user = await SystemUser.query().patchAndFetchById(userId, { active: 0 }); + this.flushUserCache(user); + return user; + } + + /** + * Flush user cache. + * @param {IUser} user + */ + flushUserCache(user: ISystemUser) { + this.cache.del(`systemUser.phoneNumber.${user.phoneNumber}`); + this.cache.del(`systemUser.email.${user.email}`); + + this.cache.del(`systemUser.id.${user.id}`); + this.cache.del(`systemUser.id.${user.id}.tenant.${user.tenantId}`); + } +} \ No newline at end of file diff --git a/server/src/system/repositories/TenantRepository.ts b/server/src/system/repositories/TenantRepository.ts new file mode 100644 index 000000000..23de76a6b --- /dev/null +++ b/server/src/system/repositories/TenantRepository.ts @@ -0,0 +1,73 @@ +import { Inject } from 'typedi'; +import moment from "moment"; +import { Tenant } from 'system/models'; +import SystemRepository from "./SystemRepository"; +import { ITenant } from 'interfaces'; +import uniqid from 'uniqid'; + +export default class TenantRepository extends SystemRepository { + @Inject('cache') + cache: any; + + /** + * Flush the given tenant stored cache. + * @param {ITenant} tenant + */ + flushTenantCache(tenant: ITenant) { + this.cache.del(`tenant.org.${tenant.organizationId}`); + this.cache.del(`tenant.id.${tenant.id}`); + } + + /** + * Creates a new tenant with random organization id. + * @return {ITenant} + */ + newTenantWithUniqueOrgId(uniqId?: string): Promise{ + const organizationId = uniqid() || uniqId; + return Tenant.query().insert({ organizationId }); + } + + /** + * Mark as seeded. + * @param {number} tenantId + */ + async markAsSeeded(tenantId: number) { + const tenant = await Tenant.query() + .patchAndFetchById(tenantId, { + seeded_at: moment().toMySqlDateTime(), + }); + this.flushTenantCache(tenant); + } + + /** + * Mark the the given organization as initialized. + * @param {string} organizationId + */ + async markAsInitialized(tenantId: number) { + const tenant = await Tenant.query() + .patchAndFetchById(tenantId, { + initialized_at: moment().toMySqlDateTime(), + }); + this.flushTenantCache(tenant); + } + + /** + * Retrieve tenant details by the given organization id. + * @param {string} organizationId + */ + getByOrgId(organizationId: string) { + return this.cache.get(`tenant.org.${organizationId}`, () => { + return Tenant.query().findOne('organization_id', organizationId); + }); + } + + /** + * Retrieve tenant details by the given tenant id. + * @param {string} tenantId + */ + getById(tenantId: number) { + return this.cache.get(`tenant.id.${tenantId}`, () => { + return Tenant.query().findById(tenantId); + }); + } +} \ No newline at end of file diff --git a/server/src/system/repositories/index.ts b/server/src/system/repositories/index.ts new file mode 100644 index 000000000..cf3f6bdd2 --- /dev/null +++ b/server/src/system/repositories/index.ts @@ -0,0 +1,9 @@ +import SystemUserRepository from 'system/repositories/SystemUserRepository'; +import SubscriptionRepository from 'system/repositories/SubscriptionRepository'; +import TenantRepository from 'system/repositories/TenantRepository'; + +export { + SystemUserRepository, + SubscriptionRepository, + TenantRepository, +}; \ No newline at end of file diff --git a/server/tests/collection/NestedSet.test.js b/server/tests/collection/NestedSet.test.js index 7246259f8..3bab1f766 100644 --- a/server/tests/collection/NestedSet.test.js +++ b/server/tests/collection/NestedSet.test.js @@ -1,5 +1,5 @@ import { expect } from '~/testInit'; -import NestedSet from '@/collection/NestedSet'; +import NestedSet from 'collection/NestedSet'; describe('NestedSet', () => { describe('linkChildren()', () => { diff --git a/server/tests/dbInit.js b/server/tests/dbInit.js index 8da9bb9c9..3ae7a7142 100644 --- a/server/tests/dbInit.js +++ b/server/tests/dbInit.js @@ -8,7 +8,7 @@ import { systemFactory, dropTenant, } from '~/testInit'; -import CacheService from '@/services/Cache'; +import CacheService from 'services/Cache'; let tenantWebsite; let tenantFactory; diff --git a/server/tests/lib/CachableModel.test.js b/server/tests/lib/CachableModel.test.js index eff891505..d81541cf1 100644 --- a/server/tests/lib/CachableModel.test.js +++ b/server/tests/lib/CachableModel.test.js @@ -2,7 +2,7 @@ import { request, expect, } from '~/testInit'; -import Account from '@/models/Account'; +import Account from 'models/Account'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/lib/MetableStore.test.ts b/server/tests/lib/MetableStore.test.ts index a2be0bdca..2dade1368 100644 --- a/server/tests/lib/MetableStore.test.ts +++ b/server/tests/lib/MetableStore.test.ts @@ -1,5 +1,5 @@ import { expect } from '~/testInit'; -import MetableStore from '@/lib/MetableStore'; +import MetableStore from 'lib/MetableStore'; describe('MetableStore()', () => { diff --git a/server/tests/models/Account.test.js b/server/tests/models/Account.test.js index 08b6e32aa..47f1db188 100644 --- a/server/tests/models/Account.test.js +++ b/server/tests/models/Account.test.js @@ -1,13 +1,13 @@ import { expect, } from '~/testInit'; -import Account from '@/models/Account'; -import AccountType from '@/models/AccountType'; +import Account from 'models/Account'; +import AccountType from 'models/AccountType'; import { tenantFactory, tenantWebsite } from '~/dbInit'; -import DependencyGraph from '@/lib/DependencyGraph'; +import DependencyGraph from 'lib/DependencyGraph'; describe('Model: Account', () => { it('Should account model belongs to the associated account type model.', async () => { diff --git a/server/tests/models/AccountType.test.js b/server/tests/models/AccountType.test.js index e1bbab474..51bd5f5d7 100644 --- a/server/tests/models/AccountType.test.js +++ b/server/tests/models/AccountType.test.js @@ -1,6 +1,6 @@ import { create, expect } from '~/testInit'; -import '@/models/Account'; -import AccountType from '@/models/AccountType'; +import 'models/Account'; +import AccountType from 'models/AccountType'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/models/Expense.test.js b/server/tests/models/Expense.test.js index 1adec914f..ed5348a46 100644 --- a/server/tests/models/Expense.test.js +++ b/server/tests/models/Expense.test.js @@ -1,6 +1,6 @@ import { create, expect } from '~/testInit'; -import Expense from '@/models/Expense'; -import ExpenseCategory from '@/models/ExpenseCategory'; +import Expense from 'models/Expense'; +import ExpenseCategory from 'models/ExpenseCategory'; import { tenantFactory, tenantWebsite diff --git a/server/tests/models/Item.test.js b/server/tests/models/Item.test.js index 0b0713cf8..8ce26771c 100644 --- a/server/tests/models/Item.test.js +++ b/server/tests/models/Item.test.js @@ -1,7 +1,7 @@ import { create, expect } from '~/testInit'; -import Item from '@/models/Item'; +import Item from 'models/Item'; // eslint-disable-next-line no-unused-vars -import itemCategory from '@/models/ItemCategory'; +import itemCategory from 'models/ItemCategory'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/models/ItemCategories.test.js b/server/tests/models/ItemCategories.test.js index 3a71a2eed..cd339d70c 100644 --- a/server/tests/models/ItemCategories.test.js +++ b/server/tests/models/ItemCategories.test.js @@ -1,6 +1,6 @@ import { create, expect } from '~/testInit'; -import '@/models/Item'; -import ItemCategory from '@/models/ItemCategory'; +import 'models/Item'; +import ItemCategory from 'models/ItemCategory'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/models/Resource.test.js b/server/tests/models/Resource.test.js index 6d7b03c79..8a2a1eb11 100644 --- a/server/tests/models/Resource.test.js +++ b/server/tests/models/Resource.test.js @@ -1,7 +1,7 @@ import { create, expect } from '~/testInit'; -import Resource from '@/models/Resource'; -import '@/models/View'; -import '@/models/ResourceField'; +import Resource from 'models/Resource'; +import 'models/View'; +import 'models/ResourceField'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/models/User.test.js b/server/tests/models/User.test.js index f6633bdbd..b29332288 100644 --- a/server/tests/models/User.test.js +++ b/server/tests/models/User.test.js @@ -1,6 +1,6 @@ import { create, expect } from '~/testInit'; -import User from '@/models/TenantUser'; -import '@/models/Role'; +import User from 'models/TenantUser'; +import 'models/Role'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/models/View.test.js b/server/tests/models/View.test.js index 3a2d5d55b..208302afa 100644 --- a/server/tests/models/View.test.js +++ b/server/tests/models/View.test.js @@ -1,8 +1,8 @@ import { create, expect } from '~/testInit'; -import View from '@/models/View'; -import Resource from '@/models/Resource'; -import ResourceField from '@/models/ResourceField'; -import ViewRole from '@/models/ViewRole'; +import View from 'models/View'; +import Resource from 'models/Resource'; +import ResourceField from 'models/ResourceField'; +import ViewRole from 'models/ViewRole'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/routes/accounting.test.js b/server/tests/routes/accounting.test.js index eae51689b..1b2d1b2c5 100644 --- a/server/tests/routes/accounting.test.js +++ b/server/tests/routes/accounting.test.js @@ -3,9 +3,9 @@ import { expect, } from '~/testInit'; import moment from 'moment'; -import ManualJournal from '@/models/ManualJournal'; -import AccountTransaction from '@/models/AccountTransaction'; -import AccountBalance from '@/models/AccountBalance'; +import ManualJournal from 'models/ManualJournal'; +import AccountTransaction from 'models/AccountTransaction'; +import AccountBalance from 'models/AccountBalance'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/routes/accounts.test.js b/server/tests/routes/accounts.test.js index 6c9a9418c..1a6fb1d31 100644 --- a/server/tests/routes/accounts.test.js +++ b/server/tests/routes/accounts.test.js @@ -2,7 +2,7 @@ import { request, expect, } from '~/testInit'; -import Account from '@/models/Account'; +import Account from 'models/Account'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/routes/auth.test.js b/server/tests/routes/auth.test.js index fffeeaf30..78895dc64 100644 --- a/server/tests/routes/auth.test.js +++ b/server/tests/routes/auth.test.js @@ -1,15 +1,15 @@ import { request, expect, createUser } from '~/testInit'; -import { hashPassword } from '@/utils'; -import knex from '@/database/knex'; +import { hashPassword } from 'utils'; +import knex from 'database/knex'; import { tenantWebsite, tenantFactory, systemFactory, loginRes } from '~/dbInit'; -import TenantUser from '@/models/TenantUser'; -import PasswordReset from '@/system/models/PasswordReset'; -import SystemUser from '@/system/models/SystemUser'; +import TenantUser from 'models/TenantUser'; +import PasswordReset from 'system/models/PasswordReset'; +import SystemUser from 'system/models/SystemUser'; describe('routes: /auth/', () => { diff --git a/server/tests/routes/currencies.test.js b/server/tests/routes/currencies.test.js index 2359d79a3..a6583c0cd 100644 --- a/server/tests/routes/currencies.test.js +++ b/server/tests/routes/currencies.test.js @@ -2,7 +2,7 @@ import { request, expect, } from '~/testInit'; -import Currency from '@/models/Currency'; +import Currency from 'models/Currency'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/routes/customers.test.js b/server/tests/routes/customers.test.js index ac7e724ba..2a3eb327d 100644 --- a/server/tests/routes/customers.test.js +++ b/server/tests/routes/customers.test.js @@ -2,7 +2,7 @@ import { request, expect, } from '~/testInit'; -import Currency from '@/models/Currency'; +import Currency from 'models/Currency'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/routes/expenses.test.js b/server/tests/routes/expenses.test.js index 7ba1bb63d..17a4c4ac9 100644 --- a/server/tests/routes/expenses.test.js +++ b/server/tests/routes/expenses.test.js @@ -4,9 +4,9 @@ import { request, expect, } from '~/testInit'; -import Expense from '@/models/Expense'; -import ExpenseCategory from '@/models/ExpenseCategory'; -import AccountTransaction from '@/models/AccountTransaction'; +import Expense from 'models/Expense'; +import ExpenseCategory from 'models/ExpenseCategory'; +import AccountTransaction from 'models/AccountTransaction'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/routes/inviteUsers.test.js b/server/tests/routes/inviteUsers.test.js index 89fdc5821..24582e2a6 100644 --- a/server/tests/routes/inviteUsers.test.js +++ b/server/tests/routes/inviteUsers.test.js @@ -1,4 +1,4 @@ -import knex from '@/database/knex'; +import knex from 'database/knex'; import { request, expect, @@ -9,9 +9,9 @@ import { tenantFactory, loginRes } from '~/dbInit'; -import Invite from '@/system/models/Invite' -import TenantUser from '@/models/TenantUser'; -import SystemUser from '@/system/models/SystemUser'; +import Invite from 'system/models/Invite' +import TenantUser from 'models/TenantUser'; +import SystemUser from 'system/models/SystemUser'; describe('routes: `/api/invite_users`', () => { describe('POST: `/api/invite_users/send`', () => { diff --git a/server/tests/routes/items.test.js b/server/tests/routes/items.test.js index 84dee7c94..4e2858668 100644 --- a/server/tests/routes/items.test.js +++ b/server/tests/routes/items.test.js @@ -2,7 +2,7 @@ import { request, expect, } from '~/testInit'; -import Item from '@/models/Item'; +import Item from 'models/Item'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/routes/itemsCategories.test.js b/server/tests/routes/itemsCategories.test.js index a8d23f6c2..63e1ee1a2 100644 --- a/server/tests/routes/itemsCategories.test.js +++ b/server/tests/routes/itemsCategories.test.js @@ -2,7 +2,7 @@ import { request, expect, } from '~/testInit'; -import ItemCategory from '@/models/ItemCategory'; +import ItemCategory from 'models/ItemCategory'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/routes/options.test.js b/server/tests/routes/options.test.js index c8a40298f..cc7445e30 100644 --- a/server/tests/routes/options.test.js +++ b/server/tests/routes/options.test.js @@ -2,7 +2,7 @@ import { request, expect, } from '~/testInit'; -import Option from '@/models/Option'; +import Option from 'models/Option'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/routes/payment_receives.test.js b/server/tests/routes/payment_receives.test.js index cc10f6882..c89af23fb 100644 --- a/server/tests/routes/payment_receives.test.js +++ b/server/tests/routes/payment_receives.test.js @@ -10,7 +10,7 @@ import { import { PaymentReceive, PaymentReceiveEntry, -} from '@/models'; +} from 'models'; describe('route: `/sales/payment_receives`', () => { describe('POST: `/sales/payment_receives`', () => { diff --git a/server/tests/routes/receivable_aging.test.js b/server/tests/routes/receivable_aging.test.js index a42037548..a08c05fb1 100644 --- a/server/tests/routes/receivable_aging.test.js +++ b/server/tests/routes/receivable_aging.test.js @@ -2,7 +2,7 @@ import { request, expect, } from '~/testInit'; -import Item from '@/models/Item'; +import Item from 'models/Item'; import { tenantWebsite, tenantFactory, diff --git a/server/tests/routes/sales_invoices.test.js b/server/tests/routes/sales_invoices.test.js index d93a21767..8e759fbdc 100644 --- a/server/tests/routes/sales_invoices.test.js +++ b/server/tests/routes/sales_invoices.test.js @@ -1,6 +1,6 @@ import { tenantWebsite, tenantFactory, loginRes } from '~/dbInit'; import { request, expect } from '~/testInit'; -import { SaleInvoice } from '@/models'; +import { SaleInvoice } from 'models'; import { SaleInvoiceEntry } from '../../src/models'; describe('route: `/sales/invoices`', () => { diff --git a/server/tests/routes/sales_receipts.test.js b/server/tests/routes/sales_receipts.test.js index 4f9db1b38..5d112acb1 100644 --- a/server/tests/routes/sales_receipts.test.js +++ b/server/tests/routes/sales_receipts.test.js @@ -1,6 +1,6 @@ import { tenantWebsite, tenantFactory, loginRes } from '~/dbInit'; import { request, expect } from '~/testInit'; -import { SaleReceipt } from '@/models'; +import { SaleReceipt } from 'models'; describe('route: `/sales/receipts`', () => { describe('POST: `/sales/receipts`', () => { diff --git a/server/tests/routes/users.test.js b/server/tests/routes/users.test.js index 9c42d4c69..4455e50a6 100644 --- a/server/tests/routes/users.test.js +++ b/server/tests/routes/users.test.js @@ -1,4 +1,4 @@ -import knex from '@/database/knex'; +import knex from 'database/knex'; import { request, expect, diff --git a/server/tests/routes/vendors.test.js b/server/tests/routes/vendors.test.js index 6f1ff96dc..0fb1661c2 100644 --- a/server/tests/routes/vendors.test.js +++ b/server/tests/routes/vendors.test.js @@ -2,13 +2,13 @@ import { request, expect, } from '~/testInit'; -import Currency from '@/models/Currency'; +import Currency from 'models/Currency'; import { tenantWebsite, tenantFactory, loginRes } from '~/dbInit'; -import Vendor from '@/models/Vendor'; +import Vendor from 'models/Vendor'; describe('route: `/vendors`', () => { describe('POST: `/vendors`', () => { diff --git a/server/tests/routes/views.test.js b/server/tests/routes/views.test.js index 36652d731..0dedf29f6 100644 --- a/server/tests/routes/views.test.js +++ b/server/tests/routes/views.test.js @@ -2,9 +2,9 @@ import { request, expect, } from '~/testInit'; -import View from '@/models/View'; -import ViewRole from '@/models/ViewRole'; -import '@/models/ResourceField'; +import View from 'models/View'; +import ViewRole from 'models/ViewRole'; +import 'models/ResourceField'; import ViewColumn from '../../src/models/ViewColumn'; import { tenantWebsite, diff --git a/server/tests/services/JournalPoster.test.js b/server/tests/services/JournalPoster.test.js index e49f9c828..d6f69a911 100644 --- a/server/tests/services/JournalPoster.test.js +++ b/server/tests/services/JournalPoster.test.js @@ -1,16 +1,16 @@ import { expect } from '~/testInit'; -import JournalPoster from '@/services/Accounting/JournalPoster'; -import JournalEntry from '@/services/Accounting/JournalEntry'; -import AccountBalance from '@/models/AccountBalance'; -import AccountTransaction from '@/models/AccountTransaction'; -import Account from '@/models/Account'; +import JournalPoster from 'services/Accounting/JournalPoster'; +import JournalEntry from 'services/Accounting/JournalEntry'; +import AccountBalance from 'models/AccountBalance'; +import AccountTransaction from 'models/AccountTransaction'; +import Account from 'models/Account'; import { tenantWebsite, tenantFactory, loginRes } from '~/dbInit'; import { omit } from 'lodash'; -import DependencyGraph from '@/lib/DependencyGraph'; +import DependencyGraph from 'lib/DependencyGraph'; let accountsDepGraph; diff --git a/server/tests/testInit.js b/server/tests/testInit.js index 10870abeb..96d738d42 100644 --- a/server/tests/testInit.js +++ b/server/tests/testInit.js @@ -1,14 +1,14 @@ import chai from 'chai'; import chaiHttp from 'chai-http'; import chaiThings from 'chai-things'; -import systemDb from '@/database/knex'; -import app from '@/app'; -import createTenantFactory from '@/database/factories'; -import TenantsManager from '@/system/TenantsManager'; +import systemDb from 'database/knex'; +import app from 'app'; +import createTenantFactory from 'database/factories'; +import TenantsManager from 'system/TenantsManager'; import faker from 'faker'; -import { hashPassword } from '@/utils'; -import TenantModel from '@/models/TenantModel'; -import createSystemFactory from '@/database/factories/system'; +import { hashPassword } from 'utils'; +import TenantModel from 'models/TenantModel'; +import createSystemFactory from 'database/factories/system'; const { expect } = chai; diff --git a/server/tests/utils/utils.test.js b/server/tests/utils/utils.test.js index ecce65862..4dc6ae640 100644 --- a/server/tests/utils/utils.test.js +++ b/server/tests/utils/utils.test.js @@ -1,4 +1,4 @@ -import { dateRangeCollection } from '@/utils'; +import { dateRangeCollection } from 'utils'; import { expect } from '../testInit'; describe('utils', () => { diff --git a/server/tsconfig.json b/server/tsconfig.json index 82c123afa..370dcf47c 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,21 +1,35 @@ + { - "include": ["./src/**/*"], - "exclude": ["node_modules", "**/*.spec.ts"], "compilerOptions": { - "outDir": "./dist/", - "sourceMap": true, - "noImplicitAny": true, - "module": "commonjs", - "target": "es5", - "jsx": "react", - "allowJs": true, - "esModuleInterop": true, + "target": "es2017", + "lib": [ + "es2017", + "esnext.asynciterable" + ], + "typeRoots": [ + "./node_modules/@types", + "./src/types" + ], + "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "baseUrl": "./", - "paths": { - "@": ["src/"], - "~": ["tests/"] - } - } + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "module": "commonjs", + "pretty": true, + "sourceMap": true, + "outDir": "./build", + "allowJs": true, + "noEmit": false, + "esModuleInterop": true, + "skipLibCheck": true, + "baseUrl": "./src", + }, + "include": [ + "./src/**/*" + ], + "exclude": [ + "node_modules", + "tests" + ] } \ No newline at end of file diff --git a/server/webpack.config.js b/server/webpack.config.js deleted file mode 100644 index 86374706e..000000000 --- a/server/webpack.config.js +++ /dev/null @@ -1,52 +0,0 @@ -const path = require('path'); -const nodeExternals = require('webpack-node-externals'); - -function resolve(dir) { - return path.join(__dirname, '.', dir); -} - -module.exports = { - mode: 'development', - entry: [ - '@/server.js', - ], - target: 'node', - devtool: 'inline-cheap-module-source-map', - externals: [nodeExternals()], - output: { - path: path.resolve(__dirname, 'dist'), - filename: 'bundle.js', - publicPath: 'dist/', - - // use absolute paths in sourcemaps (important for debugging via IDE) - devtoolModuleFilenameTemplate: '[absolute-resource-path]', - devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]', - }, - resolve: { - alias: { - '@': path.resolve(__dirname, 'src'), - '~': path.resolve(__dirname, 'tests'), - }, - extensions: [ '.tsx', '.ts', '.js' ], - }, - module: { - rules: [ - // { - // test: /\.(js)$/, - // loader: 'eslint-loader', - // enforce: 'pre', - // include: [resolve('src'), resolve('test')], - // options: { - // // eslint-disable-next-line global-require - // formatter: require('eslint-friendly-formatter'), - // // emitWarning: !config.dev.showEslintErrorsInOverlay - // }, - // }, - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/, - }, - ], - }, -};