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 => { 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 = { '.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'; };