diff --git a/sim/app/api/workflows/sync/route.ts b/sim/app/api/workflows/sync/route.ts index ef923d7946..d3b94591b8 100644 --- a/sim/app/api/workflows/sync/route.ts +++ b/sim/app/api/workflows/sync/route.ts @@ -8,6 +8,12 @@ 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', 'star']) +}).nullable().optional() + // Schema for workflow data const WorkflowStateSchema = z.object({ blocks: z.record(z.any()), @@ -20,6 +26,7 @@ const WorkflowStateSchema = z.object({ .optional() .transform((val) => (typeof val === 'string' ? new Date(val) : val)), isPublished: z.boolean().optional(), + marketplaceData: MarketplaceDataSchema }) const WorkflowSchema = z.object({ @@ -28,6 +35,7 @@ const WorkflowSchema = z.object({ description: z.string().optional(), color: z.string().optional(), state: WorkflowStateSchema, + marketplaceData: MarketplaceDataSchema }) const SyncPayloadSchema = z.object({ @@ -114,6 +122,12 @@ export async function POST(req: NextRequest) { processedIds.add(id) const dbWorkflow = dbWorkflowMap.get(id) + // Handle legacy published workflows migration + // If client workflow has isPublished but no marketplaceData, create marketplaceData with owner status + if (clientWorkflow.state.isPublished && !clientWorkflow.marketplaceData) { + clientWorkflow.marketplaceData = { id: clientWorkflow.id, status: 'owner' } + } + if (!dbWorkflow) { // New workflow - create operations.push( @@ -124,6 +138,7 @@ export async function POST(req: NextRequest) { description: clientWorkflow.description, color: clientWorkflow.color, state: clientWorkflow.state, + marketplaceData: clientWorkflow.marketplaceData || null, lastSynced: now, createdAt: now, updatedAt: now, @@ -135,7 +150,8 @@ export async function POST(req: NextRequest) { JSON.stringify(dbWorkflow.state) !== JSON.stringify(clientWorkflow.state) || dbWorkflow.name !== clientWorkflow.name || dbWorkflow.description !== clientWorkflow.description || - dbWorkflow.color !== clientWorkflow.color + dbWorkflow.color !== clientWorkflow.color || + JSON.stringify(dbWorkflow.marketplaceData) !== JSON.stringify(clientWorkflow.marketplaceData) if (needsUpdate) { operations.push( @@ -146,6 +162,7 @@ export async function POST(req: NextRequest) { description: clientWorkflow.description, color: clientWorkflow.color, state: clientWorkflow.state, + marketplaceData: clientWorkflow.marketplaceData || null, lastSynced: now, updatedAt: now, }) diff --git a/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx b/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx index 7184e7aeff..bdbe856b16 100644 --- a/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx +++ b/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx @@ -53,7 +53,6 @@ import { cn } from '@/lib/utils' import { useNotificationStore } from '@/stores/notifications/store' import { getWorkflowWithValues } from '@/stores/workflows' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { useWorkflowStore } from '@/stores/workflows/workflow/store' import { CATEGORIES, getCategoryColor, @@ -144,8 +143,24 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) const [marketplaceInfo, setMarketplaceInfo] = useState(null) const [isLoading, setIsLoading] = useState(false) const { addNotification } = useNotificationStore() - const { activeWorkflowId, workflows } = useWorkflowRegistry() - const { isPublished, setPublishStatus } = useWorkflowStore() + const { activeWorkflowId, workflows, updateWorkflow } = useWorkflowRegistry() + + // Get marketplace data from the registry + const getMarketplaceData = () => { + if (!activeWorkflowId || !workflows[activeWorkflowId]) return null + return workflows[activeWorkflowId].marketplaceData + } + + // Check if workflow is published to marketplace + const isPublished = () => { + return !!getMarketplaceData() + } + + // Check if the current user is the owner of the published workflow + const isOwner = () => { + const marketplaceData = getMarketplaceData() + return marketplaceData?.status === 'owner' + } // Initialize form with react-hook-form const form = useForm({ @@ -161,7 +176,7 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) // Fetch marketplace information when the modal opens and the workflow is published useEffect(() => { async function fetchMarketplaceInfo() { - if (!open || !activeWorkflowId || !isPublished) { + if (!open || !activeWorkflowId || !isPublished()) { setMarketplaceInfo(null) return } @@ -185,16 +200,16 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) } fetchMarketplaceInfo() - }, [open, activeWorkflowId, isPublished, addNotification]) + }, [open, activeWorkflowId, addNotification]) // Update form values when the active workflow changes or modal opens useEffect(() => { - if (open && activeWorkflowId && workflows[activeWorkflowId] && !isPublished) { + if (open && activeWorkflowId && workflows[activeWorkflowId] && !isPublished()) { const workflow = workflows[activeWorkflowId] form.setValue('name', workflow.name) form.setValue('description', workflow.description || '') } - }, [open, activeWorkflowId, workflows, form, isPublished]) + }, [open, activeWorkflowId, workflows, form]) // Listen for the custom event to open the marketplace modal useEffect(() => { @@ -255,8 +270,10 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) throw new Error(errorData.error || 'Failed to publish workflow') } - // Update the publish status with the current date - setPublishStatus(true) + // Update the marketplace data in the workflow registry + updateWorkflow(activeWorkflowId, { + marketplaceData: { id: activeWorkflowId, status: 'owner' }, + }) // Add a marketplace notification with detailed information addNotification( @@ -293,8 +310,10 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) throw new Error(errorData.error || 'Failed to unpublish workflow') } - // Update the publish status - setPublishStatus(false) + // Remove the marketplace data from the workflow registry + updateWorkflow(activeWorkflowId, { + marketplaceData: null, + }) // Add a notification addNotification( @@ -402,26 +421,20 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) - {/* Action buttons */} -
- -
+ {/* Action buttons - Only show unpublish if owner */} + {isOwner() && ( +
+ +
+ )} ) } @@ -551,7 +564,7 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
- {isPublished ? 'Marketplace Information' : 'Publish to Marketplace'} + {isPublished() ? 'Marketplace Information' : 'Publish to Marketplace'} - - - {isPublishing - ? 'Publishing...' - : isPublished - ? 'Published to Marketplace' - : 'Publish to Marketplace'} - - - ) + const renderPublishButton = () => { + const isPublished = isPublishedToMarketplace() + + return ( + + + + + + {isPublishing + ? 'Publishing...' + : isPublished + ? 'Published to Marketplace' + : 'Publish to Marketplace'} + + + ) + } /** * Render debug mode controls diff --git a/sim/app/w/components/sidebar/components/folder-section/folder-section.tsx b/sim/app/w/components/sidebar/components/folder-section/folder-section.tsx new file mode 100644 index 0000000000..2a2416649a --- /dev/null +++ b/sim/app/w/components/sidebar/components/folder-section/folder-section.tsx @@ -0,0 +1,70 @@ +'use client' + +import { useEffect, useState } from 'react' +import { usePathname } from 'next/navigation' +import { Folder } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +import { WorkflowMetadata } from '@/stores/workflows/registry/types' +import { NavItem } from '../nav-item/nav-item' + +interface FolderSectionProps { + title: string + workflows: WorkflowMetadata[] + defaultOpen?: boolean +} + +export function FolderSection({ title, workflows, defaultOpen = true }: FolderSectionProps) { + const [isOpen, setIsOpen] = useState(defaultOpen) + const pathname = usePathname() + + // Auto-expand the folder if the current path is one of the workflows inside it + useEffect(() => { + const isCurrentPathInThisFolder = workflows.some((workflow) => pathname === `/w/${workflow.id}`) + if (isCurrentPathInThisFolder) { + setIsOpen(true) + } + }, [pathname, workflows]) + + if (workflows.length === 0) { + return null + } + + return ( +
+ + + + + {title} + + + {isOpen && ( +
+ {workflows.map((workflow) => ( + +
+ + ))} +
+ )} +
+ ) +} diff --git a/sim/app/w/components/sidebar/sidebar.tsx b/sim/app/w/components/sidebar/sidebar.tsx index 8f6dcb1c5d..896fc225d9 100644 --- a/sim/app/w/components/sidebar/sidebar.tsx +++ b/sim/app/w/components/sidebar/sidebar.tsx @@ -4,11 +4,13 @@ import { useMemo, useState } from 'react' import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' import clsx from 'clsx' -import { Anvil, HelpCircle, Plus, ScrollText, Settings, Store } from 'lucide-react' +import { HelpCircle, Plus, ScrollText, Settings, Store } from 'lucide-react' import { AgentIcon } from '@/components/icons' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' +import { WorkflowMetadata } from '@/stores/workflows/registry/types' +import { FolderSection } from './components/folder-section/folder-section' import { HelpModal } from './components/help-modal/help-modal' import { NavItem } from './components/nav-item/nav-item' import { SettingsModal } from './components/settings-modal/settings-modal' @@ -20,11 +22,21 @@ export function Sidebar() { const [showSettings, setShowSettings] = useState(false) const [showHelp, setShowHelp] = useState(false) - // Sort workflows by lastModified date (which corresponds to createdAt for new workflows) - // Newest workflows at the top (ascending order by date) - const sortedWorkflows = useMemo(() => { - return Object.values(workflows).sort((a, b) => { - // Ensure we're comparing dates properly by converting to timestamps + // Separate regular workflows from temporary marketplace workflows + const { regularWorkflows, tempWorkflows } = useMemo(() => { + const regular: WorkflowMetadata[] = [] + const temp: WorkflowMetadata[] = [] + + Object.values(workflows).forEach((workflow) => { + if (workflow.marketplaceData?.status === 'temp') { + temp.push(workflow) + } else { + regular.push(workflow) + } + }) + + // Sort regular workflows by last modified date (newest first) + regular.sort((a, b) => { const dateA = a.lastModified instanceof Date ? a.lastModified.getTime() @@ -33,8 +45,23 @@ export function Sidebar() { b.lastModified instanceof Date ? b.lastModified.getTime() : new Date(b.lastModified).getTime() - return dateB - dateA // Ascending order (oldest last, newest first) + return dateB - dateA }) + + // Sort temp workflows by last modified date (newest first) + temp.sort((a, b) => { + const dateA = + a.lastModified instanceof Date + ? a.lastModified.getTime() + : new Date(a.lastModified).getTime() + const dateB = + b.lastModified instanceof Date + ? b.lastModified.getTime() + : new Date(b.lastModified).getTime() + return dateB - dateA + }) + + return { regularWorkflows: regular, tempWorkflows: temp } }, [workflows]) // Create workflow @@ -88,14 +115,11 @@ export function Sidebar() { {/* Scrollable workflows section */} @@ -123,29 +147,6 @@ export function Sidebar() { Marketplace - {/* Agents */} - {/* - - - - Agents - */} - {/* Logs */} diff --git a/sim/db/migrations/0025_curved_jubilee.sql b/sim/db/migrations/0025_curved_jubilee.sql new file mode 100644 index 0000000000..141a5f0dcf --- /dev/null +++ b/sim/db/migrations/0025_curved_jubilee.sql @@ -0,0 +1 @@ +ALTER TABLE "workflow" ALTER COLUMN "marketplace_data" DROP DEFAULT; \ No newline at end of file diff --git a/sim/db/migrations/meta/0025_snapshot.json b/sim/db/migrations/meta/0025_snapshot.json new file mode 100644 index 0000000000..8a350c814d --- /dev/null +++ b/sim/db/migrations/meta/0025_snapshot.json @@ -0,0 +1,1273 @@ +{ + "id": "e80af3b3-8eef-45fd-b4d1-749b50f1889d", + "prevId": "2df7e46c-e8b8-4fb2-8d97-f7e5dbc23519", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.marketplace": { + "name": "marketplace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stars": { + "name": "stars", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "marketplace_workflow_id_workflow_id_fk": { + "name": "marketplace_workflow_id_workflow_id_fk", + "tableFrom": "marketplace", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "marketplace_author_id_user_id_fk": { + "name": "marketplace_author_id_user_id_fk", + "tableFrom": "marketplace", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.marketplace_star": { + "name": "marketplace_star", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "marketplace_id": { + "name": "marketplace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_marketplace_idx": { + "name": "user_marketplace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "marketplace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "marketplace_star_marketplace_id_marketplace_id_fk": { + "name": "marketplace_star_marketplace_id_marketplace_id_fk", + "tableFrom": "marketplace_star", + "tableTo": "marketplace", + "columnsFrom": [ + "marketplace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "marketplace_star_user_id_user_id_fk": { + "name": "marketplace_star_user_id_user_id_fk", + "tableFrom": "marketplace_star", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "general": { + "name": "general", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_idx": { + "name": "path_idx", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "collaborators": { + "name": "collaborators", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "marketplace_data": { + "name": "marketplace_data", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_logs": { + "name": "workflow_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_logs_workflow_id_workflow_id_fk": { + "name": "workflow_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_logs", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workflow_schedule_workflow_id_unique": { + "name": "workflow_schedule_workflow_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workflow_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/sim/db/migrations/meta/_journal.json b/sim/db/migrations/meta/_journal.json index aff642a993..c5d02f699a 100644 --- a/sim/db/migrations/meta/_journal.json +++ b/sim/db/migrations/meta/_journal.json @@ -176,6 +176,13 @@ "when": 1743642824678, "tag": "0024_next_whizzer", "breakpoints": true + }, + { + "idx": 25, + "version": "7", + "when": 1743643010845, + "tag": "0025_curved_jubilee", + "breakpoints": true } ] } \ No newline at end of file diff --git a/sim/db/schema.ts b/sim/db/schema.ts index cf42416b5d..547f29686a 100644 --- a/sim/db/schema.ts +++ b/sim/db/schema.ts @@ -77,7 +77,7 @@ export const workflow = pgTable('workflow', { runCount: integer('run_count').notNull().default(0), lastRunAt: timestamp('last_run_at'), variables: json('variables').default('{}'), - marketplaceData: json('marketplace_data').default(null), // Format: { id: string, status: 'owner' | 'temp' | 'star' } + marketplaceData: json('marketplace_data'), // Format: { id: string, status: 'owner' | 'temp' | 'star' } // These columns are kept for backward compatibility during migration // and should be marked as deprecated diff --git a/sim/stores/workflows/index.ts b/sim/stores/workflows/index.ts index 01a58c96e5..edd9735b14 100644 --- a/sim/stores/workflows/index.ts +++ b/sim/stores/workflows/index.ts @@ -52,6 +52,7 @@ export function getWorkflowWithValues(workflowId: string) { name: metadata.name, description: metadata.description, color: metadata.color || '#3972F6', + marketplaceData: metadata.marketplaceData || null, state: { blocks: mergedBlocks, edges: workflowState.edges, @@ -114,6 +115,7 @@ export function getAllWorkflowsWithValues() { name: metadata.name, description: metadata.description, color: metadata.color || '#3972F6', + marketplaceData: metadata.marketplaceData || null, state: { blocks: mergedBlocks, edges: workflowState.edges, diff --git a/sim/stores/workflows/registry/store.ts b/sim/stores/workflows/registry/store.ts index fb2cea1ed1..f028465e5f 100644 --- a/sim/stores/workflows/registry/store.ts +++ b/sim/stores/workflows/registry/store.ts @@ -142,8 +142,9 @@ export const useWorkflowRegistry = create()( lastModified: new Date(), description: options.description || 'New workflow', color: options.marketplaceId ? '#808080' : getNextWorkflowColor(workflows), // Gray for marketplace imports - marketplaceStatus: options.marketplaceId ? 'temp' : undefined, - marketplaceId: options.marketplaceId, + marketplaceData: options.marketplaceId + ? { id: options.marketplaceId, status: 'temp' as const } + : undefined, } let initialState; @@ -350,8 +351,7 @@ export const useWorkflowRegistry = create()( lastModified: new Date(), description: metadata.description || 'Imported from marketplace', color: metadata.color || getNextWorkflowColor(workflows), - marketplaceStatus: 'temp', // Initial status is temp, user can star it later - marketplaceId: marketplaceId, // Reference to original marketplace workflow + marketplaceData: { id: marketplaceId, status: 'temp' as const }, } // Prepare workflow state based on the marketplace workflow state diff --git a/sim/stores/workflows/registry/types.ts b/sim/stores/workflows/registry/types.ts index 84389b33e2..634f71bcf2 100644 --- a/sim/stores/workflows/registry/types.ts +++ b/sim/stores/workflows/registry/types.ts @@ -1,11 +1,15 @@ +export interface MarketplaceData { + id: string + status: 'owner' | 'temp' | 'star' +} + export interface WorkflowMetadata { id: string name: string lastModified: Date description?: string color: string - marketplaceStatus?: 'temp' | 'star' | null - marketplaceId?: string + marketplaceData?: MarketplaceData | null } export interface WorkflowRegistryState { diff --git a/sim/stores/workflows/workflow/store.ts b/sim/stores/workflows/workflow/store.ts index 59ea0e1902..daebe5761e 100644 --- a/sim/stores/workflows/workflow/store.ts +++ b/sim/stores/workflows/workflow/store.ts @@ -297,7 +297,6 @@ export const useWorkflowStore = create()( history: currentState.history, isDeployed: currentState.isDeployed, deployedAt: currentState.deployedAt, - isPublished: currentState.isPublished, lastSaved: Date.now(), }) @@ -621,17 +620,6 @@ export const useWorkflowStore = create()( get().updateLastSaved() workflowSync.sync() }, - - setPublishStatus: (isPublished: boolean) => { - const newState = { - ...get(), - isPublished, - } - - set(newState) - get().updateLastSaved() - workflowSync.sync() - }, })), { name: 'workflow-store' } ) diff --git a/sim/stores/workflows/workflow/types.ts b/sim/stores/workflows/workflow/types.ts index 26a985376f..817f4d3c55 100644 --- a/sim/stores/workflows/workflow/types.ts +++ b/sim/stores/workflows/workflow/types.ts @@ -41,7 +41,6 @@ export interface WorkflowState { lastUpdate?: number isDeployed?: boolean deployedAt?: Date - isPublished?: boolean } export interface WorkflowActions { @@ -63,7 +62,6 @@ export interface WorkflowActions { updateLoopForEachItems: (loopId: string, items: string) => void triggerUpdate: () => void setDeploymentStatus: (isDeployed: boolean, deployedAt?: Date) => void - setPublishStatus: (isPublished: boolean) => void } export type WorkflowStore = WorkflowState & WorkflowActions