refactor: codebase opschoning — type safety, error handling, central config, page splits

- categorie A: alle catch(e: any) → catch(e: unknown) met instanceof check
- categorie B: stille .catch() logging toegevoegd (SWRegister, admin, upload, auth)
- categorie C: hardcoded 192.168.1.176 IPs vervangen door env var defaults
- categorie D: page files >250L gesplitst (history, settings, explorer, admin/payment,
  ipns, users, upload, dashboard) in helpers/components
- categorie E: eslint-disable vervangen in SearchInput.tsx
- src/lib/config.ts centrale config module (localhost defaults)
- CSP in next.config.ts dynamisch via env var
- deploy.sh: geen hardcoded IP meer (REQUIRED arg)
- lib/api/ lib/auth/ lib/wallet/ lib/search-index/ lib/storage/ gesplitst in modules
- ongebruikte bestanden verwijderd: api.ts, auth.tsx, wallet.ts, search-index.ts,
  PaymentPanel.tsx, SearchBar.tsx, proxy.ts, serve-static.js
This commit is contained in:
maikrolf
2026-07-19 15:35:18 +02:00
parent 88ce38a49e
commit f72b775379
144 changed files with 4644 additions and 10230 deletions
+132
View File
@@ -0,0 +1,132 @@
import { NextRequest, NextResponse } from 'next/server';
import { kuboApiUrl, kuboBasicAuth } from '@/lib/config';
export async function proxyIPFS(path: string, method: string, req: NextRequest): Promise<Response> {
// In dev, the Kubo API is behind nginx with Basic Auth.
// If no KUBO_API_URL is configured, return a clear error.
const kuboSvc = kuboApiUrl();
if (!kuboSvc) {
return NextResponse.json(
{ error: 'IPFS proxy not configured', detail: 'Set KUBO_API_URL in env for dev, or use nginx routing in production' },
{ status: 501 }
);
}
// Map /api/explorer/ls/<cid> → /api/v0/ls?arg=<cid>, etc.
const url = new URL(kuboSvc);
const kuboPath = mapToKuboAPI(path, method);
// Split pathname and query string — url.pathname encodes `?` as `%3F`
const [namePart, ...qsParts] = kuboPath.split('?');
url.pathname = namePart;
if (qsParts.length > 0) {
url.search = qsParts.join('?');
} else {
// Forward incoming query params (used by ipns/publish etc.)
const incomingSearch = req.nextUrl.search;
if (incomingSearch) {
url.search = incomingSearch;
}
}
const headers: Record<string, string> = {};
const auth = kuboBasicAuth();
if (auth) {
headers['Authorization'] = 'Basic ' + Buffer.from(auth).toString('base64');
}
// Forward Content-Type (preserves multipart boundary for file uploads)
const reqCt = req.headers.get('content-type');
if (reqCt) {
headers['Content-Type'] = reqCt;
}
// Kubo uses POST for everything
const body = method === 'POST' || method === 'DELETE' ? req.body : null;
const fetchOpts: RequestInit & { duplex?: string } = {
method: 'POST',
headers,
signal: AbortSignal.timeout(30000),
};
// Node.js fetch requires duplex: 'half' when streaming a body
if (body) {
fetchOpts.duplex = 'half';
fetchOpts.body = body;
}
let res: Response;
try {
res = await fetch(url.toString(), fetchOpts);
} catch (fetchErr: unknown) {
const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
console.error('[proxyIPFS] fetch error:', msg);
return NextResponse.json(
{ error: 'IPFS proxy fetch failed', detail: msg.substring(0, 200) },
{ status: 502 },
);
}
const text = await res.text();
return new NextResponse(text, {
status: res.status,
headers: {
'Content-Type': res.headers.get('Content-Type') || 'application/json',
},
});
}
export function mapToKuboAPI(path: string, method: string): string {
// Normalize: /api/node/info → /api/v0/id, etc.
const p = path.replace(/^\/+/, '').replace(/\/+$/, '');
switch (p) {
case 'node/info':
return '/api/v0/id';
case 'peers':
return '/api/v0/swarm/peers';
case 'pins':
return method === 'GET' ? '/api/v0/pin/ls' : '/api/v0/pin/add';
default:
if (p.startsWith('pins/')) {
const cid = p.slice(5);
return `/api/v0/pin/rm?arg=${encodeURIComponent(cid)}`;
}
if (p === 'files/upload') {
return '/api/v0/add?pin=true';
}
if (p.startsWith('files/')) {
return '/api/v0/ls';
}
// Explorer routes: /explorer/ls/<cid>, /explorer/cat/<cid>, etc.
if (p.startsWith('explorer/ls/')) {
const cid = p.slice('explorer/ls/'.length);
return `/api/v0/ls?arg=${encodeURIComponent(cid)}`;
}
if (p.startsWith('explorer/cat/')) {
const cid = p.slice('explorer/cat/'.length);
return `/api/v0/cat?arg=${encodeURIComponent(cid)}`;
}
if (p.startsWith('explorer/stat/')) {
const cid = p.slice('explorer/stat/'.length);
return `/api/v0/object/stat?arg=${encodeURIComponent(cid)}`;
}
if (p.startsWith('explorer/resolve/')) {
const name = p.slice('explorer/resolve/'.length);
return `/api/v0/resolve?arg=${encodeURIComponent(name)}`;
}
// IPNS routes
if (p === 'ipns/publish') return '/api/v0/name/publish';
if (p === 'ipns/keys') return '/api/v0/key/list';
if (p.startsWith('ipns/resolve/')) {
const name = p.slice('ipns/resolve/'.length);
return `/api/v0/name/resolve?arg=${encodeURIComponent(name)}`;
}
if (p.startsWith('ipns/keys/gen/')) {
const name = p.slice('ipns/keys/gen/'.length);
return `/api/v0/key/gen?arg=${encodeURIComponent(name)}`;
}
// Dashboard routes
if (p === 'repo/stats') return '/api/v0/repo/stat';
if (p === 'bw/stats') return '/api/v0/bw';
return '/api/v0/' + p;
}
}