feat: remove path alias.

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

View File

@@ -0,0 +1,32 @@
import { Container } from 'typedi';
import { Request, Response } from 'express';
/**
* Attach user to req.currentUser
* @param {Request} req Express req Object
* @param {Response} res Express res Object
* @param {NextFunction} next Express next Function
*/
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 systemUserRepository.getById(req.token.id);
if (!user) {
Logger.info('[attach_user_middleware] the system user not found.');
return res.boom.unauthorized();
}
// Delete password property from user object.
Reflect.deleteProperty(user, 'password');
req.user = user;
return next();
} catch (e) {
Logger.error('[attach_user_middleware] error attaching user to req: %o', e);
return next(e);
}
};
export default attachCurrentUser;

View File

@@ -0,0 +1,14 @@
import { Container } from 'typedi';
import { Request, Response, NextFunction } from 'express';
export default async (req: Request, res: Response, next: NextFunction) => {
const { Option } = req.models;
const option = await Option.query().where('key', 'app_configured');
if (option.getMeta('app_configured', false)) {
return res.res(400).send({
errors: [{ type: 'TENANT.NOT.CONFIGURED', code: 700 }],
});
}
next();
};

View File

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

View File

@@ -0,0 +1,15 @@
import { Container } from 'typedi';
import { Request, Response, NextFunction } from 'express';
import i18n from 'i18n';
export default (req: Request, res: Response, next: NextFunction) => {
const Logger = Container.get('logger');
let language = req.headers['accept-language'] || 'en';
if (req.user && req.user.language) {
language = req.user.language;
}
Logger.info('[i18n_middleware] set locale language to i18n.', { language, user: req.user });
i18n.setLocale(req, language);
next();
};

View File

@@ -0,0 +1,11 @@
import { NextFunction, Request } from 'express';
import { Container } from 'typedi';
function loggerMiddleware(request: Request, response: Response, next: NextFunction) {
const Logger = Container.get('logger');
Logger.info(`[routes] ${request.method} ${request.path}`);
next();
}
export default loggerMiddleware;

View File

@@ -0,0 +1,31 @@
import { Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
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}`);
if (tenantContainer && !tenantContainer.has('settings')) {
Logger.info('[settings_middleware] initialize settings store.');
const settings = new SettingsStore(knex);
Logger.info('[settings_middleware] load settings from storage or cache.');
await settings.load();
tenantContainer.set('settings', settings);
}
Logger.info('[settings_middleware] get settings instance from container.');
const settings = tenantContainer.get('settings');
req.settings = settings;
res.on('finish', async () => {
await settings.save();
});
next();
}

View File

@@ -0,0 +1,31 @@
import { Request, Response, NextFunction } from 'express';
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 subscriptionRepository.getBySlugInTenant(subscriptionSlug, tenantId);
// Validate in case there is no any already subscription.
if (!subscription) {
Logger.info('[subscription_middleware] tenant has no subscription.', { tenantId });
return res.boom.badRequest(
'Tenant has no subscription.',
{ errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }] }
);
}
// Validate in case the subscription is inactive.
else if (subscription.inactive()) {
Logger.info('[subscription_middleware] tenant main subscription is expired.', { tenantId });
return res.boom.badRequest(null, {
errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }],
});
}
next();
};

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
import logger from "src/loaders/logger";
import { Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
export default (
fn: (rq: Request, rs: Response, next?: NextFunction) => {}) =>
(req: Request, res: Response, next: NextFunction) => {
const Logger = Container.get('logger');
Promise.resolve(fn(req, res, next))
.catch((error) => {
console.log(error);
Logger.error('[async_middleware] error.', { error });
next(error);
});
};

View File

@@ -0,0 +1,32 @@
import { Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import jwt from 'jsonwebtoken';
import config from 'config';
const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
const Logger = Container.get('logger');
const token = req.headers['x-access-token'] || req.query.token;
const onError = () => {
Logger.info('[auth_middleware] jwt verify error.');
res.boom.unauthorized();
};
const onSuccess = (decoded) => {
req.token = decoded;
Logger.info('[auth_middleware] jwt verify success.');
next();
};
if (!token) { return onError(); }
const verify = new Promise((resolve, reject) => {
jwt.verify(token, config.jwtSecret, async (error, decoded) => {
if (error) {
reject(error);
} else {
resolve(decoded);
}
});
});
verify.then(onSuccess).catch(onError);
};
export default authMiddleware;

View File

@@ -0,0 +1,13 @@
import { validationResult } from 'express-validator';
export default (req, res, next) => {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error',
...validationErrors,
});
}
next();
}