Files
IPFS-portal/src/lib/local-files.ts
T
maikrolf 841f89b1d5 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
2026-09-19 19:58:28 +02:00

122 lines
3.1 KiB
TypeScript

/**
* 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 };
}