From 4b6c822bf36e810327e335c606d91aed9abe7bde Mon Sep 17 00:00:00 2001 From: zhangyuheng Date: Thu, 21 Dec 2023 17:52:03 +0800 Subject: [PATCH] finish basic http frame --- CMakeLists.txt | 5 +- resource/config.json | 3 +- src/components/ErrorHandler.h | 38 ++++++++ src/components/ServiceComponent.h | 6 +- src/components/SwaggerComponent.h | 2 +- src/controller/QuestionController.h | 37 +++++++- src/doo/Answer.h | 2 +- src/doo/Question.h | 54 +++++++++++- src/doo/Tag.h | 6 ++ src/dto/Configuration.h | 5 ++ src/dto/Question.h | 28 ++++-- src/dto/Tag.h | 9 ++ src/dto/basic/Enmus.h | 11 +++ src/dto/basic/Page.h | 132 ++++++++++++++++++++++++++++ src/dto/basic/Response.h | 68 ++++++++++++++ src/dto/db/Question.h | 4 +- src/dto/response/Question.h | 28 ++++++ src/service/QuestionService.cpp | 94 ++++++++++++++++++++ src/service/QuestionService.h | 45 ++++++++++ 19 files changed, 559 insertions(+), 18 deletions(-) create mode 100644 src/components/ErrorHandler.h create mode 100644 src/dto/basic/Page.h create mode 100644 src/dto/basic/Response.h create mode 100644 src/dto/response/Question.h create mode 100644 src/service/QuestionService.cpp create mode 100644 src/service/QuestionService.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 356cad1..3d25db7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,10 +12,13 @@ FIND_PACKAGE(oatpp-postgresql REQUIRED) INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src) FILE(GLOB_RECURSE UTILS_SRC ${CMAKE_SOURCE_DIR}/src/utils/*.cpp) +FILE(GLOB_RECURSE SERVICE_SRC ${CMAKE_SOURCE_DIR}/src/service/*.cpp) ADD_EXECUTABLE(QuickExamBackend ${CMAKE_SOURCE_DIR}/src/QuickExamBackend.cpp - ${UTILS_SRC}) + ${UTILS_SRC} + ${SERVICE_SRC} + ) TARGET_LINK_LIBRARIES(QuickExamBackend PUBLIC oatpp::oatpp diff --git a/resource/config.json b/resource/config.json index 908d215..a26dfea 100644 --- a/resource/config.json +++ b/resource/config.json @@ -9,10 +9,11 @@ "database": "quickexam" }, "resource": "../resource", + "swagger_host": "http://localhost:8280", "log": { "save_path": "./logs", "max_size_mb": 1, "preserve_days": 30, - "debug": false + "debug": true } } \ No newline at end of file diff --git a/src/components/ErrorHandler.h b/src/components/ErrorHandler.h new file mode 100644 index 0000000..457666f --- /dev/null +++ b/src/components/ErrorHandler.h @@ -0,0 +1,38 @@ + +#pragma once + +#include +#include +#include +#include + +namespace QuickExam { +class ErrorHandler : public oatpp::web::server::handler::ErrorHandler { +private: + typedef oatpp::web::protocol::http::outgoing::Response OutgoingResponse; + typedef oatpp::web::protocol::http::Status Status; + typedef oatpp::web::protocol::http::outgoing::ResponseFactory ResponseFactory; + +private: + std::shared_ptr m_objectMapper; + +public: + explicit ErrorHandler(const std::shared_ptr &objectMapper) + : m_objectMapper(objectMapper) { + LOGD("ErrorHandler", "ErrorHandler created"); + } + + std::shared_ptr handleError(const Status &status, + const oatpp::String &message, + const Headers &headers) override { + auto error = dto::basic::Response::createShared(); + error->code = status.code; + error->message = message; + auto response = ResponseFactory::createResponse(Status::CODE_200, error, m_objectMapper); + for (const auto &pair : headers.getAll()) { + response->putHeader(pair.first.toString(), pair.second.toString()); + } + return response; + } +}; +} // namespace QuickExam \ No newline at end of file diff --git a/src/components/ServiceComponent.h b/src/components/ServiceComponent.h index d308ec2..ee3c30e 100644 --- a/src/components/ServiceComponent.h +++ b/src/components/ServiceComponent.h @@ -5,6 +5,7 @@ #pragma once +#include #include #include #include @@ -42,8 +43,9 @@ public: router); // get Router component OATPP_COMPONENT(std::shared_ptr, objectMapper); // get ObjectMapper component - - return oatpp::web::server::HttpConnectionHandler::createShared(router); + auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router); + connectionHandler->setErrorHandler(std::make_shared(objectMapper)); + return connectionHandler; }()); }; } // namespace QuickExam::component diff --git a/src/components/SwaggerComponent.h b/src/components/SwaggerComponent.h index deca5aa..6cd0b0f 100644 --- a/src/components/SwaggerComponent.h +++ b/src/components/SwaggerComponent.h @@ -24,7 +24,7 @@ public: .setVersion("1.0") .addServer("http://" + config->host + ":" + std::to_string(config->port), "server on web server") - .addServer("http://localhost:" + std::to_string(config->port), "server on localhost"); + .addServer(config->swagger_host, "server on localhost"); return builder.build(); }()); diff --git a/src/controller/QuestionController.h b/src/controller/QuestionController.h index 4f8fe83..72d2d67 100644 --- a/src/controller/QuestionController.h +++ b/src/controller/QuestionController.h @@ -8,10 +8,12 @@ #include #include #include +#include #include #include #include #include +#include #include OATPP_CODEGEN_BEGIN(ApiController) @@ -33,6 +35,7 @@ public: private: std::shared_ptr objectMapper; + service::QuestionService question_service; public: ENDPOINT("POST", "/create", createQuestion, BODY_DTO(Object, req_body)) { @@ -152,11 +155,39 @@ public: } ENDPOINT_INFO(deleteQuesionContent) { info->addTag("Question"); - info->summary = "Delete Content"; - info->description = "Delete a content"; - info->pathParams.add("content_id"); + info->summary = "Delete Content"; + info->description = "Delete a content"; + info->pathParams.add("content_id").description = "Content id"; + info->pathParams.add("content_id").required = true; info->addResponse(Status::CODE_200, "Success, then re-get the question"); } + + ENDPOINT("GET", "/get/{question_id}", getQuestionById, PATH(Int32, question_id)) { + return createDtoResponse(Status::CODE_200, question_service.getQuestionById(question_id)); + } + ENDPOINT_INFO(getQuestionById) { + info->addTag("Question"); + info->summary = "Get Question By Id"; + info->description = "Get a question by id"; + info->pathParams.add("question_id").description = "Question id"; + info->pathParams.add("question_id").required = true; + info->addResponse>(Status::CODE_200, "application/json"); + } + + ENDPOINT("POST", + "/get/list/by/conditions", + getQuestions, + BODY_DTO(Object, req_body)) { + return createDtoResponse(Status::CODE_200, + question_service.getQuestionsByPageConditions(req_body)); + } + ENDPOINT_INFO(getQuestions) { + info->addTag("Question"); + info->summary = "Get Questions"; + info->description = "Get questions by page and size"; + info->addConsumes>("application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + } }; } // namespace QuickExam::controller diff --git a/src/doo/Answer.h b/src/doo/Answer.h index f335035..083cf4d 100644 --- a/src/doo/Answer.h +++ b/src/doo/Answer.h @@ -35,7 +35,7 @@ public: PREPARE(true), PARAM(oatpp::Object, a)) - QUERY(getAnswerByQuestionId, + QUERY(getAnswersByQuestionId, "SELECT " "* " "FROM qe_answer " diff --git a/src/doo/Question.h b/src/doo/Question.h index b0ae5fb..5046e42 100644 --- a/src/doo/Question.h +++ b/src/doo/Question.h @@ -4,17 +4,25 @@ // #pragma once + +#include #include #include +#include #include OATPP_CODEGEN_BEGIN(DbClient) namespace QuickExam::doo { class Question : public oatpp::orm::DbClient { +private: + std::shared_ptr m_executor; + public: explicit Question(const std::shared_ptr &executor) - : oatpp::orm::DbClient(executor) {} + : oatpp::orm::DbClient(executor) { + m_executor = executor; + } QUERY(insertQuestion, "INSERT INTO qe_question " @@ -52,7 +60,7 @@ public: "(DEFAULT, :q.tag_id, :q.question_id, :q.priority) " "RETURNING id", PREPARE(true), - PARAM(oatpp::Object, q)) + PARAM(oatpp::Object, q)) QUERY(getQuestion, "SELECT " @@ -152,6 +160,48 @@ public: "WHERE id = :q.id", PREPARE(true), PARAM(oatpp::Object, q)) + + QUERY(getQuestionsByTagId, + "SELECT " + "* " + "FROM qe_question " + "WHERE id IN (SELECT question_id FROM qe_question_tag WHERE tag_id = :tag_id)", + PREPARE(true), + PARAM(oatpp::Int32, tag_id)) + + DEFINE_CONDITION_FUNC(const oatpp::Object &query) { + std::string sql = "WHERE 1 = 1 "; + if (!query->search->empty()) { + sql += "AND "; + sql += "id IN (SELECT question_id FROM qe_question_content WHERE content LIKE '%" + + query->search + "%') "; + } + if (!query->types->empty()) { + sql += "AND "; + sql += "type IN ("; + for (auto &type : *query->types) { + sql += std::to_string(type) + ", "; + } + sql.pop_back(); + sql.pop_back(); + sql += ") "; + } + if (!query->tag_ids->empty()) { + sql += "AND "; + sql += "id IN (SELECT question_id FROM qe_question_tag WHERE tag_id IN ("; + for (auto &tag_id : *query->tag_ids) { + sql += std::to_string(tag_id) + ", "; + } + sql.pop_back(); + sql.pop_back(); + sql += ")) "; + } + return sql; + } + + DEFINE_COUNT_QUERY("qe_question", dto::QuestionCondition) + + DEFINE_PAGE_QUERY("qe_question", dto::QuestionCondition) }; } // namespace QuickExam::doo diff --git a/src/doo/Tag.h b/src/doo/Tag.h index eacbb0d..383ba92 100644 --- a/src/doo/Tag.h +++ b/src/doo/Tag.h @@ -60,6 +60,12 @@ public: "WHERE id = :t.id", PREPARE(true), PARAM(oatpp::Object, t)) + + QUERY(getAllTagIds, + "SELECT " + "id " + "FROM qe_tag", + PREPARE(true)) }; } // namespace QuickExam::doo diff --git a/src/dto/Configuration.h b/src/dto/Configuration.h index c24265b..9f76dfa 100644 --- a/src/dto/Configuration.h +++ b/src/dto/Configuration.h @@ -92,6 +92,11 @@ class Configuration : public oatpp::DTO { info->description = "Resource path"; } + DTO_FIELD(String, swagger_host) = "localhost:8000"; + DTO_FIELD_INFO(swagger_host) { + info->description = "Swagger host"; + } + DTO_FIELD(Object, log) = Log::createShared(); DTO_FIELD_INFO(log) { info->description = "Log configuration"; diff --git a/src/dto/Question.h b/src/dto/Question.h index d35660d..2ef5153 100644 --- a/src/dto/Question.h +++ b/src/dto/Question.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -18,28 +19,45 @@ namespace QuickExam::dto { class Question : public db::Question { DTO_INIT(Question, db::Question) - DTO_FIELD(List>, - question_contents) = List>::createShared(); + DTO_FIELD(List>, question_contents); DTO_FIELD_INFO(question_contents) { info->description = "Question contents"; } - DTO_FIELD(List>, sub_questions) = List>::createShared(); + DTO_FIELD(List>, sub_questions); DTO_FIELD_INFO(sub_questions) { info->description = "Sub questions"; } - DTO_FIELD(List>, answers) = List>::createShared(); + DTO_FIELD(List>, answers); DTO_FIELD_INFO(answers) { info->description = "Question answers"; } - DTO_FIELD(List>, tags) = List>::createShared(); + DTO_FIELD(List>, tags); DTO_FIELD_INFO(tags) { info->description = "Question tags"; } }; +class QuestionCondition : public basic::Condition { + DTO_INIT(QuestionCondition, basic::Condition) + + DTO_FIELD(List, tag_ids) = List::createShared(); + DTO_FIELD_INFO(tag_ids) { + info->description = "Question tags (empty means all tags)"; + } + + DTO_FIELD(List, types) = List::createShared(); + DTO_FIELD_INFO(types) { + info->description = "Question types (empty means all types)"; + } +}; + +class QuestionPage : public basic::Page> { + DTO_INIT(QuestionPage, basic::Page>) +}; + } // namespace QuickExam::dto #include OATPP_CODEGEN_END(DTO) \ No newline at end of file diff --git a/src/dto/Tag.h b/src/dto/Tag.h index a47a385..1b241fb 100644 --- a/src/dto/Tag.h +++ b/src/dto/Tag.h @@ -5,6 +5,7 @@ #pragma once +#include #include #include #include @@ -17,6 +18,14 @@ class Tag : public db::Tag { DTO_INIT(Tag, db::Tag) }; +class TagCondition : public basic::Condition { + DTO_INIT(TagCondition, basic::Condition) +}; + +class TagPage : public basic::Page> { + DTO_INIT(TagPage, basic::Page>) +}; + } // namespace QuickExam::dto #include OATPP_CODEGEN_END(DTO) \ No newline at end of file diff --git a/src/dto/basic/Enmus.h b/src/dto/basic/Enmus.h index 0e9ac26..b7400c8 100644 --- a/src/dto/basic/Enmus.h +++ b/src/dto/basic/Enmus.h @@ -29,6 +29,17 @@ ENUM(QuestionType, VALUE(long_answer, 4, "long-answer"), VALUE(file_upload, 5, "file-upload")) +static oatpp::List allQuestionTypes() { + auto list = oatpp::List::createShared(); + list->push_back(0); + list->push_back(1); + list->push_back(2); + list->push_back(3); + list->push_back(4); + list->push_back(5); + return list; +} + ENUM(CandidateStatus, v_int32, VALUE(notified, 0, "notified"), diff --git a/src/dto/basic/Page.h b/src/dto/basic/Page.h new file mode 100644 index 0000000..734e646 --- /dev/null +++ b/src/dto/basic/Page.h @@ -0,0 +1,132 @@ +// +// QuickExam +// Created on 2023/12/21. +// + +#pragma once + +#include +#include + +#define DEFINE_CONDITION_FUNC static std::string conditionSqlString + +#define DEFINE_COUNT_QUERY(TABLE_NAME, CONDITION_DTO) \ + std::shared_ptr getPageCountByConditions( \ + const oatpp::Object &query) { \ + std::string sql = "SELECT "; \ + sql += "COUNT(*) AS total_count "; \ + sql += "FROM " TABLE_NAME " "; \ + sql += conditionSqlString(query) + ";"; \ + LOGD("QuestionDOO", "getQuestionsByConditionPageCount %s", sql.c_str()); \ + return m_executor->execute(sql, {}); \ + } + +#define DEFINE_PAGE_QUERY(TABLE_NAME, CONDITION_DTO) \ + std::shared_ptr getPageByConditions( \ + const oatpp::Object &query) { \ + std::string offset = std::to_string((query->page - 1) * query->page_size); \ + std::string limit = std::to_string(query->page_size); \ + std::string sql = "SELECT "; \ + sql += "* "; \ + sql += "FROM " TABLE_NAME " "; \ + sql += conditionSqlString(query); \ + sql += "ORDER BY " + query->sort_by + " " + query->sort_order + " "; \ + sql += "OFFSET " + offset + " "; \ + sql += "LIMIT " + limit + ";"; \ + LOGD("QuestionDOO", "getQuestionsByConditionInPage %s", sql.c_str()); \ + return m_executor->execute(sql, {}); \ + } + +#define GET_PAGE(DOO, CONDITION_DATA, PAGE_DATA, ITEM_DTO) \ + { \ + auto DB_RES = (DOO)->getPageCountByConditions(CONDITION_DATA); \ + ASSERT_DB(DB_RES); \ + auto totals = \ + DB_RES->fetch>>>()->front(); \ + (PAGE_DATA)->total_count = totals->total_count; \ + (PAGE_DATA)->total_page = totals->total_count % (CONDITION_DATA)->page_size == 0 ? \ + totals->total_count / (CONDITION_DATA)->page_size : \ + totals->total_count / (CONDITION_DATA)->page_size + 1; \ + if ((CONDITION_DATA)->page > (PAGE_DATA)->total_page) { \ + (CONDITION_DATA)->page = (PAGE_DATA)->total_page; \ + } \ + if ((CONDITION_DATA)->page <= 0) { \ + (CONDITION_DATA)->page = 1; \ + } \ + DB_RES = (DOO)->getPageByConditions(CONDITION_DATA); \ + ASSERT_DB(DB_RES); \ + (PAGE_DATA)->items = DB_RES->fetch>>(); \ + } + +#include OATPP_CODEGEN_BEGIN(DTO) + +namespace QuickExam::dto::basic { +template +class Page : public oatpp::DTO { + DTO_INIT(Page, DTO) + + DTO_FIELD(Int32, page) = 1; + DTO_FIELD_INFO(page) { + info->description = "Page number"; + info->required = true; + } + + DTO_FIELD(Int32, page_size) = 10; + DTO_FIELD_INFO(page_size) { + info->description = "Page size"; + info->required = true; + } + + DTO_FIELD(Int32, total_count) = 0; + DTO_FIELD_INFO(total_count) { + info->description = "Total number of records"; + info->required = true; + } + + DTO_FIELD(Int32, total_page) = 0; + DTO_FIELD_INFO(total_page) { + info->description = "Total number of pages"; + info->required = true; + } + + DTO_FIELD(List, items) = List::createShared(); + DTO_FIELD_INFO(items) { + info->description = "Data list"; + info->required = true; + } +}; + +class Condition : public oatpp::DTO { + DTO_INIT(Condition, DTO) + + DTO_FIELD(Int32, page) = 1; + DTO_FIELD_INFO(page) { + info->description = "Page number"; + info->required = true; + } + + DTO_FIELD(Int32, page_size) = 10; + DTO_FIELD_INFO(page_size) { + info->description = "Page size"; + info->required = true; + } + + DTO_FIELD(String, sort_by) = "id"; + DTO_FIELD_INFO(sort_by) { + info->description = "Sort by"; + } + + DTO_FIELD(String, sort_order) = "ASC"; + DTO_FIELD_INFO(sort_order) { + info->description = "Sort order (ASC or DESC)"; + } + + DTO_FIELD(String, search) = ""; + DTO_FIELD_INFO(search) { + info->description = "Search keyword"; + } +}; + +} // namespace QuickExam::dto::basic + +#include OATPP_CODEGEN_END(DTO) \ No newline at end of file diff --git a/src/dto/basic/Response.h b/src/dto/basic/Response.h new file mode 100644 index 0000000..1a4b15e --- /dev/null +++ b/src/dto/basic/Response.h @@ -0,0 +1,68 @@ +// +// QuickExam +// Created on 2023/12/21. +// + +#pragma once + +#include +#include +#include +#include + +#define ASSERT_DB(DB_RES) \ + if (!(DB_RES)->isSuccess()) { \ + LOGE("Database Error", "%s", (DB_RES)->getErrorMessage()->c_str()); \ + using namespace oatpp::web::protocol::http; \ + throw oatpp::web::protocol::http::HttpError(Status::CODE_500, \ + (DB_RES)->getErrorMessage()->c_str()); \ + } + +#define ASSERT_HTTP(COND, MSG) \ + if (!(COND)) { \ + LOGE("Http Error", "%s", MSG); \ + using namespace oatpp::web::protocol::http; \ + throw oatpp::web::protocol::http::HttpError(Status::CODE_500, MSG); \ + } + +#define ASSERT_EXIST(COND, MSG) \ + if (!(COND)) { \ + LOGE("Not Found", "%s", MSG); \ + using namespace oatpp::web::protocol::http; \ + throw oatpp::web::protocol::http::HttpError(Status::CODE_404, MSG); \ + } + +#define RETURN_STATUS_SUCCESS(DTO) \ + (DTO)->code = 200; \ + (DTO)->message = "success"; \ + return DTO; + +#include OATPP_CODEGEN_BEGIN(DTO) + +namespace QuickExam::dto::basic { + +template +class Response : public oatpp::DTO { + DTO_INIT(Response, DTO) + + DTO_FIELD(Int32, code) = 200; + DTO_FIELD_INFO(code) { + info->description = "Response code"; + info->required = true; + } + + DTO_FIELD(String, message) = "OK"; + DTO_FIELD_INFO(message) { + info->description = "Response message"; + info->required = true; + } + + DTO_FIELD(T, data); + DTO_FIELD_INFO(data) { + info->description = "Response data"; + } +}; + +} // namespace QuickExam::dto::basic + +#include OATPP_CODEGEN_END(DTO) \ No newline at end of file diff --git a/src/dto/db/Question.h b/src/dto/db/Question.h index 5377a3e..d291122 100644 --- a/src/dto/db/Question.h +++ b/src/dto/db/Question.h @@ -92,8 +92,8 @@ class QuestionSubQuestions : public oatpp::DTO { } }; -class QuestionTag : public oatpp::DTO { - DTO_INIT(QuestionTag, DTO) +class QuestionTags : public oatpp::DTO { + DTO_INIT(QuestionTags, DTO) DTO_FIELD(Int32, id); DTO_FIELD_INFO(id) { diff --git a/src/dto/response/Question.h b/src/dto/response/Question.h new file mode 100644 index 0000000..09ec948 --- /dev/null +++ b/src/dto/response/Question.h @@ -0,0 +1,28 @@ +// +// QuickExam +// Created on 2023/12/21. +// + +#pragma once + +#include +#include +#include +#include +#include + +#include OATPP_CODEGEN_BEGIN(DTO) + +namespace QuickExam::dto { + +class ResponseQuestion : public basic::Response> { + DTO_INIT(ResponseQuestion, basic::Response>) +}; + +class ResponseQuestionPage : public basic::Response> { + DTO_INIT(ResponseQuestionPage, basic::Response>) +}; + +} // namespace QuickExam::dto + +#include OATPP_CODEGEN_END(DTO) \ No newline at end of file diff --git a/src/service/QuestionService.cpp b/src/service/QuestionService.cpp new file mode 100644 index 0000000..993f9e5 --- /dev/null +++ b/src/service/QuestionService.cpp @@ -0,0 +1,94 @@ +// +// QuickExam +// Created on 2023/12/21. +// + +#include "QuestionService.h" + +namespace QuickExam::service { + +Object +QuestionService::getQuestionsByPageConditions(const Object &conditions) { + auto page_info = dto::QuestionPage::createShared(); + GET_PAGE(question_doo, conditions, page_info, dto::Question); + auto page_items = List>::createShared(); + for (auto &question : *page_info->items) { + page_items->push_back(getQuestionDetails(question)); + } + page_info->items = page_items; + auto response = dto::ResponseQuestionPage::createShared(); + response->data = page_info; + RETURN_STATUS_SUCCESS(response); +} + +Object QuestionService::getQuestionDetails(const Object &question) { +#define IF_DB_ERROR \ + if (!db_res->isSuccess()) { \ + LOGE("getQuestionDetails", "getQuestionContentsByQuestionId failed %s", \ + db_res->getErrorMessage()->c_str()); \ + return question; \ + } + // init + question->sub_questions = List>::createShared(); + question->answers = List>::createShared(); + question->tags = List>::createShared(); + question->question_contents = List>::createShared(); + // get contents + auto db_res = question_doo->getQuestionContentsByQuestionId(question->id); + IF_DB_ERROR; + question->question_contents = db_res->fetch>>(); + // get answers + db_res = answer_doo->getAnswersByQuestionId(question->id); + IF_DB_ERROR; + question->answers = db_res->fetch>>(); + for (auto &answer : *question->answers) { + db_res = answer_doo->getAnswerContentsByAnswerId(answer->id); + IF_DB_ERROR; + answer->answer_contents = db_res->fetch>>(); + } + // get tags + db_res = question_doo->getQuestionTagsIdByQuestionId(question->id); + IF_DB_ERROR; + auto tags_id = db_res->fetch>>(); + for (auto &tag_id : *tags_id) { + db_res = tag_doo->getTag(tag_id->tag_id); + IF_DB_ERROR; + auto tags = db_res->fetch>>(); + if (tags->size() != 1) { + LOGW("getQuestionDetails", "getTag failed %s", db_res->getErrorMessage()->c_str()); + continue; + } + question->tags->push_back(tags->front()); + } + // get sub questions + db_res = question_doo->getQuestionSubQuestionsIdByQuestionId(question->id); + IF_DB_ERROR; + auto sub_questions_id = db_res->fetch>>(); + for (auto &sub_question_id : *sub_questions_id) { + db_res = question_doo->getQuestion(sub_question_id->sub_question_id); + IF_DB_ERROR; + auto sub_questions = db_res->fetch>>(); + if (sub_questions->size() != 1) { + LOGW("getQuestionDetails", "getQuestion failed %s", db_res->getErrorMessage()->c_str()); + continue; + } + auto sub_question = getQuestionDetails(sub_questions->front()); + question->sub_questions->push_back(sub_question); + } + return question; +#undef IF_DB_ERROR +} + +Object QuestionService::getQuestionById(const Int32 &id) { + auto db_res = question_doo->getQuestion(id); + ASSERT_DB(db_res); + auto questions = db_res->fetch>>(); + std::string msg = "Question id: " + std::to_string(id) + " not found"; + ASSERT_EXIST(questions->size() == 1, msg.c_str()); + auto data = getQuestionDetails(questions->front()); + auto response = dto::ResponseQuestion::createShared(); + response->data = data; + RETURN_STATUS_SUCCESS(response); +} + +} // namespace QuickExam::service diff --git a/src/service/QuestionService.h b/src/service/QuestionService.h new file mode 100644 index 0000000..d9e877b --- /dev/null +++ b/src/service/QuestionService.h @@ -0,0 +1,45 @@ +// +// QuickExam +// Created on 2023/12/21. +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QuickExam::service { +using namespace oatpp; +class QuestionService { +private: + OATPP_COMPONENT(std::shared_ptr, question_doo); + OATPP_COMPONENT(std::shared_ptr, tag_doo); + OATPP_COMPONENT(std::shared_ptr, answer_doo); + OATPP_COMPONENT(std::shared_ptr, m_objectMapper); + +public: + QuestionService() = default; + ~QuestionService() = default; + + Object + getQuestionsByPageConditions(const Object &page_conditions); + + Object getQuestionById(const Int32 &id); + +private: + /** + * @brief Get the Question Details object + * @param question The question object + * @return Object The question object + */ + Object getQuestionDetails(const Object &question); +}; + +} // namespace QuickExam::service