feat: add daily tasks and fix /admin139 admin entry

这个提交包含在:
Codex CLI
2026-02-15 12:51:42 +08:00
父节点 e2ab522b78
当前提交 ad29a9f62d
修改 13 个文件,包含 1200 行新增30 行删除

查看文件

@@ -208,6 +208,47 @@ void InsertContestProblem(sqlite3* db,
sqlite3_finalize(stmt);
}
void InsertRedeemItem(sqlite3* db,
const std::string& name,
const std::string& description,
const std::string& unit_label,
int holiday_cost,
int studyday_cost,
int is_active,
int is_global,
int64_t created_by,
int64_t created_at) {
sqlite3_stmt* stmt = nullptr;
const char* sql =
"INSERT INTO redeem_items("
"name,description,unit_label,holiday_cost,studyday_cost,is_active,is_global,created_by,created_at,updated_at"
") VALUES(?,?,?,?,?,?,?,?,?,?)";
ThrowSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare insert redeem_item");
ThrowSqlite(sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT), db,
"bind redeem_item.name");
ThrowSqlite(sqlite3_bind_text(stmt, 2, description.c_str(), -1, SQLITE_TRANSIENT),
db, "bind redeem_item.description");
ThrowSqlite(sqlite3_bind_text(stmt, 3, unit_label.c_str(), -1, SQLITE_TRANSIENT),
db, "bind redeem_item.unit_label");
ThrowSqlite(sqlite3_bind_int(stmt, 4, holiday_cost), db,
"bind redeem_item.holiday_cost");
ThrowSqlite(sqlite3_bind_int(stmt, 5, studyday_cost), db,
"bind redeem_item.studyday_cost");
ThrowSqlite(sqlite3_bind_int(stmt, 6, is_active), db,
"bind redeem_item.is_active");
ThrowSqlite(sqlite3_bind_int(stmt, 7, is_global), db,
"bind redeem_item.is_global");
ThrowSqlite(sqlite3_bind_int64(stmt, 8, created_by), db,
"bind redeem_item.created_by");
ThrowSqlite(sqlite3_bind_int64(stmt, 9, created_at), db,
"bind redeem_item.created_at");
ThrowSqlite(sqlite3_bind_int64(stmt, 10, created_at), db,
"bind redeem_item.updated_at");
ThrowSqlite(sqlite3_step(stmt), db, "insert redeem_item");
sqlite3_finalize(stmt);
}
} // namespace
SqliteDb SqliteDb::OpenFile(const std::string& path) {
@@ -446,6 +487,71 @@ CREATE TABLE IF NOT EXISTS problem_solutions (
updated_at INTEGER NOT NULL,
FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS problem_solution_view_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
problem_id INTEGER NOT NULL,
day_key TEXT NOT NULL,
viewed_at INTEGER NOT NULL,
charged INTEGER NOT NULL DEFAULT 0,
cost INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS submission_feedback (
id INTEGER PRIMARY KEY AUTOINCREMENT,
submission_id INTEGER NOT NULL UNIQUE,
feedback_md TEXT NOT NULL DEFAULT "",
links_json TEXT NOT NULL DEFAULT "[]",
model_name TEXT NOT NULL DEFAULT "",
status TEXT NOT NULL DEFAULT "ready",
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY(submission_id) REFERENCES submissions(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS redeem_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT "",
unit_label TEXT NOT NULL DEFAULT "小时",
holiday_cost INTEGER NOT NULL DEFAULT 5,
studyday_cost INTEGER NOT NULL DEFAULT 25,
is_active INTEGER NOT NULL DEFAULT 1,
is_global INTEGER NOT NULL DEFAULT 1,
created_by INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS redeem_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
item_id INTEGER NOT NULL,
item_name TEXT NOT NULL,
quantity INTEGER NOT NULL DEFAULT 1,
day_type TEXT NOT NULL DEFAULT "studyday",
unit_cost INTEGER NOT NULL DEFAULT 0,
total_cost INTEGER NOT NULL DEFAULT 0,
note TEXT NOT NULL DEFAULT "",
created_at INTEGER NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(item_id) REFERENCES redeem_items(id) ON DELETE RESTRICT
);
CREATE TABLE IF NOT EXISTS daily_task_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
task_code TEXT NOT NULL,
day_key TEXT NOT NULL,
reward INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user_id, task_code, day_key)
);
)SQL");
// Backward-compatible schema upgrades for existing deployments.
@@ -494,6 +600,11 @@ CREATE INDEX IF NOT EXISTS idx_import_job_items_job_status ON import_job_items(j
CREATE INDEX IF NOT EXISTS idx_problem_drafts_updated ON problem_drafts(updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_problem_solution_jobs_problem ON problem_solution_jobs(problem_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_problem_solutions_problem ON problem_solutions(problem_id, variant, id);
CREATE INDEX IF NOT EXISTS idx_solution_view_logs_user_problem ON problem_solution_view_logs(user_id, problem_id, viewed_at DESC);
CREATE INDEX IF NOT EXISTS idx_solution_view_logs_user_day ON problem_solution_view_logs(user_id, day_key, viewed_at DESC);
CREATE INDEX IF NOT EXISTS idx_redeem_items_active ON redeem_items(is_active, id);
CREATE INDEX IF NOT EXISTS idx_redeem_records_user_created ON redeem_records(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_daily_task_logs_user_day ON daily_task_logs(user_id, day_key, created_at DESC);
)SQL");
}
@@ -602,6 +713,20 @@ void SeedDemoData(SqliteDb& db) {
if (contest_id && p1) InsertContestProblem(raw, *contest_id, *p1, 1);
if (contest_id && p2) InsertContestProblem(raw, *contest_id, *p2, 2);
}
if (CountRows(raw, "redeem_items") == 0) {
InsertRedeemItem(
raw,
"私人玩游戏时间",
"全局用户可兑换:假期 1 小时 5 Rating;学习日/非节假日 1 小时 25 Rating。",
"小时",
5,
25,
1,
1,
0,
now);
}
}
} // namespace csp::db