feat: lokale file opslag zonder Kubo
- Bestanden uploaden naar .data/files/ - Auto-pin na upload - Lijst, download en verwijder bestanden - CID hash generatie via SHA-256 - 191 tests passing
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Local file storage — disk-based
|
||||
* Gebruikt wanneer geen Kubo API beschikbaar is.
|
||||
* Slaat bestanden op in .data/files/ en houdt metadata bij in .data/files.json
|
||||
*/
|
||||
|
||||
import { readFile, writeFile, mkdir, unlink, readdir, stat } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
const DATA_DIR = join(process.cwd(), '.data', 'files');
|
||||
const FILES_DB = join(process.cwd(), '.data', 'files.json');
|
||||
|
||||
interface FileRecord {
|
||||
cid: string;
|
||||
name: string;
|
||||
size: number;
|
||||
modified: string;
|
||||
path: string; // relatief pad in .data/files/
|
||||
}
|
||||
|
||||
async function ensureDir() {
|
||||
await mkdir(DATA_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
async function readDb(): Promise<Record<string, FileRecord>> {
|
||||
try {
|
||||
const data = await readFile(FILES_DB, 'utf-8');
|
||||
return JSON.parse(data);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function writeDb(db: Record<string, FileRecord>): Promise<void> {
|
||||
await ensureDir();
|
||||
await writeFile(FILES_DB, JSON.stringify(db, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/** Genereer een CID-achtige hash van de bestandsinhoud */
|
||||
function generateCid(buffer: Buffer): string {
|
||||
const hash = createHash('sha256').update(buffer).digest('hex');
|
||||
// IPFS-style base32 hash (Qm prefix + base32 hash)
|
||||
return 'Qm' + hash.slice(0, 44);
|
||||
}
|
||||
|
||||
/** Sla een bestand op en retourneer CID + metadata */
|
||||
export async function localSaveFile(
|
||||
name: string,
|
||||
buffer: Buffer,
|
||||
size: number
|
||||
): Promise<{ cid: string; size: number }> {
|
||||
await ensureDir();
|
||||
|
||||
const cid = generateCid(buffer);
|
||||
const filePath = join(DATA_DIR, `${cid}-${name}`);
|
||||
|
||||
// Sla het bestand op
|
||||
await writeFile(filePath, buffer);
|
||||
|
||||
// Update de database
|
||||
const db = await readDb();
|
||||
db[cid] = {
|
||||
cid,
|
||||
name,
|
||||
size,
|
||||
modified: new Date().toISOString(),
|
||||
path: `${cid}-${name}`,
|
||||
};
|
||||
await writeDb(db);
|
||||
|
||||
return { cid, size };
|
||||
}
|
||||
|
||||
/** Haal een bestand op */
|
||||
export async function localGetFile(cid: string): Promise<{ buffer: Buffer; name: string } | null> {
|
||||
const db = await readDb();
|
||||
const record = db[cid];
|
||||
if (!record) return null;
|
||||
|
||||
const filePath = join(DATA_DIR, record.path);
|
||||
try {
|
||||
const buffer = await readFile(filePath);
|
||||
return { buffer, name: record.name };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Lijst alle bestanden */
|
||||
export async function localListFiles(): Promise<FileRecord[]> {
|
||||
const db = await readDb();
|
||||
return Object.values(db);
|
||||
}
|
||||
|
||||
/** Verwijder een bestand */
|
||||
export async function localDeleteFile(cid: string): Promise<void> {
|
||||
const db = await readDb();
|
||||
const record = db[cid];
|
||||
if (!record) return;
|
||||
|
||||
// Verwijder het bestand van disk
|
||||
const filePath = join(DATA_DIR, record.path);
|
||||
try {
|
||||
await unlink(filePath);
|
||||
} catch {
|
||||
// Bestand bestaat niet meer — niet erg
|
||||
}
|
||||
|
||||
// Verwijder uit de database
|
||||
delete db[cid];
|
||||
await writeDb(db);
|
||||
}
|
||||
|
||||
/** Bereken totale opslag */
|
||||
export async function localGetStorageUsage(): Promise<{ files: number; totalBytes: number }> {
|
||||
const db = await readDb();
|
||||
const files = Object.values(db);
|
||||
const totalBytes = files.reduce((sum, f) => sum + f.size, 0);
|
||||
return { files: files.length, totalBytes };
|
||||
}
|
||||
Reference in New Issue
Block a user