import { Request, Response } from "express"; import fs from "fs-extra"; import JSZip from "jszip"; import path from "path"; import { logger } from "../server.js"; import ListableChecker from "../util/listableChecker.js"; import { PathToRoBillsFolder, PathToRoFolder } from "../util/pathGenerators.js"; import { BillsRelativeFilePath, JobRelativeFilePath } from "../util/serverInit.js"; //param: files: string[] | array of filenames. export async function jobsDownloadMedia(req: Request, res: Response) { const jobid: string = (req.body.jobid || "").trim(); try { const files: string[] = req.body.files || []; const zip: JSZip = new JSZip(); await fs.ensureDir(PathToRoFolder(jobid)); logger.debug(`Generating batch download for Job ID ${jobid}`, files); const jobFileList: fs.Dirent[] = ( await fs.readdir(PathToRoFolder(jobid), { withFileTypes: true }) ).filter((f) => f.isFile() && ListableChecker(f)); const billFileList: fs.Dirent[] = ( await fs.readdir(PathToRoBillsFolder(jobid), { withFileTypes: true }) ).filter((f) => f.isFile() && ListableChecker(f)); // Helper to add files to the zip const addFilesToZip = async ( fileList: fs.Dirent[], relativePathFn: (jobid: string, filename: string) => string ) => { await Promise.all( fileList.map(async (file) => { const baseName = path.basename(file.name); if (files.length === 0 || files.includes(baseName)) { try { const fileOnDisk: Buffer = await fs.readFile(relativePathFn(jobid, file.name)); zip.file(baseName, fileOnDisk); } catch (err) { logger.warning(`Could not add file to zip: ${file.name}`, err); } } }) ); }; await addFilesToZip(jobFileList, JobRelativeFilePath); await addFilesToZip(billFileList, BillsRelativeFilePath); // Set headers for download res.setHeader("Content-Disposition", `attachment; filename="${jobid}.zip"`); res.setHeader("Content-Type", "application/zip"); zip .generateNodeStream({ type: "nodebuffer", streamFiles: true }) .pipe(res) .on("finish", () => { logger.debug(`Zip stream finished for Job ID ${jobid}`); }); } catch (error) { logger.error("Error downloading job media.", { jobid, error: (error as Error).message }); if (!res.headersSent) res.status(500).json((error as Error).message); } }