import express from "express"; import { createPoster } from "./common.js"; import Joi from 'joi'; // Define default parameters for the legacy /poster endpoint const DEFAULT_WIDTH = 480; const DEFAULT_HEIGHT = 320; const DEFAULT_COUNT = 4; const DEFAULT_ORIENTATION = "horizontal"; const DEFAULT_SHUFFLE = true; const DEFAULT_BACKGROUND_COLOR = "#000000"; // Consistent 6-digit hex const DEFAULT_FORMAT = 'png'; // Default format consistent with POST / const DEFAULT_LANGUAGE = 'de-DE'; // Default language consistent with POST / const DEFAULT_OUTPUT = 'image'; // Default output consistent with POST / const router = express.Router(); // --- Validation Schema for POST / --- // Defines the expected structure and constraints for the request body. const posterRequestSchema = Joi.object({ // Dimensions and Count width: Joi.number().integer().min(1).max(4000).default(DEFAULT_WIDTH) .messages({'number.base': "'width' must be a number", 'number.min': "'width' must be at least 1", 'number.max': "'width' cannot exceed 4000"}), height: Joi.number().integer().min(1).max(4000).default(DEFAULT_HEIGHT) .messages({'number.base': "'height' must be a number", 'number.min': "'height' must be at least 1", 'number.max': "'height' cannot exceed 4000"}), count: Joi.number().integer().min(1).max(20).default(DEFAULT_COUNT) .messages({'number.base': "'count' must be a number", 'number.min': "'count' must be at least 1", 'number.max': "'count' cannot exceed 20"}), // Layout and Content orientation: Joi.string().required().valid('horizontal', 'vertical').default(DEFAULT_ORIENTATION) .messages({'any.required': "'orientation' is required", 'any.only': "'orientation' must be either 'horizontal' or 'vertical'"}), shuffle: Joi.boolean().default(DEFAULT_SHUFFLE) .messages({'boolean.base': "'shuffle' must be a boolean"}), language: Joi.string().trim().default(DEFAULT_LANGUAGE) // Added trim() .messages({'string.base': "'language' must be a string"}), // Styling and Output backgroundColor: Joi.string().trim() // Added trim() .default(DEFAULT_BACKGROUND_COLOR) // Use 6-digit hex for consistency .regex(/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$/) // Allow 3 or 6 hex digits .message("The 'backgroundColor' must be a valid hexadecimal color code (e.g., #000 or #FF0000)"), format: Joi.string().valid('png', 'jpeg').default(DEFAULT_FORMAT) .messages({'any.only': "'format' must be either 'png' or 'jpeg'"}), output: Joi.string().valid('image', 'lvgl', 'lvgl_binary').default(DEFAULT_OUTPUT) .messages({'any.only': "'output' must be either 'image', 'lvgl' or 'lvgl_binary'"}), }); // --- Validation Middleware --- // Validates the request body against the posterRequestSchema. const validatePosterRequest = (req, res, next) => { const { error, value } = posterRequestSchema.validate(req.body, { abortEarly: false, // Report all errors, not just the first one stripUnknown: true // Remove unknown keys from the validated value }); if (error) { // Log the detailed validation error for debugging console.warn('Validation failed:', error.details); // Construct a user-friendly error message const errorMessages = error.details.map(detail => detail.message).join('. '); return res.status(400).send({ error: `Invalid request body: ${errorMessages}` }); } // Replace req.body with the validated, defaulted, and potentially stripped value. // This is a common pattern in Express middleware. req.body = value; next(); // Proceed to the main route handler }; // --- Main POST Endpoint --- // Handles requests to generate the poster image or LVGL data. router.post("/", validatePosterRequest, async (req, res) => { // Destructure the validated and defaulted body for clarity const { width, height, count, orientation, shuffle, backgroundColor, format, language, output } = req.body; try { // Delegate the core poster creation logic to the common function. // createPoster handles fetching data, drawing, and sending the response. await createPoster( req, // Pass req for API key access within createPoster res, width, height, count, orientation, shuffle, backgroundColor, format, language, output ); } catch (error) { // This catch block acts as a final safety net. // Although createPoster has internal error handling, this catches // potential unexpected issues during its execution. console.error("Unhandled error during POST / handler:", error); // Ensure response is sent only once if (!res.headersSent) { res.status(500).send({ error: "An unexpected error occurred while processing your request." }); } } }); /** * @deprecated Use the POST /poster endpoint instead for more flexibility. * GET /poster: Generates a movie poster collage using predefined default settings. * This endpoint is maintained for backward compatibility. */ router.get("/", async (req, res) => { // Call the common poster creation function with hardcoded legacy defaults. // Note: This endpoint does not support customization via query parameters. try { await createPoster( req, // Pass req for API key access within createPoster res, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COUNT, DEFAULT_ORIENTATION, DEFAULT_SHUFFLE, DEFAULT_BACKGROUND_COLOR, DEFAULT_FORMAT, DEFAULT_LANGUAGE, DEFAULT_OUTPUT ); // createPoster handles sending the response on success. } catch (error) { // Although createPoster has internal error handling and sends responses, // catch potential unexpected errors during the await or within createPoster // if they somehow bypass its internal handling. console.error("Error in legacy /cinema endpoint:", error); // Ensure a response is sent if headers haven't been sent yet. if (!res.headersSent) { res.status(500).send({ error: "Failed to generate cinema poster due to an internal error." }); } } }); // Export the router under the 'cinema' namespace export const poster = { router, // Use shorthand property name };