/ src / api / pricing-export.js
pricing-export.js
  1  /**
  2   * Pricing Export to Cloudflare Worker
  3   *
  4   * Exports country pricing data to the auditandfix-api CF Worker KV
  5   * so the PHP sales page can serve localized prices.
  6   * Also writes a local backup to data/pricing-export.json.
  7   */
  8  
  9  import axios from 'axios';
 10  import { writeFileSync, mkdirSync } from 'fs';
 11  import { join, dirname } from 'path';
 12  import { fileURLToPath } from 'url';
 13  import { getOne } from '../utils/db.js';
 14  import { getAllCountries, getPrice } from '../utils/country-pricing.js';
 15  import Logger from '../utils/logger.js';
 16  import '../utils/load-env.js';
 17  
 18  const __filename = fileURLToPath(import.meta.url);
 19  const __dirname = dirname(__filename);
 20  const projectRoot = join(__dirname, '../..');
 21  
 22  const logger = new Logger('PricingExport');
 23  
 24  /**
 25   * Export pricing data to CF Worker and local backup
 26   * @returns {Object} { success, countries }
 27   */
 28  export async function exportPricing() {
 29    logger.info('Exporting pricing data...');
 30  
 31    // Get all active country pricing
 32    const countries = getAllCountries({ activeOnly: true });
 33  
 34    // Transform to simple format for PHP/JS consumption
 35    const pricingData = {};
 36    for (const country of countries) {
 37      const p = getPrice(country.country_code);
 38      if (!p) continue;
 39      // Fall back to USD when local price hasn't been set by repricing cron yet.
 40      // Don't use local currency symbol with a USD amount — show USD instead.
 41      const hasLocal = p.priceLocalCents !== null && p.priceLocalCents > 0;
 42      const priceCents = hasLocal ? p.priceLocalCents : country.price_usd;
 43      const currency = hasLocal ? p.currency : 'USD';
 44      const symbol = hasLocal ? p.currencySymbol : '$';
 45      const formatted = hasLocal ? p.formattedPrice : `$${Math.round(country.price_usd / 100)}`;
 46      pricingData[country.country_code] = {
 47        price: priceCents,
 48        currency,
 49        symbol,
 50        formatted,
 51        country_name: country.country_name || country.country_code,
 52      };
 53    }
 54  
 55    // GB is an alias for UK (ISO uses GB; our DB uses UK)
 56    if (pricingData['UK'] && !pricingData['GB']) {
 57      pricingData['GB'] = pricingData['UK'];
 58    }
 59  
 60    // Count real countries — exclude _meta and the GB alias (added above)
 61    const gbAliasAdded = pricingData['UK'] && pricingData['GB'] === pricingData['UK'];
 62    const countryCount = Object.keys(pricingData).length - (gbAliasAdded ? 1 : 0);
 63  
 64    // Add _meta: sites_scored count from DB (live social proof for sales page)
 65    try {
 66      const row = await getOne("SELECT COUNT(*) as cnt FROM sites WHERE status NOT IN ('found')");
 67      pricingData['_meta'] = { sites_scored: Number(row?.cnt ?? 10000) };
 68      logger.info(`Sites scored count: ${pricingData['_meta'].sites_scored}`);
 69    } catch (error) {
 70      logger.warn(`Failed to get sites_scored count: ${error.message}`);
 71      pricingData['_meta'] = { sites_scored: 10000 };
 72    }
 73    logger.info(`Prepared pricing for ${countryCount} countries`);
 74  
 75    // Write local backup
 76    try {
 77      const backupDir = join(projectRoot, 'data');
 78      mkdirSync(backupDir, { recursive: true });
 79      writeFileSync(join(backupDir, 'pricing-export.json'), JSON.stringify(pricingData, null, 2));
 80      logger.info('Local backup written to data/pricing-export.json');
 81    } catch (error) {
 82      logger.warn(`Failed to write local backup: ${error.message}`);
 83    }
 84  
 85    // Push to CF Worker
 86    const workerUrl = process.env.API_WORKER_URL;
 87    const workerSecret = process.env.API_WORKER_SECRET;
 88  
 89    if (!workerUrl || !workerSecret) {
 90      logger.warn(
 91        'API_WORKER_URL or API_WORKER_SECRET not configured, skipping push'
 92      );
 93      return { success: false, countries: countryCount, reason: 'not_configured' };
 94    }
 95  
 96    try {
 97      await axios.post(`${workerUrl}/pricing`, pricingData, {
 98        headers: {
 99          'Content-Type': 'application/json',
100          'X-Auth-Secret': workerSecret,
101        },
102        timeout: 15000,
103      });
104  
105      logger.success(`Pricing exported to CF Worker: ${countryCount} countries`);
106      return { success: true, countries: countryCount };
107    } catch (error) {
108      logger.error('Failed to export pricing to CF Worker', error);
109      return { success: false, countries: countryCount, error: error.message };
110    }
111  }
112  
113  // CLI
114  if (import.meta.url === `file://${process.argv[1]}`) {
115    exportPricing()
116      .then(result => {
117        console.log('Export result:', result);
118        process.exit(result.success ? 0 : 1);
119      })
120      .catch(error => {
121        logger.error('Export failed:', error);
122        process.exit(1);
123      });
124  }