Files
backend/services/UserService.cc
T

230 lines
6.1 KiB
C++

#include "services/UserService.h"
#include "repositories/Sql.h"
#include <drogon/drogon.h>
#include <algorithm>
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<std::string> &value)
{
if (!value)
{
return 1;
}
try
{
return std::max(1, std::stoi(*value));
}
catch (...)
{
return 1;
}
}
int boundedPerPage(const std::optional<std::string> &value)
{
if (!value)
{
return 50;
}
try
{
return std::clamp(std::stoi(*value), 1, 100);
}
catch (...)
{
return 50;
}
}
const JsonRowSchema &notificationSchema()
{
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<std::string> &page,
const std::optional<std::string> &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::markAllNotificationsRead(const auth::UserContext &user)
{
drogon::app().getDbClient()->execSqlSync(
"UPDATE notifications "
"SET read_at = COALESCE(read_at, NOW()) "
"WHERE user_id = ?",
user.id);
return success();
}
Json::Value UserService::rewards(const auth::UserContext &user,
const std::optional<std::string> &page,
const std::optional<std::string> &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<int>();
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<long long> 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<long long>();
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