WIP Multi-tenant architecture.

This commit is contained in:
Ahmed Bouhuolia
2020-04-21 22:47:27 +02:00
parent 4e0d3feebe
commit 8f588ffc51
64 changed files with 812 additions and 447 deletions

View File

@@ -1,12 +1,13 @@
/* eslint-disable global-require */
import { Model } from 'objection';
import { flatten } from 'lodash';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
import {
buildFilterQuery,
buildSortColumnQuery,
} from '@/lib/ViewRolesBuilder';
export default class Account extends BaseModel {
export default class Account extends TenantModel {
/**
* Table name
*/
@@ -36,7 +37,7 @@ export default class Account extends BaseModel {
},
sortColumnBuilder(query, columnKey, direction) {
buildSortColumnQuery(Account.tableName, columnKey, direction)(query);
}
},
};
}
@@ -54,7 +55,7 @@ export default class Account extends BaseModel {
*/
type: {
relation: Model.BelongsToOneRelation,
modelClass: AccountType.default,
modelClass: this.relationBindKnex(AccountType.default),
join: {
from: 'accounts.accountTypeId',
to: 'account_types.id',
@@ -66,7 +67,7 @@ export default class Account extends BaseModel {
*/
balance: {
relation: Model.HasOneRelation,
modelClass: AccountBalance.default,
modelClass: this.relationBindKnex(AccountBalance.default),
join: {
from: 'accounts.id',
to: 'account_balances.accountId',
@@ -78,7 +79,7 @@ export default class Account extends BaseModel {
*/
transactions: {
relation: Model.HasManyRelation,
modelClass: AccountTransaction.default,
modelClass: this.relationBindKnex(AccountTransaction.default),
join: {
from: 'accounts.id',
to: 'accounts_transactions.accountId',

View File

@@ -1,7 +1,7 @@
import { Model } from 'objection';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class AccountBalance extends BaseModel {
export default class AccountBalance extends TenantModel {
/**
* Table name
*/
@@ -18,9 +18,9 @@ export default class AccountBalance extends BaseModel {
return {
account: {
relation: Model.BelongsToOneRelation,
modelClass: Account.default,
modelClass: this.relationBindKnex(Account.default),
join: {
from: 'account_balance.account_id',
from: 'account_balances.account_id',
to: 'accounts.id',
},
},

View File

@@ -1,8 +1,8 @@
import { Model } from 'objection';
import moment from 'moment';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class AccountTransaction extends BaseModel {
export default class AccountTransaction extends TenantModel {
/**
* Table name
*/
@@ -70,7 +70,7 @@ export default class AccountTransaction extends BaseModel {
return {
account: {
relation: Model.BelongsToOneRelation,
modelClass: Account.default,
modelClass: this.relationBindKnex(Account.default),
join: {
from: 'accounts_transactions.accountId',
to: 'accounts.id',

View File

@@ -1,8 +1,8 @@
// import path from 'path';
import { Model } from 'objection';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class AccountType extends BaseModel {
export default class AccountType extends TenantModel {
/**
* Table name
*/
@@ -22,7 +22,7 @@ export default class AccountType extends BaseModel {
*/
accounts: {
relation: Model.HasManyRelation,
modelClass: Account.default,
modelClass: this.relationBindKnex(Account.default),
join: {
from: 'account_types.id',
to: 'accounts.accountTypeId',

View File

@@ -1,6 +1,6 @@
import BaseModel from '@/models/Model';
import TenantModel from '@/models/Model';
export default class Budget extends BaseModel {
export default class Budget extends TenantModel {
/**
* Table name
*/

View File

@@ -1,6 +1,6 @@
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class Budget extends BaseModel {
export default class Budget extends TenantModel {
/**
* Table name
*/

View File

@@ -1,6 +1,6 @@
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class Currency extends BaseModel {
export default class Currency extends TenantModel {
/**
* Table name
*/

View File

@@ -1,10 +1,54 @@
import BaseModel from '@/models/Model';
import bcrypt from 'bcryptjs';
import { Model } from 'objection';
import TenantModel from '@/models/TenantModel';
// import PermissionsService from '@/services/PermissionsService';
export default class TenantUser extends TenantModel {
// ...PermissionsService
static get virtualAttributes() {
return ['fullName'];
}
export default class ExchangeRate extends BaseModel {
/**
* Table name
*/
static get tableName() {
return 'exchange_rates';
return 'users';
}
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const Role = require('@/models/Role');
return {
roles: {
relation: Model.ManyToManyRelation,
modelClass: this.relationBindKnex(Role.default),
join: {
from: 'users.id',
through: {
from: 'user_has_roles.userId',
to: 'user_has_roles.roleId',
},
to: 'roles.id',
},
},
};
}
/**
* Verify the password of the user.
* @param {String} password - The given password.
* @return {Boolean}
*/
verifyPassword(password) {
return bcrypt.compareSync(password, this.password);
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}

View File

@@ -1,7 +1,8 @@
import { Model } from 'objection';
import BaseModel from '@/models/Model';
import {viewRolesBuilder} from '@/lib/ViewRolesBuilder';
export default class Expense extends BaseModel {
import TenantModel from '@/models/TenantModel';
import { viewRolesBuilder } from '@/lib/ViewRolesBuilder';
export default class Expense extends TenantModel {
/**
* Table name
*/
@@ -60,12 +61,12 @@ export default class Expense extends BaseModel {
*/
static get relationMappings() {
const Account = require('@/models/Account');
const User = require('@/models/User');
const User = require('@/models/TenantUser');
return {
paymentAccount: {
relation: Model.BelongsToOneRelation,
modelClass: Account.default,
modelClass: this.relationBindKnex(Account.default),
join: {
from: 'expenses.paymentAccountId',
to: 'accounts.id',
@@ -74,7 +75,7 @@ export default class Expense extends BaseModel {
expenseAccount: {
relation: Model.BelongsToOneRelation,
modelClass: Account.default,
modelClass: this.relationBindKnex(Account.default),
join: {
from: 'expenses.expenseAccountId',
to: 'accounts.id',
@@ -83,7 +84,7 @@ export default class Expense extends BaseModel {
user: {
relation: Model.BelongsToOneRelation,
modelClass: User.default,
modelClass: this.relationBindKnex(User.default),
join: {
from: 'expenses.userId',
to: 'users.id',

View File

@@ -1,11 +1,10 @@
import { Model } from 'objection';
import path from 'path';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
import {
buildFilterQuery,
} from '@/lib/ViewRolesBuilder';
export default class Item extends BaseModel {
export default class Item extends TenantModel {
/**
* Table name
*/
@@ -34,24 +33,12 @@ export default class Item extends BaseModel {
const ItemCategory = require('@/models/ItemCategory');
return {
/**
* Item may has many meta data.
*/
metadata: {
relation: Model.HasManyRelation,
modelBase: path.join(__dirname, 'ItemMetadata'),
join: {
from: 'items.id',
to: 'items_metadata.item_id',
},
},
/**
* Item may belongs to cateogory model.
*/
category: {
relation: Model.BelongsToOneRelation,
modelClass: ItemCategory.default,
modelClass: this.relationBindKnex(ItemCategory.default),
join: {
from: 'items.categoryId',
to: 'items_categories.id',
@@ -60,7 +47,7 @@ export default class Item extends BaseModel {
costAccount: {
relation: Model.BelongsToOneRelation,
modelClass: Account.default,
modelClass: this.relationBindKnex(Account.default),
join: {
from: 'items.costAccountId',
to: 'accounts.id',
@@ -69,7 +56,7 @@ export default class Item extends BaseModel {
sellAccount: {
relation: Model.BelongsToOneRelation,
modelClass: Account.default,
modelClass: this.relationBindKnex(Account.default),
join: {
from: 'items.sellAccountId',
to: 'accounts.id',
@@ -78,7 +65,7 @@ export default class Item extends BaseModel {
inventoryAccount: {
relation: Model.BelongsToOneRelation,
modelClass: Account.default,
modelClass: this.relationBindKnex(Account.default),
join: {
from: 'items.inventoryAccountId',
to: 'accounts.id',

View File

@@ -1,8 +1,8 @@
import path from 'path';
import { Model } from 'objection';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class ItemCategory extends BaseModel {
export default class ItemCategory extends TenantModel {
/**
* Table name.
*/
@@ -22,7 +22,7 @@ export default class ItemCategory extends BaseModel {
*/
items: {
relation: Model.HasManyRelation,
modelClass: Item.default,
modelClass: this.relationBindKnex(Item.default),
join: {
from: 'items_categories.id',
to: 'items.categoryId',

View File

@@ -1,8 +1,7 @@
import path from 'path';
import { Model } from 'objection';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class ItemMetadata extends BaseModel {
export default class ItemMetadata extends TenantModel {
/**
* Table name
*/
@@ -21,13 +20,15 @@ export default class ItemMetadata extends BaseModel {
* Relationship mapping.
*/
static get relationMappings() {
const Item = require('@/models/Item');
return {
/**
* Item category may has many items.
*/
items: {
relation: Model.BelongsToOneRelation,
modelBase: path.join(__dirname, 'Item'),
modelBase: this.relationBindKnex(Item.default),
join: {
from: 'items_metadata.item_id',
to: 'items.id',

View File

@@ -1,6 +1,6 @@
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class JournalEntry extends BaseModel {
export default class JournalEntry extends TenantModel {
/**
* Table name.
*/

View File

@@ -1,6 +1,6 @@
import BaseModel from '@/models/Model';
import TenantModel from '@/models/Model';
export default class ManualJournal extends BaseModel {
export default class ManualJournal extends TenantModel {
/**
* Table name.
*/

View File

@@ -36,7 +36,7 @@ export default {
setExtraColumns(columns) {
this.extraColumns = columns;
},
/**
* Metadata database query.
* @param {Object} query -
@@ -117,7 +117,7 @@ export default {
metadata.markAsDeleted = true;
}
this.shouldReload = true;
},
/**
* Remove all meta data of the given group.

View File

@@ -1,9 +1,18 @@
import { Model } from 'objection';
import {transform, snakeCase} from 'lodash';
import {mapKeysDeep} from '@/utils';
import { snakeCase } from 'lodash';
import { mapKeysDeep } from '@/utils';
import PaginationQueryBuilder from '@/models/Pagination';
export default class ModelBase extends Model {
static get knexBinded() {
return this.knexBindInstance;
}
static set knexBinded(knex) {
this.knexBindInstance = knex;
}
static get collection() {
return Array;
}
@@ -22,11 +31,14 @@ export default class ModelBase extends Model {
return snakeCase(key);
});
const parsedJson = super.$formatJson(transformed, opt);
return parsedJson;
}
static get QueryBuilder() {
return PaginationQueryBuilder;
}
static relationBindKnex(model) {
return this.knexBinded ? model.bindKnex(this.knexBinded) : model;
}
}

View File

@@ -1,16 +0,0 @@
import bookshelf from './bookshelf';
const OAuthClient = bookshelf.Model.extend({
/**
* Table name
*/
tableName: 'oauth_clients',
/**
* Timestamp columns.
*/
hasTimestamps: false,
});
export default bookshelf.model('OAuthClient', OAuthClient);

View File

@@ -1,81 +0,0 @@
import OAuthClient from '@/models/OAuthClient';
import OAuthToken from '@/models/OAuthToken';
import User from '@/models/User';
export default {
/**
* Retrieve the access token.
* @param {String} bearerToken -
*/
async getAccessToken(bearerToken) {
const token = await OAuthClient.where({
access_token: bearerToken,
}).fetch();
return {
accessToken: token.attributes.access_token,
client: {
id: token.attributes.client_id,
},
expires: token.attributes.access_token_expires_on,
};
},
/**
* Retrieve the client from client id and secret.
* @param {Number} clientId -
* @param {String} clientSecret -
*/
async getClient(clientId, clientSecret) {
const token = await OAuthClient.where({
client_id: clientId,
client_secret: clientSecret,
});
if (!token) { return {}; }
return {
clientId: token.attributes.client_id,
clientSecret: token.attributes.client_secret,
grants: ['password'],
};
},
/**
* Get specific user with given username and password.
*/
async getUser(username, password) {
const user = await User.query((query) => {
query.where('username', username);
query.where('password', password);
}).fetch();
return {
...user.attributes,
};
},
/**
* Saves the access token.
* @param {Object} token -
* @param {Object} client -
* @param {Object} user -
*/
async saveAccessToken(token, client, user) {
const oauthToken = OAuthToken.forge({
access_token: token.accessToken,
access_token_expires_on: token.accessTokenExpiresOn,
client_id: client.id,
refresh_token: token.refreshToken,
refresh_token_expires_on: token.refreshTokenExpiresOn,
user_id: user.id,
});
await oauthToken.save();
return {
client: { id: client.id },
user: { id: user.id },
};
},
};

View File

@@ -1,16 +0,0 @@
import bookshelf from './bookshelf';
const OAuthToken = bookshelf.Model.extend({
/**
* Table name
*/
tableName: 'oauth_tokens',
/**
* Timestamp columns.
*/
hasTimestamps: false,
});
export default bookshelf.model('OAuthToken', OAuthToken);

View File

@@ -1,6 +1,6 @@
import Model from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class PasswordResets extends Model {
export default class PasswordResets extends TenantModel {
/**
* Table name
*/

View File

@@ -1,8 +1,8 @@
import { Model } from 'objection';
import path from 'path';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class Permission extends BaseModel {
export default class Permission extends TenantModel {
/**
* Table name of Role model.
* @type {String}
@@ -15,18 +15,20 @@ export default class Permission extends BaseModel {
* Relationship mapping.
*/
static get relationMappings() {
const Role = require('@/models/Role');
return {
/**
* Permission model may belongs to role model.
*/
role: {
relation: Model.BelongsToOneRelation,
modelBase: path.join(__dirname, 'Role'),
join: {
from: 'permissions.role_id',
to: 'roles.id',
},
},
// role: {
// relation: Model.BelongsToOneRelation,
// modelBase: path.join(__dirname, 'Role').bindKnex(this.knexBinded),
// join: {
// from: 'permissions.role_id',
// to: 'roles.id',
// },
// },
// resource: {
// relation: Model.BelongsToOneRelation,

View File

@@ -1,8 +1,7 @@
import path from 'path';
import { Model } from 'objection';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class Resource extends BaseModel {
export default class Resource extends TenantModel {
/**
* Table name.
*/
@@ -31,7 +30,7 @@ export default class Resource extends BaseModel {
*/
views: {
relation: Model.HasManyRelation,
modelClass: View.default,
modelClass: this.relationBindKnex(View.default),
join: {
from: 'resources.id',
to: 'views.resourceId',
@@ -43,7 +42,7 @@ export default class Resource extends BaseModel {
*/
fields: {
relation: Model.HasManyRelation,
modelClass: ResourceField.default,
modelClass: this.relationBindKnex(ResourceField.default),
join: {
from: 'resources.id',
to: 'resource_fields.resourceId',
@@ -55,7 +54,7 @@ export default class Resource extends BaseModel {
*/
permissions: {
relation: Model.ManyToManyRelation,
modelClass: Permission.default,
modelClass: this.relationBindKnex(Permission.default),
join: {
from: 'resources.id',
through: {

View File

@@ -1,9 +1,9 @@
import { snakeCase } from 'lodash';
import { Model } from 'objection';
import path from 'path';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class ResourceField extends BaseModel {
export default class ResourceField extends TenantModel {
/**
* Table name.
*/
@@ -51,15 +51,17 @@ export default class ResourceField extends BaseModel {
* Relationship mapping.
*/
static get relationMappings() {
const Resource = require('@/models/Resource');
return {
/**
* Resource field may belongs to resource model.
*/
resource: {
relation: Model.BelongsToOneRelation,
modelBase: path.join(__dirname, 'Resource'),
modelClass: this.relationBindKnex(Resource.default),
join: {
from: 'resource_fields.resource_id',
from: 'resource_fields.resourceId',
to: 'resources.id',
},
},

View File

@@ -1,9 +1,9 @@
import { Model } from 'objection';
import path from 'path';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
import ResourceFieldMetadataCollection from '@/collection/ResourceFieldMetadataCollection';
export default class ResourceFieldMetadata extends BaseModel {
export default class ResourceFieldMetadata extends TenantModel {
/**
* Table name.
*/

View File

@@ -1,7 +1,7 @@
import { Model } from 'objection';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class Role extends BaseModel {
export default class Role extends TenantModel {
/**
* Table name of Role model.
* @type {String}
@@ -23,7 +23,7 @@ export default class Role extends BaseModel {
static get relationMappings() {
const Permission = require('@/models/Permission');
const Resource = require('@/models/Resource');
const User = require('@/models/User');
const User = require('@/models/TenantUser');
const ResourceField = require('@/models/ResourceField');
return {
@@ -32,7 +32,7 @@ export default class Role extends BaseModel {
*/
permissions: {
relation: Model.ManyToManyRelation,
modelClass: Permission.default,
modelClass: Permission.default.bindKnex(this.knexBinded),
join: {
from: 'roles.id',
through: {
@@ -48,7 +48,7 @@ export default class Role extends BaseModel {
*/
resources: {
relation: Model.ManyToManyRelation,
modelClass: Resource.default,
modelClass: Resource.default.bindKnex(this.knexBinded),
join: {
from: 'roles.id',
through: {
@@ -64,11 +64,11 @@ export default class Role extends BaseModel {
*/
field: {
relation: Model.BelongsToOneRelation,
modelClass: ResourceField.default,
modelClass: ResourceField.default.bindKnex(this.knexBinded),
join: {
from: 'roles.fieldId',
to: 'resource_fields.id',
}
},
},
/**
@@ -76,7 +76,7 @@ export default class Role extends BaseModel {
*/
users: {
relation: Model.ManyToManyRelation,
modelClass: User.default,
modelClass: User.default.bindKnex(this.knexBinded),
join: {
from: 'roles.id',
through: {

View File

@@ -1,7 +1,7 @@
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
import Auth from './Auth';
export default class Setting extends BaseModel {
export default class Setting extends TenantModel {
/**
* Table name
*/

View File

@@ -0,0 +1,4 @@
import BaseModel from '@/models/Model';
export default class TenantModel extends BaseModel{
}

View File

@@ -3,7 +3,7 @@ import { Model } from 'objection';
import BaseModel from '@/models/Model';
// import PermissionsService from '@/services/PermissionsService';
export default class User extends BaseModel {
export default class TenantUser extends BaseModel {
// ...PermissionsService
static get virtualAttributes() {
@@ -26,7 +26,7 @@ export default class User extends BaseModel {
return {
roles: {
relation: Model.ManyToManyRelation,
modelClass: Role.default,
modelClass: this.relationBindKnex(Role.default),
join: {
from: 'users.id',
through: {
@@ -51,4 +51,4 @@ export default class User extends BaseModel {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
}

View File

@@ -1,8 +1,7 @@
import path from 'path';
import { Model } from 'objection';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class View extends BaseModel {
export default class View extends TenantModel {
/**
* Table name.
*/
@@ -24,7 +23,7 @@ export default class View extends BaseModel {
*/
resource: {
relation: Model.BelongsToOneRelation,
modelClass: Resource.default,
modelClass: this.relationBindKnex(Resource.default),
join: {
from: 'views.resourceId',
to: 'resources.id',
@@ -36,7 +35,7 @@ export default class View extends BaseModel {
*/
columns: {
relation: Model.HasManyRelation,
modelClass: ViewColumn.default,
modelClass: this.relationBindKnex(ViewColumn.default),
join: {
from: 'views.id',
to: 'view_has_columns.viewId',
@@ -48,7 +47,7 @@ export default class View extends BaseModel {
*/
roles: {
relation: Model.HasManyRelation,
modelClass: ViewRole.default,
modelClass: this.relationBindKnex(ViewRole.default),
join: {
from: 'views.id',
to: 'view_roles.viewId',

View File

@@ -1,7 +1,7 @@
import { Model } from 'objection';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class ViewColumn extends BaseModel {
export default class ViewColumn extends TenantModel {
/**
* Table name.
*/
@@ -29,9 +29,9 @@ export default class ViewColumn extends BaseModel {
*/
field: {
relation: Model.BelongsToOneRelation,
modelClass: ResourceField.default,
modelClass: this.relationBindKnex(ResourceField.default),
join: {
from: 'view_columns.fieldId',
from: 'view_has_columns.fieldId',
to: 'resource_fields.id',
},
},

View File

@@ -1,7 +1,7 @@
import { Model } from 'objection';
import BaseModel from '@/models/Model';
import TenantModel from '@/models/TenantModel';
export default class ViewRole extends BaseModel {
export default class ViewRole extends TenantModel {
/**
* Virtual attributes.
@@ -43,7 +43,7 @@ export default class ViewRole extends BaseModel {
*/
view: {
relation: Model.BelongsToOneRelation,
modelClass: View.default,
modelClass: this.relationBindKnex(View.default),
join: {
from: 'view_roles.viewId',
to: 'views.id',
@@ -55,7 +55,7 @@ export default class ViewRole extends BaseModel {
*/
field: {
relation: Model.BelongsToOneRelation,
modelClass: ResourceField.default,
modelClass: this.relationBindKnex(ResourceField.default),
join: {
from: 'view_roles.fieldId',
to: 'resource_fields.id',

View File

@@ -1,7 +0,0 @@
import { Model } from 'objection';
import knex from '@/database/knex';
// Bind all Models to a knex instance. If you only have one database in
// your server this is all you have to do. For multi database systems, see
// the Model.bindKnex() method.
Model.knex(knex);