feat: problems local stats, user status, admin panel enhancements, rating text
- Problems page: replace Luogu pass rate with local submission stats
(local_submit_count, local_ac_count)
- Problems page: add user AC/fail status column (user_ac, user_fail_count)
- Admin users: add total_submissions and total_ac columns
- Admin users: add detail panel with submissions/rating/redeem tabs
- Admin: new endpoint GET /api/v1/admin/users/{id}/rating-history
- Rating history: note field includes problem title via JOIN
- Me page: translate task codes to friendly labels with icons
- Me page: problem links in rating history are clickable
- Wrong book service, learning note scoring, note image controller
- Backend SQL uses batch queries for performance
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
这个提交包含在:
@@ -27,6 +27,9 @@ class AdminController : public drogon::HttpController<AdminController> {
|
||||
ADD_METHOD_TO(AdminController::listRedeemRecords,
|
||||
"/api/v1/admin/redeem-records",
|
||||
drogon::Get);
|
||||
ADD_METHOD_TO(AdminController::userRatingHistory,
|
||||
"/api/v1/admin/users/{1}/rating-history",
|
||||
drogon::Get);
|
||||
METHOD_LIST_END
|
||||
|
||||
void listUsers(const drogon::HttpRequestPtr& req,
|
||||
@@ -55,6 +58,10 @@ class AdminController : public drogon::HttpController<AdminController> {
|
||||
|
||||
void listRedeemRecords(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb);
|
||||
|
||||
void userRatingHistory(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
|
||||
int64_t user_id);
|
||||
};
|
||||
|
||||
} // namespace csp::controllers
|
||||
|
||||
@@ -22,6 +22,12 @@ public:
|
||||
drogon::Get);
|
||||
ADD_METHOD_TO(MeController::upsertWrongBookNote, "/api/v1/me/wrong-book/{1}",
|
||||
drogon::Patch);
|
||||
ADD_METHOD_TO(MeController::scoreWrongBookNote, "/api/v1/me/wrong-book/{1}/note-score",
|
||||
drogon::Post);
|
||||
ADD_METHOD_TO(MeController::uploadWrongBookNoteImages, "/api/v1/me/wrong-book/{1}/note-images",
|
||||
drogon::Post);
|
||||
ADD_METHOD_TO(MeController::deleteWrongBookNoteImage, "/api/v1/me/wrong-book/{1}/note-images",
|
||||
drogon::Delete);
|
||||
ADD_METHOD_TO(MeController::deleteWrongBookItem, "/api/v1/me/wrong-book/{1}",
|
||||
drogon::Delete);
|
||||
ADD_METHOD_TO(MeController::listRatingHistory, "/api/v1/me/rating-history",
|
||||
@@ -55,6 +61,18 @@ public:
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&cb,
|
||||
int64_t problem_id);
|
||||
|
||||
void scoreWrongBookNote(const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&cb,
|
||||
int64_t problem_id);
|
||||
|
||||
void uploadWrongBookNoteImages(const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&cb,
|
||||
int64_t problem_id);
|
||||
|
||||
void deleteWrongBookNoteImage(const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&cb,
|
||||
int64_t problem_id);
|
||||
|
||||
void
|
||||
deleteWrongBookItem(const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&cb,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <drogon/HttpController.h>
|
||||
|
||||
namespace csp::controllers {
|
||||
|
||||
class NoteImageController : public drogon::HttpController<NoteImageController> {
|
||||
public:
|
||||
METHOD_LIST_BEGIN
|
||||
ADD_METHOD_TO(NoteImageController::getNoteImage, "/files/note-images/{1}", drogon::Get);
|
||||
METHOD_LIST_END
|
||||
|
||||
void getNoteImage(const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
|
||||
const std::string& filename);
|
||||
};
|
||||
|
||||
} // namespace csp::controllers
|
||||
@@ -86,6 +86,11 @@ struct WrongBookItem {
|
||||
int64_t problem_id = 0;
|
||||
std::optional<int64_t> last_submission_id;
|
||||
std::string note;
|
||||
int32_t note_score = 0;
|
||||
int32_t note_rating = 0;
|
||||
std::string note_feedback_md;
|
||||
std::string note_images_json; // JSON array of filenames
|
||||
int64_t note_scored_at = 0;
|
||||
int64_t updated_at = 0;
|
||||
};
|
||||
|
||||
@@ -127,6 +132,8 @@ struct GlobalLeaderboardEntry {
|
||||
std::string username;
|
||||
int32_t rating = 0;
|
||||
int64_t created_at = 0;
|
||||
int32_t total_submissions = 0;
|
||||
int32_t total_ac = 0;
|
||||
};
|
||||
|
||||
struct ContestLeaderboardEntry {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "csp/db/sqlite_db.h"
|
||||
#include "csp/domain/entities.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace csp::services {
|
||||
|
||||
struct LearningNoteScoreResult {
|
||||
int32_t score = 0;
|
||||
int32_t rating = 0;
|
||||
std::string feedback_md;
|
||||
std::string model_name;
|
||||
};
|
||||
|
||||
class LearningNoteScoringService {
|
||||
public:
|
||||
explicit LearningNoteScoringService(db::SqliteDb& db) : db_(db) {}
|
||||
|
||||
LearningNoteScoreResult Score(const std::string& note,
|
||||
const domain::Problem& problem);
|
||||
|
||||
private:
|
||||
db::SqliteDb& db_;
|
||||
};
|
||||
|
||||
} // namespace csp::services
|
||||
@@ -20,6 +20,13 @@ class WrongBookService {
|
||||
|
||||
std::vector<WrongBookEntry> ListByUser(int64_t user_id);
|
||||
void UpsertNote(int64_t user_id, int64_t problem_id, const std::string& note);
|
||||
std::string GetNoteImagesJson(int64_t user_id, int64_t problem_id);
|
||||
void SetNoteImagesJson(int64_t user_id, int64_t problem_id, const std::string& note_images_json);
|
||||
void UpsertNoteScore(int64_t user_id,
|
||||
int64_t problem_id,
|
||||
int32_t note_score,
|
||||
int32_t note_rating,
|
||||
const std::string& note_feedback_md);
|
||||
void UpsertBySubmission(int64_t user_id,
|
||||
int64_t problem_id,
|
||||
int64_t submission_id,
|
||||
|
||||
在新工单中引用
屏蔽一个用户