You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
63 lines
1.7 KiB
63 lines
1.7 KiB
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<BuildExportFilesResult> {
|
|
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,
|
|
};
|
|
}
|
|
|