feat: rebuild CSP practice workflow, UX and automation

这个提交包含在:
Codex CLI
2026-02-13 15:49:05 +08:00
父节点 d33deed4c5
当前提交 e2ab522b78
修改 105 个文件,包含 15669 行新增428 行删除

查看文件

@@ -0,0 +1,123 @@
#include "csp/services/wrong_book_service.h"
#include <sqlite3.h>
#include <chrono>
#include <stdexcept>
#include <string>
namespace csp::services {
namespace {
void CheckSqlite(int rc, sqlite3* db, const char* what) {
if (rc == SQLITE_OK || rc == SQLITE_ROW || rc == SQLITE_DONE) return;
throw std::runtime_error(std::string(what) + ": " + sqlite3_errmsg(db));
}
int64_t NowSec() {
using namespace std::chrono;
return duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
std::string ColText(sqlite3_stmt* stmt, int col) {
const unsigned char* txt = sqlite3_column_text(stmt, col);
return txt ? reinterpret_cast<const char*>(txt) : std::string();
}
} // namespace
std::vector<WrongBookEntry> WrongBookService::ListByUser(int64_t user_id) {
sqlite3* db = db_.raw();
sqlite3_stmt* stmt = nullptr;
const char* sql =
"SELECT w.user_id,w.problem_id,w.last_submission_id,w.note,w.updated_at,p.title "
"FROM wrong_book w "
"JOIN problems p ON p.id=w.problem_id "
"WHERE w.user_id=? ORDER BY w.updated_at DESC";
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare wrong_book list");
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
std::vector<WrongBookEntry> out;
while (sqlite3_step(stmt) == SQLITE_ROW) {
WrongBookEntry e;
e.item.user_id = sqlite3_column_int64(stmt, 0);
e.item.problem_id = sqlite3_column_int64(stmt, 1);
if (sqlite3_column_type(stmt, 2) == SQLITE_NULL) {
e.item.last_submission_id = std::nullopt;
} else {
e.item.last_submission_id = sqlite3_column_int64(stmt, 2);
}
e.item.note = ColText(stmt, 3);
e.item.updated_at = sqlite3_column_int64(stmt, 4);
e.problem_title = ColText(stmt, 5);
out.push_back(std::move(e));
}
sqlite3_finalize(stmt);
return out;
}
void WrongBookService::UpsertNote(int64_t user_id,
int64_t problem_id,
const std::string& note) {
sqlite3* db = db_.raw();
sqlite3_stmt* stmt = nullptr;
const char* sql =
"INSERT INTO wrong_book(user_id,problem_id,last_submission_id,note,updated_at) "
"VALUES(?,?,?,?,?) "
"ON CONFLICT(user_id,problem_id) DO UPDATE SET note=excluded.note,updated_at=excluded.updated_at";
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare wrong_book upsert note");
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
CheckSqlite(sqlite3_bind_null(stmt, 3), db, "bind last_submission_id");
CheckSqlite(sqlite3_bind_text(stmt, 4, note.c_str(), -1, SQLITE_TRANSIENT), db,
"bind note");
CheckSqlite(sqlite3_bind_int64(stmt, 5, NowSec()), db, "bind updated_at");
CheckSqlite(sqlite3_step(stmt), db, "wrong_book upsert note");
sqlite3_finalize(stmt);
}
void WrongBookService::UpsertBySubmission(int64_t user_id,
int64_t problem_id,
int64_t submission_id,
const std::string& note) {
sqlite3* db = db_.raw();
sqlite3_stmt* stmt = nullptr;
const char* sql =
"INSERT INTO wrong_book(user_id,problem_id,last_submission_id,note,updated_at) "
"VALUES(?,?,?,?,?) "
"ON CONFLICT(user_id,problem_id) DO UPDATE SET "
"last_submission_id=excluded.last_submission_id,"
"updated_at=excluded.updated_at,"
"note=CASE WHEN wrong_book.note='' THEN excluded.note ELSE wrong_book.note END";
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare wrong_book upsert by submission");
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
CheckSqlite(sqlite3_bind_int64(stmt, 3, submission_id), db,
"bind submission_id");
CheckSqlite(sqlite3_bind_text(stmt, 4, note.c_str(), -1, SQLITE_TRANSIENT), db,
"bind note");
CheckSqlite(sqlite3_bind_int64(stmt, 5, NowSec()), db, "bind updated_at");
CheckSqlite(sqlite3_step(stmt), db, "wrong_book upsert by submission");
sqlite3_finalize(stmt);
}
void WrongBookService::Remove(int64_t user_id, int64_t problem_id) {
sqlite3* db = db_.raw();
sqlite3_stmt* stmt = nullptr;
const char* sql = "DELETE FROM wrong_book WHERE user_id=? AND problem_id=?";
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare wrong_book delete");
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
CheckSqlite(sqlite3_step(stmt), db, "wrong_book delete");
sqlite3_finalize(stmt);
}
} // namespace csp::services