da7f0dda0e
* feat(gatekeeper): add gatekeeper module feature flag * feat(gatekeeper): add workspace pricing table domain * feat(gatekeeper): add checkout session creation * feat(gatekeeper): verify stripe signature * wip(gatekeeper): checkout callbacks * feat(gatekeeper): add unlimited and academia plan types * refactor(envHelper): getStringFromEnv helper * chore(gatekeeper): add future todos * feat(gatekeeper): add productId to the subscription domain * feat(gatekeeper): add in memory repositories * feat(gatekeeper): add more errors * feat(gatekeeper): complete checkout session service * feat(gatekeeper): add stripe client implementation * feat(gatekeeper): add checkout session completion webhook callback path * feat(gendo): fix not needing env vars if gendo module is not enabled * feat(gatekeeper): require a license for billing * chore(gatekeeper): cleanup before testing * feat(gatekeeper): subscriptionData parsing model * ci: add billing integration and gatekeeper modules to test config * test(gatekeeper): add checkout service tests * feat(gatekeeper): make completeCheckout callback idempotent properly * feat(gatekeeper): move to knex based repositories * test(gatekeeper): billing repository tests * feat(gatekeeper): add yearly billing cycle toggle * feat(ci): add stripe integration context to test job * feat(billingPage): conditionally render the checkout CTAs * fix(gatekeeper): remove flaky test condition * feat(helm): add billing integration feature flag * WIP billing gql api * feat(gatekeeper): cancel checkout session api * feat(gatekeeper): handle existing checkout sessions, when trying to create a new one * feat(gatekeeper): add workspace plans gql api * feat(gatekeeper): handle cancelation and subscription updates * fix(gatekeeper): scope initialization * fix(gatekeeper): eliminate stripe client import sideeffect * fix(gatekeeper): eliminate stripe client import sideeffect 2 * feat(gatekeeper): upsize subscription on workspace role change * feat(shared): add command pattern implementation * refactor(eventBus): remove return capabilities from the event bus * refactor(workspaces): use new commandFactory in workspace resolver * feat(core): facelift taskLock * feat(gatekeeper): shedule subscription downscale * feat(gatekeeper): manage subscription downscale * feat(gatekeeper): get workspace subscriptions, that are about to expire * feat(gatekeeper): manage subscription downscale * fix(gatekeeper): do not update subscription to canceled subs * ci: bump postgres and max connections * feat(workspaces): fix command factory event bugs
82 lines
2.4 KiB
TypeScript
82 lines
2.4 KiB
TypeScript
import cron from 'node-cron'
|
|
import { InvalidArgumentError } from '@/modules/shared/errors'
|
|
import { ensureError } from '@/modules/shared/helpers/errorHelper'
|
|
import { logger } from '@/logging/logging'
|
|
import {
|
|
AcquireTaskLock,
|
|
ReleaseTaskLock,
|
|
ScheduleExecution
|
|
} from '@/modules/core/domain/scheduledTasks/operations'
|
|
|
|
export const scheduledCallbackWrapper = async (
|
|
scheduledTime: Date,
|
|
taskName: string,
|
|
lockTimeout: number,
|
|
callback: (scheduledTime: Date) => Promise<void>,
|
|
acquireLock: AcquireTaskLock,
|
|
releaseTaskLock: ReleaseTaskLock
|
|
) => {
|
|
const boundLogger = logger.child({ taskName })
|
|
// try to acquire the task lock with the function name and a new expiration date
|
|
const lockExpiresAt = new Date(scheduledTime.getTime() + lockTimeout)
|
|
const lock = await acquireLock({ taskName, lockExpiresAt })
|
|
|
|
// if couldn't acquire it, stop execution
|
|
if (!lock) {
|
|
boundLogger.warn(`Could not acquire task lock for ${taskName}, stopping execution.`)
|
|
return
|
|
}
|
|
try {
|
|
// else continue executing the callback...
|
|
boundLogger.info(`Executing scheduled function ${taskName} at ${scheduledTime}`)
|
|
await callback(scheduledTime)
|
|
// update lock as succeeded
|
|
const finishDate = new Date()
|
|
boundLogger.info(
|
|
`Finished scheduled function ${taskName} execution in ${
|
|
(finishDate.getTime() - scheduledTime.getTime()) / 1000
|
|
} seconds`
|
|
)
|
|
} catch (error) {
|
|
boundLogger.error(
|
|
error,
|
|
`The triggered task execution ${taskName} failed at ${scheduledTime}, with error ${
|
|
ensureError(error, 'unknown reason').message
|
|
}`
|
|
)
|
|
} finally {
|
|
releaseTaskLock(lock)
|
|
}
|
|
}
|
|
|
|
export const scheduleExecutionFactory =
|
|
({
|
|
acquireTaskLock,
|
|
releaseTaskLock
|
|
}: {
|
|
acquireTaskLock: AcquireTaskLock
|
|
releaseTaskLock: ReleaseTaskLock
|
|
}): ScheduleExecution =>
|
|
(
|
|
cronExpression: string,
|
|
taskName: string,
|
|
callback: (scheduledTime: Date) => Promise<void>,
|
|
lockTimeout = 60 * 1000
|
|
): cron.ScheduledTask => {
|
|
const expressionValid = cron.validate(cronExpression)
|
|
if (!expressionValid)
|
|
throw new InvalidArgumentError(
|
|
`The given cron expression ${cronExpression} is not valid`
|
|
)
|
|
return cron.schedule(cronExpression, async (scheduledTime: Date) => {
|
|
await scheduledCallbackWrapper(
|
|
scheduledTime,
|
|
taskName,
|
|
lockTimeout,
|
|
callback,
|
|
acquireTaskLock,
|
|
releaseTaskLock
|
|
)
|
|
})
|
|
}
|