feat: expand platform management, admin controls, and learning workflows
这个提交包含在:
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <drogon/HttpController.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace csp::controllers {
|
||||
|
||||
class AdminController : public drogon::HttpController<AdminController> {
|
||||
public:
|
||||
METHOD_LIST_BEGIN
|
||||
ADD_METHOD_TO(AdminController::listUsers, "/api/v1/admin/users", drogon::Get);
|
||||
ADD_METHOD_TO(AdminController::updateUserRating,
|
||||
"/api/v1/admin/users/{1}/rating",
|
||||
drogon::Patch);
|
||||
ADD_METHOD_TO(AdminController::listRedeemItems, "/api/v1/admin/redeem-items", drogon::Get);
|
||||
ADD_METHOD_TO(AdminController::createRedeemItem, "/api/v1/admin/redeem-items", drogon::Post);
|
||||
ADD_METHOD_TO(AdminController::updateRedeemItem,
|
||||
"/api/v1/admin/redeem-items/{1}",
|
||||
drogon::Patch);
|
||||
ADD_METHOD_TO(AdminController::deleteRedeemItem,
|
||||
"/api/v1/admin/redeem-items/{1}",
|
||||
drogon::Delete);
|
||||
ADD_METHOD_TO(AdminController::listRedeemRecords,
|
||||
"/api/v1/admin/redeem-records",
|
||||
drogon::Get);
|
||||
METHOD_LIST_END
|
||||
|
||||
void listUsers(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
|
||||
void updateUserRating(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
|
||||
int64_t user_id);
|
||||
|
||||
void listRedeemItems(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
|
||||
void createRedeemItem(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
|
||||
void updateRedeemItem(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
|
||||
int64_t item_id);
|
||||
|
||||
void deleteRedeemItem(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
|
||||
int64_t item_id);
|
||||
|
||||
void listRedeemRecords(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
};
|
||||
|
||||
} // namespace csp::controllers
|
||||
@@ -8,12 +8,32 @@ class MetaController : public drogon::HttpController<MetaController> {
|
||||
public:
|
||||
METHOD_LIST_BEGIN
|
||||
ADD_METHOD_TO(MetaController::openapi, "/api/openapi.json", drogon::Get);
|
||||
ADD_METHOD_TO(MetaController::backendLogs, "/api/v1/backend/logs", drogon::Get);
|
||||
ADD_METHOD_TO(MetaController::kbRefreshStatus, "/api/v1/backend/kb/refresh", drogon::Get);
|
||||
ADD_METHOD_TO(MetaController::triggerKbRefresh, "/api/v1/backend/kb/refresh", drogon::Post);
|
||||
ADD_METHOD_TO(MetaController::triggerMissingSolutions, "/api/v1/backend/solutions/generate-missing",
|
||||
drogon::Post);
|
||||
ADD_METHOD_TO(MetaController::mcp, "/api/v1/mcp", drogon::Post);
|
||||
METHOD_LIST_END
|
||||
|
||||
void openapi(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
|
||||
void backendLogs(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
|
||||
void kbRefreshStatus(
|
||||
const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
|
||||
void triggerKbRefresh(
|
||||
const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
|
||||
void triggerMissingSolutions(
|
||||
const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
|
||||
void mcp(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ class SubmissionController : public drogon::HttpController<SubmissionController>
|
||||
ADD_METHOD_TO(SubmissionController::submitProblem, "/api/v1/problems/{1}/submit", drogon::Post);
|
||||
ADD_METHOD_TO(SubmissionController::listSubmissions, "/api/v1/submissions", drogon::Get);
|
||||
ADD_METHOD_TO(SubmissionController::getSubmission, "/api/v1/submissions/{1}", drogon::Get);
|
||||
ADD_METHOD_TO(SubmissionController::analyzeSubmission, "/api/v1/submissions/{1}/analysis", drogon::Post);
|
||||
ADD_METHOD_TO(SubmissionController::runCpp, "/api/v1/run/cpp", drogon::Post);
|
||||
METHOD_LIST_END
|
||||
|
||||
@@ -26,6 +27,10 @@ class SubmissionController : public drogon::HttpController<SubmissionController>
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
|
||||
int64_t submission_id);
|
||||
|
||||
void analyzeSubmission(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
|
||||
int64_t submission_id);
|
||||
|
||||
void runCpp(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
};
|
||||
|
||||
@@ -73,6 +73,7 @@ struct Submission {
|
||||
std::string code;
|
||||
SubmissionStatus status = SubmissionStatus::Pending;
|
||||
int32_t score = 0;
|
||||
int32_t rating_delta = 0;
|
||||
int32_t time_ms = 0;
|
||||
int32_t memory_kb = 0;
|
||||
std::string compile_log;
|
||||
|
||||
@@ -20,6 +20,7 @@ class AuthService {
|
||||
// Throws on error; for controller we will catch and convert to JSON.
|
||||
AuthResult Register(const std::string& username, const std::string& password);
|
||||
AuthResult Login(const std::string& username, const std::string& password);
|
||||
void ResetPassword(const std::string& username, const std::string& new_password);
|
||||
|
||||
std::optional<int> VerifyToken(const std::string& token);
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
namespace csp::services {
|
||||
|
||||
struct ImportRunOptions {
|
||||
std::string mode = "luogu";
|
||||
bool clear_all_problems = false;
|
||||
std::string local_pdf_dir;
|
||||
int target_total = 5000;
|
||||
int workers = 3;
|
||||
};
|
||||
|
||||
class ImportRunner {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace csp::services {
|
||||
|
||||
class KbImportRunner {
|
||||
public:
|
||||
static KbImportRunner& Instance();
|
||||
|
||||
void Configure(std::string db_path);
|
||||
bool TriggerAsync(const std::string& trigger);
|
||||
|
||||
bool IsRunning() const;
|
||||
std::string LastCommand() const;
|
||||
std::optional<int> LastExitCode() const;
|
||||
int64_t LastStartedAt() const;
|
||||
int64_t LastFinishedAt() const;
|
||||
std::string LastTrigger() const;
|
||||
|
||||
private:
|
||||
KbImportRunner() = default;
|
||||
|
||||
mutable std::mutex mu_;
|
||||
std::string db_path_;
|
||||
bool running_ = false;
|
||||
std::string last_command_;
|
||||
std::optional<int> last_exit_code_;
|
||||
int64_t last_started_at_ = 0;
|
||||
int64_t last_finished_at_ = 0;
|
||||
std::string last_trigger_;
|
||||
};
|
||||
|
||||
} // namespace csp::services
|
||||
@@ -1,28 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "csp/db/sqlite_db.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace csp::services {
|
||||
|
||||
class ProblemSolutionRunner {
|
||||
public:
|
||||
struct TriggerMissingSummary {
|
||||
int missing_total = 0;
|
||||
int candidate_count = 0;
|
||||
int queued_count = 0;
|
||||
};
|
||||
|
||||
static ProblemSolutionRunner& Instance();
|
||||
|
||||
void Configure(std::string db_path);
|
||||
|
||||
bool TriggerAsync(int64_t problem_id, int64_t job_id, int max_solutions);
|
||||
TriggerMissingSummary TriggerMissingAsync(db::SqliteDb& db,
|
||||
int64_t created_by,
|
||||
int max_solutions,
|
||||
int limit);
|
||||
void AutoStartMissingIfEnabled(db::SqliteDb& db);
|
||||
bool IsRunning(int64_t problem_id) const;
|
||||
size_t PendingCount() const;
|
||||
|
||||
private:
|
||||
struct Task {
|
||||
int64_t problem_id = 0;
|
||||
int64_t job_id = 0;
|
||||
int max_solutions = 3;
|
||||
};
|
||||
|
||||
ProblemSolutionRunner() = default;
|
||||
void StartWorkerIfNeededLocked();
|
||||
void WorkerLoop();
|
||||
void RecoverQueuedJobsLocked();
|
||||
void StartAutoPumpIfNeeded(db::SqliteDb* db, int max_solutions, int limit, int interval_sec);
|
||||
|
||||
std::string db_path_;
|
||||
mutable std::mutex mu_;
|
||||
std::set<int64_t> running_problem_ids_;
|
||||
std::deque<Task> queue_;
|
||||
std::unordered_map<int64_t, size_t> pending_problem_counts_;
|
||||
size_t pending_jobs_ = 0;
|
||||
bool worker_running_ = false;
|
||||
bool recovered_from_db_ = false;
|
||||
bool auto_pump_started_ = false;
|
||||
};
|
||||
|
||||
} // namespace csp::services
|
||||
|
||||
@@ -34,6 +34,7 @@ struct ProblemSolution {
|
||||
struct ProblemSolutionJob {
|
||||
int64_t id = 0;
|
||||
int64_t problem_id = 0;
|
||||
std::string problem_title;
|
||||
std::string status;
|
||||
int progress = 0;
|
||||
std::string message;
|
||||
@@ -60,7 +61,13 @@ class ProblemWorkspaceService {
|
||||
|
||||
int64_t CreateSolutionJob(int64_t problem_id, int64_t created_by, int max_solutions);
|
||||
std::optional<ProblemSolutionJob> GetLatestSolutionJob(int64_t problem_id);
|
||||
std::vector<ProblemSolutionJob> ListRecentSolutionJobs(int limit);
|
||||
std::vector<ProblemSolutionJob> ListSolutionJobsByStatus(const std::string& status,
|
||||
int limit);
|
||||
std::vector<ProblemSolution> ListSolutions(int64_t problem_id);
|
||||
int CountProblemsWithoutSolutions();
|
||||
std::vector<int64_t> ListProblemIdsWithoutSolutions(int limit,
|
||||
bool exclude_queued_or_running_jobs);
|
||||
|
||||
private:
|
||||
db::SqliteDb& db_;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "csp/db/sqlite_db.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace csp::services {
|
||||
|
||||
struct RedeemItem {
|
||||
int64_t id = 0;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string unit_label;
|
||||
int holiday_cost = 0;
|
||||
int studyday_cost = 0;
|
||||
bool is_active = true;
|
||||
bool is_global = true;
|
||||
int64_t created_by = 0;
|
||||
int64_t created_at = 0;
|
||||
int64_t updated_at = 0;
|
||||
};
|
||||
|
||||
struct RedeemItemWrite {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string unit_label = "小时";
|
||||
int holiday_cost = 5;
|
||||
int studyday_cost = 25;
|
||||
bool is_active = true;
|
||||
bool is_global = true;
|
||||
};
|
||||
|
||||
struct RedeemRecord {
|
||||
int64_t id = 0;
|
||||
int64_t user_id = 0;
|
||||
int64_t item_id = 0;
|
||||
std::string item_name;
|
||||
int quantity = 1;
|
||||
std::string day_type;
|
||||
int unit_cost = 0;
|
||||
int total_cost = 0;
|
||||
std::string note;
|
||||
int64_t created_at = 0;
|
||||
std::string username;
|
||||
};
|
||||
|
||||
struct RedeemRequest {
|
||||
int64_t user_id = 0;
|
||||
int64_t item_id = 0;
|
||||
int quantity = 1;
|
||||
std::string day_type = "studyday";
|
||||
std::string note;
|
||||
};
|
||||
|
||||
class RedeemService {
|
||||
public:
|
||||
explicit RedeemService(db::SqliteDb& db) : db_(db) {}
|
||||
|
||||
std::vector<RedeemItem> ListItems(bool include_inactive);
|
||||
std::optional<RedeemItem> GetItemById(int64_t item_id);
|
||||
RedeemItem CreateItem(int64_t admin_user_id, const RedeemItemWrite& input);
|
||||
RedeemItem UpdateItem(int64_t item_id, const RedeemItemWrite& input);
|
||||
void DeactivateItem(int64_t item_id);
|
||||
|
||||
std::vector<RedeemRecord> ListRecordsByUser(int64_t user_id, int limit);
|
||||
std::vector<RedeemRecord> ListRecordsAll(std::optional<int64_t> user_id, int limit);
|
||||
|
||||
RedeemRecord Redeem(const RedeemRequest& request);
|
||||
|
||||
private:
|
||||
db::SqliteDb& db_;
|
||||
};
|
||||
|
||||
} // namespace csp::services
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "csp/db/sqlite_db.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace csp::services {
|
||||
|
||||
struct SolutionViewChargeResult {
|
||||
bool granted = true;
|
||||
bool charged = false;
|
||||
bool daily_free = false;
|
||||
int cost = 0;
|
||||
int rating_before = 0;
|
||||
int rating_after = 0;
|
||||
int daily_used_count = 0;
|
||||
int64_t viewed_at = 0;
|
||||
std::string day_key;
|
||||
std::string deny_reason;
|
||||
};
|
||||
|
||||
struct SolutionViewStats {
|
||||
bool has_viewed = false;
|
||||
int total_views = 0;
|
||||
int total_cost = 0;
|
||||
std::optional<int64_t> last_viewed_at;
|
||||
};
|
||||
|
||||
class SolutionAccessService {
|
||||
public:
|
||||
explicit SolutionAccessService(db::SqliteDb& db) : db_(db) {}
|
||||
|
||||
// Daily policy: first answer view is free, then each full view costs 2 rating.
|
||||
SolutionViewChargeResult ConsumeSolutionView(int64_t user_id, int64_t problem_id);
|
||||
|
||||
SolutionViewStats QueryUserProblemViewStats(int64_t user_id, int64_t problem_id);
|
||||
|
||||
private:
|
||||
db::SqliteDb& db_;
|
||||
};
|
||||
|
||||
} // namespace csp::services
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "csp/db/sqlite_db.h"
|
||||
#include "csp/domain/entities.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace csp::services {
|
||||
|
||||
struct SubmissionFeedback {
|
||||
int64_t submission_id = 0;
|
||||
std::string feedback_md;
|
||||
std::string links_json;
|
||||
std::string model_name;
|
||||
std::string status;
|
||||
int64_t created_at = 0;
|
||||
int64_t updated_at = 0;
|
||||
};
|
||||
|
||||
class SubmissionFeedbackService {
|
||||
public:
|
||||
explicit SubmissionFeedbackService(db::SqliteDb& db) : db_(db) {}
|
||||
|
||||
std::optional<SubmissionFeedback> GetBySubmissionId(int64_t submission_id);
|
||||
|
||||
SubmissionFeedback GenerateAndSave(const domain::Submission& submission,
|
||||
const domain::Problem& problem,
|
||||
bool force_refresh);
|
||||
|
||||
private:
|
||||
db::SqliteDb& db_;
|
||||
};
|
||||
|
||||
} // namespace csp::services
|
||||
@@ -9,12 +9,19 @@
|
||||
|
||||
namespace csp::services {
|
||||
|
||||
struct UserListResult {
|
||||
std::vector<domain::GlobalLeaderboardEntry> items;
|
||||
int total_count = 0;
|
||||
};
|
||||
|
||||
class UserService {
|
||||
public:
|
||||
explicit UserService(db::SqliteDb& db) : db_(db) {}
|
||||
|
||||
std::optional<domain::User> GetById(int64_t id);
|
||||
std::vector<domain::GlobalLeaderboardEntry> GlobalLeaderboard(int limit = 100);
|
||||
UserListResult ListUsers(int page, int page_size);
|
||||
void SetRating(int64_t user_id, int rating);
|
||||
|
||||
private:
|
||||
db::SqliteDb& db_;
|
||||
|
||||
在新工单中引用
屏蔽一个用户