@yoryoboy/bi-mcp 1.5.0 → 1.5.2

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 (86) hide show
  1. package/README.md +1 -2
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/mcp-use.json +2 -2
  4. package/dist/src/analytics/ga4-report-utils.js +20 -47
  5. package/dist/src/analytics/ga4-report-utils.js.map +2 -2
  6. package/dist/src/config/google-store.js +96 -0
  7. package/dist/src/config/google-store.js.map +7 -0
  8. package/dist/src/config/google.js +4 -15
  9. package/dist/src/config/google.js.map +2 -2
  10. package/dist/src/services/analytics/oauth.js +20 -7
  11. package/dist/src/services/analytics/oauth.js.map +2 -2
  12. package/dist/src/services/search-console/search-console-utils.js +49 -170
  13. package/dist/src/services/search-console/search-console-utils.js.map +2 -2
  14. package/dist/src/tools/analytics/attribution-gaps.js +3 -2
  15. package/dist/src/tools/analytics/attribution-gaps.js.map +2 -2
  16. package/dist/src/tools/analytics/channel-mix.js +3 -2
  17. package/dist/src/tools/analytics/channel-mix.js.map +2 -2
  18. package/dist/src/tools/analytics/ecommerce-tracking-health.js +3 -2
  19. package/dist/src/tools/analytics/ecommerce-tracking-health.js.map +2 -2
  20. package/dist/src/tools/analytics/engagement-overview.js +3 -2
  21. package/dist/src/tools/analytics/engagement-overview.js.map +2 -2
  22. package/dist/src/tools/analytics/property-info.js +3 -2
  23. package/dist/src/tools/analytics/property-info.js.map +2 -2
  24. package/dist/src/tools/analytics/revenue-by-channel.js +3 -2
  25. package/dist/src/tools/analytics/revenue-by-channel.js.map +2 -2
  26. package/dist/src/tools/analytics/revenue-overview.js +3 -2
  27. package/dist/src/tools/analytics/revenue-overview.js.map +2 -2
  28. package/dist/src/tools/analytics/revenue-trend.js +3 -2
  29. package/dist/src/tools/analytics/revenue-trend.js.map +2 -2
  30. package/dist/src/tools/analytics/source-medium-breakdown.js +3 -2
  31. package/dist/src/tools/analytics/source-medium-breakdown.js.map +2 -2
  32. package/dist/src/tools/analytics/top-landing-pages.js +3 -2
  33. package/dist/src/tools/analytics/top-landing-pages.js.map +2 -2
  34. package/dist/src/tools/config/list-profiles.js +23 -8
  35. package/dist/src/tools/config/list-profiles.js.map +2 -2
  36. package/dist/src/tools/google-ads/account-overview.js +3 -2
  37. package/dist/src/tools/google-ads/account-overview.js.map +2 -2
  38. package/dist/src/tools/google-ads/account-risks.js +3 -2
  39. package/dist/src/tools/google-ads/account-risks.js.map +2 -2
  40. package/dist/src/tools/google-ads/break-even-analysis.js +3 -2
  41. package/dist/src/tools/google-ads/break-even-analysis.js.map +2 -2
  42. package/dist/src/tools/google-ads/campaign-performance.js +3 -2
  43. package/dist/src/tools/google-ads/campaign-performance.js.map +2 -2
  44. package/dist/src/tools/google-ads/channel-mix.js +3 -2
  45. package/dist/src/tools/google-ads/channel-mix.js.map +2 -2
  46. package/dist/src/tools/google-ads/compare-accounts.js +1 -1
  47. package/dist/src/tools/google-ads/compare-accounts.js.map +2 -2
  48. package/dist/src/tools/google-ads/customer-clients.js +3 -2
  49. package/dist/src/tools/google-ads/customer-clients.js.map +2 -2
  50. package/dist/src/tools/google-ads/customer-info.js +3 -2
  51. package/dist/src/tools/google-ads/customer-info.js.map +2 -2
  52. package/dist/src/tools/google-ads/scaling-health.js +3 -2
  53. package/dist/src/tools/google-ads/scaling-health.js.map +2 -2
  54. package/dist/src/tools/google-ads/search-terms-summary.js +3 -2
  55. package/dist/src/tools/google-ads/search-terms-summary.js.map +2 -2
  56. package/dist/src/tools/google-ads/time-series.js +3 -2
  57. package/dist/src/tools/google-ads/time-series.js.map +2 -2
  58. package/dist/src/tools/search-console/country-breakdown.js +1 -1
  59. package/dist/src/tools/search-console/country-breakdown.js.map +2 -2
  60. package/dist/src/tools/search-console/device-breakdown.js +1 -1
  61. package/dist/src/tools/search-console/device-breakdown.js.map +2 -2
  62. package/dist/src/tools/search-console/high-impression-low-click-queries.js +1 -1
  63. package/dist/src/tools/search-console/high-impression-low-click-queries.js.map +2 -2
  64. package/dist/src/tools/search-console/low-ctr-opportunities.js +1 -1
  65. package/dist/src/tools/search-console/low-ctr-opportunities.js.map +2 -2
  66. package/dist/src/tools/search-console/page-performance.js +1 -1
  67. package/dist/src/tools/search-console/page-performance.js.map +2 -2
  68. package/dist/src/tools/search-console/product-demand-low-capture-queries.js +1 -1
  69. package/dist/src/tools/search-console/product-demand-low-capture-queries.js.map +2 -2
  70. package/dist/src/tools/search-console/query-page-matrix.js +1 -1
  71. package/dist/src/tools/search-console/query-page-matrix.js.map +2 -2
  72. package/dist/src/tools/search-console/query-performance.js +1 -1
  73. package/dist/src/tools/search-console/query-performance.js.map +2 -2
  74. package/dist/src/tools/search-console/quick-win-opportunities.js +1 -1
  75. package/dist/src/tools/search-console/quick-win-opportunities.js.map +2 -2
  76. package/dist/src/tools/search-console/rising-non-brand-queries.js +1 -1
  77. package/dist/src/tools/search-console/rising-non-brand-queries.js.map +2 -2
  78. package/dist/src/tools/search-console/search-performance.js +1 -1
  79. package/dist/src/tools/search-console/search-performance.js.map +2 -2
  80. package/dist/src/tools/search-console/site-context.js +2 -2
  81. package/dist/src/tools/search-console/site-context.js.map +2 -2
  82. package/dist/src/tools/search-console/visibility-declines.js +1 -1
  83. package/dist/src/tools/search-console/visibility-declines.js.map +2 -2
  84. package/dist/src/utils/google-ads.js +15 -26
  85. package/dist/src/utils/google-ads.js.map +2 -2
  86. package/package.json +1 -1
package/dist/mcp-use.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "includeInspector": false,
3
- "buildTime": "2026-05-06T22:37:42.880Z",
4
- "buildId": "b21d8e9a024e7bc1",
3
+ "buildTime": "2026-05-07T16:50:10.616Z",
4
+ "buildId": "fffa24fe0e22aa30",
5
5
  "entryPoint": "dist/index.js",
6
6
  "widgets": {}
7
7
  }
@@ -1,76 +1,58 @@
1
1
  import { getDefaultGa4PropertyId } from "../config/google.js";
2
- function resolveGa4PropertyId(propertyId) {
2
+ import { getProfileGoogleServiceMapping } from "../config/google-store.js";
3
+ async function resolveGa4PropertyId(propertyId, profileId) {
3
4
  const explicitPropertyId = propertyId?.trim();
4
- if (explicitPropertyId) {
5
- return explicitPropertyId;
5
+ if (explicitPropertyId) return explicitPropertyId;
6
+ const explicitProfileId = profileId?.trim();
7
+ if (explicitProfileId) {
8
+ const mapping = await getProfileGoogleServiceMapping(explicitProfileId);
9
+ if (mapping?.ga4PropertyId) return mapping.ga4PropertyId;
6
10
  }
7
11
  const defaultPropertyId = getDefaultGa4PropertyId();
8
- if (defaultPropertyId) {
9
- return defaultPropertyId;
10
- }
12
+ if (defaultPropertyId) return defaultPropertyId;
11
13
  throw new Error(
12
- "Missing GA4 property ID. Provide propertyId, configure GA4_PROPERTY_ID, or call ga4_list_accessible_properties to choose an accessible property."
14
+ explicitProfileId ? `Missing GA4 property ID. profileId "${explicitProfileId}" has no active GA4 mapping, and GA4_PROPERTY_ID is not configured.` : "Missing GA4 property ID. Provide propertyId, pass profileId with a configured mapping, or configure GA4_PROPERTY_ID."
13
15
  );
14
16
  }
15
17
  function parseMetricValue(value) {
16
- if (!value) {
17
- return 0;
18
- }
18
+ if (!value) return 0;
19
19
  const parsedValue = Number(value);
20
20
  return Number.isFinite(parsedValue) ? parsedValue : 0;
21
21
  }
22
22
  function getMetricValueByName(row, metricHeaders, metricName) {
23
23
  const metricIndex = metricHeaders?.findIndex((metric) => metric.name === metricName) ?? -1;
24
- if (metricIndex < 0) {
25
- return 0;
26
- }
24
+ if (metricIndex < 0) return 0;
27
25
  return parseMetricValue(row.metricValues?.[metricIndex]?.value);
28
26
  }
29
27
  function getDimensionValue(row, index = 0) {
30
28
  return row.dimensionValues?.[index]?.value ?? "";
31
29
  }
32
30
  function toPercent(numerator, denominator) {
33
- if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) {
34
- return 0;
35
- }
31
+ if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) return 0;
36
32
  return numerator / denominator * 100;
37
33
  }
38
34
  function round(value, decimals = 2) {
39
- if (!Number.isFinite(value)) {
40
- return 0;
41
- }
35
+ if (!Number.isFinite(value)) return 0;
42
36
  const factor = 10 ** decimals;
43
37
  return Math.round(value * factor) / factor;
44
38
  }
45
39
  function formatGa4Date(value) {
46
- if (!/^\d{8}$/.test(value)) {
47
- return value;
48
- }
40
+ if (!/^\d{8}$/.test(value)) return value;
49
41
  return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`;
50
42
  }
51
43
  function findPropertySummary(accountSummaries, propertyId) {
52
44
  for (const account of accountSummaries) {
53
45
  for (const property of account.propertySummaries ?? []) {
54
46
  if (property.property === `properties/${propertyId}`) {
55
- return {
56
- accountId: account.account.replace("accounts/", ""),
57
- accountName: account.displayName,
58
- propertyId,
59
- propertyName: property.displayName,
60
- propertyType: property.propertyType
61
- };
47
+ return { accountId: account.account.replace("accounts/", ""), accountName: account.displayName, propertyId, propertyName: property.displayName, propertyType: property.propertyType };
62
48
  }
63
49
  }
64
50
  }
65
51
  return void 0;
66
52
  }
67
53
  function detectSeverity(sharePercent) {
68
- if (sharePercent >= 10) {
69
- return "high";
70
- }
71
- if (sharePercent >= 3) {
72
- return "medium";
73
- }
54
+ if (sharePercent >= 10) return "high";
55
+ if (sharePercent >= 3) return "medium";
74
56
  return "low";
75
57
  }
76
58
  function isProblematicAttributionValue(value) {
@@ -78,21 +60,12 @@ function isProblematicAttributionValue(value) {
78
60
  return normalizedValue.includes("(not set)") || normalizedValue.includes("(data not available)") || normalizedValue.includes("unassigned");
79
61
  }
80
62
  function sumMetricValues(rows, metricHeaders, metricName) {
81
- return (rows ?? []).reduce((total, row) => {
82
- return total + getMetricValueByName(row, metricHeaders, metricName);
83
- }, 0);
63
+ return (rows ?? []).reduce((total, row) => total + getMetricValueByName(row, metricHeaders, metricName), 0);
84
64
  }
85
65
  function extractQuotaSnapshot(report) {
86
66
  const quota = report.propertyQuota;
87
- if (!quota) {
88
- return void 0;
89
- }
90
- return {
91
- tokens_per_day_remaining: quota.tokensPerDay?.remaining ?? 0,
92
- tokens_per_hour_remaining: quota.tokensPerHour?.remaining ?? 0,
93
- concurrent_requests_remaining: quota.concurrentRequests?.remaining ?? 0,
94
- tokens_per_project_per_hour_remaining: quota.tokensPerProjectPerHour?.remaining ?? 0
95
- };
67
+ if (!quota) return void 0;
68
+ return { tokens_per_day_remaining: quota.tokensPerDay?.remaining ?? 0, tokens_per_hour_remaining: quota.tokensPerHour?.remaining ?? 0, concurrent_requests_remaining: quota.concurrentRequests?.remaining ?? 0, tokens_per_project_per_hour_remaining: quota.tokensPerProjectPerHour?.remaining ?? 0 };
96
69
  }
97
70
  function findMetricHeader(metricHeaders, metricName) {
98
71
  return metricHeaders?.find((metric) => metric.name === metricName);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/analytics/ga4-report-utils.ts"],
4
- "sourcesContent": ["import { getDefaultGa4PropertyId } from \"../config/google.js\";\nimport type {\n Ga4AccountSummary,\n Ga4MetricHeader,\n Ga4MetricValue,\n Ga4ReportResponse,\n Ga4ReportRow,\n} from \"../services/analytics/ga4-client.js\";\n\nexport function resolveGa4PropertyId(propertyId?: string): string {\n const explicitPropertyId = propertyId?.trim();\n if (explicitPropertyId) {\n return explicitPropertyId;\n }\n\n const defaultPropertyId = getDefaultGa4PropertyId();\n if (defaultPropertyId) {\n return defaultPropertyId;\n }\n\n throw new Error(\n \"Missing GA4 property ID. Provide propertyId, configure GA4_PROPERTY_ID, or call ga4_list_accessible_properties to choose an accessible property.\"\n );\n}\n\nexport function parseMetricValue(value: string | undefined): number {\n if (!value) {\n return 0;\n }\n\n const parsedValue = Number(value);\n return Number.isFinite(parsedValue) ? parsedValue : 0;\n}\n\nexport function getMetricValueByName(\n row: Ga4ReportRow,\n metricHeaders: Ga4MetricHeader[] | undefined,\n metricName: string\n): number {\n const metricIndex = metricHeaders?.findIndex((metric) => metric.name === metricName) ?? -1;\n if (metricIndex < 0) {\n return 0;\n }\n\n return parseMetricValue(row.metricValues?.[metricIndex]?.value);\n}\n\nexport function getDimensionValue(row: Ga4ReportRow, index = 0): string {\n return row.dimensionValues?.[index]?.value ?? \"\";\n}\n\nexport function toPercent(numerator: number, denominator: number): number {\n if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) {\n return 0;\n }\n\n return (numerator / denominator) * 100;\n}\n\nexport function round(value: number, decimals = 2): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n\n const factor = 10 ** decimals;\n return Math.round(value * factor) / factor;\n}\n\nexport function formatGa4Date(value: string): string {\n if (!/^\\d{8}$/.test(value)) {\n return value;\n }\n\n return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`;\n}\n\nexport function findPropertySummary(\n accountSummaries: Ga4AccountSummary[],\n propertyId: string\n): {\n accountId: string;\n accountName: string;\n propertyId: string;\n propertyName: string;\n propertyType?: string;\n} | undefined {\n for (const account of accountSummaries) {\n for (const property of account.propertySummaries ?? []) {\n if (property.property === `properties/${propertyId}`) {\n return {\n accountId: account.account.replace(\"accounts/\", \"\"),\n accountName: account.displayName,\n propertyId,\n propertyName: property.displayName,\n propertyType: property.propertyType,\n };\n }\n }\n }\n\n return undefined;\n}\n\nexport function detectSeverity(sharePercent: number): \"low\" | \"medium\" | \"high\" {\n if (sharePercent >= 10) {\n return \"high\";\n }\n\n if (sharePercent >= 3) {\n return \"medium\";\n }\n\n return \"low\";\n}\n\nexport function isProblematicAttributionValue(value: string): boolean {\n const normalizedValue = value.toLowerCase();\n\n return (\n normalizedValue.includes(\"(not set)\") ||\n normalizedValue.includes(\"(data not available)\") ||\n normalizedValue.includes(\"unassigned\")\n );\n}\n\nexport function sumMetricValues(\n rows: Ga4ReportRow[] | undefined,\n metricHeaders: Ga4MetricHeader[] | undefined,\n metricName: string\n): number {\n return (rows ?? []).reduce((total, row) => {\n return total + getMetricValueByName(row, metricHeaders, metricName);\n }, 0);\n}\n\nexport function extractQuotaSnapshot(report: Ga4ReportResponse): Record<string, number> | undefined {\n const quota = report.propertyQuota;\n if (!quota) {\n return undefined;\n }\n\n return {\n tokens_per_day_remaining: quota.tokensPerDay?.remaining ?? 0,\n tokens_per_hour_remaining: quota.tokensPerHour?.remaining ?? 0,\n concurrent_requests_remaining: quota.concurrentRequests?.remaining ?? 0,\n tokens_per_project_per_hour_remaining: quota.tokensPerProjectPerHour?.remaining ?? 0,\n };\n}\n\nexport function findMetricHeader(\n metricHeaders: Ga4MetricHeader[] | undefined,\n metricName: string\n): Ga4MetricHeader | undefined {\n return metricHeaders?.find((metric) => metric.name === metricName);\n}\n\nexport function getFirstMetricValue(\n metricValues: Ga4MetricValue[] | undefined,\n index: number\n): number {\n return parseMetricValue(metricValues?.[index]?.value);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { getDefaultGa4PropertyId } from '../config/google.js';\nimport { getProfileGoogleServiceMapping } from '../config/google-store.js';\nimport type { Ga4AccountSummary, Ga4MetricHeader, Ga4MetricValue, Ga4ReportResponse, Ga4ReportRow } from '../services/analytics/ga4-client.js';\n\nexport async function resolveGa4PropertyId(propertyId?: string, profileId?: string): Promise<string> {\n const explicitPropertyId = propertyId?.trim();\n if (explicitPropertyId) return explicitPropertyId;\n\n const explicitProfileId = profileId?.trim();\n if (explicitProfileId) {\n const mapping = await getProfileGoogleServiceMapping(explicitProfileId);\n if (mapping?.ga4PropertyId) return mapping.ga4PropertyId;\n }\n\n const defaultPropertyId = getDefaultGa4PropertyId();\n if (defaultPropertyId) return defaultPropertyId;\n\n throw new Error(\n explicitProfileId\n ? `Missing GA4 property ID. profileId \"${explicitProfileId}\" has no active GA4 mapping, and GA4_PROPERTY_ID is not configured.`\n : 'Missing GA4 property ID. Provide propertyId, pass profileId with a configured mapping, or configure GA4_PROPERTY_ID.'\n );\n}\n\nexport function parseMetricValue(value: string | undefined): number { if (!value) return 0; const parsedValue = Number(value); return Number.isFinite(parsedValue) ? parsedValue : 0; }\nexport function getMetricValueByName(row: Ga4ReportRow, metricHeaders: Ga4MetricHeader[] | undefined, metricName: string): number { const metricIndex = metricHeaders?.findIndex((metric) => metric.name === metricName) ?? -1; if (metricIndex < 0) return 0; return parseMetricValue(row.metricValues?.[metricIndex]?.value); }\nexport function getDimensionValue(row: Ga4ReportRow, index = 0): string { return row.dimensionValues?.[index]?.value ?? ''; }\nexport function toPercent(numerator: number, denominator: number): number { if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) return 0; return (numerator / denominator) * 100; }\nexport function round(value: number, decimals = 2): number { if (!Number.isFinite(value)) return 0; const factor = 10 ** decimals; return Math.round(value * factor) / factor; }\nexport function formatGa4Date(value: string): string { if (!/^\\d{8}$/.test(value)) return value; return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`; }\nexport function findPropertySummary(accountSummaries: Ga4AccountSummary[], propertyId: string) { for (const account of accountSummaries) { for (const property of account.propertySummaries ?? []) { if (property.property === `properties/${propertyId}`) { return { accountId: account.account.replace('accounts/', ''), accountName: account.displayName, propertyId, propertyName: property.displayName, propertyType: property.propertyType }; } } } return undefined; }\nexport function detectSeverity(sharePercent: number): 'low' | 'medium' | 'high' { if (sharePercent >= 10) return 'high'; if (sharePercent >= 3) return 'medium'; return 'low'; }\nexport function isProblematicAttributionValue(value: string): boolean { const normalizedValue = value.toLowerCase(); return normalizedValue.includes('(not set)') || normalizedValue.includes('(data not available)') || normalizedValue.includes('unassigned'); }\nexport function sumMetricValues(rows: Ga4ReportRow[] | undefined, metricHeaders: Ga4MetricHeader[] | undefined, metricName: string): number { return (rows ?? []).reduce((total, row) => total + getMetricValueByName(row, metricHeaders, metricName), 0); }\nexport function extractQuotaSnapshot(report: Ga4ReportResponse): Record<string, number> | undefined { const quota = report.propertyQuota; if (!quota) return undefined; return { tokens_per_day_remaining: quota.tokensPerDay?.remaining ?? 0, tokens_per_hour_remaining: quota.tokensPerHour?.remaining ?? 0, concurrent_requests_remaining: quota.concurrentRequests?.remaining ?? 0, tokens_per_project_per_hour_remaining: quota.tokensPerProjectPerHour?.remaining ?? 0 }; }\nexport function findMetricHeader(metricHeaders: Ga4MetricHeader[] | undefined, metricName: string): Ga4MetricHeader | undefined { return metricHeaders?.find((metric) => metric.name === metricName); }\nexport function getFirstMetricValue(metricValues: Ga4MetricValue[] | undefined, index: number): number { return parseMetricValue(metricValues?.[index]?.value); }\n"],
5
+ "mappings": "AAAA,SAAS,+BAA+B;AACxC,SAAS,sCAAsC;AAG/C,eAAsB,qBAAqB,YAAqB,WAAqC;AACnG,QAAM,qBAAqB,YAAY,KAAK;AAC5C,MAAI,mBAAoB,QAAO;AAE/B,QAAM,oBAAoB,WAAW,KAAK;AAC1C,MAAI,mBAAmB;AACrB,UAAM,UAAU,MAAM,+BAA+B,iBAAiB;AACtE,QAAI,SAAS,cAAe,QAAO,QAAQ;AAAA,EAC7C;AAEA,QAAM,oBAAoB,wBAAwB;AAClD,MAAI,kBAAmB,QAAO;AAE9B,QAAM,IAAI;AAAA,IACR,oBACI,uCAAuC,iBAAiB,wEACxD;AAAA,EACN;AACF;AAEO,SAAS,iBAAiB,OAAmC;AAAE,MAAI,CAAC,MAAO,QAAO;AAAG,QAAM,cAAc,OAAO,KAAK;AAAG,SAAO,OAAO,SAAS,WAAW,IAAI,cAAc;AAAG;AAC/K,SAAS,qBAAqB,KAAmB,eAA8C,YAA4B;AAAE,QAAM,cAAc,eAAe,UAAU,CAAC,WAAW,OAAO,SAAS,UAAU,KAAK;AAAI,MAAI,cAAc,EAAG,QAAO;AAAG,SAAO,iBAAiB,IAAI,eAAe,WAAW,GAAG,KAAK;AAAG;AACzT,SAAS,kBAAkB,KAAmB,QAAQ,GAAW;AAAE,SAAO,IAAI,kBAAkB,KAAK,GAAG,SAAS;AAAI;AACrH,SAAS,UAAU,WAAmB,aAA6B;AAAE,MAAI,CAAC,OAAO,SAAS,SAAS,KAAK,CAAC,OAAO,SAAS,WAAW,KAAK,eAAe,EAAG,QAAO;AAAG,SAAQ,YAAY,cAAe;AAAK;AAC7M,SAAS,MAAM,OAAe,WAAW,GAAW;AAAE,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AAAG,QAAM,SAAS,MAAM;AAAU,SAAO,KAAK,MAAM,QAAQ,MAAM,IAAI;AAAQ;AACxK,SAAS,cAAc,OAAuB;AAAE,MAAI,CAAC,UAAU,KAAK,KAAK,EAAG,QAAO;AAAO,SAAO,GAAG,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,MAAM,GAAG,CAAC,CAAC;AAAI;AACnK,SAAS,oBAAoB,kBAAuC,YAAoB;AAAE,aAAW,WAAW,kBAAkB;AAAE,eAAW,YAAY,QAAQ,qBAAqB,CAAC,GAAG;AAAE,UAAI,SAAS,aAAa,cAAc,UAAU,IAAI;AAAE,eAAO,EAAE,WAAW,QAAQ,QAAQ,QAAQ,aAAa,EAAE,GAAG,aAAa,QAAQ,aAAa,YAAY,cAAc,SAAS,aAAa,cAAc,SAAS,aAAa;AAAA,MAAG;AAAA,IAAE;AAAA,EAAE;AAAE,SAAO;AAAW;AACrc,SAAS,eAAe,cAAiD;AAAE,MAAI,gBAAgB,GAAI,QAAO;AAAQ,MAAI,gBAAgB,EAAG,QAAO;AAAU,SAAO;AAAO;AACxK,SAAS,8BAA8B,OAAwB;AAAE,QAAM,kBAAkB,MAAM,YAAY;AAAG,SAAO,gBAAgB,SAAS,WAAW,KAAK,gBAAgB,SAAS,sBAAsB,KAAK,gBAAgB,SAAS,YAAY;AAAG;AAC1P,SAAS,gBAAgB,MAAkC,eAA8C,YAA4B;AAAE,UAAQ,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,QAAQ,QAAQ,qBAAqB,KAAK,eAAe,UAAU,GAAG,CAAC;AAAG;AACpP,SAAS,qBAAqB,QAA+D;AAAE,QAAM,QAAQ,OAAO;AAAe,MAAI,CAAC,MAAO,QAAO;AAAW,SAAO,EAAE,0BAA0B,MAAM,cAAc,aAAa,GAAG,2BAA2B,MAAM,eAAe,aAAa,GAAG,+BAA+B,MAAM,oBAAoB,aAAa,GAAG,uCAAuC,MAAM,yBAAyB,aAAa,EAAE;AAAG;AACzc,SAAS,iBAAiB,eAA8C,YAAiD;AAAE,SAAO,eAAe,KAAK,CAAC,WAAW,OAAO,SAAS,UAAU;AAAG;AAC/L,SAAS,oBAAoB,cAA4C,OAAuB;AAAE,SAAO,iBAAiB,eAAe,KAAK,GAAG,KAAK;AAAG;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,96 @@
1
+ import { getDb } from "../db/client.js";
2
+ import { decryptSecret } from "./vtex-crypto.js";
3
+ function isMissingRelationError(error) {
4
+ return error instanceof Error && /does not exist/i.test(error.message);
5
+ }
6
+ function mapConnection(row) {
7
+ return {
8
+ id: row.id,
9
+ encryptedRefreshToken: row.encrypted_refresh_token,
10
+ scopes: Array.isArray(row.scopes) ? row.scopes : [],
11
+ status: row.status,
12
+ googleAccountEmail: row.google_account_email,
13
+ lastValidatedAt: row.last_validated_at,
14
+ lastError: row.last_error
15
+ };
16
+ }
17
+ function mapMapping(row) {
18
+ return {
19
+ profileId: row.profile_id,
20
+ googleAdsCustomerId: row.google_ads_customer_id,
21
+ googleAdsLabel: row.google_ads_label,
22
+ googleAdsStatus: row.google_ads_status,
23
+ googleAdsLastValidatedAt: row.google_ads_last_validated_at,
24
+ googleAdsLastError: row.google_ads_last_error,
25
+ ga4PropertyId: row.ga4_property_id,
26
+ ga4PropertyLabel: row.ga4_property_label,
27
+ ga4Status: row.ga4_status,
28
+ ga4LastValidatedAt: row.ga4_last_validated_at,
29
+ ga4LastError: row.ga4_last_error,
30
+ searchConsoleSiteUrl: row.search_console_site_url,
31
+ searchConsoleLabel: row.search_console_label,
32
+ searchConsoleStatus: row.search_console_status,
33
+ searchConsoleLastValidatedAt: row.search_console_last_validated_at,
34
+ searchConsoleLastError: row.search_console_last_error,
35
+ updatedAt: row.updated_at
36
+ };
37
+ }
38
+ async function getGoogleOAuthConnection() {
39
+ try {
40
+ const result = await getDb().query(
41
+ `select id, encrypted_refresh_token, scopes, status, google_account_email, last_validated_at, last_error
42
+ from google_oauth_connections
43
+ where id = 'global'`
44
+ );
45
+ const row = result.rows[0];
46
+ return row ? mapConnection(row) : null;
47
+ } catch (error) {
48
+ if (isMissingRelationError(error)) return null;
49
+ throw error;
50
+ }
51
+ }
52
+ async function getActiveGoogleRefreshTokenFromStore() {
53
+ const connection = await getGoogleOAuthConnection();
54
+ if (!connection || connection.status !== "active" || !connection.encryptedRefreshToken) return null;
55
+ return decryptSecret(connection.encryptedRefreshToken);
56
+ }
57
+ async function getProfileGoogleServiceMapping(profileId) {
58
+ try {
59
+ const result = await getDb().query(
60
+ `select profile_id, google_ads_customer_id, google_ads_label, google_ads_status, google_ads_last_validated_at, google_ads_last_error,
61
+ ga4_property_id, ga4_property_label, ga4_status, ga4_last_validated_at, ga4_last_error,
62
+ search_console_site_url, search_console_label, search_console_status, search_console_last_validated_at, search_console_last_error,
63
+ updated_at
64
+ from profile_google_service_mappings
65
+ where profile_id = $1`,
66
+ [profileId]
67
+ );
68
+ const row = result.rows[0];
69
+ return row ? mapMapping(row) : null;
70
+ } catch (error) {
71
+ if (isMissingRelationError(error)) return null;
72
+ throw error;
73
+ }
74
+ }
75
+ async function listProfileGoogleServiceMappings() {
76
+ try {
77
+ const result = await getDb().query(
78
+ `select profile_id, google_ads_customer_id, google_ads_label, google_ads_status, google_ads_last_validated_at, google_ads_last_error,
79
+ ga4_property_id, ga4_property_label, ga4_status, ga4_last_validated_at, ga4_last_error,
80
+ search_console_site_url, search_console_label, search_console_status, search_console_last_validated_at, search_console_last_error,
81
+ updated_at
82
+ from profile_google_service_mappings`
83
+ );
84
+ return result.rows.map(mapMapping);
85
+ } catch (error) {
86
+ if (isMissingRelationError(error)) return [];
87
+ throw error;
88
+ }
89
+ }
90
+ export {
91
+ getActiveGoogleRefreshTokenFromStore,
92
+ getGoogleOAuthConnection,
93
+ getProfileGoogleServiceMapping,
94
+ listProfileGoogleServiceMappings
95
+ };
96
+ //# sourceMappingURL=google-store.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/config/google-store.ts"],
4
+ "sourcesContent": ["import { getDb } from '../db/client.js';\nimport { decryptSecret } from './vtex-crypto.js';\n\nexport type GoogleServiceStatus = 'active' | 'disabled' | 'expired' | 'error' | 'pending';\n\nexport interface GoogleOAuthConnectionRecord {\n id: string;\n encryptedRefreshToken: string | null;\n scopes: string[];\n status: GoogleServiceStatus;\n googleAccountEmail: string | null;\n lastValidatedAt: Date | null;\n lastError: string | null;\n}\n\nexport interface ProfileGoogleServiceMappingRecord {\n profileId: string;\n googleAdsCustomerId: string | null;\n googleAdsLabel: string | null;\n googleAdsStatus: GoogleServiceStatus;\n googleAdsLastValidatedAt: Date | null;\n googleAdsLastError: string | null;\n ga4PropertyId: string | null;\n ga4PropertyLabel: string | null;\n ga4Status: GoogleServiceStatus;\n ga4LastValidatedAt: Date | null;\n ga4LastError: string | null;\n searchConsoleSiteUrl: string | null;\n searchConsoleLabel: string | null;\n searchConsoleStatus: GoogleServiceStatus;\n searchConsoleLastValidatedAt: Date | null;\n searchConsoleLastError: string | null;\n updatedAt: Date | null;\n}\n\ninterface GoogleOAuthConnectionRow {\n id: string;\n encrypted_refresh_token: string | null;\n scopes: string[] | null;\n status: GoogleServiceStatus;\n google_account_email: string | null;\n last_validated_at: Date | null;\n last_error: string | null;\n}\n\ninterface ProfileGoogleServiceMappingRow {\n profile_id: string;\n google_ads_customer_id: string | null;\n google_ads_label: string | null;\n google_ads_status: GoogleServiceStatus;\n google_ads_last_validated_at: Date | null;\n google_ads_last_error: string | null;\n ga4_property_id: string | null;\n ga4_property_label: string | null;\n ga4_status: GoogleServiceStatus;\n ga4_last_validated_at: Date | null;\n ga4_last_error: string | null;\n search_console_site_url: string | null;\n search_console_label: string | null;\n search_console_status: GoogleServiceStatus;\n search_console_last_validated_at: Date | null;\n search_console_last_error: string | null;\n updated_at: Date | null;\n}\n\nfunction isMissingRelationError(error: unknown): boolean {\n return error instanceof Error && /does not exist/i.test(error.message);\n}\n\nfunction mapConnection(row: GoogleOAuthConnectionRow): GoogleOAuthConnectionRecord {\n return {\n id: row.id,\n encryptedRefreshToken: row.encrypted_refresh_token,\n scopes: Array.isArray(row.scopes) ? row.scopes : [],\n status: row.status,\n googleAccountEmail: row.google_account_email,\n lastValidatedAt: row.last_validated_at,\n lastError: row.last_error,\n };\n}\n\nfunction mapMapping(row: ProfileGoogleServiceMappingRow): ProfileGoogleServiceMappingRecord {\n return {\n profileId: row.profile_id,\n googleAdsCustomerId: row.google_ads_customer_id,\n googleAdsLabel: row.google_ads_label,\n googleAdsStatus: row.google_ads_status,\n googleAdsLastValidatedAt: row.google_ads_last_validated_at,\n googleAdsLastError: row.google_ads_last_error,\n ga4PropertyId: row.ga4_property_id,\n ga4PropertyLabel: row.ga4_property_label,\n ga4Status: row.ga4_status,\n ga4LastValidatedAt: row.ga4_last_validated_at,\n ga4LastError: row.ga4_last_error,\n searchConsoleSiteUrl: row.search_console_site_url,\n searchConsoleLabel: row.search_console_label,\n searchConsoleStatus: row.search_console_status,\n searchConsoleLastValidatedAt: row.search_console_last_validated_at,\n searchConsoleLastError: row.search_console_last_error,\n updatedAt: row.updated_at,\n };\n}\n\nexport async function getGoogleOAuthConnection(): Promise<GoogleOAuthConnectionRecord | null> {\n try {\n const result = await getDb().query<GoogleOAuthConnectionRow>(\n `select id, encrypted_refresh_token, scopes, status, google_account_email, last_validated_at, last_error\n from google_oauth_connections\n where id = 'global'`\n );\n const row = result.rows[0];\n return row ? mapConnection(row) : null;\n } catch (error) {\n if (isMissingRelationError(error)) return null;\n throw error;\n }\n}\n\nexport async function getActiveGoogleRefreshTokenFromStore(): Promise<string | null> {\n const connection = await getGoogleOAuthConnection();\n if (!connection || connection.status !== 'active' || !connection.encryptedRefreshToken) return null;\n return decryptSecret(connection.encryptedRefreshToken);\n}\n\nexport async function getProfileGoogleServiceMapping(profileId: string): Promise<ProfileGoogleServiceMappingRecord | null> {\n try {\n const result = await getDb().query<ProfileGoogleServiceMappingRow>(\n `select profile_id, google_ads_customer_id, google_ads_label, google_ads_status, google_ads_last_validated_at, google_ads_last_error,\n ga4_property_id, ga4_property_label, ga4_status, ga4_last_validated_at, ga4_last_error,\n search_console_site_url, search_console_label, search_console_status, search_console_last_validated_at, search_console_last_error,\n updated_at\n from profile_google_service_mappings\n where profile_id = $1`,\n [profileId]\n );\n const row = result.rows[0];\n return row ? mapMapping(row) : null;\n } catch (error) {\n if (isMissingRelationError(error)) return null;\n throw error;\n }\n}\n\nexport async function listProfileGoogleServiceMappings(): Promise<ProfileGoogleServiceMappingRecord[]> {\n try {\n const result = await getDb().query<ProfileGoogleServiceMappingRow>(\n `select profile_id, google_ads_customer_id, google_ads_label, google_ads_status, google_ads_last_validated_at, google_ads_last_error,\n ga4_property_id, ga4_property_label, ga4_status, ga4_last_validated_at, ga4_last_error,\n search_console_site_url, search_console_label, search_console_status, search_console_last_validated_at, search_console_last_error,\n updated_at\n from profile_google_service_mappings`\n );\n return result.rows.map(mapMapping);\n } catch (error) {\n if (isMissingRelationError(error)) return [];\n throw error;\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAa;AACtB,SAAS,qBAAqB;AAgE9B,SAAS,uBAAuB,OAAyB;AACvD,SAAO,iBAAiB,SAAS,kBAAkB,KAAK,MAAM,OAAO;AACvE;AAEA,SAAS,cAAc,KAA4D;AACjF,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,uBAAuB,IAAI;AAAA,IAC3B,QAAQ,MAAM,QAAQ,IAAI,MAAM,IAAI,IAAI,SAAS,CAAC;AAAA,IAClD,QAAQ,IAAI;AAAA,IACZ,oBAAoB,IAAI;AAAA,IACxB,iBAAiB,IAAI;AAAA,IACrB,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,SAAS,WAAW,KAAwE;AAC1F,SAAO;AAAA,IACL,WAAW,IAAI;AAAA,IACf,qBAAqB,IAAI;AAAA,IACzB,gBAAgB,IAAI;AAAA,IACpB,iBAAiB,IAAI;AAAA,IACrB,0BAA0B,IAAI;AAAA,IAC9B,oBAAoB,IAAI;AAAA,IACxB,eAAe,IAAI;AAAA,IACnB,kBAAkB,IAAI;AAAA,IACtB,WAAW,IAAI;AAAA,IACf,oBAAoB,IAAI;AAAA,IACxB,cAAc,IAAI;AAAA,IAClB,sBAAsB,IAAI;AAAA,IAC1B,oBAAoB,IAAI;AAAA,IACxB,qBAAqB,IAAI;AAAA,IACzB,8BAA8B,IAAI;AAAA,IAClC,wBAAwB,IAAI;AAAA,IAC5B,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,eAAsB,2BAAwE;AAC5F,MAAI;AACF,UAAM,SAAS,MAAM,MAAM,EAAE;AAAA,MAC3B;AAAA;AAAA;AAAA,IAGF;AACA,UAAM,MAAM,OAAO,KAAK,CAAC;AACzB,WAAO,MAAM,cAAc,GAAG,IAAI;AAAA,EACpC,SAAS,OAAO;AACd,QAAI,uBAAuB,KAAK,EAAG,QAAO;AAC1C,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,uCAA+D;AACnF,QAAM,aAAa,MAAM,yBAAyB;AAClD,MAAI,CAAC,cAAc,WAAW,WAAW,YAAY,CAAC,WAAW,sBAAuB,QAAO;AAC/F,SAAO,cAAc,WAAW,qBAAqB;AACvD;AAEA,eAAsB,+BAA+B,WAAsE;AACzH,MAAI;AACF,UAAM,SAAS,MAAM,MAAM,EAAE;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,CAAC,SAAS;AAAA,IACZ;AACA,UAAM,MAAM,OAAO,KAAK,CAAC;AACzB,WAAO,MAAM,WAAW,GAAG,IAAI;AAAA,EACjC,SAAS,OAAO;AACd,QAAI,uBAAuB,KAAK,EAAG,QAAO;AAC1C,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,mCAAiF;AACrG,MAAI;AACF,UAAM,SAAS,MAAM,MAAM,EAAE;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF;AACA,WAAO,OAAO,KAAK,IAAI,UAAU;AAAA,EACnC,SAAS,OAAO;AACd,QAAI,uBAAuB,KAAK,EAAG,QAAO,CAAC;AAC3C,UAAM;AAAA,EACR;AACF;",
6
+ "names": []
7
+ }
@@ -1,19 +1,12 @@
1
- const REQUIRED_ENV_VARS = [
2
- "GOOGLE_OAUTH_CLIENT_ID",
3
- "GOOGLE_OAUTH_CLIENT_SECRET",
4
- "GOOGLE_OAUTH_REFRESH_TOKEN"
5
- ];
1
+ const REQUIRED_ENV_VARS = ["GOOGLE_OAUTH_CLIENT_ID", "GOOGLE_OAUTH_CLIENT_SECRET"];
6
2
  function requireEnv(name) {
7
3
  const value = process.env[name]?.trim();
8
- if (!value) {
9
- throw new Error(`Missing required environment variable: ${name}`);
10
- }
4
+ if (!value) throw new Error(`Missing required environment variable: ${name}`);
11
5
  return value;
12
6
  }
13
7
  const googleOAuthConfig = {
14
8
  clientId: requireEnv("GOOGLE_OAUTH_CLIENT_ID"),
15
- clientSecret: requireEnv("GOOGLE_OAUTH_CLIENT_SECRET"),
16
- refreshToken: requireEnv("GOOGLE_OAUTH_REFRESH_TOKEN")
9
+ clientSecret: requireEnv("GOOGLE_OAUTH_CLIENT_SECRET")
17
10
  };
18
11
  function getDefaultGa4PropertyId() {
19
12
  const value = process.env.GA4_PROPERTY_ID?.trim();
@@ -21,11 +14,7 @@ function getDefaultGa4PropertyId() {
21
14
  }
22
15
  function getGoogleAdsDeveloperToken() {
23
16
  const value = process.env.GOOGLE_ADS_DEVELOPER_TOKEN?.trim();
24
- if (!value) {
25
- throw new Error(
26
- "Missing Google Ads developer token. Configure GOOGLE_ADS_DEVELOPER_TOKEN."
27
- );
28
- }
17
+ if (!value) throw new Error("Missing Google Ads developer token. Configure GOOGLE_ADS_DEVELOPER_TOKEN.");
29
18
  return value;
30
19
  }
31
20
  function getDefaultGoogleAdsLoginCustomerId() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/google.ts"],
4
- "sourcesContent": ["const REQUIRED_ENV_VARS = [\n \"GOOGLE_OAUTH_CLIENT_ID\",\n \"GOOGLE_OAUTH_CLIENT_SECRET\",\n \"GOOGLE_OAUTH_REFRESH_TOKEN\",\n] as const;\n\ntype RequiredEnvVar = (typeof REQUIRED_ENV_VARS)[number];\n\nfunction requireEnv(name: RequiredEnvVar): 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 GoogleOAuthConfig {\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n}\n\nexport const googleOAuthConfig: GoogleOAuthConfig = {\n clientId: requireEnv(\"GOOGLE_OAUTH_CLIENT_ID\"),\n clientSecret: requireEnv(\"GOOGLE_OAUTH_CLIENT_SECRET\"),\n refreshToken: requireEnv(\"GOOGLE_OAUTH_REFRESH_TOKEN\"),\n};\n\nexport function getDefaultGa4PropertyId(): string | undefined {\n const value = process.env.GA4_PROPERTY_ID?.trim();\n return value ? value : undefined;\n}\n\nexport function getGoogleAdsDeveloperToken(): string {\n const value = process.env.GOOGLE_ADS_DEVELOPER_TOKEN?.trim();\n if (!value) {\n throw new Error(\n \"Missing Google Ads developer token. Configure GOOGLE_ADS_DEVELOPER_TOKEN.\"\n );\n }\n\n return value;\n}\n\nexport function getDefaultGoogleAdsLoginCustomerId(): string | undefined {\n const value = process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID?.trim();\n return value ? value.replace(/-/g, \"\") : undefined;\n}\n"],
5
- "mappings": "AAAA,MAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACF;AAIA,SAAS,WAAW,MAA8B;AAChD,QAAM,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK;AACtC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAAA,EAClE;AAEA,SAAO;AACT;AAQO,MAAM,oBAAuC;AAAA,EAClD,UAAU,WAAW,wBAAwB;AAAA,EAC7C,cAAc,WAAW,4BAA4B;AAAA,EACrD,cAAc,WAAW,4BAA4B;AACvD;AAEO,SAAS,0BAA8C;AAC5D,QAAM,QAAQ,QAAQ,IAAI,iBAAiB,KAAK;AAChD,SAAO,QAAQ,QAAQ;AACzB;AAEO,SAAS,6BAAqC;AACnD,QAAM,QAAQ,QAAQ,IAAI,4BAA4B,KAAK;AAC3D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,qCAAyD;AACvE,QAAM,QAAQ,QAAQ,IAAI,8BAA8B,KAAK;AAC7D,SAAO,QAAQ,MAAM,QAAQ,MAAM,EAAE,IAAI;AAC3C;",
4
+ "sourcesContent": ["const REQUIRED_ENV_VARS = ['GOOGLE_OAUTH_CLIENT_ID', 'GOOGLE_OAUTH_CLIENT_SECRET'] as const;\n\ntype RequiredEnvVar = (typeof REQUIRED_ENV_VARS)[number];\n\nfunction requireEnv(name: RequiredEnvVar): string {\n const value = process.env[name]?.trim();\n if (!value) throw new Error(`Missing required environment variable: ${name}`);\n return value;\n}\n\nexport interface GoogleOAuthClientConfig {\n clientId: string;\n clientSecret: string;\n}\n\nexport const googleOAuthConfig: GoogleOAuthClientConfig = {\n clientId: requireEnv('GOOGLE_OAUTH_CLIENT_ID'),\n clientSecret: requireEnv('GOOGLE_OAUTH_CLIENT_SECRET'),\n};\n\n\nexport function getDefaultGa4PropertyId(): string | undefined {\n const value = process.env.GA4_PROPERTY_ID?.trim();\n return value ? value : undefined;\n}\n\nexport function getGoogleAdsDeveloperToken(): string {\n const value = process.env.GOOGLE_ADS_DEVELOPER_TOKEN?.trim();\n if (!value) throw new Error('Missing Google Ads developer token. Configure GOOGLE_ADS_DEVELOPER_TOKEN.');\n return value;\n}\n\nexport function getDefaultGoogleAdsLoginCustomerId(): string | undefined {\n const value = process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID?.trim();\n return value ? value.replace(/-/g, '') : undefined;\n}\n"],
5
+ "mappings": "AAAA,MAAM,oBAAoB,CAAC,0BAA0B,4BAA4B;AAIjF,SAAS,WAAW,MAA8B;AAChD,QAAM,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK;AACtC,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAC5E,SAAO;AACT;AAOO,MAAM,oBAA6C;AAAA,EACxD,UAAU,WAAW,wBAAwB;AAAA,EAC7C,cAAc,WAAW,4BAA4B;AACvD;AAGO,SAAS,0BAA8C;AAC5D,QAAM,QAAQ,QAAQ,IAAI,iBAAiB,KAAK;AAChD,SAAO,QAAQ,QAAQ;AACzB;AAEO,SAAS,6BAAqC;AACnD,QAAM,QAAQ,QAAQ,IAAI,4BAA4B,KAAK;AAC3D,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,2EAA2E;AACvG,SAAO;AACT;AAEO,SAAS,qCAAyD;AACvE,QAAM,QAAQ,QAAQ,IAAI,8BAA8B,KAAK;AAC7D,SAAO,QAAQ,MAAM,QAAQ,MAAM,EAAE,IAAI;AAC3C;",
6
6
  "names": []
7
7
  }
@@ -1,14 +1,27 @@
1
1
  import { googleOAuthConfig } from "../../config/google.js";
2
- async function refreshGoogleAccessToken(credentials = googleOAuthConfig) {
2
+ import { getActiveGoogleRefreshTokenFromStore } from "../../config/google-store.js";
3
+ async function resolveGoogleOAuthCredentials() {
4
+ const refreshToken = await getActiveGoogleRefreshTokenFromStore();
5
+ if (!refreshToken) {
6
+ throw new Error(
7
+ "Missing Google OAuth refresh token. Connect Google in the dashboard and ensure the stored connection is active."
8
+ );
9
+ }
10
+ return {
11
+ clientId: googleOAuthConfig.clientId,
12
+ clientSecret: googleOAuthConfig.clientSecret,
13
+ refreshToken
14
+ };
15
+ }
16
+ async function refreshGoogleAccessToken(credentials) {
17
+ const resolved = credentials ?? await resolveGoogleOAuthCredentials();
3
18
  const response = await fetch("https://oauth2.googleapis.com/token", {
4
19
  method: "POST",
5
- headers: {
6
- "Content-Type": "application/x-www-form-urlencoded"
7
- },
20
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
8
21
  body: new URLSearchParams({
9
- client_id: credentials.clientId,
10
- client_secret: credentials.clientSecret,
11
- refresh_token: credentials.refreshToken,
22
+ client_id: resolved.clientId,
23
+ client_secret: resolved.clientSecret,
24
+ refresh_token: resolved.refreshToken,
12
25
  grant_type: "refresh_token"
13
26
  })
14
27
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/services/analytics/oauth.ts"],
4
- "sourcesContent": ["import { googleOAuthConfig, type GoogleOAuthConfig } from \"../../config/google.js\";\n\nexport interface GoogleAccessTokenResponse {\n accessToken: string;\n expiresIn: number;\n tokenType: string;\n scope?: string;\n}\n\ninterface GoogleTokenApiResponse {\n access_token?: string;\n expires_in?: number;\n token_type?: string;\n scope?: string;\n error?: string;\n error_description?: string;\n}\n\nexport async function refreshGoogleAccessToken(\n credentials: GoogleOAuthConfig = googleOAuthConfig\n): Promise<GoogleAccessTokenResponse> {\n const response = await fetch(\"https://oauth2.googleapis.com/token\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n client_id: credentials.clientId,\n client_secret: credentials.clientSecret,\n refresh_token: credentials.refreshToken,\n grant_type: \"refresh_token\",\n }),\n });\n\n const payload = (await response.json()) as GoogleTokenApiResponse;\n\n if (!response.ok || !payload.access_token || !payload.expires_in || !payload.token_type) {\n const errorMessage =\n payload.error_description ??\n payload.error ??\n `Unexpected Google OAuth response (${response.status})`;\n\n throw new Error(`Failed to refresh Google access token: ${errorMessage}`);\n }\n\n return {\n accessToken: payload.access_token,\n expiresIn: payload.expires_in,\n tokenType: payload.token_type,\n scope: payload.scope,\n };\n}\n"],
5
- "mappings": "AAAA,SAAS,yBAAiD;AAkB1D,eAAsB,yBACpB,cAAiC,mBACG;AACpC,QAAM,WAAW,MAAM,MAAM,uCAAuC;AAAA,IAClE,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,IAClB;AAAA,IACA,MAAM,IAAI,gBAAgB;AAAA,MACxB,WAAW,YAAY;AAAA,MACvB,eAAe,YAAY;AAAA,MAC3B,eAAe,YAAY;AAAA,MAC3B,YAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC;AAED,QAAM,UAAW,MAAM,SAAS,KAAK;AAErC,MAAI,CAAC,SAAS,MAAM,CAAC,QAAQ,gBAAgB,CAAC,QAAQ,cAAc,CAAC,QAAQ,YAAY;AACvF,UAAM,eACJ,QAAQ,qBACR,QAAQ,SACR,qCAAqC,SAAS,MAAM;AAEtD,UAAM,IAAI,MAAM,0CAA0C,YAAY,EAAE;AAAA,EAC1E;AAEA,SAAO;AAAA,IACL,aAAa,QAAQ;AAAA,IACrB,WAAW,QAAQ;AAAA,IACnB,WAAW,QAAQ;AAAA,IACnB,OAAO,QAAQ;AAAA,EACjB;AACF;",
4
+ "sourcesContent": ["import { googleOAuthConfig, type GoogleOAuthClientConfig } from '../../config/google.js';\nimport { getActiveGoogleRefreshTokenFromStore } from '../../config/google-store.js';\n\nexport interface GoogleAccessTokenResponse {\n accessToken: string;\n expiresIn: number;\n tokenType: string;\n scope?: string;\n}\n\nexport interface GoogleOAuthRefreshCredentials extends GoogleOAuthClientConfig {\n refreshToken: string;\n}\n\ninterface GoogleTokenApiResponse {\n access_token?: string;\n expires_in?: number;\n token_type?: string;\n scope?: string;\n error?: string;\n error_description?: string;\n}\n\nasync function resolveGoogleOAuthCredentials(): Promise<GoogleOAuthRefreshCredentials> {\n const refreshToken = await getActiveGoogleRefreshTokenFromStore();\n if (!refreshToken) {\n throw new Error(\n 'Missing Google OAuth refresh token. Connect Google in the dashboard and ensure the stored connection is active.'\n );\n }\n\n return {\n clientId: googleOAuthConfig.clientId,\n clientSecret: googleOAuthConfig.clientSecret,\n refreshToken,\n };\n}\n\nexport async function refreshGoogleAccessToken(\n credentials?: GoogleOAuthRefreshCredentials\n): Promise<GoogleAccessTokenResponse> {\n const resolved = credentials ?? (await resolveGoogleOAuthCredentials());\n const response = await fetch('https://oauth2.googleapis.com/token', {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n client_id: resolved.clientId,\n client_secret: resolved.clientSecret,\n refresh_token: resolved.refreshToken,\n grant_type: 'refresh_token',\n }),\n });\n\n const payload = (await response.json()) as GoogleTokenApiResponse;\n\n if (!response.ok || !payload.access_token || !payload.expires_in || !payload.token_type) {\n const errorMessage = payload.error_description ?? payload.error ?? `Unexpected Google OAuth response (${response.status})`;\n throw new Error(`Failed to refresh Google access token: ${errorMessage}`);\n }\n\n return {\n accessToken: payload.access_token,\n expiresIn: payload.expires_in,\n tokenType: payload.token_type,\n scope: payload.scope,\n };\n}\n"],
5
+ "mappings": "AAAA,SAAS,yBAAuD;AAChE,SAAS,4CAA4C;AAsBrD,eAAe,gCAAwE;AACrF,QAAM,eAAe,MAAM,qCAAqC;AAChE,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,kBAAkB;AAAA,IAC5B,cAAc,kBAAkB;AAAA,IAChC;AAAA,EACF;AACF;AAEA,eAAsB,yBACpB,aACoC;AACpC,QAAM,WAAW,eAAgB,MAAM,8BAA8B;AACrE,QAAM,WAAW,MAAM,MAAM,uCAAuC;AAAA,IAClE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,IAC/D,MAAM,IAAI,gBAAgB;AAAA,MACxB,WAAW,SAAS;AAAA,MACpB,eAAe,SAAS;AAAA,MACxB,eAAe,SAAS;AAAA,MACxB,YAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC;AAED,QAAM,UAAW,MAAM,SAAS,KAAK;AAErC,MAAI,CAAC,SAAS,MAAM,CAAC,QAAQ,gBAAgB,CAAC,QAAQ,cAAc,CAAC,QAAQ,YAAY;AACvF,UAAM,eAAe,QAAQ,qBAAqB,QAAQ,SAAS,qCAAqC,SAAS,MAAM;AACvH,UAAM,IAAI,MAAM,0CAA0C,YAAY,EAAE;AAAA,EAC1E;AAEA,SAAO;AAAA,IACL,aAAa,QAAQ;AAAA,IACrB,WAAW,QAAQ;AAAA,IACnB,WAAW,QAAQ;AAAA,IACnB,OAAO,QAAQ;AAAA,EACjB;AACF;",
6
6
  "names": []
7
7
  }