@yoryoboy/bi-mcp 1.0.5 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +140 -9
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/index.js +151 -1
  4. package/dist/index.js.map +2 -2
  5. package/dist/mcp-use.json +2 -2
  6. package/dist/scripts/_helpers.js +44 -0
  7. package/dist/scripts/_helpers.js.map +7 -0
  8. package/dist/scripts/admin-profile-delete.js +72 -0
  9. package/dist/scripts/admin-profile-delete.js.map +7 -0
  10. package/dist/scripts/admin-profile-list.js +24 -0
  11. package/dist/scripts/admin-profile-list.js.map +7 -0
  12. package/dist/scripts/admin-profile-upsert.js +25 -0
  13. package/dist/scripts/admin-profile-upsert.js.map +7 -0
  14. package/dist/scripts/admin-vtex-list.js +28 -0
  15. package/dist/scripts/admin-vtex-list.js.map +7 -0
  16. package/dist/scripts/admin-vtex-upsert.js +73 -0
  17. package/dist/scripts/admin-vtex-upsert.js.map +7 -0
  18. package/dist/scripts/admin-vtex-validate.js +55 -0
  19. package/dist/scripts/admin-vtex-validate.js.map +7 -0
  20. package/dist/scripts/run-migrations.js +50 -0
  21. package/dist/scripts/run-migrations.js.map +7 -0
  22. package/dist/scripts/test-db-connection.js +19 -0
  23. package/dist/scripts/test-db-connection.js.map +7 -0
  24. package/dist/src/config/meta.js +29 -0
  25. package/dist/src/config/meta.js.map +7 -0
  26. package/dist/src/config/profile-store.js +86 -0
  27. package/dist/src/config/profile-store.js.map +7 -0
  28. package/dist/src/config/vtex-crypto.js +43 -0
  29. package/dist/src/config/vtex-crypto.js.map +7 -0
  30. package/dist/src/config/vtex-profile-store.js +132 -0
  31. package/dist/src/config/vtex-profile-store.js.map +7 -0
  32. package/dist/src/config/vtex.js +27 -21
  33. package/dist/src/config/vtex.js.map +2 -2
  34. package/dist/src/db/client.js +58 -0
  35. package/dist/src/db/client.js.map +7 -0
  36. package/dist/src/meta/meta-utils.js +148 -0
  37. package/dist/src/meta/meta-utils.js.map +7 -0
  38. package/dist/src/services/meta/meta-ads.js +89 -0
  39. package/dist/src/services/meta/meta-ads.js.map +7 -0
  40. package/dist/src/services/meta/meta-api.js +35 -0
  41. package/dist/src/services/meta/meta-api.js.map +7 -0
  42. package/dist/src/services/vtex/vtex-api.js +24 -8
  43. package/dist/src/services/vtex/vtex-api.js.map +2 -2
  44. package/dist/src/services/vtex/vtex-catalog.js +5 -3
  45. package/dist/src/services/vtex/vtex-catalog.js.map +2 -2
  46. package/dist/src/services/vtex/vtex-logistics.js +18 -9
  47. package/dist/src/services/vtex/vtex-logistics.js.map +2 -2
  48. package/dist/src/services/vtex/vtex-orders.js +13 -7
  49. package/dist/src/services/vtex/vtex-orders.js.map +2 -2
  50. package/dist/src/services/vtex/vtex-pricing.js +5 -3
  51. package/dist/src/services/vtex/vtex-pricing.js.map +2 -2
  52. package/dist/src/tools/config/check-database-connection.js +59 -0
  53. package/dist/src/tools/config/check-database-connection.js.map +7 -0
  54. package/dist/src/tools/config/index.js +3 -0
  55. package/dist/src/tools/config/index.js.map +7 -0
  56. package/dist/src/tools/config/list-profiles.js +26 -0
  57. package/dist/src/tools/config/list-profiles.js.map +7 -0
  58. package/dist/src/tools/index.js +2 -0
  59. package/dist/src/tools/index.js.map +2 -2
  60. package/dist/src/tools/meta/account-overview.js +92 -0
  61. package/dist/src/tools/meta/account-overview.js.map +7 -0
  62. package/dist/src/tools/meta/ad-account-info.js +37 -0
  63. package/dist/src/tools/meta/ad-account-info.js.map +7 -0
  64. package/dist/src/tools/meta/ads-performance.js +79 -0
  65. package/dist/src/tools/meta/ads-performance.js.map +7 -0
  66. package/dist/src/tools/meta/campaign-performance.js +81 -0
  67. package/dist/src/tools/meta/campaign-performance.js.map +7 -0
  68. package/dist/src/tools/meta/index.js +9 -0
  69. package/dist/src/tools/meta/index.js.map +7 -0
  70. package/dist/src/tools/meta/list-accessible-ad-accounts.js +44 -0
  71. package/dist/src/tools/meta/list-accessible-ad-accounts.js.map +7 -0
  72. package/dist/src/tools/meta/list-accessible-businesses.js +36 -0
  73. package/dist/src/tools/meta/list-accessible-businesses.js.map +7 -0
  74. package/dist/src/tools/meta/placement-mix.js +67 -0
  75. package/dist/src/tools/meta/placement-mix.js.map +7 -0
  76. package/dist/src/tools/meta/time-series.js +69 -0
  77. package/dist/src/tools/meta/time-series.js.map +7 -0
  78. package/dist/src/tools/vtex/computed-price.js +12 -1
  79. package/dist/src/tools/vtex/computed-price.js.map +2 -2
  80. package/dist/src/tools/vtex/index.js +1 -0
  81. package/dist/src/tools/vtex/index.js.map +2 -2
  82. package/dist/src/tools/vtex/inventory-check.js +15 -2
  83. package/dist/src/tools/vtex/inventory-check.js.map +2 -2
  84. package/dist/src/tools/vtex/order-details.js +16 -2
  85. package/dist/src/tools/vtex/order-details.js.map +2 -2
  86. package/dist/src/tools/vtex/orders-summary.js +11 -1
  87. package/dist/src/tools/vtex/orders-summary.js.map +2 -2
  88. package/dist/src/tools/vtex/product-offers.js +15 -2
  89. package/dist/src/tools/vtex/product-offers.js.map +2 -2
  90. package/dist/src/tools/vtex/profile-resolution.js +57 -0
  91. package/dist/src/tools/vtex/profile-resolution.js.map +7 -0
  92. package/dist/src/tools/vtex/sku-offers.js +16 -2
  93. package/dist/src/tools/vtex/sku-offers.js.map +2 -2
  94. package/dist/src/tools/vtex/sku-price.js +12 -2
  95. package/dist/src/tools/vtex/sku-price.js.map +2 -2
  96. package/dist/src/tools/vtex/update-inventory.js +12 -1
  97. package/dist/src/tools/vtex/update-inventory.js.map +2 -2
  98. package/dist/src/tools/vtex/update-lead-time.js +12 -1
  99. package/dist/src/tools/vtex/update-lead-time.js.map +2 -2
  100. package/dist/src/tools/vtex/warehouse-inventory.js +12 -1
  101. package/dist/src/tools/vtex/warehouse-inventory.js.map +2 -2
  102. package/package.json +12 -2
@@ -0,0 +1,73 @@
1
+ import process from "node:process";
2
+ import { hasFlag, loadLocalEnv, parseCliArgs, requireArg } from "./_helpers.js";
3
+ async function main() {
4
+ loadLocalEnv();
5
+ const args = parseCliArgs(process.argv.slice(2));
6
+ const profileId = requireArg(args, "profile-id");
7
+ const accountName = requireArg(args, "account-name");
8
+ const apiKey = requireArg(args, "api-key");
9
+ const apiToken = requireArg(args, "api-token");
10
+ const isActive = !hasFlag(args, "inactive");
11
+ const [{ getProfile }, { encryptSecret }, { upsertVtexConnection }, { buildVtexUrls }, { validateVtexConfig }] = await Promise.all([
12
+ import("../src/config/profile-store.js"),
13
+ import("../src/config/vtex-crypto.js"),
14
+ import("../src/config/vtex-profile-store.js"),
15
+ import("../src/config/vtex.js"),
16
+ import("../src/services/vtex/vtex-api.js")
17
+ ]);
18
+ const profile = await getProfile(profileId);
19
+ if (!profile) {
20
+ throw new Error(`Profile not found: "${profileId}"`);
21
+ }
22
+ const encryptedApiKey = encryptSecret(apiKey);
23
+ const encryptedApiToken = encryptSecret(apiToken);
24
+ const urls = buildVtexUrls(accountName);
25
+ let status = "valid";
26
+ let lastError = null;
27
+ let lastValidatedAt = null;
28
+ try {
29
+ await validateVtexConfig({
30
+ accountName,
31
+ apiKey,
32
+ apiToken,
33
+ baseUrl: urls.baseUrl,
34
+ pricingBaseUrl: urls.pricingBaseUrl
35
+ });
36
+ lastValidatedAt = /* @__PURE__ */ new Date();
37
+ } catch (error) {
38
+ status = "invalid";
39
+ lastError = error instanceof Error ? error.message : "VTEX validation failed";
40
+ lastValidatedAt = /* @__PURE__ */ new Date();
41
+ }
42
+ const connection = await upsertVtexConnection({
43
+ profileId,
44
+ accountName,
45
+ apiKeyEncrypted: encryptedApiKey,
46
+ apiTokenEncrypted: encryptedApiToken,
47
+ isActive,
48
+ status,
49
+ lastValidatedAt,
50
+ lastError
51
+ });
52
+ console.log("VTEX connection upsert OK");
53
+ console.log(
54
+ JSON.stringify(
55
+ {
56
+ profile_id: connection.profileId,
57
+ account_name: connection.accountName,
58
+ is_active: connection.isActive,
59
+ status: connection.status,
60
+ last_validated_at: connection.lastValidatedAt?.toISOString() ?? null,
61
+ last_error: connection.lastError
62
+ },
63
+ null,
64
+ 2
65
+ )
66
+ );
67
+ }
68
+ main().catch((error) => {
69
+ console.error("VTEX connection upsert failed");
70
+ console.error(error);
71
+ process.exitCode = 1;
72
+ });
73
+ //# sourceMappingURL=admin-vtex-upsert.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../scripts/admin-vtex-upsert.ts"],
4
+ "sourcesContent": ["import process from \"node:process\";\n\nimport { hasFlag, loadLocalEnv, parseCliArgs, requireArg } from \"./_helpers.js\";\n\nasync function main() {\n loadLocalEnv();\n\n const args = parseCliArgs(process.argv.slice(2));\n const profileId = requireArg(args, \"profile-id\");\n const accountName = requireArg(args, \"account-name\");\n const apiKey = requireArg(args, \"api-key\");\n const apiToken = requireArg(args, \"api-token\");\n const isActive = !hasFlag(args, \"inactive\");\n\n const [{ getProfile }, { encryptSecret }, { upsertVtexConnection }, { buildVtexUrls }, { validateVtexConfig }] =\n await Promise.all([\n import(\"../src/config/profile-store.js\"),\n import(\"../src/config/vtex-crypto.js\"),\n import(\"../src/config/vtex-profile-store.js\"),\n import(\"../src/config/vtex.js\"),\n import(\"../src/services/vtex/vtex-api.js\"),\n ]);\n\n const profile = await getProfile(profileId);\n if (!profile) {\n throw new Error(`Profile not found: \"${profileId}\"`);\n }\n\n const encryptedApiKey = encryptSecret(apiKey);\n const encryptedApiToken = encryptSecret(apiToken);\n const urls = buildVtexUrls(accountName);\n\n let status: \"valid\" | \"invalid\" = \"valid\";\n let lastError: string | null = null;\n let lastValidatedAt: Date | null = null;\n\n try {\n await validateVtexConfig({\n accountName,\n apiKey,\n apiToken,\n baseUrl: urls.baseUrl,\n pricingBaseUrl: urls.pricingBaseUrl,\n });\n lastValidatedAt = new Date();\n } catch (error) {\n status = \"invalid\";\n lastError = error instanceof Error ? error.message : \"VTEX validation failed\";\n lastValidatedAt = new Date();\n }\n\n const connection = await upsertVtexConnection({\n profileId,\n accountName,\n apiKeyEncrypted: encryptedApiKey,\n apiTokenEncrypted: encryptedApiToken,\n isActive,\n status,\n lastValidatedAt,\n lastError,\n });\n\n console.log(\"VTEX connection upsert OK\");\n console.log(\n JSON.stringify(\n {\n profile_id: connection.profileId,\n account_name: connection.accountName,\n is_active: connection.isActive,\n status: connection.status,\n last_validated_at: connection.lastValidatedAt?.toISOString() ?? null,\n last_error: connection.lastError,\n },\n null,\n 2\n )\n );\n}\n\nmain().catch((error: unknown) => {\n console.error(\"VTEX connection upsert failed\");\n console.error(error);\n process.exitCode = 1;\n});\n"],
5
+ "mappings": "AAAA,OAAO,aAAa;AAEpB,SAAS,SAAS,cAAc,cAAc,kBAAkB;AAEhE,eAAe,OAAO;AACpB,eAAa;AAEb,QAAM,OAAO,aAAa,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC/C,QAAM,YAAY,WAAW,MAAM,YAAY;AAC/C,QAAM,cAAc,WAAW,MAAM,cAAc;AACnD,QAAM,SAAS,WAAW,MAAM,SAAS;AACzC,QAAM,WAAW,WAAW,MAAM,WAAW;AAC7C,QAAM,WAAW,CAAC,QAAQ,MAAM,UAAU;AAE1C,QAAM,CAAC,EAAE,WAAW,GAAG,EAAE,cAAc,GAAG,EAAE,qBAAqB,GAAG,EAAE,cAAc,GAAG,EAAE,mBAAmB,CAAC,IAC3G,MAAM,QAAQ,IAAI;AAAA,IAChB,OAAO,gCAAgC;AAAA,IACvC,OAAO,8BAA8B;AAAA,IACrC,OAAO,qCAAqC;AAAA,IAC5C,OAAO,uBAAuB;AAAA,IAC9B,OAAO,kCAAkC;AAAA,EAC3C,CAAC;AAEH,QAAM,UAAU,MAAM,WAAW,SAAS;AAC1C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,uBAAuB,SAAS,GAAG;AAAA,EACrD;AAEA,QAAM,kBAAkB,cAAc,MAAM;AAC5C,QAAM,oBAAoB,cAAc,QAAQ;AAChD,QAAM,OAAO,cAAc,WAAW;AAEtC,MAAI,SAA8B;AAClC,MAAI,YAA2B;AAC/B,MAAI,kBAA+B;AAEnC,MAAI;AACF,UAAM,mBAAmB;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,KAAK;AAAA,MACd,gBAAgB,KAAK;AAAA,IACvB,CAAC;AACD,sBAAkB,oBAAI,KAAK;AAAA,EAC7B,SAAS,OAAO;AACd,aAAS;AACT,gBAAY,iBAAiB,QAAQ,MAAM,UAAU;AACrD,sBAAkB,oBAAI,KAAK;AAAA,EAC7B;AAEA,QAAM,aAAa,MAAM,qBAAqB;AAAA,IAC5C;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,UAAQ,IAAI,2BAA2B;AACvC,UAAQ;AAAA,IACN,KAAK;AAAA,MACH;AAAA,QACE,YAAY,WAAW;AAAA,QACvB,cAAc,WAAW;AAAA,QACzB,WAAW,WAAW;AAAA,QACtB,QAAQ,WAAW;AAAA,QACnB,mBAAmB,WAAW,iBAAiB,YAAY,KAAK;AAAA,QAChE,YAAY,WAAW;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,UAAmB;AAC/B,UAAQ,MAAM,+BAA+B;AAC7C,UAAQ,MAAM,KAAK;AACnB,UAAQ,WAAW;AACrB,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,55 @@
1
+ import process from "node:process";
2
+ import { loadLocalEnv, parseCliArgs, requireArg } from "./_helpers.js";
3
+ async function main() {
4
+ loadLocalEnv();
5
+ const args = parseCliArgs(process.argv.slice(2));
6
+ const profileId = requireArg(args, "profile-id");
7
+ const [{ getProfile }, { getVtexConnectionRow, markVtexConnectionValidation }, { decryptSecret }, { buildVtexUrls }, { validateVtexConfig }] = await Promise.all([
8
+ import("../src/config/profile-store.js"),
9
+ import("../src/config/vtex-profile-store.js"),
10
+ import("../src/config/vtex-crypto.js"),
11
+ import("../src/config/vtex.js"),
12
+ import("../src/services/vtex/vtex-api.js")
13
+ ]);
14
+ const profile = await getProfile(profileId);
15
+ if (!profile) {
16
+ throw new Error(`Profile not found: "${profileId}"`);
17
+ }
18
+ const connection = await getVtexConnectionRow(profileId);
19
+ if (!connection) {
20
+ throw new Error(`VTEX connection not found for profileId "${profileId}"`);
21
+ }
22
+ const urls = buildVtexUrls(connection.accountName);
23
+ try {
24
+ await validateVtexConfig({
25
+ accountName: connection.accountName,
26
+ apiKey: decryptSecret(connection.apiKeyEncrypted),
27
+ apiToken: decryptSecret(connection.apiTokenEncrypted),
28
+ baseUrl: urls.baseUrl,
29
+ pricingBaseUrl: urls.pricingBaseUrl
30
+ });
31
+ await markVtexConnectionValidation(profileId, "valid", null);
32
+ console.log("VTEX connection validation OK");
33
+ console.log(
34
+ JSON.stringify(
35
+ {
36
+ profile_id: profileId,
37
+ status: "valid",
38
+ validated_at: (/* @__PURE__ */ new Date()).toISOString()
39
+ },
40
+ null,
41
+ 2
42
+ )
43
+ );
44
+ } catch (error) {
45
+ const message = error instanceof Error ? error.message : "VTEX validation failed";
46
+ await markVtexConnectionValidation(profileId, "invalid", message);
47
+ throw new Error(message);
48
+ }
49
+ }
50
+ main().catch((error) => {
51
+ console.error("VTEX connection validation failed");
52
+ console.error(error);
53
+ process.exitCode = 1;
54
+ });
55
+ //# sourceMappingURL=admin-vtex-validate.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../scripts/admin-vtex-validate.ts"],
4
+ "sourcesContent": ["import process from \"node:process\";\n\nimport { loadLocalEnv, parseCliArgs, requireArg } from \"./_helpers.js\";\n\nasync function main() {\n loadLocalEnv();\n\n const args = parseCliArgs(process.argv.slice(2));\n const profileId = requireArg(args, \"profile-id\");\n\n const [{ getProfile }, { getVtexConnectionRow, markVtexConnectionValidation }, { decryptSecret }, { buildVtexUrls }, { validateVtexConfig }] =\n await Promise.all([\n import(\"../src/config/profile-store.js\"),\n import(\"../src/config/vtex-profile-store.js\"),\n import(\"../src/config/vtex-crypto.js\"),\n import(\"../src/config/vtex.js\"),\n import(\"../src/services/vtex/vtex-api.js\"),\n ]);\n\n const profile = await getProfile(profileId);\n if (!profile) {\n throw new Error(`Profile not found: \"${profileId}\"`);\n }\n const connection = await getVtexConnectionRow(profileId);\n\n if (!connection) {\n throw new Error(`VTEX connection not found for profileId \"${profileId}\"`);\n }\n\n const urls = buildVtexUrls(connection.accountName);\n\n try {\n await validateVtexConfig({\n accountName: connection.accountName,\n apiKey: decryptSecret(connection.apiKeyEncrypted),\n apiToken: decryptSecret(connection.apiTokenEncrypted),\n baseUrl: urls.baseUrl,\n pricingBaseUrl: urls.pricingBaseUrl,\n });\n\n await markVtexConnectionValidation(profileId, \"valid\", null);\n\n console.log(\"VTEX connection validation OK\");\n console.log(\n JSON.stringify(\n {\n profile_id: profileId,\n status: \"valid\",\n validated_at: new Date().toISOString(),\n },\n null,\n 2\n )\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : \"VTEX validation failed\";\n await markVtexConnectionValidation(profileId, \"invalid\", message);\n throw new Error(message);\n }\n}\n\nmain().catch((error: unknown) => {\n console.error(\"VTEX connection validation failed\");\n console.error(error);\n process.exitCode = 1;\n});\n"],
5
+ "mappings": "AAAA,OAAO,aAAa;AAEpB,SAAS,cAAc,cAAc,kBAAkB;AAEvD,eAAe,OAAO;AACpB,eAAa;AAEb,QAAM,OAAO,aAAa,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC/C,QAAM,YAAY,WAAW,MAAM,YAAY;AAE/C,QAAM,CAAC,EAAE,WAAW,GAAG,EAAE,sBAAsB,6BAA6B,GAAG,EAAE,cAAc,GAAG,EAAE,cAAc,GAAG,EAAE,mBAAmB,CAAC,IACzI,MAAM,QAAQ,IAAI;AAAA,IAChB,OAAO,gCAAgC;AAAA,IACvC,OAAO,qCAAqC;AAAA,IAC5C,OAAO,8BAA8B;AAAA,IACrC,OAAO,uBAAuB;AAAA,IAC9B,OAAO,kCAAkC;AAAA,EAC3C,CAAC;AAEH,QAAM,UAAU,MAAM,WAAW,SAAS;AAC1C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,uBAAuB,SAAS,GAAG;AAAA,EACrD;AACA,QAAM,aAAa,MAAM,qBAAqB,SAAS;AAEvD,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,4CAA4C,SAAS,GAAG;AAAA,EAC1E;AAEA,QAAM,OAAO,cAAc,WAAW,WAAW;AAEjD,MAAI;AACF,UAAM,mBAAmB;AAAA,MACvB,aAAa,WAAW;AAAA,MACxB,QAAQ,cAAc,WAAW,eAAe;AAAA,MAChD,UAAU,cAAc,WAAW,iBAAiB;AAAA,MACpD,SAAS,KAAK;AAAA,MACd,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAED,UAAM,6BAA6B,WAAW,SAAS,IAAI;AAE3D,YAAQ,IAAI,+BAA+B;AAC3C,YAAQ;AAAA,MACN,KAAK;AAAA,QACH;AAAA,UACE,YAAY;AAAA,UACZ,QAAQ;AAAA,UACR,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,QACvC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,UAAM,6BAA6B,WAAW,WAAW,OAAO;AAChE,UAAM,IAAI,MAAM,OAAO;AAAA,EACzB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,UAAmB;AAC/B,UAAQ,MAAM,mCAAmC;AACjD,UAAQ,MAAM,KAAK;AACnB,UAAQ,WAAW;AACrB,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,50 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { loadLocalEnv } from "./_helpers";
5
+ async function main() {
6
+ loadLocalEnv();
7
+ const { getDb, closeDb } = await import("../src/db/client.js");
8
+ const db = getDb();
9
+ const migrationsDir = path.resolve("db/migrations");
10
+ const files = (await readdir(migrationsDir)).filter((file) => file.endsWith(".sql")).sort((left, right) => left.localeCompare(right));
11
+ await db.query(`
12
+ create table if not exists schema_migrations (
13
+ filename text primary key,
14
+ applied_at timestamptz not null default now()
15
+ )
16
+ `);
17
+ const appliedResult = await db.query(
18
+ "select filename from schema_migrations"
19
+ );
20
+ const applied = new Set(appliedResult.rows.map((row) => row.filename));
21
+ const pending = files.filter((file) => !applied.has(file));
22
+ if (pending.length === 0) {
23
+ console.log("No pending migrations");
24
+ await closeDb();
25
+ return;
26
+ }
27
+ for (const file of pending) {
28
+ const sql = await readFile(path.join(migrationsDir, file), "utf8");
29
+ const client = await db.connect();
30
+ try {
31
+ await client.query("begin");
32
+ await client.query(sql);
33
+ await client.query("insert into schema_migrations (filename) values ($1)", [file]);
34
+ await client.query("commit");
35
+ console.log(`Applied migration: ${file}`);
36
+ } catch (error) {
37
+ await client.query("rollback");
38
+ throw error;
39
+ } finally {
40
+ client.release();
41
+ }
42
+ }
43
+ await closeDb();
44
+ }
45
+ main().catch((error) => {
46
+ console.error("Migration run failed");
47
+ console.error(error);
48
+ process.exitCode = 1;
49
+ });
50
+ //# sourceMappingURL=run-migrations.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../scripts/run-migrations.ts"],
4
+ "sourcesContent": ["import { readdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\nimport { loadLocalEnv } from \"./_helpers\";\n\nasync function main() {\n loadLocalEnv();\n\n const { getDb, closeDb } = await import(\"../src/db/client.js\");\n const db = getDb();\n const migrationsDir = path.resolve(\"db/migrations\");\n const files = (await readdir(migrationsDir))\n .filter((file) => file.endsWith(\".sql\"))\n .sort((left, right) => left.localeCompare(right));\n\n await db.query(`\n create table if not exists schema_migrations (\n filename text primary key,\n applied_at timestamptz not null default now()\n )\n `);\n\n const appliedResult = await db.query<{ filename: string }>(\n \"select filename from schema_migrations\"\n );\n const applied = new Set(appliedResult.rows.map((row) => row.filename));\n\n const pending = files.filter((file) => !applied.has(file));\n\n if (pending.length === 0) {\n console.log(\"No pending migrations\");\n await closeDb();\n return;\n }\n\n for (const file of pending) {\n const sql = await readFile(path.join(migrationsDir, file), \"utf8\");\n const client = await db.connect();\n\n try {\n await client.query(\"begin\");\n await client.query(sql);\n await client.query(\"insert into schema_migrations (filename) values ($1)\", [file]);\n await client.query(\"commit\");\n console.log(`Applied migration: ${file}`);\n } catch (error) {\n await client.query(\"rollback\");\n throw error;\n } finally {\n client.release();\n }\n }\n\n await closeDb();\n}\n\nmain().catch((error: unknown) => {\n console.error(\"Migration run failed\");\n console.error(error);\n process.exitCode = 1;\n});\n"],
5
+ "mappings": "AAAA,SAAS,SAAS,gBAAgB;AAClC,OAAO,UAAU;AACjB,OAAO,aAAa;AAEpB,SAAS,oBAAoB;AAE7B,eAAe,OAAO;AACpB,eAAa;AAEb,QAAM,EAAE,OAAO,QAAQ,IAAI,MAAM,OAAO,qBAAqB;AAC7D,QAAM,KAAK,MAAM;AACjB,QAAM,gBAAgB,KAAK,QAAQ,eAAe;AAClD,QAAM,SAAS,MAAM,QAAQ,aAAa,GACvC,OAAO,CAAC,SAAS,KAAK,SAAS,MAAM,CAAC,EACtC,KAAK,CAAC,MAAM,UAAU,KAAK,cAAc,KAAK,CAAC;AAElD,QAAM,GAAG,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,GAKd;AAED,QAAM,gBAAgB,MAAM,GAAG;AAAA,IAC7B;AAAA,EACF;AACA,QAAM,UAAU,IAAI,IAAI,cAAc,KAAK,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC;AAErE,QAAM,UAAU,MAAM,OAAO,CAAC,SAAS,CAAC,QAAQ,IAAI,IAAI,CAAC;AAEzD,MAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,uBAAuB;AACnC,UAAM,QAAQ;AACd;AAAA,EACF;AAEA,aAAW,QAAQ,SAAS;AAC1B,UAAM,MAAM,MAAM,SAAS,KAAK,KAAK,eAAe,IAAI,GAAG,MAAM;AACjE,UAAM,SAAS,MAAM,GAAG,QAAQ;AAEhC,QAAI;AACF,YAAM,OAAO,MAAM,OAAO;AAC1B,YAAM,OAAO,MAAM,GAAG;AACtB,YAAM,OAAO,MAAM,wDAAwD,CAAC,IAAI,CAAC;AACjF,YAAM,OAAO,MAAM,QAAQ;AAC3B,cAAQ,IAAI,sBAAsB,IAAI,EAAE;AAAA,IAC1C,SAAS,OAAO;AACd,YAAM,OAAO,MAAM,UAAU;AAC7B,YAAM;AAAA,IACR,UAAE;AACA,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,QAAQ;AAChB;AAEA,KAAK,EAAE,MAAM,CAAC,UAAmB;AAC/B,UAAQ,MAAM,sBAAsB;AACpC,UAAQ,MAAM,KAAK;AACnB,UAAQ,WAAW;AACrB,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,19 @@
1
+ import process from "node:process";
2
+ async function main() {
3
+ process.loadEnvFile?.(".env");
4
+ const { testDatabaseConnection } = await import("../src/db/client.js");
5
+ const result = await testDatabaseConnection();
6
+ console.log("Database connection OK");
7
+ console.log(`database: ${result.current_database}`);
8
+ console.log(`user: ${result.current_user}`);
9
+ console.log(`time: ${result.now.toISOString()}`);
10
+ }
11
+ main().catch((error) => {
12
+ console.error("Database connection failed");
13
+ console.error(error);
14
+ process.exitCode = 1;
15
+ }).finally(async () => {
16
+ const { closeDb } = await import("../src/db/client.js");
17
+ await closeDb();
18
+ });
19
+ //# sourceMappingURL=test-db-connection.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../scripts/test-db-connection.ts"],
4
+ "sourcesContent": ["import process from \"node:process\";\n\nasync function main() {\n // Local development helper: load values from .env when the runtime supports it.\n process.loadEnvFile?.(\".env\");\n\n const { testDatabaseConnection } = await import(\"../src/db/client.js\");\n const result = await testDatabaseConnection();\n\n console.log(\"Database connection OK\");\n console.log(`database: ${result.current_database}`);\n console.log(`user: ${result.current_user}`);\n console.log(`time: ${result.now.toISOString()}`);\n}\n\nmain()\n .catch((error: unknown) => {\n console.error(\"Database connection failed\");\n console.error(error);\n process.exitCode = 1;\n })\n .finally(async () => {\n const { closeDb } = await import(\"../src/db/client.js\");\n await closeDb();\n });\n"],
5
+ "mappings": "AAAA,OAAO,aAAa;AAEpB,eAAe,OAAO;AAEpB,UAAQ,cAAc,MAAM;AAE5B,QAAM,EAAE,uBAAuB,IAAI,MAAM,OAAO,qBAAqB;AACrE,QAAM,SAAS,MAAM,uBAAuB;AAE5C,UAAQ,IAAI,wBAAwB;AACpC,UAAQ,IAAI,aAAa,OAAO,gBAAgB,EAAE;AAClD,UAAQ,IAAI,SAAS,OAAO,YAAY,EAAE;AAC1C,UAAQ,IAAI,SAAS,OAAO,IAAI,YAAY,CAAC,EAAE;AACjD;AAEA,KAAK,EACF,MAAM,CAAC,UAAmB;AACzB,UAAQ,MAAM,4BAA4B;AAC1C,UAAQ,MAAM,KAAK;AACnB,UAAQ,WAAW;AACrB,CAAC,EACA,QAAQ,YAAY;AACnB,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,qBAAqB;AACtD,QAAM,QAAQ;AAChB,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,29 @@
1
+ import { createHmac } from "node:crypto";
2
+ function requireEnv(name) {
3
+ const value = process.env[name]?.trim();
4
+ if (!value) {
5
+ throw new Error(`Missing required environment variable: ${name}`);
6
+ }
7
+ return value;
8
+ }
9
+ function getMetaConfig() {
10
+ const appId = requireEnv("META_APP_ID");
11
+ const appSecret = requireEnv("META_APP_SECRET");
12
+ const accessToken = requireEnv("META_ACCESS_TOKEN");
13
+ const apiVersion = process.env.META_API_VERSION?.trim() || "v25.0";
14
+ return {
15
+ appId,
16
+ appSecret,
17
+ accessToken,
18
+ apiVersion,
19
+ baseUrl: `https://graph.facebook.com/${apiVersion}`
20
+ };
21
+ }
22
+ function createMetaAppSecretProof(accessToken, appSecret) {
23
+ return createHmac("sha256", appSecret).update(accessToken).digest("hex");
24
+ }
25
+ export {
26
+ createMetaAppSecretProof,
27
+ getMetaConfig
28
+ };
29
+ //# sourceMappingURL=meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/config/meta.ts"],
4
+ "sourcesContent": ["import { createHmac } from \"node:crypto\";\n\nfunction requireEnv(name: \"META_APP_ID\" | \"META_APP_SECRET\" | \"META_ACCESS_TOKEN\"): string {\n const value = process.env[name]?.trim();\n if (!value) {\n throw new Error(`Missing required environment variable: ${name}`);\n }\n\n return value;\n}\n\nexport interface MetaConfig {\n appId: string;\n appSecret: string;\n accessToken: string;\n apiVersion: string;\n baseUrl: string;\n}\n\nexport function getMetaConfig(): MetaConfig {\n const appId = requireEnv(\"META_APP_ID\");\n const appSecret = requireEnv(\"META_APP_SECRET\");\n const accessToken = requireEnv(\"META_ACCESS_TOKEN\");\n const apiVersion = process.env.META_API_VERSION?.trim() || \"v25.0\";\n\n return {\n appId,\n appSecret,\n accessToken,\n apiVersion,\n baseUrl: `https://graph.facebook.com/${apiVersion}`,\n };\n}\n\nexport function createMetaAppSecretProof(accessToken: string, appSecret: string): string {\n return createHmac(\"sha256\", appSecret).update(accessToken).digest(\"hex\");\n}\n"],
5
+ "mappings": "AAAA,SAAS,kBAAkB;AAE3B,SAAS,WAAW,MAAuE;AACzF,QAAM,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK;AACtC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAAA,EAClE;AAEA,SAAO;AACT;AAUO,SAAS,gBAA4B;AAC1C,QAAM,QAAQ,WAAW,aAAa;AACtC,QAAM,YAAY,WAAW,iBAAiB;AAC9C,QAAM,cAAc,WAAW,mBAAmB;AAClD,QAAM,aAAa,QAAQ,IAAI,kBAAkB,KAAK,KAAK;AAE3D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,8BAA8B,UAAU;AAAA,EACnD;AACF;AAEO,SAAS,yBAAyB,aAAqB,WAA2B;AACvF,SAAO,WAAW,UAAU,SAAS,EAAE,OAAO,WAAW,EAAE,OAAO,KAAK;AACzE;",
6
+ "names": []
7
+ }
@@ -0,0 +1,86 @@
1
+ import { getDb } from "../db/client.js";
2
+ function mapProfileRow(row) {
3
+ return {
4
+ id: row.id,
5
+ name: row.name,
6
+ isActive: row.is_active,
7
+ createdAt: row.created_at,
8
+ updatedAt: row.updated_at
9
+ };
10
+ }
11
+ async function getProfile(profileId) {
12
+ const result = await getDb().query(
13
+ `
14
+ select id, name, is_active, created_at, updated_at
15
+ from profiles
16
+ where id = $1
17
+ `,
18
+ [profileId]
19
+ );
20
+ const row = result.rows[0];
21
+ return row ? mapProfileRow(row) : null;
22
+ }
23
+ async function upsertProfile(input) {
24
+ const result = await getDb().query(
25
+ `
26
+ insert into profiles (id, name, is_active)
27
+ values ($1, $2, $3)
28
+ on conflict (id) do update
29
+ set
30
+ name = excluded.name,
31
+ is_active = excluded.is_active,
32
+ updated_at = now()
33
+ returning id, name, is_active, created_at, updated_at
34
+ `,
35
+ [input.id, input.name, input.isActive]
36
+ );
37
+ return mapProfileRow(result.rows[0]);
38
+ }
39
+ async function listProfiles() {
40
+ const result = await getDb().query(
41
+ `
42
+ select id, name, is_active, created_at, updated_at
43
+ from profiles
44
+ order by id asc
45
+ `
46
+ );
47
+ return result.rows.map(mapProfileRow);
48
+ }
49
+ async function deleteProfile(profileId) {
50
+ const result = await getDb().query(
51
+ `
52
+ delete from profiles
53
+ where id = $1
54
+ returning id, name, is_active, created_at, updated_at
55
+ `,
56
+ [profileId]
57
+ );
58
+ const row = result.rows[0];
59
+ if (!row) {
60
+ return null;
61
+ }
62
+ return {
63
+ id: row.id,
64
+ name: row.name,
65
+ isActive: row.is_active,
66
+ deletedAt: /* @__PURE__ */ new Date()
67
+ };
68
+ }
69
+ async function assertActiveProfile(profileId) {
70
+ const profile = await getProfile(profileId);
71
+ if (!profile) {
72
+ throw new Error(`Profile not found: "${profileId}"`);
73
+ }
74
+ if (!profile.isActive) {
75
+ throw new Error(`Profile "${profileId}" is disabled`);
76
+ }
77
+ return profile;
78
+ }
79
+ export {
80
+ assertActiveProfile,
81
+ deleteProfile,
82
+ getProfile,
83
+ listProfiles,
84
+ upsertProfile
85
+ };
86
+ //# sourceMappingURL=profile-store.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/config/profile-store.ts"],
4
+ "sourcesContent": ["import { getDb } from \"../db/client.js\";\n\nexport interface ProfileRecord {\n id: string;\n name: string;\n isActive: boolean;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface DeletedProfileRecord {\n id: string;\n name: string;\n isActive: boolean;\n deletedAt: Date;\n}\n\ninterface ProfileRow {\n id: string;\n name: string;\n is_active: boolean;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction mapProfileRow(row: ProfileRow): ProfileRecord {\n return {\n id: row.id,\n name: row.name,\n isActive: row.is_active,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport async function getProfile(profileId: string): Promise<ProfileRecord | null> {\n const result = await getDb().query<ProfileRow>(\n `\n select id, name, is_active, created_at, updated_at\n from profiles\n where id = $1\n `,\n [profileId]\n );\n\n const row = result.rows[0];\n return row ? mapProfileRow(row) : null;\n}\n\nexport async function upsertProfile(input: {\n id: string;\n name: string;\n isActive: boolean;\n}): Promise<ProfileRecord> {\n const result = await getDb().query<ProfileRow>(\n `\n insert into profiles (id, name, is_active)\n values ($1, $2, $3)\n on conflict (id) do update\n set\n name = excluded.name,\n is_active = excluded.is_active,\n updated_at = now()\n returning id, name, is_active, created_at, updated_at\n `,\n [input.id, input.name, input.isActive]\n );\n\n return mapProfileRow(result.rows[0]);\n}\n\nexport async function listProfiles(): Promise<ProfileRecord[]> {\n const result = await getDb().query<ProfileRow>(\n `\n select id, name, is_active, created_at, updated_at\n from profiles\n order by id asc\n `\n );\n\n return result.rows.map(mapProfileRow);\n}\n\nexport async function deleteProfile(profileId: string): Promise<DeletedProfileRecord | null> {\n const result = await getDb().query<ProfileRow>(\n `\n delete from profiles\n where id = $1\n returning id, name, is_active, created_at, updated_at\n `,\n [profileId]\n );\n\n const row = result.rows[0];\n if (!row) {\n return null;\n }\n\n return {\n id: row.id,\n name: row.name,\n isActive: row.is_active,\n deletedAt: new Date(),\n };\n}\n\nexport async function assertActiveProfile(profileId: string): Promise<ProfileRecord> {\n const profile = await getProfile(profileId);\n\n if (!profile) {\n throw new Error(`Profile not found: \"${profileId}\"`);\n }\n\n if (!profile.isActive) {\n throw new Error(`Profile \"${profileId}\" is disabled`);\n }\n\n return profile;\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAa;AAyBtB,SAAS,cAAc,KAAgC;AACrD,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,eAAsB,WAAW,WAAkD;AACjF,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,MAAM,OAAO,KAAK,CAAC;AACzB,SAAO,MAAM,cAAc,GAAG,IAAI;AACpC;AAEA,eAAsB,cAAc,OAIT;AACzB,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,CAAC,MAAM,IAAI,MAAM,MAAM,MAAM,QAAQ;AAAA,EACvC;AAEA,SAAO,cAAc,OAAO,KAAK,CAAC,CAAC;AACrC;AAEA,eAAsB,eAAyC;AAC7D,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAKF;AAEA,SAAO,OAAO,KAAK,IAAI,aAAa;AACtC;AAEA,eAAsB,cAAc,WAAyD;AAC3F,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,MAAM,OAAO,KAAK,CAAC;AACzB,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,UAAU,IAAI;AAAA,IACd,WAAW,oBAAI,KAAK;AAAA,EACtB;AACF;AAEA,eAAsB,oBAAoB,WAA2C;AACnF,QAAM,UAAU,MAAM,WAAW,SAAS;AAE1C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,uBAAuB,SAAS,GAAG;AAAA,EACrD;AAEA,MAAI,CAAC,QAAQ,UAAU;AACrB,UAAM,IAAI,MAAM,YAAY,SAAS,eAAe;AAAA,EACtD;AAEA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,43 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
+ const ALGORITHM = "aes-256-gcm";
3
+ const IV_LENGTH = 12;
4
+ function getEncryptionKey() {
5
+ const rawValue = process.env.DB_ENCRYPTION_KEY?.trim();
6
+ if (!rawValue) {
7
+ throw new Error(
8
+ "Missing DB_ENCRYPTION_KEY. Configure it before encrypting or decrypting persisted secrets."
9
+ );
10
+ }
11
+ const buffer = Buffer.from(rawValue, "hex");
12
+ if (buffer.length !== 32) {
13
+ throw new Error(
14
+ "Invalid DB_ENCRYPTION_KEY. Expected a 32-byte key encoded as 64 hex characters."
15
+ );
16
+ }
17
+ return buffer;
18
+ }
19
+ function encryptSecret(plainText) {
20
+ const iv = randomBytes(IV_LENGTH);
21
+ const cipher = createCipheriv(ALGORITHM, getEncryptionKey(), iv);
22
+ const encrypted = Buffer.concat([cipher.update(plainText, "utf8"), cipher.final()]);
23
+ const authTag = cipher.getAuthTag();
24
+ return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
25
+ }
26
+ function decryptSecret(payload) {
27
+ const [ivHex, authTagHex, encryptedHex] = payload.split(":");
28
+ if (!ivHex || !authTagHex || !encryptedHex) {
29
+ throw new Error("Invalid encrypted secret payload");
30
+ }
31
+ const decipher = createDecipheriv(ALGORITHM, getEncryptionKey(), Buffer.from(ivHex, "hex"));
32
+ decipher.setAuthTag(Buffer.from(authTagHex, "hex"));
33
+ const decrypted = Buffer.concat([
34
+ decipher.update(Buffer.from(encryptedHex, "hex")),
35
+ decipher.final()
36
+ ]);
37
+ return decrypted.toString("utf8");
38
+ }
39
+ export {
40
+ decryptSecret,
41
+ encryptSecret
42
+ };
43
+ //# sourceMappingURL=vtex-crypto.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/config/vtex-crypto.ts"],
4
+ "sourcesContent": ["import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst IV_LENGTH = 12;\n\nfunction getEncryptionKey(): Buffer {\n const rawValue = process.env.DB_ENCRYPTION_KEY?.trim();\n if (!rawValue) {\n throw new Error(\n \"Missing DB_ENCRYPTION_KEY. Configure it before encrypting or decrypting persisted secrets.\"\n );\n }\n\n const buffer = Buffer.from(rawValue, \"hex\");\n if (buffer.length !== 32) {\n throw new Error(\n \"Invalid DB_ENCRYPTION_KEY. Expected a 32-byte key encoded as 64 hex characters.\"\n );\n }\n\n return buffer;\n}\n\nexport function encryptSecret(plainText: string): string {\n const iv = randomBytes(IV_LENGTH);\n const cipher = createCipheriv(ALGORITHM, getEncryptionKey(), iv);\n const encrypted = Buffer.concat([cipher.update(plainText, \"utf8\"), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n return `${iv.toString(\"hex\")}:${authTag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\n}\n\nexport function decryptSecret(payload: string): string {\n const [ivHex, authTagHex, encryptedHex] = payload.split(\":\");\n if (!ivHex || !authTagHex || !encryptedHex) {\n throw new Error(\"Invalid encrypted secret payload\");\n }\n\n const decipher = createDecipheriv(ALGORITHM, getEncryptionKey(), Buffer.from(ivHex, \"hex\"));\n decipher.setAuthTag(Buffer.from(authTagHex, \"hex\"));\n\n const decrypted = Buffer.concat([\n decipher.update(Buffer.from(encryptedHex, \"hex\")),\n decipher.final(),\n ]);\n\n return decrypted.toString(\"utf8\");\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB,kBAAkB,mBAAmB;AAE9D,MAAM,YAAY;AAClB,MAAM,YAAY;AAElB,SAAS,mBAA2B;AAClC,QAAM,WAAW,QAAQ,IAAI,mBAAmB,KAAK;AACrD,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,OAAO,KAAK,UAAU,KAAK;AAC1C,MAAI,OAAO,WAAW,IAAI;AACxB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,cAAc,WAA2B;AACvD,QAAM,KAAK,YAAY,SAAS;AAChC,QAAM,SAAS,eAAe,WAAW,iBAAiB,GAAG,EAAE;AAC/D,QAAM,YAAY,OAAO,OAAO,CAAC,OAAO,OAAO,WAAW,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;AAClF,QAAM,UAAU,OAAO,WAAW;AAElC,SAAO,GAAG,GAAG,SAAS,KAAK,CAAC,IAAI,QAAQ,SAAS,KAAK,CAAC,IAAI,UAAU,SAAS,KAAK,CAAC;AACtF;AAEO,SAAS,cAAc,SAAyB;AACrD,QAAM,CAAC,OAAO,YAAY,YAAY,IAAI,QAAQ,MAAM,GAAG;AAC3D,MAAI,CAAC,SAAS,CAAC,cAAc,CAAC,cAAc;AAC1C,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAEA,QAAM,WAAW,iBAAiB,WAAW,iBAAiB,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;AAC1F,WAAS,WAAW,OAAO,KAAK,YAAY,KAAK,CAAC;AAElD,QAAM,YAAY,OAAO,OAAO;AAAA,IAC9B,SAAS,OAAO,OAAO,KAAK,cAAc,KAAK,CAAC;AAAA,IAChD,SAAS,MAAM;AAAA,EACjB,CAAC;AAED,SAAO,UAAU,SAAS,MAAM;AAClC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,132 @@
1
+ import { getDb } from "../db/client.js";
2
+ function mapVtexConnectionRow(row) {
3
+ return {
4
+ profileId: row.profile_id,
5
+ profileName: row.profile_name,
6
+ accountName: row.account_name,
7
+ apiKeyEncrypted: row.api_key_encrypted,
8
+ apiTokenEncrypted: row.api_token_encrypted,
9
+ isActive: row.is_active,
10
+ status: row.status,
11
+ lastValidatedAt: row.last_validated_at,
12
+ lastError: row.last_error,
13
+ createdAt: row.created_at,
14
+ updatedAt: row.updated_at
15
+ };
16
+ }
17
+ async function getVtexConnectionRow(profileId) {
18
+ const result = await getDb().query(
19
+ `
20
+ select
21
+ pvc.profile_id,
22
+ p.name as profile_name,
23
+ pvc.account_name,
24
+ pvc.api_key_encrypted,
25
+ pvc.api_token_encrypted,
26
+ pvc.is_active,
27
+ pvc.status,
28
+ pvc.last_validated_at,
29
+ pvc.last_error,
30
+ pvc.created_at,
31
+ pvc.updated_at
32
+ from profile_vtex_connections pvc
33
+ inner join profiles p on p.id = pvc.profile_id
34
+ where pvc.profile_id = $1
35
+ `,
36
+ [profileId]
37
+ );
38
+ const row = result.rows[0];
39
+ return row ? mapVtexConnectionRow(row) : null;
40
+ }
41
+ async function upsertVtexConnection(input) {
42
+ const result = await getDb().query(
43
+ `
44
+ insert into profile_vtex_connections (
45
+ profile_id,
46
+ account_name,
47
+ api_key_encrypted,
48
+ api_token_encrypted,
49
+ is_active,
50
+ status,
51
+ last_validated_at,
52
+ last_error
53
+ )
54
+ values ($1, $2, $3, $4, $5, $6, $7, $8)
55
+ on conflict (profile_id) do update
56
+ set
57
+ account_name = excluded.account_name,
58
+ api_key_encrypted = excluded.api_key_encrypted,
59
+ api_token_encrypted = excluded.api_token_encrypted,
60
+ is_active = excluded.is_active,
61
+ status = excluded.status,
62
+ last_validated_at = excluded.last_validated_at,
63
+ last_error = excluded.last_error,
64
+ updated_at = now()
65
+ returning
66
+ profile_id,
67
+ account_name,
68
+ api_key_encrypted,
69
+ api_token_encrypted,
70
+ is_active,
71
+ status,
72
+ last_validated_at,
73
+ last_error,
74
+ created_at,
75
+ updated_at
76
+ `,
77
+ [
78
+ input.profileId,
79
+ input.accountName,
80
+ input.apiKeyEncrypted,
81
+ input.apiTokenEncrypted,
82
+ input.isActive,
83
+ input.status,
84
+ input.lastValidatedAt ?? null,
85
+ input.lastError ?? null
86
+ ]
87
+ );
88
+ return mapVtexConnectionRow(result.rows[0]);
89
+ }
90
+ async function markVtexConnectionValidation(profileId, status, lastError) {
91
+ await getDb().query(
92
+ `
93
+ update profile_vtex_connections
94
+ set
95
+ status = $2,
96
+ last_validated_at = now(),
97
+ last_error = $3,
98
+ updated_at = now()
99
+ where profile_id = $1
100
+ `,
101
+ [profileId, status, lastError ?? null]
102
+ );
103
+ }
104
+ async function listVtexConnections() {
105
+ const result = await getDb().query(
106
+ `
107
+ select
108
+ pvc.profile_id,
109
+ p.name as profile_name,
110
+ pvc.account_name,
111
+ pvc.api_key_encrypted,
112
+ pvc.api_token_encrypted,
113
+ pvc.is_active,
114
+ pvc.status,
115
+ pvc.last_validated_at,
116
+ pvc.last_error,
117
+ pvc.created_at,
118
+ pvc.updated_at
119
+ from profile_vtex_connections pvc
120
+ inner join profiles p on p.id = pvc.profile_id
121
+ order by pvc.profile_id asc
122
+ `
123
+ );
124
+ return result.rows.map(mapVtexConnectionRow);
125
+ }
126
+ export {
127
+ getVtexConnectionRow,
128
+ listVtexConnections,
129
+ markVtexConnectionValidation,
130
+ upsertVtexConnection
131
+ };
132
+ //# sourceMappingURL=vtex-profile-store.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/config/vtex-profile-store.ts"],
4
+ "sourcesContent": ["import { getDb } from \"../db/client.js\";\n\nexport type VtexConnectionStatus = \"pending\" | \"valid\" | \"invalid\" | \"disabled\";\n\nexport interface VtexConnectionRecord {\n profileId: string;\n profileName?: string;\n accountName: string;\n apiKeyEncrypted: string;\n apiTokenEncrypted: string;\n isActive: boolean;\n status: VtexConnectionStatus;\n lastValidatedAt: Date | null;\n lastError: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface VtexConnectionRow {\n profile_id: string;\n profile_name?: string;\n account_name: string;\n api_key_encrypted: string;\n api_token_encrypted: string;\n is_active: boolean;\n status: VtexConnectionStatus;\n last_validated_at: Date | null;\n last_error: string | null;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction mapVtexConnectionRow(row: VtexConnectionRow): VtexConnectionRecord {\n return {\n profileId: row.profile_id,\n profileName: row.profile_name,\n accountName: row.account_name,\n apiKeyEncrypted: row.api_key_encrypted,\n apiTokenEncrypted: row.api_token_encrypted,\n isActive: row.is_active,\n status: row.status,\n lastValidatedAt: row.last_validated_at,\n lastError: row.last_error,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport async function getVtexConnectionRow(\n profileId: string\n): Promise<VtexConnectionRecord | null> {\n const result = await getDb().query<VtexConnectionRow>(\n `\n select\n pvc.profile_id,\n p.name as profile_name,\n pvc.account_name,\n pvc.api_key_encrypted,\n pvc.api_token_encrypted,\n pvc.is_active,\n pvc.status,\n pvc.last_validated_at,\n pvc.last_error,\n pvc.created_at,\n pvc.updated_at\n from profile_vtex_connections pvc\n inner join profiles p on p.id = pvc.profile_id\n where pvc.profile_id = $1\n `,\n [profileId]\n );\n\n const row = result.rows[0];\n return row ? mapVtexConnectionRow(row) : null;\n}\n\nexport async function upsertVtexConnection(input: {\n profileId: string;\n accountName: string;\n apiKeyEncrypted: string;\n apiTokenEncrypted: string;\n isActive: boolean;\n status: VtexConnectionStatus;\n lastValidatedAt?: Date | null;\n lastError?: string | null;\n}): Promise<VtexConnectionRecord> {\n const result = await getDb().query<VtexConnectionRow>(\n `\n insert into profile_vtex_connections (\n profile_id,\n account_name,\n api_key_encrypted,\n api_token_encrypted,\n is_active,\n status,\n last_validated_at,\n last_error\n )\n values ($1, $2, $3, $4, $5, $6, $7, $8)\n on conflict (profile_id) do update\n set\n account_name = excluded.account_name,\n api_key_encrypted = excluded.api_key_encrypted,\n api_token_encrypted = excluded.api_token_encrypted,\n is_active = excluded.is_active,\n status = excluded.status,\n last_validated_at = excluded.last_validated_at,\n last_error = excluded.last_error,\n updated_at = now()\n returning\n profile_id,\n account_name,\n api_key_encrypted,\n api_token_encrypted,\n is_active,\n status,\n last_validated_at,\n last_error,\n created_at,\n updated_at\n `,\n [\n input.profileId,\n input.accountName,\n input.apiKeyEncrypted,\n input.apiTokenEncrypted,\n input.isActive,\n input.status,\n input.lastValidatedAt ?? null,\n input.lastError ?? null,\n ]\n );\n\n return mapVtexConnectionRow(result.rows[0]);\n}\n\nexport async function markVtexConnectionValidation(\n profileId: string,\n status: VtexConnectionStatus,\n lastError?: string | null\n): Promise<void> {\n await getDb().query(\n `\n update profile_vtex_connections\n set\n status = $2,\n last_validated_at = now(),\n last_error = $3,\n updated_at = now()\n where profile_id = $1\n `,\n [profileId, status, lastError ?? null]\n );\n}\n\nexport async function listVtexConnections(): Promise<VtexConnectionRecord[]> {\n const result = await getDb().query<VtexConnectionRow>(\n `\n select\n pvc.profile_id,\n p.name as profile_name,\n pvc.account_name,\n pvc.api_key_encrypted,\n pvc.api_token_encrypted,\n pvc.is_active,\n pvc.status,\n pvc.last_validated_at,\n pvc.last_error,\n pvc.created_at,\n pvc.updated_at\n from profile_vtex_connections pvc\n inner join profiles p on p.id = pvc.profile_id\n order by pvc.profile_id asc\n `\n );\n\n return result.rows.map(mapVtexConnectionRow);\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAa;AAgCtB,SAAS,qBAAqB,KAA8C;AAC1E,SAAO;AAAA,IACL,WAAW,IAAI;AAAA,IACf,aAAa,IAAI;AAAA,IACjB,aAAa,IAAI;AAAA,IACjB,iBAAiB,IAAI;AAAA,IACrB,mBAAmB,IAAI;AAAA,IACvB,UAAU,IAAI;AAAA,IACd,QAAQ,IAAI;AAAA,IACZ,iBAAiB,IAAI;AAAA,IACrB,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,eAAsB,qBACpB,WACsC;AACtC,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAiBA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,MAAM,OAAO,KAAK,CAAC;AACzB,SAAO,MAAM,qBAAqB,GAAG,IAAI;AAC3C;AAEA,eAAsB,qBAAqB,OAST;AAChC,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAkCA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,mBAAmB;AAAA,MACzB,MAAM,aAAa;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,qBAAqB,OAAO,KAAK,CAAC,CAAC;AAC5C;AAEA,eAAsB,6BACpB,WACA,QACA,WACe;AACf,QAAM,MAAM,EAAE;AAAA,IACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,CAAC,WAAW,QAAQ,aAAa,IAAI;AAAA,EACvC;AACF;AAEA,eAAsB,sBAAuD;AAC3E,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBF;AAEA,SAAO,OAAO,KAAK,IAAI,oBAAoB;AAC7C;",
6
+ "names": []
7
+ }
@@ -1,26 +1,32 @@
1
- const REQUIRED_ENV_VARS = [
2
- "VTEX_ACCOUNT_NAME",
3
- "VTEX_API_KEY",
4
- "VTEX_API_TOKEN"
5
- ];
6
- function requireEnv(name) {
7
- const value = process.env[name]?.trim();
8
- if (!value) {
9
- throw new Error(`Missing required environment variable: ${name}`);
1
+ import { assertActiveProfile } from "./profile-store.js";
2
+ import { decryptSecret } from "./vtex-crypto.js";
3
+ import { getVtexConnectionRow } from "./vtex-profile-store.js";
4
+ function buildVtexUrls(accountName) {
5
+ return {
6
+ baseUrl: `https://${accountName}.vtexcommercestable.com.br`,
7
+ pricingBaseUrl: `https://api.vtex.com/${accountName}`
8
+ };
9
+ }
10
+ async function getVtexConfigForProfile(profileId) {
11
+ await assertActiveProfile(profileId);
12
+ const connection = await getVtexConnectionRow(profileId);
13
+ if (!connection) {
14
+ throw new Error(`VTEX connection not found for profileId "${profileId}"`);
15
+ }
16
+ if (!connection.isActive || connection.status === "disabled") {
17
+ throw new Error(`VTEX connection for profileId "${profileId}" is disabled`);
10
18
  }
11
- return value;
19
+ const urls = buildVtexUrls(connection.accountName);
20
+ return {
21
+ accountName: connection.accountName,
22
+ apiKey: decryptSecret(connection.apiKeyEncrypted),
23
+ apiToken: decryptSecret(connection.apiTokenEncrypted),
24
+ baseUrl: urls.baseUrl,
25
+ pricingBaseUrl: urls.pricingBaseUrl
26
+ };
12
27
  }
13
- const accountName = requireEnv("VTEX_ACCOUNT_NAME");
14
- const apiKey = requireEnv("VTEX_API_KEY");
15
- const apiToken = requireEnv("VTEX_API_TOKEN");
16
- const vtexConfig = {
17
- accountName,
18
- apiKey,
19
- apiToken,
20
- baseUrl: `https://${accountName}.vtexcommercestable.com.br`,
21
- pricingBaseUrl: `https://api.vtex.com/${accountName}`
22
- };
23
28
  export {
24
- vtexConfig
29
+ buildVtexUrls,
30
+ getVtexConfigForProfile
25
31
  };
26
32
  //# sourceMappingURL=vtex.js.map