Files
plane/apps/live/src/controllers/document.controller.ts
Manish Gupta 5a0286b76f style(security): drop advisory identifiers and shorten comments
Advisory IDs map a published advisory to the exact fix location and the repo
is public. Keep the invariant and the non-obvious failure mode; drop the
narration, restated code and stale line references.

Co-authored-by: Plane AI <noreply@plane.so>
2026-08-27 10:39:32 +05:30

78 lines
2.6 KiB
TypeScript

/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { Request, Response } from "express";
import { z } from "zod";
// helpers
import { Controller, Middleware, Post } from "@plane/decorators";
import { convertHTMLDocumentToAllFormats } from "@plane/editor";
// logger
import { logger } from "@plane/logger";
import { requireSecretKey } from "@/lib/auth-middleware";
import type { TConvertDocumentRequestBody } from "@/types";
// Define the schema with more robust validation
const convertDocumentSchema = z.object({
description_html: z
.string()
.min(1, "HTML content cannot be empty")
.refine((html) => html.trim().length > 0, "HTML content cannot be just whitespace")
.refine((html) => html.includes("<") && html.includes(">"), "Content must be valid HTML"),
variant: z.enum(["rich", "document"]),
});
@Controller("/convert-document")
export class DocumentController {
/**
* Server-to-server only: the sole caller is the API's `copy_s3_object` background
* task. It was previously reachable unauthenticated by anyone who could hit the
* Live service, making an expensive HTML -> Y.js conversion free compute for the
* internet. Callers must now present `live-server-secret-key`.
*/
@Post("/")
@Middleware(requireSecretKey)
async convertDocument(req: Request, res: Response) {
try {
// Validate request body
const validatedData = convertDocumentSchema.parse(req.body as TConvertDocumentRequestBody);
const { description_html, variant } = validatedData;
// Process document conversion
const { description_json, description_binary } = convertHTMLDocumentToAllFormats({
document_html: description_html,
variant,
});
// Return successful response
res.status(200).json({
description_json,
description_binary,
});
} catch (error) {
if (error instanceof z.ZodError) {
const validationErrors = error.errors.map((err) => ({
path: err.path.join("."),
message: err.message,
}));
logger.error("DOCUMENT_CONTROLLER: Validation error", {
validationErrors,
});
return res.status(400).json({
message: `Validation error`,
context: {
validationErrors,
},
});
} else {
logger.error("DOCUMENT_CONTROLLER: Internal server error", error);
return res.status(500).json({
message: `Internal server error.`,
});
}
}
}
}