Initial proof-of-concept commit
This commit is contained in:
+193
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$token = getenv( 'THERMOPRO_BRIDGE_TOKEN' ) ?: 'dev-secret';
|
||||
$dataPath = __DIR__ . '/data';
|
||||
$latestPath = $dataPath . '/latest.json';
|
||||
$logPath = $dataPath . '/readings.ndjson';
|
||||
|
||||
if ( ! is_dir( $dataPath ) ) {
|
||||
mkdir( $dataPath, 0775, true );
|
||||
}
|
||||
|
||||
function sendJson( array $payload, int $statusCode = 200 ): void {
|
||||
http_response_code( $statusCode );
|
||||
header( 'Content-Type: application/json' );
|
||||
|
||||
echo json_encode(
|
||||
$payload,
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
|
||||
);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
function getRequestPath(): string {
|
||||
return parse_url( $_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH ) ?: '/';
|
||||
}
|
||||
|
||||
function readJsonBody(): array {
|
||||
$body = file_get_contents( 'php://input' );
|
||||
$payload = json_decode( $body ?: '', true );
|
||||
|
||||
if ( ! is_array( $payload ) ) {
|
||||
sendJson(
|
||||
array(
|
||||
'ok' => false,
|
||||
'error' => 'Invalid JSON body',
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
function requireBridgeToken( string $expectedToken ): void {
|
||||
$providedToken = $_SERVER['HTTP_X_BRIDGE_TOKEN'] ?? '';
|
||||
|
||||
if ( ! hash_equals( $expectedToken, $providedToken ) ) {
|
||||
sendJson(
|
||||
array(
|
||||
'ok' => false,
|
||||
'error' => 'Invalid bridge token',
|
||||
),
|
||||
401
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateReading( array $payload ): void {
|
||||
$requiredFields = array(
|
||||
'deviceId',
|
||||
'connected',
|
||||
'battery',
|
||||
'unit',
|
||||
'probes',
|
||||
'readingTime',
|
||||
);
|
||||
|
||||
foreach ( $requiredFields as $field ) {
|
||||
if ( ! array_key_exists( $field, $payload ) ) {
|
||||
sendJson(
|
||||
array(
|
||||
'ok' => false,
|
||||
'error' => 'Missing required field: ' . $field,
|
||||
),
|
||||
422
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_array( $payload['probes'] ) ) {
|
||||
sendJson(
|
||||
array(
|
||||
'ok' => false,
|
||||
'error' => 'The probes field must be an array',
|
||||
),
|
||||
422
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$path = getRequestPath();
|
||||
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||
|
||||
if ( $method === 'POST' && $path === '/api/thermopro/readings' ) {
|
||||
requireBridgeToken( $token );
|
||||
|
||||
$payload = readJsonBody();
|
||||
validateReading( $payload );
|
||||
|
||||
$payload['serverReceivedAt'] = gmdate( DATE_ATOM );
|
||||
|
||||
file_put_contents(
|
||||
$latestPath,
|
||||
json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES )
|
||||
);
|
||||
|
||||
file_put_contents(
|
||||
$logPath,
|
||||
json_encode( $payload, JSON_UNESCAPED_SLASHES ) . PHP_EOL,
|
||||
FILE_APPEND | LOCK_EX
|
||||
);
|
||||
|
||||
sendJson(
|
||||
array(
|
||||
'ok' => true,
|
||||
'receivedAt' => $payload['serverReceivedAt'],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( $method === 'GET' && $path === '/api/thermopro/latest' ) {
|
||||
if ( ! file_exists( $latestPath ) ) {
|
||||
sendJson(
|
||||
array(
|
||||
'ok' => false,
|
||||
'error' => 'No readings received yet',
|
||||
),
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
header( 'Content-Type: application/json' );
|
||||
readfile( $latestPath );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $method === 'GET' && ( $path === '/' || $path === '/status' ) ) {
|
||||
$latest = null;
|
||||
|
||||
if ( file_exists( $latestPath ) ) {
|
||||
$latest = json_decode( file_get_contents( $latestPath ), true );
|
||||
}
|
||||
|
||||
header( 'Content-Type: text/html; charset=utf-8' );
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ThermoPro Bridge POC</title>
|
||||
<meta http-equiv="refresh" content="10">
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
margin: 2rem;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f4f4;
|
||||
overflow: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>ThermoPro Bridge POC</h1>
|
||||
|
||||
<?php if ( $latest === null ) { ?>
|
||||
<p>No readings received yet.</p>
|
||||
<?php } else { ?>
|
||||
<p>
|
||||
Latest reading received at:
|
||||
<strong><?php echo htmlspecialchars( $latest['serverReceivedAt'] ?? 'unknown', ENT_QUOTES, 'UTF-8' ); ?></strong>
|
||||
</p>
|
||||
|
||||
<pre><?php echo htmlspecialchars( json_encode( $latest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), ENT_QUOTES, 'UTF-8' ); ?></pre>
|
||||
<?php } ?>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
|
||||
sendJson(
|
||||
array(
|
||||
'ok' => false,
|
||||
'error' => 'Not found',
|
||||
),
|
||||
404
|
||||
);
|
||||
Reference in New Issue
Block a user