← back to Rentv Adintel

scripts/import-google.js

174 lines

#!/usr/bin/env node
'use strict';
/**
 * CLI: node scripts/import-google.js <ga4|gsc|google-ads>
 *
 * Calls src/connectors/* if present (guarded with try/require).
 * Prints import summary. When connectors are unavailable, prints
 * guidance on how to use the CSV upload fallback instead.
 *
 * Spec §6.20: Use only official APIs for GA4, GSC, Google Ads.
 * No unofficial scraping, no cookie injection.
 *
 * @module scripts/import-google
 */

require('../lib/env');
const { pool } = require('../db');

const KIND = (process.argv[2] || '').toLowerCase();
const VALID = ['ga4', 'gsc', 'google-ads'];

if (!VALID.includes(KIND)) {
  console.error('Usage: node scripts/import-google.js <ga4|gsc|google-ads>');
  console.error('Valid kinds:', VALID.join(', '));
  process.exit(1);
}

// ---------------------------------------------------------------------------
// Try to load the real connector
// ---------------------------------------------------------------------------
let connector = null;
const connectorKey = KIND === 'google-ads' ? 'google-ads' : KIND;
const connectorPaths = [
  `../src/connectors/${connectorKey}`,
  `../src/connectors/${connectorKey}/index`,
];

for (const cp of connectorPaths) {
  try {
    connector = require(cp);
    break;
  } catch (_) {
    // not yet built
  }
}

// ---------------------------------------------------------------------------
// CSV fallback guidance
// ---------------------------------------------------------------------------
const CSV_GUIDANCE = {
  ga4: {
    title: 'Google Analytics 4',
    envVars: ['GOOGLE_SERVICE_ACCOUNT_JSON_BASE64', 'GA4_PROPERTY_ID'],
    csvFallback: [
      '1. In GA4, go to Reports > Export > CSV',
      '2. Save the file locally',
      '3. POST it to /api/v1/imports with source=ga4 as multipart/form-data',
      '   curl -F "file=@ga4-export.csv" -F "source=ga4" http://localhost:3001/api/v1/imports',
    ],
    docsUrl: 'https://developers.google.com/analytics/devguides/reporting/data/v1',
    setupSteps: [
      '1. Create a Google Cloud service account with GA4 Viewer role.',
      '2. Download the JSON key file.',
      '3. Base64-encode it: base64 -i key.json',
      '4. Set GOOGLE_SERVICE_ACCOUNT_JSON_BASE64 in .env',
      '5. Set GA4_PROPERTY_ID to your numeric property ID (e.g. 123456789)',
      '6. Re-run: node scripts/import-google.js ga4',
    ],
  },
  gsc: {
    title: 'Google Search Console',
    envVars: ['GOOGLE_SERVICE_ACCOUNT_JSON_BASE64', 'GSC_SITE_URL'],
    csvFallback: [
      '1. In GSC, go to Performance > Export > Download CSV',
      '2. Save the file locally',
      '3. POST it to /api/v1/imports with source=gsc',
      '   curl -F "file=@gsc-export.csv" -F "source=gsc" http://localhost:3001/api/v1/imports',
    ],
    docsUrl: 'https://developers.google.com/webmaster-tools/v1/api_reference_index',
    setupSteps: [
      '1. Same service account as GA4 — add it as a full user in GSC settings.',
      '2. Set GSC_SITE_URL to the exact property URL (e.g. https://rentv.com/)',
      '3. Re-run: node scripts/import-google.js gsc',
    ],
  },
  'google-ads': {
    title: 'Google Ads',
    envVars: [
      'GOOGLE_ADS_ENABLED', 'GOOGLE_ADS_DEVELOPER_TOKEN', 'GOOGLE_ADS_CUSTOMER_ID',
      'GOOGLE_ADS_CLIENT_ID', 'GOOGLE_ADS_CLIENT_SECRET', 'GOOGLE_ADS_REFRESH_TOKEN',
    ],
    csvFallback: [
      '1. In Google Ads, go to Reports > Predefined reports > Campaign',
      '2. Download as CSV',
      '3. POST to /api/v1/imports with source=google-ads',
    ],
    docsUrl: 'https://developers.google.com/google-ads/api/docs/start',
    setupSteps: [
      '1. Apply for a Google Ads developer token (can take 1-3 business days).',
      '2. Create an OAuth2 client ID with Google Ads scope.',
      '3. Generate a refresh token using OAuth2 playground.',
      '4. Set all GOOGLE_ADS_* vars in .env',
      '5. Set GOOGLE_ADS_ENABLED=true',
      '6. Re-run: node scripts/import-google.js google-ads',
    ],
  },
};

// ---------------------------------------------------------------------------
// Check env vars
// ---------------------------------------------------------------------------
function checkEnvVars(vars) {
  const missing = vars.filter((v) => !process.env[v]);
  return missing;
}

// ---------------------------------------------------------------------------
// Run import
// ---------------------------------------------------------------------------
(async () => {
  console.log(`\n[import-google] Importing: ${KIND}`);
  const guide = CSV_GUIDANCE[KIND];

  if (connector && typeof connector.runImport === 'function') {
    // Real connector present
    const missing = checkEnvVars(guide.envVars);
    if (missing.length > 0) {
      console.error(`[import-google] Missing required env vars for ${KIND}:`);
      missing.forEach((v) => console.error(`  - ${v}`));
      console.error('\nSetup steps:');
      guide.setupSteps.forEach((s) => console.error(' ', s));
      await pool.end();
      process.exit(1);
    }

    console.log('[import-google] Connector found. Running import...');
    try {
      const result = await connector.runImport();
      console.log('[import-google] Import complete:');
      console.log(JSON.stringify(result, null, 2));
    } catch (err) {
      console.error('[import-google] Import failed:', err.message);
      console.error(err.stack);
      await pool.end();
      process.exit(1);
    }
    await pool.end();
    return;
  }

  // Connector not yet built — print guidance
  console.log(`[import-google] ${guide.title} connector not yet installed.`);
  console.log('\nRequired environment variables:');
  guide.envVars.forEach((v) => {
    const set = !!process.env[v];
    console.log(`  ${set ? '[SET]' : '[MISSING]'} ${v}`);
  });

  if (KIND === 'google-ads' && process.env.GOOGLE_ADS_ENABLED !== 'true') {
    console.log('\n  Note: GOOGLE_ADS_ENABLED must be "true" to activate this connector.');
  }

  console.log('\nSetup steps:');
  guide.setupSteps.forEach((s) => console.log(' ', s));

  console.log(`\nAPI documentation: ${guide.docsUrl}`);

  console.log('\nCSV upload fallback (works now without connector):');
  guide.csvFallback.forEach((s) => console.log(' ', s));

  await pool.end();
  process.exit(0);
})();