feat(app-config): persist relational virtual attrs

This commit is contained in:
Ali BARIN
2024-10-07 09:11:55 +00:00
committed by Faruk AYDIN
parent ecb04b4ba9
commit f5d796ea77
4 changed files with 94 additions and 21 deletions

View File

@@ -0,0 +1,33 @@
export async function up(knex) {
await knex.schema.alterTable('app_configs', (table) => {
table.boolean('can_connect').defaultTo(false);
});
const appConfigs = await knex('app_configs').select('*');
for (const appConfig of appConfigs) {
const appAuthClients = await knex('app_auth_clients').where(
'app_key',
appConfig.key
);
const hasSomeActiveAppAuthClients = !!appAuthClients?.some(
(appAuthClient) => appAuthClient.active
);
const shared = appConfig.shared;
const active = appConfig.disabled === false;
const canConnectConditions = [hasSomeActiveAppAuthClients, shared, active];
const canConnect = canConnectConditions.every(Boolean);
await knex('app_configs')
.where('id', appConfig.id)
.update({ can_connect: canConnect });
}
}
export async function down(knex) {
await knex.schema.alterTable('app_configs', (table) => {
table.dropColumn('can_connect');
});
}

View File

@@ -2,6 +2,7 @@ import AES from 'crypto-js/aes.js';
import enc from 'crypto-js/enc-utf8.js'; import enc from 'crypto-js/enc-utf8.js';
import appConfig from '../config/app.js'; import appConfig from '../config/app.js';
import Base from './base.js'; import Base from './base.js';
import AppConfig from './app-config.js';
class AppAuthClient extends Base { class AppAuthClient extends Base {
static tableName = 'app_auth_clients'; static tableName = 'app_auth_clients';
@@ -21,6 +22,17 @@ class AppAuthClient extends Base {
}, },
}; };
static relationMappings = () => ({
appConfig: {
relation: Base.HasOneRelation,
modelClass: AppConfig,
join: {
from: 'app_auth_clients.app_key',
to: 'app_configs.key',
},
},
});
encryptData() { encryptData() {
if (!this.eligibleForEncryption()) return; if (!this.eligibleForEncryption()) return;
@@ -48,6 +60,12 @@ class AppAuthClient extends Base {
return this.authDefaults ? true : false; return this.authDefaults ? true : false;
} }
async triggerAppConfigUpdate() {
const appConfig = await this.$relatedQuery('appConfig').select('*');
await appConfig.$query().patch({});
}
// TODO: Make another abstraction like beforeSave instead of using // TODO: Make another abstraction like beforeSave instead of using
// beforeInsert and beforeUpdate separately for the same operation. // beforeInsert and beforeUpdate separately for the same operation.
async $beforeInsert(queryContext) { async $beforeInsert(queryContext) {
@@ -55,11 +73,23 @@ class AppAuthClient extends Base {
this.encryptData(); this.encryptData();
} }
async $afterInsert(queryContext) {
await super.$afterInsert(queryContext);
await this.triggerAppConfigUpdate();
}
async $beforeUpdate(opt, queryContext) { async $beforeUpdate(opt, queryContext) {
await super.$beforeUpdate(opt, queryContext); await super.$beforeUpdate(opt, queryContext);
this.encryptData(); this.encryptData();
} }
async $afterUpdate(queryContext) {
await super.$afterUpdate(queryContext);
await this.triggerAppConfigUpdate();
}
async $afterFind() { async $afterFind() {
this.decryptData(); this.decryptData();
} }

View File

@@ -12,6 +12,7 @@ class AppConfig extends Base {
properties: { properties: {
id: { type: 'string', format: 'uuid' }, id: { type: 'string', format: 'uuid' },
key: { type: 'string' }, key: { type: 'string' },
canConnect: { type: 'boolean', default: false },
allowCustomConnection: { type: 'boolean', default: false }, allowCustomConnection: { type: 'boolean', default: false },
shared: { type: 'boolean', default: false }, shared: { type: 'boolean', default: false },
disabled: { type: 'boolean', default: false }, disabled: { type: 'boolean', default: false },
@@ -32,30 +33,39 @@ class AppConfig extends Base {
}); });
static get virtualAttributes() { static get virtualAttributes() {
return ['canConnect', 'canCustomConnect']; return ['canCustomConnect'];
} }
get canCustomConnect() { get canCustomConnect() {
return !this.disabled && this.allowCustomConnection; return !this.disabled && this.allowCustomConnection;
} }
get canConnect() {
const hasSomeActiveAppAuthClients = !!this.appAuthClients?.some(
(appAuthClient) => appAuthClient.active
);
const shared = this.shared;
const active = this.disabled === false;
const conditions = [hasSomeActiveAppAuthClients, shared, active];
return conditions.every(Boolean);
}
async getApp() { async getApp() {
if (!this.key) return null; if (!this.key) return null;
return await App.findOneByKey(this.key); return await App.findOneByKey(this.key);
} }
async updateCanConnectValue() {
const appAuthClients = await this.$relatedQuery('appAuthClients');
const hasSomeActiveAppAuthClients = !!appAuthClients?.some(
(appAuthClient) => appAuthClient.active
);
const shared = this.shared;
const active = this.disabled === false;
const conditions = [hasSomeActiveAppAuthClients, shared, active];
this.canConnect = conditions.every(Boolean);
return this;
}
async $beforeUpdate(opt, queryContext) {
await super.$beforeUpdate(opt, queryContext);
await opt.old.updateCanConnectValue();
}
} }
export default AppConfig; export default AppConfig;

View File

@@ -36,14 +36,6 @@ describe('AppConfig model', () => {
expect(AppConfig.virtualAttributes).toMatchSnapshot(); expect(AppConfig.virtualAttributes).toMatchSnapshot();
}); });
it('getApp should return associated application', async () => {
const appConfig = await createAppConfig({ key: 'deepl' });
const app = await appConfig.getApp();
expect(app.key).toBe('deepl');
});
describe('canCustomConnect', () => { describe('canCustomConnect', () => {
it('should return true when app is enabled and allows custom connection', async () => { it('should return true when app is enabled and allows custom connection', async () => {
const appConfig = await createAppConfig({ const appConfig = await createAppConfig({
@@ -112,4 +104,12 @@ describe('AppConfig model', () => {
expect(appConfig.canConnect).toBe(false); expect(appConfig.canConnect).toBe(false);
}); });
}); });
it('getApp should return associated application', async () => {
const appConfig = await createAppConfig({ key: 'deepl' });
const app = await appConfig.getApp();
expect(app.key).toBe('deepl');
});
}); });