diff --git a/Dockerfile b/Dockerfile index 73bf450..c86e45b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,8 @@ RUN ls -hal # RUNNER FROM base +ENV NODE_ENV=production + COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile --prod diff --git a/package.json b/package.json index 36044a0..170514b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "morphus", - "version": "0.1.0", + "version": "1.0.0-rc.2", "description": "", "author": "Niccolo Borgioli", "license": "MIT", diff --git a/src/config.ts b/src/config.ts index aa9704a..c79999e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -30,6 +30,7 @@ const Schema = yaml.DEFAULT_SCHEMA.extend([RegExpTag]) export type NullableStringOrRegexpArray = (string | RegExp)[] | null function formatNullableStringOrRegexpArray(values: any) { + if (values === null) return if (!Array.isArray(values)) throw new Error('must be an array') if (values.length === 0) throw new Error('must be an array with at least one element') for (const value of values) { @@ -39,6 +40,18 @@ function formatNullableStringOrRegexpArray(values: any) { } } +type PresetsConfig = Record | null +function formatPresets(values: any) { + if (values === null) return + if (typeof values === 'object') { + for (const key in values) { + if (typeof values[key] !== 'string') throw new Error('entries for presets must be strings') + } + } else { + throw new Error('presets must be an object or null') + } +} + convict.addParser({ extension: ['yml', 'yaml'], parse: (s) => yaml.load(s, { schema: Schema }) }) export const config = convict({ @@ -101,6 +114,19 @@ export const config = convict({ env: 'STORAGE', }, + // Presets + presets: { + doc: 'The presets to use', + format: formatPresets, + nullable: true, + default: null as PresetsConfig, + }, + onlyAllowPresets: { + doc: 'Whether to allow only presets', + format: Boolean, + default: false, + }, + // Local storage local: { assets: { diff --git a/src/controllers/image.ts b/src/controllers/image.ts index c35c6f8..601b5d9 100644 --- a/src/controllers/image.ts +++ b/src/controllers/image.ts @@ -61,6 +61,11 @@ export class ComplexParameter { @IsObject() options: T + /** + * parses a parameter value from a string + * + * @param parameter parameter to parse + */ constructor(parameter: string) { const [name, optionsRaw] = parameter.split('|') if (!name) throw new Error('Invalid parameter') @@ -115,8 +120,33 @@ export class TransformQueryBase { @ValidateNested() op: ComplexParameter[] = [] + @IsOptional() + @IsString() + preset?: string + constructor(data: any, options: { headers: IncomingHttpHeaders }) { - Object.assign(this, data) + if (Config.onlyAllowPresets) { + const { url, preset, ...rest } = data + if (!preset) { + throw new Error('Preset is required') + } + if (Object.keys(rest).length > 0) { + throw new Error('only preset parameter is allowed') + } + this.url = url + this.preset = data.preset + } else { + Object.assign(this, data) + } + + if (this.preset) { + const preset = Config.presets[this.preset] + if (!preset) { + throw new Error('preset not found') + } + const params = Object.fromEntries(new URLSearchParams(preset).entries()) + Object.assign(this, params) + } if (this.width) this.width = parseInt(this.width as any) if (this.height) this.height = parseInt(this.height as any) @@ -127,8 +157,7 @@ export class TransformQueryBase { // @ts-ignore this.format = new ComplexParameter((this.format as any) || 'auto') if ((this.format.name as string) === 'auto') { - if (!options.headers) throw new Error('cannot use auto format without user agent') - + if (!options.headers) throw new Error('cannot use auto format without headers') this.autoFormat(options.headers) } @@ -152,13 +181,7 @@ export class TransformQueryBase { } } - toString(): string { - const data = flatten(this) as Record - return new URLSearchParams(sortObjectByKeys(data)).toString() - } - - autoFormat(headers: IncomingHttpHeaders) { - const ua = headers['user-agent'] + private autoFormat(headers: IncomingHttpHeaders) { const accept = headers['accept'] // Accept: image/avif,image/webp,*/* if (accept) { const acceptTypes = accept.split(',') @@ -173,6 +196,11 @@ export class TransformQueryBase { this.format!.name = 'jpeg' } + toString(): string { + const data = flatten(this) as Record + return new URLSearchParams(sortObjectByKeys(data)).toString() + } + get hash(): string { return sha3(this.toString()) }