- Add HEAD method handler in Next.js route - Fix proxy URL to use correct city-service endpoint - Handle HEAD requests properly (return headers only) - This should fix 405 errors when browser checks image availability
98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
|
|
/**
|
|
* API Proxy for MinIO assets
|
|
*
|
|
* Proxies requests to MinIO through city-service or directly to MinIO.
|
|
* This allows serving assets from https://daarion.space/api/assets/...
|
|
* instead of requiring assets.daarion.space DNS setup.
|
|
*/
|
|
|
|
const MINIO_ENDPOINT = process.env.MINIO_ENDPOINT || 'http://minio:9000';
|
|
const ASSETS_BUCKET = process.env.ASSETS_BUCKET || 'daarion-assets';
|
|
|
|
export async function GET(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ path: string[] }> }
|
|
) {
|
|
return handleAssetRequest(request, params);
|
|
}
|
|
|
|
export async function HEAD(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ path: string[] }> }
|
|
) {
|
|
return handleAssetRequest(request, params);
|
|
}
|
|
|
|
async function handleAssetRequest(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ path: string[] }> }
|
|
) {
|
|
try {
|
|
const { path } = await params;
|
|
const objectPath = path.join('/');
|
|
|
|
// Construct MinIO URL
|
|
// Format: /api/assets/microdao/logo/2025/12/02/abc123.png
|
|
// Should proxy to: http://minio:9000/daarion-assets/microdao/logo/2025/12/02/abc123.png
|
|
const minioUrl = `${MINIO_ENDPOINT}/${ASSETS_BUCKET}/${objectPath}`;
|
|
|
|
// Proxy through city-service
|
|
// INTERNAL_API_URL is like http://daarion-city-service:7001
|
|
// We need: http://daarion-city-service:7001/city/assets/proxy/...
|
|
const cityServiceUrl = process.env.INTERNAL_API_URL || 'http://daarion-city-service:7001';
|
|
const proxyUrl = `${cityServiceUrl}/city/assets/proxy/${objectPath}`;
|
|
|
|
// Use the same HTTP method as the incoming request (GET or HEAD)
|
|
const method = request.method as 'GET' | 'HEAD';
|
|
|
|
// Try to fetch from city-service proxy
|
|
const response = await fetch(proxyUrl, {
|
|
method: method,
|
|
headers: {
|
|
'Accept': request.headers.get('Accept') || '*/*',
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
return new NextResponse('Asset not found', { status: 404 });
|
|
}
|
|
|
|
// Get content type from response
|
|
const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
|
|
const contentLength = response.headers.get('Content-Length');
|
|
|
|
// For HEAD requests, return headers only
|
|
if (method === 'HEAD') {
|
|
return new NextResponse(null, {
|
|
status: 200,
|
|
headers: {
|
|
'Content-Type': contentType,
|
|
'Content-Length': contentLength || '0',
|
|
'Cache-Control': 'public, max-age=86400, immutable',
|
|
'Access-Control-Allow-Origin': '*',
|
|
},
|
|
});
|
|
}
|
|
|
|
// For GET requests, return file data
|
|
const blob = await response.blob();
|
|
const buffer = await blob.arrayBuffer();
|
|
|
|
return new NextResponse(buffer, {
|
|
status: 200,
|
|
headers: {
|
|
'Content-Type': contentType,
|
|
'Content-Length': contentLength || buffer.byteLength.toString(),
|
|
'Cache-Control': 'public, max-age=86400, immutable',
|
|
'Access-Control-Allow-Origin': '*',
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('[assets-proxy] Error:', error);
|
|
return new NextResponse('Internal Server Error', { status: 500 });
|
|
}
|
|
}
|
|
|