#include "server/api_server.hpp" #include "util/file_util.hpp" #include "util/http_util.hpp" namespace lm { void ApiServer::registerMediaRoutes(httplib::Server& svr) { svr.Get("/api/sites", [this](const httplib::Request&, httplib::Response& res) { HttpUtil::addCors(res); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = nlohmann::json({{"sites", site_store_.list()}}).dump(); }); svr.Post("/api/sites", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); nlohmann::json body; try { body = nlohmann::json::parse(req.body); } catch (...) { return HttpUtil::jsonError(res, 400, "invalid JSON"); } std::string err; const auto created = site_store_.create(body, err); if (!created) return HttpUtil::jsonError(res, 400, err); res.status = 201; res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = created->dump(); }); svr.Put(R"(/api/sites/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; nlohmann::json body; try { body = nlohmann::json::parse(req.body); } catch (...) { return HttpUtil::jsonError(res, 400, "invalid JSON"); } std::string err; if (!site_store_.update(id, body, err)) return HttpUtil::jsonError(res, 404, err); const auto updated = site_store_.find(id); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = updated ? updated->dump() : nlohmann::json::object().dump(); }); svr.Delete(R"(/api/sites/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; std::string err; if (!site_store_.remove(id, err)) return HttpUtil::jsonError(res, 400, err); res.status = 204; }); svr.Get("/api/maps", [this](const httplib::Request&, httplib::Response& res) { HttpUtil::addCors(res); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = nlohmann::json({{"maps", map_store_.list()}}).dump(); }); svr.Get(R"(/api/maps/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; const auto map = map_store_.find(id); if (!map) return HttpUtil::jsonError(res, 404, "map not found"); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = map->dump(); }); svr.Post("/api/maps", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); nlohmann::json body; try { body = nlohmann::json::parse(req.body); } catch (...) { return HttpUtil::jsonError(res, 400, "invalid JSON"); } std::string err; const auto created = map_store_.create(body, err); if (!created) return HttpUtil::jsonError(res, 400, err); res.status = 201; res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = created->dump(); }); svr.Put(R"(/api/maps/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; nlohmann::json body; try { body = nlohmann::json::parse(req.body); } catch (...) { return HttpUtil::jsonError(res, 400, "invalid JSON"); } std::string err; if (!map_store_.update(id, body, err)) return HttpUtil::jsonError(res, 404, err); const auto updated = map_store_.find(id); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = updated ? updated->dump() : nlohmann::json::object().dump(); }); svr.Delete(R"(/api/maps/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; std::string err; if (!map_store_.remove(id, err)) return HttpUtil::jsonError(res, 404, err); robot_runtime_.clearActiveMapIf(id); res.status = 204; }); svr.Get(R"(/api/maps/([^/]+)/image$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; const auto path = map_store_.imagePath(id); if (!path) return HttpUtil::jsonError(res, 404, "map image not found"); res.set_header("Content-Type", HttpUtil::contentTypeForPath(*path)); res.body = FileUtil::readBinary(*path); }); svr.Post(R"(/api/maps/([^/]+)/image$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; if (!req.form.has_file("file")) return HttpUtil::jsonError(res, 400, "file is required"); const auto& file = req.form.get_file("file"); const std::string filename = file.filename.empty() ? "map.png" : file.filename; std::string err; if (!map_store_.saveImageFile(id, filename, file.content, err)) return HttpUtil::jsonError(res, 400, err); const auto updated = map_store_.find(id); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = updated ? updated->dump() : nlohmann::json::object().dump(); }); svr.Get(R"(/api/maps/([^/]+)/yaml$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; const auto path = map_store_.yamlPath(id); if (!path) return HttpUtil::jsonError(res, 404, "map yaml not found"); res.set_header("Content-Type", "text/yaml; charset=utf-8"); res.body = FileUtil::readBinary(*path); }); svr.Post(R"(/api/maps/([^/]+)/yaml$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; if (req.body.empty()) return HttpUtil::jsonError(res, 400, "yaml body is required"); std::string err; if (!map_store_.saveYamlFile(id, req.body, err)) return HttpUtil::jsonError(res, 400, err); const auto updated = map_store_.find(id); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = updated ? updated->dump() : nlohmann::json::object().dump(); }); svr.Get("/api/sounds", [this](const httplib::Request&, httplib::Response& res) { HttpUtil::addCors(res); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = nlohmann::json({{"sounds", sound_store_.list()}}).dump(); }); svr.Get(R"(/api/sounds/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; const auto sound = sound_store_.find(id); if (!sound) return HttpUtil::jsonError(res, 404, "sound not found"); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = sound->dump(); }); svr.Post("/api/sounds", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); nlohmann::json body; try { body = nlohmann::json::parse(req.body); } catch (...) { return HttpUtil::jsonError(res, 400, "invalid JSON"); } std::string err; const auto created = sound_store_.create(body, err); if (!created) return HttpUtil::jsonError(res, 400, err); res.status = 201; res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = created->dump(); }); svr.Put(R"(/api/sounds/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; nlohmann::json body; try { body = nlohmann::json::parse(req.body); } catch (...) { return HttpUtil::jsonError(res, 400, "invalid JSON"); } std::string err; if (!sound_store_.update(id, body, err)) return HttpUtil::jsonError(res, 404, err); const auto updated = sound_store_.find(id); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = updated ? updated->dump() : nlohmann::json::object().dump(); }); svr.Delete(R"(/api/sounds/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; std::string err; if (!sound_store_.remove(id, err)) return HttpUtil::jsonError(res, 404, err); res.status = 204; }); svr.Get(R"(/api/sounds/([^/]+)/file$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; const auto path = sound_store_.filePath(id); if (!path) return HttpUtil::jsonError(res, 404, "sound file not found"); res.set_header("Content-Type", HttpUtil::contentTypeForPath(*path)); res.body = FileUtil::readBinary(*path); }); svr.Post(R"(/api/sounds/([^/]+)/file$)", [this](const httplib::Request& req, httplib::Response& res) { HttpUtil::addCors(res); const std::string id = req.matches[1]; if (!req.form.has_file("file")) return HttpUtil::jsonError(res, 400, "file is required"); const auto& file = req.form.get_file("file"); const std::string filename = file.filename.empty() ? (id + ".wav") : file.filename; std::string err; if (!sound_store_.saveFile(id, filename, file.content, err)) return HttpUtil::jsonError(res, 400, err); const auto updated = sound_store_.find(id); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = updated ? updated->dump() : nlohmann::json::object().dump(); }); svr.Get("/api/recordings", [](const httplib::Request&, httplib::Response& res) { HttpUtil::addCors(res); res.set_header("Content-Type", "application/json; charset=utf-8"); res.body = nlohmann::json({{"recordings", nlohmann::json::array()}}).dump(); }); } } // namespace lm