The PHP app runs on Cafe24 shared web hosting -- calling it 'cloud' was misleading. Renames the directory, the edge client module, and every doc/script/identifier that used the term. - cloud/ -> server/ (incl. server/deploy/build_cafe24_package.sh paths) - edge/cloudclient.py -> edge/serverclient.py, CloudClient -> ServerClient - cloud/docs/cloud-deploy.md -> server/docs/server-deploy.md - CLOUD_* env/config keys -> SERVER_* - tests renamed to match Tests: PHP 109 pass (4 skipped), pytest 167 pass. dist/ regenerated.
64 lines
3.5 KiB
PHP
64 lines
3.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
namespace App\Ingest;
|
|
|
|
final class EventValidator
|
|
{
|
|
private const TYPES = ['fall','no_motion','immobility','ghost','zone_warning',
|
|
'zone_restricted','pinch','monitoring_degraded','device_fault'];
|
|
private const SEVERITIES = ['critical','high','warning','info'];
|
|
private const REQUIRED = ['device_id','camera_id','event_type','severity','occurred_at'];
|
|
|
|
public static function validate(array $p): array
|
|
{
|
|
$errors = [];
|
|
foreach (self::REQUIRED as $k) {
|
|
if (!array_key_exists($k, $p) || $p[$k] === '' || $p[$k] === null) $errors[] = "missing:$k";
|
|
}
|
|
if (isset($p['event_type']) && !in_array($p['event_type'], self::TYPES, true)) $errors[] = 'bad:event_type';
|
|
if (isset($p['severity']) && !in_array($p['severity'], self::SEVERITIES, true)) $errors[] = 'bad:severity';
|
|
$snaps = $p['snapshots'] ?? [];
|
|
if (!is_array($snaps)) $errors[] = 'bad:snapshots';
|
|
elseif (count($snaps) > 3) $errors[] = 'too_many:snapshots';
|
|
else foreach ($snaps as $s) if (!is_string($s)) { $errors[] = 'bad:snapshot_item'; break; }
|
|
|
|
// Type guards for required fields
|
|
if (isset($p['device_id']) && !is_string($p['device_id'])) $errors[] = 'bad:device_id';
|
|
if (isset($p['camera_id']) && !is_string($p['camera_id'])) $errors[] = 'bad:camera_id';
|
|
if (isset($p['occurred_at']) && !is_numeric($p['occurred_at'])) $errors[] = 'bad:occurred_at';
|
|
|
|
// Type guards for optional fields
|
|
if (isset($p['track_id']) && !is_numeric($p['track_id'])) $errors[] = 'bad:track_id';
|
|
if (isset($p['zone_id']) && !is_string($p['zone_id'])) $errors[] = 'bad:zone_id';
|
|
if (isset($p['duration_s']) && !is_numeric($p['duration_s'])) $errors[] = 'bad:duration_s';
|
|
// Optional idempotency key: the edge generates a stable 32-hex uuid per event and re-sends
|
|
// it on retry so the server can dedup. Enforce the exact shape (matches CHAR(32) + the
|
|
// snapshot-uuid convention); a malformed key is a bug, not a silent pass.
|
|
if (isset($p['event_uuid']) && (!is_string($p['event_uuid']) || !preg_match('/^[0-9a-f]{32}$/', $p['event_uuid']))) {
|
|
$errors[] = 'bad:event_uuid';
|
|
}
|
|
|
|
// Length caps matching the DB columns (device_id/camera_id VARCHAR(64), zone_id
|
|
// CHAR(32)). Without these an overlong id is a MySQL 'Data too long' 500 that the edge
|
|
// re-queues and retries forever; a 400 lets it drop the poison event instead.
|
|
foreach (['device_id' => 64, 'camera_id' => 64, 'zone_id' => 32] as $k => $max) {
|
|
if (isset($p[$k]) && is_string($p[$k]) && mb_strlen($p[$k]) > $max) $errors[] = "too_long:$k";
|
|
}
|
|
|
|
if ($errors) return ['ok' => false, 'errors' => $errors, 'clean' => []];
|
|
|
|
$clean = [
|
|
'event_uuid' => isset($p['event_uuid']) ? (string)$p['event_uuid'] : null,
|
|
'device_id' => (string)$p['device_id'],
|
|
'camera_id' => (string)$p['camera_id'],
|
|
'event_type' => (string)$p['event_type'],
|
|
'severity' => (string)$p['severity'],
|
|
'track_id' => isset($p['track_id']) ? (int)$p['track_id'] : null,
|
|
'zone_id' => isset($p['zone_id']) ? (string)$p['zone_id'] : null,
|
|
'occurred_at' => (float)$p['occurred_at'],
|
|
'duration_s' => (float)($p['duration_s'] ?? 0.0),
|
|
'metrics' => is_array($p['metrics'] ?? null) ? $p['metrics'] : [],
|
|
];
|
|
return ['ok' => true, 'errors' => [], 'clean' => $clean];
|
|
}
|
|
}
|