feat: Convert model files to JS

This commit is contained in:
Faruk AYDIN
2024-01-04 19:27:13 +01:00
parent b693c12500
commit 8819ddefa7
20 changed files with 140 additions and 363 deletions

View File

@@ -1,19 +1,9 @@
import { IJSONObject } from '@automatisch/types';
import { AES, enc } from 'crypto-js'; import { AES, enc } from 'crypto-js';
import { ModelOptions, QueryContext } from 'objection';
import appConfig from '../config/app'; import appConfig from '../config/app';
import AppConfig from './app-config'; import AppConfig from './app-config';
import Base from './base'; import Base from './base';
class AppAuthClient extends Base { class AppAuthClient extends Base {
id!: string;
name: string;
active: boolean;
appConfigId!: string;
authDefaults: string;
formattedAuthDefaults?: IJSONObject;
appConfig?: AppConfig;
static tableName = 'app_auth_clients'; static tableName = 'app_auth_clients';
static jsonSchema = { static jsonSchema = {
@@ -42,7 +32,7 @@ class AppAuthClient extends Base {
}, },
}); });
encryptData(): void { encryptData() {
if (!this.eligibleForEncryption()) return; if (!this.eligibleForEncryption()) return;
this.authDefaults = AES.encrypt( this.authDefaults = AES.encrypt(
@@ -52,7 +42,7 @@ class AppAuthClient extends Base {
delete this.formattedAuthDefaults; delete this.formattedAuthDefaults;
} }
decryptData(): void { decryptData() {
if (!this.eligibleForDecryption()) return; if (!this.eligibleForDecryption()) return;
this.formattedAuthDefaults = JSON.parse( this.formattedAuthDefaults = JSON.parse(
@@ -60,30 +50,27 @@ class AppAuthClient extends Base {
); );
} }
eligibleForEncryption(): boolean { eligibleForEncryption() {
return this.formattedAuthDefaults ? true : false; return this.formattedAuthDefaults ? true : false;
} }
eligibleForDecryption(): boolean { eligibleForDecryption() {
return this.authDefaults ? true : false; return this.authDefaults ? true : false;
} }
// 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: QueryContext): Promise<void> { async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext); await super.$beforeInsert(queryContext);
this.encryptData(); this.encryptData();
} }
async $beforeUpdate( async $beforeUpdate(opt, queryContext) {
opt: ModelOptions,
queryContext: QueryContext
): Promise<void> {
await super.$beforeUpdate(opt, queryContext); await super.$beforeUpdate(opt, queryContext);
this.encryptData(); this.encryptData();
} }
async $afterFind(): Promise<void> { async $afterFind() {
this.decryptData(); this.decryptData();
} }
} }

View File

@@ -3,14 +3,6 @@ import Base from './base';
import AppAuthClient from './app-auth-client'; import AppAuthClient from './app-auth-client';
class AppConfig extends Base { class AppConfig extends Base {
id!: string;
key!: string;
allowCustomConnection: boolean;
shared: boolean;
disabled: boolean;
app?: App;
appAuthClients?: AppAuthClient[];
static tableName = 'app_configs'; static tableName = 'app_configs';
static jsonSchema = { static jsonSchema = {
@@ -46,16 +38,13 @@ class AppConfig extends Base {
} }
get canConnect() { get canConnect() {
const hasSomeActiveAppAuthClients = !!this.appAuthClients const hasSomeActiveAppAuthClients = !!this.appAuthClients?.some(
?.some(appAuthClient => appAuthClient.active); (appAuthClient) => appAuthClient.active
);
const shared = this.shared; const shared = this.shared;
const active = this.disabled === false; const active = this.disabled === false;
const conditions = [ const conditions = [hasSomeActiveAppAuthClients, shared, active];
hasSomeActiveAppAuthClients,
shared,
active
];
return conditions.every(Boolean); return conditions.every(Boolean);
} }

View File

@@ -1,6 +1,5 @@
import fs from 'fs'; import fs from 'fs';
import { join } from 'path'; import { join } from 'path';
import { IApp } from '@automatisch/types';
import appInfoConverter from '../helpers/app-info-converter'; import appInfoConverter from '../helpers/app-info-converter';
import getApp from '../helpers/get-app'; import getApp from '../helpers/get-app';
@@ -10,7 +9,7 @@ class App {
.readdirSync(this.folderPath) .readdirSync(this.folderPath)
.filter((file) => fs.statSync(this.folderPath + '/' + file).isDirectory()); .filter((file) => fs.statSync(this.folderPath + '/' + file).isDirectory());
static async findAll(name?: string, stripFuncs = true): Promise<IApp[]> { static async findAll(name, stripFuncs = true) {
if (!name) if (!name)
return Promise.all( return Promise.all(
this.list.map( this.list.map(
@@ -25,39 +24,45 @@ class App {
); );
} }
static async findOneByName(name: string, stripFuncs = false): Promise<IApp> { static async findOneByName(name, stripFuncs = false) {
const rawAppData = await getApp(name.toLocaleLowerCase(), stripFuncs); const rawAppData = await getApp(name.toLocaleLowerCase(), stripFuncs);
return appInfoConverter(rawAppData); return appInfoConverter(rawAppData);
} }
static async findOneByKey(key: string, stripFuncs = false): Promise<IApp> { static async findOneByKey(key, stripFuncs = false) {
const rawAppData = await getApp(key, stripFuncs); const rawAppData = await getApp(key, stripFuncs);
return appInfoConverter(rawAppData); return appInfoConverter(rawAppData);
} }
static async checkAppAndAction(appKey: string, actionKey: string): Promise<void> { static async checkAppAndAction(appKey, actionKey) {
const app = await this.findOneByKey(appKey); const app = await this.findOneByKey(appKey);
if (!actionKey) return; if (!actionKey) return;
const hasAction = app.actions?.find(action => action.key === actionKey); const hasAction = app.actions?.find((action) => action.key === actionKey);
if (!hasAction) { if (!hasAction) {
throw new Error(`${app.name} does not have an action with the "${actionKey}" key!`); throw new Error(
`${app.name} does not have an action with the "${actionKey}" key!`
);
} }
} }
static async checkAppAndTrigger(appKey: string, triggerKey: string): Promise<void> { static async checkAppAndTrigger(appKey, triggerKey) {
const app = await this.findOneByKey(appKey); const app = await this.findOneByKey(appKey);
if (!triggerKey) return; if (!triggerKey) return;
const hasTrigger = app.triggers?.find(trigger => trigger.key === triggerKey); const hasTrigger = app.triggers?.find(
(trigger) => trigger.key === triggerKey
);
if (!hasTrigger) { if (!hasTrigger) {
throw new Error(`${app.name} does not have a trigger with the "${triggerKey}" key!`); throw new Error(
`${app.name} does not have a trigger with the "${triggerKey}" key!`
);
} }
} }
} }

View File

@@ -1,18 +1,12 @@
import { AjvValidator, Model, snakeCaseMappers } from 'objection'; import { AjvValidator, Model, snakeCaseMappers } from 'objection';
import type { QueryContext, ModelOptions, ColumnNameMappers } from 'objection';
import addFormats from 'ajv-formats'; import addFormats from 'ajv-formats';
import ExtendedQueryBuilder from './query-builder'; import ExtendedQueryBuilder from './query-builder';
class Base extends Model { class Base extends Model {
createdAt!: string;
updatedAt!: string;
deletedAt: string;
QueryBuilderType!: ExtendedQueryBuilder<this>;
static QueryBuilder = ExtendedQueryBuilder; static QueryBuilder = ExtendedQueryBuilder;
static get columnNameMappers(): ColumnNameMappers { static get columnNameMappers() {
return snakeCaseMappers(); return snakeCaseMappers();
} }
@@ -29,17 +23,14 @@ class Base extends Model {
}); });
} }
async $beforeInsert(queryContext: QueryContext): Promise<void> { async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext); await super.$beforeInsert(queryContext);
this.createdAt = new Date().toISOString(); this.createdAt = new Date().toISOString();
this.updatedAt = new Date().toISOString(); this.updatedAt = new Date().toISOString();
} }
async $beforeUpdate( async $beforeUpdate(opts, queryContext) {
opts: ModelOptions,
queryContext: QueryContext
): Promise<void> {
this.updatedAt = new Date().toISOString(); this.updatedAt = new Date().toISOString();
await super.$beforeUpdate(opts, queryContext); await super.$beforeUpdate(opts, queryContext);

View File

@@ -1,11 +1,6 @@
import { IJSONValue } from '@automatisch/types';
import Base from './base'; import Base from './base';
class Config extends Base { class Config extends Base {
id!: string;
key!: string;
value!: { data: IJSONValue };
static tableName = 'config'; static tableName = 'config';
static jsonSchema = { static jsonSchema = {

View File

@@ -1,36 +1,15 @@
import { QueryContext, ModelOptions } from 'objection';
import type { RelationMappings } from 'objection';
import { AES, enc } from 'crypto-js'; import { AES, enc } from 'crypto-js';
import { IRequest } from '@automatisch/types';
import App from './app'; import App from './app';
import AppConfig from './app-config'; import AppConfig from './app-config';
import AppAuthClient from './app-auth-client'; import AppAuthClient from './app-auth-client';
import Base from './base'; import Base from './base';
import User from './user'; import User from './user';
import Step from './step'; import Step from './step';
import ExtendedQueryBuilder from './query-builder';
import appConfig from '../config/app'; import appConfig from '../config/app';
import { IJSONObject } from '@automatisch/types';
import Telemetry from '../helpers/telemetry'; import Telemetry from '../helpers/telemetry';
import globalVariable from '../helpers/global-variable'; import globalVariable from '../helpers/global-variable';
class Connection extends Base { class Connection extends Base {
id!: string;
key!: string;
data: string;
formattedData?: IJSONObject;
userId!: string;
verified: boolean;
draft: boolean;
count?: number;
flowCount?: number;
user?: User;
steps?: Step[];
triggerSteps?: Step[];
appAuthClientId?: string;
appAuthClient?: AppAuthClient;
appConfig?: AppConfig;
static tableName = 'connections'; static tableName = 'connections';
static jsonSchema = { static jsonSchema = {
@@ -56,7 +35,7 @@ class Connection extends Base {
return ['reconnectable']; return ['reconnectable'];
} }
static relationMappings = (): RelationMappings => ({ static relationMappings = () => ({
user: { user: {
relation: Base.BelongsToOneRelation, relation: Base.BelongsToOneRelation,
modelClass: User, modelClass: User,
@@ -80,7 +59,7 @@ class Connection extends Base {
from: 'connections.id', from: 'connections.id',
to: 'steps.connection_id', to: 'steps.connection_id',
}, },
filter(builder: ExtendedQueryBuilder<Step>) { filter(builder) {
builder.where('type', '=', 'trigger'); builder.where('type', '=', 'trigger');
}, },
}, },
@@ -114,7 +93,7 @@ class Connection extends Base {
return true; return true;
} }
encryptData(): void { encryptData() {
if (!this.eligibleForEncryption()) return; if (!this.eligibleForEncryption()) return;
this.data = AES.encrypt( this.data = AES.encrypt(
@@ -125,7 +104,7 @@ class Connection extends Base {
delete this.formattedData; delete this.formattedData;
} }
decryptData(): void { decryptData() {
if (!this.eligibleForDecryption()) return; if (!this.eligibleForDecryption()) return;
this.formattedData = JSON.parse( this.formattedData = JSON.parse(
@@ -133,39 +112,36 @@ class Connection extends Base {
); );
} }
eligibleForEncryption(): boolean { eligibleForEncryption() {
return this.formattedData ? true : false; return this.formattedData ? true : false;
} }
eligibleForDecryption(): boolean { eligibleForDecryption() {
return this.data ? true : false; return this.data ? true : false;
} }
// 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: QueryContext): Promise<void> { async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext); await super.$beforeInsert(queryContext);
this.encryptData(); this.encryptData();
} }
async $beforeUpdate( async $beforeUpdate(opt, queryContext) {
opt: ModelOptions,
queryContext: QueryContext
): Promise<void> {
await super.$beforeUpdate(opt, queryContext); await super.$beforeUpdate(opt, queryContext);
this.encryptData(); this.encryptData();
} }
async $afterFind(): Promise<void> { async $afterFind() {
this.decryptData(); this.decryptData();
} }
async $afterInsert(queryContext: QueryContext) { async $afterInsert(queryContext) {
await super.$afterInsert(queryContext); await super.$afterInsert(queryContext);
Telemetry.connectionCreated(this); Telemetry.connectionCreated(this);
} }
async $afterUpdate(opt: ModelOptions, queryContext: QueryContext) { async $afterUpdate(opt, queryContext) {
await super.$afterUpdate(opt, queryContext); await super.$afterUpdate(opt, queryContext);
Telemetry.connectionUpdated(this); Telemetry.connectionUpdated(this);
} }
@@ -176,7 +152,7 @@ class Connection extends Base {
return await App.findOneByKey(this.key); return await App.findOneByKey(this.key);
} }
async verifyWebhook(request: IRequest) { async verifyWebhook(request) {
if (!this.key) return true; if (!this.key) return true;
const app = await this.getApp(); const app = await this.getApp();

View File

@@ -1,5 +1,3 @@
import type { QueryContext } from 'objection';
import { IJSONObject } from '@automatisch/types';
import appConfig from '../config/app'; import appConfig from '../config/app';
import Base from './base'; import Base from './base';
import Execution from './execution'; import Execution from './execution';
@@ -7,17 +5,6 @@ import Step from './step';
import Telemetry from '../helpers/telemetry'; import Telemetry from '../helpers/telemetry';
class ExecutionStep extends Base { class ExecutionStep extends Base {
id!: string;
executionId!: string;
stepId!: string;
dataIn!: IJSONObject;
dataOut!: IJSONObject;
errorDetails: IJSONObject;
status: 'success' | 'failure';
step: Step;
execution?: Execution;
count?: number;
static tableName = 'execution_steps'; static tableName = 'execution_steps';
static jsonSchema = { static jsonSchema = {
@@ -60,7 +47,7 @@ class ExecutionStep extends Base {
return this.status === 'failure'; return this.status === 'failure';
} }
async $afterInsert(queryContext: QueryContext) { async $afterInsert(queryContext) {
await super.$afterInsert(queryContext); await super.$afterInsert(queryContext);
Telemetry.executionStepCreated(this); Telemetry.executionStepCreated(this);

View File

@@ -1,17 +1,9 @@
import type { QueryContext } from 'objection';
import Base from './base'; import Base from './base';
import Flow from './flow'; import Flow from './flow';
import ExecutionStep from './execution-step'; import ExecutionStep from './execution-step';
import Telemetry from '../helpers/telemetry'; import Telemetry from '../helpers/telemetry';
class Execution extends Base { class Execution extends Base {
id!: string;
flowId!: string;
testRun: boolean;
internalId: string;
executionSteps: ExecutionStep[];
flow?: Flow;
static tableName = 'executions'; static tableName = 'executions';
static jsonSchema = { static jsonSchema = {
@@ -47,7 +39,7 @@ class Execution extends Base {
}, },
}); });
async $afterInsert(queryContext: QueryContext) { async $afterInsert(queryContext) {
await super.$afterInsert(queryContext); await super.$afterInsert(queryContext);
Telemetry.executionCreated(this); Telemetry.executionCreated(this);
} }

View File

@@ -1,10 +1,4 @@
import { ValidationError } from 'objection'; import { ValidationError } from 'objection';
import type {
ModelOptions,
QueryContext,
StaticHookArguments,
} from 'objection';
import ExtendedQueryBuilder from './query-builder';
import Base from './base'; import Base from './base';
import Step from './step'; import Step from './step';
import User from './user'; import User from './user';
@@ -12,19 +6,6 @@ import Execution from './execution';
import Telemetry from '../helpers/telemetry'; import Telemetry from '../helpers/telemetry';
class Flow extends Base { class Flow extends Base {
id!: string;
name!: string;
userId!: string;
active: boolean;
status: 'paused' | 'published' | 'draft';
steps: Step[];
triggerStep: Step;
publishedAt: string;
remoteWebhookId: string;
executions?: Execution[];
lastExecution?: Execution;
user?: User;
static tableName = 'flows'; static tableName = 'flows';
static jsonSchema = { static jsonSchema = {
@@ -52,7 +33,7 @@ class Flow extends Base {
from: 'flows.id', from: 'flows.id',
to: 'steps.flow_id', to: 'steps.flow_id',
}, },
filter(builder: ExtendedQueryBuilder<Step>) { filter(builder) {
builder.orderBy('position', 'asc'); builder.orderBy('position', 'asc');
}, },
}, },
@@ -63,11 +44,8 @@ class Flow extends Base {
from: 'flows.id', from: 'flows.id',
to: 'steps.flow_id', to: 'steps.flow_id',
}, },
filter(builder: ExtendedQueryBuilder<Step>) { filter(builder) {
builder builder.where('type', 'trigger').limit(1).first();
.where('type', 'trigger')
.limit(1)
.first();
}, },
}, },
executions: { executions: {
@@ -85,7 +63,7 @@ class Flow extends Base {
from: 'flows.id', from: 'flows.id',
to: 'executions.flow_id', to: 'executions.flow_id',
}, },
filter(builder: ExtendedQueryBuilder<Execution>) { filter(builder) {
builder.orderBy('created_at', 'desc').limit(1).first(); builder.orderBy('created_at', 'desc').limit(1).first();
}, },
}, },
@@ -99,7 +77,7 @@ class Flow extends Base {
}, },
}); });
static async afterFind(args: StaticHookArguments<any>): Promise<any> { static async afterFind(args) {
const { result } = args; const { result } = args;
const referenceFlow = result[0]; const referenceFlow = result[0];
@@ -122,7 +100,7 @@ class Flow extends Base {
async lastInternalId() { async lastInternalId() {
const lastExecution = await this.$relatedQuery('lastExecution'); const lastExecution = await this.$relatedQuery('lastExecution');
return lastExecution ? (lastExecution as Execution).internalId : null; return lastExecution ? lastExecution.internalId : null;
} }
async lastInternalIds(itemCount = 50) { async lastInternalIds(itemCount = 50) {
@@ -134,15 +112,12 @@ class Flow extends Base {
return lastExecutions.map((execution) => execution.internalId); return lastExecutions.map((execution) => execution.internalId);
} }
async $beforeUpdate( async $beforeUpdate(opt, queryContext) {
opt: ModelOptions,
queryContext: QueryContext
): Promise<void> {
await super.$beforeUpdate(opt, queryContext); await super.$beforeUpdate(opt, queryContext);
if (!this.active) return; if (!this.active) return;
const oldFlow = opt.old as Flow; const oldFlow = opt.old;
const incompleteStep = await oldFlow.$relatedQuery('steps').findOne({ const incompleteStep = await oldFlow.$relatedQuery('steps').findOne({
status: 'incomplete', status: 'incomplete',
@@ -168,17 +143,17 @@ class Flow extends Base {
return; return;
} }
async $afterInsert(queryContext: QueryContext) { async $afterInsert(queryContext) {
await super.$afterInsert(queryContext); await super.$afterInsert(queryContext);
Telemetry.flowCreated(this); Telemetry.flowCreated(this);
} }
async $afterUpdate(opt: ModelOptions, queryContext: QueryContext) { async $afterUpdate(opt, queryContext) {
await super.$afterUpdate(opt, queryContext); await super.$afterUpdate(opt, queryContext);
Telemetry.flowUpdated(this); Telemetry.flowUpdated(this);
} }
async getTriggerStep(): Promise<Step> { async getTriggerStep() {
return await this.$relatedQuery('steps').findOne({ return await this.$relatedQuery('steps').findOne({
type: 'trigger', type: 'trigger',
}); });

View File

@@ -3,22 +3,11 @@ import SamlAuthProvider from './saml-auth-provider.ee';
import User from './user'; import User from './user';
class Identity extends Base { class Identity extends Base {
id!: string;
remoteId!: string;
userId!: string;
providerId!: string;
providerType!: 'saml';
static tableName = 'identities'; static tableName = 'identities';
static jsonSchema = { static jsonSchema = {
type: 'object', type: 'object',
required: [ required: ['providerId', 'remoteId', 'userId', 'providerType'],
'providerId',
'remoteId',
'userId',
'providerType',
],
properties: { properties: {
id: { type: 'string', format: 'uuid' }, id: { type: 'string', format: 'uuid' },
@@ -43,11 +32,10 @@ class Identity extends Base {
modelClass: SamlAuthProvider, modelClass: SamlAuthProvider,
join: { join: {
from: 'saml_auth_providers.id', from: 'saml_auth_providers.id',
to: 'identities.provider_id' to: 'identities.provider_id',
}, },
}, },
}); });
} }
export default Identity; export default Identity;

View File

@@ -1,12 +1,6 @@
import Base from './base'; import Base from './base';
class Permission extends Base { class Permission extends Base {
id: string;
roleId: string;
action: string;
subject: string;
conditions: string[];
static tableName = 'permissions'; static tableName = 'permissions';
static jsonSchema = { static jsonSchema = {

View File

@@ -0,0 +1,58 @@
import { Model } from 'objection';
const DELETED_COLUMN_NAME = 'deleted_at';
const supportsSoftDeletion = (modelClass) => {
return modelClass.jsonSchema.properties.deletedAt;
};
const buildQueryBuidlerForClass = () => {
return (modelClass) => {
const qb = Model.QueryBuilder.forClass.call(
ExtendedQueryBuilder,
modelClass
);
qb.onBuild((builder) => {
if (
!builder.context().withSoftDeleted &&
supportsSoftDeletion(qb.modelClass())
) {
builder.whereNull(
`${qb.modelClass().tableName}.${DELETED_COLUMN_NAME}`
);
}
});
return qb;
};
};
class ExtendedQueryBuilder extends Model.QueryBuilder {
static forClass = buildQueryBuidlerForClass();
delete() {
if (supportsSoftDeletion(this.modelClass())) {
return this.patch({
[DELETED_COLUMN_NAME]: new Date().toISOString(),
});
}
return super.delete();
}
hardDelete() {
return super.delete();
}
withSoftDeleted() {
this.context().withSoftDeleted = true;
return this;
}
restore() {
return this.patch({
[DELETED_COLUMN_NAME]: null,
});
}
}
export default ExtendedQueryBuilder;

View File

@@ -1,71 +0,0 @@
import {
Model,
Page,
ModelClass,
PartialModelObject,
ForClassMethod,
AnyQueryBuilder,
} from 'objection';
const DELETED_COLUMN_NAME = 'deleted_at';
const supportsSoftDeletion = (modelClass: ModelClass<any>) => {
return modelClass.jsonSchema.properties.deletedAt;
}
const buildQueryBuidlerForClass = (): ForClassMethod => {
return (modelClass) => {
const qb: AnyQueryBuilder = Model.QueryBuilder.forClass.call(
ExtendedQueryBuilder,
modelClass
);
qb.onBuild((builder) => {
if (!builder.context().withSoftDeleted && supportsSoftDeletion(qb.modelClass())) {
builder.whereNull(
`${qb.modelClass().tableName}.${DELETED_COLUMN_NAME}`
);
}
});
return qb;
};
};
class ExtendedQueryBuilder<M extends Model, R = M[]> extends Model.QueryBuilder<
M,
R
> {
ArrayQueryBuilderType!: ExtendedQueryBuilder<M, M[]>;
SingleQueryBuilderType!: ExtendedQueryBuilder<M, M>;
MaybeSingleQueryBuilderType!: ExtendedQueryBuilder<M, M | undefined>;
NumberQueryBuilderType!: ExtendedQueryBuilder<M, number>;
PageQueryBuilderType!: ExtendedQueryBuilder<M, Page<M>>;
static forClass: ForClassMethod = buildQueryBuidlerForClass();
delete() {
if (supportsSoftDeletion(this.modelClass())) {
return this.patch({
[DELETED_COLUMN_NAME]: new Date().toISOString(),
} as unknown as PartialModelObject<M>);
}
return super.delete();
}
hardDelete() {
return super.delete();
}
withSoftDeleted() {
this.context().withSoftDeleted = true;
return this;
}
restore() {
return this.patch({
[DELETED_COLUMN_NAME]: null,
} as unknown as PartialModelObject<M>);
}
}
export default ExtendedQueryBuilder;

View File

@@ -3,13 +3,6 @@ import Permission from './permission';
import User from './user'; import User from './user';
class Role extends Base { class Role extends Base {
id!: string;
name!: string;
key: string;
description: string;
users?: User[];
permissions?: Permission[];
static tableName = 'roles'; static tableName = 'roles';
static jsonSchema = { static jsonSchema = {

View File

@@ -1,25 +1,10 @@
import { URL } from 'node:url'; import { URL } from 'node:url';
import type { SamlConfig } from '@node-saml/passport-saml';
import appConfig from '../config/app'; import appConfig from '../config/app';
import Base from './base'; import Base from './base';
import Identity from './identity.ee'; import Identity from './identity.ee';
import SamlAuthProvidersRoleMapping from './saml-auth-providers-role-mapping.ee'; import SamlAuthProvidersRoleMapping from './saml-auth-providers-role-mapping.ee';
class SamlAuthProvider extends Base { class SamlAuthProvider extends Base {
id!: string;
name: string;
certificate: string;
signatureAlgorithm: SamlConfig['signatureAlgorithm'];
issuer: string;
entryPoint: string;
firstnameAttributeName: string;
surnameAttributeName: string;
emailAttributeName: string;
roleAttributeName: string;
defaultRoleId: string;
active: boolean;
samlAuthProvidersRoleMappings?: SamlAuthProvidersRoleMapping[];
static tableName = 'saml_auth_providers'; static tableName = 'saml_auth_providers';
static jsonSchema = { static jsonSchema = {
@@ -83,7 +68,7 @@ class SamlAuthProvider extends Base {
return new URL(`/login/saml/${this.issuer}`, appConfig.baseUrl).toString(); return new URL(`/login/saml/${this.issuer}`, appConfig.baseUrl).toString();
} }
get config(): SamlConfig { get config() {
const callbackUrl = new URL( const callbackUrl = new URL(
`/login/saml/${this.issuer}/callback`, `/login/saml/${this.issuer}/callback`,
appConfig.baseUrl appConfig.baseUrl

View File

@@ -2,11 +2,6 @@ import Base from './base';
import SamlAuthProvider from './saml-auth-provider.ee'; import SamlAuthProvider from './saml-auth-provider.ee';
class SamlAuthProvidersRoleMapping extends Base { class SamlAuthProvidersRoleMapping extends Base {
id!: string;
samlAuthProviderId: string;
roleId: string;
remoteRoleName: string;
static tableName = 'saml_auth_providers_role_mappings'; static tableName = 'saml_auth_providers_role_mappings';
static jsonSchema = { static jsonSchema = {

View File

@@ -1,7 +1,5 @@
import { URL } from 'node:url'; import { URL } from 'node:url';
import { QueryContext, ModelOptions } from 'objection';
import get from 'lodash.get'; import get from 'lodash.get';
import type { IJSONObject, IStep } from '@automatisch/types';
import Base from './base'; import Base from './base';
import App from './app'; import App from './app';
import Flow from './flow'; import Flow from './flow';
@@ -11,20 +9,6 @@ import Telemetry from '../helpers/telemetry';
import appConfig from '../config/app'; import appConfig from '../config/app';
class Step extends Base { class Step extends Base {
id!: string;
flowId!: string;
key?: string;
appKey?: string;
type!: IStep['type'];
connectionId?: string;
status: 'incomplete' | 'completed';
position!: number;
parameters: IJSONObject;
connection?: Connection;
flow: Flow;
executionSteps: ExecutionStep[];
webhookPath?: string;
static tableName = 'steps'; static tableName = 'steps';
static jsonSchema = { static jsonSchema = {
@@ -100,18 +84,18 @@ class Step extends Base {
if (!triggerCommand) return null; if (!triggerCommand) return null;
const { const { useSingletonWebhook, singletonWebhookRefValueParameter, type } =
useSingletonWebhook, triggerCommand;
singletonWebhookRefValueParameter,
type,
} = triggerCommand;
const isWebhook = type === 'webhook'; const isWebhook = type === 'webhook';
if (!isWebhook) return null; if (!isWebhook) return null;
if (singletonWebhookRefValueParameter) { if (singletonWebhookRefValueParameter) {
const parameterValue = get(this.parameters, singletonWebhookRefValueParameter); const parameterValue = get(
this.parameters,
singletonWebhookRefValueParameter
);
return `/webhooks/connections/${this.connectionId}/${parameterValue}`; return `/webhooks/connections/${this.connectionId}/${parameterValue}`;
} }
@@ -131,21 +115,21 @@ class Step extends Base {
return webhookUrl; return webhookUrl;
} }
async $afterInsert(queryContext: QueryContext) { async $afterInsert(queryContext) {
await super.$afterInsert(queryContext); await super.$afterInsert(queryContext);
Telemetry.stepCreated(this); Telemetry.stepCreated(this);
} }
async $afterUpdate(opt: ModelOptions, queryContext: QueryContext) { async $afterUpdate(opt, queryContext) {
await super.$afterUpdate(opt, queryContext); await super.$afterUpdate(opt, queryContext);
Telemetry.stepUpdated(this); Telemetry.stepUpdated(this);
} }
get isTrigger(): boolean { get isTrigger() {
return this.type === 'trigger'; return this.type === 'trigger';
} }
get isAction(): boolean { get isAction() {
return this.type === 'action'; return this.type === 'action';
} }

View File

@@ -5,20 +5,6 @@ import { DateTime } from 'luxon';
import { getPlanById } from '../helpers/billing/plans.ee'; import { getPlanById } from '../helpers/billing/plans.ee';
class Subscription extends Base { class Subscription extends Base {
id!: string;
userId!: string;
paddleSubscriptionId!: string;
paddlePlanId!: string;
updateUrl!: string;
cancelUrl!: string;
status!: string;
nextBillAmount!: string;
nextBillDate!: string;
lastBillDate?: string;
cancellationEffectiveDate?: string;
usageData?: UsageData[];
currentUsageData?: UsageData;
static tableName = 'subscriptions'; static tableName = 'subscriptions';
static jsonSchema = { static jsonSchema = {
@@ -87,7 +73,7 @@ class Subscription extends Base {
return ( return (
this.status === 'deleted' && this.status === 'deleted' &&
Number(this.cancellationEffectiveDate) > Number(this.cancellationEffectiveDate) >
DateTime.now().startOf('day').toMillis() DateTime.now().startOf('day').toMillis()
); );
} }

View File

@@ -4,14 +4,6 @@ import User from './user';
import Subscription from './subscription.ee'; import Subscription from './subscription.ee';
class UsageData extends Base { class UsageData extends Base {
id!: string;
userId!: string;
subscriptionId?: string;
consumedTaskCount!: number;
nextResetAt!: string;
subscription?: Subscription;
user?: User;
static tableName = 'usage_data'; static tableName = 'usage_data';
static jsonSchema = { static jsonSchema = {

View File

@@ -1,7 +1,6 @@
import bcrypt from 'bcrypt'; import bcrypt from 'bcrypt';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import crypto from 'node:crypto'; import crypto from 'node:crypto';
import { ModelOptions, QueryContext } from 'objection';
import appConfig from '../config/app'; import appConfig from '../config/app';
import { hasValidLicense } from '../helpers/license.ee'; import { hasValidLicense } from '../helpers/license.ee';
@@ -12,33 +11,12 @@ import Execution from './execution';
import Flow from './flow'; import Flow from './flow';
import Identity from './identity.ee'; import Identity from './identity.ee';
import Permission from './permission'; import Permission from './permission';
import ExtendedQueryBuilder from './query-builder';
import Role from './role'; import Role from './role';
import Step from './step'; import Step from './step';
import Subscription from './subscription.ee'; import Subscription from './subscription.ee';
import UsageData from './usage-data.ee'; import UsageData from './usage-data.ee';
class User extends Base { class User extends Base {
id!: string;
fullName!: string;
email!: string;
roleId: string;
password!: string;
resetPasswordToken: string;
resetPasswordTokenSentAt: string;
trialExpiryDate: string;
connections?: Connection[];
flows?: Flow[];
steps?: Step[];
executions?: Execution[];
usageData?: UsageData[];
currentUsageData?: UsageData;
subscriptions?: Subscription[];
currentSubscription?: Subscription;
role: Role;
permissions: Permission[];
identities: Identity[];
static tableName = 'users'; static tableName = 'users';
static jsonSchema = { static jsonSchema = {
@@ -116,7 +94,7 @@ class User extends Base {
from: 'usage_data.user_id', from: 'usage_data.user_id',
to: 'users.id', to: 'users.id',
}, },
filter(builder: ExtendedQueryBuilder<UsageData>) { filter(builder) {
builder.orderBy('created_at', 'desc').limit(1).first(); builder.orderBy('created_at', 'desc').limit(1).first();
}, },
}, },
@@ -135,7 +113,7 @@ class User extends Base {
from: 'subscriptions.user_id', from: 'subscriptions.user_id',
to: 'users.id', to: 'users.id',
}, },
filter(builder: ExtendedQueryBuilder<Subscription>) { filter(builder) {
builder.orderBy('created_at', 'desc').limit(1).first(); builder.orderBy('created_at', 'desc').limit(1).first();
}, },
}, },
@@ -165,7 +143,7 @@ class User extends Base {
}, },
}); });
login(password: string) { login(password) {
return bcrypt.compare(password, this.password); return bcrypt.compare(password, this.password);
} }
@@ -176,7 +154,7 @@ class User extends Base {
await this.$query().patch({ resetPasswordToken, resetPasswordTokenSentAt }); await this.$query().patch({ resetPasswordToken, resetPasswordTokenSentAt });
} }
async resetPassword(password: string) { async resetPassword(password) {
return await this.$query().patch({ return await this.$query().patch({
resetPasswordToken: null, resetPasswordToken: null,
resetPasswordTokenSentAt: null, resetPasswordTokenSentAt: null,
@@ -235,9 +213,7 @@ class User extends Base {
return false; return false;
} }
const expiryDate = DateTime.fromJSDate( const expiryDate = DateTime.fromJSDate(this.trialExpiryDate);
this.trialExpiryDate as unknown as Date
);
const now = DateTime.now(); const now = DateTime.now();
return now < expiryDate; return now < expiryDate;
@@ -261,7 +237,7 @@ class User extends Base {
return currentUsageData.consumedTaskCount < plan.quota; return currentUsageData.consumedTaskCount < plan.quota;
} }
async $beforeInsert(queryContext: QueryContext) { async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext); await super.$beforeInsert(queryContext);
this.email = this.email.toLowerCase(); this.email = this.email.toLowerCase();
@@ -272,7 +248,7 @@ class User extends Base {
} }
} }
async $beforeUpdate(opt: ModelOptions, queryContext: QueryContext) { async $beforeUpdate(opt, queryContext) {
await super.$beforeUpdate(opt, queryContext); await super.$beforeUpdate(opt, queryContext);
if (this.email) { if (this.email) {
@@ -282,7 +258,7 @@ class User extends Base {
await this.generateHash(); await this.generateHash();
} }
async $afterInsert(queryContext: QueryContext) { async $afterInsert(queryContext) {
await super.$afterInsert(queryContext); await super.$afterInsert(queryContext);
if (appConfig.isCloud) { if (appConfig.isCloud) {
@@ -294,7 +270,7 @@ class User extends Base {
} }
} }
async $afterFind(): Promise<any> { async $afterFind() {
if (await hasValidLicense()) return this; if (await hasValidLicense()) return this;
if (Array.isArray(this.permissions)) { if (Array.isArray(this.permissions)) {
@@ -313,26 +289,26 @@ class User extends Base {
return this; return this;
} }
get ability(): ReturnType<typeof userAbility> { get ability() {
return userAbility(this); return userAbility(this);
} }
can(action: string, subject: string) { can(action, subject) {
const can = this.ability.can(action, subject); const can = this.ability.can(action, subject);
if (!can) throw new Error('Not authorized!'); if (!can) throw new Error('Not authorized!');
const relevantRule = this.ability.relevantRuleFor(action, subject); const relevantRule = this.ability.relevantRuleFor(action, subject);
const conditions = (relevantRule?.conditions as string[]) || []; const conditions = relevantRule?.conditions || [];
const conditionMap: Record<string, true> = Object.fromEntries( const conditionMap = Object.fromEntries(
conditions.map((condition) => [condition, true]) conditions.map((condition) => [condition, true])
); );
return conditionMap; return conditionMap;
} }
cannot(action: string, subject: string) { cannot(action, subject) {
const cannot = this.ability.cannot(action, subject); const cannot = this.ability.cannot(action, subject);
if (cannot) throw new Error('Not authorized!'); if (cannot) throw new Error('Not authorized!');