Files
lobe-sandbox-backend/orchestrator/src/export.ts

84 lines
2.4 KiB
TypeScript

import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { env } from './env.ts';
import { incus, containerName } from './incus.ts';
import { state } from './state.ts';
const s3 = new S3Client({
endpoint: env.s3.endpoint,
region: env.s3.region,
forcePathStyle: env.s3.forcePathStyle,
credentials: {
accessKeyId: env.s3.accessKeyId,
secretAccessKey: env.s3.secretAccessKey,
},
});
export interface ExportResult {
success: boolean;
key?: string;
size?: number;
mimeType?: string;
filename?: string;
error?: string;
}
export const exportFile = async (
userId: string,
path: string,
filename: string,
): Promise<ExportResult> => {
const name = containerName(userId);
const s = await incus.state(name);
if (s === 'MISSING') return { success: false, error: `no sandbox for ${userId}` };
if (s === 'STOPPED') await incus.start(name);
state.touch(userId);
try {
const bytes = await incus.pull(name, path);
const today = new Date().toISOString().split('T')[0];
const key = `exports/${today}/${userId}/${filename}`;
const mime = guessMime(filename);
await s3.send(
new PutObjectCommand({
Bucket: env.s3.bucket,
Key: key,
Body: bytes,
ContentType: mime,
}),
);
return { success: true, key, size: bytes.byteLength, mimeType: mime, filename };
} catch (e) {
return { success: false, error: (e as Error).message };
}
};
const MIME: Record<string, string> = {
'.txt': 'text/plain',
'.md': 'text/markdown',
'.json': 'application/json',
'.csv': 'text/csv',
'.tsv': 'text/tab-separated-values',
'.pdf': 'application/pdf',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.svg': 'image/svg+xml',
'.html': 'text/html',
'.xml': 'application/xml',
'.zip': 'application/zip',
'.gz': 'application/gzip',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
};
const guessMime = (filename: string): string => {
const dot = filename.lastIndexOf('.');
if (dot === -1) return 'application/octet-stream';
return MIME[filename.slice(dot).toLowerCase()] ?? 'application/octet-stream';
};