better-chatbot / src /lib /file-storage /local-file-storage.ts
Bot
feat: implement zero-config local filesystem storage driver for out-of-the-box file uploads on Hugging Face Spaces
a375c71
Raw
History Blame Contribute Delete
2.87 kB
import { FileStorage, UploadContent, UploadOptions, UploadResult, FileMetadata } from "./file-storage.interface";
import fs from "fs/promises";
import path from "path";
import { BASE_URL } from "lib/const";
const UPLOADS_DIR = "/tmp/uploads";
export class LocalFileStorage implements FileStorage {
constructor() {
fs.mkdir(UPLOADS_DIR, { recursive: true }).catch(() => {});
}
async upload(content: UploadContent, options?: UploadOptions): Promise<UploadResult> {
const filename = options?.filename || "file";
const contentType = options?.contentType || "application/octet-stream";
const key = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}-${filename}`;
const filePath = path.join(UPLOADS_DIR, key);
let buffer: Buffer;
if (Buffer.isBuffer(content)) {
buffer = content;
} else if (content instanceof ArrayBuffer) {
buffer = Buffer.from(content);
} else if (ArrayBuffer.isView(content)) {
buffer = Buffer.from(content.buffer, content.byteOffset, content.byteLength);
} else if (content instanceof Blob) {
buffer = Buffer.from(await content.arrayBuffer());
} else if (typeof (content as any).read === "function" || typeof (content as any).on === "function") {
const chunks: any[] = [];
for await (const chunk of content as any) {
chunks.push(chunk);
}
buffer = Buffer.concat(chunks);
} else {
throw new Error("Unsupported upload content type");
}
await fs.writeFile(filePath, buffer);
const metadata: FileMetadata = {
key,
filename,
contentType,
size: buffer.length,
uploadedAt: new Date(),
};
return {
key,
sourceUrl: `${BASE_URL}/api/storage/file/${key}`,
metadata,
};
}
async download(key: string): Promise<Buffer> {
const filePath = path.join(UPLOADS_DIR, key);
return fs.readFile(filePath);
}
async delete(key: string): Promise<void> {
const filePath = path.join(UPLOADS_DIR, key);
await fs.unlink(filePath).catch(() => {});
}
async exists(key: string): Promise<boolean> {
const filePath = path.join(UPLOADS_DIR, key);
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async getMetadata(key: string): Promise<FileMetadata | null> {
const filePath = path.join(UPLOADS_DIR, key);
try {
const stat = await fs.stat(filePath);
return {
key,
filename: key.split("-").slice(2).join("-") || "file",
contentType: "application/octet-stream",
size: stat.size,
uploadedAt: stat.mtime,
};
} catch {
return null;
}
}
async getSourceUrl(key: string): Promise<string | null> {
return `${BASE_URL}/api/storage/file/${key}`;
}
}
export const createLocalFileStorage = () => new LocalFileStorage();