diff --git a/packages/backend/src/config/app.ts b/packages/backend/src/config/app.ts index ab7fd430..032b1e53 100644 --- a/packages/backend/src/config/app.ts +++ b/packages/backend/src/config/app.ts @@ -39,6 +39,7 @@ type AppConfig = { smtpPassword: string; fromEmail: string; isCloud: boolean; + isSelfHosted: boolean; paddleVendorId: number; paddleVendorAuthCode: string; paddlePublicKey: string; @@ -110,6 +111,7 @@ const appConfig: AppConfig = { smtpPassword: process.env.SMTP_PASSWORD, fromEmail: process.env.FROM_EMAIL, isCloud: process.env.AUTOMATISCH_CLOUD === 'true', + isSelfHosted: process.env.AUTOMATISCH_CLOUD !== 'true', paddleVendorId: Number(process.env.PADDLE_VENDOR_ID), paddleVendorAuthCode: process.env.PADDLE_VENDOR_AUTH_CODE, paddlePublicKey: process.env.PADDLE_PUBLIC_KEY, diff --git a/packages/backend/src/controllers/webhooks/handler.ts b/packages/backend/src/controllers/webhooks/handler.ts index 5ac8a160..6ef0414f 100644 --- a/packages/backend/src/controllers/webhooks/handler.ts +++ b/packages/backend/src/controllers/webhooks/handler.ts @@ -6,17 +6,24 @@ import Flow from '../../models/flow'; import { processTrigger } from '../../services/trigger'; import actionQueue from '../../queues/action'; import globalVariable from '../../helpers/global-variable'; -import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../../helpers/remove-job-configuration'; +import QuotaExceededError from '../../errors/quote-exceeded'; +import { + REMOVE_AFTER_30_DAYS_OR_150_JOBS, + REMOVE_AFTER_7_DAYS_OR_50_JOBS, +} from '../../helpers/remove-job-configuration'; export default async (request: IRequest, response: Response) => { const flow = await Flow.query() .findById(request.params.flowId) .throwIfNotFound(); - const testRun = !flow.active; + const user = await flow.$relatedQuery('user'); - if (!testRun) { - await flow.throwIfQuotaExceeded(); + const testRun = !flow.active; + const quotaExceeded = !testRun && !(await user.isAllowedToRunFlows()); + + if (quotaExceeded) { + throw new QuotaExceededError(); } const triggerStep = await flow.getTriggerStep(); @@ -58,7 +65,7 @@ export default async (request: IRequest, response: Response) => { headers: request.headers, body: request.body, query: request.query, - } + }; rawInternalId = JSON.stringify(payload); } @@ -74,7 +81,7 @@ export default async (request: IRequest, response: Response) => { flowId: flow.id, stepId: triggerStep.id, triggerItem, - testRun + testRun, }); if (testRun) { @@ -93,7 +100,7 @@ export default async (request: IRequest, response: Response) => { const jobOptions = { removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS, removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS, - } + }; await actionQueue.add(jobName, jobPayload, jobOptions); diff --git a/packages/backend/src/models/flow.ts b/packages/backend/src/models/flow.ts index f9e1b191..2c8a06b3 100644 --- a/packages/backend/src/models/flow.ts +++ b/packages/backend/src/models/flow.ts @@ -1,13 +1,15 @@ import { ValidationError } from 'objection'; -import type { ModelOptions, QueryContext, StaticHookArguments } from 'objection'; -import appConfig from '../config/app'; +import type { + ModelOptions, + QueryContext, + StaticHookArguments, +} from 'objection'; import ExtendedQueryBuilder from './query-builder'; import Base from './base'; import Step from './step'; import User from './user'; import Execution from './execution'; import Telemetry from '../helpers/telemetry'; -import QuotaExceededError from '../errors/quote-exceeded'; class Flow extends Base { id!: string; @@ -155,36 +157,7 @@ class Flow extends Base { async isPaused() { const user = await this.$relatedQuery('user'); - const currentUsageData = await user.$relatedQuery('currentUsageData'); - - return await currentUsageData.checkIfLimitExceeded(); - } - - async checkIfQuotaExceeded() { - if (!appConfig.isCloud) return; - - const user = await this.$relatedQuery('user'); - const usageData = await user.$relatedQuery('currentUsageData'); - - const hasExceeded = await usageData.checkIfLimitExceeded(); - - if (hasExceeded) { - return true; - } - - return false; - } - - async throwIfQuotaExceeded() { - if (!appConfig.isCloud) return; - - const hasExceeded = await this.checkIfQuotaExceeded(); - - if (hasExceeded) { - throw new QuotaExceededError(); - } - - return this; + return await user.isAllowedToRunFlows(); } } diff --git a/packages/backend/src/models/usage-data.ee.ts b/packages/backend/src/models/usage-data.ee.ts index 6ad13093..6cae3e56 100644 --- a/packages/backend/src/models/usage-data.ee.ts +++ b/packages/backend/src/models/usage-data.ee.ts @@ -2,7 +2,6 @@ import { raw } from 'objection'; import Base from './base'; import User from './user'; import Subscription from './subscription.ee'; -import { getPlanById } from '../helpers/billing/plans.ee'; class UsageData extends Base { id!: string; @@ -47,28 +46,6 @@ class UsageData extends Base { }, }); - async checkIfLimitExceeded() { - const user = await this.$relatedQuery('user'); - - if (await user.inTrial()) { - return false; - } - - const subscription = await this.$relatedQuery('subscription'); - - if (!subscription) { - return true; - } - - if (!subscription.isActive) { - return true; - } - - const plan = subscription.plan; - - return this.consumedTaskCount >= plan.quota; - } - async increaseConsumedTaskCountByOne() { return await this.$query().patch({ consumedTaskCount: raw('consumed_task_count + 1'), diff --git a/packages/backend/src/models/user.ts b/packages/backend/src/models/user.ts index daba8d74..2e17203e 100644 --- a/packages/backend/src/models/user.ts +++ b/packages/backend/src/models/user.ts @@ -165,18 +165,24 @@ class User extends Base { this.trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate(); } - async hasActiveSubscription() { - if (!appConfig.isCloud) { - return false; + async isAllowedToRunFlows() { + if (appConfig.isSelfHosted) { + return true; } - const subscription = await this.$relatedQuery('currentSubscription'); + if (await this.inTrial()) { + return true; + } - return subscription?.isActive; + if ((await this.hasActiveSubscription()) && (await this.withinLimits())) { + return true; + } + + return false; } async inTrial() { - if (!appConfig.isCloud) { + if (appConfig.isSelfHosted) { return false; } @@ -196,6 +202,24 @@ class User extends Base { return now < expiryDate; } + async hasActiveSubscription() { + if (!appConfig.isCloud) { + return false; + } + + const subscription = await this.$relatedQuery('currentSubscription'); + + return subscription?.isActive; + } + + async withinLimits() { + const currentSubscription = await this.$relatedQuery('currentSubscription'); + const plan = currentSubscription.plan; + const currentUsageData = await this.$relatedQuery('currentUsageData'); + + return currentUsageData.consumedTaskCount >= plan.quota; + } + async $beforeInsert(queryContext: QueryContext) { await super.$beforeInsert(queryContext); await this.generateHash(); diff --git a/packages/backend/src/workers/flow.ts b/packages/backend/src/workers/flow.ts index 884be7d8..49ced1f8 100644 --- a/packages/backend/src/workers/flow.ts +++ b/packages/backend/src/workers/flow.ts @@ -17,10 +17,10 @@ export const worker = new Worker( const { flowId } = job.data; const flow = await Flow.query().findById(flowId).throwIfNotFound(); + const user = await flow.$relatedQuery('user'); + const allowedToRunFlows = await user.isAllowedToRunFlows(); - const quotaExceeded = await flow.checkIfQuotaExceeded(); - - if (quotaExceeded) { + if (!allowedToRunFlows) { return; }