From 066464018d697364c576c1ce0b1784cf8348968c Mon Sep 17 00:00:00 2001 From: keqingmoe Date: Mon, 30 Dec 2024 14:57:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=97=AE=E9=A2=98=E5=A4=84?= =?UTF-8?q?=E7=90=86=E6=A8=A1=E5=9D=97=EF=BC=8C=E5=8C=85=E5=90=AB=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E6=95=B0=E6=8D=AE=E5=BA=93=E6=93=8D=E4=BD=9C=E5=92=8C?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=A4=84=E7=90=86=E5=87=BD=E6=95=B0=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=97=AE=E9=A2=98=E7=9A=84=E5=A2=9E=E5=88=A0?= =?UTF-8?q?=E6=9F=A5=E6=94=B9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/db/problems.h | 31 ++++ include/server/response.h | 21 ++- include/server/study.h | 10 ++ src/db/problems.cpp | 194 ++++++++++++++++++++++++ src/server/response.c | 92 +++++++++++- src/server/study/problems.c | 290 ++++++++++++++++++++++++++++++++++++ 6 files changed, 626 insertions(+), 12 deletions(-) create mode 100644 include/db/problems.h create mode 100644 include/server/study.h create mode 100644 src/db/problems.cpp create mode 100644 src/server/study/problems.c diff --git a/include/db/problems.h b/include/db/problems.h new file mode 100644 index 0000000..fe18a52 --- /dev/null +++ b/include/db/problems.h @@ -0,0 +1,31 @@ +#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 \ No newline at end of file diff --git a/include/server/response.h b/include/server/response.h index 7af73ac..05e7202 100644 --- a/include/server/response.h +++ b/include/server/response.h @@ -7,8 +7,6 @@ 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); @@ -17,9 +15,10 @@ 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); @@ -43,9 +42,15 @@ void res_permission(mg_connection* conn, int permission); void res_repermission_fail(mg_connection* conn); void res_repermission(mg_connection* conn); -void res_(mg_connection* conn); -void res_(mg_connection* conn); -void res_(mg_connection* conn); -void res_(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); #endif \ No newline at end of file diff --git a/include/server/study.h b/include/server/study.h new file mode 100644 index 0000000..2e13c8d --- /dev/null +++ b/include/server/study.h @@ -0,0 +1,10 @@ +#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 \ No newline at end of file diff --git a/src/db/problems.cpp b/src/db/problems.cpp new file mode 100644 index 0000000..0e20775 --- /dev/null +++ b/src/db/problems.cpp @@ -0,0 +1,194 @@ +#include "problems.h" + +#include + +#include +#include + +#include +#include + +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{}; + + 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; + } +} \ No newline at end of file diff --git a/src/server/response.c b/src/server/response.c index ef60ac3..e2a4920 100644 --- a/src/server/response.c +++ b/src/server/response.c @@ -99,11 +99,15 @@ 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) { @@ -253,6 +257,36 @@ 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, @@ -261,3 +295,53 @@ 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); +} diff --git a/src/server/study/problems.c b/src/server/study/problems.c new file mode 100644 index 0000000..ac7b2fe --- /dev/null +++ b/src/server/study/problems.c @@ -0,0 +1,290 @@ +#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 + +#include + +#include +#include +#include + +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; +}