feat(api): add unified v2 gateway and mobile read slice

This commit is contained in:
zouantchaw
2026-03-13 15:17:00 +01:00
parent 13bcfc9d40
commit 817a39e305
29 changed files with 6788 additions and 87 deletions

View File

@@ -0,0 +1,75 @@
import { Router } from 'express';
import { AppError } from '../lib/errors.js';
const HOP_BY_HOP_HEADERS = new Set([
'connection',
'content-length',
'host',
'keep-alive',
'proxy-authenticate',
'proxy-authorization',
'te',
'trailer',
'transfer-encoding',
'upgrade',
]);
function resolveTargetBase(pathname) {
if (pathname.startsWith('/core')) return process.env.CORE_API_BASE_URL;
if (pathname.startsWith('/commands')) return process.env.COMMAND_API_BASE_URL;
if (pathname.startsWith('/query')) return process.env.QUERY_API_BASE_URL;
return null;
}
function copyHeaders(source, target) {
for (const [key, value] of source.entries()) {
if (HOP_BY_HOP_HEADERS.has(key.toLowerCase())) continue;
target.setHeader(key, value);
}
}
async function forwardRequest(req, res, next, fetchImpl) {
try {
const requestPath = new URL(req.originalUrl, 'http://localhost').pathname;
const baseUrl = resolveTargetBase(requestPath);
if (!baseUrl) {
throw new AppError('NOT_FOUND', `No upstream configured for ${requestPath}`, 404);
}
const url = new URL(req.originalUrl, baseUrl);
const headers = new Headers();
for (const [key, value] of Object.entries(req.headers)) {
if (value == null || HOP_BY_HOP_HEADERS.has(key.toLowerCase())) continue;
if (Array.isArray(value)) {
for (const item of value) headers.append(key, item);
} else {
headers.set(key, value);
}
}
headers.set('x-request-id', req.requestId);
const upstream = await fetchImpl(url, {
method: req.method,
headers,
body: req.method === 'GET' || req.method === 'HEAD' ? undefined : req,
duplex: req.method === 'GET' || req.method === 'HEAD' ? undefined : 'half',
});
copyHeaders(upstream.headers, res);
res.status(upstream.status);
const buffer = Buffer.from(await upstream.arrayBuffer());
return res.send(buffer);
} catch (error) {
return next(error);
}
}
export function createProxyRouter(options = {}) {
const router = Router();
const fetchImpl = options.fetchImpl || fetch;
router.use(['/core', '/commands', '/query'], (req, res, next) => forwardRequest(req, res, next, fetchImpl));
return router;
}