Files
stack/apps/api/prisma/schema.prisma
Jason Woltje 2e6b7d4070 feat(#29): implement cron job configuration
- Add CronSchedule model to Prisma schema
- Implement CronService with CRUD operations
- Add REST API endpoints for cron management
- Create MoltBot plugin skill definition (SKILL.md)
- TDD: 9 passing tests for CronService
2026-01-29 23:00:48 -06:00

840 lines
25 KiB
Plaintext

// Mosaic Stack Database Schema
// PostgreSQL 17 with pgvector extension
generator client {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
extensions = [pgvector(map: "vector"), uuid_ossp(map: "uuid-ossp")]
}
// ============================================
// ENUMS
// ============================================
enum TaskStatus {
NOT_STARTED
IN_PROGRESS
PAUSED
COMPLETED
ARCHIVED
}
enum TaskPriority {
LOW
MEDIUM
HIGH
}
enum ProjectStatus {
PLANNING
ACTIVE
PAUSED
COMPLETED
ARCHIVED
}
enum WorkspaceMemberRole {
OWNER
ADMIN
MEMBER
GUEST
}
enum TeamMemberRole {
OWNER
ADMIN
MEMBER
}
enum ActivityAction {
CREATED
UPDATED
DELETED
COMPLETED
ASSIGNED
COMMENTED
LOGIN
LOGOUT
PASSWORD_RESET
EMAIL_VERIFIED
}
enum EntityType {
TASK
EVENT
PROJECT
WORKSPACE
USER
IDEA
DOMAIN
}
enum IdeaStatus {
CAPTURED
PROCESSING
ACTIONABLE
ARCHIVED
DISCARDED
}
enum RelationshipType {
BLOCKS
BLOCKED_BY
DEPENDS_ON
PARENT_OF
CHILD_OF
RELATED_TO
DUPLICATE_OF
SUPERSEDES
PART_OF
}
enum AgentStatus {
IDLE
WORKING
WAITING
ERROR
TERMINATED
}
enum EntryStatus {
DRAFT
PUBLISHED
ARCHIVED
}
enum Visibility {
PRIVATE
WORKSPACE
PUBLIC
}
// ============================================
// MODELS
// ============================================
model User {
id String @id @default(uuid()) @db.Uuid
email String @unique
name String
emailVerified Boolean @default(false) @map("email_verified")
image String?
authProviderId String? @unique @map("auth_provider_id")
preferences Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
ownedWorkspaces Workspace[] @relation("WorkspaceOwner")
workspaceMemberships WorkspaceMember[]
teamMemberships TeamMember[]
assignedTasks Task[] @relation("TaskAssignee")
createdTasks Task[] @relation("TaskCreator")
createdEvents Event[] @relation("EventCreator")
createdProjects Project[] @relation("ProjectCreator")
activityLogs ActivityLog[]
sessions Session[]
accounts Account[]
ideas Idea[] @relation("IdeaCreator")
relationships Relationship[] @relation("RelationshipCreator")
agentSessions AgentSession[]
userLayouts UserLayout[]
userPreference UserPreference?
@@map("users")
}
model UserPreference {
id String @id @default(uuid()) @db.Uuid
userId String @unique @map("user_id") @db.Uuid
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
theme String @default("system")
locale String @default("en")
timezone String?
settings Json @default("{}")
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@map("user_preferences")
}
model Workspace {
id String @id @default(uuid()) @db.Uuid
name String
ownerId String @map("owner_id") @db.Uuid
settings Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
owner User @relation("WorkspaceOwner", fields: [ownerId], references: [id], onDelete: Cascade)
members WorkspaceMember[]
teams Team[]
tasks Task[]
events Event[]
projects Project[]
activityLogs ActivityLog[]
memoryEmbeddings MemoryEmbedding[]
domains Domain[]
ideas Idea[]
relationships Relationship[]
agents Agent[]
agentSessions AgentSession[]
userLayouts UserLayout[]
knowledgeEntries KnowledgeEntry[]
knowledgeTags KnowledgeTag[]
cronSchedules CronSchedule[]
@@index([ownerId])
@@map("workspaces")
}
model WorkspaceMember {
workspaceId String @map("workspace_id") @db.Uuid
userId String @map("user_id") @db.Uuid
role WorkspaceMemberRole @default(MEMBER)
joinedAt DateTime @default(now()) @map("joined_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([workspaceId, userId])
@@index([userId])
@@map("workspace_members")
}
model Team {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
name String
description String? @db.Text
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
members TeamMember[]
@@index([workspaceId])
@@map("teams")
}
model TeamMember {
teamId String @map("team_id") @db.Uuid
userId String @map("user_id") @db.Uuid
role TeamMemberRole @default(MEMBER)
joinedAt DateTime @default(now()) @map("joined_at") @db.Timestamptz
// Relations
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([teamId, userId])
@@index([userId])
@@map("team_members")
}
model Task {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
title String
description String? @db.Text
status TaskStatus @default(NOT_STARTED)
priority TaskPriority @default(MEDIUM)
dueDate DateTime? @map("due_date") @db.Timestamptz
assigneeId String? @map("assignee_id") @db.Uuid
creatorId String @map("creator_id") @db.Uuid
projectId String? @map("project_id") @db.Uuid
parentId String? @map("parent_id") @db.Uuid
domainId String? @map("domain_id") @db.Uuid
sortOrder Int @default(0) @map("sort_order")
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
completedAt DateTime? @map("completed_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
assignee User? @relation("TaskAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
creator User @relation("TaskCreator", fields: [creatorId], references: [id], onDelete: Cascade)
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
parent Task? @relation("TaskSubtasks", fields: [parentId], references: [id], onDelete: Cascade)
subtasks Task[] @relation("TaskSubtasks")
domain Domain? @relation(fields: [domainId], references: [id], onDelete: SetNull)
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, status])
@@index([workspaceId, dueDate])
@@index([assigneeId])
@@index([projectId])
@@index([parentId])
@@index([domainId])
@@map("tasks")
}
model Event {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
title String
description String? @db.Text
startTime DateTime @map("start_time") @db.Timestamptz
endTime DateTime? @map("end_time") @db.Timestamptz
allDay Boolean @default(false) @map("all_day")
location String?
recurrence Json?
creatorId String @map("creator_id") @db.Uuid
projectId String? @map("project_id") @db.Uuid
domainId String? @map("domain_id") @db.Uuid
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
creator User @relation("EventCreator", fields: [creatorId], references: [id], onDelete: Cascade)
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
domain Domain? @relation(fields: [domainId], references: [id], onDelete: SetNull)
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, startTime])
@@index([creatorId])
@@index([projectId])
@@index([domainId])
@@map("events")
}
model Project {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
name String
description String? @db.Text
status ProjectStatus @default(PLANNING)
startDate DateTime? @map("start_date") @db.Date
endDate DateTime? @map("end_date") @db.Date
creatorId String @map("creator_id") @db.Uuid
domainId String? @map("domain_id") @db.Uuid
color String?
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
creator User @relation("ProjectCreator", fields: [creatorId], references: [id], onDelete: Cascade)
tasks Task[]
events Event[]
domain Domain? @relation(fields: [domainId], references: [id], onDelete: SetNull)
ideas Idea[]
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, status])
@@index([creatorId])
@@index([domainId])
@@map("projects")
}
model ActivityLog {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
userId String @map("user_id") @db.Uuid
action ActivityAction
entityType EntityType @map("entity_type")
entityId String @map("entity_id") @db.Uuid
details Json @default("{}")
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, createdAt])
@@index([entityType, entityId])
@@index([userId])
@@index([action])
@@map("activity_logs")
}
model MemoryEmbedding {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
content String @db.Text
// Note: vector dimension (1536) must match EMBEDDING_DIMENSION constant in @mosaic/shared
embedding Unsupported("vector(1536)")?
entityType EntityType? @map("entity_type")
entityId String? @map("entity_id") @db.Uuid
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@index([workspaceId])
@@map("memory_embeddings")
}
// ============================================
// NEW MODELS
// ============================================
model Domain {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
name String
slug String
description String? @db.Text
color String?
icon String?
sortOrder Int @default(0) @map("sort_order")
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
tasks Task[]
events Event[]
projects Project[]
ideas Idea[]
@@unique([id, workspaceId])
@@unique([workspaceId, slug])
@@index([workspaceId])
@@map("domains")
}
model Idea {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
domainId String? @map("domain_id") @db.Uuid
projectId String? @map("project_id") @db.Uuid
// Core fields
title String?
content String @db.Text
// Status
status IdeaStatus @default(CAPTURED)
priority TaskPriority @default(MEDIUM)
// Categorization
category String?
tags String[]
metadata Json @default("{}")
// Embedding for semantic search (pgvector)
embedding Unsupported("vector(1536)")?
// Audit
creatorId String @map("creator_id") @db.Uuid
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
domain Domain? @relation(fields: [domainId], references: [id], onDelete: SetNull)
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
creator User @relation("IdeaCreator", fields: [creatorId], references: [id], onDelete: Cascade)
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, status])
@@index([domainId])
@@index([projectId])
@@index([creatorId])
@@map("ideas")
}
model Relationship {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
// Source entity
sourceType EntityType @map("source_type")
sourceId String @map("source_id") @db.Uuid
// Target entity
targetType EntityType @map("target_type")
targetId String @map("target_id") @db.Uuid
// Relationship type
relationship RelationshipType
metadata Json @default("{}")
notes String? @db.Text
// Audit
creatorId String @map("creator_id") @db.Uuid
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
creator User @relation("RelationshipCreator", fields: [creatorId], references: [id], onDelete: Cascade)
// Prevent duplicate relationships
@@unique([workspaceId, sourceType, sourceId, targetType, targetId, relationship])
@@index([sourceType, sourceId])
@@index([targetType, targetId])
@@index([relationship])
@@map("relationships")
}
model Agent {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
// Identity
agentId String @map("agent_id")
name String?
model String?
role String?
// Status
status AgentStatus @default(IDLE)
currentTask String? @map("current_task") @db.Text
// Performance metrics
metrics Json @default("{\"totalTasks\": 0, \"successfulTasks\": 0, \"failedTasks\": 0, \"avgResponseTimeMs\": 0}")
// Health
lastHeartbeat DateTime? @map("last_heartbeat") @db.Timestamptz
errorCount Int @default(0) @map("error_count")
lastError String? @map("last_error") @db.Text
// Firing history
firedCount Int @default(0) @map("fired_count")
fireHistory Json @default("[]") @map("fire_history")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
terminatedAt DateTime? @map("terminated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
sessions AgentSession[]
@@unique([workspaceId, agentId])
@@index([workspaceId])
@@index([status])
@@map("agents")
}
model AgentSession {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
userId String @map("user_id") @db.Uuid
agentId String? @map("agent_id") @db.Uuid
// Identity
sessionKey String @map("session_key")
label String?
channel String?
// Context
contextSummary String? @map("context_summary") @db.Text
messageCount Int @default(0) @map("message_count")
// Status
isActive Boolean @default(true) @map("is_active")
startedAt DateTime @default(now()) @map("started_at") @db.Timestamptz
lastMessageAt DateTime? @map("last_message_at") @db.Timestamptz
endedAt DateTime? @map("ended_at") @db.Timestamptz
metadata Json @default("{}")
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
agent Agent? @relation(fields: [agentId], references: [id], onDelete: SetNull)
@@unique([workspaceId, sessionKey])
@@index([workspaceId])
@@index([userId])
@@index([agentId])
@@index([isActive])
@@map("agent_sessions")
}
model WidgetDefinition {
id String @id @default(uuid()) @db.Uuid
name String @unique
displayName String @map("display_name")
description String? @db.Text
component String
// Default size (grid units)
defaultWidth Int @default(1) @map("default_width")
defaultHeight Int @default(1) @map("default_height")
minWidth Int @default(1) @map("min_width")
minHeight Int @default(1) @map("min_height")
maxWidth Int? @map("max_width")
maxHeight Int? @map("max_height")
// Configuration schema (JSON Schema for widget config)
configSchema Json @default("{}") @map("config_schema")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
@@map("widget_definitions")
}
model UserLayout {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
userId String @map("user_id") @db.Uuid
name String
isDefault Boolean @default(false) @map("is_default")
// Layout configuration (array of widget placements)
layout Json @default("[]")
// Additional metadata for the layout
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([id, workspaceId])
@@unique([workspaceId, userId, name])
@@index([userId])
@@map("user_layouts")
}
// ============================================
// AUTHENTICATION MODELS (BetterAuth)
// ============================================
model Session {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
token String @unique
expiresAt DateTime @map("expires_at") @db.Timestamptz
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([token])
@@map("sessions")
}
model Account {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
accountId String @map("account_id")
providerId String @map("provider_id")
accessToken String? @map("access_token")
refreshToken String? @map("refresh_token")
idToken String? @map("id_token")
accessTokenExpiresAt DateTime? @map("access_token_expires_at") @db.Timestamptz
refreshTokenExpiresAt DateTime? @map("refresh_token_expires_at") @db.Timestamptz
scope String?
password String?
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([providerId, accountId])
@@index([userId])
@@map("accounts")
}
model Verification {
id String @id @default(uuid()) @db.Uuid
identifier String
value String
expiresAt DateTime @map("expires_at") @db.Timestamptz
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@index([identifier])
@@map("verifications")
}
// ============================================
// KNOWLEDGE MODULE
// ============================================
model KnowledgeEntry {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
// Identity
slug String
title String
// Content
content String @db.Text
contentHtml String? @map("content_html") @db.Text
summary String?
// Status
status EntryStatus @default(DRAFT)
visibility Visibility @default(PRIVATE)
// Audit
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
createdBy String @map("created_by") @db.Uuid
updatedBy String @map("updated_by") @db.Uuid
// Relations
tags KnowledgeEntryTag[]
outgoingLinks KnowledgeLink[] @relation("SourceEntry")
incomingLinks KnowledgeLink[] @relation("TargetEntry")
versions KnowledgeEntryVersion[]
embedding KnowledgeEmbedding?
@@unique([workspaceId, slug])
@@index([workspaceId, status])
@@index([workspaceId, updatedAt])
@@index([createdBy])
@@index([updatedBy])
@@map("knowledge_entries")
}
model KnowledgeEntryVersion {
id String @id @default(uuid()) @db.Uuid
entryId String @map("entry_id") @db.Uuid
entry KnowledgeEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
version Int
title String
content String @db.Text
summary String?
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
createdBy String @map("created_by") @db.Uuid
changeNote String? @map("change_note")
@@unique([entryId, version])
@@index([entryId, version])
@@map("knowledge_entry_versions")
}
model KnowledgeLink {
id String @id @default(uuid()) @db.Uuid
sourceId String @map("source_id") @db.Uuid
source KnowledgeEntry @relation("SourceEntry", fields: [sourceId], references: [id], onDelete: Cascade)
targetId String @map("target_id") @db.Uuid
target KnowledgeEntry @relation("TargetEntry", fields: [targetId], references: [id], onDelete: Cascade)
// Link metadata
linkText String @map("link_text")
context String?
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
@@unique([sourceId, targetId])
@@index([sourceId])
@@index([targetId])
@@map("knowledge_links")
}
model KnowledgeTag {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
name String
slug String
color String?
description String?
entries KnowledgeEntryTag[]
@@unique([workspaceId, slug])
@@index([workspaceId])
@@map("knowledge_tags")
}
model KnowledgeEntryTag {
entryId String @map("entry_id") @db.Uuid
entry KnowledgeEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
tagId String @map("tag_id") @db.Uuid
tag KnowledgeTag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([entryId, tagId])
@@index([entryId])
@@index([tagId])
@@map("knowledge_entry_tags")
}
model KnowledgeEmbedding {
id String @id @default(uuid()) @db.Uuid
entryId String @unique @map("entry_id") @db.Uuid
entry KnowledgeEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
embedding Unsupported("vector(1536)")
model String
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@index([entryId])
@@map("knowledge_embeddings")
}
// ============================================
// CRON JOBS
// ============================================
model CronSchedule {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
// Cron configuration
expression String // Standard cron: "0 9 * * *" = 9am daily
command String // MoltBot command to trigger
// State
enabled Boolean @default(true)
lastRun DateTime? @map("last_run") @db.Timestamptz
nextRun DateTime? @map("next_run") @db.Timestamptz
// Audit
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@index([workspaceId])
@@index([workspaceId, enabled])
@@index([nextRun])
@@map("cron_schedules")
}