← back to Rentv Adintel

src/connectors/google-ads.js

173 lines

'use strict';

/**
 * Google Ads connector — spec §19.
 *
 * DISABLED BY DEFAULT.  `isEnabled()` returns false unless
 * GOOGLE_ADS_ENABLED=true is explicitly set.
 *
 * Google Ads data MUST remain strictly separate from Search Console data.
 * Never mix paid-search metrics with organic GSC metrics in the same table
 * or display surface.
 *
 * No fixtures are provided for Google Ads because enabling it without real
 * credentials would produce misleading paid-performance numbers.
 *
 * When credentials are eventually configured:
 * - Set GOOGLE_ADS_ENABLED=true
 * - Provide GOOGLE_ADS_DEVELOPER_TOKEN, GOOGLE_ADS_CUSTOMER_ID,
 *   GOOGLE_ADS_LOGIN_CUSTOMER_ID, GOOGLE_ADS_CLIENT_ID,
 *   GOOGLE_ADS_CLIENT_SECRET, GOOGLE_ADS_REFRESH_TOKEN
 * - Implement the TODO real API branch using the google-ads-api npm package
 *   or direct REST calls to googleads.googleapis.com/v17
 *
 * @module src/connectors/google-ads
 */

const { query } = require('../../db');

// ---------------------------------------------------------------------------
// Feature gate
// ---------------------------------------------------------------------------

/**
 * Returns true only when Google Ads integration is explicitly enabled.
 * @returns {boolean}
 */
function isEnabled() {
  return process.env.GOOGLE_ADS_ENABLED === 'true';
}

// ---------------------------------------------------------------------------
// Disabled guard — applied to every public function
// ---------------------------------------------------------------------------

function requireEnabled() {
  if (!isEnabled()) {
    throw new Error(
      'Google Ads connector is not configured. ' +
      'Set GOOGLE_ADS_ENABLED=true and provide all GOOGLE_ADS_* environment variables ' +
      'to enable this integration.  Google Ads data is separate from Search Console data.'
    );
  }
}

// ---------------------------------------------------------------------------
// Connection test
// ---------------------------------------------------------------------------

/**
 * Test the Google Ads connection.
 * Throws a friendly error when disabled.
 *
 * @returns {{ connected: boolean, demo: boolean, error?: string }}
 */
async function connectionTest() {
  requireEnabled();

  // TODO real API:
  // const { GoogleAdsClient } = require('google-ads-api');  // not installed
  // const client = new GoogleAdsClient({
  //   developer_token: process.env.GOOGLE_ADS_DEVELOPER_TOKEN,
  //   client_id:       process.env.GOOGLE_ADS_CLIENT_ID,
  //   client_secret:   process.env.GOOGLE_ADS_CLIENT_SECRET,
  //   refresh_token:   process.env.GOOGLE_ADS_REFRESH_TOKEN,
  //   login_customer_id: process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID,
  // });
  // const customer = client.Customer({ customer_id: process.env.GOOGLE_ADS_CUSTOMER_ID });
  // await customer.query(`SELECT customer.id, customer.descriptive_name FROM customer LIMIT 1`);
  throw new Error('Google Ads live API not enabled (install google-ads-api and implement the TODO real API branch)');
}

// ---------------------------------------------------------------------------
// Campaign metrics import
// ---------------------------------------------------------------------------

/**
 * Import campaign-level daily metrics into google_ads_campaign_metrics.
 *
 * TODO real API: GAQL query shape:
 *   SELECT
 *     campaign.name,
 *     segments.date,
 *     metrics.cost_micros,
 *     metrics.clicks,
 *     metrics.impressions,
 *     metrics.conversions
 *   FROM campaign
 *   WHERE segments.date DURING LAST_90_DAYS
 *     AND campaign.status != 'REMOVED'
 *   ORDER BY segments.date DESC
 *
 * @param {{ dryRun?: boolean }} options
 */
async function importCampaignMetrics({ dryRun = false } = {}) {
  requireEnabled();
  // Implementation pending live credentials
  throw new Error('Google Ads not configured');
}

// ---------------------------------------------------------------------------
// Search term metrics import
// ---------------------------------------------------------------------------

/**
 * Import search-term level metrics into google_ads_search_term_metrics.
 *
 * TODO real API: GAQL query shape:
 *   SELECT
 *     search_term_view.search_term,
 *     campaign.name,
 *     segments.date,
 *     metrics.clicks,
 *     metrics.impressions,
 *     metrics.cost_micros,
 *     metrics.conversions
 *   FROM search_term_view
 *   WHERE segments.date DURING LAST_90_DAYS
 *   ORDER BY metrics.impressions DESC
 *   LIMIT 10000
 *
 * @param {{ dryRun?: boolean }} options
 */
async function importSearchTermMetrics({ dryRun = false } = {}) {
  requireEnabled();
  throw new Error('Google Ads not configured');
}

// ---------------------------------------------------------------------------
// Full import
// ---------------------------------------------------------------------------

/**
 * Import all Google Ads data.
 * Only callable when isEnabled() is true.
 *
 * @param {{ dryRun?: boolean }} options
 */
async function importAll({ dryRun = false } = {}) {
  requireEnabled();
  throw new Error('Google Ads not configured');
}

// ---------------------------------------------------------------------------
// Connection record
// ---------------------------------------------------------------------------

async function ensureConnectionRecord() {
  await query(
    `INSERT INTO analytics_connections (kind, status, is_demo)
     VALUES ('GOOGLE_ADS', 'NOT_CONNECTED', true)
     ON CONFLICT DO NOTHING`
  );
}

module.exports = {
  isEnabled,
  connectionTest,
  importCampaignMetrics,
  importSearchTermMetrics,
  importAll,
  ensureConnectionRecord,
};