diff --git a/include/db/records.h b/include/db/records.h new file mode 100644 index 0000000..49cf682 --- /dev/null +++ b/include/db/records.h @@ -0,0 +1,33 @@ +#ifndef DB_RECORDS_H +#define DB_RECORDS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + + int open_records_db(); + + void close_records_db(); + + int add_record(const char* user_id, int problems_id, const char* data); + + int add_record_ac(const char* user_id, int problems_id, const char* answer); + + int add_record_wa(const char* user_id, int problems_id, const char* answer); + + int add_record_uke(const char* user_id, int problems_id, const char* answer); + + int get_record(int id, char** result); + + int all_records_by(const char* user_id, char** result); + + int all_records_of(int problems_id, char** result); + + int records_count(); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/include/server/response.h b/include/server/response.h index 05e7202..389386d 100644 --- a/include/server/response.h +++ b/include/server/response.h @@ -53,4 +53,10 @@ void res_query_problem2(mg_connection* conn, const char* problem, const char* an void res_check_answer(mg_connection* conn, int flag); void res_all_problems(mg_connection* conn, const char* problems); +void res_get_record(mg_connection* conn, const char* record); +void res_all_records(mg_connection* conn, const char* records); +void res_all_records_by(mg_connection* conn, const char* records); +void res_all_records_of(mg_connection* conn, const char* records); +void res_records_count(mg_connection* conn, int count); + #endif \ No newline at end of file diff --git a/include/server/study.h b/include/server/study.h index 2e13c8d..d2547db 100644 --- a/include/server/study.h +++ b/include/server/study.h @@ -4,6 +4,7 @@ #include "server/types.h" int problems_handler(mg_connection* conn, void* cbdata); +int records_handler(mg_connection* conn, void* cbdata); extern char* secret; diff --git a/src/db/records.cpp b/src/db/records.cpp new file mode 100644 index 0000000..257766c --- /dev/null +++ b/src/db/records.cpp @@ -0,0 +1,166 @@ +#include "records.h" + +#include +#include + + +#include + +#include + + +using leveldb_ptr = leveldb::DB*; + +auto records_db = leveldb_ptr{nullptr}; +auto records_count_ = 0; + +extern "C" +{ + int open_records_db() + { + auto opts = leveldb::Options{}; + + opts.create_if_missing = true; + + auto status = leveldb::DB::Open(opts, "db/records", &records_db); + + if (!status.ok()) { + std::println(stderr, "Failed to open records database: {}", status.ToString()); + return 0; + } + + auto it = records_db->NewIterator(leveldb::ReadOptions()); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + records_count_++; + } + + if (!it->status().ok()) { + std::println(stderr, "Failed to count records: {}", it->status().ToString()); + delete it; + delete records_db; + return 0; + } + + delete it; + return 1; + } + + void close_records_db() + { + delete records_db; + } + + int add_record(const char* user_id, int problems_id, const char* data) + { + auto json = nlohmann::json{ + {"user_id", user_id }, + {"problem", problems_id }, + {"data", nlohmann::json::parse(data)} + }; + auto status = records_db->Put(leveldb::WriteOptions{}, std::to_string(records_count_), json.dump()); + if (!status.ok()) { + std::println(stderr, "Failed to add a record: {}", status.ToString()); + return 0; + } + ++records_count_; + return 1; + } + + int add_record_impl(const char* user_id, int problems_id, const char* answer, auto status) + { + auto now = std::chrono::utc_clock::now(); + auto timestamp_ms = std::chrono::time_point_cast(now); + auto timestamp = timestamp_ms.time_since_epoch().count(); + + auto json = nlohmann::json{ + {"answer", answer }, + {"status", status }, + {"timestamp", timestamp} + }; + return add_record(user_id, problems_id, json.dump().c_str()); + } + + int add_record_ac(const char* user_id, int problems_id, const char* answer) + { + return add_record_impl(user_id, problems_id, answer, "ac"); + } + int add_record_wa(const char* user_id, int problems_id, const char* answer) + { + return add_record_impl(user_id, problems_id, answer, "wa"); + } + int add_record_uke(const char* user_id, int problems_id, const char* answer) + { + return add_record_impl(user_id, problems_id, answer, "uke"); + } + + int get_record(int id, char** result) + { + auto value = std::string{}; + + auto status = records_db->Get(leveldb::ReadOptions{}, std::to_string(id), &value); + if (status.IsNotFound()) { + *result = NULL; + } else if (!status.ok()) { + std::println(stderr, "Failed to get a record: {}", status.ToString()); + return 0; + } else { + *result = strdup(value.c_str()); + } + return 1; + } + + int all_records_by(const char* user_id, char** result) + { + auto problems = std::vector{}; + auto buf = std::string{}; + + auto it = records_db->NewIterator(leveldb::ReadOptions()); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + auto json = nlohmann::json::parse(it->value().ToString()); + json["user_id"].get_to(buf); + if (buf != user_id) continue; + problems.emplace_back(std::stoi(it->key().ToString())); + } + + if (!it->status().ok()) { + std::println(stderr, "Failed to get all records by user id: {}", it->status().ToString()); + delete it; + return 0; + } + delete it; + auto json = nlohmann::json(problems); + + *result = strdup(json.dump().c_str()); + return 1; + } + + int all_records_of(int problems_id, char** result) + { + auto problems = std::vector{}; + auto buf = 0; + + auto it = records_db->NewIterator(leveldb::ReadOptions()); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + auto json = nlohmann::json::parse(it->value().ToString()); + json["problem"].get_to(buf); + if (buf != problems_id) continue; + problems.emplace_back(std::stoi(it->key().ToString())); + } + + if (!it->status().ok()) { + std::println(stderr, "Failed to get all records of the problem: {}", it->status().ToString()); + delete it; + return 0; + } + delete it; + auto json = nlohmann::json(problems); + + *result = strdup(json.dump().c_str()); + return 1; + } + + int records_count() + { + return records_count_; + } +} \ No newline at end of file diff --git a/src/server/response.c b/src/server/response.c index e2a4920..239e82b 100644 --- a/src/server/response.c +++ b/src/server/response.c @@ -260,7 +260,7 @@ void res_need_xxx(mg_connection* conn, const char* xxx) void res_500(mg_connection* conn, const char* error) { mg_printf(conn, - "HTTP/1.1 400 Bad Request\r\n" + "HTTP/1.1 500 Internal Server Error\r\n" "Content-Type: application/json\r\n" "Access-Control-Allow-Origin: *\r\n\r\n" "{\"error\":\"%s\"}", @@ -270,7 +270,7 @@ void res_500(mg_connection* conn, const char* error) void res_404(mg_connection* conn, const char* error) { mg_printf(conn, - "HTTP/1.1 400 Bad Request\r\n" + "HTTP/1.1 404 Not Found\r\n" "Content-Type: application/json\r\n" "Access-Control-Allow-Origin: *\r\n\r\n" "{\"error\":\"%s\"}", @@ -345,3 +345,53 @@ void res_all_problems(mg_connection* conn, const char* problems) "{\"success\":\"success to get all the problems\", \"result\":%s}", problems); } + +void res_get_record(mg_connection* conn, const char* record) +{ + 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 the record\", \"record\":%s}", + record); +} + +void res_all_records(mg_connection* conn, const char* records) +{ + 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 records\", \"records\":%s}", + records); +} + +void res_all_records_by(mg_connection* conn, const char* records) +{ + 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 records by the user\", \"records\":%s}", + records); +} + +void res_all_records_of(mg_connection* conn, const char* records) +{ + 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 records of the problem\", \"records\":%s}", + records); +} + +void res_records_count(mg_connection* conn, int count) +{ + 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 the records count\", \"result\":%d}", + count); +} diff --git a/src/server/study/records.c b/src/server/study/records.c new file mode 100644 index 0000000..4a90e37 --- /dev/null +++ b/src/server/study/records.c @@ -0,0 +1,186 @@ +#include "db/records.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; + int id; + int has_id; + char* user_id; + int problem_id; + int has_problem_id; +} record_form_t; + +static void record_form_dtor(record_form_t* form) +{ + if (form->action) free(form->action); + if (form->user_id) free(form->user_id); +} + +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) +{ + record_form_t* form = (record_form_t*)user_data; + if (strcmp(key, "action") == 0) { + form->action = 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, "user_id") == 0) { + form->user_id = kqm_strndup(value, valuelen); + } else if (strcmp(key, "problem_id") == 0) { + char* id_str = kqm_strndup(value, valuelen); + + form->problem_id = atoi(id_str); + form->has_problem_id = 1; + free(id_str); + } + return MG_FORM_FIELD_HANDLE_GET; +} + +static void impl_query(mg_connection* conn, record_form_t* form) +{ + if (!form->has_id) { + res_need_xxx(conn, "record id"); + return; + } + + char* result = NULL; + int flag = get_record(form->id, &result); + if (!flag) { + res_500(conn, "failed to get a record"); + return; + } + if (!result) { + res_404(conn, "the record does not exist"); + return; + } + + res_get_record(conn, result); + free(result); +} + +static void impl_all(mg_connection* conn, record_form_t* form) +{ + char* result = NULL; + int flag = all_records(&result); + if (!flag) { + res_500(conn, "failed to get all records"); + return; + } + + res_all_records(conn, result); + free(result); +} + +static void impl_by(mg_connection* conn, record_form_t* form) +{ + if (!form->user_id) { + res_need_xxx(conn, "user id"); + return; + } + + char* result = NULL; + int flag = all_records_by(form->user_id, &result); + if (!flag) { + res_500(conn, "failed to get the records by the user"); + return; + } + + res_all_records_by(conn, result); + free(result); +} + +static void impl_of(mg_connection* conn, record_form_t* form) +{ + if (!form->has_problem_id) { + res_need_xxx(conn, "problem id"); + return; + } + + char* result = NULL; + int flag = all_records_of(form->problem_id, &result); + if (!flag) { + res_500(conn, "failed to get the records of the problem"); + return; + } + + res_all_records_of(conn, result); + free(result); +} + +static void impl_count(mg_connection* conn) +{ + int count = records_count(); + res_records_count(conn, count); +} + +int records_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; + } + + record_form_t form = {NULL, 0, 0, NULL, 0, 0}; + + mg_form_data_handler record_callback = { + .field_found = field_found, + .field_get = field_get, + .field_store = NULL, + .user_data = &form, + }; + + mg_handle_form_request(conn, &record_callback); + + + if (!form.action) { + res_need_action(conn); + } else { + if (!strcmp(form.action, "query")) { + impl_query(conn, &form); + } else if (!strcmp(form.action, "by")) { + impl_by(conn, &form); + } else if (!strcmp(form.action, "of")) { + impl_of(conn, &form); + } else if (!strcmp(form.action, "count")) { + impl_count(conn); + } else { + res_bad_action(conn); + } + } + + record_form_dtor(&form); + return 1; +} diff --git a/ui/src/components/RecordList.vue b/ui/src/components/RecordList.vue new file mode 100644 index 0000000..91bc26b --- /dev/null +++ b/ui/src/components/RecordList.vue @@ -0,0 +1,269 @@ + + + \ No newline at end of file