chore: run format

This commit is contained in:
Waleed Latif
2025-04-09 01:40:54 -07:00
parent 7dbb53747c
commit 4f9c70b9d4
31 changed files with 328 additions and 439 deletions

View File

@@ -1,5 +1,5 @@
import { NextRequest } from 'next/server'
import { vi } from 'vitest'
import { NextRequest } from 'next/server'
/**
* Mock sample workflow state for testing

View File

@@ -3,8 +3,8 @@
*
* @vitest-environment node
*/
import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { NextRequest } from 'next/server'
import { createMockRequest } from '@/app/api/__test-utils__/utils'
describe('File Delete API Route', () => {

View File

@@ -3,9 +3,9 @@
*
* @vitest-environment node
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { NextRequest } from 'next/server'
import path from 'path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createMockRequest } from '@/app/api/__test-utils__/utils'
// Create actual mocks for path functions that we can use instead of using vi.doMock for path

View File

@@ -3,8 +3,8 @@
*
* @vitest-environment node
*/
import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { NextRequest } from 'next/server'
describe('File Serve API Route', () => {
// Mock file system and S3 client modules

View File

@@ -3,8 +3,8 @@
*
* @vitest-environment node
*/
import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { NextRequest } from 'next/server'
describe('File Upload API Route', () => {
// Mock file system and S3 client modules

View File

@@ -1,8 +1,8 @@
import { NextRequest } from 'next/server'
import { eq } from 'drizzle-orm'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console-logger'
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
import { getSession } from '@/lib/auth'
import { db } from '@/db'
import * as schema from '@/db/schema'
@@ -10,7 +10,7 @@ const logger = createLogger('MarketplaceUnpublishAPI')
/**
* API endpoint to unpublish a workflow from the marketplace by its marketplace ID
*
*
* Security:
* - Requires authentication
* - Validates that the current user is the author of the marketplace entry
@@ -21,7 +21,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
try {
const { id } = await params
// Get the session first for authorization
const session = await getSession()
if (!session?.user?.id) {
@@ -30,7 +30,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
}
const userId = session.user.id
// Get the marketplace entry using the marketplace ID
const marketplaceEntry = await db
.select({
@@ -48,7 +48,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
logger.warn(`[${requestId}] No marketplace entry found with ID: ${id}`)
return createErrorResponse('Marketplace entry not found', 404)
}
// Check if the user is the author of the marketplace entry
if (marketplaceEntry.authorId !== userId) {
logger.warn(
@@ -58,7 +58,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
}
const workflowId = marketplaceEntry.workflowId
// Verify the workflow exists and belongs to the user
const workflow = await db
.select({
@@ -69,7 +69,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
.where(eq(schema.workflow.id, workflowId))
.limit(1)
.then((rows) => rows[0])
if (!workflow) {
logger.warn(`[${requestId}] Associated workflow not found: ${workflowId}`)
// We'll still delete the marketplace entry even if the workflow is missing
@@ -79,20 +79,23 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
)
return createErrorResponse('You do not have permission to unpublish this workflow', 403)
}
try {
// Delete the marketplace entry - this is the primary action
await db.delete(schema.marketplace).where(eq(schema.marketplace.id, id))
// Update the workflow to mark it as unpublished if it exists
if (workflow) {
await db.update(schema.workflow)
await db
.update(schema.workflow)
.set({ isPublished: false })
.where(eq(schema.workflow.id, workflowId))
}
logger.info(`[${requestId}] Workflow "${marketplaceEntry.name}" unpublished from marketplace: ID=${id}, workflowId=${workflowId}`)
logger.info(
`[${requestId}] Workflow "${marketplaceEntry.name}" unpublished from marketplace: ID=${id}, workflowId=${workflowId}`
)
return createSuccessResponse({
success: true,
message: 'Workflow successfully unpublished from marketplace',

View File

@@ -275,14 +275,14 @@ export async function GET(request: NextRequest) {
return section.map((item) => {
if ('state' in item) {
// Create a new object without the state field, but with workflowState
const { state, ...rest } = item;
const { state, ...rest } = item
return {
...rest,
workflowState: state
};
workflowState: state,
}
}
return item;
});
return item
})
}
if (result.popular.length > 0) {

View File

@@ -117,7 +117,7 @@ export async function POST(request: Request) {
startTime: startTimeISO,
endTime: endTimeISO,
})
// Return the response with CORS headers
return NextResponse.json(responseWithTimingData, {
headers: {
@@ -132,7 +132,7 @@ export async function POST(request: Request) {
} catch (error: any) {
logger.error(`[${requestId}] Proxy request failed`, {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined
stack: error instanceof Error ? error.stack : undefined,
})
// Add timing information even to error responses
@@ -140,19 +140,22 @@ export async function POST(request: Request) {
const endTimeISO = endTime.toISOString()
const duration = endTime.getTime() - startTime.getTime()
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : String(error),
startTime: startTimeISO,
endTime: endTimeISO,
duration,
}, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : String(error),
startTime: startTimeISO,
endTime: endTimeISO,
duration,
},
})
{
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
}
)
}
}

View File

@@ -9,8 +9,8 @@ import { workflowSchedule } from '@/db/schema'
const logger = createLogger('ScheduledAPI')
// Track recent requests to reduce redundant logging
const recentRequests = new Map<string, number>();
const LOGGING_THROTTLE_MS = 5000; // 5 seconds between logging for the same workflow
const recentRequests = new Map<string, number>()
const LOGGING_THROTTLE_MS = 5000 // 5 seconds between logging for the same workflow
/**
* Get schedule information for a workflow
@@ -20,7 +20,7 @@ export async function GET(req: NextRequest) {
const url = new URL(req.url)
const workflowId = url.searchParams.get('workflowId')
const mode = url.searchParams.get('mode')
// Skip processing if mode is provided and not 'schedule'
if (mode && mode !== 'schedule') {
return NextResponse.json({ schedule: null })
@@ -38,13 +38,13 @@ export async function GET(req: NextRequest) {
}
// Check if we should log this request (throttle logging for repeat requests)
const now = Date.now();
const lastLog = recentRequests.get(workflowId) || 0;
const shouldLog = now - lastLog > LOGGING_THROTTLE_MS;
const now = Date.now()
const lastLog = recentRequests.get(workflowId) || 0
const shouldLog = now - lastLog > LOGGING_THROTTLE_MS
if (shouldLog) {
logger.info(`[${requestId}] Getting schedule for workflow ${workflowId}`)
recentRequests.set(workflowId, now);
recentRequests.set(workflowId, now)
}
// Find the schedule for this workflow
@@ -55,8 +55,8 @@ export async function GET(req: NextRequest) {
.limit(1)
// Set cache control headers to reduce repeated API calls
const headers = new Headers();
headers.set('Cache-Control', 'max-age=30'); // Cache for 30 seconds
const headers = new Headers()
headers.set('Cache-Control', 'max-age=30') // Cache for 30 seconds
if (schedule.length === 0) {
return NextResponse.json({ schedule: null }, { headers })

View File

@@ -59,36 +59,36 @@ export async function POST(req: NextRequest) {
// Check if there's a valid schedule configuration
const hasScheduleConfig = (() => {
const getValue = (id: string): string => {
const value = getSubBlockValue(starterBlock, id);
return value && value.trim() !== '' ? value : '';
};
const value = getSubBlockValue(starterBlock, id)
return value && value.trim() !== '' ? value : ''
}
if (scheduleType === 'minutes' && getValue('minutesInterval')) {
return true;
return true
}
if (scheduleType === 'hourly' && getValue('hourlyMinute') !== '') {
return true;
return true
}
if (scheduleType === 'daily' && getValue('dailyTime')) {
return true;
return true
}
if (scheduleType === 'weekly' && getValue('weeklyDay') &&
getValue('weeklyDayTime')) {
return true;
if (scheduleType === 'weekly' && getValue('weeklyDay') && getValue('weeklyDayTime')) {
return true
}
if (scheduleType === 'monthly' && getValue('monthlyDay') &&
getValue('monthlyTime')) {
return true;
if (scheduleType === 'monthly' && getValue('monthlyDay') && getValue('monthlyTime')) {
return true
}
if (scheduleType === 'custom' && getValue('cronExpression')) {
return true;
return true
}
return false;
})();
return false
})()
// If the workflow is not configured for scheduling, delete any existing schedule
if (startWorkflow !== 'schedule' && !hasScheduleConfig) {
logger.info(`[${requestId}] Removing schedule for workflow ${workflowId} - no valid configuration found`)
logger.info(
`[${requestId}] Removing schedule for workflow ${workflowId} - no valid configuration found`
)
await db.delete(workflowSchedule).where(eq(workflowSchedule.workflowId, workflowId))
return NextResponse.json({ message: 'Schedule removed' })
@@ -96,7 +96,9 @@ export async function POST(req: NextRequest) {
// If we're here, we either have startWorkflow === 'schedule' or hasScheduleConfig is true
if (startWorkflow !== 'schedule') {
logger.info(`[${requestId}] Setting workflow to scheduled mode based on schedule configuration`)
logger.info(
`[${requestId}] Setting workflow to scheduled mode based on schedule configuration`
)
// The UI should handle this, but as a fallback we'll assume the user intended to schedule
// the workflow even if startWorkflow wasn't set properly
}

View File

@@ -3,8 +3,8 @@
*
* @vitest-environment node
*/
import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { NextRequest } from 'next/server'
import { createMockRequest } from '@/app/api/__test-utils__/utils'
describe('Workflow Execution API Route', () => {

View File

@@ -9,10 +9,13 @@ import { workflow } from '@/db/schema'
const logger = createLogger('WorkflowAPI')
// Define marketplace data schema
const MarketplaceDataSchema = z.object({
id: z.string(),
status: z.enum(['owner', 'temp'])
}).nullable().optional()
const MarketplaceDataSchema = z
.object({
id: z.string(),
status: z.enum(['owner', 'temp']),
})
.nullable()
.optional()
// Schema for workflow data
const WorkflowStateSchema = z.object({
@@ -26,7 +29,7 @@ const WorkflowStateSchema = z.object({
.optional()
.transform((val) => (typeof val === 'string' ? new Date(val) : val)),
isPublished: z.boolean().optional(),
marketplaceData: MarketplaceDataSchema
marketplaceData: MarketplaceDataSchema,
})
const WorkflowSchema = z.object({
@@ -35,7 +38,7 @@ const WorkflowSchema = z.object({
description: z.string().optional(),
color: z.string().optional(),
state: WorkflowStateSchema,
marketplaceData: MarketplaceDataSchema
marketplaceData: MarketplaceDataSchema,
})
const SyncPayloadSchema = z.object({
@@ -151,7 +154,8 @@ export async function POST(req: NextRequest) {
dbWorkflow.name !== clientWorkflow.name ||
dbWorkflow.description !== clientWorkflow.description ||
dbWorkflow.color !== clientWorkflow.color ||
JSON.stringify(dbWorkflow.marketplaceData) !== JSON.stringify(clientWorkflow.marketplaceData)
JSON.stringify(dbWorkflow.marketplaceData) !==
JSON.stringify(clientWorkflow.marketplaceData)
if (needsUpdate) {
operations.push(

View File

@@ -110,8 +110,9 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
condition: {
// field: 'operation',
// value: 'list',
// and: {
field: 'folderId', value: ''
// and: {
field: 'folderId',
value: '',
// },
},
},
@@ -137,8 +138,8 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
config: {
tool: (params) => {
// Since we only have 'list' now, we can simplify this
return 'google_drive_list';
return 'google_drive_list'
// switch (params.operation) {
// case 'upload':
// return 'google_drive_upload'

View File

@@ -132,8 +132,8 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
config: {
tool: (params) => {
// Since we only have send_gmail now, we can simplify this
return 'gmail_send';
return 'gmail_send'
// switch (params.operation) {
// case 'send_gmail':
// return 'gmail_send'

View File

@@ -81,15 +81,15 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
const { data, ...rest } = params
// Parse JSON data if it's a string
let parsedData;
let parsedData
if (data && typeof data === 'string') {
try {
parsedData = JSON.parse(data);
parsedData = JSON.parse(data)
} catch (e) {
throw new Error('Invalid JSON data format');
throw new Error('Invalid JSON data format')
}
} else {
parsedData = data;
parsedData = data
}
return {
@@ -111,8 +111,8 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
response: {
type: {
message: 'string',
results: 'json'
}
results: 'json',
},
},
},
}

View File

@@ -60,6 +60,12 @@ import { cn } from '@/lib/utils'
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive> & {

View File

@@ -93,12 +93,8 @@
"name": "account_user_id_user_id_fk",
"tableFrom": "account",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -170,12 +166,8 @@
"name": "api_key_user_id_user_id_fk",
"tableFrom": "api_key",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -185,9 +177,7 @@
"api_key_key_unique": {
"name": "api_key_key_unique",
"nullsNotDistinct": false,
"columns": [
"key"
]
"columns": ["key"]
}
},
"policies": {},
@@ -230,12 +220,8 @@
"name": "environment_user_id_user_id_fk",
"tableFrom": "environment",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -245,9 +231,7 @@
"environment_user_id_unique": {
"name": "environment_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
"columns": ["user_id"]
}
},
"policies": {},
@@ -341,12 +325,8 @@
"name": "marketplace_workflow_id_workflow_id_fk",
"tableFrom": "marketplace",
"tableTo": "workflow",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["workflow_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
},
@@ -354,12 +334,8 @@
"name": "marketplace_author_id_user_id_fk",
"tableFrom": "marketplace",
"tableTo": "user",
"columnsFrom": [
"author_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["author_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
@@ -428,12 +404,8 @@
"name": "marketplace_star_marketplace_id_marketplace_id_fk",
"tableFrom": "marketplace_star",
"tableTo": "marketplace",
"columnsFrom": [
"marketplace_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["marketplace_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
},
@@ -441,12 +413,8 @@
"name": "marketplace_star_user_id_user_id_fk",
"tableFrom": "marketplace_star",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
@@ -516,12 +484,8 @@
"name": "session_user_id_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -531,9 +495,7 @@
"session_token_unique": {
"name": "session_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
"columns": ["token"]
}
},
"policies": {},
@@ -576,12 +538,8 @@
"name": "settings_user_id_user_id_fk",
"tableFrom": "settings",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -591,9 +549,7 @@
"settings_user_id_unique": {
"name": "settings_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
"columns": ["user_id"]
}
},
"policies": {},
@@ -654,9 +610,7 @@
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
"columns": ["email"]
}
},
"policies": {},
@@ -735,12 +689,8 @@
"name": "user_stats_user_id_user_id_fk",
"tableFrom": "user_stats",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -750,9 +700,7 @@
"user_stats_user_id_unique": {
"name": "user_stats_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
"columns": ["user_id"]
}
},
"policies": {},
@@ -853,9 +801,7 @@
"waitlist_email_unique": {
"name": "waitlist_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
"columns": ["email"]
}
},
"policies": {},
@@ -940,12 +886,8 @@
"name": "webhook_workflow_id_workflow_id_fk",
"tableFrom": "webhook",
"tableTo": "workflow",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["workflow_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -1076,12 +1018,8 @@
"name": "workflow_user_id_user_id_fk",
"tableFrom": "workflow",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -1158,12 +1096,8 @@
"name": "workflow_logs_workflow_id_workflow_id_fk",
"tableFrom": "workflow_logs",
"tableTo": "workflow",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["workflow_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -1235,12 +1169,8 @@
"name": "workflow_schedule_workflow_id_workflow_id_fk",
"tableFrom": "workflow_schedule",
"tableTo": "workflow",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["workflow_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -1250,9 +1180,7 @@
"workflow_schedule_workflow_id_unique": {
"name": "workflow_schedule_workflow_id_unique",
"nullsNotDistinct": false,
"columns": [
"workflow_id"
]
"columns": ["workflow_id"]
}
},
"policies": {},
@@ -1271,4 +1199,4 @@
"schemas": {},
"tables": {}
}
}
}

View File

@@ -93,12 +93,8 @@
"name": "account_user_id_user_id_fk",
"tableFrom": "account",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -170,12 +166,8 @@
"name": "api_key_user_id_user_id_fk",
"tableFrom": "api_key",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -185,9 +177,7 @@
"api_key_key_unique": {
"name": "api_key_key_unique",
"nullsNotDistinct": false,
"columns": [
"key"
]
"columns": ["key"]
}
},
"policies": {},
@@ -230,12 +220,8 @@
"name": "environment_user_id_user_id_fk",
"tableFrom": "environment",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -245,9 +231,7 @@
"environment_user_id_unique": {
"name": "environment_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
"columns": ["user_id"]
}
},
"policies": {},
@@ -334,12 +318,8 @@
"name": "marketplace_workflow_id_workflow_id_fk",
"tableFrom": "marketplace",
"tableTo": "workflow",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["workflow_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
},
@@ -347,12 +327,8 @@
"name": "marketplace_author_id_user_id_fk",
"tableFrom": "marketplace",
"tableTo": "user",
"columnsFrom": [
"author_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["author_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
@@ -422,12 +398,8 @@
"name": "session_user_id_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -437,9 +409,7 @@
"session_token_unique": {
"name": "session_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
"columns": ["token"]
}
},
"policies": {},
@@ -482,12 +452,8 @@
"name": "settings_user_id_user_id_fk",
"tableFrom": "settings",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -497,9 +463,7 @@
"settings_user_id_unique": {
"name": "settings_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
"columns": ["user_id"]
}
},
"policies": {},
@@ -560,9 +524,7 @@
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
"columns": ["email"]
}
},
"policies": {},
@@ -641,12 +603,8 @@
"name": "user_stats_user_id_user_id_fk",
"tableFrom": "user_stats",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -656,9 +614,7 @@
"user_stats_user_id_unique": {
"name": "user_stats_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
"columns": ["user_id"]
}
},
"policies": {},
@@ -759,9 +715,7 @@
"waitlist_email_unique": {
"name": "waitlist_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
"columns": ["email"]
}
},
"policies": {},
@@ -846,12 +800,8 @@
"name": "webhook_workflow_id_workflow_id_fk",
"tableFrom": "webhook",
"tableTo": "workflow",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["workflow_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -987,12 +937,8 @@
"name": "workflow_user_id_user_id_fk",
"tableFrom": "workflow",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -1069,12 +1015,8 @@
"name": "workflow_logs_workflow_id_workflow_id_fk",
"tableFrom": "workflow_logs",
"tableTo": "workflow",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["workflow_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -1146,12 +1088,8 @@
"name": "workflow_schedule_workflow_id_workflow_id_fk",
"tableFrom": "workflow_schedule",
"tableTo": "workflow",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["workflow_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -1161,9 +1099,7 @@
"workflow_schedule_workflow_id_unique": {
"name": "workflow_schedule_workflow_id_unique",
"nullsNotDistinct": false,
"columns": [
"workflow_id"
]
"columns": ["workflow_id"]
}
},
"policies": {},
@@ -1182,4 +1118,4 @@
"schemas": {},
"tables": {}
}
}
}

View File

@@ -199,4 +199,4 @@
"breakpoints": true
}
]
}
}

View File

@@ -79,7 +79,7 @@ export const workflow = pgTable('workflow', {
lastRunAt: timestamp('last_run_at'),
variables: json('variables').default('{}'),
marketplaceData: json('marketplace_data'), // Format: { id: string, status: 'owner' | 'temp' }
// These columns are kept for backward compatibility during migration
// @deprecated - Use marketplaceData instead
isPublished: boolean('is_published').notNull().default(false),

View File

@@ -3,8 +3,8 @@
*
* @vitest-environment node
*/
import path from 'path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import path from 'path'
import type { FileParser, FileParseResult } from './types'
// Mock file system modules

View File

@@ -96,8 +96,18 @@ export const parseCronToHumanReadable = (cronExpression: string): string => {
// Day component
if (dayOfMonth !== '*' && month !== '*') {
const months = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December',
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
]
if (month.includes(',')) {
const monthNames = month.split(',').map((m) => months[parseInt(m, 10) - 1])
@@ -133,15 +143,15 @@ export const getScheduleInfo = (
lastRanAt: string | null,
scheduleType?: string | null
): {
scheduleTiming: string;
nextRunFormatted: string | null;
lastRunFormatted: string | null;
scheduleTiming: string
nextRunFormatted: string | null
lastRunFormatted: string | null
} => {
if (!nextRunAt) {
return {
scheduleTiming: 'Unknown schedule',
nextRunFormatted: null,
lastRunFormatted: null
lastRunFormatted: null,
}
}
@@ -156,6 +166,6 @@ export const getScheduleInfo = (
return {
scheduleTiming,
nextRunFormatted: formatDateTime(new Date(nextRunAt)),
lastRunFormatted: lastRanAt ? formatDateTime(new Date(lastRanAt)) : null
lastRunFormatted: lastRanAt ? formatDateTime(new Date(lastRanAt)) : null,
}
}
}

View File

@@ -46,35 +46,32 @@ export const useNotificationStore = create<NotificationStore>()(
set((state) => {
// Add new notification at the start and limit total count
let newNotifications = [notification, ...state.notifications].slice(
0,
MAX_NOTIFICATIONS
)
let newNotifications = [notification, ...state.notifications].slice(0, MAX_NOTIFICATIONS)
// Check if we need to auto-fade older notifications if we exceed the limit
const workflowVisibleCount = get().getVisibleNotificationCount(workflowId);
const workflowVisibleCount = get().getVisibleNotificationCount(workflowId)
if (workflowVisibleCount > MAX_VISIBLE_NOTIFICATIONS) {
// Find the oldest non-persistent visible notification from this workflow to fade out
newNotifications = newNotifications.map((n, index) => {
// Don't touch the newly added notification
if (index === 0) return n;
if (index === 0) return n
// Only target notifications from the same workflow that are visible, not persistent, and not already fading
if (
n.workflowId === workflowId &&
n.isVisible &&
!n.options?.isPersistent &&
n.workflowId === workflowId &&
n.isVisible &&
!n.options?.isPersistent &&
!n.isFading
) {
// Mark it as fading - the oldest one will be the first we encounter
return { ...n, isFading: true };
return { ...n, isFading: true }
}
return n;
});
return n
})
}
persistNotifications(newNotifications)
return { notifications: newNotifications }
})
@@ -124,43 +121,43 @@ export const useNotificationStore = create<NotificationStore>()(
const newNotifications = state.notifications.map((n) => {
if (n.id === id) {
// Only update visibility state, preserve timestamp and position
return {
...n,
isVisible: true,
read: false,
isFading: false
return {
...n,
isVisible: true,
read: false,
isFading: false,
}
}
return n
})
// Check if we need to auto-fade older notifications due to the limit
const workflowId = notification.workflowId;
const workflowVisibleCount = get().getVisibleNotificationCount(workflowId);
let updatedNotifications = [...newNotifications];
const workflowId = notification.workflowId
const workflowVisibleCount = get().getVisibleNotificationCount(workflowId)
let updatedNotifications = [...newNotifications]
if (workflowVisibleCount > MAX_VISIBLE_NOTIFICATIONS) {
// Find the oldest non-persistent visible notification to fade out
updatedNotifications = updatedNotifications.map((n) => {
// Don't touch the newly shown notification
if (n.id === id) return n;
if (n.id === id) return n
// Only target notifications from the same workflow that are visible, not persistent, and not already fading
if (
n.workflowId === workflowId &&
n.isVisible &&
!n.options?.isPersistent &&
n.workflowId === workflowId &&
n.isVisible &&
!n.options?.isPersistent &&
!n.isFading
) {
// Mark it as fading - the oldest one will be the first we encounter
return { ...n, isFading: true };
return { ...n, isFading: true }
}
return n;
});
return n
})
}
// If notification is not persistent, restart the fade timer
if (!notification.options?.isPersistent) {
setTimeout(() => {
@@ -174,7 +171,7 @@ export const useNotificationStore = create<NotificationStore>()(
})
}, NOTIFICATION_TIMEOUT)
}
persistNotifications(updatedNotifications)
return { notifications: updatedNotifications }
}),
@@ -212,15 +209,13 @@ export const useNotificationStore = create<NotificationStore>()(
getWorkflowNotifications: (workflowId) => {
return get().notifications.filter((n) => n.workflowId === workflowId)
},
getVisibleNotificationCount: (workflowId) => {
if (!workflowId) return 0;
if (!workflowId) return 0
return get().notifications.filter(
n => n.workflowId === workflowId &&
n.isVisible &&
!n.read
).length;
(n) => n.workflowId === workflowId && n.isVisible && !n.read
).length
},
}),
{ name: 'notification-store' }

View File

@@ -144,12 +144,12 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
lastModified: new Date(),
description: options.description || 'New workflow',
color: options.marketplaceId ? '#808080' : getNextWorkflowColor(workflows), // Gray for marketplace imports
marketplaceData: options.marketplaceId
? { id: options.marketplaceId, status: 'temp' as const }
marketplaceData: options.marketplaceId
? { id: options.marketplaceId, status: 'temp' as const }
: undefined,
}
let initialState;
let initialState
// If this is a marketplace import with existing state
if (options.marketplaceId && options.marketplaceState) {
@@ -177,7 +177,7 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
},
lastSaved: Date.now(),
}
logger.info(`Created workflow from marketplace: ${options.marketplaceId}`)
} else {
// Create starter block for new workflow
@@ -342,7 +342,11 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
* @param metadata - Additional metadata like name, description from marketplace
* @returns The ID of the newly created workflow
*/
createMarketplaceWorkflow: (marketplaceId: string, state: any, metadata: Partial<WorkflowMetadata>) => {
createMarketplaceWorkflow: (
marketplaceId: string,
state: any,
metadata: Partial<WorkflowMetadata>
) => {
const { workflows } = get()
const id = crypto.randomUUID()

View File

@@ -1,5 +1,5 @@
export interface MarketplaceData {
id: string // Marketplace entry ID to track original marketplace source
id: string // Marketplace entry ID to track original marketplace source
status: 'owner' | 'temp'
}
@@ -23,11 +23,11 @@ export interface WorkflowRegistryActions {
setActiveWorkflow: (id: string) => Promise<void>
removeWorkflow: (id: string) => void
updateWorkflow: (id: string, metadata: Partial<WorkflowMetadata>) => void
createWorkflow: (options?: {
isInitial?: boolean,
marketplaceId?: string,
marketplaceState?: any,
name?: string,
createWorkflow: (options?: {
isInitial?: boolean
marketplaceId?: string
marketplaceState?: any
name?: string
description?: string
}) => string
}

View File

@@ -78,14 +78,14 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
method: params.method || 'GET',
headers: transformTable(params.headers || null),
}
// Add body for non-GET requests
if (params.method && params.method !== 'GET' && params.body) {
if (typeof params.body === 'object') {
fetchOptions.body = JSON.stringify(params.body)
// Ensure Content-Type is set
if (fetchOptions.headers) {
(fetchOptions.headers as Record<string, string>)['Content-Type'] = 'application/json'
;(fetchOptions.headers as Record<string, string>)['Content-Type'] = 'application/json'
} else {
fetchOptions.headers = { 'Content-Type': 'application/json' }
}
@@ -93,7 +93,7 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
fetchOptions.body = params.body
}
}
// Handle form data
if (params.formData) {
const formData = new FormData()
@@ -102,45 +102,45 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
})
fetchOptions.body = formData
}
// Handle timeout
const controller = new AbortController()
const timeout = params.timeout || 50000
const timeoutId = setTimeout(() => controller.abort(), timeout)
fetchOptions.signal = controller.signal
try {
// Process URL with path parameters and query params
let url = params.url
// Replace path parameters
if (params.pathParams) {
Object.entries(params.pathParams).forEach(([key, value]) => {
url = url.replace(`:${key}`, encodeURIComponent(value))
})
}
// Handle query parameters
const queryParamsObj = transformTable(params.params || null)
const queryString = Object.entries(queryParamsObj)
.filter(([_, value]) => value !== undefined && value !== null)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&')
if (queryString) {
url += (url.includes('?') ? '&' : '?') + queryString
}
// Make the actual fetch request
const response = await fetch(url, fetchOptions)
clearTimeout(timeoutId)
// Convert Headers to a plain object
const headers: Record<string, string> = {}
response.headers.forEach((value, key) => {
headers[key] = value
})
// Parse response based on content type
let data
try {
@@ -152,7 +152,7 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
} catch (error) {
data = await response.text()
}
return {
success: response.ok,
output: {
@@ -160,11 +160,11 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
status: response.status,
headers,
},
error: response.ok ? undefined : `HTTP error ${response.status}: ${response.statusText}`
error: response.ok ? undefined : `HTTP error ${response.status}: ${response.statusText}`,
}
} catch (error: any) {
clearTimeout(timeoutId)
// Handle specific abort error
if (error.name === 'AbortError') {
return {
@@ -174,10 +174,10 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
status: 0,
headers: {},
},
error: `Request timeout after ${timeout}ms`
error: `Request timeout after ${timeout}ms`,
}
}
return {
success: false,
output: {
@@ -185,7 +185,7 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
status: 0,
headers: {},
},
error: error.message || 'Failed to fetch'
error: error.message || 'Failed to fetch',
}
}
} catch (error: any) {
@@ -196,7 +196,7 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
status: 0,
headers: {},
},
error: error.message || 'Error preparing HTTP request'
error: error.message || 'Error preparing HTTP request',
}
}
},

View File

@@ -1,3 +1,3 @@
import { mistralParserTool } from './parser'
export { mistralParserTool }
export { mistralParserTool }

View File

@@ -12,68 +12,66 @@ export const insertTool: ToolConfig<SupabaseInsertParams, SupabaseInsertResponse
additionalScopes: ['database.write', 'projects.read'],
},
params: {
apiKey: {
type: 'string',
required: true,
apiKey: {
type: 'string',
required: true,
requiredForToolCall: true,
description: 'Your Supabase client anon key'
description: 'Your Supabase client anon key',
},
projectId: {
type: 'string',
required: true,
projectId: {
type: 'string',
required: true,
requiredForToolCall: true,
description: 'Your Supabase project ID (e.g., jdrkgepadsdopsntdlom)'
description: 'Your Supabase project ID (e.g., jdrkgepadsdopsntdlom)',
},
table: { type: 'string', required: true },
data: { type: 'any', required: true },
},
request: {
url: (params) =>
`https://${params.projectId}.supabase.co/rest/v1/${params.table}?select=*`,
url: (params) => `https://${params.projectId}.supabase.co/rest/v1/${params.table}?select=*`,
method: 'POST',
headers: (params) => ({
'apikey': params.apiKey,
'Authorization': `Bearer ${params.apiKey}`,
apikey: params.apiKey,
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
'Prefer': 'return=representation'
Prefer: 'return=representation',
}),
body: (params) => {
// If data is an object but not an array, wrap it in an array
if (typeof params.data === 'object' && !Array.isArray(params.data)) {
return [params.data];
return [params.data]
}
// If it's already an array, return as is
return params.data;
return params.data
},
},
directExecution: async (params: SupabaseInsertParams) => {
try {
// Construct the URL for the Supabase REST API with select=* to return inserted data
const url = `https://${params.projectId}.supabase.co/rest/v1/${params.table}?select=*`;
const url = `https://${params.projectId}.supabase.co/rest/v1/${params.table}?select=*`
// Prepare the data - if it's an object but not an array, wrap it in an array
const dataToSend = typeof params.data === 'object' && !Array.isArray(params.data)
? [params.data]
: params.data;
const dataToSend =
typeof params.data === 'object' && !Array.isArray(params.data) ? [params.data] : params.data
// Insert the data
const response = await fetch(url, {
method: 'POST',
headers: {
'apikey': params.apiKey,
'Authorization': `Bearer ${params.apiKey}`,
apikey: params.apiKey,
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
'Prefer': 'return=representation'
Prefer: 'return=representation',
},
body: JSON.stringify(dataToSend),
});
})
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Error from Supabase: ${response.status} ${errorText}`);
const errorText = await response.text()
throw new Error(`Error from Supabase: ${response.status} ${errorText}`)
}
const data = await response.json();
const data = await response.json()
return {
success: true,
@@ -96,12 +94,12 @@ export const insertTool: ToolConfig<SupabaseInsertParams, SupabaseInsertResponse
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to insert data into Supabase');
const error = await response.json()
throw new Error(error.message || 'Failed to insert data into Supabase')
}
// Handle empty response case
const text = await response.text();
const text = await response.text()
if (!text || text.trim() === '') {
return {
success: true,
@@ -113,7 +111,7 @@ export const insertTool: ToolConfig<SupabaseInsertParams, SupabaseInsertResponse
}
}
const data = JSON.parse(text);
const data = JSON.parse(text)
return {
success: true,

View File

@@ -12,50 +12,49 @@ export const queryTool: ToolConfig<SupabaseQueryParams, SupabaseQueryResponse> =
additionalScopes: ['database.read', 'projects.read'],
},
params: {
apiKey: {
type: 'string',
required: true,
apiKey: {
type: 'string',
required: true,
requiredForToolCall: true,
description: 'Your Supabase client anon key'
description: 'Your Supabase client anon key',
},
projectId: {
type: 'string',
required: true,
projectId: {
type: 'string',
required: true,
requiredForToolCall: true,
description: 'Your Supabase project ID (e.g., jdrkgepadsdopsntdlom)'
description: 'Your Supabase project ID (e.g., jdrkgepadsdopsntdlom)',
},
table: { type: 'string', required: true },
filter: { type: 'object', required: false },
},
request: {
url: (params) =>
`https://${params.projectId}.supabase.co/rest/v1/${params.table}`,
url: (params) => `https://${params.projectId}.supabase.co/rest/v1/${params.table}`,
method: 'GET',
headers: (params) => ({
'apikey': params.apiKey,
'Authorization': `Bearer ${params.apiKey}`,
apikey: params.apiKey,
Authorization: `Bearer ${params.apiKey}`,
}),
},
directExecution: async (params: SupabaseQueryParams) => {
try {
// Construct the URL for the Supabase REST API
const url = `https://${params.projectId}.supabase.co/rest/v1/${params.table}?select=*`;
const url = `https://${params.projectId}.supabase.co/rest/v1/${params.table}?select=*`
// Fetch the data
const response = await fetch(url, {
method: 'GET',
headers: {
'apikey': params.apiKey,
'Authorization': `Bearer ${params.apiKey}`,
apikey: params.apiKey,
Authorization: `Bearer ${params.apiKey}`,
},
});
})
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Error from Supabase: ${response.status} ${errorText}`);
const errorText = await response.text()
throw new Error(`Error from Supabase: ${response.status} ${errorText}`)
}
const data = await response.json();
const data = await response.json()
return {
success: true,
@@ -78,11 +77,11 @@ export const queryTool: ToolConfig<SupabaseQueryParams, SupabaseQueryResponse> =
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to query data from Supabase');
const error = await response.json()
throw new Error(error.message || 'Failed to query data from Supabase')
}
const data = await response.json();
const data = await response.json()
return {
success: true,

View File

@@ -27,4 +27,4 @@ export interface SupabaseInsertResponse extends ToolResponse {
message: string
results: any
}
}
}

View File

@@ -1,5 +1,5 @@
import '@testing-library/jest-dom'
import { afterAll, vi } from 'vitest'
import '@testing-library/jest-dom'
// Mock global fetch
global.fetch = vi.fn(() =>