Compare commits

..

No commits in common. "066464018d697364c576c1ce0b1784cf8348968c" and "ba541e0e64b8e53087623eb22d1460fc36b0990d" have entirely different histories.

8 changed files with 63 additions and 1068 deletions

View File

@ -1,31 +0,0 @@
#ifndef DB_PROBLEMS_H
#define DB_PROBLEMS_H
#ifdef __cplusplus
extern "C"
{
#endif
int open_problems_db();
void close_problems_db();
int add_problem(const char* problem, const char* answer, const char* check_error, int* result);
int delete_problems(int problems_id);
int get_problem(int id, char** result, char** result2, char** result3);
int modify_problem(int problems_id, const char* problem, const char* answer, const char* check_error);
int check_answer(int problems_id, const char* answer, int* result);
int has_problem(int problems_id, int* result);
int all_problems(char** result);
#ifdef __cplusplus
}
#endif
#endif // DB_PROBLEMS_H

View File

@ -7,6 +7,8 @@ void res_null_req(mg_connection* conn);
void res_must_get(mg_connection* conn);
void res_must_post(mg_connection* conn);
void res_need_token(mg_connection* conn);
void res_auth_fail(mg_connection* conn);
void res_unauth(mg_connection* conn);
void res_check_exist_fail(mg_connection* conn);
void res_user_exist(mg_connection* conn);
void res_not_exist(mg_connection* conn);
@ -15,10 +17,9 @@ void res_need_user_id(mg_connection* conn);
void res_need_password(mg_connection* conn);
void res_need_action(mg_connection* conn);
void res_bad_action(mg_connection* conn);
void res_need_xxx(mg_connection* conn,const char* xxx);
void res_(mg_connection* conn);
void res_auth_fail(mg_connection* conn);
void res_unauth(mg_connection* conn);
void res_auth(mg_connection* conn);
void res_delete_account_fail(mg_connection* conn);
void res_delete_account(mg_connection* conn);
@ -42,15 +43,9 @@ void res_permission(mg_connection* conn, int permission);
void res_repermission_fail(mg_connection* conn);
void res_repermission(mg_connection* conn);
void res_need_xxx(mg_connection* conn, const char* xxx);
void res_500(mg_connection* conn, const char* error);
void res_404(mg_connection* conn, const char* error);
void res_200(mg_connection* conn, const char* success);
void res_add_problem(mg_connection* conn, int id);
void res_query_problem(mg_connection* conn, const char* problem, const char* check_error);
void res_query_problem2(mg_connection* conn, const char* problem, const char* answer, const char* check_error);
void res_check_answer(mg_connection* conn, int flag);
void res_all_problems(mg_connection* conn, const char* problems);
void res_(mg_connection* conn);
void res_(mg_connection* conn);
void res_(mg_connection* conn);
void res_(mg_connection* conn);
#endif

View File

@ -1,10 +0,0 @@
#ifndef SERVER_PROBLEMS_H
#define SERVER_PROBLEMS_H
#include "server/types.h"
int problems_handler(mg_connection* conn, void* cbdata);
extern char* secret;
#endif // SERVER_PROBLEMS_H

View File

@ -7,13 +7,26 @@
#include <leveldb/db.h>
#include <leveldb/write_batch.h>
#include <nlohmann/json.hpp>
using leveldb_ptr = leveldb::DB*;
auto user_db = leveldb_ptr{nullptr};
auto iter_round = 10000;
auto filter(const std::string_view id) -> bool
{
return id.contains(':');
}
auto mangle_user_id(const std::string_view id) -> std::string
{
return std::format("{}:user_id", id);
}
auto mangle_permission(const std::string_view id) -> std::string
{
return std::format("{}:permission", id);
}
extern "C"
{
auto open_user_db() -> int
@ -22,7 +35,7 @@ extern "C"
opts.create_if_missing = true;
auto status = leveldb::DB::Open(opts, "db/users", &user_db);
auto status = leveldb::DB::Open(opts, "db/user", &user_db);
if (!status.ok()) {
std::println(stderr, "Failed to open user database: {}", status.ToString());
@ -38,8 +51,10 @@ extern "C"
auto check_user_exists(const char* user_id, int* result) -> int
{
if (filter(user_id)) return 0;
auto value = std::string{};
auto status = user_db->Get(leveldb::ReadOptions{}, user_id, &value);
auto status = user_db->Get(leveldb::ReadOptions{}, mangle_user_id(user_id), &value);
if (status.ok()) {
*result = 1;
} else if (status.IsNotFound()) {
@ -53,16 +68,10 @@ extern "C"
auto set_user_password(const char* user_id, const char* password) -> int
{
auto value = std::string{};
auto status = user_db->Get(leveldb::ReadOptions{}, user_id, &value);
if (!status.ok()) {
std::println(stderr, "Failed to set user password: {}", status.ToString());
return 0;
}
auto json = nlohmann::json::parse(value);
json["hash"] = generate_hash(password, iter_round);
if (filter(user_id)) return 0;
status = user_db->Put(leveldb::WriteOptions{}, user_id, json.dump());
auto hash = generate_hash(password, iter_round);
auto status = user_db->Put(leveldb::WriteOptions{}, mangle_user_id(user_id), hash);
if (!status.ok()) {
std::println(stderr, "Failed to set user password: {}", status.ToString());
return 0;
@ -72,24 +81,26 @@ extern "C"
auto login(const char* user_id, const char* password, int* result) -> int
{
if (filter(user_id)) return 0;
auto value = std::string{};
auto status = user_db->Get(leveldb::ReadOptions{}, user_id, &value);
auto status = user_db->Get(leveldb::ReadOptions{}, mangle_user_id(user_id), &value);
if (!status.ok()) {
if (!status.IsNotFound()) std::println(stderr, "Failed to login: {}", status.ToString());
return 0;
}
auto json = nlohmann::json::parse(value);
*result = validate_password(password, json["hash"]);
*result = validate_password(password, value.data());
return 1;
}
int registe(const char* user_id, const char* password)
{
auto json = nlohmann::json{
{"hash", generate_hash(password, iter_round)},
{"permission", 2}
};
auto status = user_db->Put(leveldb::WriteOptions{}, user_id, json.dump());
if (filter(user_id)) return 0;
auto batch = leveldb::WriteBatch{};
batch.Put(mangle_user_id(user_id), generate_hash(password, iter_round));
batch.Put(mangle_permission(user_id), "2");
auto status = user_db->Write(leveldb::WriteOptions{}, &batch);
if (!status.ok()) {
std::println(stderr, "Failed to register: {}", status.ToString());
return 0;
@ -99,7 +110,12 @@ extern "C"
auto delete_user(const char* user_id) -> int
{
auto status = user_db->Delete(leveldb::WriteOptions{}, user_id);
if (filter(user_id)) return 0;
auto batch = leveldb::WriteBatch{};
batch.Delete(mangle_user_id(user_id));
batch.Delete(mangle_permission(user_id));
auto status = user_db->Write(leveldb::WriteOptions{}, &batch);
if (!status.ok()) {
std::println(stderr, "Failed to delete: {}", status.ToString());
return 0;
@ -109,29 +125,24 @@ extern "C"
int get_user_permission(const char* user_id, int* result)
{
if (filter(user_id)) return 0;
auto value = std::string{};
auto status = user_db->Get(leveldb::ReadOptions{}, user_id, &value);
if (!status.ok()) {
auto status = user_db->Get(leveldb::ReadOptions{}, mangle_permission(user_id), &value);
if (status.ok()) {
*result = std::stoi(value);
} else {
if (!status.IsNotFound()) std::println(stderr, "Failed to get user permission: {}", status.ToString());
return 0;
}
auto json = nlohmann::json::parse(value);
json["permission"].get_to(*result);
return 1;
}
int set_user_permission(const char* user_id, int permission)
{
auto value = std::string{};
auto status = user_db->Get(leveldb::ReadOptions{}, user_id, &value);
if (!status.ok()) {
std::println(stderr, "Failed to set user password: {}", status.ToString());
return 0;
}
auto json = nlohmann::json::parse(value);
json["permission"] = permission;
if (filter(user_id)) return 0;
status = user_db->Put(leveldb::WriteOptions{}, user_id, json.dump());
auto status = user_db->Put(leveldb::WriteOptions{}, mangle_permission(user_id), std::to_string(permission));
if (!status.ok()) {
std::println(stderr, "Failed to set user permission: {}", status.ToString());
return 0;
@ -141,10 +152,8 @@ extern "C"
int set_admin_password(const char* password)
{
auto json = nlohmann::json{
{"admin_password_hash", generate_hash(password, iter_round)}
};
auto status = user_db->Put(leveldb::WriteOptions{}, "@", json.dump());
auto hash = generate_hash(password, iter_round);
auto status = user_db->Put(leveldb::WriteOptions{}, "admin_password_hash", hash);
if (!status.ok()) {
std::println(stderr, "Failed to set admin password: {}", status.ToString());
return 0;
@ -155,21 +164,19 @@ extern "C"
int admin_login(const char* password, int* result)
{
auto value = std::string{};
auto status = user_db->Get(leveldb::ReadOptions{}, "@", &value);
auto status = user_db->Get(leveldb::ReadOptions{}, "admin_password_hash", &value);
if (!status.ok()) {
std::println(stderr, "Failed to login admin: {}", status.ToString());
return 0;
}
auto json = nlohmann::json::parse(value);
json["admin_password_hash"].get_to(value);
*result = validate_password(password, value);
*result = validate_password(password, value.data());
return 1;
}
int has_admin_password(int* result)
{
auto value = std::string{};
auto status = user_db->Get(leveldb::ReadOptions{}, "@", &value);
auto status = user_db->Get(leveldb::ReadOptions{}, "admin_password_hash", &value);
if (status.ok()) {
*result = 1;
} else if (status.IsNotFound()) {

View File

@ -1,194 +0,0 @@
#include "problems.h"
#include <print>
#include <leveldb/db.h>
#include <leveldb/write_batch.h>
#include <nlohmann/json.hpp>
#include <string>
using leveldb_ptr = leveldb::DB*;
auto problems_db = leveldb_ptr{nullptr};
auto max_problem_id = 0;
extern "C"
{
int open_problems_db()
{
auto opts = leveldb::Options{};
opts.create_if_missing = true;
auto status = leveldb::DB::Open(opts, "db/problems", &problems_db);
if (!status.ok()) {
std::println(stderr, "Failed to open problems database: {}", status.ToString());
return 0;
}
auto value = std::string{};
status = problems_db->Get(leveldb::ReadOptions{}, "@", &value);
if (status.ok()) {
auto json = nlohmann::json::parse(value);
json["max_problem_id"].get_to(max_problem_id);
} else if (status.IsNotFound()) {
auto json = nlohmann::json{
{"max_problem_id", 0}
};
status = problems_db->Put(leveldb::WriteOptions{}, "@", json.dump());
return 1;
} else {
std::println(stderr, "Failed to get max problem id: {}", status.ToString());
delete problems_db;
return 0;
}
return 1;
}
void close_problems_db()
{
delete problems_db;
}
int add_problem(const char* problem, const char* answer, const char* check_error, int* result)
{
auto batch = leveldb::WriteBatch{};
++max_problem_id;
auto json = nlohmann::json{
{"max_problem_id", max_problem_id}
};
batch.Put("@", json.dump());
json = nlohmann::json{
{"content", problem },
{"answer", answer },
{"error", check_error}
};
batch.Put(std::to_string(max_problem_id), json.dump());
auto status = problems_db->Write(leveldb::WriteOptions{}, &batch);
if (!status.ok()) {
std::println(stderr, "Failed to add a problem: {}", status.ToString());
return 0;
}
*result = max_problem_id;
return 1;
}
int delete_problems(int problems_id)
{
auto batch = leveldb::WriteBatch{};
auto status = problems_db->Delete(leveldb::WriteOptions{}, std::to_string(problems_id));
if (!status.ok()) {
std::println(stderr, "Failed to delete a problem: {}", status.ToString());
return 0;
}
return 1;
}
int get_problem(int id, char** result1, char** result2, char** result3)
{
auto value = std::string{};
auto status = problems_db->Get(leveldb::ReadOptions{}, std::to_string(id), &value);
if (!status.ok()) {
std::println(stderr, "Failed to get a problem: {}", status.ToString());
return 0;
}
auto json = nlohmann::json::parse(value);
if (result1) {
json["content"].get_to(value);
*result1 = strdup(value.data());
}
if (result2) {
json["answer"].get_to(value);
*result2 = strdup(value.data());
}
if (result3) {
json["error"].get_to(value);
*result3 = strdup(value.data());
}
return 1;
}
int modify_problem(int problems_id, const char* problem, const char* answer, const char* check_error)
{
auto json = nlohmann::json{
{"content", problem },
{"answer", answer },
{"error", check_error}
};
auto status = problems_db->Put(leveldb::WriteOptions{}, std::to_string(problems_id), json.dump());
if (!status.ok()) {
std::println(stderr, "Failed to modify a problem: {}", status.ToString());
return 0;
}
return 1;
}
int check_answer(int problems_id, const char* answer, int* result)
{
auto value = std::string{};
auto status = problems_db->Get(leveldb::ReadOptions{}, std::to_string(problems_id), &value);
if (!status.ok()) {
std::println(stderr, "Failed to check the answer: {}", status.ToString());
return 0;
}
auto value2 = std::string{};
auto json = nlohmann::json::parse(value);
json["answer"].get_to(value);
json["error"].get_to(value2);
if (value2.empty()) {
*result = value == answer;
} else {
auto error = std::stod(value2);
auto std_answer = std::stod(value);
auto user_answer = std::stod(answer);
*result = std::fabs(std_answer - user_answer) <= error;
}
return 1;
}
int has_problem(int problems_id, int* result)
{
auto value = std::string{};
auto status = problems_db->Get(leveldb::ReadOptions{}, std::to_string(problems_id), &value);
if (status.ok()) {
*result = 1;
} else if (status.IsNotFound()) {
*result = 0;
} else {
std::println(stderr, "Failed to check problem existence: {}", status.ToString());
return 0;
}
return 1;
}
int all_problems(char** result)
{
auto problems = std::vector<int>{};
leveldb::Iterator* it = problems_db->NewIterator(leveldb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
if (!it->key().compare("@")) continue;
problems.emplace_back(std::stoi(it->key().ToString()));
}
if (!it->status().ok()) {
std::println(stderr, "Failed to get all problems: {}", it->status().ToString());
return 0;
}
auto json = nlohmann::json(problems);
*result = strdup(json.dump().c_str());
return 1;
}
}

View File

@ -99,15 +99,11 @@ void res_need_password(mg_connection* conn)
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"error\":\"need password\"}");
}
void res_(mg_connection* conn);
void res_(mg_connection* conn);
void res_(mg_connection* conn);
void res_(mg_connection* conn);
void res_auth(mg_connection* conn)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"success\":\"auth\"}");
}
void res_delete_account_fail(mg_connection* conn)
{
@ -257,36 +253,6 @@ void res_need_xxx(mg_connection* conn, const char* xxx)
xxx);
}
void res_500(mg_connection* conn, const char* error)
{
mg_printf(conn,
"HTTP/1.1 400 Bad Request\r\n"
"Content-Type: application/json\r\n"
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"error\":\"%s\"}",
error);
}
void res_404(mg_connection* conn, const char* error)
{
mg_printf(conn,
"HTTP/1.1 400 Bad Request\r\n"
"Content-Type: application/json\r\n"
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"error\":\"%s\"}",
error);
}
void res_200(mg_connection* conn, const char* success)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"success\":\"%s\"}",
success);
}
void res_auth_fail(mg_connection* conn)
{
mg_printf(conn,
@ -295,53 +261,3 @@ void res_auth_fail(mg_connection* conn)
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"error\":\"failed to authorize\"}");
}
void res_add_problem(mg_connection* conn, int id)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"success\":\"add problem success\", \"id\":%d}",
id);
}
void res_query_problem(mg_connection* conn, const char* problem, const char* check_error)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"success\":\"query problem success\", \"problem\":\"%s\", \"check_error\":%s}",
problem,
check_error);
}
void res_query_problem2(mg_connection* conn, const char* problem, const char* answer, const char* check_error)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"success\":\"query problem success\", \"problem\":\"%s\",\"answer\":\"%s\", \"check_error\":%s}",
problem,
answer,
check_error);
}
void res_check_answer(mg_connection* conn, int flag)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"success\":\"success to check\", \"result\":%s}",
flag ? "true" : "false");
}
void res_all_problems(mg_connection* conn, const char* problems)
{
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Access-Control-Allow-Origin: *\r\n\r\n"
"{\"success\":\"success to get all the problems\", \"result\":%s}",
problems);
}

View File

@ -1,290 +0,0 @@
#include "db/problems.h"
#include "jwt/jwt.h"
#include "server/response.h"
#include "server/study.h"
#include "server/types.h"
#include "server/util.h"
#include "db/auth.h"
#include "hash/hash.h"
#include <civetweb.h>
#include <cjson/cJSON.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
char* action;
char* token;
int id;
int has_id;
char* problem;
char* answer;
char* error;
} problem_form_t;
static void problem_form_dtor(problem_form_t* form)
{
if (form->action) free(form->action);
if (form->token) free(form->token);
if (form->problem) free(form->problem);
if (form->answer) free(form->answer);
if (form->error) free(form->error);
}
static int field_found(const char* key, const char* filename, char* path, size_t pathlen, void* user_data)
{
return MG_FORM_FIELD_HANDLE_GET;
}
static int field_get(const char* key, const char* value, size_t valuelen, void* user_data)
{
problem_form_t* form = (problem_form_t*)user_data;
if (strcmp(key, "action") == 0) {
form->action = kqm_strndup(value, valuelen);
} else if (strcmp(key, "token") == 0) {
form->token = kqm_strndup(value, valuelen);
} else if (strcmp(key, "id") == 0) {
char* id_str = kqm_strndup(value, valuelen);
form->id = atoi(id_str);
form->has_id = 1;
free(id_str);
} else if (strcmp(key, "problem") == 0) {
form->problem = kqm_strndup(value, valuelen);
} else if (strcmp(key, "answer") == 0) {
form->answer = kqm_strndup(value, valuelen);
} else if (strcmp(key, "error") == 0) {
form->error = kqm_strndup(value, valuelen);
}
return MG_FORM_FIELD_HANDLE_GET;
}
static void impl_add(mg_connection* conn, problem_form_t* form)
{
if (!form->problem) {
res_need_xxx(conn, "problem content");
return;
}
if (!form->answer) {
res_need_xxx(conn, "standard answer");
return;
}
if (!form->error) {
res_need_xxx(conn, "checking error");
return;
}
int result;
int flag = add_problem(form->problem, form->answer, form->error, &result);
if (!flag) {
res_500(conn, "failed to add a problem");
return;
}
res_add_problem(conn, result);
}
static void impl_delete(mg_connection* conn, problem_form_t* form)
{
if (!form->has_id) {
res_need_xxx(conn, "problem id");
return;
}
int result;
if (!has_problem(form->id, &result)) {
res_500(conn, "fail to check problem existence");
return;
} else if (!result) {
res_404(conn, "problem do not exist");
return;
}
if (!delete_problems(form->id)) {
res_500(conn, "fail to delete problem");
return;
}
res_200(conn, "deleted");
}
static void impl_query(mg_connection* conn, problem_form_t* form, int permission)
{
if (!form->has_id) {
res_need_xxx(conn, "problem id");
return;
}
int result;
if (!has_problem(form->id, &result)) {
res_500(conn, "fail to check problem existence");
return;
} else if (!result) {
res_404(conn, "problem do not exist");
return;
}
char* result1 = NULL;
char* result2 = NULL;
char* result3 = NULL;
char** pr2 = permission == 1 ? &result2 : NULL;
if (!get_problem(form->id, &result1, pr2, &result3)) {
res_500(conn, "fail to get problem");
return;
}
if (pr2) {
res_query_problem2(conn, result1, result2, result3);
} else {
res_query_problem(conn, result1, result3);
}
}
static void impl_modify(mg_connection* conn, problem_form_t* form)
{
if (!form->has_id) {
res_need_xxx(conn, "problem id");
return;
}
if (!form->problem) {
res_need_xxx(conn, "problem content");
return;
}
if (!form->answer) {
res_need_xxx(conn, "standard answer");
return;
}
if (!form->error) {
res_need_xxx(conn, "checking error");
return;
}
int flag = modify_problem(form->id, form->problem, form->answer, form->error);
if (!flag) {
res_500(conn, "failed to modify the problem");
return;
}
res_200(conn, "success to modify the problem");
}
static void impl_check(mg_connection* conn, problem_form_t* form)
{
if (!form->has_id) {
res_need_xxx(conn, "problem id");
return;
}
if (!form->answer) {
res_need_xxx(conn, "answer");
return;
}
int result;
if (!has_problem(form->id, &result)) {
res_500(conn, "fail to check problem existence");
return;
} else if (!result) {
res_404(conn, "problem do not exist");
return;
}
int flag = check_answer(form->id, form->answer, &result);
if (!flag) {
res_500(conn, "fail to check the answer");
return;
}
res_check_answer(conn, result);
}
static void impl_all(mg_connection* conn, problem_form_t* form)
{
char* result = NULL;
int flag = all_problems(&result);
if (!flag) {
res_500(conn, "fail to get all the problem");
return;
}
res_all_problems(conn, result);
}
int problems_handler(mg_connection* conn, void* cbdata)
{
const mg_request_info* post_body = mg_get_request_info(conn);
if (post_body == NULL) {
res_null_req(conn);
return 1;
}
if (strcmp(post_body->request_method, "POST")) {
res_must_post(conn);
return 1;
}
problem_form_t form = {NULL, NULL, 0, 0, NULL, NULL, NULL};
mg_form_data_handler problem_callback = {
.field_found = field_found,
.field_get = field_get,
.field_store = NULL,
.user_data = &form,
};
mg_handle_form_request(conn, &problem_callback);
if (!form.action) {
res_need_action(conn);
} else if (!form.token) {
res_need_token(conn);
} else {
char* user_id = get_payload(form.token);
int result;
int flag = get_user_permission(user_id, &result);
if (!flag) {
res_check_permission_fail(conn);
} else if (result != 1 && result != 2) {
res_permission_denied(conn);
} else {
if (!strcmp(form.action, "add")) {
if (result == 2) {
res_permission_denied(conn);
} else {
impl_add(conn, &form);
}
} else if (!strcmp(form.action, "delete")) {
if (result == 2) {
res_permission_denied(conn);
} else {
impl_delete(conn, &form);
}
} else if (!strcmp(form.action, "query")) {
impl_query(conn, &form, result);
} else if (!strcmp(form.action, "modify")) {
if (result == 2) {
res_permission_denied(conn);
} else {
impl_modify(conn, &form);
}
} else if (!strcmp(form.action, "check")) {
impl_check(conn, &form);
} else if (!strcmp(form.action, "all")) {
if (result == 2) {
res_permission_denied(conn);
} else {
impl_all(conn, &form);
}
} else {
res_bad_action(conn);
}
}
}
problem_form_dtor(&form);
return 1;
}

View File

@ -1,398 +0,0 @@
<template>
<v-data-table :loading="loading" :headers="(headers as any)" :items="problems"
:sort-by="[{ key: 'id', order: 'asc' }, { key: 'problem', order: 'asc' }]" multi-sort>
<template v-slot:loading>
<v-skeleton-loader></v-skeleton-loader>
</template>
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>所有题目</v-toolbar-title>
<v-btn @click="refresh" class="mb-2" color="primary" dark>
刷新
</v-btn>
<v-dialog v-model="dialog0" max-width="500px">
<template v-slot:activator="{ props }">
<v-btn class="mb-2" color="primary" dark v-bind="props">
新建题目
</v-btn>
</template>
<v-form fast-fail @submit.prevent="save">
<v-card class="mx-auto pa-12 pb-8" width="660" elevation="8" rounded="lg">
<v-card-title>
<span class="text-h5">{{ formTitle }}</span>
</v-card-title>
<v-container>
<v-row>
<v-col>
<v-text-field v-model="editedItem.problem" prepend-inner-icon="mdi-head-question" label="题面"
:rules="notEmptyRules"></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field v-model="editedItem.answer" prepend-inner-icon="mdi-numeric" label="答案"
append-inner-icon="mdi-auto-fix" @click:append-inner="generateAnswer" :rules="notEmptyRules">
<v-tooltip activator="parent" location="bottom">
点击右侧 <v-icon icon="mdi-auto-fix"></v-icon> {{ useFraction ? '' : '' }}
<br />
再次点击还能生成其{{ useFraction ? '舍入' : '带循环节' }}表示
</v-tooltip>
</v-text-field>
</v-col>
<v-col id="HookElement">
<v-text-field v-model="checkingError" prepend-inner-icon="mdi-sine-wave" :label="checkingErrorTitle"
:readonly="preciseMode" :append-inner-icon="checkingErrorIcon" :rules="checkingErrorRules"
@click:append-inner="preciseMode = !preciseMode">
<v-tooltip activator="parent" location="bottom">
点击右侧
<v-icon :icon="checkingErrorIcon"></v-icon>
切换模式当前模式为{{ preciseMode ? '精确匹配' : '误差匹配' }}
<br />
精确匹配填写答案时必须与预设的标准答案完全相同才判定为正确
<br />
误差匹配与标准答案的差值的绝对值不超过误差的所有答案都会被判定为正确
</v-tooltip>
</v-text-field>
</v-col>
</v-row>
</v-container>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue-darken-1" variant="text" @click="close1">
取消
</v-btn>
<v-btn color="blue-darken-1" variant="text" type="submit">
保存
</v-btn>
</v-card-actions>
</v-card>
</v-form>
</v-dialog>
<v-dialog v-model="dialogDelete" max-width="500px">
<v-card>
<v-card-title class="text-h5">再次确认</v-card-title>
<v-card-text>确定要删除这题吗这是不可逆操作</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue-darken-1" variant="text" @click="closeDelete">放弃操作</v-btn>
<v-btn color="blue-darken-1" variant="text" @click="deleteItemConfirm">确定删除</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.actions="{ item }">
<v-icon class="me-2" size="small" @click="editItem(item)">
mdi-pencil
</v-icon>
<v-icon size="small" @click="deleteItem(item)">
mdi-delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" @click="initialize">
Reset
</v-btn>
</template>
</v-data-table>
<v-dialog v-model="dialogShow" width="auto">
<v-card max-width="400" prepend-icon="mdi-update" :text="dialogText" :title="dialogTitle">
<template v-slot:actions>
<v-btn class="ms-auto" text="Ok" @click="dialogShow = false"></v-btn>
</template>
</v-card>
</v-dialog>
</template>
<script lang="ts" setup>
import { useAuthStore } from '@/store/auth';
import axios, { Axios, AxiosError } from 'axios';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import * as mathjs from 'mathjs';
const loading = ref(false);
const refresh = async () => {
loading.value = true;
await initialize();
loading.value = false;
};
const dialogShow = ref(false);
const dialogTitle = ref('');
const dialogText = ref('');
const dialog = (title: string, text: string) => {
dialogTitle.value = title;
dialogText.value = text;
dialogShow.value = true;
};
const dialog0 = ref(false);
const dialogDelete = ref(false);
const headers = ref([
{
title: '题号',
align: 'center',
key: 'id'
},
{
title: '题面',
align: 'center',
key: 'problem'
},
{
title: '答案',
key: 'answer',
align: 'center',
sortable: false
},
{
title: '误差',
key: 'check_error',
align: 'center',
sortable: false
},
{
title: '操作',
key: 'actions',
align: 'center',
sortable: false
},
]);
const problems = ref<{ id: number, problem: string, answer: string, check_error: number }[]>([]);
const editedIndex = ref(-1);
const editedItem = ref({
id: 0,
problem: '',
answer: '',
check_error: 0,
});
const defaultItem = ref({
id: 0,
problem: '1',
answer: '1',
check_error: 0,
});
const preciseMode = ref(editedItem.value.check_error == 0);
const checkingErrorTitle = ref('');
const checkingErrorIcon = ref('');
const checkingError = ref('');
watch(preciseMode, () => {
if (preciseMode.value) {
checkingError.value = '精确匹配';
checkingErrorTitle.value = '';
checkingErrorIcon.value = 'mdi-crosshairs-gps';
} else {
checkingError.value = '';
checkingErrorTitle.value = '误差';
checkingErrorIcon.value = 'mdi-crosshairs-question';
}
}, { immediate: true });
watch(checkingError, () => {
if (preciseMode.value) {
editedItem.value.check_error = 0;
} else {
editedItem.value.check_error = parseFloat(checkingError.value);
}
})
const notEmptyRules = [(value: string) => {
if (value.length == 0) return '不能为空';
return true;
}];
const checkingErrorRules = [(value: string) => {
if (preciseMode.value) return true;
if (value.length == 0) return '必须设置误差';
let parsed = parseFloat(value);
if (parsed > 0) return true;
return '误差必须是大于零的实数';
}];
const mathBigNumber = mathjs.create(mathjs.all, {
number: 'BigNumber'
});
const mathFraction = mathjs.create(mathjs.all, {
number: 'Fraction'
});
let useFraction = false;
const generateAnswer = () => {
try {
editedItem.value.answer = (useFraction ? mathFraction : mathBigNumber).evaluate(editedItem.value.problem);
useFraction = !useFraction;
} catch (e) {
dialog('发生异常', `${e}`);
}
};
const formTitle = computed(() => {
return editedIndex.value === -1 ? '新建题目' : `编辑题目 ${editedItem.value.id}`;
});
watch(dialog0, (val) => {
val || close1();
});
watch(dialogDelete, (val) => {
val || closeDelete();
});
const authStore = useAuthStore();
const storedToken = ref(authStore.token);
const decorate = <T>(ex: AxiosError) => {
if (ex.response?.data) {
return ex.response?.data as T;
} {
return { error: ex.message } as T;
}
}
type AllProblemResponse = { success?: string, result?: number[], error?: string };
const requestAllProblem = async () => {
try {
const formData = new FormData;
formData.append("action", "all");
formData.append("token", storedToken.value);
let res = await axios.post('/api/study/problems', formData);
return res.data as AllProblemResponse;
} catch (e) {
return decorate<AllProblemResponse>(e as AxiosError);
}
};
type ProblemResponse = { success?: string, problem?: string, answer?: string, check_error?: number, error?: string };
const requestQueryProblem = async (id: number) => {
try {
const formData = new FormData;
formData.append("action", "query");
formData.append("token", storedToken.value);
formData.append("id", `${id}`);
let res = await axios.post('/api/study/problems', formData);
return res.data as ProblemResponse;
} catch (e) {
return decorate<ProblemResponse>(e as AxiosError);
}
}
type AddProblemResponse = { success?: string, id?: number, error?: string };
const requestAddProblem = async () => {
try {
const formData = new FormData;
formData.append("action", editedItem.value.id == 0 ? "add" : 'modify');
formData.append("token", storedToken.value);
if (editedItem.value.id != 0) {
formData.append("id", `${editedItem.value.id}`);
}
formData.append("problem", `${editedItem.value.problem}`);
formData.append("answer", `${editedItem.value.answer}`);
formData.append("error", `${preciseMode.value ? 0 : editedItem.value.check_error}`);
let res = await axios.post('/api/study/problems', formData);
return res.data as AddProblemResponse;
} catch (e) {
return decorate<AddProblemResponse>(e as AxiosError);
}
}
type DeleteProblemResponse = { success?: string, error?: string };
const requestDeleteProblem = async (id: number) => {
try {
const formData = new FormData;
formData.append("action", 'delete');
formData.append("token", storedToken.value);
formData.append("id", `${id}`);
let res = await axios.post('/api/study/problems', formData);
return res.data as DeleteProblemResponse;
} catch (e) {
return decorate<DeleteProblemResponse>(e as AxiosError);
}
}
const initialize = async () => {
let res = await requestAllProblem();
if (res?.error) {
dialog('发生异常', res.error);
return;
}
problems.value = [];
let ids = res.result as number[];
for (let i in ids) {
let id = ids[i];
let resp = await requestQueryProblem(id);
let pro = { id, problem: '获取失败', answer: 'N/A', check_error: NaN };
if (resp?.success) {
pro.problem = resp.problem as string;
pro.answer = resp.answer as string;
pro.check_error = resp.check_error as number;
}
problems.value.push(pro);
}
};
onMounted(initialize);
const editItem = (item: any) => {
editedIndex.value = problems.value.indexOf(item);
editedItem.value = Object.assign({}, item);
dialog0.value = true;
};
const deleteItem = (item: any) => {
editedIndex.value = problems.value.indexOf(item);
editedItem.value = Object.assign({}, item);
dialogDelete.value = true;
};
const deleteItemConfirm = async () => {
let res = await requestDeleteProblem(editedItem.value.id);
if (res?.error) {
dialog('发生异常', res.error);
return;
}
problems.value.splice(editedIndex.value, 1);
closeDelete();
};
const close1 = () => {
dialog0.value = false;
nextTick(() => {
editedItem.value = Object.assign({}, defaultItem.value);
editedIndex.value = -1;
});
};
const closeDelete = () => {
dialogDelete.value = false;
nextTick(() => {
editedItem.value = Object.assign({}, defaultItem.value);
editedIndex.value = -1;
})
};
const save = async (event: SubmitEvent) => {
const results: any = await event;
if (!results.valid) return;
let res = await requestAddProblem();
if (res?.error) {
dialog(`${editedIndex.value > -1 ? '修改' : '添加'}失败`, res.error);
} else {
if (editedIndex.value > -1) {
Object.assign(problems.value[editedIndex.value], editedItem.value);
} else {
editedItem.value.id = res.id as number;
problems.value.push(editedItem.value);
}
}
close1();
};
</script>