文件
csp/backend/src/controllers/me_controller.cc

350 行
10 KiB
C++

#include "csp/controllers/me_controller.h"
#include "csp/app_state.h"
#include "csp/domain/json.h"
#include "csp/services/daily_task_service.h"
#include "csp/services/redeem_service.h"
#include "csp/services/solution_access_service.h"
#include "csp/services/user_service.h"
#include "csp/services/wrong_book_service.h"
#include "http_auth.h"
#include <algorithm>
#include <exception>
#include <optional>
#include <stdexcept>
#include <string>
namespace csp::controllers {
namespace {
drogon::HttpResponsePtr JsonError(drogon::HttpStatusCode code,
const std::string &msg) {
Json::Value j;
j["ok"] = false;
j["error"] = msg;
auto resp = drogon::HttpResponse::newHttpJsonResponse(j);
resp->setStatusCode(code);
return resp;
}
drogon::HttpResponsePtr JsonOk(const Json::Value &data) {
Json::Value j;
j["ok"] = true;
j["data"] = data;
auto resp = drogon::HttpResponse::newHttpJsonResponse(j);
resp->setStatusCode(drogon::k200OK);
return resp;
}
std::optional<int64_t>
RequireAuth(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &cb) {
std::string auth_error;
const auto user_id = GetAuthedUserId(req, auth_error);
if (!user_id.has_value()) {
cb(JsonError(drogon::k401Unauthorized, auth_error));
return std::nullopt;
}
return user_id;
}
int ParseClampedInt(const std::string &s, int default_value, int min_value,
int max_value) {
if (s.empty())
return default_value;
const int value = std::stoi(s);
return std::max(min_value, std::min(max_value, value));
}
} // namespace
void MeController::profile(
const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&cb) {
try {
const auto user_id = RequireAuth(req, cb);
if (!user_id.has_value())
return;
services::UserService users(csp::AppState::Instance().db());
const auto user = users.GetById(*user_id);
if (!user.has_value()) {
cb(JsonError(drogon::k404NotFound, "user not found"));
return;
}
cb(JsonOk(domain::ToPublicJson(*user)));
} catch (const std::exception &e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));
}
}
void MeController::listRedeemItems(
const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&cb) {
try {
if (!RequireAuth(req, cb).has_value())
return;
services::RedeemService redeem(csp::AppState::Instance().db());
const auto items = redeem.ListItems(false);
Json::Value arr(Json::arrayValue);
for (const auto &item : items) {
Json::Value j;
j["id"] = Json::Int64(item.id);
j["name"] = item.name;
j["description"] = item.description;
j["unit_label"] = item.unit_label;
j["holiday_cost"] = item.holiday_cost;
j["studyday_cost"] = item.studyday_cost;
j["is_active"] = item.is_active;
j["is_global"] = item.is_global;
j["created_at"] = Json::Int64(item.created_at);
j["updated_at"] = Json::Int64(item.updated_at);
arr.append(j);
}
cb(JsonOk(arr));
} catch (const std::exception &e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));
}
}
void MeController::listRedeemRecords(
const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&cb) {
try {
const auto user_id = RequireAuth(req, cb);
if (!user_id.has_value())
return;
const int limit = ParseClampedInt(req->getParameter("limit"), 100, 1, 500);
services::RedeemService redeem(csp::AppState::Instance().db());
const auto rows = redeem.ListRecordsByUser(*user_id, limit);
Json::Value arr(Json::arrayValue);
for (const auto &row : rows) {
Json::Value j;
j["id"] = Json::Int64(row.id);
j["user_id"] = Json::Int64(row.user_id);
j["item_id"] = Json::Int64(row.item_id);
j["item_name"] = row.item_name;
j["quantity"] = row.quantity;
j["day_type"] = row.day_type;
j["unit_cost"] = row.unit_cost;
j["total_cost"] = row.total_cost;
j["note"] = row.note;
j["created_at"] = Json::Int64(row.created_at);
arr.append(j);
}
cb(JsonOk(arr));
} catch (const std::invalid_argument &) {
cb(JsonError(drogon::k400BadRequest, "invalid query parameter"));
} catch (const std::out_of_range &) {
cb(JsonError(drogon::k400BadRequest, "query parameter out of range"));
} catch (const std::exception &e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));
}
}
void MeController::createRedeemRecord(
const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&cb) {
try {
const auto user_id = RequireAuth(req, cb);
if (!user_id.has_value())
return;
const auto json = req->getJsonObject();
if (!json) {
cb(JsonError(drogon::k400BadRequest, "body must be json"));
return;
}
services::RedeemRequest request;
request.user_id = *user_id;
request.item_id = (*json).get("item_id", 0).asInt64();
request.quantity = (*json).get("quantity", 1).asInt();
request.day_type = (*json).get("day_type", "studyday").asString();
request.note = (*json).get("note", "").asString();
services::RedeemService redeem(csp::AppState::Instance().db());
const auto row = redeem.Redeem(request);
services::UserService users(csp::AppState::Instance().db());
const auto user = users.GetById(*user_id);
Json::Value j;
j["id"] = Json::Int64(row.id);
j["user_id"] = Json::Int64(row.user_id);
j["item_id"] = Json::Int64(row.item_id);
j["item_name"] = row.item_name;
j["quantity"] = row.quantity;
j["day_type"] = row.day_type;
j["unit_cost"] = row.unit_cost;
j["total_cost"] = row.total_cost;
j["note"] = row.note;
j["created_at"] = Json::Int64(row.created_at);
if (user.has_value()) {
j["rating_after"] = user->rating;
}
cb(JsonOk(j));
} catch (const std::runtime_error &e) {
cb(JsonError(drogon::k400BadRequest, e.what()));
} catch (const std::exception &e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));
}
}
void MeController::listDailyTasks(
const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&cb) {
try {
const auto user_id = RequireAuth(req, cb);
if (!user_id.has_value())
return;
services::DailyTaskService tasks(csp::AppState::Instance().db());
const auto rows = tasks.ListTodayTasks(*user_id);
Json::Value arr(Json::arrayValue);
int total_reward = 0;
int gained_reward = 0;
for (const auto &row : rows) {
Json::Value j;
j["code"] = row.code;
j["title"] = row.title;
j["description"] = row.description;
j["reward"] = row.reward;
j["completed"] = row.completed;
if (row.completed) {
j["completed_at"] = Json::Int64(row.completed_at);
gained_reward += row.reward;
} else {
j["completed_at"] = Json::nullValue;
}
total_reward += row.reward;
arr.append(j);
}
Json::Value out;
out["day_key"] = tasks.CurrentDayKey();
out["total_reward"] = total_reward;
out["gained_reward"] = gained_reward;
out["tasks"] = arr;
cb(JsonOk(out));
} catch (const std::exception &e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));
}
}
void MeController::listWrongBook(
const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&cb) {
try {
const auto user_id = RequireAuth(req, cb);
if (!user_id.has_value())
return;
services::WrongBookService wrong_book(csp::AppState::Instance().db());
const auto rows = wrong_book.ListByUser(*user_id);
Json::Value arr(Json::arrayValue);
for (const auto &row : rows) {
Json::Value item = domain::ToJson(row.item);
item["problem_title"] = row.problem_title;
arr.append(item);
}
cb(JsonOk(arr));
} catch (const std::exception &e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));
}
}
void MeController::upsertWrongBookNote(
const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&cb,
int64_t problem_id) {
try {
const auto user_id = RequireAuth(req, cb);
if (!user_id.has_value())
return;
const auto json = req->getJsonObject();
if (!json) {
cb(JsonError(drogon::k400BadRequest, "body must be json"));
return;
}
const std::string note = (*json).get("note", "").asString();
if (note.size() > 4000) {
cb(JsonError(drogon::k400BadRequest, "note too long"));
return;
}
services::WrongBookService wrong_book(csp::AppState::Instance().db());
wrong_book.UpsertNote(*user_id, problem_id, note);
Json::Value data;
data["user_id"] = Json::Int64(*user_id);
data["problem_id"] = Json::Int64(problem_id);
data["note"] = note;
cb(JsonOk(data));
} catch (const std::exception &e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));
}
}
void MeController::deleteWrongBookItem(
const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&cb,
int64_t problem_id) {
try {
const auto user_id = RequireAuth(req, cb);
if (!user_id.has_value())
return;
services::WrongBookService wrong_book(csp::AppState::Instance().db());
wrong_book.Remove(*user_id, problem_id);
Json::Value data;
data["user_id"] = Json::Int64(*user_id);
data["problem_id"] = Json::Int64(problem_id);
data["deleted"] = true;
cb(JsonOk(data));
} catch (const std::exception &e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));
}
}
void MeController::listRatingHistory(
const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&cb) {
try {
const auto user_id = RequireAuth(req, cb);
if (!user_id.has_value())
return;
const int limit = ParseClampedInt(req->getParameter("limit"), 100, 1, 500);
services::SolutionAccessService access_svc(csp::AppState::Instance().db());
const auto rows = access_svc.ListRatingHistory(*user_id, limit);
Json::Value arr(Json::arrayValue);
for (const auto &row : rows) {
Json::Value j;
j["type"] = row.type;
j["created_at"] = Json::Int64(row.created_at);
j["change"] = row.change;
j["note"] = row.note;
arr.append(j);
}
cb(JsonOk(arr));
} catch (const std::exception &e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));
}
}
} // namespace csp::controllers