feat: lokale pin opslag zonder Kubo

- JSON-file opslag in .data/pins.json
- Gebruikt wanneer KUBO_API_URL niet beschikbaar is
- Lokale pins voor development, Kubo voor productie
- 191 tests passing
This commit is contained in:
maikrolf
2026-09-19 17:04:44 +02:00
parent 361f11033d
commit c64ca2b8c2
2 changed files with 114 additions and 0 deletions
+51
View File
@@ -1,7 +1,20 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { kuboApiUrl, kuboBasicAuth } from '@/lib/config'; import { kuboApiUrl, kuboBasicAuth } from '@/lib/config';
import { localListPins, localAddPin, localRemovePin } from '@/lib/local-pins';
export async function proxyIPFS(path: string, method: string, req: NextRequest): Promise<Response> { export async function proxyIPFS(path: string, method: string, req: NextRequest): Promise<Response> {
// Normalize path
const p = path.replace(/^\/+/, '').replace(/\/+$/, '');
// ── Lokale pin opslag (zonder Kubo) ──
// Wanneer KUBO_API_URL niet beschikbaar is, gebruik lokale JSON file
if (p === 'pins' || p.startsWith('pins/')) {
const kuboSvc = kuboApiUrl();
if (!kuboSvc) {
return handleLocalPins(p, method, req);
}
}
// In dev, the Kubo API is behind nginx with Basic Auth. // In dev, the Kubo API is behind nginx with Basic Auth.
// If no KUBO_API_URL is configured, return a clear error. // If no KUBO_API_URL is configured, return a clear error.
const kuboSvc = kuboApiUrl(); const kuboSvc = kuboApiUrl();
@@ -75,6 +88,44 @@ export async function proxyIPFS(path: string, method: string, req: NextRequest):
}); });
} }
/** Lokale pin handler — werkt zonder Kubo API */
async function handleLocalPins(path: string, method: string, req: NextRequest): Promise<Response> {
const p = path.replace(/^\/+/, '').replace(/\/+$/, '');
// GET /api/pins — lijst alle pins
if (p === 'pins' && method === 'GET') {
const pins = await localListPins();
// Retourneer in Kubo-formaat voor backwards compatibiliteit
const Keys: Record<string, { Type: string }> = {};
for (const pin of pins) {
Keys[pin.cid] = { Type: pin.created || 'indirect' };
}
return NextResponse.json({ Keys });
}
// POST /api/pins — pin een CID
if (p === 'pins' && method === 'POST') {
try {
const body = await req.json();
const { cid, name, size } = body;
if (!cid) return NextResponse.json({ error: 'cid required' }, { status: 400 });
await localAddPin(cid, name, size || 0);
return NextResponse.json({ cid });
} catch {
return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
}
}
// DELETE /api/pins/<cid> — verwijder pin
if (p.startsWith('pins/') && method === 'DELETE') {
const cid = p.slice(5);
await localRemovePin(cid);
return NextResponse.json({ ok: true });
}
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
export function mapToKuboAPI(path: string, method: string): string { export function mapToKuboAPI(path: string, method: string): string {
// Normalize: /api/node/info → /api/v0/id, etc. // Normalize: /api/node/info → /api/v0/id, etc.
const p = path.replace(/^\/+/, '').replace(/\/+$/, ''); const p = path.replace(/^\/+/, '').replace(/\/+$/, '');
+63
View File
@@ -0,0 +1,63 @@
/**
* Local pin storage — JSON file based
* Gebruikt wanneer KUBO_API_URL niet beschikbaar is (development/local).
*/
import { readFile, writeFile, mkdir } from 'fs/promises';
import { join } from 'path';
const PIN_FILE = join(process.cwd(), '.data', 'pins.json');
interface PinRecord {
cid: string;
name: string;
size: number;
created: string;
}
async function ensureDir() {
const dir = join(process.cwd(), '.data');
await mkdir(dir, { recursive: true });
}
async function readPins(): Promise<Record<string, PinRecord>> {
try {
const data = await readFile(PIN_FILE, 'utf-8');
return JSON.parse(data);
} catch {
return {};
}
}
async function writePins(pins: Record<string, PinRecord>): Promise<void> {
await ensureDir();
await writeFile(PIN_FILE, JSON.stringify(pins, null, 2), 'utf-8');
}
export async function localListPins(): Promise<PinRecord[]> {
const pins = await readPins();
return Object.values(pins);
}
export async function localAddPin(cid: string, name?: string, size: number = 0): Promise<{ cid: string }> {
const pins = await readPins();
pins[cid] = {
cid,
name: name || '',
size,
created: new Date().toISOString(),
};
await writePins(pins);
return { cid };
}
export async function localRemovePin(cid: string): Promise<void> {
const pins = await readPins();
delete pins[cid];
await writePins(pins);
}
export async function localGetPinCount(): Promise<number> {
const pins = await readPins();
return Object.keys(pins).length;
}