- Add Python code execution piece with subprocess-based runner - Add Ruby code execution piece with subprocess-based runner - Fix template loading: fetch individual templates from cloud for community edition - Add piece name aliasing for renamed pieces (piece-text-ai → piece-ai, etc.) - Add dev pieces caching to avoid disk reads on every request (60s TTL) - Add Python and Ruby logos to Django static files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
218 lines
7.1 KiB
TypeScript
218 lines
7.1 KiB
TypeScript
import {
|
|
ActivepiecesError,
|
|
ALL_PRINCIPAL_TYPES,
|
|
ApEdition,
|
|
CreateTemplateRequestBody,
|
|
ErrorCode,
|
|
FlowVersionTemplate,
|
|
isNil,
|
|
ListTemplatesRequestQuery,
|
|
Principal,
|
|
PrincipalType,
|
|
SERVICE_KEY_SECURITY_OPENAPI,
|
|
TemplateType,
|
|
UpdateTemplateRequestBody,
|
|
} from '@activepieces/shared'
|
|
import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'
|
|
import { Static, Type } from '@sinclair/typebox'
|
|
import { StatusCodes } from 'http-status-codes'
|
|
import { platformMustBeOwnedByCurrentUser } from '../ee/authentication/ee-authorization'
|
|
import { migrateFlowVersionTemplate } from '../flows/flow-version/migrations'
|
|
import { system } from '../helper/system/system'
|
|
import { platformService } from '../platform/platform.service'
|
|
import { communityTemplates } from './community-flow-template.service'
|
|
import { templateService } from './template.service'
|
|
|
|
const edition = system.getEdition()
|
|
|
|
export const templateController: FastifyPluginAsyncTypebox = async (app) => {
|
|
app.get('/:id', GetParams, async (request) => {
|
|
// For community edition, try to fetch from cloud templates first
|
|
if (edition !== ApEdition.CLOUD) {
|
|
const cloudTemplate = await communityTemplates.getById(request.params.id)
|
|
if (!isNil(cloudTemplate)) {
|
|
return cloudTemplate
|
|
}
|
|
}
|
|
// Fall back to local database
|
|
return templateService().getOneOrThrow({ id: request.params.id })
|
|
})
|
|
|
|
app.get('/', ListTemplatesParams, async (request) => {
|
|
const platformId = await resolveTemplatesPlatformIdOrThrow(request.principal, request.query.type ?? TemplateType.OFFICIAL)
|
|
if (isNil(platformId)) {
|
|
if (edition === ApEdition.CLOUD) {
|
|
return templateService().list({ platformId: null, requestQuery: request.query })
|
|
}
|
|
return communityTemplates.get(request.query)
|
|
}
|
|
return templateService().list({ platformId, requestQuery: request.query })
|
|
})
|
|
|
|
app.post('/', {
|
|
...CreateParams,
|
|
preValidation: async (request) => {
|
|
const migratedFlows = await Promise.all((request.body.flows ?? []).map(async (flow: FlowVersionTemplate) => {
|
|
const migratedFlow = await migrateFlowVersionTemplate(flow.trigger, flow.schemaVersion)
|
|
return {
|
|
...flow,
|
|
trigger: migratedFlow.trigger,
|
|
schemaVersion: migratedFlow.schemaVersion,
|
|
}
|
|
}))
|
|
request.body.flows = migratedFlows
|
|
},
|
|
}, async (request, reply) => {
|
|
const { type } = request.body
|
|
let platformId: string | undefined
|
|
|
|
switch (type) {
|
|
case TemplateType.CUSTOM: {
|
|
await platformMustBeOwnedByCurrentUser.call(app, request, reply)
|
|
platformId = request.principal.platform.id
|
|
}
|
|
break
|
|
case TemplateType.SHARED:
|
|
break
|
|
case TemplateType.OFFICIAL: {
|
|
throw new ActivepiecesError({
|
|
code: ErrorCode.VALIDATION,
|
|
params: {
|
|
message: 'Official templates are not supported to being created',
|
|
},
|
|
})
|
|
}
|
|
}
|
|
const result = await templateService().create({ platformId, params: request.body })
|
|
return reply.status(StatusCodes.CREATED).send(result)
|
|
})
|
|
|
|
app.post('/:id', UpdateParams, async (request, reply) => {
|
|
const result = await templateService().update({ id: request.params.id, params: request.body })
|
|
return reply.status(StatusCodes.OK).send(result)
|
|
})
|
|
|
|
app.post('/:id/increment-usage-count', IncrementUsageCountParams, async (request, reply) => {
|
|
await templateService().incrementUsageCount({ id: request.params.id })
|
|
return reply.status(StatusCodes.OK).send()
|
|
})
|
|
|
|
app.delete('/:id', DeleteParams, async (request, reply) => {
|
|
const template = await templateService().getOneOrThrow({ id: request.params.id })
|
|
|
|
if (template.type === TemplateType.CUSTOM) {
|
|
await platformMustBeOwnedByCurrentUser.call(app, request, reply)
|
|
}
|
|
|
|
await templateService().delete({
|
|
id: request.params.id,
|
|
})
|
|
return reply.status(StatusCodes.NO_CONTENT).send()
|
|
})
|
|
}
|
|
|
|
async function resolveTemplatesPlatformIdOrThrow(principal: Principal, type: TemplateType): Promise<string | null> {
|
|
if (principal.type === PrincipalType.UNKNOWN || principal.type === PrincipalType.WORKER || type === TemplateType.OFFICIAL) {
|
|
return null
|
|
}
|
|
|
|
if (type === TemplateType.CUSTOM) {
|
|
const platform = await platformService.getOneWithPlanOrThrow(principal.platform.id)
|
|
if (!platform.plan.manageTemplatesEnabled) {
|
|
throw new ActivepiecesError({
|
|
code: ErrorCode.FEATURE_DISABLED,
|
|
params: {
|
|
message: 'Templates are not enabled for this platform',
|
|
},
|
|
})
|
|
}
|
|
return platform.id
|
|
}
|
|
|
|
throw new ActivepiecesError({
|
|
code: ErrorCode.VALIDATION,
|
|
params: {
|
|
message: 'Invalid request, shared templates are not supported to being listed',
|
|
},
|
|
})
|
|
}
|
|
|
|
|
|
const GetIdParams = Type.Object({
|
|
id: Type.String(),
|
|
})
|
|
type GetIdParams = Static<typeof GetIdParams>
|
|
|
|
|
|
const GetParams = {
|
|
config: {
|
|
allowedPrincipals: ALL_PRINCIPAL_TYPES,
|
|
},
|
|
schema: {
|
|
tags: ['templates'],
|
|
description: 'Get a template.',
|
|
security: [SERVICE_KEY_SECURITY_OPENAPI],
|
|
params: GetIdParams,
|
|
},
|
|
}
|
|
|
|
const ListTemplatesParams = {
|
|
config: {
|
|
allowedPrincipals: ALL_PRINCIPAL_TYPES,
|
|
},
|
|
schema: {
|
|
tags: ['templates'],
|
|
description: 'List templates.',
|
|
security: [SERVICE_KEY_SECURITY_OPENAPI],
|
|
querystring: ListTemplatesRequestQuery,
|
|
},
|
|
}
|
|
|
|
const DeleteParams = {
|
|
config: {
|
|
allowedPrincipals: [PrincipalType.USER, PrincipalType.SERVICE] as const,
|
|
},
|
|
schema: {
|
|
description: 'Delete a template.',
|
|
tags: ['templates'],
|
|
security: [SERVICE_KEY_SECURITY_OPENAPI],
|
|
params: GetIdParams,
|
|
},
|
|
}
|
|
|
|
const CreateParams = {
|
|
config: {
|
|
allowedPrincipals: [PrincipalType.USER, PrincipalType.SERVICE] as const,
|
|
},
|
|
schema: {
|
|
description: 'Create a template.',
|
|
tags: ['templates'],
|
|
security: [SERVICE_KEY_SECURITY_OPENAPI],
|
|
body: CreateTemplateRequestBody,
|
|
},
|
|
}
|
|
|
|
const UpdateParams = {
|
|
config: {
|
|
allowedPrincipals: [PrincipalType.USER, PrincipalType.SERVICE] as const,
|
|
},
|
|
schema: {
|
|
description: 'Update a template.',
|
|
tags: ['templates'],
|
|
security: [SERVICE_KEY_SECURITY_OPENAPI],
|
|
params: GetIdParams,
|
|
body: UpdateTemplateRequestBody,
|
|
},
|
|
}
|
|
|
|
const IncrementUsageCountParams = {
|
|
config: {
|
|
allowedPrincipals: [PrincipalType.USER, PrincipalType.SERVICE] as const,
|
|
},
|
|
schema: {
|
|
description: 'Increment usage count of a template.',
|
|
tags: ['templates'],
|
|
security: [SERVICE_KEY_SECURITY_OPENAPI],
|
|
params: GetIdParams,
|
|
},
|
|
} |