Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions sim/app/api/schedules/execute/route.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { NextRequest, NextResponse } from 'next/server'
import { Cron } from 'croner'
import { eq, lte } from 'drizzle-orm'
import { sql } from 'drizzle-orm'
import { v4 as uuidv4 } from 'uuid'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console-logger'
import { persistExecutionError, persistExecutionLogs } from '@/lib/logs/execution-logger'
import { buildTraceSpans } from '@/lib/logs/trace-spans'
import { decryptSecret } from '@/lib/utils'
import { updateWorkflowRunCounts } from '@/lib/workflows/utils'
import { mergeSubblockState } from '@/stores/workflows/utils'
import { BlockState, WorkflowState } from '@/stores/workflows/workflow/types'
import { db } from '@/db'
import { environment, workflow, workflowSchedule } from '@/db/schema'
import { environment, userStats, workflow, workflowSchedule } from '@/db/schema'
import { Executor } from '@/executor'
import { Serializer } from '@/serializer'

Expand Down Expand Up @@ -326,6 +328,25 @@ export async function GET(req: NextRequest) {
)
const result = await executor.execute(schedule.workflowId)

logger.info(`[${requestId}] Workflow execution completed: ${schedule.workflowId}`, {
success: result.success,
executionTime: result.metadata?.duration,
})

// Update workflow run counts if execution was successful
if (result.success) {
await updateWorkflowRunCounts(schedule.workflowId)

// Track scheduled execution in user stats
await db
.update(userStats)
.set({
totalScheduledExecutions: sql`total_scheduled_executions + 1`,
lastActive: new Date(),
})
.where(eq(userStats.userId, workflowRecord.userId))
}

// Build trace spans from execution logs
const { traceSpans, totalDuration } = buildTraceSpans(result)

Expand Down Expand Up @@ -371,10 +392,6 @@ export async function GET(req: NextRequest) {
nextRunAt: nextRetryAt,
})
.where(eq(workflowSchedule.id, schedule.id))

logger.debug(
`[${requestId}] Scheduled retry for workflow ${schedule.workflowId} at ${nextRetryAt.toISOString()}`
)
}
} catch (error: any) {
logger.error(
Expand Down
2 changes: 1 addition & 1 deletion sim/app/api/schedules/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createLogger } from '@/lib/logs/console-logger'
import { db } from '@/db'
import { workflowSchedule } from '@/db/schema'

const logger = createLogger('Scheduled API')
const logger = createLogger('ScheduledAPI')

/**
* Get schedule information for a workflow
Expand Down
68 changes: 68 additions & 0 deletions sim/app/api/user/stats/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { NextRequest, NextResponse } from 'next/server'
import { eq, sql } from 'drizzle-orm'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console-logger'
import { db } from '@/db'
import { userStats, workflow } from '@/db/schema'

const logger = createLogger('UserStatsAPI')

/**
* GET endpoint to retrieve user statistics including the count of workflows
*/
export async function GET(request: NextRequest) {
try {
// Get the user session
const session = await getSession()
if (!session?.user?.id) {
logger.warn('Unauthorized user stats access attempt')
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const userId = session.user.id

// Get workflow count for user
const [workflowCountResult] = await db
.select({ count: sql`count(*)::int` })
.from(workflow)
.where(eq(workflow.userId, userId))

const workflowCount = workflowCountResult?.count || 0

// Get user stats record
const userStatsRecords = await db.select().from(userStats).where(eq(userStats.userId, userId))

// If no stats record exists, create one
if (userStatsRecords.length === 0) {
const newStats = {
id: crypto.randomUUID(),
userId,
totalManualExecutions: 0,
totalApiCalls: 0,
totalWebhookTriggers: 0,
totalScheduledExecutions: 0,
totalTokensUsed: 0,
totalCost: '0.00',
lastActive: new Date(),
}

await db.insert(userStats).values(newStats)

// Return the newly created stats with workflow count
return NextResponse.json({
...newStats,
workflowCount,
})
}

// Return stats with workflow count
const stats = userStatsRecords[0]
return NextResponse.json({
...stats,
workflowCount,
})
} catch (error) {
logger.error('Error fetching user stats:', error)
return NextResponse.json({ error: 'Failed to fetch user statistics' }, { status: 500 })
}
}
19 changes: 17 additions & 2 deletions sim/app/api/webhooks/trigger/[path]/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { NextRequest, NextResponse } from 'next/server'
import { and, eq } from 'drizzle-orm'
import { and, eq, sql } from 'drizzle-orm'
import { v4 as uuidv4 } from 'uuid'
import { createLogger } from '@/lib/logs/console-logger'
import { persistExecutionError, persistExecutionLogs } from '@/lib/logs/execution-logger'
import { buildTraceSpans } from '@/lib/logs/trace-spans'
import { closeRedisConnection, hasProcessedMessage, markMessageAsProcessed } from '@/lib/redis'
import { decryptSecret } from '@/lib/utils'
import { updateWorkflowRunCounts } from '@/lib/workflows/utils'
import { mergeSubblockStateAsync } from '@/stores/workflows/utils'
import { db } from '@/db'
import { environment, webhook, workflow } from '@/db/schema'
import { environment, userStats, webhook, workflow } from '@/db/schema'
import { Executor } from '@/executor'
import { Serializer } from '@/serializer'

Expand Down Expand Up @@ -559,6 +560,20 @@ async function processWebhook(
executionTime: result.metadata?.duration,
})

// Update workflow run counts if execution was successful
if (result.success) {
await updateWorkflowRunCounts(foundWorkflow.id)

// Track webhook trigger in user stats
await db
.update(userStats)
.set({
totalWebhookTriggers: sql`total_webhook_triggers + 1`,
lastActive: new Date(),
})
.where(eq(userStats.userId, foundWorkflow.userId))
}

// Build trace spans from execution logs
const { traceSpans, totalDuration } = buildTraceSpans(result)

Expand Down
53 changes: 53 additions & 0 deletions sim/app/api/workflows/[id]/deploy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,59 @@ const logger = createLogger('WorkflowDeployAPI')
export const dynamic = 'force-dynamic'
export const runtime = 'nodejs'

export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = crypto.randomUUID().slice(0, 8)
const { id } = await params

try {
logger.debug(`[${requestId}] Fetching deployment info for workflow: ${id}`)
const validation = await validateWorkflowAccess(request, id, false)

if (validation.error) {
logger.warn(`[${requestId}] Failed to fetch deployment info: ${validation.error.message}`)
return createErrorResponse(validation.error.message, validation.error.status)
}

// Fetch the workflow information including deployment details
const result = await db
.select({
apiKey: workflow.apiKey,
isDeployed: workflow.isDeployed,
deployedAt: workflow.deployedAt,
})
.from(workflow)
.where(eq(workflow.id, id))
.limit(1)

if (result.length === 0) {
logger.warn(`[${requestId}] Workflow not found: ${id}`)
return createErrorResponse('Workflow not found', 404)
}

const workflowData = result[0]

// If the workflow is not deployed, return appropriate response
if (!workflowData.isDeployed || !workflowData.apiKey) {
logger.info(`[${requestId}] Workflow is not deployed: ${id}`)
return createSuccessResponse({
isDeployed: false,
apiKey: null,
deployedAt: null,
})
}

logger.info(`[${requestId}] Successfully retrieved deployment info: ${id}`)
return createSuccessResponse({
apiKey: workflowData.apiKey,
isDeployed: workflowData.isDeployed,
deployedAt: workflowData.deployedAt,
})
} catch (error: any) {
logger.error(`[${requestId}] Error fetching deployment info: ${id}`, error)
return createErrorResponse(error.message || 'Failed to fetch deployment information', 500)
}
}

export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = crypto.randomUUID().slice(0, 8)
const { id } = await params
Expand Down
19 changes: 17 additions & 2 deletions sim/app/api/workflows/[id]/execute/route.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { NextRequest } from 'next/server'
import { eq } from 'drizzle-orm'
import { eq, sql } from 'drizzle-orm'
import { v4 as uuidv4 } from 'uuid'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console-logger'
import { persistExecutionError, persistExecutionLogs } from '@/lib/logs/execution-logger'
import { buildTraceSpans } from '@/lib/logs/trace-spans'
import { decryptSecret } from '@/lib/utils'
import { updateWorkflowRunCounts } from '@/lib/workflows/utils'
import { mergeSubblockState } from '@/stores/workflows/utils'
import { WorkflowState } from '@/stores/workflows/workflow/types'
import { db } from '@/db'
import { environment } from '@/db/schema'
import { environment, userStats } from '@/db/schema'
import { Executor } from '@/executor'
import { Serializer } from '@/serializer'
import { validateWorkflowAccess } from '../../middleware'
Expand Down Expand Up @@ -159,6 +160,20 @@ async function executeWorkflow(workflow: any, requestId: string, input?: any) {
executionTime: result.metadata?.duration,
})

// Update workflow run counts if execution was successful
if (result.success) {
await updateWorkflowRunCounts(workflowId)

// Track API call in user stats
await db
.update(userStats)
.set({
totalApiCalls: sql`total_api_calls + 1`,
lastActive: new Date(),
})
.where(eq(userStats.userId, workflow.userId))
}

// Build trace spans from execution logs
const { traceSpans, totalDuration } = buildTraceSpans(result)

Expand Down
89 changes: 89 additions & 0 deletions sim/app/api/workflows/[id]/stats/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { NextRequest, NextResponse } from 'next/server'
import { eq, sql } from 'drizzle-orm'
import { createLogger } from '@/lib/logs/console-logger'
import { db } from '@/db'
import { userStats, workflow } from '@/db/schema'

const logger = createLogger('WorkflowStatsAPI')

export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const searchParams = request.nextUrl.searchParams
const runs = parseInt(searchParams.get('runs') || '1', 10)

if (isNaN(runs) || runs < 1 || runs > 100) {
logger.error(`Invalid number of runs: ${runs}`)
return NextResponse.json(
{ error: 'Invalid number of runs. Must be between 1 and 100.' },
{ status: 400 }
)
}

try {
// Get workflow record
const [workflowRecord] = await db.select().from(workflow).where(eq(workflow.id, id)).limit(1)

if (!workflowRecord) {
return NextResponse.json({ error: `Workflow ${id} not found` }, { status: 404 })
}

// Update workflow runCount
try {
await db
.update(workflow)
.set({
runCount: workflowRecord.runCount + runs,
lastRunAt: new Date(),
})
.where(eq(workflow.id, id))
} catch (error) {
logger.error('Error updating workflow runCount:', error)
throw error
}

// Upsert user stats record
try {
// Check if record exists
const userStatsRecords = await db
.select()
.from(userStats)
.where(eq(userStats.userId, workflowRecord.userId))

if (userStatsRecords.length === 0) {
// Create new record if none exists
await db.insert(userStats).values({
id: crypto.randomUUID(),
userId: workflowRecord.userId,
totalManualExecutions: runs,
totalApiCalls: 0,
totalWebhookTriggers: 0,
totalScheduledExecutions: 0,
totalTokensUsed: 0,
totalCost: '0.00',
lastActive: new Date(),
})
} else {
// Update existing record
await db
.update(userStats)
.set({
totalManualExecutions: sql`total_manual_executions + ${runs}`,
lastActive: new Date(),
})
.where(eq(userStats.userId, workflowRecord.userId))
}
} catch (error) {
logger.error(`Error upserting userStats for userId ${workflowRecord.userId}:`, error)
// Don't rethrow - we want to continue even if this fails
}

return NextResponse.json({
success: true,
runsAdded: runs,
newTotal: workflowRecord.runCount + runs,
})
} catch (error) {
logger.error('Error updating workflow stats:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
2 changes: 1 addition & 1 deletion sim/app/api/workflows/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextRequest } from 'next/server'
import { createLogger } from '@/lib/logs/console-logger'
import { getWorkflowById } from '@/lib/workflows'
import { getWorkflowById } from '@/lib/workflows/utils'

const logger = createLogger('WorkflowMiddleware')

Expand Down
Loading