sess->get('user_id') === null) return Response::json(['error' => 'unauthorized'], 401); if (!Rbac::can((string)$this->sess->get('role'), $perm)) return Response::json(['error' => 'forbidden'], 403); return null; } public function list(Request $req, array $params): Response { if ($this->sess->get('user_id') === null) return Response::json(['error' => 'unauthorized'], 401); return Response::json(['zones' => $this->zones->forCamera((string)($params['camera'] ?? ''))], 200); } public function save(Request $req): Response { if ($g = $this->guard('edit_zones')) return $g; if (!Csrf::check((string)$this->sess->get('csrf'), (string)$req->header('X-Csrf-Token'))) { return Response::json(['error' => 'csrf'], 403); } $b = $req->json(); $level = (string)($b['level'] ?? ''); $polygon = $b['polygon'] ?? []; if (!in_array($level, self::LEVELS, true) || !is_array($polygon) || !Polygon::validate($polygon)) { return Response::json(['error' => 'invalid_zone'], 400); } $zoneId = $this->zones->save([ 'zone_id' => $b['zone_id'] ?? null, 'device_id' => (string)($b['device_id'] ?? ''), 'camera_id' => (string)($b['camera_id'] ?? ''), 'level' => $level, 'polygon_json' => json_encode($polygon), ]); $this->audit->append((string)$this->sess->get('username'), 'zone_save', "zone:$zoneId", [], ['level' => $level]); return Response::json(['zone_id' => $zoneId], 201); } public function delete(Request $req, array $params): Response { if ($g = $this->guard('edit_zones')) return $g; if (!Csrf::check((string)$this->sess->get('csrf'), (string)$req->header('X-Csrf-Token'))) { return Response::json(['error' => 'csrf'], 403); } $id = (string)($params['id'] ?? ''); if (!$this->zones->delete($id)) return Response::json(['error' => 'not_found'], 404); $this->audit->append((string)$this->sess->get('username'), 'zone_delete', "zone:$id", [], []); return Response::json(['ok' => true], 200); } }