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 { ModelOptions, QueryContext } from 'objection';
import appConfig from '../config/app';
import AppConfig from './app-config';
import Base from './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 jsonSchema = {
@@ -42,7 +32,7 @@ class AppAuthClient extends Base {
},
});
encryptData(): void {
encryptData() {
if (!this.eligibleForEncryption()) return;
this.authDefaults = AES.encrypt(
@@ -52,7 +42,7 @@ class AppAuthClient extends Base {
delete this.formattedAuthDefaults;
}
decryptData(): void {
decryptData() {
if (!this.eligibleForDecryption()) return;
this.formattedAuthDefaults = JSON.parse(
@@ -60,30 +50,27 @@ class AppAuthClient extends Base {
);
}
eligibleForEncryption(): boolean {
eligibleForEncryption() {
return this.formattedAuthDefaults ? true : false;
}
eligibleForDecryption(): boolean {
eligibleForDecryption() {
return this.authDefaults ? true : false;
}
// TODO: Make another abstraction like beforeSave instead of using
// beforeInsert and beforeUpdate separately for the same operation.
async $beforeInsert(queryContext: QueryContext): Promise<void> {
async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext);
this.encryptData();
}
async $beforeUpdate(
opt: ModelOptions,
queryContext: QueryContext
): Promise<void> {
async $beforeUpdate(opt, queryContext) {
await super.$beforeUpdate(opt, queryContext);
this.encryptData();
}
async $afterFind(): Promise<void> {
async $afterFind() {
this.decryptData();
}
}

View File

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

View File

@@ -1,6 +1,5 @@
import fs from 'fs';
import { join } from 'path';
import { IApp } from '@automatisch/types';
import appInfoConverter from '../helpers/app-info-converter';
import getApp from '../helpers/get-app';
@@ -10,7 +9,7 @@ class App {
.readdirSync(this.folderPath)
.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)
return Promise.all(
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);
return appInfoConverter(rawAppData);
}
static async findOneByKey(key: string, stripFuncs = false): Promise<IApp> {
static async findOneByKey(key, stripFuncs = false) {
const rawAppData = await getApp(key, stripFuncs);
return appInfoConverter(rawAppData);
}
static async checkAppAndAction(appKey: string, actionKey: string): Promise<void> {
static async checkAppAndAction(appKey, actionKey) {
const app = await this.findOneByKey(appKey);
if (!actionKey) return;
const hasAction = app.actions?.find(action => action.key === actionKey);
const hasAction = app.actions?.find((action) => action.key === actionKey);
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);
if (!triggerKey) return;
const hasTrigger = app.triggers?.find(trigger => trigger.key === triggerKey);
const hasTrigger = app.triggers?.find(
(trigger) => trigger.key === triggerKey
);
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 type { QueryContext, ModelOptions, ColumnNameMappers } from 'objection';
import addFormats from 'ajv-formats';
import ExtendedQueryBuilder from './query-builder';
class Base extends Model {
createdAt!: string;
updatedAt!: string;
deletedAt: string;
QueryBuilderType!: ExtendedQueryBuilder<this>;
static QueryBuilder = ExtendedQueryBuilder;
static get columnNameMappers(): ColumnNameMappers {
static get columnNameMappers() {
return snakeCaseMappers();
}
@@ -29,17 +23,14 @@ class Base extends Model {
});
}
async $beforeInsert(queryContext: QueryContext): Promise<void> {
async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext);
this.createdAt = new Date().toISOString();
this.updatedAt = new Date().toISOString();
}
async $beforeUpdate(
opts: ModelOptions,
queryContext: QueryContext
): Promise<void> {
async $beforeUpdate(opts, queryContext) {
this.updatedAt = new Date().toISOString();
await super.$beforeUpdate(opts, queryContext);

View File

@@ -1,11 +1,6 @@
import { IJSONValue } from '@automatisch/types';
import Base from './base';
class Config extends Base {
id!: string;
key!: string;
value!: { data: IJSONValue };
static tableName = 'config';
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 { IRequest } from '@automatisch/types';
import App from './app';
import AppConfig from './app-config';
import AppAuthClient from './app-auth-client';
import Base from './base';
import User from './user';
import Step from './step';
import ExtendedQueryBuilder from './query-builder';
import appConfig from '../config/app';
import { IJSONObject } from '@automatisch/types';
import Telemetry from '../helpers/telemetry';
import globalVariable from '../helpers/global-variable';
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 jsonSchema = {
@@ -56,7 +35,7 @@ class Connection extends Base {
return ['reconnectable'];
}
static relationMappings = (): RelationMappings => ({
static relationMappings = () => ({
user: {
relation: Base.BelongsToOneRelation,
modelClass: User,
@@ -80,7 +59,7 @@ class Connection extends Base {
from: 'connections.id',
to: 'steps.connection_id',
},
filter(builder: ExtendedQueryBuilder<Step>) {
filter(builder) {
builder.where('type', '=', 'trigger');
},
},
@@ -114,7 +93,7 @@ class Connection extends Base {
return true;
}
encryptData(): void {
encryptData() {
if (!this.eligibleForEncryption()) return;
this.data = AES.encrypt(
@@ -125,7 +104,7 @@ class Connection extends Base {
delete this.formattedData;
}
decryptData(): void {
decryptData() {
if (!this.eligibleForDecryption()) return;
this.formattedData = JSON.parse(
@@ -133,39 +112,36 @@ class Connection extends Base {
);
}
eligibleForEncryption(): boolean {
eligibleForEncryption() {
return this.formattedData ? true : false;
}
eligibleForDecryption(): boolean {
eligibleForDecryption() {
return this.data ? true : false;
}
// TODO: Make another abstraction like beforeSave instead of using
// beforeInsert and beforeUpdate separately for the same operation.
async $beforeInsert(queryContext: QueryContext): Promise<void> {
async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext);
this.encryptData();
}
async $beforeUpdate(
opt: ModelOptions,
queryContext: QueryContext
): Promise<void> {
async $beforeUpdate(opt, queryContext) {
await super.$beforeUpdate(opt, queryContext);
this.encryptData();
}
async $afterFind(): Promise<void> {
async $afterFind() {
this.decryptData();
}
async $afterInsert(queryContext: QueryContext) {
async $afterInsert(queryContext) {
await super.$afterInsert(queryContext);
Telemetry.connectionCreated(this);
}
async $afterUpdate(opt: ModelOptions, queryContext: QueryContext) {
async $afterUpdate(opt, queryContext) {
await super.$afterUpdate(opt, queryContext);
Telemetry.connectionUpdated(this);
}
@@ -176,7 +152,7 @@ class Connection extends Base {
return await App.findOneByKey(this.key);
}
async verifyWebhook(request: IRequest) {
async verifyWebhook(request) {
if (!this.key) return true;
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 Base from './base';
import Execution from './execution';
@@ -7,17 +5,6 @@ import Step from './step';
import Telemetry from '../helpers/telemetry';
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 jsonSchema = {
@@ -60,7 +47,7 @@ class ExecutionStep extends Base {
return this.status === 'failure';
}
async $afterInsert(queryContext: QueryContext) {
async $afterInsert(queryContext) {
await super.$afterInsert(queryContext);
Telemetry.executionStepCreated(this);

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,6 @@
import Base from './base';
class Permission extends Base {
id: string;
roleId: string;
action: string;
subject: string;
conditions: string[];
static tableName = 'permissions';
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';
class Role extends Base {
id!: string;
name!: string;
key: string;
description: string;
users?: User[];
permissions?: Permission[];
static tableName = 'roles';
static jsonSchema = {

View File

@@ -1,25 +1,10 @@
import { URL } from 'node:url';
import type { SamlConfig } from '@node-saml/passport-saml';
import appConfig from '../config/app';
import Base from './base';
import Identity from './identity.ee';
import SamlAuthProvidersRoleMapping from './saml-auth-providers-role-mapping.ee';
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 jsonSchema = {
@@ -83,7 +68,7 @@ class SamlAuthProvider extends Base {
return new URL(`/login/saml/${this.issuer}`, appConfig.baseUrl).toString();
}
get config(): SamlConfig {
get config() {
const callbackUrl = new URL(
`/login/saml/${this.issuer}/callback`,
appConfig.baseUrl

View File

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

View File

@@ -1,7 +1,5 @@
import { URL } from 'node:url';
import { QueryContext, ModelOptions } from 'objection';
import get from 'lodash.get';
import type { IJSONObject, IStep } from '@automatisch/types';
import Base from './base';
import App from './app';
import Flow from './flow';
@@ -11,20 +9,6 @@ import Telemetry from '../helpers/telemetry';
import appConfig from '../config/app';
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 jsonSchema = {
@@ -100,18 +84,18 @@ class Step extends Base {
if (!triggerCommand) return null;
const {
useSingletonWebhook,
singletonWebhookRefValueParameter,
type,
} = triggerCommand;
const { useSingletonWebhook, singletonWebhookRefValueParameter, type } =
triggerCommand;
const isWebhook = type === 'webhook';
if (!isWebhook) return null;
if (singletonWebhookRefValueParameter) {
const parameterValue = get(this.parameters, singletonWebhookRefValueParameter);
const parameterValue = get(
this.parameters,
singletonWebhookRefValueParameter
);
return `/webhooks/connections/${this.connectionId}/${parameterValue}`;
}
@@ -131,21 +115,21 @@ class Step extends Base {
return webhookUrl;
}
async $afterInsert(queryContext: QueryContext) {
async $afterInsert(queryContext) {
await super.$afterInsert(queryContext);
Telemetry.stepCreated(this);
}
async $afterUpdate(opt: ModelOptions, queryContext: QueryContext) {
async $afterUpdate(opt, queryContext) {
await super.$afterUpdate(opt, queryContext);
Telemetry.stepUpdated(this);
}
get isTrigger(): boolean {
get isTrigger() {
return this.type === 'trigger';
}
get isAction(): boolean {
get isAction() {
return this.type === 'action';
}

View File

@@ -5,20 +5,6 @@ import { DateTime } from 'luxon';
import { getPlanById } from '../helpers/billing/plans.ee';
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 jsonSchema = {
@@ -87,7 +73,7 @@ class Subscription extends Base {
return (
this.status === 'deleted' &&
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';
class UsageData extends Base {
id!: string;
userId!: string;
subscriptionId?: string;
consumedTaskCount!: number;
nextResetAt!: string;
subscription?: Subscription;
user?: User;
static tableName = 'usage_data';
static jsonSchema = {

View File

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