chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { assertWorkflowMutable, WorkflowLockedError } from '@sim/platform-authz/workflow'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { workflowDeploymentVersionParamSchema } from '@/lib/api/contracts/workflows'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
|
||||
import { performRevertToVersion } from '@/lib/workflows/orchestration'
|
||||
import { validateWorkflowPermissions } from '@/lib/workflows/utils'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
|
||||
const logger = createLogger('RevertToDeploymentVersionAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const runtime = 'nodejs'
|
||||
|
||||
export const POST = withRouteHandler(
|
||||
async (
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; version: string }> }
|
||||
) => {
|
||||
const requestId = generateRequestId()
|
||||
const { id, version } = await params
|
||||
|
||||
try {
|
||||
const {
|
||||
error,
|
||||
session,
|
||||
workflow: workflowRecord,
|
||||
} = await validateWorkflowPermissions(id, requestId, 'admin')
|
||||
if (error) {
|
||||
return createErrorResponse(error.message, error.status)
|
||||
}
|
||||
await assertWorkflowMutable(id)
|
||||
|
||||
const versionValidation = workflowDeploymentVersionParamSchema.safeParse(version)
|
||||
if (!versionValidation.success) {
|
||||
return createErrorResponse('Invalid version', 400)
|
||||
}
|
||||
|
||||
const result = await performRevertToVersion({
|
||||
workflowId: id,
|
||||
version: versionValidation.data,
|
||||
userId: session!.user.id,
|
||||
workflow: (workflowRecord ?? {}) as Record<string, unknown>,
|
||||
request,
|
||||
actorName: session!.user.name ?? undefined,
|
||||
actorEmail: session!.user.email ?? undefined,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
return createErrorResponse(
|
||||
result.error || 'Failed to revert',
|
||||
result.errorCode === 'not_found' ? 404 : 500
|
||||
)
|
||||
}
|
||||
|
||||
return createSuccessResponse({
|
||||
message: 'Reverted to deployment version',
|
||||
lastSaved: result.lastSaved,
|
||||
})
|
||||
} catch (error: any) {
|
||||
if (error instanceof WorkflowLockedError) {
|
||||
return createErrorResponse(error.message, error.status)
|
||||
}
|
||||
|
||||
logger.error('Error reverting to deployment version', error)
|
||||
return createErrorResponse(error.message || 'Failed to revert', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,188 @@
|
||||
import { db, workflowDeploymentVersion } from '@sim/db'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { updateDeploymentVersionMetadataContract } from '@/lib/api/contracts/deployments'
|
||||
import { getValidationErrorMessage, parseRequest } from '@/lib/api/server'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
|
||||
import { captureServerEvent } from '@/lib/posthog/server'
|
||||
import { performActivateVersion } from '@/lib/workflows/orchestration'
|
||||
import { statusForOrchestrationError } from '@/lib/workflows/orchestration/types'
|
||||
import {
|
||||
getWorkflowDeploymentVersion,
|
||||
updateDeploymentVersionMetadata,
|
||||
} from '@/lib/workflows/persistence/utils'
|
||||
import { validateWorkflowPermissions } from '@/lib/workflows/utils'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
|
||||
const logger = createLogger('WorkflowDeploymentVersionAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const runtime = 'nodejs'
|
||||
export const maxDuration = 120
|
||||
|
||||
export const GET = withRouteHandler(
|
||||
async (
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; version: string }> }
|
||||
) => {
|
||||
const requestId = generateRequestId()
|
||||
const { id, version } = await params
|
||||
|
||||
try {
|
||||
const { error } = await validateWorkflowPermissions(id, requestId, 'read')
|
||||
if (error) {
|
||||
return createErrorResponse(error.message, error.status)
|
||||
}
|
||||
|
||||
const versionNum = Number(version)
|
||||
if (!Number.isFinite(versionNum)) {
|
||||
return createErrorResponse('Invalid version', 400)
|
||||
}
|
||||
|
||||
const row = await getWorkflowDeploymentVersion(id, versionNum)
|
||||
if (!row?.state) {
|
||||
return createErrorResponse('Deployment version not found', 404)
|
||||
}
|
||||
|
||||
return createSuccessResponse({ deployedState: row.state })
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`[${requestId}] Error fetching deployment version ${version} for workflow ${id}`,
|
||||
error
|
||||
)
|
||||
return createErrorResponse(error.message || 'Failed to fetch deployment version', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export const PATCH = withRouteHandler(
|
||||
async (request: NextRequest, context: { params: Promise<{ id: string; version: string }> }) => {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const parsed = await parseRequest(updateDeploymentVersionMetadataContract, request, context, {
|
||||
validationErrorResponse: (error) =>
|
||||
createErrorResponse(getValidationErrorMessage(error, 'Invalid request body'), 400),
|
||||
})
|
||||
if (!parsed.success) return parsed.response
|
||||
|
||||
const { id, version } = parsed.data.params
|
||||
const { name, description, isActive } = parsed.data.body
|
||||
|
||||
// Activation requires admin permission, other updates require write
|
||||
const requiredPermission = isActive ? 'admin' : 'write'
|
||||
const {
|
||||
error,
|
||||
session,
|
||||
workflow: workflowData,
|
||||
} = await validateWorkflowPermissions(id, requestId, requiredPermission)
|
||||
if (error) {
|
||||
return createErrorResponse(error.message, error.status)
|
||||
}
|
||||
|
||||
const versionNum = version
|
||||
|
||||
// Handle activation
|
||||
if (isActive) {
|
||||
const actorUserId = session?.user?.id
|
||||
if (!actorUserId) {
|
||||
logger.warn(
|
||||
`[${requestId}] Unable to resolve actor user for deployment activation: ${id}`
|
||||
)
|
||||
return createErrorResponse('Unable to determine activating user', 400)
|
||||
}
|
||||
|
||||
const activateResult = await performActivateVersion({
|
||||
workflowId: id,
|
||||
version: versionNum,
|
||||
userId: actorUserId,
|
||||
workflow: workflowData as Record<string, unknown>,
|
||||
requestId,
|
||||
request,
|
||||
})
|
||||
|
||||
if (!activateResult.success) {
|
||||
return createErrorResponse(
|
||||
activateResult.error || 'Failed to activate deployment',
|
||||
statusForOrchestrationError(activateResult.errorCode)
|
||||
)
|
||||
}
|
||||
|
||||
let updatedName: string | null | undefined
|
||||
let updatedDescription: string | null | undefined
|
||||
if (name !== undefined || description !== undefined) {
|
||||
const activationUpdateData: { name?: string; description?: string | null } = {}
|
||||
if (name !== undefined) {
|
||||
activationUpdateData.name = name
|
||||
}
|
||||
if (description !== undefined) {
|
||||
activationUpdateData.description = description
|
||||
}
|
||||
|
||||
const [updated] = await db
|
||||
.update(workflowDeploymentVersion)
|
||||
.set(activationUpdateData)
|
||||
.where(
|
||||
and(
|
||||
eq(workflowDeploymentVersion.workflowId, id),
|
||||
eq(workflowDeploymentVersion.version, versionNum)
|
||||
)
|
||||
)
|
||||
.returning({
|
||||
name: workflowDeploymentVersion.name,
|
||||
description: workflowDeploymentVersion.description,
|
||||
})
|
||||
|
||||
if (updated) {
|
||||
updatedName = updated.name
|
||||
updatedDescription = updated.description
|
||||
logger.info(
|
||||
`[${requestId}] Updated deployment version ${version} metadata during activation`,
|
||||
{ name: activationUpdateData.name, description: activationUpdateData.description }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const wsId = (workflowData as { workspaceId?: string } | null)?.workspaceId
|
||||
captureServerEvent(
|
||||
actorUserId,
|
||||
'deployment_version_activated',
|
||||
{ workflow_id: id, workspace_id: wsId ?? '', version: versionNum },
|
||||
wsId ? { groups: { workspace: wsId } } : undefined
|
||||
)
|
||||
|
||||
return createSuccessResponse({
|
||||
success: true,
|
||||
deployedAt: activateResult.deployedAt,
|
||||
warnings: activateResult.warnings,
|
||||
...(updatedName !== undefined && { name: updatedName }),
|
||||
...(updatedDescription !== undefined && { description: updatedDescription }),
|
||||
})
|
||||
}
|
||||
|
||||
// Handle name/description updates (shared with the update_deployment_version copilot tool)
|
||||
const updated = await updateDeploymentVersionMetadata({
|
||||
workflowId: id,
|
||||
version: versionNum,
|
||||
name,
|
||||
description,
|
||||
})
|
||||
|
||||
if (!updated) {
|
||||
return createErrorResponse('Deployment version not found', 404)
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Updated deployment version ${version} for workflow ${id}`, {
|
||||
name,
|
||||
description,
|
||||
})
|
||||
|
||||
return createSuccessResponse({ name: updated.name, description: updated.description })
|
||||
} catch (error: any) {
|
||||
logger.error(`[${requestId}] Error updating deployment version`, error)
|
||||
return createErrorResponse(error.message || 'Failed to update deployment version', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { listDeploymentVersionsContract } from '@/lib/api/contracts/deployments'
|
||||
import { parseRequest } from '@/lib/api/server'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
|
||||
import { listWorkflowVersions } from '@/lib/workflows/persistence/utils'
|
||||
import { validateWorkflowPermissions } from '@/lib/workflows/utils'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
|
||||
const logger = createLogger('WorkflowDeploymentsListAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const runtime = 'nodejs'
|
||||
|
||||
export const GET = withRouteHandler(
|
||||
async (request: NextRequest, context: { params: Promise<{ id: string }> }) => {
|
||||
const requestId = generateRequestId()
|
||||
const parsed = await parseRequest(listDeploymentVersionsContract, request, context)
|
||||
if (!parsed.success) return parsed.response
|
||||
const { id } = parsed.data.params
|
||||
|
||||
try {
|
||||
const { error } = await validateWorkflowPermissions(id, requestId, 'read')
|
||||
if (error) {
|
||||
return createErrorResponse(error.message, error.status)
|
||||
}
|
||||
|
||||
const { versions: rows } = await listWorkflowVersions(id)
|
||||
const versions = rows.map(({ deployedByName, ...version }) => ({
|
||||
...version,
|
||||
deployedBy: deployedByName,
|
||||
}))
|
||||
|
||||
return createSuccessResponse({ versions })
|
||||
} catch (error: any) {
|
||||
logger.error(`[${requestId}] Error listing deployments for workflow: ${id}`, error)
|
||||
return createErrorResponse(error.message || 'Failed to list deployments', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user