UNPKG

@appsemble/node-utils

Version:

NodeJS utilities used by Appsemble internally.

118 lines 5.63 kB
import { randomUUID } from 'node:crypto'; import { copyFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { isDeepStrictEqual } from 'node:util'; import { getResourceDefinition, getSingleGroupId, processResourceBody, setResourceEtagHeader, validateResourceReferences, } from '../../../../../index.js'; function clonePreparedAssets(preparedAssets) { return Promise.all(preparedAssets.map(async (asset) => { const path = join(tmpdir(), `${Date.now()}-${randomUUID()}`); await copyFile(asset.path, path); return { ...asset, path }; })); } /** * Create a controller for resource creation. * * The created controller handles resource creation of one or more resources, * extracted from the request body. * * The controller can be used to seed resources into an app when the endpoint is * called from the CLI with the `seed` parameter equal to `true`. * * @param options The options object to use for resource creation * @returns A middleware function that handles resource creation. */ export function createCreateAppResourceController(options) { return async (ctx) => { const { pathParams: { appId, resourceType }, query, queryParams: { selectedGroupId }, } = ctx; const groupId = getSingleGroupId(selectedGroupId); const { checkAppPermissions, createAppResourcesWithAssets, getApp, getAppAssets, getAppResources, } = options; const app = await getApp({ context: ctx, query: { attributes: ['id', 'demoMode', 'definition'], where: { id: appId } }, }); await checkAppPermissions({ context: ctx, permissions: [`$resource:${resourceType}:create`], app, // The resource is created in a single group; authorize against that group. groupId, }); const resourceDefinition = getResourceDefinition(app.definition, resourceType, ctx); const appAssets = await getAppAssets({ app, context: ctx }); const [processedBody, preparedAssets] = await processResourceBody(ctx, resourceDefinition, appAssets.map(({ id }) => id), undefined, appAssets.map((appAsset) => ({ id: appAsset.id, name: appAsset.name }))); if (Array.isArray(processedBody) && !processedBody.length) { ctx.body = []; return; } await validateResourceReferences(ctx, app, resourceDefinition, processedBody, getAppResources); const resources = Array.isArray(processedBody) ? processedBody : [processedBody]; if (!(ctx.client && 'app' in ctx.client) && query?.seed === 'true') { const preparedSeedAssets = app.demoMode ? await clonePreparedAssets(structuredClone(preparedAssets)) : structuredClone(preparedAssets); const preparedSeedResources = resources.map((resource) => { const cleanResource = { ...resource }; if (app.demoMode) { for (const referencedProperty of Object.keys(resourceDefinition.references ?? {})) { delete cleanResource[referencedProperty]; } } return { ...cleanResource, $seed: true, $ephemeral: false, }; }); for (const preparedSeedAsset of preparedSeedAssets) { const index = resources.findIndex(({ $clonable: clonable, $ephemeral: ephemeral, $seed: unused, ...cleanResource }) => { // @ts-expect-error Messed up const { $clonable, $ephemeral, $seed, ...cleanAssetResource } = preparedSeedAsset.resource; return isDeepStrictEqual(cleanResource, cleanAssetResource); }); const previousAssetId = preparedSeedAsset.id; const newAssetId = randomUUID(); const updatedResource = JSON.parse(JSON.stringify(preparedSeedResources[index]).replace(previousAssetId, newAssetId)); preparedSeedAsset.id = newAssetId; preparedSeedAsset.resource = updatedResource; preparedSeedResources[index] = updatedResource; } const createdSeedResources = await createAppResourcesWithAssets({ app, groupId, context: ctx, resources: preparedSeedResources, preparedAssets: preparedSeedAssets, resourceType, options, }); if (!app.demoMode) { ctx.body = Array.isArray(processedBody) ? createdSeedResources : createdSeedResources[0]; if (!Array.isArray(ctx.body)) { setResourceEtagHeader(ctx, ctx.body); } return; } } const createdResources = await createAppResourcesWithAssets({ app, groupId, context: ctx, resources: resources.map((resource) => ({ ...resource, $seed: false, $ephemeral: app.demoMode, ...(app.demoMode ? { $clonable: false } : {}), })), preparedAssets, resourceType, options, }); ctx.body = Array.isArray(processedBody) ? createdResources : createdResources[0]; if (!Array.isArray(ctx.body)) { setResourceEtagHeader(ctx, ctx.body); } }; } //# sourceMappingURL=createCreateAppResourceController.js.map