#include "services/UserService.h" #include "repositories/Sql.h" #include #include namespace visione::services { namespace { using repositories::JsonFieldType; using repositories::JsonRowSchema; Json::Value failure(const std::string &code) { Json::Value body; body["ok"] = false; body["error"] = code; return body; } Json::Value success() { Json::Value body; body["ok"] = true; return body; } int boundedPage(const std::optional &value) { if (!value) { return 1; } try { return std::max(1, std::stoi(*value)); } catch (...) { return 1; } } int boundedPerPage(const std::optional &value) { if (!value) { return 50; } try { return std::clamp(std::stoi(*value), 1, 100); } catch (...) { return 50; } } const JsonRowSchema ¬ificationSchema() { static const JsonRowSchema schema{ {"id", JsonFieldType::UInt64}, {"report_id", JsonFieldType::UInt64}}; return schema; } const JsonRowSchema &rewardEventSchema() { static const JsonRowSchema schema{ {"id", JsonFieldType::UInt64}, {"report_id", JsonFieldType::UInt64}, {"points", JsonFieldType::Int64}}; return schema; } } // namespace Json::Value UserService::listNotifications( const auth::UserContext &user, const std::optional &page, const std::optional &perPage) { const auto pageValue = boundedPage(page); const auto perPageValue = boundedPerPage(perPage); const auto offset = (pageValue - 1) * perPageValue; auto response = success(); response["page"] = pageValue; response["per_page"] = perPageValue; response["notifications"] = repositories::resultToJson( drogon::app().getDbClient()->execSqlSync( "SELECT id, type, title, body, report_id, read_at, created_at " "FROM notifications WHERE user_id = ? " "ORDER BY created_at DESC LIMIT ? OFFSET ?", user.id, perPageValue, offset), notificationSchema()); return response; } Json::Value UserService::markNotificationRead(const auth::UserContext &user, long long notificationId) { auto db = drogon::app().getDbClient(); const auto rows = db->execSqlSync( "SELECT id FROM notifications WHERE id = ? AND user_id = ?", notificationId, user.id); if (rows.empty()) { return failure("notification_not_found"); } db->execSqlSync( "UPDATE notifications SET read_at = COALESCE(read_at, NOW()) WHERE id = ?", notificationId); return success(); } Json::Value UserService::rewards(const auth::UserContext &user, const std::optional &page, const std::optional &perPage) { const auto pageValue = boundedPage(page); const auto perPageValue = boundedPerPage(perPage); const auto offset = (pageValue - 1) * perPageValue; auto db = drogon::app().getDbClient(); const auto userRows = db->execSqlSync( "SELECT points FROM users WHERE id = ?", user.id); auto response = success(); response["points"] = userRows.empty() ? 0 : userRows[0]["points"].as(); response["page"] = pageValue; response["per_page"] = perPageValue; response["reward_events"] = repositories::resultToJson(db->execSqlSync( "SELECT id, report_id, points, reason, created_at " "FROM reward_events WHERE user_id = ? " "ORDER BY created_at DESC LIMIT ? OFFSET ?", user.id, perPageValue, offset), rewardEventSchema()); return response; } void UserService::createNotification(long long userId, const std::string &type, const std::string &title, const std::string &body, std::optional reportId) { auto db = drogon::app().getDbClient(); if (reportId) { db->execSqlSync( "INSERT INTO notifications (user_id, type, title, body, report_id) " "VALUES (?, ?, ?, ?, ?)", userId, type, title, body, *reportId); } else { db->execSqlSync( "INSERT INTO notifications (user_id, type, title, body) " "VALUES (?, ?, ?, ?)", userId, type, title, body); } } void UserService::awardReportOwner(long long reportId, const std::string &reason, int points) { auto db = drogon::app().getDbClient(); const auto reportRows = db->execSqlSync( "SELECT user_id FROM reports WHERE id = ?", reportId); if (reportRows.empty()) { return; } const auto userId = reportRows[0]["user_id"].as(); const auto existing = db->execSqlSync( "SELECT id FROM reward_events WHERE report_id = ? AND reason = ?", reportId, reason); if (!existing.empty()) { return; } db->execSqlSync( "INSERT INTO reward_events (user_id, report_id, points, reason) " "VALUES (?, ?, ?, ?)", userId, reportId, points, reason); db->execSqlSync( "UPDATE users SET points = points + ? WHERE id = ?", points, userId); createNotification(userId, "reward_awarded", "Points awarded", "Your resolved report earned points.", reportId); } } // namespace visione::services