diff --git a/sim/app/api/marketplace/[id]/info/route.ts b/sim/app/api/marketplace/[id]/info/route.ts index c3e7d95b0..3edab386a 100644 --- a/sim/app/api/marketplace/[id]/info/route.ts +++ b/sim/app/api/marketplace/[id]/info/route.ts @@ -42,7 +42,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ description: marketplaceEntry.description, category: marketplaceEntry.category, authorName: marketplaceEntry.authorName, - stars: marketplaceEntry.stars, views: marketplaceEntry.views, createdAt: marketplaceEntry.createdAt, updatedAt: marketplaceEntry.updatedAt, diff --git a/sim/app/api/marketplace/[id]/unpublish/route.ts b/sim/app/api/marketplace/[id]/unpublish/route.ts index f636238d5..3bc1ec186 100644 --- a/sim/app/api/marketplace/[id]/unpublish/route.ts +++ b/sim/app/api/marketplace/[id]/unpublish/route.ts @@ -1,54 +1,108 @@ import { NextRequest } from 'next/server' import { eq } from 'drizzle-orm' import { createLogger } from '@/lib/logs/console-logger' -import { validateWorkflowAccess } from '@/app/api/workflows/middleware' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' +import { getSession } from '@/lib/auth' import { db } from '@/db' import * as schema from '@/db/schema' 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 + * - Only allows the owner to unpublish + */ export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const requestId = crypto.randomUUID().slice(0, 8) try { const { id } = await params - - // Validate access to the workflow (must be owner to unpublish) - // Pass false to requireDeployment since unpublishing doesn't require the workflow to be deployed - const validation = await validateWorkflowAccess(request, id, false) - if (validation.error) { - logger.warn(`[${requestId}] Workflow access validation failed: ${validation.error.message}`) - return createErrorResponse(validation.error.message, validation.error.status) + + // Get the session first for authorization + const session = await getSession() + if (!session?.user?.id) { + logger.warn(`[${requestId}] Unauthorized unpublish attempt for marketplace ID: ${id}`) + return createErrorResponse('Unauthorized', 401) } - // Check if workflow is published + const userId = session.user.id + + // Get the marketplace entry using the marketplace ID const marketplaceEntry = await db - .select() + .select({ + id: schema.marketplace.id, + workflowId: schema.marketplace.workflowId, + authorId: schema.marketplace.authorId, + name: schema.marketplace.name, + }) .from(schema.marketplace) - .where(eq(schema.marketplace.workflowId, id)) + .where(eq(schema.marketplace.id, id)) .limit(1) .then((rows) => rows[0]) if (!marketplaceEntry) { - logger.warn(`[${requestId}] No marketplace entry found for workflow: ${id}`) - return createErrorResponse('Workflow is not published to marketplace', 404) + 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( + `[${requestId}] User ${userId} tried to unpublish marketplace entry they don't own: ${id}, author: ${marketplaceEntry.authorId}` + ) + return createErrorResponse('You do not have permission to unpublish this workflow', 403) } - // Delete the marketplace entry - await db.delete(schema.marketplace).where(eq(schema.marketplace.workflowId, id)) - - // Update the workflow to mark it as unpublished - await db.update(schema.workflow).set({ isPublished: false }).where(eq(schema.workflow.id, id)) - - logger.info(`[${requestId}] Workflow unpublished from marketplace: ${id}`) - - return createSuccessResponse({ - success: true, - message: 'Workflow successfully unpublished from marketplace', - }) + const workflowId = marketplaceEntry.workflowId + + // Verify the workflow exists and belongs to the user + const workflow = await db + .select({ + id: schema.workflow.id, + userId: schema.workflow.userId, + }) + .from(schema.workflow) + .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 + } else if (workflow.userId !== userId) { + logger.warn( + `[${requestId}] Workflow ${workflowId} belongs to user ${workflow.userId}, not current user ${userId}` + ) + 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) + .set({ isPublished: false }) + .where(eq(schema.workflow.id, workflowId)) + } + + logger.info(`[${requestId}] Workflow "${marketplaceEntry.name}" unpublished from marketplace: ID=${id}, workflowId=${workflowId}`) + + return createSuccessResponse({ + success: true, + message: 'Workflow successfully unpublished from marketplace', + }) + } catch (dbError) { + logger.error(`[${requestId}] Database error unpublishing marketplace entry:`, dbError) + return createErrorResponse('Failed to unpublish workflow due to a database error', 500) + } } catch (error) { - logger.error(`[${requestId}] Error unpublishing workflow: ${(await params).id}`, error) + logger.error(`[${requestId}] Error unpublishing marketplace entry: ${(await params).id}`, error) return createErrorResponse('Failed to unpublish workflow', 500) } } diff --git a/sim/app/api/marketplace/publish/route.ts b/sim/app/api/marketplace/publish/route.ts index f13accfa4..0bae30342 100644 --- a/sim/app/api/marketplace/publish/route.ts +++ b/sim/app/api/marketplace/publish/route.ts @@ -112,7 +112,6 @@ export async function POST(request: NextRequest) { .values({ ...marketplaceEntry, createdAt: new Date(), - stars: 0, views: 0, }) .returning() diff --git a/sim/app/api/marketplace/workflows/route.ts b/sim/app/api/marketplace/workflows/route.ts index 4aa2c9c9f..a55710c36 100644 --- a/sim/app/api/marketplace/workflows/route.ts +++ b/sim/app/api/marketplace/workflows/route.ts @@ -57,7 +57,6 @@ export async function GET(request: NextRequest) { authorId: schema.marketplace.authorId, authorName: schema.marketplace.authorName, state: schema.marketplace.state, - stars: schema.marketplace.stars, views: schema.marketplace.views, category: schema.marketplace.category, createdAt: schema.marketplace.createdAt, @@ -77,7 +76,6 @@ export async function GET(request: NextRequest) { description: schema.marketplace.description, authorId: schema.marketplace.authorId, authorName: schema.marketplace.authorName, - stars: schema.marketplace.stars, views: schema.marketplace.views, category: schema.marketplace.category, createdAt: schema.marketplace.createdAt, @@ -123,7 +121,6 @@ export async function GET(request: NextRequest) { authorId: schema.marketplace.authorId, authorName: schema.marketplace.authorName, state: schema.marketplace.state, - stars: schema.marketplace.stars, views: schema.marketplace.views, category: schema.marketplace.category, createdAt: schema.marketplace.createdAt, @@ -143,7 +140,6 @@ export async function GET(request: NextRequest) { description: schema.marketplace.description, authorId: schema.marketplace.authorId, authorName: schema.marketplace.authorName, - stars: schema.marketplace.stars, views: schema.marketplace.views, category: schema.marketplace.category, createdAt: schema.marketplace.createdAt, @@ -192,7 +188,6 @@ export async function GET(request: NextRequest) { name: schema.marketplace.name, description: schema.marketplace.description, authorName: schema.marketplace.authorName, - stars: schema.marketplace.stars, views: schema.marketplace.views, category: schema.marketplace.category, createdAt: schema.marketplace.createdAt, @@ -212,7 +207,7 @@ export async function GET(request: NextRequest) { result.popular = await db .select(selectFields) .from(schema.marketplace) - .orderBy(desc(schema.marketplace.stars), desc(schema.marketplace.views)) + .orderBy(desc(schema.marketplace.views)) .limit(limit) } @@ -262,7 +257,7 @@ export async function GET(request: NextRequest) { .select(selectFields) .from(schema.marketplace) .where(eq(schema.marketplace.category, categoryValue)) - .orderBy(desc(schema.marketplace.stars), desc(schema.marketplace.views)) + .orderBy(desc(schema.marketplace.views)) .limit(limit) // Always add the category to the result, even if empty @@ -277,15 +272,17 @@ export async function GET(request: NextRequest) { // Transform the data if state was included to match the expected format if (includeState) { const transformSection = (section: any[]) => { - return section.map((item) => - 'state' in item - ? { - ...item, - workflowState: item.state, - state: undefined, - } - : item - ) + return section.map((item) => { + if ('state' in item) { + // Create a new object without the state field, but with workflowState + const { state, ...rest } = item; + return { + ...rest, + workflowState: state + }; + } + return item; + }); } if (result.popular.length > 0) { diff --git a/sim/app/api/workflows/sync/route.ts b/sim/app/api/workflows/sync/route.ts index d3b94591b..fab2087a6 100644 --- a/sim/app/api/workflows/sync/route.ts +++ b/sim/app/api/workflows/sync/route.ts @@ -11,7 +11,7 @@ const logger = createLogger('WorkflowAPI') // Define marketplace data schema const MarketplaceDataSchema = z.object({ id: z.string(), - status: z.enum(['owner', 'temp', 'star']) + status: z.enum(['owner', 'temp']) }).nullable().optional() // Schema for workflow data 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 bdbe856b1..1d4ba10b2 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 @@ -17,7 +17,6 @@ import { LineChart, MailIcon, NotebookPen, - Star, Store, TimerIcon, Trash, @@ -38,6 +37,7 @@ import { } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' +import { LoadingAgent } from '@/components/ui/loading-agent' import { Select, SelectContent, @@ -131,7 +131,6 @@ interface MarketplaceInfo { description: string category: string authorName: string - stars: number views: number createdAt: string updatedAt: string @@ -183,14 +182,25 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) try { setIsLoading(true) - const response = await fetch(`/api/marketplace/${activeWorkflowId}/info`) + + // Get marketplace ID from the workflow's marketplaceData + const marketplaceData = getMarketplaceData() + if (!marketplaceData?.id) { + throw new Error('No marketplace ID found in workflow data') + } + + // Use the marketplace ID to fetch details instead of workflow ID + const response = await fetch( + `/api/marketplace/workflows?marketplaceId=${marketplaceData.id}` + ) if (!response.ok) { throw new Error('Failed to fetch marketplace information') } - const data = await response.json() - setMarketplaceInfo(data) + // The API returns the data directly without wrapping + const marketplaceEntry = await response.json() + setMarketplaceInfo(marketplaceEntry) } catch (error) { console.error('Error fetching marketplace info:', error) addNotification('error', 'Failed to fetch marketplace information', activeWorkflowId) @@ -270,9 +280,13 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) throw new Error(errorData.error || 'Failed to publish workflow') } + // Get the marketplace ID from the response + const responseData = await response.json() + const marketplaceId = responseData.data.id + // Update the marketplace data in the workflow registry updateWorkflow(activeWorkflowId, { - marketplaceData: { id: activeWorkflowId, status: 'owner' }, + marketplaceData: { id: marketplaceId, status: 'owner' }, }) // Add a marketplace notification with detailed information @@ -301,29 +315,46 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) try { setIsUnpublishing(true) - const response = await fetch(`/api/marketplace/${activeWorkflowId}/unpublish`, { + // Get marketplace ID from the workflow's marketplaceData + const marketplaceData = getMarketplaceData() + if (!marketplaceData?.id) { + throw new Error('No marketplace ID found in workflow data') + } + + logger.info('Attempting to unpublish marketplace entry', { + marketplaceId: marketplaceData.id, + workflowId: activeWorkflowId, + status: marketplaceData.status, + }) + + const response = await fetch(`/api/marketplace/${marketplaceData.id}/unpublish`, { method: 'POST', }) if (!response.ok) { const errorData = await response.json() + logger.error('Error response from unpublish endpoint', { + status: response.status, + data: errorData, + }) throw new Error(errorData.error || 'Failed to unpublish workflow') } - // Remove the marketplace data from the workflow registry - updateWorkflow(activeWorkflowId, { - marketplaceData: null, + logger.info('Successfully unpublished workflow from marketplace', { + marketplaceId: marketplaceData.id, + workflowId: activeWorkflowId, }) - // Add a notification - addNotification( - 'marketplace', - `"${marketplaceInfo?.name || 'Workflow'}" successfully unpublished from marketplace`, - activeWorkflowId - ) - - // Close the modal after successful unpublishing + // First close the modal to prevent any flashing onOpenChange(false) + + // Then update the workflow state after modal is closed + setTimeout(() => { + // Remove the marketplace data from the workflow registry + updateWorkflow(activeWorkflowId, { + marketplaceData: null, + }) + }, 100) } catch (error: any) { console.error('Error unpublishing workflow:', error) addNotification('error', `Failed to unpublish workflow: ${error.message}`, activeWorkflowId) @@ -350,13 +381,8 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) const renderMarketplaceInfo = () => { if (isLoading) { return ( -
-
-
- -
-

Loading marketplace information...

-
+
+
) } @@ -379,12 +405,6 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)

{marketplaceInfo.name}

-
- - - {marketplaceInfo.stars} - -
diff --git a/sim/app/w/[id]/workflow.tsx b/sim/app/w/[id]/workflow.tsx index ebbdac04a..262473c0f 100644 --- a/sim/app/w/[id]/workflow.tsx +++ b/sim/app/w/[id]/workflow.tsx @@ -11,6 +11,7 @@ import ReactFlow, { useReactFlow, } from 'reactflow' import 'reactflow/dist/style.css' +import { LoadingAgent } from '@/components/ui/loading-agent' import { createLogger } from '@/lib/logs/console-logger' import { useExecutionStore } from '@/stores/execution/store' import { useNotificationStore } from '@/stores/notifications/store' @@ -460,7 +461,13 @@ function WorkflowContent() { } }, [setSubBlockValue]) - if (!isInitialized) return null + if (!isInitialized) { + return ( +
+ +
+ ) + } return ( <> 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 index 2a2416649..56c143bf0 100644 --- a/sim/app/w/components/sidebar/components/folder-section/folder-section.tsx +++ b/sim/app/w/components/sidebar/components/folder-section/folder-section.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { usePathname } from 'next/navigation' -import { Folder } from 'lucide-react' +import { Folder, LucideIcon } from 'lucide-react' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { WorkflowMetadata } from '@/stores/workflows/registry/types' @@ -12,9 +12,15 @@ interface FolderSectionProps { title: string workflows: WorkflowMetadata[] defaultOpen?: boolean + icon?: LucideIcon } -export function FolderSection({ title, workflows, defaultOpen = true }: FolderSectionProps) { +export function FolderSection({ + title, + workflows, + defaultOpen = true, + icon: Icon = Folder, +}: FolderSectionProps) { const [isOpen, setIsOpen] = useState(defaultOpen) const pathname = usePathname() @@ -30,8 +36,41 @@ export function FolderSection({ title, workflows, defaultOpen = true }: FolderSe return null } + // Calculate height based on number of workflows, with a maximum of 4 + const navItemHeight = 32 // Height of each NavItem in pixels + const gapSize = 12 // Gap between items (3 in rem units = ~12px) + const maxItems = 4 + + // Calculate height for a single item (no gaps) + const singleItemHeight = navItemHeight + + // Calculate heights for different number of items + const twoItemsHeight = navItemHeight * 2 + gapSize + const threeItemsHeight = navItemHeight * 3 + gapSize * 2 + const fourItemsHeight = navItemHeight * 4 + gapSize * 3 + + // Max height is always the height of 4 items + const maxHeight = fourItemsHeight + const needsScroll = workflows.length > maxItems + + // Get exact height based on number of workflows (up to 4) + const getExactHeight = () => { + switch (Math.min(workflows.length, maxItems)) { + case 1: + return singleItemHeight + case 2: + return twoItemsHeight + case 3: + return threeItemsHeight + case 4: + return fourItemsHeight + default: + return 'auto' + } + } + return ( -
+
@@ -50,19 +89,36 @@ export function FolderSection({ title, workflows, defaultOpen = true }: FolderSe {isOpen && ( -
- {workflows.map((workflow) => ( - -
- - ))} +
+
+
    + {workflows.map((workflow) => ( +
  • + +
    + +
  • + ))} +
+
)}
diff --git a/sim/app/w/components/sidebar/components/nav-item/nav-item.tsx b/sim/app/w/components/sidebar/components/nav-item/nav-item.tsx index b8068b157..6fd10f39b 100644 --- a/sim/app/w/components/sidebar/components/nav-item/nav-item.tsx +++ b/sim/app/w/components/sidebar/components/nav-item/nav-item.tsx @@ -9,10 +9,12 @@ export function NavItem({ href, label, children, + className, }: { href: string label: string children: React.ReactNode + className?: string }) { const pathname = usePathname() @@ -25,7 +27,8 @@ export function NavItem({ 'flex !h-9 !w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8', { 'bg-accent': pathname === href, - } + }, + className )} > {children} diff --git a/sim/app/w/components/sidebar/sidebar.tsx b/sim/app/w/components/sidebar/sidebar.tsx index 896fc225d..dca2e39bb 100644 --- a/sim/app/w/components/sidebar/sidebar.tsx +++ b/sim/app/w/components/sidebar/sidebar.tsx @@ -119,7 +119,12 @@ export function Sidebar() { {/* Marketplace Workflows folder */} - +
diff --git a/sim/app/w/marketplace/components/workflow-card-skeleton.tsx b/sim/app/w/marketplace/components/workflow-card-skeleton.tsx index be66a1b29..f0521d8ea 100644 --- a/sim/app/w/marketplace/components/workflow-card-skeleton.tsx +++ b/sim/app/w/marketplace/components/workflow-card-skeleton.tsx @@ -25,13 +25,10 @@ export function WorkflowCardSkeleton() { - {/* Footer with author and stats skeletons */} + {/* Footer with author and views skeletons */} -
- - -
+
diff --git a/sim/app/w/marketplace/components/workflow-card.tsx b/sim/app/w/marketplace/components/workflow-card.tsx index 4413b0453..babcccf07 100644 --- a/sim/app/w/marketplace/components/workflow-card.tsx +++ b/sim/app/w/marketplace/components/workflow-card.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' -import { Eye, Star } from 'lucide-react' +import { Eye } from 'lucide-react' import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { Workflow } from '../marketplace' @@ -81,9 +81,13 @@ export function WorkflowCard({ workflow, onHover }: WorkflowCardProps) { } return ( -
+
{/* Workflow preview/thumbnail area */} @@ -91,7 +95,7 @@ export function WorkflowCard({ workflow, onHover }: WorkflowCardProps) { {isPreviewReady && workflow.workflowState ? ( // Show interactive workflow preview if state is available
-
+
@@ -124,11 +128,7 @@ export function WorkflowCard({ workflow, onHover }: WorkflowCardProps) { {/* Footer with author and stats */}
by {workflow.author}
-
-
- - {workflow.stars} -
+
{workflow.views} diff --git a/sim/app/w/marketplace/components/workflow-preview.tsx b/sim/app/w/marketplace/components/workflow-preview.tsx index 74404cf09..448bc2b05 100644 --- a/sim/app/w/marketplace/components/workflow-preview.tsx +++ b/sim/app/w/marketplace/components/workflow-preview.tsx @@ -408,7 +408,7 @@ function WorkflowPreviewContent({ workflowState }: WorkflowPreviewProps) { edgeTypes={edgeTypes} fitView fitViewOptions={{ - padding: 0.2, + padding: 0, minZoom: 0.2, maxZoom: 3, }} @@ -430,12 +430,13 @@ function WorkflowPreviewContent({ workflowState }: WorkflowPreviewProps) { zoomOnPinch={false} zoomOnDoubleClick={false} panOnDrag={false} - preventScrolling={true} + preventScrolling={false} + disableKeyboardA11y={true} attributionPosition="bottom-right" - className="w-full h-full" - style={{ background: 'transparent' }} + className="w-full h-full pointer-events-none" + style={{ background: 'transparent', pointerEvents: 'none' }} > - + ) } @@ -448,7 +449,7 @@ function WorkflowPreviewContent({ workflowState }: WorkflowPreviewProps) { export function WorkflowPreview({ workflowState }: WorkflowPreviewProps) { return ( -
+
diff --git a/sim/app/w/marketplace/marketplace.tsx b/sim/app/w/marketplace/marketplace.tsx index db867ebc2..f2a788b93 100644 --- a/sim/app/w/marketplace/marketplace.tsx +++ b/sim/app/w/marketplace/marketplace.tsx @@ -16,7 +16,6 @@ export interface Workflow { name: string description: string author: string - stars: number views: number tags: string[] thumbnail?: string @@ -41,7 +40,6 @@ export interface MarketplaceWorkflow { name: string description: string authorName: string - stars: number views: number category: string createdAt: string @@ -94,7 +92,6 @@ export default function Marketplace() { name: item.name, description: item.description || '', author: item.authorName, - stars: item.stars, views: item.views, tags: [item.category], workflowState: item.workflowState, @@ -105,7 +102,6 @@ export default function Marketplace() { name: item.name, description: item.description || '', author: item.authorName, - stars: item.stars, views: item.views, tags: [item.category], workflowState: item.workflowState, @@ -121,7 +117,6 @@ export default function Marketplace() { name: item.name, description: item.description || '', author: item.authorName, - stars: item.stars, views: item.views, tags: [item.category], workflowState: item.workflowState, diff --git a/sim/components/ui/loading-agent.tsx b/sim/components/ui/loading-agent.tsx new file mode 100644 index 000000000..1b7afc215 --- /dev/null +++ b/sim/components/ui/loading-agent.tsx @@ -0,0 +1,81 @@ +'use client' + +export interface LoadingAgentProps { + /** + * Size of the loading agent + * @default 'md' + */ + size?: 'sm' | 'md' | 'lg' +} + +export function LoadingAgent({ size = 'md' }: LoadingAgentProps) { + const pathLength = 40 + + // Size mappings for width and height + const sizes = { + sm: { width: 16, height: 18 }, + md: { width: 21, height: 24 }, + lg: { width: 30, height: 34 }, + } + + const { width, height } = sizes[size] + + return ( + + + + + + + ) +} diff --git a/sim/db/migrations/0027_careless_gamora.sql b/sim/db/migrations/0027_careless_gamora.sql new file mode 100644 index 000000000..a5dda4045 --- /dev/null +++ b/sim/db/migrations/0027_careless_gamora.sql @@ -0,0 +1,2 @@ +DROP TABLE "marketplace_star" CASCADE;--> statement-breakpoint +ALTER TABLE "marketplace" DROP COLUMN "stars"; \ No newline at end of file diff --git a/sim/db/migrations/meta/0027_snapshot.json b/sim/db/migrations/meta/0027_snapshot.json new file mode 100644 index 000000000..cd276309f --- /dev/null +++ b/sim/db/migrations/meta/0027_snapshot.json @@ -0,0 +1,1185 @@ +{ + "id": "1ae77c41-81cc-44a5-b084-3116baaee5fe", + "prevId": "e09a23de-78f7-48a9-b26c-6f98fa5bb601", + "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 + }, + "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.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_state": { + "name": "deployed_state", + "type": "json", + "primaryKey": false, + "notNull": 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 f854c9181..6d5aa0634 100644 --- a/sim/db/migrations/meta/_journal.json +++ b/sim/db/migrations/meta/_journal.json @@ -190,6 +190,13 @@ "when": 1743654486007, "tag": "0026_daily_killraven", "breakpoints": true + }, + { + "idx": 27, + "version": "7", + "when": 1743667795545, + "tag": "0027_careless_gamora", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/sim/db/schema.ts b/sim/db/schema.ts index 8ac179880..71c1ae7e8 100644 --- a/sim/db/schema.ts +++ b/sim/db/schema.ts @@ -78,10 +78,9 @@ 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'), // Format: { id: string, status: 'owner' | 'temp' | 'star' } - + marketplaceData: json('marketplace_data'), // Format: { id: string, status: 'owner' | 'temp' } + // These columns are kept for backward compatibility during migration - // and should be marked as deprecated // @deprecated - Use marketplaceData instead isPublished: boolean('is_published').notNull().default(false), }) @@ -189,32 +188,12 @@ export const marketplace = pgTable('marketplace', { .notNull() .references(() => user.id), authorName: text('author_name').notNull(), - stars: integer('stars').notNull().default(0), views: integer('views').notNull().default(0), category: text('category'), createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at').notNull().defaultNow(), }) -export const marketplaceStar = pgTable( - 'marketplace_star', - { - id: text('id').primaryKey(), - marketplaceId: text('marketplace_id') - .notNull() - .references(() => marketplace.id, { onDelete: 'cascade' }), - userId: text('user_id') - .notNull() - .references(() => user.id), - createdAt: timestamp('created_at').notNull().defaultNow(), - }, - (table) => { - return { - userMarketplaceIdx: uniqueIndex('user_marketplace_idx').on(table.userId, table.marketplaceId), - } - } -) - export const userStats = pgTable('user_stats', { id: text('id').primaryKey(), userId: text('user_id') diff --git a/sim/stores/workflows/registry/types.ts b/sim/stores/workflows/registry/types.ts index 634f71bcf..675bcf538 100644 --- a/sim/stores/workflows/registry/types.ts +++ b/sim/stores/workflows/registry/types.ts @@ -1,6 +1,6 @@ export interface MarketplaceData { - id: string - status: 'owner' | 'temp' | 'star' + id: string // Marketplace entry ID to track original marketplace source + status: 'owner' | 'temp' } export interface WorkflowMetadata { diff --git a/sim/stores/workflows/sync.ts b/sim/stores/workflows/sync.ts index 9ef1d1dc8..e2f4b79e0 100644 --- a/sim/stores/workflows/sync.ts +++ b/sim/stores/workflows/sync.ts @@ -94,6 +94,7 @@ export async function fetchWorkflowsFromDB(): Promise { deployedAt, apiKey, createdAt, + marketplaceData, } = workflow // 1. Update registry store with workflow metadata @@ -104,6 +105,7 @@ export async function fetchWorkflowsFromDB(): Promise { color: color || '#3972F6', // Use createdAt for sorting if available, otherwise fall back to lastSynced lastModified: createdAt ? new Date(createdAt) : new Date(lastSynced), + marketplaceData: marketplaceData || null, } // 2. Prepare workflow state data @@ -115,6 +117,7 @@ export async function fetchWorkflowsFromDB(): Promise { deployedAt: deployedAt ? new Date(deployedAt) : undefined, apiKey, lastSaved: Date.now(), + marketplaceData: marketplaceData || null, } // 3. Initialize subblock values from the workflow state