import fs from "node:fs/promises"; import path from "node:path"; import { RELATIVE_ASSETS_DIR } from "../../constants/media"; import { listExportUserMediaAssets } from "./build-data"; type ExportFileAsset = { storageKey: string; }; export type BuildExportFilesResult = { files: Array<{ file: string; bytes: number }>; missingCount: number; }; function resolveAssetsBaseDir(): string { return path.resolve(process.cwd(), RELATIVE_ASSETS_DIR); } export async function buildExportMediaFiles(params: { userId: number; cutoffAt: Date; outputFilesDir: string; }): Promise { await fs.mkdir(params.outputFilesDir, { recursive: true }); const mediaAssets: ExportFileAsset[] = await listExportUserMediaAssets({ userId: params.userId, cutoffAt: params.cutoffAt, }); const assetsBaseDir = resolveAssetsBaseDir(); let missingCount = 0; const files: Array<{ file: string; bytes: number }> = []; for (const asset of mediaAssets) { const key = asset.storageKey; if (!key || key.includes("..") || path.isAbsolute(key)) { missingCount += 1; continue; } const fromPath = path.resolve(assetsBaseDir, key); const toPath = path.resolve(params.outputFilesDir, key); const relativeOutputPath = `files/${key}`; await fs.mkdir(path.dirname(toPath), { recursive: true }); try { await fs.copyFile(fromPath, toPath); const stat = await fs.stat(toPath); files.push({ file: relativeOutputPath, bytes: stat.size }); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") { missingCount += 1; continue; } throw error; } } return { files, missingCount, }; }