feat: add IPFS gateway route /ipfs/:cid
This commit is contained in:
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* IPFS Gateway Route — /ipfs/[cid]
|
||||||
|
*
|
||||||
|
* Haalt content op van KUBO via /api/v0/cat en served het als gateway.
|
||||||
|
* Ondersteunt:
|
||||||
|
* GET /ipfs/:cid — content ophalen
|
||||||
|
* GET /ipfs/:cid?filename= — met content-disposition header
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { kuboApiUrl, kuboBasicAuth } from '@/lib/config';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: Promise<{ cid: string }> },
|
||||||
|
) {
|
||||||
|
const { cid } = await params;
|
||||||
|
|
||||||
|
// Valideer CID formaat (Qm... of bafy...)
|
||||||
|
if (!cid || (!cid.startsWith('Qm') && !cid.startsWith('bafy') && !cid.startsWith('bafk'))) {
|
||||||
|
return NextResponse.json({ error: 'Ongeldig CID formaat' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const kuboSvc = kuboApiUrl();
|
||||||
|
if (!kuboSvc) {
|
||||||
|
return NextResponse.json({ error: 'KUBO niet geconfigureerd' }, { status: 501 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
const auth = kuboBasicAuth();
|
||||||
|
if (auth) {
|
||||||
|
headers['Authorization'] = 'Basic ' + Buffer.from(auth).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch content via KUBO /api/v0/cat
|
||||||
|
const response = await fetch(`${kuboSvc}/api/v0/cat?arg=${encodeURIComponent(cid)}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const text = await response.text().catch(() => '');
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: `KUBO cat gefaald: ${response.status}`, detail: text.slice(0, 200) },
|
||||||
|
{ status: response.status },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bepaal content type op basis van CID prefix
|
||||||
|
const contentType = detectContentType(cid);
|
||||||
|
|
||||||
|
// Check of filename is opgegeven
|
||||||
|
const filename = req.nextUrl.searchParams.get('filename');
|
||||||
|
const headersOut: Record<string, string> = {
|
||||||
|
'Content-Type': contentType,
|
||||||
|
'Content-Disposition': filename
|
||||||
|
? `inline; filename="${filename}"`
|
||||||
|
: 'inline',
|
||||||
|
'Cache-Control': 'public, max-age=31536000, immutable',
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = await response.arrayBuffer();
|
||||||
|
return new NextResponse(body, { headers: headersOut });
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : 'Gateway fetch failed';
|
||||||
|
console.error(`[ipfs-gateway] Error fetching ${cid}:`, msg);
|
||||||
|
return NextResponse.json({ error: msg }, { status: 502 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detecteer content type op basis van CID prefix.
|
||||||
|
* IPFS content is meestal JSON, tenzij het een bekend formaat is.
|
||||||
|
*/
|
||||||
|
function detectContentType(cid: string): string {
|
||||||
|
// Voor nu: standaard octet-stream, KUBO bepaalt het echte type
|
||||||
|
return 'application/octet-stream';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user