feat: expand platform management, admin controls, and learning workflows

这个提交包含在:
Codex CLI
2026-02-15 15:41:56 +08:00
父节点 ad29a9f62d
当前提交 f209ae82da
修改 75 个文件,包含 9663 行新增794 行删除

查看文件

@@ -5,6 +5,7 @@
#include "csp/services/problem_service.h"
#include "csp/services/problem_solution_runner.h"
#include "csp/services/problem_workspace_service.h"
#include "csp/services/solution_access_service.h"
#include "http_auth.h"
#include <algorithm>
@@ -192,7 +193,7 @@ void ProblemController::saveDraft(
}
void ProblemController::listSolutions(
const drogon::HttpRequestPtr& /*req*/,
const drogon::HttpRequestPtr& req,
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
int64_t problem_id) {
try {
@@ -203,28 +204,72 @@ void ProblemController::listSolutions(
}
const auto rows = svc.ListSolutions(problem_id);
const bool has_solutions = !rows.empty();
const auto latest_job = svc.GetLatestSolutionJob(problem_id);
const std::string mode = req->getParameter("mode");
const bool need_full = mode == "full";
Json::Value arr(Json::arrayValue);
for (const auto& item : rows) {
Json::Value j;
j["id"] = Json::Int64(item.id);
j["problem_id"] = Json::Int64(item.problem_id);
j["variant"] = item.variant;
j["title"] = item.title;
j["idea_md"] = item.idea_md;
j["explanation_md"] = item.explanation_md;
j["code_cpp"] = item.code_cpp;
j["complexity"] = item.complexity;
j["tags_json"] = item.tags_json;
j["source"] = item.source;
j["created_at"] = Json::Int64(item.created_at);
j["updated_at"] = Json::Int64(item.updated_at);
arr.append(j);
Json::Value access(Json::objectValue);
access["required"] = true;
access["daily_free_quota"] = 1;
access["cost_after_free"] = 2;
if (need_full && has_solutions) {
std::string auth_error;
const auto user_id = GetAuthedUserId(req, auth_error);
if (!user_id.has_value()) {
cb(JsonError(drogon::k401Unauthorized, auth_error));
return;
}
services::SolutionAccessService access_svc(csp::AppState::Instance().db());
const auto charge = access_svc.ConsumeSolutionView(*user_id, problem_id);
if (!charge.granted) {
cb(JsonError(drogon::k402PaymentRequired,
"rating 不足:首次免费后每次查看答案需 2 分"));
return;
}
access["mode"] = "full";
access["charged"] = charge.charged;
access["daily_free"] = charge.daily_free;
access["cost"] = charge.cost;
access["day_key"] = charge.day_key;
access["daily_used_count"] = charge.daily_used_count;
access["rating_before"] = charge.rating_before;
access["rating_after"] = charge.rating_after;
access["viewed_at"] = Json::Int64(charge.viewed_at);
for (const auto& item : rows) {
Json::Value j;
j["id"] = Json::Int64(item.id);
j["problem_id"] = Json::Int64(item.problem_id);
j["variant"] = item.variant;
j["title"] = item.title;
j["idea_md"] = item.idea_md;
j["explanation_md"] = item.explanation_md;
j["code_cpp"] = item.code_cpp;
j["complexity"] = item.complexity;
j["tags_json"] = item.tags_json;
j["source"] = item.source;
j["created_at"] = Json::Int64(item.created_at);
j["updated_at"] = Json::Int64(item.updated_at);
arr.append(j);
}
} else {
access["mode"] = "preview";
access["charged"] = false;
access["daily_free"] = false;
access["cost"] = 0;
access["daily_used_count"] = 0;
}
Json::Value payload;
payload["items"] = arr;
payload["has_solutions"] = has_solutions;
payload["answer_status"] = has_solutions ? "已有" : "待生成";
payload["access"] = access;
payload["runner_running"] =
services::ProblemSolutionRunner::Instance().IsRunning(problem_id);
if (latest_job.has_value()) {
@@ -280,16 +325,20 @@ void ProblemController::generateSolutions(
}
const int64_t job_id = svc.CreateSolutionJob(problem_id, *user_id, max_solutions);
const bool started = services::ProblemSolutionRunner::Instance().TriggerAsync(
auto& runner = services::ProblemSolutionRunner::Instance();
const bool queued = runner.TriggerAsync(
problem_id, job_id, max_solutions);
if (!started) {
cb(JsonError(drogon::k409Conflict, "solution generation is already running"));
if (!queued) {
cb(JsonError(drogon::k500InternalServerError,
"solution generation queue is unavailable"));
return;
}
Json::Value payload;
payload["queued"] = true;
payload["started"] = true;
payload["job_id"] = Json::Int64(job_id);
payload["pending_jobs"] = Json::UInt64(runner.PendingCount());
cb(JsonOk(payload));
} catch (const std::exception& e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));