datasource db { provider = "postgresql" url = env("DATABASE_URL") directUrl = env("DIRECT_URL") extensions = [pgvector(map: "vector")] } generator client { provider = "prisma-client-py" recursive_type_depth = -1 interface = "asyncio" previewFeatures = ["views", "fullTextSearch", "postgresqlExtensions"] partial_type_generator = "backend/data/partial_types.py" } // User model to mirror Auth provider users model User { id String @id // This should match the Supabase user ID email String @unique emailVerified Boolean @default(true) name String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt metadata Json @default("{}") integrations String @default("") stripeCustomerId String? topUpConfig Json? maxEmailsPerDay Int @default(3) notifyOnAgentRun Boolean @default(true) notifyOnZeroBalance Boolean @default(true) notifyOnLowBalance Boolean @default(true) notifyOnBlockExecutionFailed Boolean @default(true) notifyOnContinuousAgentError Boolean @default(true) notifyOnDailySummary Boolean @default(true) notifyOnWeeklySummary Boolean @default(true) notifyOnMonthlySummary Boolean @default(true) notifyOnAgentApproved Boolean @default(true) notifyOnAgentRejected Boolean @default(true) timezone String @default("not-set") // Relations AgentGraphs AgentGraph[] AgentGraphExecutions AgentGraphExecution[] AnalyticsDetails AnalyticsDetails[] AnalyticsMetrics AnalyticsMetrics[] CreditTransactions CreditTransaction[] UserBalance UserBalance? ChatSessions ChatSession[] AgentPresets AgentPreset[] LibraryAgents LibraryAgent[] Profile Profile[] UserOnboarding UserOnboarding? CoPilotUnderstanding CoPilotUnderstanding? BuilderSearchHistory BuilderSearchHistory[] StoreListings StoreListing[] StoreListingReviews StoreListingReview[] StoreVersionsReviewed StoreListingVersion[] APIKeys APIKey[] IntegrationWebhooks IntegrationWebhook[] NotificationBatches UserNotificationBatch[] PendingHumanReviews PendingHumanReview[] Workspace UserWorkspace? // OAuth Provider relations OAuthApplications OAuthApplication[] OAuthAuthorizationCodes OAuthAuthorizationCode[] OAuthAccessTokens OAuthAccessToken[] OAuthRefreshTokens OAuthRefreshToken[] } enum OnboardingStep { // Introductory onboarding (Library) WELCOME USAGE_REASON INTEGRATIONS AGENT_CHOICE AGENT_NEW_RUN AGENT_INPUT CONGRATS // First Wins VISIT_COPILOT GET_RESULTS MARKETPLACE_VISIT MARKETPLACE_ADD_AGENT MARKETPLACE_RUN_AGENT BUILDER_SAVE_AGENT // Consistency Challenge RE_RUN_AGENT SCHEDULE_AGENT RUN_AGENTS RUN_3_DAYS // The Pro Playground TRIGGER_WEBHOOK RUN_14_DAYS RUN_AGENTS_100 // No longer rewarded but exist for analytical purposes BUILDER_OPEN BUILDER_RUN_AGENT } model UserOnboarding { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime? @updatedAt completedSteps OnboardingStep[] @default([]) walletShown Boolean @default(false) notified OnboardingStep[] @default([]) rewardedFor OnboardingStep[] @default([]) usageReason String? integrations String[] @default([]) otherIntegrations String? selectedStoreListingVersionId String? agentInput Json? onboardingAgentExecutionId String? agentRuns Int @default(0) lastRunAt DateTime? consecutiveRunDays Int @default(0) userId String @unique User User @relation(fields: [userId], references: [id], onDelete: Cascade) } model CoPilotUnderstanding { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt userId String @unique User User @relation(fields: [userId], references: [id], onDelete: Cascade) data Json? @@index([userId]) } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //////////////// USER WORKSPACE TABLES ///////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // User's persistent file storage workspace model UserWorkspace { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userId String @unique User User @relation(fields: [userId], references: [id], onDelete: Cascade) Files UserWorkspaceFile[] @@index([userId]) } // Individual files in a user's workspace model UserWorkspaceFile { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt workspaceId String Workspace UserWorkspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) // File metadata name String // User-visible filename path String // Virtual path (e.g., "/documents/report.pdf") storagePath String // Actual GCS or local storage path mimeType String sizeBytes BigInt checksum String? // SHA256 for integrity // File state isDeleted Boolean @default(false) deletedAt DateTime? metadata Json @default("{}") @@unique([workspaceId, path]) @@index([workspaceId, isDeleted]) } model BuilderSearchHistory { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt searchQuery String filter String[] @default([]) byCreator String[] @default([]) userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //////////////// CHAT SESSION TABLES /////////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// model ChatSession { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) // Session metadata title String? credentials Json @default("{}") // Map of provider -> credential metadata // Rate limiting counters (stored as JSON maps) successfulAgentRuns Json @default("{}") // Map of graph_id -> count successfulAgentSchedules Json @default("{}") // Map of graph_id -> count // Usage tracking totalPromptTokens Int @default(0) totalCompletionTokens Int @default(0) Messages ChatMessage[] @@index([userId, updatedAt]) } model ChatMessage { id String @id @default(uuid()) createdAt DateTime @default(now()) sessionId String Session ChatSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) // Message content role String // "user", "assistant", "system", "tool", "function" content String? name String? toolCallId String? refusal String? toolCalls Json? // List of tool calls for assistant messages functionCall Json? // Deprecated but kept for compatibility // Ordering within session sequence Int @@unique([sessionId, sequence]) } // This model describes the Agent Graph/Flow (Multi Agent System). model AgentGraph { id String @default(uuid()) version Int @default(1) createdAt DateTime @default(now()) updatedAt DateTime? @updatedAt name String? description String? instructions String? recommendedScheduleCron String? isActive Boolean @default(true) // Link to User model userId String // FIX: Do not cascade delete the agent when the user is deleted // This allows us to delete user data with deleting the agent which maybe in use by other users User User @relation(fields: [userId], references: [id], onDelete: Cascade) forkedFromId String? forkedFromVersion Int? forkedFrom AgentGraph? @relation("AgentGraphForks", fields: [forkedFromId, forkedFromVersion], references: [id, version]) forks AgentGraph[] @relation("AgentGraphForks") Nodes AgentNode[] Executions AgentGraphExecution[] Presets AgentPreset[] LibraryAgents LibraryAgent[] StoreListings StoreListing[] StoreListingVersions StoreListingVersion[] @@id(name: "graphVersionId", [id, version]) @@index([userId, isActive, id, version]) @@index([forkedFromId, forkedFromVersion]) } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //////////////// USER SPECIFIC DATA //////////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // An AgentPrest is an Agent + User Configuration of that agent. // For example, if someone has created a weather agent and they want to set it up to // Inform them of extreme weather warnings in Texas, the agent with the configuration to set it to // monitor texas, along with the cron setup or webhook tiggers, is an AgentPreset model AgentPreset { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt name String description String // For agents that can be triggered by webhooks or cronjob // This bool allows us to disable a configured agent without deleting it isActive Boolean @default(true) userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) agentGraphId String agentGraphVersion Int AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Restrict) InputPresets AgentNodeExecutionInputOutput[] @relation("AgentPresetsInputData") Executions AgentGraphExecution[] // For webhook-triggered agents: reference to the webhook that triggers the agent webhookId String? Webhook IntegrationWebhook? @relation(fields: [webhookId], references: [id]) isDeleted Boolean @default(false) @@index([userId]) @@index([agentGraphId, agentGraphVersion]) @@index([webhookId]) } enum NotificationType { AGENT_RUN ZERO_BALANCE LOW_BALANCE BLOCK_EXECUTION_FAILED CONTINUOUS_AGENT_ERROR DAILY_SUMMARY WEEKLY_SUMMARY MONTHLY_SUMMARY REFUND_REQUEST REFUND_PROCESSED AGENT_APPROVED AGENT_REJECTED } model NotificationEvent { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt UserNotificationBatch UserNotificationBatch? @relation(fields: [userNotificationBatchId], references: [id]) userNotificationBatchId String? type NotificationType data Json @@index([userNotificationBatchId]) } model UserNotificationBatch { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) type NotificationType Notifications NotificationEvent[] // Each user can only have one batch of a notification type at a time @@unique([userId, type]) } // For the library page // It is a user controlled list of agents, that they will see in their library model LibraryAgent { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) imageUrl String? agentGraphId String agentGraphVersion Int AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Restrict) creatorId String? Creator Profile? @relation(fields: [creatorId], references: [id]) useGraphIsActiveVersion Boolean @default(false) isFavorite Boolean @default(false) isCreatedByUser Boolean @default(false) isArchived Boolean @default(false) isDeleted Boolean @default(false) settings Json @default("{}") @@unique([userId, agentGraphId, agentGraphVersion]) @@index([agentGraphId, agentGraphVersion]) @@index([creatorId]) } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //////// AGENT DEFINITION AND EXECUTION TABLES //////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // This model describes a single node in the Agent Graph/Flow (Multi Agent System). model AgentNode { id String @id @default(uuid()) agentBlockId String AgentBlock AgentBlock @relation(fields: [agentBlockId], references: [id], onUpdate: Cascade) agentGraphId String agentGraphVersion Int @default(1) AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Cascade) // List of consumed input, that the parent node should provide. Input AgentNodeLink[] @relation("AgentNodeSink") // List of produced output, that the child node should be executed. Output AgentNodeLink[] @relation("AgentNodeSource") constantInput Json @default("{}") // For webhook-triggered blocks: reference to the webhook that triggers the node webhookId String? Webhook IntegrationWebhook? @relation(fields: [webhookId], references: [id]) metadata Json @default("{}") Executions AgentNodeExecution[] @@index([agentGraphId, agentGraphVersion]) @@index([agentBlockId]) @@index([webhookId]) } // This model describes the link between two AgentNodes. model AgentNodeLink { id String @id @default(uuid()) // Output of a node is connected to the source of the link. agentNodeSourceId String AgentNodeSource AgentNode @relation("AgentNodeSource", fields: [agentNodeSourceId], references: [id], onDelete: Cascade) sourceName String // Input of a node is connected to the sink of the link. agentNodeSinkId String AgentNodeSink AgentNode @relation("AgentNodeSink", fields: [agentNodeSinkId], references: [id], onDelete: Cascade) sinkName String // Default: the data coming from the source can only be consumed by the sink once, Static: input data will be reused. isStatic Boolean @default(false) @@index([agentNodeSourceId]) @@index([agentNodeSinkId]) } // This model describes a component that will be executed by the AgentNode. model AgentBlock { id String @id @default(uuid()) name String @unique // We allow a block to have multiple types of input & output. // Serialized object-typed `jsonschema` with top-level properties as input/output name. inputSchema String outputSchema String // Prisma requires explicit back-references. ReferencedByAgentNode AgentNode[] } // This model describes the status of an AgentGraphExecution or AgentNodeExecution. enum AgentExecutionStatus { INCOMPLETE QUEUED RUNNING COMPLETED TERMINATED FAILED REVIEW } // This model describes the execution of an AgentGraph. model AgentGraphExecution { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime? @updatedAt startedAt DateTime? endedAt DateTime? isDeleted Boolean @default(false) executionStatus AgentExecutionStatus @default(COMPLETED) agentGraphId String agentGraphVersion Int @default(1) AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Cascade) agentPresetId String? AgentPreset AgentPreset? @relation(fields: [agentPresetId], references: [id]) inputs Json? credentialInputs Json? nodesInputMasks Json? NodeExecutions AgentNodeExecution[] // Link to User model -- Executed by this user userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) stats Json? // Parent-child execution tracking for nested agent graphs parentGraphExecutionId String? ParentExecution AgentGraphExecution? @relation("ParentChildExecution", fields: [parentGraphExecutionId], references: [id], onDelete: SetNull) ChildExecutions AgentGraphExecution[] @relation("ParentChildExecution") // Sharing fields isShared Boolean @default(false) shareToken String? @unique sharedAt DateTime? PendingHumanReviews PendingHumanReview[] @@index([agentGraphId, agentGraphVersion]) @@index([userId, isDeleted, createdAt]) @@index([createdAt]) @@index([agentPresetId]) @@index([shareToken]) @@index([parentGraphExecutionId]) } // This model describes the execution of an AgentNode. model AgentNodeExecution { id String @id @default(uuid()) agentGraphExecutionId String GraphExecution AgentGraphExecution @relation(fields: [agentGraphExecutionId], references: [id], onDelete: Cascade) agentNodeId String Node AgentNode @relation(fields: [agentNodeId], references: [id], onDelete: Cascade) Input AgentNodeExecutionInputOutput[] @relation("AgentNodeExecutionInput") Output AgentNodeExecutionInputOutput[] @relation("AgentNodeExecutionOutput") executionStatus AgentExecutionStatus @default(COMPLETED) executionData Json? addedTime DateTime @default(now()) queuedTime DateTime? startedTime DateTime? endedTime DateTime? stats Json? @@index([agentGraphExecutionId, agentNodeId, executionStatus]) @@index([agentNodeId, executionStatus]) @@index([addedTime, queuedTime]) } // This model describes the output of an AgentNodeExecution. model AgentNodeExecutionInputOutput { id String @id @default(uuid()) name String data Json? time DateTime @default(now()) // Prisma requires explicit back-references. referencedByInputExecId String? ReferencedByInputExec AgentNodeExecution? @relation("AgentNodeExecutionInput", fields: [referencedByInputExecId], references: [id], onDelete: Cascade) referencedByOutputExecId String? ReferencedByOutputExec AgentNodeExecution? @relation("AgentNodeExecutionOutput", fields: [referencedByOutputExecId], references: [id], onDelete: Cascade) agentPresetId String? AgentPreset AgentPreset? @relation("AgentPresetsInputData", fields: [agentPresetId], references: [id]) // Input and Output pin names are unique for each AgentNodeExecution. @@unique([referencedByInputExecId, referencedByOutputExecId, name]) @@index([referencedByOutputExecId]) // Composite index for `upsert_execution_input`. @@index([name, time]) @@index([agentPresetId]) } model AgentNodeExecutionKeyValueData { userId String key String agentNodeExecutionId String data Json? createdAt DateTime @default(now()) updatedAt DateTime? @updatedAt @@id([userId, key]) } enum ReviewStatus { WAITING APPROVED REJECTED } // Pending human reviews for Human-in-the-loop blocks // Also stores auto-approval records with special nodeExecId patterns (e.g., "auto_approve_{graph_exec_id}_{node_id}") model PendingHumanReview { nodeExecId String @id userId String graphExecId String graphId String graphVersion Int payload Json // The actual payload data to be reviewed instructions String? // Instructions/message for the reviewer editable Boolean @default(true) // Whether the reviewer can edit the data status ReviewStatus @default(WAITING) reviewMessage String? // Optional message from the reviewer wasEdited Boolean? // Whether the data was modified during review processed Boolean @default(false) // Whether the review result has been processed by the execution engine createdAt DateTime @default(now()) updatedAt DateTime? @updatedAt reviewedAt DateTime? User User @relation(fields: [userId], references: [id], onDelete: Cascade) GraphExecution AgentGraphExecution @relation(fields: [graphExecId], references: [id], onDelete: Cascade) @@unique([nodeExecId]) // One pending review per node execution @@index([userId, status]) @@index([graphExecId, status]) } // Webhook that is registered with a provider and propagates to one or more nodes model IntegrationWebhook { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime? @updatedAt userId String User User @relation(fields: [userId], references: [id], onDelete: Restrict) // Webhooks must be deregistered before deleting provider String // e.g. 'github' credentialsId String // relation to the credentials that the webhook was created with webhookType String // e.g. 'repo' resource String // e.g. 'Significant-Gravitas/AutoGPT' events String[] // e.g. ['created', 'updated'] config Json secret String // crypto string, used to verify payload authenticity providerWebhookId String // Webhook ID assigned by the provider AgentNodes AgentNode[] AgentPresets AgentPreset[] } model AnalyticsDetails { // PK uses gen_random_uuid() to allow the db inserts to happen outside of prisma // typical uuid() inserts are handled by prisma id String @id @default(dbgenerated("gen_random_uuid()")) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt // Link to User model userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) // Analytics Categorical data used for filtering (indexable w and w/o userId) type String // Analytic Specific Data. We should use a union type here, but prisma doesn't support it. data Json? // Indexable field for any count based analytical measures like page order clicking, tutorial step completion, etc. dataIndex String? @@index([userId, type]) } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// ////////////// METRICS TRACKING TABLES //////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// model AnalyticsMetrics { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Analytics Categorical data used for filtering (indexable w and w/o userId) analyticMetric String // Any numeric data that should be counted upon, summed, or otherwise aggregated. value Float // Any string data that should be used to identify the metric as distinct. // ex: '/build' vs '/market' dataString String? // Link to User model userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //////// ACCOUNTING AND CREDIT SYSTEM TABLES ////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// enum CreditTransactionType { TOP_UP USAGE GRANT REFUND CARD_CHECK } model CreditTransaction { transactionKey String @default(uuid()) createdAt DateTime @default(now()) userId String User User? @relation(fields: [userId], references: [id], onDelete: NoAction) amount Int type CreditTransactionType runningBalance Int? isActive Boolean @default(true) metadata Json? @@id(name: "creditTransactionIdentifier", [transactionKey, userId]) @@index([userId, createdAt]) } enum CreditRefundRequestStatus { PENDING APPROVED REJECTED } model CreditRefundRequest { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userId String transactionKey String amount Int reason String result String? status CreditRefundRequestStatus @default(PENDING) @@index([userId, transactionKey]) } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// ////////////// Store TABLES /////////////////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// model SearchTerms { // User ID not being logged as this is anonymous analytics data // Not using uuid as we want to minimise table size id BigInt @id @default(autoincrement()) createdDate DateTime searchTerm String @@index([createdDate]) } model Profile { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt // Only 1 of user or group can be set. // The user this profile belongs to, if any. userId String? User User? @relation(fields: [userId], references: [id], onDelete: Cascade) name String username String @unique description String links String[] avatarUrl String? isFeatured Boolean @default(false) LibraryAgents LibraryAgent[] @@index([userId]) } view Creator { username String @unique name String avatar_url String description String top_categories String[] links String[] num_agents Int agent_rating Float agent_runs Int is_featured Boolean // Materialized views used (refreshed every 15 minutes via pg_cron): // - mv_agent_run_counts - Pre-aggregated agent execution counts by agentGraphId // * idx_mv_agent_run_counts (UNIQUE on agentGraphId) - Primary lookup // - mv_review_stats - Pre-aggregated review statistics (count, avg rating) by storeListingId // * idx_mv_review_stats (UNIQUE on storeListingId) - Primary lookup // * idx_mv_review_stats_rating (avg_rating DESC) - Sort by rating performance // * idx_mv_review_stats_count (review_count DESC) - Sort by review count performance // // Query strategy: Uses CTEs to efficiently aggregate creator statistics leveraging materialized views } view StoreAgent { listing_id String @id storeListingVersionId String updated_at DateTime slug String agent_name String agent_video String? agent_output_demo String? agent_image String[] featured Boolean @default(false) creator_username String? creator_avatar String? sub_heading String description String categories String[] runs Int rating Float versions String[] agentGraphVersions String[] agentGraphId String is_available Boolean @default(true) useForOnboarding Boolean @default(false) // Materialized views used (refreshed every 15 minutes via pg_cron): // - mv_agent_run_counts - Pre-aggregated agent execution counts by agentGraphId // * idx_mv_agent_run_counts (UNIQUE on agentGraphId) - Primary lookup // - mv_review_stats - Pre-aggregated review statistics (count, avg rating) by storeListingId // * idx_mv_review_stats (UNIQUE on storeListingId) - Primary lookup // * idx_mv_review_stats_rating (avg_rating DESC) - Sort by rating performance // * idx_mv_review_stats_count (review_count DESC) - Sort by review count performance // // Query strategy: Uses CTE for version aggregation and joins with materialized views for performance } view StoreSubmission { listing_id String @id user_id String slug String name String sub_heading String description String image_urls String[] date_submitted DateTime status SubmissionStatus runs Int rating Float agent_id String agent_version Int store_listing_version_id String reviewer_id String? review_comments String? internal_comments String? reviewed_at DateTime? changes_summary String? video_url String? categories String[] // Index or unique are not applied to views } // Note: This is actually a MATERIALIZED VIEW in the database // Refreshed automatically every 15 minutes via pg_cron (with fallback to manual refresh) view mv_agent_run_counts { agentGraphId String @unique run_count Int // Pre-aggregated count of AgentGraphExecution records by agentGraphId // Used by StoreAgent and Creator views for performance optimization // Unique index created automatically on agentGraphId for fast lookups // Refresh uses CONCURRENTLY to avoid blocking reads } // Note: This is actually a MATERIALIZED VIEW in the database // Refreshed automatically every 15 minutes via pg_cron (with fallback to manual refresh) view mv_review_stats { storeListingId String @unique review_count Int avg_rating Float // Pre-aggregated review statistics from StoreListingReview // Includes count of reviews and average rating per StoreListing // Only includes approved versions (submissionStatus = 'APPROVED') and non-deleted listings // Used by StoreAgent view for performance optimization // Unique index created automatically on storeListingId for fast lookups // Refresh uses CONCURRENTLY to avoid blocking reads } model StoreListing { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt isDeleted Boolean @default(false) // Whether any version has been approved and is available for display hasApprovedVersion Boolean @default(false) // URL-friendly identifier for this agent (moved from StoreListingVersion) slug String // Allow this agent to be used during onboarding useForOnboarding Boolean @default(false) // The currently active version that should be shown to users activeVersionId String? @unique ActiveVersion StoreListingVersion? @relation("ActiveVersion", fields: [activeVersionId], references: [id]) // The agent link here is only so we can do lookup on agentId agentGraphId String agentGraphVersion Int AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Cascade) owningUserId String OwningUser User @relation(fields: [owningUserId], references: [id]) // Relations Versions StoreListingVersion[] @relation("ListingVersions") // Unique index on agentId to ensure only one listing per agent, regardless of number of versions the agent has. @@unique([agentGraphId]) @@unique([owningUserId, slug]) // Used in the view query @@index([isDeleted, hasApprovedVersion]) @@index([agentGraphId, agentGraphVersion]) } model StoreListingVersion { id String @id @default(uuid()) version Int @default(1) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt // The agent and version to be listed on the store agentGraphId String agentGraphVersion Int AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version]) // Content fields name String subHeading String videoUrl String? agentOutputDemoUrl String? imageUrls String[] description String instructions String? categories String[] isFeatured Boolean @default(false) isDeleted Boolean @default(false) // Old versions can be made unavailable by the author if desired isAvailable Boolean @default(true) // Note: search column removed - now using UnifiedContentEmbedding.search // Version workflow state submissionStatus SubmissionStatus @default(DRAFT) submittedAt DateTime? // Relations storeListingId String StoreListing StoreListing @relation("ListingVersions", fields: [storeListingId], references: [id], onDelete: Cascade) // This version might be the active version for a listing ActiveFor StoreListing? @relation("ActiveVersion") // Submission history changesSummary String? // Review information reviewerId String? Reviewer User? @relation(fields: [reviewerId], references: [id]) internalComments String? // Private notes for admin use only reviewComments String? // Comments visible to creator reviewedAt DateTime? recommendedScheduleCron String? // cron expression like "0 9 * * *" // Reviews for this specific version Reviews StoreListingReview[] // Note: Embeddings now stored in UnifiedContentEmbedding table // Use contentType=STORE_AGENT and contentId=storeListingVersionId @@unique([storeListingId, version]) @@index([storeListingId, submissionStatus, isAvailable]) @@index([submissionStatus]) @@index([reviewerId]) @@index([agentGraphId, agentGraphVersion]) // Non-unique index for efficient lookups } // Content type enum for unified search across store agents, blocks, docs // Note: BLOCK/INTEGRATION are file-based (Python classes), not DB records // DOCUMENTATION are file-based (.md files), not DB records // Only STORE_AGENT and LIBRARY_AGENT are stored in database enum ContentType { STORE_AGENT // Database: StoreListingVersion BLOCK // File-based: Python classes in /backend/blocks/ INTEGRATION // File-based: Python classes (blocks with credentials) DOCUMENTATION // File-based: .md/.mdx files LIBRARY_AGENT // Database: User's personal agents } // Unified embeddings table for all searchable content types // Supports both public content (userId=null) and user-specific content (userId=userID) model UnifiedContentEmbedding { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Content identification contentType ContentType contentId String // DB ID (storeListingVersionId) or file identifier (block.id, file_path) userId String? // NULL for public content (store, blocks, docs), userId for private content (library agents) // Search data embedding Unsupported("vector(1536)") // pgvector embedding (extension in platform schema) searchableText String // Combined text for search and fallback search Unsupported("tsvector")? @default(dbgenerated("''::tsvector")) // Full-text search (auto-populated by trigger) metadata Json @default("{}") // Content-specific metadata @@unique([contentType, contentId, userId], map: "UnifiedContentEmbedding_contentType_contentId_userId_key") @@index([contentType]) @@index([userId]) @@index([contentType, userId]) @@index([embedding], map: "UnifiedContentEmbedding_embedding_idx") // NO @@index for search - GIN index "UnifiedContentEmbedding_search_idx" created via SQL migration // Prisma may generate DROP INDEX on migrate dev - that's okay, migration recreates it } model StoreListingReview { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt storeListingVersionId String StoreListingVersion StoreListingVersion @relation(fields: [storeListingVersionId], references: [id], onDelete: Cascade) reviewByUserId String ReviewByUser User @relation(fields: [reviewByUserId], references: [id]) score Int comments String? @@unique([storeListingVersionId, reviewByUserId]) @@index([reviewByUserId]) } enum SubmissionStatus { DRAFT // Being prepared, not yet submitted PENDING // Submitted, awaiting review APPROVED // Reviewed and approved REJECTED // Reviewed and rejected } enum APIKeyPermission { IDENTITY // Info about the authenticated user EXECUTE_GRAPH // Can execute agent graphs READ_GRAPH // Can get graph versions and details EXECUTE_BLOCK // Can execute individual blocks READ_BLOCK // Can get block information READ_STORE // Can read store agents and creators USE_TOOLS // Can use chat tools via external API MANAGE_INTEGRATIONS // Can initiate OAuth flows and complete them READ_INTEGRATIONS // Can list credentials and providers DELETE_INTEGRATIONS // Can delete credentials } model APIKey { id String @id @default(uuid()) name String head String // First few chars for identification tail String hash String @unique salt String? // null for legacy unsalted keys status APIKeyStatus @default(ACTIVE) permissions APIKeyPermission[] createdAt DateTime @default(now()) lastUsedAt DateTime? revokedAt DateTime? description String? // Relation to user userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([head, name]) @@index([userId, status]) } model UserBalance { userId String @id balance Int @default(0) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) } enum APIKeyStatus { ACTIVE REVOKED SUSPENDED } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// ////////////// OAUTH PROVIDER TABLES ////////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // OAuth2 applications that can access AutoGPT on behalf of users model OAuthApplication { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Application metadata name String description String? logoUrl String? // URL to app logo stored in GCS clientId String @unique clientSecret String // Hashed with Scrypt (same as API keys) clientSecretSalt String // Salt for Scrypt hashing // OAuth configuration redirectUris String[] // Allowed callback URLs grantTypes String[] @default(["authorization_code", "refresh_token"]) scopes APIKeyPermission[] // Which permissions the app can request // Application management ownerId String Owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) isActive Boolean @default(true) // Relations AuthorizationCodes OAuthAuthorizationCode[] AccessTokens OAuthAccessToken[] RefreshTokens OAuthRefreshToken[] @@index([clientId]) @@index([ownerId]) } // Temporary authorization codes (10 min TTL) model OAuthAuthorizationCode { id String @id @default(uuid()) code String @unique createdAt DateTime @default(now()) expiresAt DateTime // Now + 10 minutes applicationId String Application OAuthApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) scopes APIKeyPermission[] redirectUri String // Must match one from application // PKCE (Proof Key for Code Exchange) support codeChallenge String? codeChallengeMethod String? // "S256" or "plain" usedAt DateTime? // Set when code is consumed @@index([code]) @@index([applicationId, userId]) @@index([expiresAt]) // For cleanup } // Access tokens (1 hour TTL) model OAuthAccessToken { id String @id @default(uuid()) token String @unique // SHA256 hash of plaintext token createdAt DateTime @default(now()) expiresAt DateTime // Now + 1 hour applicationId String Application OAuthApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) scopes APIKeyPermission[] revokedAt DateTime? // Set when token is revoked @@index([token]) // For token lookup @@index([userId, applicationId]) @@index([expiresAt]) // For cleanup } // Refresh tokens (30 days TTL) model OAuthRefreshToken { id String @id @default(uuid()) token String @unique // SHA256 hash of plaintext token createdAt DateTime @default(now()) expiresAt DateTime // Now + 30 days applicationId String Application OAuthApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) scopes APIKeyPermission[] revokedAt DateTime? // Set when token is revoked @@index([token]) // For token lookup @@index([userId, applicationId]) @@index([expiresAt]) // For cleanup }