files.ts
1 import { randomUUID } from 'node:crypto' 2 import { readFile, writeFile } from 'node:fs/promises' 3 import path from 'node:path' 4 5 import type { UploadAsset, UploadKind } from '@/lib/shared/chat' 6 import { getStoragePaths } from '@/server/storage/db' 7 import { saveUploadRecord } from '@/server/storage/chat-store' 8 9 export async function saveUploadFile(input: { 10 sessionId: string 11 file: File 12 kind: UploadKind 13 }): Promise<UploadAsset> { 14 const safeName = sanitizeFileName(input.file.name || `${input.kind}-${randomUUID()}`) 15 const ext = path.extname(safeName) 16 const base = path.basename(safeName, ext) 17 const diskFileName = `${Date.now()}-${randomUUID()}-${base}${ext}` 18 const { uploadsDir } = getStoragePaths() 19 const diskPath = path.join(uploadsDir, diskFileName) 20 21 const bytes = new Uint8Array(await input.file.arrayBuffer()) 22 await writeFile(diskPath, bytes) 23 24 return saveUploadRecord({ 25 sessionId: input.sessionId, 26 kind: input.kind, 27 fileName: safeName, 28 mimeType: input.file.type || guessMimeType(input.kind), 29 size: input.file.size, 30 diskPath, 31 }) 32 } 33 34 export async function readUploadFile(diskPath: string): Promise<Uint8Array> { 35 return readFile(diskPath) 36 } 37 38 function sanitizeFileName(value: string): string { 39 const name = value.replace(/[^a-zA-Z0-9._-]/g, '_').replace(/^_+/, '') 40 return name.slice(0, 160) || 'upload' 41 } 42 43 function guessMimeType(kind: UploadKind): string { 44 return kind === 'audio' ? 'audio/webm' : 'application/octet-stream' 45 }