Allow custom transformations of assets (#6593)

* Allow custom transformations of assets

This exposes one query parameter `transforms`, which is a JSON array of
shard transformation operations.

It also updates the asset presets. The UX for this still needs some work

* Rename options to arguments for presets

More explicit

* options -> arguments in setting spec

* Better errors for invalid JSON in asset presets

* Add limit to transforms query parameter

* Use flattened option for extra transforms

* Fix placeholder color of code input

* Allow "simple mode" aliases

* Add documentation

Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
Tim
2021-07-22 07:57:47 +12:00
committed by GitHub
parent 7dffa48570
commit 2c9ff3bca6
15 changed files with 387 additions and 118 deletions

View File

@@ -10,7 +10,7 @@ import { ForbiddenException, InvalidQueryException, RangeNotSatisfiableException
import useCollection from '../middleware/use-collection';
import { AssetsService, PayloadService } from '../services';
import storage from '../storage';
import { Transformation } from '../types/assets';
import { TransformationParams, TransformationMethods, TransformationPreset } from '../types/assets';
import asyncHandler from '../utils/async-handler';
const router = Router();
@@ -68,26 +68,63 @@ router.get(
if ('key' in transformation && Object.keys(transformation).length > 1) {
throw new InvalidQueryException(`You can't combine the "key" query parameter with any other transformation.`);
}
if ('quality' in transformation && (Number(transformation.quality) < 1 || Number(transformation.quality) > 100)) {
throw new InvalidQueryException(`"quality" Parameter has to between 1 to 100`);
if ('transforms' in transformation) {
let transforms: unknown;
// Try parse the JSON array
try {
transforms = JSON.parse(transformation['transforms'] as string);
} catch {
throw new InvalidQueryException(`"transforms" Parameter needs to be a JSON array of allowed transformations.`);
}
// Check if it is actually an array.
if (!Array.isArray(transforms)) {
throw new InvalidQueryException(`"transforms" Parameter needs to be a JSON array of allowed transformations.`);
}
// Check against ASSETS_TRANSFORM_MAX_OPERATIONS
if (transforms.length > Number(env.ASSETS_TRANSFORM_MAX_OPERATIONS)) {
throw new InvalidQueryException(
`"transforms" Parameter is only allowed ${env.ASSETS_TRANSFORM_MAX_OPERATIONS} transformations.`
);
}
// Check the transformations are valid
transforms.forEach((transform) => {
const name = transform[0];
if (!TransformationMethods.includes(name)) {
throw new InvalidQueryException(`"transforms" Parameter does not allow "${name}" as a transformation.`);
}
});
transformation.transforms = transforms;
}
const systemKeys = SYSTEM_ASSET_ALLOW_LIST.map((transformation) => transformation.key);
const systemKeys = SYSTEM_ASSET_ALLOW_LIST.map((transformation) => transformation.key!);
const allKeys: string[] = [
...systemKeys,
...(assetSettings.storage_asset_presets || []).map((transformation: Transformation) => transformation.key),
...(assetSettings.storage_asset_presets || []).map((transformation: TransformationParams) => transformation.key),
];
// For use in the next request handler
res.locals.shortcuts = [...SYSTEM_ASSET_ALLOW_LIST, ...(assetSettings.storage_asset_presets || [])];
res.locals.transformation = transformation;
if (Object.keys(transformation).length === 0) {
if (
Object.keys(transformation).length === 0 ||
('transforms' in transformation && transformation.transforms!.length === 0)
) {
return next();
}
if (assetSettings.storage_asset_transform === 'all') {
if (transformation.key && allKeys.includes(transformation.key as string) === false)
if (transformation.key && allKeys.includes(transformation.key as string) === false) {
throw new InvalidQueryException(`Key "${transformation.key}" isn't configured.`);
}
return next();
} else if (assetSettings.storage_asset_transform === 'presets') {
if (allKeys.includes(transformation.key as string)) return next();
@@ -107,9 +144,9 @@ router.get(
schema: req.schema,
});
const transformation: Transformation = res.locals.transformation.key
? res.locals.shortcuts.find(
(transformation: Transformation) => transformation.key === res.locals.transformation.key
const transformation: TransformationParams | TransformationPreset = res.locals.transformation.key
? (res.locals.shortcuts as TransformationPreset[]).find(
(transformation) => transformation.key === res.locals.transformation.key
)
: res.locals.transformation;