Files
wevads-platform/scripts/api_ads-commander-api.php
2026-02-26 04:53:11 +01:00

293 lines
14 KiB
PHP
Executable File

<?php
/**
* ADS COMMANDER API — Unified Meta/TikTok + ROAS Arbitrage
* Endpoint: :5821/api/ads-commander-api.php
*/
require_once("/opt/wevads/config/credentials.php");
header('Content-Type: application/json');
$db = pg_connect("host=localhost dbname=adx_system user=admin password=".WEVADS_DB_PASS);
pg_query($db, "SET search_path TO admin");
$action = $_GET['action'] ?? $_POST['action'] ?? 'status';
// Config
$ROAS_KILL = 1.3;
$ROAS_BOOST = 2.5;
$MIN_SPEND_DECISION = 20.00;
switch($action) {
case 'create_campaign':
$name = pg_escape_string($db, $_POST['name'] ?? 'Campaign '.date('Ymd-His'));
$platform = pg_escape_string($db, $_POST['platform'] ?? 'meta');
$objective = pg_escape_string($db, $_POST['objective'] ?? 'conversions');
$offer_link = pg_escape_string($db, $_POST['offer_link'] ?? '');
$offer_network = pg_escape_string($db, $_POST['offer_network'] ?? '');
$offer_payout = (float)($_POST['offer_payout'] ?? 25.00);
$yt_url = pg_escape_string($db, $_POST['youtube_url'] ?? '');
$budget_daily = (float)($_POST['budget_daily'] ?? 50.00);
$roas_target = (float)($_POST['roas_target'] ?? 2.0);
$id = pg_fetch_result(pg_query($db, "SELECT gen_random_uuid()"), 0);
pg_query($db, "INSERT INTO xc_campaigns(id, campaign_name, platform_target, objective, offer_link, offer_network, offer_payout, youtube_url, budget_daily, roas_target, status)
VALUES('$id', '$name', '$platform', '$objective', '$offer_link', '$offer_network', $offer_payout, '$yt_url', $budget_daily, $roas_target, 'draft')");
echo json_encode(['status' => 'success', 'campaign_id' => $id, 'next' => 'launch_on_platform']);
break;
case 'launch_meta':
$campaign_id = pg_escape_string($db, $_POST['campaign_id'] ?? '');
$account_id_internal = (int)($_POST['account_id'] ?? 0);
$camp = pg_fetch_assoc(pg_query($db, "SELECT * FROM xc_campaigns WHERE id='$campaign_id'"));
$acc = pg_fetch_assoc(pg_query($db, "SELECT * FROM ads_accounts WHERE id=$account_id_internal AND platform='meta' AND is_active=true"));
if (!$camp || !$acc) { echo json_encode(['error' => 'Campaign or account not found']); break; }
$token = $acc['access_token_encrypted']; // Decrypt in prod
$fb_account = $acc['account_id'];
// Create campaign on Meta
$meta_url = "https://graph.facebook.com/v18.0/act_$fb_account/campaigns";
$ch = curl_init($meta_url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => http_build_query([
'name' => $camp['campaign_name'],
'objective' => 'OUTCOME_' . strtoupper($camp['objective']),
'status' => 'PAUSED',
'special_ad_categories' => '[]',
'access_token' => $token
])
]);
$resp = json_decode(curl_exec($ch), true);
curl_close($ch);
if (isset($resp['id'])) {
pg_query($db, "UPDATE xc_campaigns SET platform_campaign_id='{$resp['id']}', status='pending', start_date=NOW() WHERE id='$campaign_id'");
echo json_encode(['status' => 'success', 'meta_campaign_id' => $resp['id']]);
} else {
echo json_encode(['status' => 'error', 'meta_error' => $resp['error'] ?? $resp]);
}
break;
case 'launch_tiktok':
$campaign_id = pg_escape_string($db, $_POST['campaign_id'] ?? '');
$account_id_internal = (int)($_POST['account_id'] ?? 0);
$camp = pg_fetch_assoc(pg_query($db, "SELECT * FROM xc_campaigns WHERE id='$campaign_id'"));
$acc = pg_fetch_assoc(pg_query($db, "SELECT * FROM ads_accounts WHERE id=$account_id_internal AND platform='tiktok' AND is_active=true"));
if (!$camp || !$acc) { echo json_encode(['error' => 'Not found']); break; }
$token = $acc['access_token_encrypted'];
$tt_url = "https://business-api.tiktok.com/open_api/v1.3/campaign/create/";
$ch = curl_init($tt_url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Access-Token: $token", 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'advertiser_id' => $acc['account_id'],
'campaign_name' => $camp['campaign_name'],
'objective_type' => 'CONVERSIONS',
'budget_mode' => 'BUDGET_MODE_DAY',
'budget' => $camp['budget_daily']
])
]);
$resp = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($resp['code'] ?? -1) == 0) {
$tt_id = $resp['data']['campaign_id'];
pg_query($db, "UPDATE xc_campaigns SET platform_campaign_id='$tt_id', status='pending', start_date=NOW() WHERE id='$campaign_id'");
echo json_encode(['status' => 'success', 'tiktok_campaign_id' => $tt_id]);
} else {
echo json_encode(['status' => 'error', 'tiktok_error' => $resp]);
}
break;
case 'roas_check':
// Check ROAS for all active campaigns + make decisions
$active = pg_fetch_all(pg_query($db, "SELECT * FROM xc_campaigns WHERE status IN ('active','boosted') ORDER BY last_roas_check ASC"));
if (!$active) { echo json_encode(['status' => 'no_active_campaigns']); break; }
$decisions = [];
foreach ($active as $camp) {
// Calculate ROAS
$spent = (float)$camp['budget_spent'];
$revenue = (float)$camp['revenue_generated'];
$roas = $spent > 0 ? round($revenue / $spent, 2) : 0;
// Update ROAS
pg_query($db, "UPDATE xc_campaigns SET actual_roas=$roas, last_roas_check=NOW() WHERE id='{$camp['id']}'");
// Decisions only after min spend
if ($spent < $MIN_SPEND_DECISION) continue;
$decision = null;
if ($roas < $ROAS_KILL) {
// KILL: pause campaign
pg_query($db, "UPDATE xc_campaigns SET status='killed', paused_reason='low_roas' WHERE id='{$camp['id']}'");
$decision = ['type' => 'kill', 'reason' => "ROAS $roas < $ROAS_KILL"];
// TODO: Call platform API to pause
} elseif ($roas > $ROAS_BOOST && $camp['status'] != 'boosted') {
// BOOST: increase budget 50%
$new_budget = round($camp['budget_daily'] * 1.5, 2);
pg_query($db, "UPDATE xc_campaigns SET status='boosted', budget_daily=$new_budget WHERE id='{$camp['id']}'");
$decision = ['type' => 'boost', 'reason' => "ROAS $roas > $ROAS_BOOST", 'new_budget' => $new_budget];
// TODO: Call platform API to increase budget
}
if ($decision) {
pg_query($db, "INSERT INTO ads_arbitrage_log(campaign_id, decision_type, decision_reason, roas_at_decision, budget_before, budget_after, previous_status, new_status)
VALUES('{$camp['id']}', '{$decision['type']}', '".pg_escape_string($db, $decision['reason'])."', $roas, {$camp['budget_daily']}, ".($decision['new_budget'] ?? $camp['budget_daily']).", '{$camp['status']}', '".($decision['type'] == 'kill' ? 'killed' : 'boosted')."')");
$decisions[] = ['campaign' => $camp['campaign_name'], 'decision' => $decision];
}
}
echo json_encode(['status' => 'success', 'campaigns_checked' => count($active), 'decisions' => $decisions]);
break;
case 'record_conversion':
// Record a conversion from tracking pixel/postback
$campaign_id = pg_escape_string($db, $_POST['campaign_id'] ?? $_GET['cid'] ?? '');
$revenue = (float)($_POST['revenue'] ?? $_GET['rev'] ?? 0);
if (!$campaign_id) { echo json_encode(['error' => 'Need campaign_id']); break; }
pg_query($db, "UPDATE xc_campaigns SET conversions=conversions+1, revenue_generated=revenue_generated+$revenue WHERE id='$campaign_id'");
echo json_encode(['status' => 'recorded', 'campaign_id' => $campaign_id, 'revenue' => $revenue]);
break;
case 'record_spend':
// Update spend from platform webhook or sync
$campaign_id = pg_escape_string($db, $_POST['campaign_id'] ?? '');
$spent = (float)($_POST['spent'] ?? 0);
$impressions = (int)($_POST['impressions'] ?? 0);
$clicks = (int)($_POST['clicks'] ?? 0);
if (!$campaign_id) { echo json_encode(['error' => 'Need campaign_id']); break; }
pg_query($db, "UPDATE xc_campaigns SET
budget_spent=budget_spent+$spent, impressions=impressions+$impressions, clicks=clicks+$clicks,
ctr=CASE WHEN impressions+$impressions>0 THEN round((clicks+$clicks)::numeric/(impressions+$impressions)*100,2) ELSE 0 END,
cpc=CASE WHEN clicks+$clicks>0 THEN round((budget_spent+$spent)::numeric/(clicks+$clicks),2) ELSE 0 END
WHERE id='$campaign_id'");
echo json_encode(['status' => 'updated']);
break;
case 'create_creative':
$titles = $_POST['titles'] ?? ['Default Ad Title'];
$descriptions = $_POST['descriptions'] ?? ['Default description'];
$media_urls = $_POST['media_urls'] ?? [];
$media_type = pg_escape_string($db, $_POST['media_type'] ?? 'video');
$cta = pg_escape_string($db, $_POST['cta'] ?? 'Learn More');
$audience = pg_escape_string($db, json_encode($_POST['audience'] ?? new stdClass()));
$yt_job_id = pg_escape_string($db, $_POST['youtube_job_id'] ?? '');
$id = pg_fetch_result(pg_query($db, "SELECT gen_random_uuid()"), 0);
$titles_pg = "'{\"" . implode('","', array_map(function($t) use ($db) { return pg_escape_string($db, $t); }, (array)$titles)) . "\"}'";
$descs_pg = "'{\"" . implode('","', array_map(function($d) use ($db) { return pg_escape_string($db, $d); }, (array)$descriptions)) . "\"}'";
$media_pg = "'{\"" . implode('","', array_map(function($m) use ($db) { return pg_escape_string($db, $m); }, (array)$media_urls)) . "\"}'";
pg_query($db, "INSERT INTO ads_creatives(id, title_variations, description_variations, media_urls, media_type, call_to_action, target_audience, source_type)
VALUES('$id', $titles_pg, $descs_pg, $media_pg, '$media_type', '$cta', '$audience', '".($yt_job_id ? 'youtube_auto' : 'manual')."')");
echo json_encode(['status' => 'success', 'creative_id' => $id]);
break;
case 'launch_multichannel':
// Launch a full multi-channel campaign: Ads + Email + SMS + YouTube
$offer_link = $_POST['offer_link'] ?? '';
$name = $_POST['name'] ?? 'MultiCH-'.date('Ymd');
$budget = (float)($_POST['budget'] ?? 50);
$platforms = $_POST['platforms'] ?? 'all';
$enable_email = ($_POST['email'] ?? '0') == '1';
$enable_sms = ($_POST['sms'] ?? '0') == '1';
$enable_youtube = ($_POST['youtube'] ?? '0') == '1';
// 1. Create ads campaign
$camp_id = pg_fetch_result(pg_query($db, "SELECT gen_random_uuid()"), 0);
pg_query($db, "INSERT INTO xc_campaigns(id, campaign_name, platform_target, offer_link, budget_daily, status)
VALUES('$camp_id', '".pg_escape_string($db, $name)."', '".pg_escape_string($db, $platforms)."', '".pg_escape_string($db, $offer_link)."', $budget, 'draft')");
$results = ['campaign_id' => $camp_id, 'channels' => []];
// 2. YouTube video generation (async)
if ($enable_youtube) {
$yt_resp = json_decode(@file_get_contents("http://127.0.0.1:5821/api/youtube-factory-api.php?action=auto_pipeline"), true);
$results['channels']['youtube'] = $yt_resp;
if (isset($yt_resp['job_id'])) {
pg_query($db, "INSERT INTO xc_content_sync(ads_campaign_id, yt_job_id, content_angle, sync_status)
VALUES('$camp_id', '{$yt_resp['job_id']}', '".pg_escape_string($db, $yt_resp['trend'] ?? '')."', 'synced')");
}
}
// 3. Email campaign trigger
if ($enable_email) {
$results['channels']['email'] = ['status' => 'queued', 'note' => 'Use unified-send with campaign_id=' . $camp_id];
}
// 4. SMS trigger
if ($enable_sms) {
$results['channels']['sms'] = ['status' => 'queued', 'note' => 'Link SMS batch to campaign_id=' . $camp_id];
}
echo json_encode(['status' => 'success', 'multichannel' => $results]);
break;
case 'roas_live':
// Live ROAS dashboard data
$campaigns = pg_fetch_all(pg_query($db, "
SELECT id, campaign_name, platform_target, status, actual_roas, budget_daily, budget_spent, revenue_generated,
impressions, clicks, conversions, ctr, cpc, cpa,
EXTRACT(EPOCH FROM (NOW()-start_date))/3600 as hours_running
FROM xc_campaigns WHERE status IN ('active','boosted','draft','pending')
ORDER BY actual_roas DESC NULLS LAST
"));
$totals = pg_fetch_assoc(pg_query($db, "
SELECT COALESCE(SUM(budget_spent),0) as total_spent, COALESCE(SUM(revenue_generated),0) as total_revenue,
COALESCE(SUM(conversions),0) as total_conversions,
CASE WHEN SUM(budget_spent)>0 THEN round(SUM(revenue_generated)/SUM(budget_spent),2) ELSE 0 END as overall_roas,
COUNT(*) FILTER (WHERE status='active') as active_count,
COUNT(*) FILTER (WHERE status='killed') as killed_count,
COUNT(*) FILTER (WHERE status='boosted') as boosted_count
FROM xc_campaigns
"));
echo json_encode(['status' => 'success', 'totals' => $totals, 'campaigns' => $campaigns ?: []]);
break;
case 'arbitrage_history':
$logs = pg_fetch_all(pg_query($db, "
SELECT al.*, xc.campaign_name
FROM ads_arbitrage_log al
LEFT JOIN xc_campaigns xc ON xc.id=al.campaign_id
ORDER BY al.executed_at DESC LIMIT 50
"));
echo json_encode(['status' => 'success', 'decisions' => $logs ?: []]);
break;
case 'status':
$s = pg_fetch_assoc(pg_query($db, "
SELECT
(SELECT COUNT(*) FROM ads_accounts WHERE is_active=true) as active_ad_accounts,
(SELECT COUNT(*) FROM ads_creatives WHERE is_active=true) as active_creatives,
(SELECT COUNT(*) FROM xc_campaigns) as total_campaigns,
(SELECT COUNT(*) FROM xc_campaigns WHERE status='active') as active_campaigns,
(SELECT COUNT(*) FROM xc_campaigns WHERE status='killed') as killed_campaigns,
(SELECT COUNT(*) FROM xc_campaigns WHERE status='boosted') as boosted_campaigns,
(SELECT COALESCE(SUM(budget_spent),0) FROM xc_campaigns) as total_spent,
(SELECT COALESCE(SUM(revenue_generated),0) FROM xc_campaigns) as total_revenue,
(SELECT COUNT(*) FROM ads_arbitrage_log) as total_decisions
"));
echo json_encode(['status' => 'success', 'ads_commander' => $s]);
break;
}
?>