284 lines
8.8 KiB
TypeScript
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
|
|
);
|