import { Router, Request, Response, NextFunction } from 'express'; import { SpApiClient } from './client.ts'; import { shopAuthMiddleware } from './middleware/auth.ts'; import { CustomerFeedbackApi } from './api/customerFeedback.ts'; import { OrdersApi } from './api/orders.ts'; import { SalesApi } from './api/sales.ts'; import { ListingsApi } from './api/listings.ts'; import { ExternalFulfillmentApi } from './api/externalFulfillment.ts'; import { CatalogItemsApi } from './api/catalogItems.ts'; // import { InventoryApi } from './api/inventory.ts'; // import { PricingApi } from './api/pricing.ts'; // import { ReportsApi } from './api/reports.ts'; // import { NotificationsApi } from './api/notifications.ts'; import { SellersApi } from './api/sellers.ts'; // Unified Response Format const sendResponse = (res: Response, data: any) => { res.json({ success: true, data, timestamp: new Date().toISOString() }); }; const asyncHandler = (fn: (req: Request, res: Response, next: NextFunction) => Promise) => (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); }; /** * 创建 SP-API 路由 (动态配置版) * 不需要传入静态 client,而是使用中间件动态配置 */ export const createSpApiRouter = (staticClient?: SpApiClient) => { const router = Router(); // Apply Shop Authentication Middleware to all routes router.use(shopAuthMiddleware); // Helper to inject client with context const getClient = (req: Request): SpApiClient => { // We reuse a singleton instance or static methods if possible, // but since SpApiClient is now stateless-ish (config via method params), // we can use a single instance. // However, to keep it clean, let's use the passed staticClient or create a new one. // The important part is passing the context to the API methods. return staticClient || new SpApiClient(); }; // Helper to wrap API calls with context injection // Since we modified SpApiClient.request to take context, // but the individual API classes (e.g. OrdersApi) wraps the client.request call, // we need to pass the context down. // OPTION: We can modify the individual API classes to accept context in their methods? // OR: We can use a Proxy or Factory to inject context into the client before passing to API. // Better approach for minimal refactor of API classes: // Create a "Scoped Client" that pre-fills the context. const createScopedApi = (ApiClass: new (client: SpApiClient) => T, req: Request): T => { const baseClient = getClient(req); // Create a proxy client that injects context into request() const scopedClient = { request: async (options: any) => { return baseClient.request({ ...options, context: { shopId: req.spApiContext?.shopId, marketplaceId: req.spApiContext?.marketplaceId, config: req.spApiContext?.config } }); } } as SpApiClient; return new ApiClass(scopedClient); }; // --- Customer Feedback --- router.get('/customerFeedback/items/:asin/reviews/topics', asyncHandler(async (req, res) => { const api = createScopedApi(CustomerFeedbackApi, req); const result = await api.getItemReviewTopics({ ...req.query, asin: req.params.asin } as any); sendResponse(res, result); })); router.get('/customerFeedback/items/:asin/browseNode', asyncHandler(async (req, res) => { const api = createScopedApi(CustomerFeedbackApi, req); const result = await api.getItemBrowseNode({ ...req.query, asin: req.params.asin } as any); sendResponse(res, result); })); router.get('/customerFeedback/browseNodes/:browseNodeId/reviews/topics', asyncHandler(async (req, res) => { const api = createScopedApi(CustomerFeedbackApi, req); const result = await api.getBrowseNodeReviewTopics({ ...req.query, browseNodeId: req.params.browseNodeId } as any); sendResponse(res, result); })); router.get('/customerFeedback/items/:asin/reviews/trends', asyncHandler(async (req, res) => { const api = createScopedApi(CustomerFeedbackApi, req); const result = await api.getItemReviewTrends({ ...req.query, asin: req.params.asin } as any); sendResponse(res, result); })); router.get('/customerFeedback/browseNodes/:browseNodeId/reviews/trends', asyncHandler(async (req, res) => { const api = createScopedApi(CustomerFeedbackApi, req); const result = await api.getBrowseNodeReviewTrends({ ...req.query, browseNodeId: req.params.browseNodeId } as any); sendResponse(res, result); })); router.get('/customerFeedback/browseNodes/:browseNodeId/returns/topics', asyncHandler(async (req, res) => { const api = createScopedApi(CustomerFeedbackApi, req); const result = await api.getBrowseNodeReturnTopics({ ...req.query, browseNodeId: req.params.browseNodeId } as any); sendResponse(res, result); })); router.get('/customerFeedback/browseNodes/:browseNodeId/returns/trends', asyncHandler(async (req, res) => { const api = createScopedApi(CustomerFeedbackApi, req); const result = await api.getBrowseNodeReturnTrends({ ...req.query, browseNodeId: req.params.browseNodeId } as any); sendResponse(res, result); })); // --- Orders --- router.get('/orders', asyncHandler(async (req, res) => { const api = createScopedApi(OrdersApi, req); const result = await api.getOrders(req.query as any); console.log('result', result); sendResponse(res, result); })); router.get('/orders/:orderId', asyncHandler(async (req:any, res) => { const api = createScopedApi(OrdersApi, req); const result = await api.getOrder(req.params.orderId); sendResponse(res, result); })); router.get('/orders/:orderId/items', asyncHandler(async (req:any, res) => { const api = createScopedApi(OrdersApi, req); const result = await api.getOrderItems(req.params.orderId, req.query.NextToken as string); sendResponse(res, result); })); // --- Sales --- router.get('/sales/orderMetrics', asyncHandler(async (req:any, res) => { const api = createScopedApi(SalesApi, req); const result = await api.getOrderMetrics(req.query as any); sendResponse(res, result); })); // --- Listings --- router.get('/listings/items/:sellerId', asyncHandler(async (req:any, res) => { const api = createScopedApi(ListingsApi, req); const result = await api.searchListingsItems({ ...req.query, sellerId: req.params.sellerId } as any); sendResponse(res, result); })); router.get('/listings/items/:sellerId/:sku', asyncHandler(async (req:any, res) => { const api = createScopedApi(ListingsApi, req); const result = await api.getListingsItem({ ...req.query, sellerId: req.params.sellerId, sku: req.params.sku } as any); sendResponse(res, result); })); router.put('/listings/items/:sellerId/:sku', asyncHandler(async (req:any, res) => { const api = createScopedApi(ListingsApi, req); const marketplaceIds = (req.query.marketplaceIds as string)?.split(',') || []; const result = await api.putListingsItem(req.params.sellerId, req.params.sku, marketplaceIds, req.body); sendResponse(res, result); })); router.delete('/listings/items/:sellerId/:sku', asyncHandler(async (req:any, res) => { const api = createScopedApi(ListingsApi, req); const marketplaceIds = (req.query.marketplaceIds as string)?.split(',') || []; const result = await api.deleteListingsItem(req.params.sellerId, req.params.sku, marketplaceIds); sendResponse(res, result); })); // --- External Fulfillment --- router.get('/externalFulfillment/returns', asyncHandler(async (req, res) => { const api = createScopedApi(ExternalFulfillmentApi, req); const result = await api.listReturns(req.query as any); sendResponse(res, result); })); router.get('/externalFulfillment/returns/:returnId', asyncHandler(async (req:any, res) => { const api = createScopedApi(ExternalFulfillmentApi, req); const result = await api.getReturn(req.params.returnId); sendResponse(res, result); })); // --- Catalog Items --- router.get('/catalog/categories', asyncHandler(async (req, res) => { const api = createScopedApi(CatalogItemsApi, req); const result = await api.listCatalogCategories(req.query as any); sendResponse(res, result); })); router.get('/catalog/items', asyncHandler(async (req, res) => { const api = createScopedApi(CatalogItemsApi, req); const result = await api.searchCatalogItems(req.query as any); sendResponse(res, result); })); router.get('/catalog/items/:asin', asyncHandler(async (req, res) => { const api = createScopedApi(CatalogItemsApi, req); const result = await api.getCatalogItem({ ...req.query, asin: req.params.asin } as any); sendResponse(res, result); })); // // --- Inventory --- // router.get('/inventory/summaries', asyncHandler(async (req, res) => { // const api = createScopedApi(InventoryApi, req); // const result = await api.getInventorySummaries(req.query as any); // sendResponse(res, result); // })); // // --- Pricing --- // router.get('/pricing/price', asyncHandler(async (req, res) => { // const api = createScopedApi(PricingApi, req); // const result = await api.getPricing(req.query as any); // sendResponse(res, result); // })); // router.get('/pricing/competitivePrice', asyncHandler(async (req, res) => { // const api = createScopedApi(PricingApi, req); // const result = await api.getCompetitivePricing(req.query as any); // sendResponse(res, result); // })); // // --- Reports --- // router.post('/reports', asyncHandler(async (req, res) => { // const api = createScopedApi(ReportsApi, req); // const result = await api.createReport(req.body); // sendResponse(res, result); // })); // router.get('/reports/:reportId', asyncHandler(async (req, res) => { // const api = createScopedApi(ReportsApi, req); // const result = await api.getReport(req.params.reportId); // sendResponse(res, result); // })); // router.get('/reports/documents/:reportDocumentId', asyncHandler(async (req, res) => { // const api = createScopedApi(ReportsApi, req); // const result = await api.getReportDocument(req.params.reportDocumentId); // sendResponse(res, result); // })); // // --- Notifications --- // router.post('/notifications/subscriptions/:notificationType', asyncHandler(async (req, res) => { // const api = createScopedApi(NotificationsApi, req); // const result = await api.createSubscription(req.params.notificationType, req.body); // sendResponse(res, result); // })); // router.get('/notifications/subscriptions/:notificationType/:subscriptionId', asyncHandler(async (req, res) => { // const api = createScopedApi(NotificationsApi, req); // const result = await api.getSubscription(req.params.notificationType, req.params.subscriptionId); // sendResponse(res, result); // })); // router.delete('/notifications/subscriptions/:notificationType/:subscriptionId', asyncHandler(async (req, res) => { // const api = createScopedApi(NotificationsApi, req); // const result = await api.deleteSubscription(req.params.notificationType, req.params.subscriptionId); // sendResponse(res, result); // })); // --- Sellers --- router.get('/sellers/account', asyncHandler(async (req, res) => { const api = createScopedApi(SellersApi, req); const result: any = await api.getAccount(); console.log(result.data.errors); sendResponse(res, result); })); router.get('/sellers/marketplaceParticipations', asyncHandler(async (req, res) => { const api = createScopedApi(SellersApi, req); const result = await api.getMarketplaceParticipations(); sendResponse(res, result); })); return router; };