84 lines
2.4 KiB
TypeScript
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';
|
|
};
|