feat(api): complete unified v2 mobile surface
This commit is contained in:
@@ -1,14 +1,29 @@
|
||||
import express from 'express';
|
||||
import { AppError } from '../lib/errors.js';
|
||||
import { parseClientSignIn, parseClientSignUp, signInClient, signOutActor, signUpClient, getSessionForActor } from '../services/auth-service.js';
|
||||
import {
|
||||
getSessionForActor,
|
||||
parseClientSignIn,
|
||||
parseClientSignUp,
|
||||
parseStaffPhoneStart,
|
||||
parseStaffPhoneVerify,
|
||||
signInClient,
|
||||
signOutActor,
|
||||
signUpClient,
|
||||
startStaffPhoneAuth,
|
||||
verifyStaffPhoneAuth,
|
||||
} from '../services/auth-service.js';
|
||||
import { verifyFirebaseToken } from '../services/firebase-auth.js';
|
||||
|
||||
const defaultAuthService = {
|
||||
parseClientSignIn,
|
||||
parseClientSignUp,
|
||||
parseStaffPhoneStart,
|
||||
parseStaffPhoneVerify,
|
||||
signInClient,
|
||||
signOutActor,
|
||||
signUpClient,
|
||||
startStaffPhoneAuth,
|
||||
verifyStaffPhoneAuth,
|
||||
getSessionForActor,
|
||||
};
|
||||
|
||||
@@ -31,7 +46,7 @@ async function requireAuth(req, _res, next) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const decoded = await verifyFirebaseToken(token, { checkRevoked: true });
|
||||
const decoded = await verifyFirebaseToken(token);
|
||||
req.actor = {
|
||||
uid: decoded.uid,
|
||||
email: decoded.email || null,
|
||||
@@ -77,6 +92,32 @@ export function createAuthRouter(options = {}) {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/staff/phone/start', async (req, res, next) => {
|
||||
try {
|
||||
const payload = authService.parseStaffPhoneStart(req.body);
|
||||
const result = await authService.startStaffPhoneAuth(payload, { fetchImpl });
|
||||
return res.status(200).json({
|
||||
...result,
|
||||
requestId: req.requestId,
|
||||
});
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/staff/phone/verify', async (req, res, next) => {
|
||||
try {
|
||||
const payload = authService.parseStaffPhoneVerify(req.body);
|
||||
const session = await authService.verifyStaffPhoneAuth(payload, { fetchImpl });
|
||||
return res.status(200).json({
|
||||
...session,
|
||||
requestId: req.requestId,
|
||||
});
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/session', requireAuth, async (req, res, next) => {
|
||||
try {
|
||||
const session = await authService.getSessionForActor(req.actor);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user