#include "controllers/ReportController.h" #include "common/Auth.h" #include "common/Http.h" #include "services/ReportService.h" #include #include #include using drogon::HttpResponsePtr; namespace { std::optional queryValue(const drogon::HttpRequestPtr &req, const std::string &key) { const auto value = req->getParameter(key); if (value.empty()) { return std::nullopt; } return value; } std::optional requireUser( const drogon::HttpRequestPtr &req, std::function &callback) { const auto user = visione::auth::authenticate(req); if (!user) { callback(visione::http::error("unauthorized", drogon::k401Unauthorized)); } return user; } bool blockPasswordChangeRequired( const visione::auth::UserContext &user, std::function &callback) { if (!visione::auth::mustChangePassword(user)) { return false; } callback(visione::http::error("password_change_required", drogon::k403Forbidden)); return true; } void sendService(Json::Value body, std::function &&callback) { if (body.get("ok", false).asBool()) { callback(visione::http::json(std::move(body))); return; } const auto error = body.get("error", "").asString(); const auto status = error == "forbidden" ? drogon::k403Forbidden : drogon::k400BadRequest; callback(visione::http::json(std::move(body), status)); } bool isAllowedImage(const drogon::HttpFile &file) { const auto type = file.getContentType(); return type == drogon::CT_IMAGE_JPG || type == drogon::CT_IMAGE_PNG || type == drogon::CT_IMAGE_WEBP; } std::string imageContentType(const drogon::HttpFile &file) { switch (file.getContentType()) { case drogon::CT_IMAGE_JPG: return "image/jpeg"; case drogon::CT_IMAGE_PNG: return "image/png"; case drogon::CT_IMAGE_WEBP: return "image/webp"; default: return "application/octet-stream"; } } } // namespace void ReportController::create( const drogon::HttpRequestPtr &req, std::function &&callback) { auto user = requireUser(req, callback); if (!user || blockPasswordChangeRequired(*user, callback)) { return; } const auto body = visione::http::requireJson(req); if (body.isNull()) { callback(visione::http::error("invalid_json", drogon::k400BadRequest)); return; } visione::services::ReportService service; sendService(service.createReport(*user, body), std::move(callback)); } void ReportController::list( const drogon::HttpRequestPtr &req, std::function &&callback) { auto user = requireUser(req, callback); if (!user || blockPasswordChangeRequired(*user, callback)) { return; } visione::services::ReportService service; sendService(service.listReports(*user, queryValue(req, "status"), queryValue(req, "category_id"), queryValue(req, "area_id")), std::move(callback)); } void ReportController::detail( const drogon::HttpRequestPtr &req, std::function &&callback, long long reportId) { auto user = requireUser(req, callback); if (!user || blockPasswordChangeRequired(*user, callback)) { return; } visione::services::ReportService service; sendService(service.reportDetail(*user, reportId), std::move(callback)); } void ReportController::updateStatus( const drogon::HttpRequestPtr &req, std::function &&callback, long long reportId) { auto user = requireUser(req, callback); if (!user || blockPasswordChangeRequired(*user, callback)) { return; } const auto body = visione::http::requireJson(req); visione::services::ReportService service; sendService(service.updateStatus(*user, reportId, body), std::move(callback)); } void ReportController::takeReport( const drogon::HttpRequestPtr &req, std::function &&callback, long long reportId) { auto user = requireUser(req, callback); if (!user || blockPasswordChangeRequired(*user, callback)) { return; } visione::services::ReportService service; sendService(service.takeReport(*user, reportId), std::move(callback)); } void ReportController::addMessage( const drogon::HttpRequestPtr &req, std::function &&callback, long long reportId) { auto user = requireUser(req, callback); if (!user || blockPasswordChangeRequired(*user, callback)) { return; } const auto body = visione::http::requireJson(req); visione::services::ReportService service; sendService(service.addMessage(*user, reportId, body), std::move(callback)); } void ReportController::listMessages( const drogon::HttpRequestPtr &req, std::function &&callback, long long reportId) { auto user = requireUser(req, callback); if (!user || blockPasswordChangeRequired(*user, callback)) { return; } visione::services::ReportService service; sendService(service.listMessages(*user, reportId), std::move(callback)); } void ReportController::assignTeam( const drogon::HttpRequestPtr &req, std::function &&callback, long long reportId) { auto user = requireUser(req, callback); if (!user || blockPasswordChangeRequired(*user, callback)) { return; } const auto body = visione::http::requireJson(req); visione::services::ReportService service; sendService(service.assignTeam(*user, reportId, body), std::move(callback)); } void ReportController::addFeedback( const drogon::HttpRequestPtr &req, std::function &&callback, long long reportId) { auto user = requireUser(req, callback); if (!user || blockPasswordChangeRequired(*user, callback)) { return; } const auto body = visione::http::requireJson(req); visione::services::ReportService service; sendService(service.addFeedback(*user, reportId, body), std::move(callback)); } void ReportController::uploadPhotos( const drogon::HttpRequestPtr &req, std::function &&callback, long long reportId) { auto user = requireUser(req, callback); if (!user || blockPasswordChangeRequired(*user, callback)) { return; } drogon::MultiPartParser parser; if (parser.parse(req) != 0 || parser.getFiles().empty()) { callback(visione::http::error("invalid_multipart", drogon::k400BadRequest)); return; } const auto &uploadConfig = drogon::app().getCustomConfig()["uploads"]; const auto reportsPath = uploadConfig.get("reports_path", "uploads/reports").asString(); const auto publicBasePath = uploadConfig.get("public_base_path", "/uploads").asString(); const auto reportDir = std::filesystem::path(reportsPath) / std::to_string(reportId); visione::services::ReportService service; auto allowed = service.canAddPhoto(*user, reportId); if (!allowed.get("ok", false).asBool()) { callback(visione::http::json(std::move(allowed), drogon::k403Forbidden)); return; } if (parser.getFiles().size() > static_cast(allowed.get("remaining_photo_slots", 0).asInt())) { callback(visione::http::error("too_many_photos", drogon::k400BadRequest)); return; } for (const auto &file : parser.getFiles()) { if (!isAllowedImage(file)) { callback(visione::http::error("unsupported_photo_type", drogon::k400BadRequest)); return; } } std::filesystem::create_directories(reportDir); Json::Value response; response["ok"] = true; response["photos"] = Json::arrayValue; for (const auto &file : parser.getFiles()) { auto extension = std::string(file.getFileExtension()); if (!extension.empty() && extension.front() != '.') { extension = "." + extension; } const auto fileName = drogon::utils::getUuid() + extension; const auto fullPath = reportDir / fileName; if (file.saveAs(fullPath.string()) != 0) { callback(visione::http::error("photo_save_failed", drogon::k500InternalServerError)); return; } const auto url = publicBasePath + "/reports/" + std::to_string(reportId) + "/" + fileName; auto saved = service.addPhoto(*user, reportId, url, file.getFileName(), imageContentType(file), file.fileLength()); if (!saved.get("ok", false).asBool()) { callback(visione::http::json(std::move(saved), drogon::k403Forbidden)); return; } response["photos"].append(url); } callback(visione::http::json(std::move(response), drogon::k201Created)); }