@appsemble/node-utils
Version:
NodeJS utilities used by Appsemble internally.
118 lines • 5.63 kB
JavaScript
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