finish basic http frame
This commit is contained in:
parent
6e0f2fc1e4
commit
4b6c822bf3
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
38
src/components/ErrorHandler.h
Normal file
38
src/components/ErrorHandler.h
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dto/basic/Response.h>
|
||||
#include <oatpp/web/protocol/http/outgoing/ResponseFactory.hpp>
|
||||
#include <oatpp/web/server/handler/ErrorHandler.hpp>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
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<oatpp::data::mapping::ObjectMapper> m_objectMapper;
|
||||
|
||||
public:
|
||||
explicit ErrorHandler(const std::shared_ptr<oatpp::data::mapping::ObjectMapper> &objectMapper)
|
||||
: m_objectMapper(objectMapper) {
|
||||
LOGD("ErrorHandler", "ErrorHandler created");
|
||||
}
|
||||
|
||||
std::shared_ptr<OutgoingResponse> handleError(const Status &status,
|
||||
const oatpp::String &message,
|
||||
const Headers &headers) override {
|
||||
auto error = dto::basic::Response<oatpp::String>::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
|
@ -5,6 +5,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <components/ErrorHandler.h>
|
||||
#include <dto/Configuration.h>
|
||||
#include <oatpp/core/macro/component.hpp>
|
||||
#include <oatpp/network/tcp/server/ConnectionProvider.hpp>
|
||||
@ -42,8 +43,9 @@ public:
|
||||
router); // get Router component
|
||||
OATPP_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>,
|
||||
objectMapper); // get ObjectMapper component
|
||||
|
||||
return oatpp::web::server::HttpConnectionHandler::createShared(router);
|
||||
auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);
|
||||
connectionHandler->setErrorHandler(std::make_shared<ErrorHandler>(objectMapper));
|
||||
return connectionHandler;
|
||||
}());
|
||||
};
|
||||
} // namespace QuickExam::component
|
||||
|
@ -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();
|
||||
}());
|
||||
|
@ -8,10 +8,12 @@
|
||||
#include <dto/Question.h>
|
||||
#include <dto/QuestionContent.h>
|
||||
#include <dto/Tag.h>
|
||||
#include <dto/response/Question.h>
|
||||
#include <oatpp/core/macro/codegen.hpp>
|
||||
#include <oatpp/core/macro/component.hpp>
|
||||
#include <oatpp/parser/json/mapping/ObjectMapper.hpp>
|
||||
#include <oatpp/web/server/api/ApiController.hpp>
|
||||
#include <service/QuestionService.h>
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
@ -33,6 +35,7 @@ public:
|
||||
|
||||
private:
|
||||
std::shared_ptr<ObjectMapper> objectMapper;
|
||||
service::QuestionService question_service;
|
||||
|
||||
public:
|
||||
ENDPOINT("POST", "/create", createQuestion, BODY_DTO(Object<dto::Question>, 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<Int32>("content_id");
|
||||
info->summary = "Delete Content";
|
||||
info->description = "Delete a content";
|
||||
info->pathParams.add<Int32>("content_id").description = "Content id";
|
||||
info->pathParams.add<Int32>("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<Int32>("question_id").description = "Question id";
|
||||
info->pathParams.add<Int32>("question_id").required = true;
|
||||
info->addResponse<Object<dto::ResponseQuestion>>(Status::CODE_200, "application/json");
|
||||
}
|
||||
|
||||
ENDPOINT("POST",
|
||||
"/get/list/by/conditions",
|
||||
getQuestions,
|
||||
BODY_DTO(Object<dto::QuestionCondition>, 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<Object<dto::QuestionCondition>>("application/json");
|
||||
info->addResponse<Object<dto::ResponseQuestionPage>>(Status::CODE_200, "application/json");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QuickExam::controller
|
||||
|
@ -35,7 +35,7 @@ public:
|
||||
PREPARE(true),
|
||||
PARAM(oatpp::Object<dto::db::AnswerContents>, a))
|
||||
|
||||
QUERY(getAnswerByQuestionId,
|
||||
QUERY(getAnswersByQuestionId,
|
||||
"SELECT "
|
||||
"* "
|
||||
"FROM qe_answer "
|
||||
|
@ -4,17 +4,25 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dto/Question.h>
|
||||
#include <dto/db/Question.h>
|
||||
#include <oatpp-postgresql/orm.hpp>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(DbClient)
|
||||
|
||||
namespace QuickExam::doo {
|
||||
|
||||
class Question : public oatpp::orm::DbClient {
|
||||
private:
|
||||
std::shared_ptr<oatpp::orm::Executor> m_executor;
|
||||
|
||||
public:
|
||||
explicit Question(const std::shared_ptr<oatpp::orm::Executor> &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<dto::db::QuestionTag>, q))
|
||||
PARAM(oatpp::Object<dto::db::QuestionTags>, q))
|
||||
|
||||
QUERY(getQuestion,
|
||||
"SELECT "
|
||||
@ -152,6 +160,48 @@ public:
|
||||
"WHERE id = :q.id",
|
||||
PREPARE(true),
|
||||
PARAM(oatpp::Object<dto::db::QuestionContents>, 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<dto::QuestionCondition> &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
|
||||
|
@ -60,6 +60,12 @@ public:
|
||||
"WHERE id = :t.id",
|
||||
PREPARE(true),
|
||||
PARAM(oatpp::Object<dto::db::Tag>, t))
|
||||
|
||||
QUERY(getAllTagIds,
|
||||
"SELECT "
|
||||
"id "
|
||||
"FROM qe_tag",
|
||||
PREPARE(true))
|
||||
};
|
||||
|
||||
} // namespace QuickExam::doo
|
||||
|
@ -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) = Log::createShared();
|
||||
DTO_FIELD_INFO(log) {
|
||||
info->description = "Log configuration";
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <dto/Answer.h>
|
||||
#include <dto/QuestionContent.h>
|
||||
#include <dto/Tag.h>
|
||||
#include <dto/basic/Page.h>
|
||||
#include <oatpp/core/Types.hpp>
|
||||
#include <oatpp/core/macro/codegen.hpp>
|
||||
|
||||
@ -18,28 +19,45 @@ namespace QuickExam::dto {
|
||||
class Question : public db::Question {
|
||||
DTO_INIT(Question, db::Question)
|
||||
|
||||
DTO_FIELD(List<Object<QuestionContent>>,
|
||||
question_contents) = List<Object<QuestionContent>>::createShared();
|
||||
DTO_FIELD(List<Object<QuestionContent>>, question_contents);
|
||||
DTO_FIELD_INFO(question_contents) {
|
||||
info->description = "Question contents";
|
||||
}
|
||||
|
||||
DTO_FIELD(List<Object<Question>>, sub_questions) = List<Object<Question>>::createShared();
|
||||
DTO_FIELD(List<Object<Question>>, sub_questions);
|
||||
DTO_FIELD_INFO(sub_questions) {
|
||||
info->description = "Sub questions";
|
||||
}
|
||||
|
||||
DTO_FIELD(List<Object<Answer>>, answers) = List<Object<Answer>>::createShared();
|
||||
DTO_FIELD(List<Object<Answer>>, answers);
|
||||
DTO_FIELD_INFO(answers) {
|
||||
info->description = "Question answers";
|
||||
}
|
||||
|
||||
DTO_FIELD(List<Object<Tag>>, tags) = List<Object<Tag>>::createShared();
|
||||
DTO_FIELD(List<Object<Tag>>, tags);
|
||||
DTO_FIELD_INFO(tags) {
|
||||
info->description = "Question tags";
|
||||
}
|
||||
};
|
||||
|
||||
class QuestionCondition : public basic::Condition {
|
||||
DTO_INIT(QuestionCondition, basic::Condition)
|
||||
|
||||
DTO_FIELD(List<Int32>, tag_ids) = List<Int32>::createShared();
|
||||
DTO_FIELD_INFO(tag_ids) {
|
||||
info->description = "Question tags (empty means all tags)";
|
||||
}
|
||||
|
||||
DTO_FIELD(List<Int32>, types) = List<Int32>::createShared();
|
||||
DTO_FIELD_INFO(types) {
|
||||
info->description = "Question types (empty means all types)";
|
||||
}
|
||||
};
|
||||
|
||||
class QuestionPage : public basic::Page<oatpp::Object<Question>> {
|
||||
DTO_INIT(QuestionPage, basic::Page<oatpp::Object<Question>>)
|
||||
};
|
||||
|
||||
} // namespace QuickExam::dto
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
@ -5,6 +5,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dto/basic/Page.h>
|
||||
#include <dto/db/Tag.h>
|
||||
#include <oatpp/core/Types.hpp>
|
||||
#include <oatpp/core/macro/codegen.hpp>
|
||||
@ -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<oatpp::Object<Tag>> {
|
||||
DTO_INIT(TagPage, basic::Page<oatpp::Object<Tag>>)
|
||||
};
|
||||
|
||||
} // namespace QuickExam::dto
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
@ -29,6 +29,17 @@ ENUM(QuestionType,
|
||||
VALUE(long_answer, 4, "long-answer"),
|
||||
VALUE(file_upload, 5, "file-upload"))
|
||||
|
||||
static oatpp::List<oatpp::Int32> allQuestionTypes() {
|
||||
auto list = oatpp::List<oatpp::Int32>::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"),
|
||||
|
132
src/dto/basic/Page.h
Normal file
132
src/dto/basic/Page.h
Normal file
@ -0,0 +1,132 @@
|
||||
//
|
||||
// QuickExam
|
||||
// Created on 2023/12/21.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <oatpp/core/Types.hpp>
|
||||
#include <oatpp/core/macro/codegen.hpp>
|
||||
|
||||
#define DEFINE_CONDITION_FUNC static std::string conditionSqlString
|
||||
|
||||
#define DEFINE_COUNT_QUERY(TABLE_NAME, CONDITION_DTO) \
|
||||
std::shared_ptr<oatpp::orm::QueryResult> getPageCountByConditions( \
|
||||
const oatpp::Object<CONDITION_DTO> &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<oatpp::orm::QueryResult> getPageByConditions( \
|
||||
const oatpp::Object<CONDITION_DTO> &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<oatpp::List<oatpp::Object<dto::basic::Page<Void>>>>()->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<oatpp::List<oatpp::Object<ITEM_DTO>>>(); \
|
||||
}
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
namespace QuickExam::dto::basic {
|
||||
template <class L>
|
||||
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<L>, items) = List<L>::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)
|
68
src/dto/basic/Response.h
Normal file
68
src/dto/basic/Response.h
Normal file
@ -0,0 +1,68 @@
|
||||
//
|
||||
// QuickExam
|
||||
// Created on 2023/12/21.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dto/basic/Enmus.h>
|
||||
#include <oatpp/core/Types.hpp>
|
||||
#include <oatpp/core/macro/codegen.hpp>
|
||||
#include <oatpp/web/protocol/http/Http.hpp>
|
||||
|
||||
#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 T>
|
||||
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)
|
@ -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) {
|
||||
|
28
src/dto/response/Question.h
Normal file
28
src/dto/response/Question.h
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// QuickExam
|
||||
// Created on 2023/12/21.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dto/Question.h>
|
||||
#include <dto/basic/Enmus.h>
|
||||
#include <dto/basic/Response.h>
|
||||
#include <oatpp/core/Types.hpp>
|
||||
#include <oatpp/core/macro/codegen.hpp>
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
namespace QuickExam::dto {
|
||||
|
||||
class ResponseQuestion : public basic::Response<oatpp::Object<dto::Question>> {
|
||||
DTO_INIT(ResponseQuestion, basic::Response<oatpp::Object<dto::Question>>)
|
||||
};
|
||||
|
||||
class ResponseQuestionPage : public basic::Response<oatpp::Object<dto::QuestionPage>> {
|
||||
DTO_INIT(ResponseQuestionPage, basic::Response<oatpp::Object<dto::QuestionPage>>)
|
||||
};
|
||||
|
||||
} // namespace QuickExam::dto
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
94
src/service/QuestionService.cpp
Normal file
94
src/service/QuestionService.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
//
|
||||
// QuickExam
|
||||
// Created on 2023/12/21.
|
||||
//
|
||||
|
||||
#include "QuestionService.h"
|
||||
|
||||
namespace QuickExam::service {
|
||||
|
||||
Object<dto::ResponseQuestionPage>
|
||||
QuestionService::getQuestionsByPageConditions(const Object<dto::QuestionCondition> &conditions) {
|
||||
auto page_info = dto::QuestionPage::createShared();
|
||||
GET_PAGE(question_doo, conditions, page_info, dto::Question);
|
||||
auto page_items = List<Object<dto::Question>>::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<dto::Question> QuestionService::getQuestionDetails(const Object<dto::Question> &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<Object<dto::Question>>::createShared();
|
||||
question->answers = List<Object<dto::Answer>>::createShared();
|
||||
question->tags = List<Object<dto::Tag>>::createShared();
|
||||
question->question_contents = List<Object<dto::QuestionContent>>::createShared();
|
||||
// get contents
|
||||
auto db_res = question_doo->getQuestionContentsByQuestionId(question->id);
|
||||
IF_DB_ERROR;
|
||||
question->question_contents = db_res->fetch<List<Object<dto::QuestionContent>>>();
|
||||
// get answers
|
||||
db_res = answer_doo->getAnswersByQuestionId(question->id);
|
||||
IF_DB_ERROR;
|
||||
question->answers = db_res->fetch<List<Object<dto::Answer>>>();
|
||||
for (auto &answer : *question->answers) {
|
||||
db_res = answer_doo->getAnswerContentsByAnswerId(answer->id);
|
||||
IF_DB_ERROR;
|
||||
answer->answer_contents = db_res->fetch<List<Object<dto::AnswerContent>>>();
|
||||
}
|
||||
// get tags
|
||||
db_res = question_doo->getQuestionTagsIdByQuestionId(question->id);
|
||||
IF_DB_ERROR;
|
||||
auto tags_id = db_res->fetch<List<Object<dto::db::QuestionTags>>>();
|
||||
for (auto &tag_id : *tags_id) {
|
||||
db_res = tag_doo->getTag(tag_id->tag_id);
|
||||
IF_DB_ERROR;
|
||||
auto tags = db_res->fetch<List<Object<dto::Tag>>>();
|
||||
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<List<Object<dto::db::QuestionSubQuestions>>>();
|
||||
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<List<Object<dto::Question>>>();
|
||||
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<dto::ResponseQuestion> QuestionService::getQuestionById(const Int32 &id) {
|
||||
auto db_res = question_doo->getQuestion(id);
|
||||
ASSERT_DB(db_res);
|
||||
auto questions = db_res->fetch<List<Object<dto::Question>>>();
|
||||
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
|
45
src/service/QuestionService.h
Normal file
45
src/service/QuestionService.h
Normal file
@ -0,0 +1,45 @@
|
||||
//
|
||||
// QuickExam
|
||||
// Created on 2023/12/21.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <doo/Answer.h>
|
||||
#include <doo/Question.h>
|
||||
#include <doo/Tag.h>
|
||||
#include <dto/Question.h>
|
||||
#include <dto/basic/Enmus.h>
|
||||
#include <dto/response/Question.h>
|
||||
#include <oatpp/core/macro/component.hpp>
|
||||
#include <oatpp/parser/json/mapping/ObjectMapper.hpp>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
namespace QuickExam::service {
|
||||
using namespace oatpp;
|
||||
class QuestionService {
|
||||
private:
|
||||
OATPP_COMPONENT(std::shared_ptr<doo::Question>, question_doo);
|
||||
OATPP_COMPONENT(std::shared_ptr<doo::Tag>, tag_doo);
|
||||
OATPP_COMPONENT(std::shared_ptr<doo::Answer>, answer_doo);
|
||||
OATPP_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, m_objectMapper);
|
||||
|
||||
public:
|
||||
QuestionService() = default;
|
||||
~QuestionService() = default;
|
||||
|
||||
Object<dto::ResponseQuestionPage>
|
||||
getQuestionsByPageConditions(const Object<dto::QuestionCondition> &page_conditions);
|
||||
|
||||
Object<dto::ResponseQuestion> getQuestionById(const Int32 &id);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Get the Question Details object
|
||||
* @param question The question object
|
||||
* @return Object<dto::Question> The question object
|
||||
*/
|
||||
Object<dto::Question> getQuestionDetails(const Object<dto::Question> &question);
|
||||
};
|
||||
|
||||
} // namespace QuickExam::service
|
Loading…
Reference in New Issue
Block a user