bootstrap-shops.mjs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import fs from 'node:fs';
  2. const parseUrl = process.env.PARSE_URL || 'http://127.0.0.1:3000/parse';
  3. const appId = process.env.PARSE_APP_ID;
  4. const masterKey = process.env.PARSE_MASTER_KEY;
  5. const sellerId = process.env.AMAZON_SELLER_ID;
  6. const credentialFile = process.env.SP_API_CREDENTIAL_FILE;
  7. if (!appId || !masterKey || !sellerId || !credentialFile) {
  8. throw new Error('Missing bootstrap environment variables');
  9. }
  10. const headers = {
  11. 'Content-Type': 'application/json',
  12. 'X-Parse-Application-Id': appId,
  13. 'X-Parse-Master-Key': masterKey,
  14. };
  15. async function request(path, method = 'GET', body) {
  16. const response = await fetch(`${parseUrl}${path}`, {
  17. method,
  18. headers,
  19. body: body === undefined ? undefined : JSON.stringify(body),
  20. });
  21. const result = await response.json();
  22. if (!response.ok || result.error) {
  23. throw new Error(`${method} ${path}: ${result.error || response.status}`);
  24. }
  25. return result;
  26. }
  27. const text = fs.readFileSync(credentialFile, 'utf8');
  28. const clientId = text.match(/客户端编码:\s*(\S+)/)?.[1];
  29. const clientSecret = text.match(/客户端秘钥:\s*(\S+)/)?.[1];
  30. const refreshTokens = [...text.matchAll(/Atzr\|\S+/g)].map(match => match[0]);
  31. if (!clientId || !clientSecret || refreshTokens.length !== 6) {
  32. throw new Error('SP-API credential file is incomplete');
  33. }
  34. const marketplaces = [
  35. [
  36. ['美国', 'ATVPDKIKX0DER', '1', 'NA', 'us-east-1'],
  37. ['加拿大', 'A2EUQ1WTGCTBG2', '6', 'NA', 'us-east-1'],
  38. ['墨西哥', 'A1AM78C64UM0Y8', '10', 'NA', 'us-east-1'],
  39. ['巴西', 'A2Q3Y263D00KWC', '13', 'NA', 'us-east-1'],
  40. ],
  41. [['新加坡', 'A19VAU5U5O7RUS', '', 'FE', 'us-west-2']],
  42. [
  43. ['意大利', 'APJ6JRA9NG5V4', '9', 'EU', 'eu-west-1'],
  44. ['法国', 'A13V1IB3VIYZZH', '4', 'EU', 'eu-west-1'],
  45. ['爱尔兰', 'A28R8C7NBKEWEA', '', 'EU', 'eu-west-1'],
  46. ['西班牙', 'A1RKKUPIHCS9HS', '8', 'EU', 'eu-west-1'],
  47. ['波兰', 'A1C3SOZRARQ6R3', '', 'EU', 'eu-west-1'],
  48. ['荷兰', 'A1805IZSGTT6HS', '', 'EU', 'eu-west-1'],
  49. ['比利时', 'AMEN7PMS3EDWL', '', 'EU', 'eu-west-1'],
  50. ['瑞典', 'A2NODRKZP88ZB9', '', 'EU', 'eu-west-1'],
  51. ['德国', 'A1PA6795UKMFR9', '3', 'EU', 'eu-west-1'],
  52. ['英国', 'A1F83G8C2ARO7P', '2', 'EU', 'eu-west-1'],
  53. ],
  54. [['阿联酋', 'A2VIGQ35RCS4UG', '11', 'EU', 'eu-west-1']],
  55. [['沙特阿拉伯', 'A17E79C6D8DWNP', '14', 'EU', 'eu-west-1']],
  56. [['澳大利亚', 'A39IBJ37TRP1C6', '12', 'FE', 'us-west-2']],
  57. ];
  58. let created = 0;
  59. let updated = 0;
  60. for (let groupIndex = 0; groupIndex < marketplaces.length; groupIndex += 1) {
  61. for (const [country, marketplaceId, domain, region, spRegion] of marketplaces[groupIndex]) {
  62. const where = encodeURIComponent(JSON.stringify({ marketplaceId }));
  63. const existing = await request(`/classes/Shop?where=${where}&limit=1`);
  64. const payload = {
  65. name: `${country} - zzhhxcl`,
  66. platform: 'amazon',
  67. region,
  68. marketplaceId,
  69. domain,
  70. nodeIds: [],
  71. config: {
  72. SpApiConfig: {
  73. clientId,
  74. sellerID: sellerId,
  75. clientSecret,
  76. refreshToken: refreshTokens[groupIndex],
  77. region: spRegion,
  78. sandbox: false,
  79. },
  80. },
  81. sync_status: 'idle',
  82. status: 'active',
  83. };
  84. if (existing.results?.[0]?.objectId) {
  85. await request(`/classes/Shop/${existing.results[0].objectId}`, 'PUT', payload);
  86. updated += 1;
  87. } else {
  88. await request('/classes/Shop', 'POST', payload);
  89. created += 1;
  90. }
  91. console.log(`Shop ready: ${country} (${marketplaceId})`);
  92. }
  93. }
  94. const adminUsers = await request(`/users?where=${encodeURIComponent(JSON.stringify({ username: 'admin' }))}&limit=1`);
  95. const adminUser = adminUsers.results?.[0];
  96. if (!adminUser) throw new Error('Initial admin user was not found');
  97. const roles = await request(`/roles?where=${encodeURIComponent(JSON.stringify({ name: 'admin' }))}&limit=1`);
  98. let roleId = roles.results?.[0]?.objectId;
  99. if (!roleId) {
  100. const role = await request('/roles', 'POST', {
  101. name: 'admin',
  102. ACL: { '*': { read: false, write: false } },
  103. });
  104. roleId = role.objectId;
  105. }
  106. await request(`/roles/${roleId}`, 'PUT', {
  107. users: {
  108. __op: 'AddRelation',
  109. objects: [{ __type: 'Pointer', className: '_User', objectId: adminUser.objectId }],
  110. },
  111. });
  112. const adminOnly = { 'role:admin': true };
  113. await request('/schemas/Shop', 'PUT', {
  114. classLevelPermissions: {
  115. find: adminOnly,
  116. count: adminOnly,
  117. get: adminOnly,
  118. create: adminOnly,
  119. update: adminOnly,
  120. delete: adminOnly,
  121. addField: {},
  122. },
  123. });
  124. console.log(JSON.stringify({ created, updated, total: created + updated, adminRole: roleId }));