routes.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import { Router, Request, Response, NextFunction } from 'express';
  2. import { SpApiClient } from './client.ts';
  3. import { shopAuthMiddleware } from './middleware/auth.ts';
  4. import { CustomerFeedbackApi } from './api/customerFeedback.ts';
  5. import { OrdersApi } from './api/orders.ts';
  6. import { SalesApi } from './api/sales.ts';
  7. import { ListingsApi } from './api/listings.ts';
  8. import { ExternalFulfillmentApi } from './api/externalFulfillment.ts';
  9. import { CatalogItemsApi } from './api/catalogItems.ts';
  10. // import { InventoryApi } from './api/inventory.ts';
  11. // import { PricingApi } from './api/pricing.ts';
  12. // import { ReportsApi } from './api/reports.ts';
  13. // import { NotificationsApi } from './api/notifications.ts';
  14. import { SellersApi } from './api/sellers.ts';
  15. // Unified Response Format
  16. const sendResponse = (res: Response, data: any) => {
  17. res.json({
  18. success: true,
  19. data,
  20. timestamp: new Date().toISOString()
  21. });
  22. };
  23. const asyncHandler = (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) =>
  24. (req: Request, res: Response, next: NextFunction) => {
  25. Promise.resolve(fn(req, res, next)).catch(next);
  26. };
  27. /**
  28. * 创建 SP-API 路由 (动态配置版)
  29. * 不需要传入静态 client,而是使用中间件动态配置
  30. */
  31. export const createSpApiRouter = (staticClient?: SpApiClient) => {
  32. const router = Router();
  33. // Apply Shop Authentication Middleware to all routes
  34. router.use(shopAuthMiddleware);
  35. // Helper to inject client with context
  36. const getClient = (req: Request): SpApiClient => {
  37. // We reuse a singleton instance or static methods if possible,
  38. // but since SpApiClient is now stateless-ish (config via method params),
  39. // we can use a single instance.
  40. // However, to keep it clean, let's use the passed staticClient or create a new one.
  41. // The important part is passing the context to the API methods.
  42. return staticClient || new SpApiClient();
  43. };
  44. // Helper to wrap API calls with context injection
  45. // Since we modified SpApiClient.request to take context,
  46. // but the individual API classes (e.g. OrdersApi) wraps the client.request call,
  47. // we need to pass the context down.
  48. // OPTION: We can modify the individual API classes to accept context in their methods?
  49. // OR: We can use a Proxy or Factory to inject context into the client before passing to API.
  50. // Better approach for minimal refactor of API classes:
  51. // Create a "Scoped Client" that pre-fills the context.
  52. const createScopedApi = <T>(ApiClass: new (client: SpApiClient) => T, req: Request): T => {
  53. const baseClient = getClient(req);
  54. // Create a proxy client that injects context into request()
  55. const scopedClient = {
  56. request: async (options: any) => {
  57. return baseClient.request({
  58. ...options,
  59. context: {
  60. shopId: req.spApiContext?.shopId,
  61. marketplaceId: req.spApiContext?.marketplaceId,
  62. config: req.spApiContext?.config
  63. }
  64. });
  65. }
  66. } as SpApiClient;
  67. return new ApiClass(scopedClient);
  68. };
  69. // --- Customer Feedback ---
  70. router.get('/customerFeedback/items/:asin/reviews/topics', asyncHandler(async (req, res) => {
  71. const api = createScopedApi(CustomerFeedbackApi, req);
  72. const result = await api.getItemReviewTopics({ ...req.query, asin: req.params.asin } as any);
  73. sendResponse(res, result);
  74. }));
  75. router.get('/customerFeedback/items/:asin/browseNode', asyncHandler(async (req, res) => {
  76. const api = createScopedApi(CustomerFeedbackApi, req);
  77. const result = await api.getItemBrowseNode({ ...req.query, asin: req.params.asin } as any);
  78. sendResponse(res, result);
  79. }));
  80. router.get('/customerFeedback/browseNodes/:browseNodeId/reviews/topics', asyncHandler(async (req, res) => {
  81. const api = createScopedApi(CustomerFeedbackApi, req);
  82. const result = await api.getBrowseNodeReviewTopics({ ...req.query, browseNodeId: req.params.browseNodeId } as any);
  83. sendResponse(res, result);
  84. }));
  85. router.get('/customerFeedback/items/:asin/reviews/trends', asyncHandler(async (req, res) => {
  86. const api = createScopedApi(CustomerFeedbackApi, req);
  87. const result = await api.getItemReviewTrends({ ...req.query, asin: req.params.asin } as any);
  88. sendResponse(res, result);
  89. }));
  90. router.get('/customerFeedback/browseNodes/:browseNodeId/reviews/trends', asyncHandler(async (req, res) => {
  91. const api = createScopedApi(CustomerFeedbackApi, req);
  92. const result = await api.getBrowseNodeReviewTrends({ ...req.query, browseNodeId: req.params.browseNodeId } as any);
  93. sendResponse(res, result);
  94. }));
  95. router.get('/customerFeedback/browseNodes/:browseNodeId/returns/topics', asyncHandler(async (req, res) => {
  96. const api = createScopedApi(CustomerFeedbackApi, req);
  97. const result = await api.getBrowseNodeReturnTopics({ ...req.query, browseNodeId: req.params.browseNodeId } as any);
  98. sendResponse(res, result);
  99. }));
  100. router.get('/customerFeedback/browseNodes/:browseNodeId/returns/trends', asyncHandler(async (req, res) => {
  101. const api = createScopedApi(CustomerFeedbackApi, req);
  102. const result = await api.getBrowseNodeReturnTrends({ ...req.query, browseNodeId: req.params.browseNodeId } as any);
  103. sendResponse(res, result);
  104. }));
  105. // --- Orders ---
  106. router.get('/orders', asyncHandler(async (req, res) => {
  107. const api = createScopedApi(OrdersApi, req);
  108. const result = await api.getOrders(req.query as any);
  109. console.log('result', result);
  110. sendResponse(res, result);
  111. }));
  112. router.get('/orders/:orderId', asyncHandler(async (req:any, res) => {
  113. const api = createScopedApi(OrdersApi, req);
  114. const result = await api.getOrder(req.params.orderId);
  115. sendResponse(res, result);
  116. }));
  117. router.get('/orders/:orderId/items', asyncHandler(async (req:any, res) => {
  118. const api = createScopedApi(OrdersApi, req);
  119. const result = await api.getOrderItems(req.params.orderId, req.query.NextToken as string);
  120. sendResponse(res, result);
  121. }));
  122. // --- Sales ---
  123. router.get('/sales/orderMetrics', asyncHandler(async (req:any, res) => {
  124. const api = createScopedApi(SalesApi, req);
  125. const result = await api.getOrderMetrics(req.query as any);
  126. sendResponse(res, result);
  127. }));
  128. // --- Listings ---
  129. router.get('/listings/items/:sellerId', asyncHandler(async (req:any, res) => {
  130. const api = createScopedApi(ListingsApi, req);
  131. const result = await api.searchListingsItems({ ...req.query, sellerId: req.params.sellerId } as any);
  132. sendResponse(res, result);
  133. }));
  134. router.get('/listings/items/:sellerId/:sku', asyncHandler(async (req:any, res) => {
  135. const api = createScopedApi(ListingsApi, req);
  136. const result = await api.getListingsItem({ ...req.query, sellerId: req.params.sellerId, sku: req.params.sku } as any);
  137. sendResponse(res, result);
  138. }));
  139. router.put('/listings/items/:sellerId/:sku', asyncHandler(async (req:any, res) => {
  140. const api = createScopedApi(ListingsApi, req);
  141. const marketplaceIds = (req.query.marketplaceIds as string)?.split(',') || [];
  142. const result = await api.putListingsItem(req.params.sellerId, req.params.sku, marketplaceIds, req.body);
  143. sendResponse(res, result);
  144. }));
  145. router.delete('/listings/items/:sellerId/:sku', asyncHandler(async (req:any, res) => {
  146. const api = createScopedApi(ListingsApi, req);
  147. const marketplaceIds = (req.query.marketplaceIds as string)?.split(',') || [];
  148. const result = await api.deleteListingsItem(req.params.sellerId, req.params.sku, marketplaceIds);
  149. sendResponse(res, result);
  150. }));
  151. // --- External Fulfillment ---
  152. router.get('/externalFulfillment/returns', asyncHandler(async (req, res) => {
  153. const api = createScopedApi(ExternalFulfillmentApi, req);
  154. const result = await api.listReturns(req.query as any);
  155. sendResponse(res, result);
  156. }));
  157. router.get('/externalFulfillment/returns/:returnId', asyncHandler(async (req:any, res) => {
  158. const api = createScopedApi(ExternalFulfillmentApi, req);
  159. const result = await api.getReturn(req.params.returnId);
  160. sendResponse(res, result);
  161. }));
  162. // --- Catalog Items ---
  163. router.get('/catalog/categories', asyncHandler(async (req, res) => {
  164. const api = createScopedApi(CatalogItemsApi, req);
  165. const result = await api.listCatalogCategories(req.query as any);
  166. sendResponse(res, result);
  167. }));
  168. router.get('/catalog/items', asyncHandler(async (req, res) => {
  169. const api = createScopedApi(CatalogItemsApi, req);
  170. const result = await api.searchCatalogItems(req.query as any);
  171. sendResponse(res, result);
  172. }));
  173. router.get('/catalog/items/:asin', asyncHandler(async (req, res) => {
  174. const api = createScopedApi(CatalogItemsApi, req);
  175. const result = await api.getCatalogItem({ ...req.query, asin: req.params.asin } as any);
  176. sendResponse(res, result);
  177. }));
  178. // // --- Inventory ---
  179. // router.get('/inventory/summaries', asyncHandler(async (req, res) => {
  180. // const api = createScopedApi(InventoryApi, req);
  181. // const result = await api.getInventorySummaries(req.query as any);
  182. // sendResponse(res, result);
  183. // }));
  184. // // --- Pricing ---
  185. // router.get('/pricing/price', asyncHandler(async (req, res) => {
  186. // const api = createScopedApi(PricingApi, req);
  187. // const result = await api.getPricing(req.query as any);
  188. // sendResponse(res, result);
  189. // }));
  190. // router.get('/pricing/competitivePrice', asyncHandler(async (req, res) => {
  191. // const api = createScopedApi(PricingApi, req);
  192. // const result = await api.getCompetitivePricing(req.query as any);
  193. // sendResponse(res, result);
  194. // }));
  195. // // --- Reports ---
  196. // router.post('/reports', asyncHandler(async (req, res) => {
  197. // const api = createScopedApi(ReportsApi, req);
  198. // const result = await api.createReport(req.body);
  199. // sendResponse(res, result);
  200. // }));
  201. // router.get('/reports/:reportId', asyncHandler(async (req, res) => {
  202. // const api = createScopedApi(ReportsApi, req);
  203. // const result = await api.getReport(req.params.reportId);
  204. // sendResponse(res, result);
  205. // }));
  206. // router.get('/reports/documents/:reportDocumentId', asyncHandler(async (req, res) => {
  207. // const api = createScopedApi(ReportsApi, req);
  208. // const result = await api.getReportDocument(req.params.reportDocumentId);
  209. // sendResponse(res, result);
  210. // }));
  211. // // --- Notifications ---
  212. // router.post('/notifications/subscriptions/:notificationType', asyncHandler(async (req, res) => {
  213. // const api = createScopedApi(NotificationsApi, req);
  214. // const result = await api.createSubscription(req.params.notificationType, req.body);
  215. // sendResponse(res, result);
  216. // }));
  217. // router.get('/notifications/subscriptions/:notificationType/:subscriptionId', asyncHandler(async (req, res) => {
  218. // const api = createScopedApi(NotificationsApi, req);
  219. // const result = await api.getSubscription(req.params.notificationType, req.params.subscriptionId);
  220. // sendResponse(res, result);
  221. // }));
  222. // router.delete('/notifications/subscriptions/:notificationType/:subscriptionId', asyncHandler(async (req, res) => {
  223. // const api = createScopedApi(NotificationsApi, req);
  224. // const result = await api.deleteSubscription(req.params.notificationType, req.params.subscriptionId);
  225. // sendResponse(res, result);
  226. // }));
  227. // --- Sellers ---
  228. router.get('/sellers/account', asyncHandler(async (req, res) => {
  229. const api = createScopedApi(SellersApi, req);
  230. const result: any = await api.getAccount();
  231. console.log(result.data.errors);
  232. sendResponse(res, result);
  233. }));
  234. router.get('/sellers/marketplaceParticipations', asyncHandler(async (req, res) => {
  235. const api = createScopedApi(SellersApi, req);
  236. const result = await api.getMarketplaceParticipations();
  237. sendResponse(res, result);
  238. }));
  239. return router;
  240. };