feat(api): complete unified v2 mobile surface

This commit is contained in:
zouantchaw
2026-03-13 17:02:24 +01:00
parent 817a39e305
commit b455455a49
39 changed files with 7726 additions and 506 deletions

View File

@@ -14,10 +14,91 @@ const HOP_BY_HOP_HEADERS = new Set([
'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;
const DIRECT_CORE_ALIASES = [
{ methods: new Set(['POST']), pattern: /^\/upload-file$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['POST']), pattern: /^\/create-signed-url$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['POST']), pattern: /^\/invoke-llm$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['POST']), pattern: /^\/rapid-orders\/transcribe$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['POST']), pattern: /^\/rapid-orders\/parse$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['POST']), pattern: /^\/staff\/profile\/photo$/, targetPath: (pathname) => `/core${pathname}` },
{
methods: new Set(['POST']),
pattern: /^\/staff\/profile\/documents\/([^/]+)\/upload$/,
targetPath: (_pathname, match) => `/core/staff/documents/${match[1]}/upload`,
},
{
methods: new Set(['POST']),
pattern: /^\/staff\/profile\/attire\/([^/]+)\/upload$/,
targetPath: (_pathname, match) => `/core/staff/attire/${match[1]}/upload`,
},
{
methods: new Set(['POST']),
pattern: /^\/staff\/profile\/certificates$/,
targetPath: () => '/core/staff/certificates/upload',
},
{
methods: new Set(['DELETE']),
pattern: /^\/staff\/profile\/certificates\/([^/]+)$/,
targetPath: (_pathname, match) => `/core/staff/certificates/${match[1]}`,
},
{ methods: new Set(['POST']), pattern: /^\/staff\/documents\/([^/]+)\/upload$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['POST']), pattern: /^\/staff\/attire\/([^/]+)\/upload$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['POST']), pattern: /^\/staff\/certificates\/upload$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['DELETE']), pattern: /^\/staff\/certificates\/([^/]+)$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['POST']), pattern: /^\/verifications$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['GET']), pattern: /^\/verifications\/([^/]+)$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['POST']), pattern: /^\/verifications\/([^/]+)\/review$/, targetPath: (pathname) => `/core${pathname}` },
{ methods: new Set(['POST']), pattern: /^\/verifications\/([^/]+)\/retry$/, targetPath: (pathname) => `/core${pathname}` },
];
function resolveTarget(pathname, method) {
const upperMethod = method.toUpperCase();
if (pathname.startsWith('/core')) {
return {
baseUrl: process.env.CORE_API_BASE_URL,
upstreamPath: pathname,
};
}
if (pathname.startsWith('/commands')) {
return {
baseUrl: process.env.COMMAND_API_BASE_URL,
upstreamPath: pathname,
};
}
if (pathname.startsWith('/query')) {
return {
baseUrl: process.env.QUERY_API_BASE_URL,
upstreamPath: pathname,
};
}
for (const alias of DIRECT_CORE_ALIASES) {
if (!alias.methods.has(upperMethod)) continue;
const match = pathname.match(alias.pattern);
if (!match) continue;
return {
baseUrl: process.env.CORE_API_BASE_URL,
upstreamPath: alias.targetPath(pathname, match),
};
}
if ((upperMethod === 'GET' || upperMethod === 'HEAD') && (pathname.startsWith('/client') || pathname.startsWith('/staff'))) {
return {
baseUrl: process.env.QUERY_API_BASE_URL,
upstreamPath: `/query${pathname}`,
};
}
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(upperMethod) && (pathname.startsWith('/client') || pathname.startsWith('/staff'))) {
return {
baseUrl: process.env.COMMAND_API_BASE_URL,
upstreamPath: `/commands${pathname}`,
};
}
return null;
}
@@ -30,13 +111,13 @@ function copyHeaders(source, target) {
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 requestUrl = new URL(req.originalUrl, 'http://localhost');
const target = resolveTarget(requestUrl.pathname, req.method);
if (!target?.baseUrl) {
throw new AppError('NOT_FOUND', `No upstream configured for ${requestUrl.pathname}`, 404);
}
const url = new URL(req.originalUrl, baseUrl);
const url = new URL(`${target.upstreamPath}${requestUrl.search}`, target.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;
@@ -69,7 +150,7 @@ 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));
router.use((req, res, next) => forwardRequest(req, res, next, fetchImpl));
return router;
}