mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
chore: run format
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { NextRequest } from 'next/server'
|
||||
import { vi } from 'vitest'
|
||||
import { NextRequest } from 'next/server'
|
||||
|
||||
/**
|
||||
* Mock sample workflow state for testing
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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> & {
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,4 +199,4 @@
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { mistralParserTool } from './parser'
|
||||
|
||||
export { mistralParserTool }
|
||||
export { mistralParserTool }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -27,4 +27,4 @@ export interface SupabaseInsertResponse extends ToolResponse {
|
||||
message: string
|
||||
results: any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(() =>
|
||||
|
||||
Reference in New Issue
Block a user