File: /home/storage/5/78/dd/wicomm2/public_html/blackfriday/api/start_scan.php
<?php
// ===== start_scan.php =====
// Executa PSI/Lighthouse, devolve resumo e persiste payload inteiro em VTEX MD (LH)
require_once __DIR__ . '/../config/config.php'; // usa as CONSTANTES informadas pelo usuário
@date_default_timezone_set(defined('APP_TIMEZONE') ? APP_TIMEZONE : 'America/Sao_Paulo');
if (!is_dir(STORAGE_DIR))
@mkdir(STORAGE_DIR, 0775, true);
// ---------- Helpers ----------
function json_out($data, $code = 200)
{
if (!headers_sent()) {
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
}
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
function require_post_json()
{
$raw = file_get_contents('php://input');
$j = json_decode($raw, true);
if (!is_array($j))
json_out(['ok' => false, 'error' => 'Invalid JSON body'], 400);
return $j;
}
function http_get($url, $timeout_ms = 15000, $connect_ms = 6000)
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT_MS => $connect_ms,
CURLOPT_TIMEOUT_MS => $timeout_ms,
CURLOPT_USERAGENT => 'FridayUpBot/1.0',
CURLOPT_ENCODING => '',
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
]);
$body = curl_exec($ch);
$err = curl_error($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [$code, $body, $err];
}
function clamp100($v)
{
$n = is_numeric($v) ? floatval($v) : 0;
return max(0, min(100, $n));
}
// ---------- VTEX Master Data (LH) ----------
function vtex_md_upsert_LH($scanId, $payloadJson)
{
if (!defined('VTEX_ACCOUNT') || !VTEX_ACCOUNT)
return [false, 'missing account'];
$host = 'https://' . VTEX_ACCOUNT . '.' . (defined('VTEX_ENVIRONMENT') ? VTEX_ENVIRONMENT : 'vtexcommercestable') . '.com.br';
$url = $host . '/api/dataentities/LH/documents';
$doc = ['scanId' => $scanId, 'payload' => $payloadJson];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-VTEX-API-AppKey: ' . VTEX_APPKEY,
'X-VTEX-API-AppToken: ' . VTEX_APPTOKEN,
],
CURLOPT_POSTFIELDS => json_encode($doc, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 8,
]);
$body = curl_exec($ch);
$err = curl_error($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code >= 400 || !$body)
return [false, $body ?: $err ?: ("HTTP " . $code)];
return [true, json_decode($body, true)];
}
// ---------- PSI / Lighthouse ----------
function run_psi_once($url, $strategy)
{
$u = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=' . rawurlencode($url) .
'&locale=pt_BR' .
'&strategy=' . rawurlencode($strategy) .
'&key=' . rawurlencode(PSI_API_KEY);
[$code, $body, $err] = http_get($u, PSI_TOTAL_MS, PSI_CONNECT_MS);
if ($code >= 400 || !$body) {
return [null, $body ?: ($err ?: ('HTTP ' . $code))];
}
$json = json_decode($body, true);
if (!is_array($json) || !isset($json['lighthouseResult'])) {
$msg = $json['error']['message'] ?? 'Sem lighthouseResult';
return [null, $msg];
}
return [$json, null];
}
function summarize_lighthouse($json)
{
$lh = $json['lighthouseResult'] ?? [];
$aud = $lh['audits'] ?? [];
$cats = $lh['categories'] ?? [];
$score = (int) round((($cats['performance']['score'] ?? 0) * 100));
$disp = function ($id) use ($aud) {
return $aud[$id]['displayValue'] ?? ''; };
$opps = [];
foreach ($aud as $id => $a) {
if (isset($a['details']['type']) && $a['details']['type'] === 'opportunity') {
$opps[] = [
'id' => $id,
'title' => $a['title'] ?? $id,
'savingsMs' => $a['details']['overallSavingsMs'] ?? 0
];
}
}
usort($opps, fn($A, $B) => ($B['savingsMs'] ?? 0) <=> ($A['savingsMs'] ?? 0));
$opps = array_slice($opps, 0, 10);
return [
'score' => clamp100($score),
'metrics' => [
'lcp' => $disp('largest-contentful-paint'),
'tbt' => $disp('total-blocking-time'),
'cls' => $disp('cumulative-layout-shift'),
'interactive' => $disp('interactive'),
],
'opportunidades' => $opps
];
}
// ---------- Main ----------
try {
$data = require_post_json();
$site = trim((string) ($data['site'] ?? ''));
$strategy = trim((string) ($data['strategy'] ?? 'mobile'));
if ($strategy === '')
$strategy = 'mobile';
$strategy = strtolower($strategy) === 'desktop' ? 'desktop' : 'mobile';
if ($site === '' || !defined('PSI_API_KEY') || PSI_API_KEY === '') {
json_out(['ok' => false, 'error' => 'site e/ou PSI_API_KEY ausentes'], 400);
}
// Re-tentativas leves
$json = null;
$err = null;
for ($i = 0; $i < (defined('PSI_RETRIES') ? PSI_RETRIES : 3); $i++) {
[$json, $err] = run_psi_once($site, $strategy);
if ($json)
break;
usleep(200000); // 200ms
}
if (!$json)
json_out(['ok' => false, 'error' => $err ?: 'Falha PSI'], 502);
// Gera um scan_id e salva storage local
$scanId = bin2hex(random_bytes(8));
$storagePath = rtrim(STORAGE_DIR, '/') . '/scan_' . $scanId . '.json';
@file_put_contents($storagePath, json_encode($json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
// Salva no Master Data (LH): scanId + payload (JSON puro)
[$okSave, $saveRes] = vtex_md_upsert_LH($scanId, json_encode($json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
// (não quebra fluxo se falhar)
// Resumo + cookie (fallback)
$summary = summarize_lighthouse($json);
$summaryCookie = json_encode($summary, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// Cookie por 30 min para o mesmo host
setcookie('fridayup_lighthouse', $summaryCookie, [
'expires' => time() + 1800,
'path' => '/',
'secure' => false,
'httponly' => false,
'samesite' => 'Lax'
]);
json_out([
'ok' => true,
'scan_id' => $scanId,
'url' => $site,
'strategy' => $strategy,
'score' => $summary['score'],
'metrics' => $summary['metrics'],
'oportunidades' => $summary['opportunidades'],
'md_saved' => $okSave ? true : false
]);
} catch (Throwable $e) {
json_out(['ok' => false, 'error' => $e->getMessage()], 500);
}