[clsi] initial version of /convert/pdf-to-jpeg (#33752)

* [monorepo] consolidate clsi-lb host/ip env-vars

Target env-var is CLSI_LB_HOST. Keep CLSI_LB_IP populated for a week.

* [clsi] initial version of /convert/pdf-to-jpeg

* [rails] use fake-secrets in CI and Codespaces

* [rails] adapt tests for using clsi to convert PDFs to image

* [rails] add rake task for comparing clsi conversion with transloadit

* [clsi] double check that output.jpg is a regular file

Co-authored-by: Brian Gough <brian.gough@overleaf.com>

* [clsi] fix composing basename

* [monorepo] fix clsi-lb host env-var post merge

* [monorepo] sort dev-environment.env hosts

* [rails] use local pdf file rather than downloading it again

Download from the old renderer code path still. It's dead code.

* [terraform] clsi: enable pdf to jpg conversion

---------

Co-authored-by: Brian Gough <brian.gough@overleaf.com>
GitOrigin-RevId: 5ecaa8559d299486340bb3961f06b29f7c4dfcca
This commit is contained in:
Jakob Ackermann
2026-06-05 08:05:55 +00:00
committed by Copybot
co-authored by Brian Gough
parent 9ec0ff065d
commit df61bfc788
14 changed files with 242 additions and 5 deletions
@@ -14,6 +14,7 @@ import RequestParser from './RequestParser.js'
import { pipeline } from 'node:stream/promises'
import Settings from '@overleaf/settings'
import Path from 'node:path'
import { z } from '@overleaf/validation-tools'
const CONVERSION_CONFIGS = {
docx: { extension: 'docx' },
@@ -77,6 +78,51 @@ async function convertDocumentToLaTeX(req, res) {
}
}
const PDFToJPEGQuerySchema = z.object({
mode: z.enum(['preview', 'thumbnail']),
})
async function convertPDFToJPEG(req, res) {
const { path } = req.file
if (!Settings.enablePdfConversions) {
await fs.unlink(path).catch(() => {})
return res.sendStatus(404)
}
const parsed = PDFToJPEGQuerySchema.safeParse(req.query)
if (!parsed.success) {
await fs.unlink(path).catch(() => {})
return res.sendStatus(400)
}
const { mode } = parsed.data
logger.debug({ path, mode }, 'received pdf for conversion to jpeg')
const conversionId = crypto.randomUUID()
let jpegPath
try {
jpegPath = await ConversionManager.promises.convertPDFToJPEGWithLock(
conversionId,
path,
mode
)
} finally {
await fs.unlink(path).catch(() => {})
}
try {
const jpegStat = await fs.stat(jpegPath)
res.setHeader('Content-Length', jpegStat.size)
res.attachment('output.jpg')
res.setHeader('X-Content-Type-Options', 'nosniff')
const readStream = fsSync.createReadStream(jpegPath)
await pipeline(readStream, res)
} finally {
await fs
.rm(Path.dirname(jpegPath), { recursive: true, force: true })
.catch(() => {})
}
}
async function convertProjectToDocument(req, res) {
if (!Settings.enablePandocConversions) {
return res.sendStatus(404)
@@ -207,4 +253,5 @@ async function convertProjectToDocument(req, res) {
export default {
convertDocumentToLaTeX: expressify(convertDocumentToLaTeX),
convertProjectToDocument: expressify(convertProjectToDocument),
convertPDFToJPEG: expressify(convertPDFToJPEG),
}