Server : Apache System : Linux webd003.cluster111.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64 User : edevmultrx ( 811899) PHP Version : 7.4.33 Disable Function : _dyuweyrj4,_dyuweyrj4r,dl Directory : /home/edevmultrx/www/terra-d-oro/api/ |
<?php
/**
* Terra D'Oro — iCal Sync API Endpoint
* Route: /api/sync (POST or GET with ?key=TOKEN)
*
* Triggers SyncManager to pull all external iCal feeds (Airbnb / Booking.com)
* and reconcile them with the local SQLite database.
*
* AUTHENTICATION:
* ?key=YOUR_SYNC_KEY (defined as SYNC_KEY in core/config.php)
* OR an Authorization: Bearer YOUR_SYNC_KEY header.
*
* This endpoint is designed to be called by:
* 1. A server cron job every 15 minutes:
* curl -s "https://terradoro.com/api/sync?key=TOKEN"
* 2. Nina's admin panel (manual sync button in admin/ical-sync.php).
* 3. The init script (php core/init.php) for initial setup verification.
*
* Responds with JSON regardless of success or failure.
*/
declare(strict_types=1);
// ── Bootstrap ─────────────────────────────────────────────────────────────────
if (!defined('ROOT_PATH')) {
define('ROOT_PATH', dirname(__DIR__));
}
require_once ROOT_PATH . '/core/config.php';
require_once ROOT_PATH . '/core/db.php';
require_once ROOT_PATH . '/core/helpers.php';
require_once ROOT_PATH . '/core/ical/ICalParser.php';
require_once ROOT_PATH . '/core/ical/SyncManager.php';
// ── JSON response helper ───────────────────────────────────────────────────────
function json_respond(int $status, array $data): never
{
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache');
header('X-Content-Type-Options: nosniff');
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}
// ── Authentication ────────────────────────────────────────────────────────────
// Accept key via query string or Authorization header
$provided_key = $_GET['key']
?? (str_replace('Bearer ', '', $_SERVER['HTTP_AUTHORIZATION'] ?? ''))
?: null;
if (!$provided_key || !hash_equals(SYNC_KEY, $provided_key)) {
json_respond(401, [
'error' => 'Unauthorized',
'message' => 'Valid ?key= parameter or Authorization: Bearer header required.',
]);
}
// ── Method guard ──────────────────────────────────────────────────────────────
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if (!in_array($method, ['GET', 'POST'], true)) {
json_respond(405, ['error' => 'Method Not Allowed']);
}
// ── Optional scope parameters ─────────────────────────────────────────────────
// ?unit_id=1 → sync only that unit's sources
// ?force=1 → bypass the ICAL_SYNC_INTERVAL throttle (not yet implemented in SM,
// but passed through for future use)
$unit_id = isset($_GET['unit_id']) ? (int) $_GET['unit_id'] : null;
// ── Run sync ──────────────────────────────────────────────────────────────────
$started = microtime(true);
try {
$manager = new SyncManager($db);
$stats = $unit_id
? $manager->syncUnit($unit_id)
: $manager->syncAll();
$elapsed = round(microtime(true) - $started, 3);
json_respond(200, [
'ok' => true,
'elapsed' => $elapsed . 's',
'scope' => $unit_id ? "unit_id={$unit_id}" : 'all',
'stats' => $stats,
'synced_at' => date('c'),
]);
} catch (\Throwable $e) {
$elapsed = round(microtime(true) - $started, 3);
$detail = DEBUG ? $e->getMessage() : 'Internal server error during sync.';
json_respond(500, [
'ok' => false,
'elapsed' => $elapsed . 's',
'error' => $detail,
]);
}