From 0df08400e192e207eba1948877c7f5f69a6418a8 Mon Sep 17 00:00:00 2001 From: Manoj K Date: Sat, 8 Feb 2025 15:48:13 +0530 Subject: [PATCH] Fix: add due date, remind at in task --- .env | 15 + apps/server/package.json | 1 + .../migration.sql | 3 + apps/server/prisma/schema.prisma | 2 + apps/server/src/app.module.ts | 2 + apps/server/src/modules/tasks/tasks.module.ts | 2 + .../server/src/modules/tasks/tasks.service.ts | 16 + .../src/modules/triggerdev/trigger.utils.ts | 170 +++++++ .../triggerdev/triggerdev.controller.ts | 25 + .../triggerdev/triggerdev.interface.ts | 4 + .../modules/triggerdev/triggerdev.module.ts | 15 + .../modules/triggerdev/triggerdev.service.ts | 440 ++++++++++++++++++ .../modules/triggerdev/triggerdev.utils.ts | 205 ++++++++ .../server/src/trigger/tasks/beautify-task.ts | 60 +++ apps/server/trigger.config.ts | 1 + packages/services/src/task/get-ai-tasks.ts | 12 + packages/services/src/task/index.ts | 1 + .../action-schedule/action-schedule.dto.ts | 17 + .../action-schedule/action-schedule.entity.ts | 11 + packages/types/src/action-schedule/index.ts | 2 + packages/types/src/index.ts | 1 + packages/types/src/prompt/prompt.interface.ts | 63 +-- packages/types/src/task/create-task.dto.ts | 8 + packages/types/src/task/task.entity.ts | 2 + packages/types/src/task/update-task.dto.ts | 22 +- pnpm-lock.yaml | 99 ++++ turbo.json | 8 +- 27 files changed, 1177 insertions(+), 30 deletions(-) create mode 100644 apps/server/prisma/migrations/20250208094609_add_due_date_remind_at_in_task/migration.sql create mode 100644 apps/server/src/modules/triggerdev/trigger.utils.ts create mode 100644 apps/server/src/modules/triggerdev/triggerdev.controller.ts create mode 100644 apps/server/src/modules/triggerdev/triggerdev.interface.ts create mode 100644 apps/server/src/modules/triggerdev/triggerdev.module.ts create mode 100644 apps/server/src/modules/triggerdev/triggerdev.service.ts create mode 100644 apps/server/src/modules/triggerdev/triggerdev.utils.ts create mode 100644 apps/server/src/trigger/tasks/beautify-task.ts create mode 100644 packages/services/src/task/get-ai-tasks.ts create mode 100644 packages/services/src/task/index.ts create mode 100644 packages/types/src/action-schedule/action-schedule.dto.ts create mode 100644 packages/types/src/action-schedule/action-schedule.entity.ts create mode 100644 packages/types/src/action-schedule/index.ts diff --git a/.env b/.env index db0d809..4576191 100644 --- a/.env +++ b/.env @@ -61,3 +61,18 @@ PUBLIC_ATTACHMENT_URL=http://localhost:8000/api # You can add your sentry to get the errors from the application NEXT_PUBLIC_SENTRY_DSN=http://localhost:8000 +############# Trigger.dev ############### +TRIGGER_PORT=3030 +TRIGGER_COMMON_ID=clyofc7dn0000o33e4sup590l + +TRIGGER_TOKEN=27192e6432564f4788d55c15131bd5ac +TRIGGER_ACCESS_TOKEN=tr_pat_${TRIGGER_TOKEN} +TRIGGER_API_URL=http://localhost:3030 + +TRIGGER_DB=trigger +TRIGGER_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}/${TRIGGER_DB} + +V3_ENABLED=true +HTTP_SERVER_PORT=9020 +COORDINATOR_HOST=host.docker.internal +COORDINATOR_PORT=${HTTP_SERVER_PORT} \ No newline at end of file diff --git a/apps/server/package.json b/apps/server/package.json index 94645d5..2858d68 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -85,6 +85,7 @@ "execa": "^9.3.0", "jsonwebtoken": "^9.0.2", "jwks-rsa": "^3.1.0", + "knex": "^3.1.0", "marked": "^14.1.2", "nest-winston": "^1.9.7", "nestjs-prisma": "0.22.0", diff --git a/apps/server/prisma/migrations/20250208094609_add_due_date_remind_at_in_task/migration.sql b/apps/server/prisma/migrations/20250208094609_add_due_date_remind_at_in_task/migration.sql new file mode 100644 index 0000000..d5beb7d --- /dev/null +++ b/apps/server/prisma/migrations/20250208094609_add_due_date_remind_at_in_task/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Task" ADD COLUMN "dueDate" TIMESTAMP(3), +ADD COLUMN "remindAt" TIMESTAMP(3); diff --git a/apps/server/prisma/schema.prisma b/apps/server/prisma/schema.prisma index ce862b6..a26c322 100644 --- a/apps/server/prisma/schema.prisma +++ b/apps/server/prisma/schema.prisma @@ -289,6 +289,8 @@ model Task { endTime DateTime? recurrence String[] recurrenceText String? + dueDate DateTime? + remindAt DateTime? page Page @relation(fields: [pageId], references: [id]) pageId String diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index 05d855c..4faf4a5 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -26,6 +26,7 @@ import { ReplicationModule } from 'modules/replication/replication.module'; import { SyncActionsModule } from 'modules/sync-actions/sync-actions.module'; import { TaskOccurenceModule } from 'modules/task-occurence/task-occurence.model'; import { TasksModule } from 'modules/tasks/tasks.module'; +import { TriggerdevModule } from 'modules/triggerdev/triggerdev.module'; import { UsersModule } from 'modules/users/users.module'; import { WebhookModule } from 'modules/webhook/webhook.module'; import { WorkspacesModule } from 'modules/workspaces/workspaces.module'; @@ -94,6 +95,7 @@ import { AppService } from './app.service'; TaskOccurenceModule, AIRequestsModule, PromptsModule, + TriggerdevModule, ], controllers: [AppController], providers: [ diff --git a/apps/server/src/modules/tasks/tasks.module.ts b/apps/server/src/modules/tasks/tasks.module.ts index 224a8e7..b1dc7b7 100644 --- a/apps/server/src/modules/tasks/tasks.module.ts +++ b/apps/server/src/modules/tasks/tasks.module.ts @@ -5,6 +5,7 @@ import AIRequestsService from 'modules/ai-requests/ai-requests.services'; import { ConversationModule } from 'modules/conversation/conversation.module'; import { IntegrationsService } from 'modules/integrations/integrations.service'; import { TaskOccurenceService } from 'modules/task-occurence/task-occurence.service'; +import { TriggerdevService } from 'modules/triggerdev/triggerdev.service'; import { UsersService } from 'modules/users/users.service'; import { TasksAIController } from './tasks-ai.controller'; @@ -23,6 +24,7 @@ import { TasksService } from './tasks.service'; TasksAIService, AIRequestsService, IntegrationsService, + TriggerdevService, ], exports: [TasksService], }) diff --git a/apps/server/src/modules/tasks/tasks.service.ts b/apps/server/src/modules/tasks/tasks.service.ts index dd50aee..ee5eb6a 100644 --- a/apps/server/src/modules/tasks/tasks.service.ts +++ b/apps/server/src/modules/tasks/tasks.service.ts @@ -4,6 +4,9 @@ import { PrismaService } from 'nestjs-prisma'; import { IntegrationsService } from 'modules/integrations/integrations.service'; import { TaskOccurenceService } from 'modules/task-occurence/task-occurence.service'; +import { Env } from 'modules/triggerdev/triggerdev.interface'; +import { TriggerdevService } from 'modules/triggerdev/triggerdev.service'; +import { UsersService } from 'modules/users/users.service'; import { handleCalendarTask } from './tasks.utils'; @@ -13,6 +16,8 @@ export class TasksService { private prisma: PrismaService, private taskOccurenceService: TaskOccurenceService, private integrationService: IntegrationsService, + private triggerDevService: TriggerdevService, + private usersService: UsersService, ) {} async getTaskBySourceId(sourceId: string): Promise { @@ -141,6 +146,17 @@ export class TasksService { ); } + const pat = this.usersService.getOrCreatePat(userId, workspaceId); + await this.triggerDevService.triggerTaskAsync( + 'common', + 'beautify-task', + { + taskId: task.id, + pat, + }, + Env.PROD, + ); + return task; } diff --git a/apps/server/src/modules/triggerdev/trigger.utils.ts b/apps/server/src/modules/triggerdev/trigger.utils.ts new file mode 100644 index 0000000..3855c16 --- /dev/null +++ b/apps/server/src/modules/triggerdev/trigger.utils.ts @@ -0,0 +1,170 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { JsonValue } from '@sigma/types'; + +// Used to parse the logs - This is taken directly from the +// https://github.dev/triggerdotdev/trigger.dev - triggerdotdev/trigger.dev/apps/webapp/app/v3/eventRepository.server.ts, +// triggerdotdev/trigger.dev/apps/webapp/app/routes/resources.runs.$runParam.logs.download.ts + +export const NULL_SENTINEL = '$@null(('; + +function rehydrateNull(value: any): any { + if (value === NULL_SENTINEL) { + return null; + } + + return value; +} + +export function unflattenAttributes( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + obj: any, +): Record | string | number | boolean | null | undefined { + if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) { + return obj; + } + + if ( + typeof obj === 'object' && + obj !== null && + Object.keys(obj).length === 1 && + Object.keys(obj)[0] === '' + ) { + return rehydrateNull(obj['']) as any; + } + + if (Object.keys(obj).length === 0) { + return {}; + } + + const result: Record = {}; + + for (const [key, value] of Object.entries(obj)) { + const parts = key.split('.').reduce((acc, part) => { + if (part.includes('[')) { + // Handling nested array indices + const subparts = part.split(/\[|\]/).filter((p) => p !== ''); + acc.push(...subparts); + } else { + acc.push(part); + } + return acc; + }, [] as string[]); + + let current: any = result; + for (let i = 0; i < parts.length - 1; i++) { + const part = parts[i]; + const nextPart = parts[i + 1]; + const isArray = /^\d+$/.test(nextPart); + if (isArray && !Array.isArray(current[part])) { + current[part] = []; + } else if (!isArray && current[part] === undefined) { + current[part] = {}; + } + current = current[part]; + } + const lastPart = parts[parts.length - 1]; + current[lastPart] = rehydrateNull(value); + } + + // Convert the result to an array if all top-level keys are numeric indices + if (Object.keys(result).every((k) => /^\d+$/.test(k))) { + const maxIndex = Math.max(...Object.keys(result).map((k) => parseInt(k))); + const arrayResult = Array(maxIndex + 1); + for (const key in result) { + arrayResult[parseInt(key)] = result[key]; + } + return arrayResult as any; + } + + return result; +} + +export function prepareEvent(event: any) { + return { + ...event, + duration: Number(event.duration), + events: parseEventsField(event.events), + style: parseStyleField(event.style), + }; +} + +function parseEventsField(events: JsonValue) { + const unsafe = events + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + (events as unknown[]).map((e: any) => ({ + ...e, + properties: unflattenAttributes(e.properties), + })) + : undefined; + + return unsafe; +} + +function parseStyleField(style: JsonValue) { + const unsafe = unflattenAttributes(style); + + if (!unsafe) { + return {}; + } + + if (typeof unsafe === 'object') { + return { + icon: undefined, + variant: undefined, + ...unsafe, + }; + } + + return {}; +} + +export function getDateFromNanoseconds(nanoseconds: bigint) { + const time = Number(nanoseconds) / 1_000_000; + return new Date(time); +} + +export function formatRunEvent(event: any): string { + const entries = []; + const parts: string[] = []; + + parts.push(getDateFromNanoseconds(event.startTime).toISOString()); + + if (event.task_slug) { + parts.push(event.task_slug); + } + + parts.push(event.level); + parts.push(event.message); + + if (event.level === 'TRACE') { + parts.push(`(${getDateFromNanoseconds(event.duration)})`); + } + + entries.push(parts.join(' ')); + + if (event.events) { + for (const subEvent of event.events) { + if (subEvent.name === 'exception') { + const subEventParts: string[] = []; + + subEventParts.push(new Date(subEvent.time).toISOString()); + + if (event.task_slug) { + subEventParts.push(event.task_slug); + } + + subEventParts.push(subEvent.name); + subEventParts.push(subEvent.properties.exception.message); + + if (subEvent.properties.exception.stack) { + subEventParts.push(subEvent.properties.exception.stack); + } + + entries.push(subEventParts.join(' ')); + } + } + } + + return entries.join('\n'); +} diff --git a/apps/server/src/modules/triggerdev/triggerdev.controller.ts b/apps/server/src/modules/triggerdev/triggerdev.controller.ts new file mode 100644 index 0000000..00bde70 --- /dev/null +++ b/apps/server/src/modules/triggerdev/triggerdev.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; + +import { AuthGuard } from 'modules/auth/auth.guard'; + +import { TriggerdevService } from './triggerdev.service'; + +@Controller({ + version: '1', + path: 'triggerdev', +}) +export class TriggerdevController { + constructor(private triggerdevService: TriggerdevService) {} + + @Get('docker-token') + @UseGuards(AuthGuard) + async getDockerToken() { + return this.triggerdevService.getDockerToken(); + } + + @Get() + @UseGuards(AuthGuard) + async getRequiredKeys() { + return this.triggerdevService.getRequiredKeys(); + } +} diff --git a/apps/server/src/modules/triggerdev/triggerdev.interface.ts b/apps/server/src/modules/triggerdev/triggerdev.interface.ts new file mode 100644 index 0000000..7d0aa9d --- /dev/null +++ b/apps/server/src/modules/triggerdev/triggerdev.interface.ts @@ -0,0 +1,4 @@ +export enum Env { + DEV = 'dev', + PROD = 'prod', +} diff --git a/apps/server/src/modules/triggerdev/triggerdev.module.ts b/apps/server/src/modules/triggerdev/triggerdev.module.ts new file mode 100644 index 0000000..29c9f97 --- /dev/null +++ b/apps/server/src/modules/triggerdev/triggerdev.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; + +import { UsersService } from 'modules/users/users.service'; +import WorkspacesService from 'modules/workspaces/workspaces.service'; + +import { TriggerdevController } from './triggerdev.controller'; +import { TriggerdevService } from './triggerdev.service'; + +@Module({ + imports: [], + controllers: [TriggerdevController], + providers: [TriggerdevService, UsersService, WorkspacesService], + exports: [TriggerdevService], +}) +export class TriggerdevModule {} diff --git a/apps/server/src/modules/triggerdev/triggerdev.service.ts b/apps/server/src/modules/triggerdev/triggerdev.service.ts new file mode 100644 index 0000000..7f6e9a4 --- /dev/null +++ b/apps/server/src/modules/triggerdev/triggerdev.service.ts @@ -0,0 +1,440 @@ +import { Injectable, InternalServerErrorException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { ActionScheduleStatusEnum } from '@sigma/types'; +import axios from 'axios'; +import Knex, { Knex as KnexT } from 'knex'; // Import Knex for database operations +import { v4 as uuidv4 } from 'uuid'; // Import uuid for generating unique identifiers + +import { LoggerService } from 'modules/logger/logger.service'; + +import { formatRunEvent, prepareEvent } from './trigger.utils'; // Import utility functions for formatting run events +import { Env } from './triggerdev.interface'; +import { + cancelRun, + encryptToken, + getRun, + getRuns, + hashToken, + replayRun, + triggerTask, + triggerTaskSync, +} from './triggerdev.utils'; // Import utility functions for triggering tasks and handling runs + +export const TriggerProjects = { + Common: 'common', // Define a constant for the common project +}; + +@Injectable() +export class TriggerdevService { + private readonly logger: LoggerService = new LoggerService('TriggerService'); // Logger instance for logging + + knex: KnexT; // Knex instance for database operations + commonId: string; // ID of the common project + + constructor( + private configService: ConfigService, // Config service for accessing environment variables + ) { + this.knex = Knex({ + client: 'pg', // Use PostgreSQL as the database client + connection: process.env.TRIGGER_DATABASE_URL, // Database connection URL from environment variable + }); + this.commonId = process.env.TRIGGER_COMMON_ID; // ID of the common project from environment variable + } + + afterInit() { + this.logger.info({ + message: 'Trigger service Module initiated', + where: `TriggerdevService.afterInit`, + }); // Log a message after initialization + } + + // This project is used to run internal background jobs + async initCommonProject() { + const commonProjectExists = await this.checkIfProjectExist({ + slug: 'common', // Check if a project with the slug 'common' exists + }); + + this.createPersonalToken(); // Create a personal access token + + if (!commonProjectExists) { + this.logger.info({ + message: `Common project doesn't exist`, + where: `TriggerdevService.initCommonProject`, + }); // Log a message if the common project doesn't exist + await this.createProject('Common', 'common', uuidv4().replace(/-/g, '')); // Create the common project + } + } + + async getDockerToken() { + return { token: this.configService.get('DOCKER_TOKEN') }; + } + + // Create personal token taking from the .env + // Used to deploy the tegon backgrounds + async createPersonalToken() { + const id = uuidv4().replace(/-/g, ''); // Generate a unique ID for the personal access token + + const response = await this.knex('PersonalAccessToken') + .where({ + userId: this.commonId, // Filter by the common project user ID + }) + .select('id'); // Select the ID column + + if (response.length > 0) { + return; // Return if a personal access token already exists + } + + await this.knex('PersonalAccessToken').insert({ + id, // ID of the personal access token + name: 'cli', // Name of the personal access token + userId: this.commonId, // User ID associated with the personal access token + updatedAt: new Date(), // Updated timestamp + obfuscatedToken: process.env.TRIGGER_ACCESS_TOKEN, // Obfuscated token from environment variable + hashedToken: hashToken(process.env.TRIGGER_ACCESS_TOKEN), // Hashed token using the hashToken utility function + encryptedToken: encryptToken(process.env.TRIGGER_ACCESS_TOKEN), // Encrypted token using the encryptToken utility function + }); + } + + // Get a project by name, slug, or ID + async getProject(whereClause: { name?: string; slug?: string; id?: string }) { + try { + const response = await this.knex('Project') + .where(whereClause) // Filter projects based on the provided whereClause + .select('id', 'name', 'slug'); // Select the ID, name, and slug columns + + return response[0]; // Return the first project matching the whereClause + } catch (e) { + return undefined; // Return undefined if an error occurs + } + } + + // Get the latest version for a given task slug + async getLatestVersionForTask(slug: string): Promise { + const latestWorker = await this.knex('BackgroundWorkerTask') + .where('slug', slug) // Filter by the task slug + .join( + 'BackgroundWorker', // Join with the BackgroundWorker table + 'BackgroundWorkerTask.workerId', // Join condition + 'BackgroundWorker.id', + ) + .orderBy('BackgroundWorker.updatedAt', 'desc') // Order by the updated timestamp in descending order + .first('BackgroundWorker.version'); // Select the first row's version column + + return latestWorker?.version; // Return the latest version or undefined if not found + } + + // Get the production runtime environment for a given project ID + async getProdRuntimeForProject(projectId: string, slug: string) { + try { + const response = await this.knex('RuntimeEnvironment') + .where({ + projectId, // Filter by the project ID + slug, + }) + .select('id', 'slug', 'apiKey'); // Select the ID, slug, and API key columns + + return response[0]; // Return the first runtime environment matching the criteria + } catch (e) { + return undefined; // Return undefined if an error occurs + } + } + + // Check if a project exists based on the provided whereClause + async checkIfProjectExist(whereClause: { + name?: string; + slug?: string; + id?: string; + }): Promise { + const project = await this.getProject(whereClause); // Get the project based on the whereClause + return !!project; // Return true if a project exists, false otherwise + } + + // Create a new project with the given name, slug, and optional secret key + async createProject(name: string, slug: string, secretKey?: string) { + try { + const id = uuidv4().replace(/-/g, ''); // Generate a unique ID for the project + slug = slug.replace(/-/g, ''); // Remove hyphens from the slug + const secret = secretKey ? secretKey : id; // Use the provided secret key or the project ID as the secret + + await this.knex.transaction(async (trx) => { + await this.knex('Project') + .insert({ + id, // Project ID + name, // Project name + organizationId: this.commonId, // Organization ID (common project ID) + slug, // Project slug + externalRef: `proj_${slug}`, // External reference for the project + version: 'V3', // Project version + updatedAt: new Date(), // Updated timestamp + }) + .transacting(trx); // Use the transaction + + await this.knex('RuntimeEnvironment') + .insert( + ['dev', 'prod'].map((env: string) => ({ + id: uuidv4(), // Generate a unique ID for the runtime environment + slug: env, // Slug for the runtime environment (dev or prod) + apiKey: `tr_${env}_${secret}`, // API key for the runtime environment + organizationId: this.commonId, // Organization ID (common project ID) + orgMemberId: this.commonId, // Organization member ID (common project ID) + projectId: id, // Project ID + type: env === 'prod' ? 'PRODUCTION' : 'DEVELOPMENT', // Type of the runtime environment (production or development) + pkApiKey: `tr_pk_${env}${secret}`, // Primary key API key for the runtime environment + shortcode: env, // Shortcode for the runtime environment (dev or prod) + updatedAt: new Date(), // Updated timestamp + })), + ) + .transacting(trx); // Use the transaction + }); + + return id; // Return the project ID + } catch (error) { + this.logger.error({ + message: `Error creating project`, + where: `TriggerdevService.createProject`, + error, + }); // Log an error if project creation fails + + return undefined; // Return undefined if an error occurs + } + } + + // Get the production runtime API key for a given project slug + async getProdRuntimeKey(projectSlug: string, slug: string) { + const project = await this.getProject({ slug: projectSlug }); // Get the project by slug + const runtime = await this.getProdRuntimeForProject(project.id, slug); // Get the production runtime environment for the project + + return runtime.apiKey; // Return the API key for the production runtime environment + } + + // Trigger a task synchronously for a given project slug, task ID, and payload + async triggerTask( + projectSlug: string, + id: string, + payload: any, // eslint-disable-line @typescript-eslint/no-explicit-any + env: Env, + options?: Record, + ) { + this.logger.info({ + message: 'Triggering task', + where: `TriggerdevService.triggerTask`, + payload: { projectSlug, id }, + }); + + const runtimeSlug = this.getRuntimeSlugForProject(projectSlug, env); + + const projectslugWithoutHyphen = projectSlug.replace(/-/g, ''); // Remove hyphens from the project slug + const apiKey = await this.getProdRuntimeKey( + projectslugWithoutHyphen, + runtimeSlug, + ); // Get the production runtime API key + const response = await triggerTaskSync(id, payload, apiKey, options); // Trigger the task synchronously + + return response; // Return the response from the triggered task + } + + // Trigger a task asynchronously for a given project slug, task ID, payload, and options + async triggerTaskAsync( + projectSlug: string, + id: string, + payload: any, // eslint-disable-line @typescript-eslint/no-explicit-any + env: Env, + options?: Record, + ) { + const runtimeSlug = this.getRuntimeSlugForProject(projectSlug, env); + + const projectslugWithoutHyphen = projectSlug.replace(/-/g, ''); // Remove hyphens from the project slug + + const apiKey = await this.getProdRuntimeKey( + projectslugWithoutHyphen, + runtimeSlug, + ); // Get the production runtime API key + const response = await triggerTask(id, payload, apiKey, options); // Trigger the task asynchronously + + return response; // Return the response from the triggered task + } + + // Get the required keys (trigger key) for a given workspace ID and user ID + async getRequiredKeys() { + const token = this.configService.get('TRIGGER_ACCESS_TOKEN'); // Get the trigger access token from the config service + return { triggerKey: token }; // Return the trigger key + } + + getRuntimeSlugForProject(projectSlug: string, env: Env = Env.PROD) { + if (projectSlug === 'common') { + return this.configService.get('NODE_ENV') === 'production' + ? Env.PROD + : Env.DEV; + } + + return env; + } + + // Get runs for a given project slug and task ID + async getRunsForTask(projectSlug: string, taskId: string, env: Env) { + const projectslugWithoutHyphen = projectSlug.replace(/-/g, ''); // Remove hyphens from the project slug + + const runtimeSlug = this.getRuntimeSlugForProject(projectSlug, env); + + const apiKey = await this.getProdRuntimeKey( + projectslugWithoutHyphen, + runtimeSlug, + ); // Get the production runtime API key + + return await getRuns(taskId, apiKey); // Get the runs for the task + } + + // Get a run for a given project slug and run ID + async getRun(projectSlug: string, runId: string, env: Env) { + const projectslugWithoutHyphen = projectSlug.replace(/-/g, ''); // Remove hyphens from the project slug + const runtimeSlug = this.getRuntimeSlugForProject(projectSlug, env); + + const apiKey = await this.getProdRuntimeKey( + projectslugWithoutHyphen, + runtimeSlug, + ); // Get the production runtime API key + const run = await getRun(runId, apiKey); // Get the run data + const logs = await this.getLogsForRunId(runId); // Get the logs for the run + + return { ...run, logs }; // Return the run data with logs + } + + // Replay a run for a given project slug and run ID + async replayRun(projectSlug: string, runId: string, env: Env) { + const projectslugWithoutHyphen = projectSlug.replace(/-/g, ''); // Remove hyphens from the project slug + const runtimeSlug = this.getRuntimeSlugForProject(projectSlug, env); + + const apiKey = await this.getProdRuntimeKey( + projectslugWithoutHyphen, + runtimeSlug, + ); // Get the production runtime API key + + return await replayRun(runId, apiKey); + } + + async cancelRun(projectSlug: string, runId: string, env: Env) { + const projectslugWithoutHyphen = projectSlug.replace(/-/g, ''); // Remove hyphens from the project slug + const runtimeSlug = this.getRuntimeSlugForProject(projectSlug, env); + + const apiKey = await this.getProdRuntimeKey( + projectslugWithoutHyphen, + runtimeSlug, + ); // Get the production runtime API key + return await cancelRun(runId, apiKey); + } + + // Get logs for a given run ID + async getLogsForRunId(runId: string): Promise { + // Fetch run events from the database using Knex + const runEvents = await this.knex('TaskEvent') + .where('runId', runId) // Filter by the run ID + .orderBy('startTime', 'asc'); // Order by the start time in ascending order + + const preparedEvents = []; + + for (const event of runEvents) { + preparedEvents.push(prepareEvent(event)); // Prepare the event data + } + + // Format the run events into a log string + const logEntries = preparedEvents.map(formatRunEvent); // Format the prepared events + const logString = logEntries.join('\n'); // Join the log entries with newlines + + return logString; // Return the log string + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async createScheduleTask(payload: any, env: Env) { + const projectSlug = 'common'; // Specify the project slug + const runtimeSlug = this.getRuntimeSlugForProject(projectSlug, env); // Get the runtime slug for the project and environment + + const apiKey = await this.getProdRuntimeKey(projectSlug, runtimeSlug); // Get the production runtime API key + + const url = `${process.env['TRIGGER_API_URL']}/api/v1/schedules`; // Construct the API URL for creating a schedule task + + try { + const response = await axios.post( + // Send a POST request to create the schedule task + url, + { task: 'schedule-proxy', ...payload }, // Include the task type and payload data + { headers: { Authorization: `Bearer ${apiKey}` } }, // Include the API key in the headers + ); + + return response.data; // Return the response data from the POST request + } catch (error) { + this.logger.error({ + // Log the error if the create schedule task operation fails + message: 'Failed to create schedule task', + where: 'TriggerDevService.CreateScheduleTask', + error, + }); + throw new InternalServerErrorException(`Failed to create schedule task`); // Throw an InternalServerErrorException + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async updateScheduleTask(actionScheduleId: string, payload: any, env: Env) { + const projectSlug = 'common'; // Specify the project slug + const runtimeSlug = this.getRuntimeSlugForProject(projectSlug, env); // Get the runtime slug for the project and environment + + const apiKey = await this.getProdRuntimeKey(projectSlug, runtimeSlug); // Get the production runtime API key + + try { + const { status, ...otherPayload } = payload; // Destructure the payload to separate the status + let url = `${process.env['TRIGGER_API_URL']}/api/v1/schedules/${actionScheduleId}`; // Construct the API URL for updating the schedule task + const response = await axios.put( + // Send a PUT request to update the schedule task + url, + { task: 'schedule-proxy', ...otherPayload }, // Include the task type and other payload data + { headers: { Authorization: `Bearer ${apiKey}` } }, // Include the API key in the headers + ); + + // Construct the URL for activating or deactivating the schedule task based on the status + url = + status === ActionScheduleStatusEnum.ACTIVE + ? `${url}/activate` + : `${url}/deactivate`; + + await axios.post( + // Send a POST request to activate or deactivate the schedule task + url, + {}, + { headers: { Authorization: `Bearer ${apiKey}` } }, // Include the API key in the headers + ); + + return response.data; // Return the response data from the initial PUT request + } catch (error) { + this.logger.error({ + // Log the error if the update schedule task operation fails + message: 'Failed to update schedule task', + where: 'TriggerDevService.CreateScheduleTask', + error, + }); + throw new InternalServerErrorException(`Failed to update schedule task`); // Throw an InternalServerErrorException + } + } + + async deleteScheduleTask(actionScheduleId: string, env: Env) { + const projectSlug = 'common'; + const runtimeSlug = this.getRuntimeSlugForProject(projectSlug, env); + + const apiKey = await this.getProdRuntimeKey(projectSlug, runtimeSlug); // Get the production runtime API key + + const url = `${process.env['TRIGGER_API_URL']}/api/v1/schedules/${actionScheduleId}`; + + try { + const response = await axios.delete(url, { + headers: { Authorization: `Bearer ${apiKey}` }, + }); + + return response.data; + } catch (error) { + this.logger.error({ + message: 'Failed to delete schedule task', + where: 'TriggerDevService.CreateScheduleTask', + error, + }); + throw new InternalServerErrorException(`Failed to delete schedule task`); // Throw InternalServerErrorException + } + } +} diff --git a/apps/server/src/modules/triggerdev/triggerdev.utils.ts b/apps/server/src/modules/triggerdev/triggerdev.utils.ts new file mode 100644 index 0000000..e1dc885 --- /dev/null +++ b/apps/server/src/modules/triggerdev/triggerdev.utils.ts @@ -0,0 +1,205 @@ +import nodeCrypto from 'node:crypto'; + +import axios from 'axios'; + +export function encryptToken(value: string) { + const nonce = nodeCrypto.randomBytes(12); + const cipher = nodeCrypto.createCipheriv( + 'aes-256-gcm', + process.env.TRIGGER_TOKEN, + nonce, + ); + + let encrypted = cipher.update(value, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + + const tag = cipher.getAuthTag().toString('hex'); + + return { + nonce: nonce.toString('hex'), + ciphertext: encrypted, + tag, + }; +} + +export function hashToken(token: string): string { + const hash = nodeCrypto.createHash('sha256'); + hash.update(token); + return hash.digest('hex'); +} + +export async function getRuns(taskId: string, apiKey: string) { + const url = `${process.env['TRIGGER_API_URL']}/api/v1/runs?filter[taskIdentifier]=${taskId}`; + + try { + const response = await axios.get(url, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + return response.data; + } catch (error) { + console.log(error); + return []; + } +} + +export async function getRun(runId: string, apiKey: string) { + const url = `${process.env['TRIGGER_API_URL']}/api/v3/runs/${runId}`; + + try { + const response = await axios.get(url, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + return response.data; + } catch (error) { + console.log(error); + return { data: [] }; + } +} + +export async function replayRun(runId: string, apiKey: string) { + const url = `${process.env['TRIGGER_API_URL']}/api/v1/runs/${runId}/replay`; + + try { + const response = await axios.post(url, undefined, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + return response.data; + } catch (error) { + console.log(error); + return { data: [] }; + } +} + +export async function cancelRun(runId: string, apiKey: string) { + const url = `${process.env['TRIGGER_API_URL']}/api/v3/runs/${runId}/cancel`; + + try { + const response = await axios.post(url, undefined, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + return response.data; + } catch (error) { + console.log(error); + return { data: [] }; + } +} + +export async function triggerTask( + taskId: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + payload: Record, + apiKey: string, + options?: Record, +) { + const url = `${process.env['TRIGGER_API_URL']}/api/v1/tasks/${taskId}/trigger`; + + try { + const response = await axios.post( + url, + { payload, options }, + { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }, + ); + + return response.data; + } catch (error) { + console.log(error); + return {}; + } +} + +export async function getTriggerRun(runId: string, apiKey: string) { + const url = `${process.env['TRIGGER_API_URL']}/api/v3/runs/${runId}`; + try { + const response = await axios.get(url, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + return { + data: response.data, + rateLimit: { + limit: response.headers['x-ratelimit-limit'], + remaining: response.headers['x-ratelimit-remaining'], + reset: response.headers['x-ratelimit-reset'], + }, + }; + } catch (error) { + return { + status: 'FAILED', + }; + } +} + +function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export async function pollTriggerRun( + runId: string, + apiKey: string, + retryDelay = 1000, +) { + try { + const completionStatuses = [ + 'COMPLETED', + 'CANCELED', + 'FAILED', + 'CRASHED', + 'INTERRUPTED', + 'SYSTEM_FAILURE', + ]; + const timeoutDuration = 10 * 60 * 1000; // 10 minutes in milliseconds + + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutDuration) { + const { data: run, rateLimit } = await getTriggerRun(runId, apiKey); + + if (completionStatuses.includes(run.status)) { + return run; // Return run status if completed + } + + // Extract rate limit info from headers + const remaining = parseInt(rateLimit.remaining, 10); + const resetTime = parseInt(rateLimit.reset, 10) * 1000; // Convert to milliseconds + + if (remaining === 0) { + const delayUntilReset = resetTime - Date.now(); + await wait(delayUntilReset); // Wait until reset if rate limit is reached + } else { + // Normal delay between retries + await wait(retryDelay); // Convert retryDelay to milliseconds + } + } + } catch (e) { + return {}; + } +} + +export async function triggerTaskSync( + taskId: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + payload: Record, + apiKey: string, + options?: Record, +) { + const handle = await triggerTask(taskId, payload, apiKey, options); + const run = await pollTriggerRun(handle.id, apiKey); + return run.output; +} diff --git a/apps/server/src/trigger/tasks/beautify-task.ts b/apps/server/src/trigger/tasks/beautify-task.ts new file mode 100644 index 0000000..8a7e8ad --- /dev/null +++ b/apps/server/src/trigger/tasks/beautify-task.ts @@ -0,0 +1,60 @@ +import { PrismaClient } from '@prisma/client'; +import { task } from '@trigger.dev/sdk/v3'; +import axios from 'axios'; + +const prisma = new PrismaClient(); + +export const beautifyTask = task({ + id: 'beautify-task', + run: async (payload: { taskId: string; pat: string }) => { + const sigmaTask = await prisma.task.findUnique({ + where: { id: payload.taskId }, + include: { page: true }, + }); + + const recurrenceData = ( + await axios.post( + `${process.env.BACKEND_URL}/v1/tasks/ai/recurrence`, + { + text: sigmaTask.page.title, + currentTime: new Date().toISOString(), + }, + { headers: { Authorization: `Bearer ${payload.pat}` } }, + ) + ).data; + + let updatedTask; + if (recurrenceData) { + updatedTask = await prisma.task.update({ + where: { id: payload.taskId }, + data: { + ...(recurrenceData.startTime && { + startTime: recurrenceData.startTime, + }), + ...(recurrenceData.endTime && { endTime: recurrenceData.endTime }), + ...(recurrenceData.recurrenceRule && { + recurrence: [recurrenceData.recurrenceRule], + }), + ...(recurrenceData.recurrenceText && { + recurrenceText: recurrenceData.recurrenceText, + }), + ...(recurrenceData.dueDate && { dueDate: recurrenceData.dueDate }), + ...(recurrenceData.remindAt && { remindAt: recurrenceData.remindAt }), + ...(recurrenceData.title + ? { + page: { + update: { + title: recurrenceData.title, + }, + }, + } + : {}), + }, + }); + } + + return { + task: updatedTask, + }; + }, +}); diff --git a/apps/server/trigger.config.ts b/apps/server/trigger.config.ts index c52890e..aa24ca9 100644 --- a/apps/server/trigger.config.ts +++ b/apps/server/trigger.config.ts @@ -12,6 +12,7 @@ export const resolveEnvVars: ResolveEnvironmentVariablesFunction = async ({ return { variables: { DATABASE_URL: env.DATABASE_URL, + BACKEND_URL: env.BACKEND_HOST, }, }; }; diff --git a/packages/services/src/task/get-ai-tasks.ts b/packages/services/src/task/get-ai-tasks.ts new file mode 100644 index 0000000..6c71437 --- /dev/null +++ b/packages/services/src/task/get-ai-tasks.ts @@ -0,0 +1,12 @@ +import type { ReccurenceInput } from '@sigma/types'; + +import axios from 'axios'; + +export async function getAiTaskRecurrence(reccurenceInput: ReccurenceInput) { + const response = await axios.post( + `/api/v1/tasks/ai/recurrence`, + reccurenceInput, + ); + + return response.data; +} diff --git a/packages/services/src/task/index.ts b/packages/services/src/task/index.ts new file mode 100644 index 0000000..fb8feab --- /dev/null +++ b/packages/services/src/task/index.ts @@ -0,0 +1 @@ +export * from './get-ai-tasks'; diff --git a/packages/types/src/action-schedule/action-schedule.dto.ts b/packages/types/src/action-schedule/action-schedule.dto.ts new file mode 100644 index 0000000..def0ad6 --- /dev/null +++ b/packages/types/src/action-schedule/action-schedule.dto.ts @@ -0,0 +1,17 @@ +import { ActionScheduleStatusEnum } from './action-schedule.entity'; + +export class ActionScheduleParamsDto { + actionSlug: string; + actionScheduleId: string; +} + +export class ActionScheduleDto { + cron: string; + timezone?: string; + status?: ActionScheduleStatusEnum; +} + +export class ActionScheduleTriggerParamsDto { + actionId: string; + actionEntityId: string; +} diff --git a/packages/types/src/action-schedule/action-schedule.entity.ts b/packages/types/src/action-schedule/action-schedule.entity.ts new file mode 100644 index 0000000..661a401 --- /dev/null +++ b/packages/types/src/action-schedule/action-schedule.entity.ts @@ -0,0 +1,11 @@ +export const ActionScheduleStatus = { + ACTIVE: 'ACTIVE', + IN_ACTIVE: 'IN_ACTIVE', + DELETED: 'DELETED', +}; + +export enum ActionScheduleStatusEnum { + ACTIVE = 'ACTIVE', + IN_ACTIVE = 'IN_ACTIVE', + DELETED = 'DELETED', +} diff --git a/packages/types/src/action-schedule/index.ts b/packages/types/src/action-schedule/index.ts new file mode 100644 index 0000000..f2c1152 --- /dev/null +++ b/packages/types/src/action-schedule/index.ts @@ -0,0 +1,2 @@ +export * from './action-schedule.entity'; +export * from './action-schedule.dto'; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index a68b899..660c65f 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,6 +1,7 @@ import 'reflect-metadata'; export * from './activity'; +export * from './action-schedule'; export * from './ai-request'; export * from './attachment'; export * from './common'; diff --git a/packages/types/src/prompt/prompt.interface.ts b/packages/types/src/prompt/prompt.interface.ts index 11fa3d2..ffbf240 100644 --- a/packages/types/src/prompt/prompt.interface.ts +++ b/packages/types/src/prompt/prompt.interface.ts @@ -2,9 +2,9 @@ export const recurrencePrompt = `You are an AI assistant specialized in parsing Here is the text describing the event: - + {{text}} - + The current time and timezone for reference is: @@ -14,40 +14,47 @@ The current time and timezone for reference is: Please follow these steps to complete the task: -1. Analyze the input text to identify information about event frequency, recurrence patterns, start time, and end time. +1. Analyze the input text to identify information about event frequency, recurrence patterns, start time, end time, due date, and reminders. -2. Create an RRULE string based on the frequency information found in the text. The RRULE should follow the iCalendar specification (RFC 5545). Include only the frequency part (e.g., FREQ=DAILY, FREQ=WEEKLY;BYDAY=MO,WE,FR, etc.). +2. If frequency information is found, create an RRULE string based on it. The RRULE should follow the iCalendar specification (RFC 5545). Include only the frequency part (e.g., FREQ=DAILY, FREQ=WEEKLY;BYDAY=MO,WE,FR, etc.). -3. If start and end times are mentioned in the text, extract them and convert them to the same timezone as the provided current time. Use the format "YYYY-MM-DDTHH:MM:SS±HH:MM" (ISO 8601). +3. Extract start and end times if mentioned in the text. Convert them to the same timezone as the provided current time. Use the format "YYYY-MM-DDTHH:MM:SS±HH:MM" (ISO 8601). -4. Generate a human-readable description of the recurrence pattern. +4. If an end time is not explicitly stated for a scheduled task, add 15 minutes to the start time to create an end time. -5. Format your output as a JSON object with the following structure: - - { - "recurrence_rule": "RRULE string or empty string if not found", - "recurrence_text": "Human-readable description of the recurrence", - "start_time": "Formatted start time or empty string if not found", - "end_time": "Formatted end time or empty string if not found" - } - +5. Extract and format any mentioned due date using the same ISO 8601 format. + +6. Extract and format any specified reminder time using the same ISO 8601 format. -Before providing the final output, wrap your analysis and reasoning process inside tags. Your analysis should include: +7. Generate a human-readable description of the recurrence pattern or schedule. Only include this if there is a recurrence rule or reminder. -a. Relevant quotes from the input text mentioning frequency, recurrence, start time, and end time. -b. Interpretation of the frequency and recurrence information, considering different possibilities. -c. Step-by-step plan for constructing the RRULE string. -d. Considerations for generating a clear human-readable description. -e. Identification of any ambiguities or missing information and how to handle them. +8. Transform the text of the task into a concise task title. -After your analysis, provide the final JSON output. +Before providing the final output, wrap your analysis in tags. Include: -Example output structure (note: this is a generic example, your actual output should be based on the input text): +a. A numbered list of each scheduling component found in the text (frequency, recurrence, start time, end time, due date, reminders). +b. Relevant quotes from the input text for each identified component. +c. Your interpretation of the scheduling information, considering different possibilities. +d. If applicable, a step-by-step plan for constructing the RRULE string. +e. Your approach for generating a clear human-readable description of the recurrence or schedule. +f. Identification of any ambiguities or missing information and how you'll handle them. +g. Reasoning for including or excluding any of the JSON fields based on the input. +h. Your process for transforming the text into a concise task title: + - List key elements from the description. + - Identify the main action or purpose. + - Combine these elements into a short, descriptive title. +i. A list of potential ambiguities in the text and your proposed resolutions. + +After your analysis, provide the final JSON output with the following structure: { - "recurrence_rule": "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR", - "recurrence_text": "Every week on Monday, Wednesday, and Friday", - "start_time": "2023-06-06T09:00:00+00:00", - "end_time": "2023-06-06T10:00:00+00:00" + "title": "Concise title of the task", + "recurrenceRule": "RRULE string or empty string if not applicable", + "recurrenceText": "Human-readable description of the recurrence or schedule (only if there's a rule or reminder)", + "startTime": "Formatted start time or empty string if not found", + "endTime": "Formatted end time or empty string if not found", + "dueDate": "Formatted due date or empty string if not applicable", + "remindAt": "Formatted reminder time or empty string if not applicable" } -`; + +If the input text does not contain any schedulable information, return an empty JSON object {}.`; diff --git a/packages/types/src/task/create-task.dto.ts b/packages/types/src/task/create-task.dto.ts index abbc5c0..e747102 100644 --- a/packages/types/src/task/create-task.dto.ts +++ b/packages/types/src/task/create-task.dto.ts @@ -25,6 +25,10 @@ export class CreateTaskDto { @IsOptional() integrationAccountId?: string; + @IsString() + @IsOptional() + dueDate?: string; + @IsString() @IsOptional() startTime?: string; @@ -40,4 +44,8 @@ export class CreateTaskDto { @IsString() @IsOptional() recurrenceText?: string; + + @IsString() + @IsOptional() + remindAt?: string; } diff --git a/packages/types/src/task/task.entity.ts b/packages/types/src/task/task.entity.ts index 3880033..cc7848a 100644 --- a/packages/types/src/task/task.entity.ts +++ b/packages/types/src/task/task.entity.ts @@ -28,6 +28,8 @@ export class Task { endTime?: Date; recurrence?: string[]; recurrenceText?: string; + dueDate?: Date; + remindAt?: Date; pageId?: string; page?: Page; diff --git a/packages/types/src/task/update-task.dto.ts b/packages/types/src/task/update-task.dto.ts index a546f90..917eebc 100644 --- a/packages/types/src/task/update-task.dto.ts +++ b/packages/types/src/task/update-task.dto.ts @@ -1,4 +1,4 @@ -import { IsOptional, IsString } from 'class-validator'; +import { IsArray, IsOptional, IsString } from 'class-validator'; export class UpdateTaskDto { @IsString() @@ -12,4 +12,24 @@ export class UpdateTaskDto { @IsString() @IsOptional() dueDate?: string; + + @IsString() + @IsOptional() + startTime?: string; + + @IsString() + @IsOptional() + endTime?: string; + + @IsArray() + @IsOptional() + recurrence?: string[]; + + @IsString() + @IsOptional() + recurrenceText?: string; + + @IsString() + @IsOptional() + remindAt?: string; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af8e50a..2286854 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -267,6 +267,9 @@ importers: jwks-rsa: specifier: ^3.1.0 version: 3.1.0 + knex: + specifier: ^3.1.0 + version: 3.1.0(pg@8.13.0) marked: specifier: ^14.1.2 version: 14.1.3 @@ -6455,6 +6458,9 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + colorette@2.0.19: + resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} + colorspace@1.1.4: resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} @@ -7466,6 +7472,10 @@ packages: jiti: optional: true + esm@3.2.25: + resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} + engines: {node: '>=6'} + espree@10.2.0: resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8019,6 +8029,9 @@ packages: get-tsconfig@4.7.2: resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + getopts@2.3.0: + resolution: {integrity: sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==} + git-hooks-list@3.1.0: resolution: {integrity: sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA==} @@ -8436,6 +8449,10 @@ packages: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} + interpret@2.2.0: + resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==} + engines: {node: '>= 0.10'} + intl-tel-input@17.0.21: resolution: {integrity: sha512-TfyPxLe41QZPOf6RqBxRE2dpQ0FThB/PBD/gRbxVhGW7IuYg30QD90x/vjmEo4vkZw7j8etxpVcjIZVRcG+Otw==} @@ -9087,6 +9104,34 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + knex@3.1.0: + resolution: {integrity: sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==} + engines: {node: '>=16'} + hasBin: true + peerDependencies: + better-sqlite3: '*' + mysql: '*' + mysql2: '*' + pg: '*' + pg-native: '*' + sqlite3: '*' + tedious: '*' + peerDependenciesMeta: + better-sqlite3: + optional: true + mysql: + optional: true + mysql2: + optional: true + pg: + optional: true + pg-native: + optional: true + sqlite3: + optional: true + tedious: + optional: true + kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} @@ -10504,6 +10549,9 @@ packages: pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} + pg-connection-string@2.7.0: resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} @@ -11192,6 +11240,10 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} + rechoir@0.8.0: + resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} + engines: {node: '>= 10.13.0'} + redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -12050,6 +12102,10 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + tarn@3.0.2: + resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==} + engines: {node: '>=8.0.0'} + teeny-request@9.0.0: resolution: {integrity: sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==} engines: {node: '>=14'} @@ -12128,6 +12184,10 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tildify@2.0.0: + resolution: {integrity: sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==} + engines: {node: '>=8'} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -20189,6 +20249,8 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 + colorette@2.0.19: {} + colorspace@1.1.4: dependencies: color: 3.2.1 @@ -21871,6 +21933,8 @@ snapshots: transitivePeerDependencies: - supports-color + esm@3.2.25: {} + espree@10.2.0: dependencies: acorn: 8.12.1 @@ -22543,6 +22607,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + getopts@2.3.0: {} + git-hooks-list@3.1.0: {} git-remote-origin-url@4.0.0: @@ -23091,6 +23157,8 @@ snapshots: interpret@1.4.0: {} + interpret@2.2.0: {} + intl-tel-input@17.0.21: {} invariant@2.2.4: @@ -23919,6 +23987,27 @@ snapshots: kleur@4.1.5: {} + knex@3.1.0(pg@8.13.0): + dependencies: + colorette: 2.0.19 + commander: 10.0.1 + debug: 4.3.4 + escalade: 3.2.0 + esm: 3.2.25 + get-package-type: 0.1.0 + getopts: 2.3.0 + interpret: 2.2.0 + lodash: 4.17.21 + pg-connection-string: 2.6.2 + rechoir: 0.8.0 + resolve-from: 5.0.0 + tarn: 3.0.2 + tildify: 2.0.0 + optionalDependencies: + pg: 8.13.0 + transitivePeerDependencies: + - supports-color + kuler@2.0.0: {} language-subtag-registry@0.3.22: {} @@ -25855,6 +25944,8 @@ snapshots: pg-cloudflare@1.1.1: optional: true + pg-connection-string@2.6.2: {} + pg-connection-string@2.7.0: {} pg-int8@1.0.1: {} @@ -26692,6 +26783,10 @@ snapshots: dependencies: resolve: 1.22.8 + rechoir@0.8.0: + dependencies: + resolve: 1.22.8 + redis-errors@1.2.0: {} redis-parser@3.0.0: @@ -27755,6 +27850,8 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + tarn@3.0.2: {} + teeny-request@9.0.0(encoding@0.1.13): dependencies: http-proxy-agent: 5.0.0 @@ -27857,6 +27954,8 @@ snapshots: through@2.3.8: {} + tildify@2.0.0: {} + tiny-invariant@1.3.3: {} tiny-typed-emitter@2.1.0: {} diff --git a/turbo.json b/turbo.json index c4fbcce..fab37d2 100644 --- a/turbo.json +++ b/turbo.json @@ -35,7 +35,13 @@ "GCP_SERVICE_ACCOUNT_FILE", "REDIS_URL", "REDIS_PORT", - "NEXT_PUBLIC_AI_HOST" + "NEXT_PUBLIC_AI_HOST", + "TRIGGER_DB", + "TRIGGER_DATABASE_URL", + "TRIGGER_COMMON_ID", + "TRIGGER_TOKEN", + "TRIGGER_ACCESS_TOKEN", + "TRIGGER_API_URL" ], "ui": "tui", "tasks": {