Minor changes to structure
This commit is contained in:
@@ -2,13 +2,12 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
require_once __DIR__ . '/../src/Calco2latoApiClient.php';
|
require_once __DIR__ . '/../src/Calco2latoApiClient.php';
|
||||||
|
|
||||||
// --- Basic CORS (adjust origin to your site/domain) ---
|
// --- CORS (restrict to your site) ---
|
||||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||||
$allowedOrigin = preg_match('#^https://(www\.)?your-frontend\.example$#', $origin) ? $origin : '';
|
if (preg_match('#^https://(www\.)?your-frontend\.example$#', $origin)) {
|
||||||
if ($allowedOrigin) {
|
header('Access-Control-Allow-Origin: ' . $origin);
|
||||||
header('Access-Control-Allow-Origin: ' . $allowedOrigin);
|
|
||||||
header('Vary: Origin');
|
header('Vary: Origin');
|
||||||
header('Access-Control-Allow-Credentials: true');
|
header('Access-Control-Allow-Credentials', 'true');
|
||||||
}
|
}
|
||||||
header('Access-Control-Allow-Headers: Content-Type');
|
header('Access-Control-Allow-Headers: Content-Type');
|
||||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||||
@@ -55,7 +54,9 @@ if (!$key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Instantiate API client ---
|
// --- Instantiate API client ---
|
||||||
$client = new Calco2latoApiClient($base, $key);
|
// Version from query (?ver=v1|test|latest), default latest
|
||||||
|
$ver = $_GET['ver'] ?? $_POST['ver'] ?? Calco2latoApiVersion::LATEST;
|
||||||
|
$client = new Calco2latoApiClient($base, $key, (string)$ver);
|
||||||
|
|
||||||
// --- Whitelist router ---
|
// --- Whitelist router ---
|
||||||
$input = json_decode(file_get_contents('php://input') ?: '[]', true) ?: [];
|
$input = json_decode(file_get_contents('php://input') ?: '[]', true) ?: [];
|
||||||
@@ -66,21 +67,32 @@ header('Content-Type: application/json; charset=utf-8');
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
switch ($endpoint) {
|
switch ($endpoint) {
|
||||||
case 'airports.search':
|
case 'health':
|
||||||
// GET /?endpoint=airports.search&q=FRA&per_page=10
|
echo json_encode(['ok'=>true,'ver'=>$ver]);
|
||||||
$q = $_GET['q'] ?? '';
|
|
||||||
$per_page = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 20;
|
|
||||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
|
||||||
$data = $client->searchAirports($q, $per_page, $page);
|
|
||||||
echo json_encode($data);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'airports.search':
|
||||||
|
if ($method === 'GET') {
|
||||||
|
$q = [
|
||||||
|
'page' => isset($_GET['page']) ? (int)$_GET['page'] : null,
|
||||||
|
'per_page' => isset($_GET['per_page']) ? (int)$_GET['per_page'] : null,
|
||||||
|
'sort_by' => $_GET['sort_by'] ?? null,
|
||||||
|
'order' => $_GET['order'] ?? null,
|
||||||
|
'iata' => $_GET['iata'] ?? null,
|
||||||
|
];
|
||||||
|
echo json_encode($client->airports_get($q));
|
||||||
|
break;
|
||||||
|
} elseif ($method === 'POST') {
|
||||||
|
$params = $input['params'] ?? [];
|
||||||
|
echo json_encode($client->airports_post($params));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'flights.estimate':
|
case 'flights.estimate':
|
||||||
// POST with JSON body: { endpoint: "flights.estimate", params: {...} }
|
// POST with JSON body: { endpoint: "flights.estimate", params: {...} }
|
||||||
if ($method !== 'POST') throw new RuntimeException('Use POST');
|
if ($method !== 'POST') throw new RuntimeException('Use POST');
|
||||||
$params = $input['params'] ?? [];
|
$params = $input['params'] ?? [];
|
||||||
$data = $client->flightEstimate($params);
|
echo json_encode($client->flight_post($params));
|
||||||
echo json_encode($data);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<h1>Search for airports</h1>
|
<h1>Search for airports</h1>
|
||||||
<form id="airport-search-form">
|
<form id="airport-search-form">
|
||||||
<input type="text" id="q" name="q" />
|
<input type="text" id="iata" name="iata" />
|
||||||
<button type="submit" name="Los" title="Los">Search ...</button>
|
<button type="submit" name="Los" title="Los">Search ...</button>
|
||||||
</form>
|
</form>
|
||||||
<div id="airport-results">
|
<div id="airport-results">
|
||||||
@@ -55,8 +55,8 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
list.innerHTML = 'Loading…';
|
list.innerHTML = 'Loading…';
|
||||||
try {
|
try {
|
||||||
const q = new FormData(searchform).get('q');
|
const iata = new FormData(searchform).get('iata');
|
||||||
const airports = await api.searchAirports(q, 10, 1);
|
const airports = await api.searchAirports(iata, 10, 1);
|
||||||
list.innerHTML = airports.map(a => `<li>${a.display}</li>`).join('');
|
list.innerHTML = airports.map(a => `<li>${a.display}</li>`).join('');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
list.innerHTML = `<li style="color:red">${err.message}</li>`;
|
list.innerHTML = `<li style="color:red">${err.message}</li>`;
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
try {
|
try {
|
||||||
const f = new FormData(flightform);
|
const f = new FormData(flightform);
|
||||||
const flight = await api.estimateFlight({"flights": [{"departure": f.get('departure'), "arrival": f.get('arrival'), "passengerCount": 1, "travelClass": f.get('cabinclass'), "departureDate": f.get('departuredate')}]});
|
const flight = await api.estimateFlight({"flights": [{"departure": f.get('departure'), "arrival": f.get('arrival'), "passengerCount": 1, "travelClass": f.get('cabinclass'), "departureDate": f.get('departuredate')}]});
|
||||||
flightresult.innerHTML = JSON.stringify(flight, null, 2);
|
flightresult.innerHTML = flight.summary();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
flightresult.innerHTML = `<span style="color:red">${err.message}</span>`;
|
flightresult.innerHTML = `<span style="color:red">${err.message}</span>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export class Calco2latoClient {
|
|||||||
async searchAirports(q, limit = 20, offset = 1) {
|
async searchAirports(q, limit = 20, offset = 1) {
|
||||||
const data = await this._fetchJSON({
|
const data = await this._fetchJSON({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
query: { endpoint: 'airports.search', q, limit, offset }
|
query: { endpoint: 'airports.search', iata: q, per_page: limit, page: offset }
|
||||||
});
|
});
|
||||||
const items = Array.isArray(data?.results) ? data.results : (Array.isArray(data) ? data : []);
|
const items = Array.isArray(data?.results) ? data.results : (Array.isArray(data) ? data : []);
|
||||||
return items.map(a => new Airport(a));
|
return items.map(a => new Airport(a));
|
||||||
@@ -94,6 +94,6 @@ export class Calco2latoClient {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { endpoint: 'flights.estimate', params }
|
body: { endpoint: 'flights.estimate', params }
|
||||||
});
|
});
|
||||||
return new Flight(data);
|
return new Flight(data.flights[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,56 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
final class Calco2latoApiVersion
|
||||||
|
{
|
||||||
|
public const V1 = 'v1';
|
||||||
|
public const TEST = 'test';
|
||||||
|
public const LATEST = 'latest';
|
||||||
|
|
||||||
|
public static function ensure(string $v): string
|
||||||
|
{
|
||||||
|
$v = strtolower($v);
|
||||||
|
return in_array($v, [self::V1, self::TEST, self::LATEST], true) ? $v : self::LATEST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class Calco2latoApiClient
|
final class Calco2latoApiClient
|
||||||
{
|
{
|
||||||
private string $baseUrl;
|
private string $baseUrl; // e.g. https://api.calco2la.to
|
||||||
private string $apiKey;
|
private string $apiKey; // Bearer token (server-side only)
|
||||||
|
private string $version; // v1|test|latest
|
||||||
private int $timeout;
|
private int $timeout;
|
||||||
|
|
||||||
public function __construct(string $baseUrl, string $apiKey, int $timeoutSeconds = 10)
|
public function __construct(
|
||||||
{
|
string $baseUrl = 'https://api.calco2la.to',
|
||||||
|
string $apiKey = '',
|
||||||
|
string $version = Calco2latoApiVersion::LATEST,
|
||||||
|
int $timeoutSeconds = 12
|
||||||
|
) {
|
||||||
$this->baseUrl = rtrim($baseUrl, '/');
|
$this->baseUrl = rtrim($baseUrl, '/');
|
||||||
$this->apiKey = $apiKey;
|
$this->apiKey = $apiKey;
|
||||||
|
$this->version = Calco2latoApiVersion::ensure($version);
|
||||||
$this->timeout = $timeoutSeconds;
|
$this->timeout = $timeoutSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function withVersion(string $version): self
|
||||||
* Low-level request wrapper (GET/POST/DELETE/PUT/PATCH).
|
|
||||||
* $query is appended to URL; $body (if provided) is JSON-encoded.
|
|
||||||
*/
|
|
||||||
private function request(string $method, string $path, array $query = [], ?array $body = null): array
|
|
||||||
{
|
{
|
||||||
$url = $this->baseUrl . '/' . ltrim($path, '/');
|
$clone = clone $this;
|
||||||
if (!empty($query)) {
|
$clone->version = Calco2latoApiVersion::ensure($version);
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function url(string $path): string
|
||||||
|
{
|
||||||
|
$path = '/' . ltrim($path, '/');
|
||||||
|
return "{$this->baseUrl}/{$this->version}{$path}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<mixed> */
|
||||||
|
private function request(string $method, string $path, array $query = [], ?array $json = null): array
|
||||||
|
{
|
||||||
|
$url = $this->url($path);
|
||||||
|
if ($query) {
|
||||||
$url .= (str_contains($url, '?') ? '&' : '?') . http_build_query($query);
|
$url .= (str_contains($url, '?') ? '&' : '?') . http_build_query($query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,62 +59,73 @@ final class Calco2latoApiClient
|
|||||||
'Accept: application/json',
|
'Accept: application/json',
|
||||||
'Authorization: Bearer ' . $this->apiKey,
|
'Authorization: Bearer ' . $this->apiKey,
|
||||||
];
|
];
|
||||||
curl_setopt_array($ch, [
|
|
||||||
|
$opts = [
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
CURLOPT_CUSTOMREQUEST => strtoupper($method),
|
CURLOPT_CUSTOMREQUEST => strtoupper($method),
|
||||||
CURLOPT_HTTPHEADER => $headers,
|
CURLOPT_HTTPHEADER => $headers,
|
||||||
CURLOPT_TIMEOUT => $this->timeout,
|
CURLOPT_TIMEOUT => $this->timeout,
|
||||||
]);
|
];
|
||||||
|
|
||||||
if ($body !== null) {
|
if ($json !== null) {
|
||||||
$json = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
$payload = json_encode($json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
$headers[] = 'Content-Type: application/json';
|
$headers[] = 'Content-Type: application/json';
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
$opts[CURLOPT_HTTPHEADER] = $headers;
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
|
$opts[CURLOPT_POSTFIELDS] = $payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
$responseBody = curl_exec($ch);
|
curl_setopt_array($ch, $opts);
|
||||||
|
$raw = curl_exec($ch);
|
||||||
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
$err = curl_error($ch);
|
$err = curl_error($ch);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
if ($responseBody === false) {
|
if ($raw === false) {
|
||||||
throw new RuntimeException('cURL error: ' . $err);
|
throw new RuntimeException('cURL error: ' . $err);
|
||||||
}
|
}
|
||||||
|
|
||||||
$decoded = json_decode($responseBody, true);
|
$decoded = json_decode($raw, true);
|
||||||
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
|
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
|
||||||
throw new RuntimeException('Invalid JSON from API (HTTP ' . $status . '): ' . $responseBody);
|
throw new RuntimeException("Invalid JSON from API (HTTP {$status}): {$raw}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($status < 200 || $status >= 300) {
|
if ($status < 200 || $status >= 300) {
|
||||||
$msg = $decoded['error'] ?? $decoded['message'] ?? 'Upstream API error';
|
$msg = is_array($decoded) ? ($decoded['error'] ?? $decoded['message'] ?? 'Upstream API error') : 'Upstream API error';
|
||||||
throw new RuntimeException('API error (HTTP ' . $status . '): ' . $msg);
|
throw new RuntimeException("API error (HTTP {$status}): {$msg}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var array<mixed> $decoded */
|
||||||
return $decoded;
|
return $decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Airports ----------
|
// ===================== Airports =====================
|
||||||
|
|
||||||
/** Search airports by free-text (IATA/ICAO/name/city/country) */
|
/** GET /{v}/airports — query: page, per_page, sort_by, order, iata */
|
||||||
public function searchAirports(string $q, int $per_page = 20, int $page = 0): array
|
public function airports_get(array $query = []): array
|
||||||
{
|
{
|
||||||
return $this->request('GET', '/latest/transport/airports', [
|
// Only pass the fields your spec declares (extra keys are harmless but we keep it tidy)
|
||||||
'iata' => $q,
|
$allowed = ['page','per_page','sort_by','order','iata'];
|
||||||
'per_page' => $per_page,
|
$q = [];
|
||||||
'page' => $page,
|
foreach ($allowed as $k) {
|
||||||
]);
|
if (array_key_exists($k, $query) && $query[$k] !== null) {
|
||||||
|
$q[$k] = $query[$k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->request('GET', '/transport/airports', $q, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Flights ----------
|
/** POST /{v}/airports — body: AirportsRequest */
|
||||||
|
public function airports_post(array $airportsRequest = []): array
|
||||||
/**
|
|
||||||
* Get flight estimate / emissions (example – adjust to your API)
|
|
||||||
* $params might include origin, destination, date, pax, class, etc.
|
|
||||||
*/
|
|
||||||
public function flightEstimate(array $params): array
|
|
||||||
{
|
{
|
||||||
return $this->request('POST', '/latest/transport/flight', [], $params);
|
// Accepts the exact schema: page, per_page, sort_by, order, iata
|
||||||
|
return $this->request('POST', '/transport/airports', [], $airportsRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== Flight =====================
|
||||||
|
|
||||||
|
/** POST /{v}/flight — body: FlightRequest */
|
||||||
|
public function flight_post(array $flightRequest): array
|
||||||
|
{
|
||||||
|
// The spec requires "flights" in the body; api_key could be included, but we already send Bearer
|
||||||
|
return $this->request('POST', '/transport/flight', [], $flightRequest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user