mono/packages/media/cpp/ref/images/routes.ts
2026-04-12 22:38:43 +02:00

284 lines
8.8 KiB
TypeScript

import { createRoute, z } from '@hono/zod-openapi';
import { createLogRoutes } from '../../commons/log-routes-factory.js';
import { Public, Admin } from '../../commons/decorators.js';
export const { getRoute: getImageLogsRoute, streamRoute: streamImageLogsRoute } = createLogRoutes('Images', '/api/images/logs');
/**
* Factory function to create an image service route with optional decorators
*/
function createRouteBody(
method: string,
path: string,
tags: string[],
summary: string,
description: string,
request: any,
responses: any,
publicRoute: boolean = false,
adminRoute: boolean = false
) {
let route = createRoute({
method: method as any,
path,
tags,
summary,
description,
request,
responses
});
if (publicRoute) {
route = Public(route);
}
if (adminRoute) {
route = Admin(route);
}
return route;
}
export const postImageRoute = createRouteBody(
'post',
'/api/images',
['Images'],
'Upload and resize image',
'Upload an image and get a resized version via redirect.',
{
query: z.object({
preset: z.string().optional().openapi({ description: 'Predefined preset (e.g., desktop:thumb)' }),
forward: z.string().optional().openapi({ description: 'Forward to external storage (e.g., supabase)' }),
cache: z.string().optional().default('true').openapi({ description: 'Use cache (true/false)' })
}),
body: {
content: {
'multipart/form-data': {
schema: z.object({
file: z.any().openapi({
type: 'string',
format: 'binary',
description: 'Image file'
}),
width: z.string().optional(),
height: z.string().optional(),
format: z.string().optional(),
})
}
}
}
},
{
303: {
description: 'Redirect to processed image (local cache)',
},
200: {
description: 'Image processed and uploaded',
content: {
'application/json': {
schema: z.object({
url: z.string(),
width: z.number().optional(),
height: z.number().optional(),
format: z.string().optional(),
size: z.number().optional(),
filename: z.string().optional()
})
}
}
},
500: {
description: 'Server error',
}
},
false // private - requires auth
);
export const getImageRoute = createRouteBody(
'get',
'/api/images/cache/:filename',
['Images'],
'Get cached image',
'Serving processed images from cache map.',
{
params: z.object({
filename: z.string()
})
},
{
200: {
description: 'Image file',
},
404: {
description: 'Not found',
}
},
true // public - serve cached images
);
export const postResponsiveImageRoute = createRouteBody(
'post',
'/api/images/responsive',
['Images'],
'Generate responsive images',
'Upload an image and get a list of responsive versions.',
{
body: {
content: {
'multipart/form-data': {
schema: z.object({
file: z.any().openapi({
type: 'string',
format: 'binary',
description: 'Image file'
}),
sizes: z.string().optional().openapi({ description: 'JSON array of widths, e.g. [640, 1024]' }),
formats: z.string().optional().openapi({ description: 'JSON array of formats, e.g. ["webp", "jpg"]' }),
})
}
}
}
},
{
200: {
description: 'Responsive images generated',
content: {
'application/json': {
schema: z.object({
img: z.object({
src: z.string(),
width: z.number(),
height: z.number(),
format: z.string(),
}),
sources: z.array(z.object({
srcset: z.string(),
type: z.string()
}))
})
}
}
},
303: {
description: 'Redirect to cached image',
},
500: {
description: 'Server error',
}
},
false // private - file upload requires auth
);
export const getResponsiveImageRoute = createRouteBody(
'get',
'/api/images/responsive',
['Images'],
'Generate responsive images (GET)',
'Generate responsive image variants from a remote URL. Cacheable alternative to the POST endpoint.',
{
query: z.object({
url: z.string().openapi({ description: 'Remote URL to generate responsive images from' }),
sizes: z.string().optional().openapi({ description: 'JSON array of widths, e.g. [640, 1024]' }),
formats: z.string().optional().openapi({ description: 'JSON array of formats, e.g. ["webp", "avif"]' }),
})
},
{
200: {
description: 'Responsive images generated',
content: {
'application/json': {
schema: z.object({
img: z.object({
src: z.string(),
width: z.number(),
height: z.number(),
format: z.string(),
}),
sources: z.array(z.object({
srcset: z.string(),
type: z.string()
}))
})
}
}
},
400: {
description: 'Missing URL parameter',
},
500: {
description: 'Server error',
}
},
true // public - cacheable GET endpoint
);
export const renderImageRoute = createRouteBody(
'get',
'/api/images/render',
['Images'],
'Render lazy-optimized image',
'Fetch, resize, and serve an image from a remote URL. Intended for use in srcSet.',
{
query: z.object({
url: z.string().openapi({ description: 'Remote URL to fetch' }),
width: z.string().optional().openapi({ description: 'Target width' }),
height: z.string().optional().openapi({ description: 'Target height' }),
format: z.string().optional().openapi({ description: 'Output format (jpeg, webp, etc.)' })
})
},
{
200: {
description: 'Image content',
content: {
'image/*': {
schema: z.string().openapi({ format: 'binary' })
}
}
},
500: {
description: 'Server error'
}
},
true // public - image rendering/proxy
);
export const postTransformRoute = createRouteBody(
'post',
'/api/images/transform',
['Images'],
'Transform image',
'Apply operations (resize, crop, rotate) to an uploaded image.',
{
body: {
content: {
'multipart/form-data': {
schema: z.object({
file: z.any().openapi({
type: 'string',
format: 'binary',
description: 'Image file'
}),
operations: z.string().openapi({
description: 'JSON array of operations: [{ type: "rotate", angle: 90 }, { type: "resize", width: 100 }, { type: "crop", x: 0, y: 0, width: 100, height: 100 }]'
})
})
}
}
}
},
{
200: {
description: 'Transformed image',
content: {
'image/*': {
schema: z.string().openapi({ format: 'binary' })
}
}
},
500: {
description: 'Server error',
}
},
false // private - requires auth
);