#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/user_service.h" #include "csp/services/wrong_book_service.h" #include "http_auth.h" #include #include #include #include #include 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 RequireAuth(const drogon::HttpRequestPtr& req, std::function& 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&& 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&& 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&& 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&& 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&& 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&& 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&& 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&& 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())); } } } // namespace csp::controllers