diff --git a/351004/Lazuta/.gitignore b/351004/Lazuta/.gitignore index d8e18a7a5..20b86f6e5 100644 --- a/351004/Lazuta/.gitignore +++ b/351004/Lazuta/.gitignore @@ -655,4 +655,8 @@ drogon_ctl_* *.truststore cert/ certs/ -ssl/ \ No newline at end of file +ssl/ + + +# /config +/tools \ No newline at end of file diff --git a/351004/Lazuta/CMakeLists.txt b/351004/Lazuta/CMakeLists.txt index a44c8f008..851068141 100644 --- a/351004/Lazuta/CMakeLists.txt +++ b/351004/Lazuta/CMakeLists.txt @@ -11,14 +11,15 @@ file(GLOB_RECURSE SOURCE_FILES ) find_package(Drogon REQUIRED) +find_package(Jsoncpp REQUIRED) add_executable(${PROJECT_NAME} ${SOURCE_FILES} ) - target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" ) -target_link_libraries(${PROJECT_NAME} Drogon::Drogon) \ No newline at end of file +target_link_libraries(${PROJECT_NAME} Drogon::Drogon) + diff --git a/351004/Lazuta/README.md b/351004/Lazuta/README.md new file mode 100644 index 000000000..9e06f5eae --- /dev/null +++ b/351004/Lazuta/README.md @@ -0,0 +1,38 @@ +# Создаем директорию для инструментов +cd /project-root +mkdir -p tools/liquibase + +# Скачиваем Liquibase +wget https://github.com/liquibase/liquibase/releases/download/v4.24.0/liquibase-4.24.0.tar.gz + +# Распаковываем в tools/liquibase +tar -xzf liquibase-4.24.0.tar.gz -C tools/liquibase/ + +# Делаем исполняемый файл доступным +chmod +x tools/liquibase/liquibase + +# Скачиваем JDBC драйвер для PostgreSQL +wget https://jdbc.postgresql.org/download/postgresql-42.7.1.jar +mv postgresql-42.7.1.jar tools/liquibase/lib/ + +# В директории liquibase + +# Проверить статус +./liquibase status + +# Применить миграции +./liquibase update + +# Применить только для разработки (с тестовыми данными) +./liquibase update -Dcontext=dev + +# Сгенерировать SQL без выполнения +./liquibase update-sql > migration.sql + +# Откатить последние 2 изменения +./liquibase rollback-count 2 + +# Посмотреть историю +./liquibase history + +# Запустите скрипт migrate.sh в папке scripts, предварительно создав БД \ No newline at end of file diff --git a/351004/Lazuta/config.json b/351004/Lazuta/config.json deleted file mode 100644 index 741430e0e..000000000 --- a/351004/Lazuta/config.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "listeners": [ - { - "address": "0.0.0.0", - "port": 24110, - "https": false - } - ], - "app": - { - "threads_num": 1, - "document_root": "/home/dmitry/Distcomp/351004/Lazuta/http", - "implicit_page": "index.html" - } - -} \ No newline at end of file diff --git a/351004/Lazuta/config/app/config.json b/351004/Lazuta/config/app/config.json new file mode 100644 index 000000000..92879a3ab --- /dev/null +++ b/351004/Lazuta/config/app/config.json @@ -0,0 +1,28 @@ +{ + "listeners": [ + { + "address": "0.0.0.0", + "port": 24110, + "https": false + } + ], + "app": { + "threads_num": 1, + "document_root": "/home/dmitry/Distcomp/351004/Lazuta/http", + "implicit_page": "index.html" + }, + "db_clients": [ + { + "name": "default", + "rdbms": "postgresql", + "host": "127.0.0.1", + "port": 5432, + "dbname": "distcomp", + "user": "postgres", + "passwd": "postgres", + "is_fast": false, + "connection_number": 1, + "auto_batch": false + } + ] +} \ No newline at end of file diff --git a/351004/Lazuta/config/database/dev.properties b/351004/Lazuta/config/database/dev.properties new file mode 100644 index 000000000..69f18e5ff --- /dev/null +++ b/351004/Lazuta/config/database/dev.properties @@ -0,0 +1,5 @@ +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=distcomp +DB_USER=postgres +DB_PASSWORD=postgres \ No newline at end of file diff --git a/351004/Lazuta/migrations/changelog/db.changelog-master.xml b/351004/Lazuta/migrations/changelog/db.changelog-master.xml new file mode 100644 index 000000000..6fc61f02b --- /dev/null +++ b/351004/Lazuta/migrations/changelog/db.changelog-master.xml @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + ALTER TABLE tbl_editor + ADD CONSTRAINT chk_editor_login_length + CHECK (LENGTH(login) BETWEEN 2 AND 64); + + ALTER TABLE tbl_editor + ADD CONSTRAINT chk_editor_password_length + CHECK (LENGTH(password) BETWEEN 8 AND 128); + + ALTER TABLE tbl_editor + ADD CONSTRAINT chk_editor_firstname_length + CHECK (LENGTH(firstname) BETWEEN 2 AND 64); + + ALTER TABLE tbl_editor + ADD CONSTRAINT chk_editor_lastname_length + CHECK (LENGTH(lastname) BETWEEN 2 AND 64); + + + + COMMENT ON TABLE tbl_editor IS 'Редакторы/пользователи системы'; + COMMENT ON COLUMN tbl_editor.id IS 'Уникальный идентификатор'; + COMMENT ON COLUMN tbl_editor.login IS 'Логин пользователя (2-64 символа)'; + COMMENT ON COLUMN tbl_editor.password IS 'Пароль (8-128 символов)'; + COMMENT ON COLUMN tbl_editor.firstname IS 'Имя (2-64 символа)'; + COMMENT ON COLUMN tbl_editor.lastname IS 'Фамилия (2-64 символа)'; + + + + + + + + + + + + + + + + ALTER TABLE tbl_label + ADD CONSTRAINT chk_label_name_length + CHECK (LENGTH(name) BETWEEN 2 AND 32); + + COMMENT ON TABLE tbl_label IS 'Метки для категоризации задач'; + COMMENT ON COLUMN tbl_label.id IS 'Уникальный идентификатор метки'; + COMMENT ON COLUMN tbl_label.name IS 'Название метки (2-32 символа)'; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ALTER TABLE tbl_issue + ADD CONSTRAINT chk_issue_title_length + CHECK (LENGTH(title) BETWEEN 2 AND 64); + + ALTER TABLE tbl_issue + ADD CONSTRAINT chk_issue_content_length + CHECK (LENGTH(content) BETWEEN 4 AND 2048); + + ALTER TABLE tbl_issue + ADD CONSTRAINT chk_issue_dates + CHECK (modified >= created); + + COMMENT ON TABLE tbl_issue IS 'Задачи/посты системы'; + COMMENT ON COLUMN tbl_issue.id IS 'Уникальный идентификатор задачи'; + COMMENT ON COLUMN tbl_issue.editor_id IS 'ID автора задачи'; + COMMENT ON COLUMN tbl_issue.title IS 'Заголовок задачи (2-64 символа)'; + COMMENT ON COLUMN tbl_issue.content IS 'Содержание задачи (4-2048 символов)'; + COMMENT ON COLUMN tbl_issue.created IS 'Дата и время создания'; + COMMENT ON COLUMN tbl_issue.modified IS 'Дата и время последнего изменения'; + + + + + + + + CREATE OR REPLACE FUNCTION update_modified_column() + RETURNS TRIGGER AS $function_body$ + BEGIN + NEW.modified = CURRENT_TIMESTAMP; + RETURN NEW; + END; + $function_body$ LANGUAGE plpgsql; + --;; + + + + DROP FUNCTION IF EXISTS update_modified_column() CASCADE; + + + + + + + + + + + DROP TRIGGER IF EXISTS trigger_update_issue_modified ON tbl_issue; + + CREATE TRIGGER trigger_update_issue_modified + BEFORE UPDATE ON tbl_issue + FOR EACH ROW + EXECUTE FUNCTION update_modified_column(); + + + + DROP TRIGGER IF EXISTS trigger_update_issue_modified ON tbl_issue; + + + + + + + + + + + + + + + + + + + + + + + + ALTER TABLE tbl_post + ADD CONSTRAINT chk_post_content_length + CHECK (LENGTH(content) BETWEEN 2 AND 2048); + + COMMENT ON TABLE tbl_post IS 'Комментарии к задачам'; + COMMENT ON COLUMN tbl_post.id IS 'Уникальный идентификатор комментария'; + COMMENT ON COLUMN tbl_post.issue_id IS 'ID задачи, к которой относится комментарий'; + COMMENT ON COLUMN tbl_post.content IS 'Текст комментария (2-2048 символов)'; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + COMMENT ON TABLE tbl_issue_label IS 'Связь задач с метками'; + COMMENT ON COLUMN tbl_issue_label.id IS 'Уникальный идентификатор связи'; + COMMENT ON COLUMN tbl_issue_label.issue_id IS 'ID задачи'; + COMMENT ON COLUMN tbl_issue_label.label_id IS 'ID метки'; + + + + \ No newline at end of file diff --git a/351004/Lazuta/migrations/liquibase.properties b/351004/Lazuta/migrations/liquibase.properties new file mode 100644 index 000000000..5ebe76f31 --- /dev/null +++ b/351004/Lazuta/migrations/liquibase.properties @@ -0,0 +1,6 @@ +changeLogFile=changelog/db.changelog-master.xml +driver=org.postgresql.Driver +url=jdbc:postgresql://localhost:5432/distcomp +username=postgres +password=postgres +liquibase.hub.mode=off \ No newline at end of file diff --git a/351004/Lazuta/src/api/v1.0/controllers/EditorController.cc b/351004/Lazuta/src/api/v1.0/controllers/EditorController.cc index 54bb136da..ca8224228 100644 --- a/351004/Lazuta/src/api/v1.0/controllers/EditorController.cc +++ b/351004/Lazuta/src/api/v1.0/controllers/EditorController.cc @@ -1,6 +1,9 @@ #include "EditorController.h" #include +using namespace myapp; +using namespace myapp::dto; + EditorController::EditorController(std::unique_ptr service) { m_service = std::move(service); @@ -43,18 +46,9 @@ void EditorController::CreateEditor(const HttpRequestPtr& req, std::functionsetStatusCode(HttpStatusCode::k403Forbidden); httpResponse->setBody(Json::FastWriter().write(errorResponse)); - httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); - httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); - } - catch(const NotFoundException& e) - { - std::cout << "[ERROR] Not found: " << e.what() << std::endl; - Json::Value errorResponse; - errorResponse["message"] = e.what(); - httpResponse->setBody(Json::FastWriter().write(errorResponse)); - httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); - httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); } catch(const DatabaseException& e) { diff --git a/351004/Lazuta/src/api/v1.0/controllers/EditorController.h b/351004/Lazuta/src/api/v1.0/controllers/EditorController.h index 67ffbe344..1ddffcdd1 100644 --- a/351004/Lazuta/src/api/v1.0/controllers/EditorController.h +++ b/351004/Lazuta/src/api/v1.0/controllers/EditorController.h @@ -9,6 +9,7 @@ #include using namespace drogon; +using namespace myapp; class EditorController : public drogon::HttpController { diff --git a/351004/Lazuta/src/api/v1.0/controllers/IssueController.cc b/351004/Lazuta/src/api/v1.0/controllers/IssueController.cc index e752fd5aa..28a6a709e 100644 --- a/351004/Lazuta/src/api/v1.0/controllers/IssueController.cc +++ b/351004/Lazuta/src/api/v1.0/controllers/IssueController.cc @@ -2,6 +2,9 @@ #include "IssueController.h" #include +using namespace myapp; +using namespace myapp::dto; + IssueController::IssueController(std::unique_ptr service) { m_service = std::move(service); @@ -46,7 +49,7 @@ void IssueController::CreateIssue(const HttpRequestPtr& req, std::functionsetBody(Json::FastWriter().write(errorResponse)); httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); - httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + httpResponse->setStatusCode(HttpStatusCode::k403Forbidden); } catch(const NotFoundException& e) { diff --git a/351004/Lazuta/src/api/v1.0/controllers/IssueController.h b/351004/Lazuta/src/api/v1.0/controllers/IssueController.h index 70cf4cca4..5d0abf36f 100644 --- a/351004/Lazuta/src/api/v1.0/controllers/IssueController.h +++ b/351004/Lazuta/src/api/v1.0/controllers/IssueController.h @@ -9,6 +9,7 @@ #include using namespace drogon; +using namespace myapp; class IssueController : public drogon::HttpController { diff --git a/351004/Lazuta/src/api/v1.0/controllers/IssueLabelController.cc b/351004/Lazuta/src/api/v1.0/controllers/IssueLabelController.cc new file mode 100644 index 000000000..1ed0c7529 --- /dev/null +++ b/351004/Lazuta/src/api/v1.0/controllers/IssueLabelController.cc @@ -0,0 +1,608 @@ +#include "IssueLabelController.h" +#include + +using namespace myapp; +using namespace myapp::dto; + +IssueLabelController::IssueLabelController(std::unique_ptr service) +{ + m_service = std::move(service); + std::cout << "[INFO] IssueLabelController initialized" << std::endl; +} + +void IssueLabelController::CreateIssueLabel(const HttpRequestPtr& req, std::function&& callback) +{ + HttpResponsePtr httpResponse = HttpResponse::newHttpResponse(); + std::cout << "[INFO] CreateIssueLabel called" << std::endl; + + try + { + auto jsonFromRequest = req->getJsonObject(); + if (!jsonFromRequest) + { + std::cout << "[ERROR] Invalid JSON in CreateIssueLabel" << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Invalid JSON format"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + callback(httpResponse); + return; + } + + IssueLabelResponseTo dto = m_service->Create(IssueLabelRequestTo::fromJson(*jsonFromRequest)); + Json::Value jsonResponse = dto.toJson(); + + std::string responseBody = Json::FastWriter().write(jsonResponse); + std::cout << "[RESPONSE] " << responseBody << std::endl; + + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setBody(responseBody); + httpResponse->setStatusCode(HttpStatusCode::k201Created); + std::cout << "[INFO] IssueLabel created successfully" << std::endl; + } + catch(const ValidationException& e) + { + std::cout << "[ERROR] Validation error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + } + catch(const NotFoundException& e) + { + std::cout << "[ERROR] Not found: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + catch(const DatabaseException& e) + { + std::cout << "[ERROR] Database error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Database error occurred"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + catch(const std::exception& e) + { + std::cout << "[ERROR] Unknown error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Internal server error"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + + callback(httpResponse); +} + +void IssueLabelController::ReadIssueLabel(const HttpRequestPtr& req, std::function&& callback, uint64_t id) +{ + HttpResponsePtr httpResponse = HttpResponse::newHttpResponse(); + std::cout << "[INFO] ReadIssueLabel called for id: " << id << std::endl; + + try + { + IssueLabelResponseTo dto = m_service->Read(id); + Json::Value jsonResponse = dto.toJson(); + + std::string responseBody = Json::FastWriter().write(jsonResponse); + std::cout << "[RESPONSE] " << responseBody << std::endl; + + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setBody(responseBody); + httpResponse->setStatusCode(HttpStatusCode::k200OK); + std::cout << "[INFO] IssueLabel retrieved successfully" << std::endl; + } + catch(const ValidationException& e) + { + std::cout << "[ERROR] Validation error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + } + catch(const NotFoundException& e) + { + std::cout << "[ERROR] IssueLabel not found: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + catch(const DatabaseException& e) + { + std::cout << "[ERROR] Database error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Database error occurred"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + catch(const std::exception& e) + { + std::cout << "[ERROR] Unknown error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Internal server error"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + + callback(httpResponse); +} + +void IssueLabelController::UpdateIssueLabelIdFromRoute(const HttpRequestPtr& req, std::function&& callback, uint64_t id) +{ + HttpResponsePtr httpResponse = HttpResponse::newHttpResponse(); + std::cout << "[INFO] UpdateIssueLabelWithId called for id: " << id << std::endl; + + try + { + auto jsonFromRequest = req->getJsonObject(); + if (!jsonFromRequest) + { + std::cout << "[ERROR] Invalid JSON in UpdateIssueLabelWithId" << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Invalid JSON format"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + callback(httpResponse); + return; + } + + auto requestDto = IssueLabelRequestTo::fromJson(*jsonFromRequest); + IssueLabelResponseTo dto = m_service->Update(requestDto, id); + + Json::Value jsonResponse = dto.toJson(); + std::string responseBody = Json::FastWriter().write(jsonResponse); + std::cout << "[RESPONSE] " << responseBody << std::endl; + + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setBody(responseBody); + httpResponse->setStatusCode(HttpStatusCode::k200OK); + std::cout << "[INFO] IssueLabel updated successfully" << std::endl; + } + catch(const ValidationException& e) + { + std::cout << "[ERROR] Validation error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + } + catch(const NotFoundException& e) + { + std::cout << "[ERROR] IssueLabel not found for update: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + catch(const DatabaseException& e) + { + std::cout << "[ERROR] Database error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Database error occurred"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + catch(const std::exception& e) + { + std::cout << "[ERROR] Unknown error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Internal server error"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + + callback(httpResponse); +} + +void IssueLabelController::UpdateIssueLabelIdFromBody(const HttpRequestPtr& req, std::function&& callback) +{ + HttpResponsePtr httpResponse = HttpResponse::newHttpResponse(); + std::cout << "[INFO] UpdateIssueLabel called (without ID in path)" << std::endl; + + try + { + auto jsonFromRequest = req->getJsonObject(); + if (!jsonFromRequest) + { + std::cout << "[ERROR] Invalid JSON in UpdateIssueLabel" << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Invalid JSON format"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + callback(httpResponse); + return; + } + + auto requestDto = IssueLabelRequestTo::fromJson(*jsonFromRequest); + if (!requestDto.id.has_value()) + { + std::cout << "[ERROR] No ID in JSON" << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "ID is required in request body"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + callback(httpResponse); + return; + } + + IssueLabelResponseTo dto = m_service->Update(requestDto, requestDto.id.value()); + + Json::Value jsonResponse = dto.toJson(); + std::string responseBody = Json::FastWriter().write(jsonResponse); + std::cout << "[RESPONSE] " << responseBody << std::endl; + + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setBody(responseBody); + httpResponse->setStatusCode(HttpStatusCode::k200OK); + std::cout << "[INFO] IssueLabel updated successfully" << std::endl; + } + catch(const ValidationException& e) + { + std::cout << "[ERROR] Validation error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + } + catch(const NotFoundException& e) + { + std::cout << "[ERROR] IssueLabel not found for update: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + catch(const DatabaseException& e) + { + std::cout << "[ERROR] Database error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Database error occurred"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + catch(const std::exception& e) + { + std::cout << "[ERROR] Unknown error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Internal server error"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + + callback(httpResponse); +} + +void IssueLabelController::DeleteIssueLabel(const HttpRequestPtr& req, std::function&& callback, uint64_t id) +{ + HttpResponsePtr httpResponse = HttpResponse::newHttpResponse(); + std::cout << "[INFO] DeleteIssueLabel called for id: " << id << std::endl; + + try + { + if (m_service->Delete(id)) + { + httpResponse->setStatusCode(HttpStatusCode::k204NoContent); + std::cout << "[INFO] IssueLabel deleted successfully" << std::endl; + std::cout << "[RESPONSE] No content (204)" << std::endl; + } + else + { + std::cout << "[ERROR] IssueLabel not found for deletion" << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "IssueLabel not found"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + } + catch(const ValidationException& e) + { + std::cout << "[ERROR] Validation error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + } + catch(const NotFoundException& e) + { + std::cout << "[ERROR] IssueLabel not found: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + catch(const DatabaseException& e) + { + std::cout << "[ERROR] Database error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Database error occurred"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + catch(const std::exception& e) + { + std::cout << "[ERROR] Unknown error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Internal server error"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + + callback(httpResponse); +} + +void IssueLabelController::GetAllIssueLabels(const HttpRequestPtr& req, std::function&& callback) +{ + HttpResponsePtr httpResponse = HttpResponse::newHttpResponse(); + std::cout << "[INFO] GetAllIssueLabels called" << std::endl; + + try + { + std::vector dtos = m_service->GetAll(); + Json::Value jsonResponse(Json::arrayValue); + for (auto& dto: dtos) + { + jsonResponse.append(dto.toJson()); + } + + std::string responseBody = Json::FastWriter().write(jsonResponse); + std::cout << "[RESPONSE] " << responseBody << std::endl; + + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setBody(responseBody); + httpResponse->setStatusCode(HttpStatusCode::k200OK); + std::cout << "[INFO] Retrieved " << dtos.size() << " issue labels" << std::endl; + } + catch(const ValidationException& e) + { + std::cout << "[ERROR] Validation error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + } + catch(const NotFoundException& e) + { + std::cout << "[ERROR] Not found: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + catch(const DatabaseException& e) + { + std::cout << "[ERROR] Database error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Database error occurred"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + catch(const std::exception& e) + { + std::cout << "[ERROR] Unknown error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Internal server error"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + + callback(httpResponse); +} + +void IssueLabelController::GetByIssueId(const HttpRequestPtr& req, std::function&& callback, uint64_t issueId) +{ + HttpResponsePtr httpResponse = HttpResponse::newHttpResponse(); + std::cout << "[INFO] GetByIssueId called for issueId: " << issueId << std::endl; + + try + { + std::vector dtos = m_service->GetByIssueId(issueId); + Json::Value jsonResponse(Json::arrayValue); + for (auto& dto: dtos) + { + jsonResponse.append(dto.toJson()); + } + + std::string responseBody = Json::FastWriter().write(jsonResponse); + std::cout << "[RESPONSE] " << responseBody << std::endl; + + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setBody(responseBody); + httpResponse->setStatusCode(HttpStatusCode::k200OK); + std::cout << "[INFO] Retrieved " << dtos.size() << " issue labels for issue " << issueId << std::endl; + } + catch(const ValidationException& e) + { + std::cout << "[ERROR] Validation error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + } + catch(const NotFoundException& e) + { + std::cout << "[ERROR] Not found: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + catch(const DatabaseException& e) + { + std::cout << "[ERROR] Database error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Database error occurred"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + catch(const std::exception& e) + { + std::cout << "[ERROR] Unknown error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Internal server error"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + + callback(httpResponse); +} + +void IssueLabelController::GetByLabelId(const HttpRequestPtr& req, std::function&& callback, uint64_t labelId) +{ + HttpResponsePtr httpResponse = HttpResponse::newHttpResponse(); + std::cout << "[INFO] GetByLabelId called for labelId: " << labelId << std::endl; + + try + { + std::vector dtos = m_service->GetByLabelId(labelId); + Json::Value jsonResponse(Json::arrayValue); + for (auto& dto: dtos) + { + jsonResponse.append(dto.toJson()); + } + + std::string responseBody = Json::FastWriter().write(jsonResponse); + std::cout << "[RESPONSE] " << responseBody << std::endl; + + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setBody(responseBody); + httpResponse->setStatusCode(HttpStatusCode::k200OK); + std::cout << "[INFO] Retrieved " << dtos.size() << " issue labels for label " << labelId << std::endl; + } + catch(const ValidationException& e) + { + std::cout << "[ERROR] Validation error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + } + catch(const NotFoundException& e) + { + std::cout << "[ERROR] Not found: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + catch(const DatabaseException& e) + { + std::cout << "[ERROR] Database error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Database error occurred"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + catch(const std::exception& e) + { + std::cout << "[ERROR] Unknown error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Internal server error"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + + callback(httpResponse); +} + +void IssueLabelController::DeleteByIssueAndLabel(const HttpRequestPtr& req, std::function&& callback, uint64_t issueId, uint64_t labelId) +{ + HttpResponsePtr httpResponse = HttpResponse::newHttpResponse(); + std::cout << "[INFO] DeleteByIssueAndLabel called for issueId: " << issueId << ", labelId: " << labelId << std::endl; + + try + { + if (m_service->DeleteByIssueAndLabel(issueId, labelId)) + { + httpResponse->setStatusCode(HttpStatusCode::k204NoContent); + std::cout << "[INFO] IssueLabel combination deleted successfully" << std::endl; + std::cout << "[RESPONSE] No content (204)" << std::endl; + } + else + { + std::cout << "[ERROR] IssueLabel combination not found for deletion" << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "IssueLabel combination not found"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + } + catch(const ValidationException& e) + { + std::cout << "[ERROR] Validation error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k400BadRequest); + } + catch(const NotFoundException& e) + { + std::cout << "[ERROR] IssueLabel combination not found: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = e.what(); + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k404NotFound); + } + catch(const DatabaseException& e) + { + std::cout << "[ERROR] Database error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Database error occurred"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + catch(const std::exception& e) + { + std::cout << "[ERROR] Unknown error: " << e.what() << std::endl; + Json::Value errorResponse; + errorResponse["message"] = "Internal server error"; + httpResponse->setBody(Json::FastWriter().write(errorResponse)); + httpResponse->setContentTypeCode(ContentType::CT_APPLICATION_JSON); + httpResponse->setStatusCode(HttpStatusCode::k500InternalServerError); + } + + callback(httpResponse); +} \ No newline at end of file diff --git a/351004/Lazuta/src/api/v1.0/controllers/IssueLabelController.h b/351004/Lazuta/src/api/v1.0/controllers/IssueLabelController.h new file mode 100644 index 000000000..33f89a879 --- /dev/null +++ b/351004/Lazuta/src/api/v1.0/controllers/IssueLabelController.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +using namespace drogon; +using namespace myapp; + +class IssueLabelController : public drogon::HttpController +{ +private: + std::unique_ptr m_service = nullptr; + +public: + explicit IssueLabelController(std::unique_ptr service); + + METHOD_LIST_BEGIN + ADD_METHOD_TO(IssueLabelController::CreateIssueLabel, "/api/v1.0/issue-labels", drogon::Post); + ADD_METHOD_TO(IssueLabelController::ReadIssueLabel, "/api/v1.0/issue-labels/{id}", drogon::Get); + ADD_METHOD_TO(IssueLabelController::UpdateIssueLabelIdFromRoute, "/api/v1.0/issue-labels/{id}", drogon::Put); + ADD_METHOD_TO(IssueLabelController::UpdateIssueLabelIdFromBody, "/api/v1.0/issue-labels", drogon::Put); + ADD_METHOD_TO(IssueLabelController::DeleteIssueLabel, "/api/v1.0/issue-labels/{id}", drogon::Delete); + ADD_METHOD_TO(IssueLabelController::GetAllIssueLabels, "/api/v1.0/issue-labels", drogon::Get); + ADD_METHOD_TO(IssueLabelController::GetByIssueId, "/api/v1.0/issue-labels/issue/{issueId}", drogon::Get); + ADD_METHOD_TO(IssueLabelController::GetByLabelId, "/api/v1.0/issue-labels/label/{labelId}", drogon::Get); + ADD_METHOD_TO(IssueLabelController::DeleteByIssueAndLabel, "/api/v1.0/issue-labels/issue/{issueId}/label/{labelId}", drogon::Delete); + METHOD_LIST_END + +private: + void CreateIssueLabel(const HttpRequestPtr& req, std::function&& callback); + void ReadIssueLabel(const HttpRequestPtr& req, std::function&& callback, uint64_t id); + void UpdateIssueLabelIdFromRoute(const HttpRequestPtr& req, std::function&& callback, uint64_t id); + void UpdateIssueLabelIdFromBody(const HttpRequestPtr& req, std::function&& callback); + void DeleteIssueLabel(const HttpRequestPtr& req, std::function&& callback, uint64_t id); + void GetAllIssueLabels(const HttpRequestPtr& req, std::function&& callback); + void GetByIssueId(const HttpRequestPtr& req, std::function&& callback, uint64_t issueId); + void GetByLabelId(const HttpRequestPtr& req, std::function&& callback, uint64_t labelId); + void DeleteByIssueAndLabel(const HttpRequestPtr& req, std::function&& callback, uint64_t issueId, uint64_t labelId); +}; \ No newline at end of file diff --git a/351004/Lazuta/src/api/v1.0/controllers/LabelController.cc b/351004/Lazuta/src/api/v1.0/controllers/LabelController.cc index 2524d9f1c..3d82dac9c 100644 --- a/351004/Lazuta/src/api/v1.0/controllers/LabelController.cc +++ b/351004/Lazuta/src/api/v1.0/controllers/LabelController.cc @@ -2,6 +2,9 @@ #include "LabelController.h" #include +using namespace myapp; +using namespace myapp::dto; + LabelController::LabelController(std::unique_ptr service) { m_service = std::move(service); diff --git a/351004/Lazuta/src/api/v1.0/controllers/LabelController.h b/351004/Lazuta/src/api/v1.0/controllers/LabelController.h index 24a73310d..d620fdaad 100644 --- a/351004/Lazuta/src/api/v1.0/controllers/LabelController.h +++ b/351004/Lazuta/src/api/v1.0/controllers/LabelController.h @@ -9,6 +9,7 @@ #include using namespace drogon; +using namespace myapp; class LabelController : public drogon::HttpController { diff --git a/351004/Lazuta/src/api/v1.0/controllers/PostController.cc b/351004/Lazuta/src/api/v1.0/controllers/PostController.cc index 919f8cb5b..5a0fdfc73 100644 --- a/351004/Lazuta/src/api/v1.0/controllers/PostController.cc +++ b/351004/Lazuta/src/api/v1.0/controllers/PostController.cc @@ -2,6 +2,9 @@ #include "PostController.h" #include +using namespace myapp; +using namespace myapp::dto; + PostController::PostController(std::unique_ptr service) { m_service = std::move(service); diff --git a/351004/Lazuta/src/api/v1.0/controllers/PostController.h b/351004/Lazuta/src/api/v1.0/controllers/PostController.h index d19194b9e..cfd3131e4 100644 --- a/351004/Lazuta/src/api/v1.0/controllers/PostController.h +++ b/351004/Lazuta/src/api/v1.0/controllers/PostController.h @@ -9,6 +9,7 @@ #include using namespace drogon; +using namespace myapp; class PostController : public drogon::HttpController { diff --git a/351004/Lazuta/src/dao/DAO.h b/351004/Lazuta/src/dao/DAO.h index df914f456..a847a150f 100644 --- a/351004/Lazuta/src/dao/DAO.h +++ b/351004/Lazuta/src/dao/DAO.h @@ -3,20 +3,20 @@ #include #include #include +#include #include +#include -template +template class DAO { public: virtual ~DAO() = default; - virtual K Create(const T& entity) = 0; - virtual std::optional GetByID(K id) = 0; - virtual bool Update(K id, const T& entity) = 0; - virtual bool Delete(K id) = 0; - virtual std::vector ReadAll() = 0; - - virtual std::vector FindBy(std::function predicate) = 0; - virtual bool Exists(K id) = 0; + virtual std::variant Create(const T& entity) = 0; + virtual std::variant GetByID(K id) = 0; + virtual std::variant Update(K id, const T& entity) = 0; + virtual std::variant Delete(K id) = 0; + virtual std::variant, E> ReadAll() = 0; + virtual std::variant Exists(K id) = 0; }; \ No newline at end of file diff --git a/351004/Lazuta/src/dao/EditorDAO.h b/351004/Lazuta/src/dao/EditorDAO.h deleted file mode 100644 index 86bfb2f1a..000000000 --- a/351004/Lazuta/src/dao/EditorDAO.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "DAO.h" -#include - -class EditorDAO : public DAO -{ -public: - virtual ~EditorDAO() = default; - - virtual std::optional FindByLogin(const std::string& login) = 0; - virtual bool ExistsByLogin(const std::string& login) = 0; -}; \ No newline at end of file diff --git a/351004/Lazuta/src/dao/IssueDAO.h b/351004/Lazuta/src/dao/IssueDAO.h deleted file mode 100644 index 98069900d..000000000 --- a/351004/Lazuta/src/dao/IssueDAO.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "DAO.h" -#include -#include -#include - -class IssueDAO : public DAO -{ -public: - virtual ~IssueDAO() = default; - - virtual std::vector FindByEditorId(uint64_t editorId) = 0; - - virtual std::vector FindByLabelId(uint64_t labelId) = 0; - virtual void AddLabelToIssue(uint64_t issueId, uint64_t labelId) = 0; - virtual void RemoveLabelFromIssue(uint64_t issueId, uint64_t labelId) = 0; - virtual std::vector GetLabelIdsForIssue(uint64_t issueId) = 0; - - virtual std::vector FindByPostId(uint64_t postId) = 0; - - virtual std::vector FindByCriteria - ( - std::optional title, - std::optional content, - std::optional editorLogin, - std::optional> labelIds, - std::optional> labelNames - ) = 0; -}; \ No newline at end of file diff --git a/351004/Lazuta/src/dao/LabelDAO.h b/351004/Lazuta/src/dao/LabelDAO.h deleted file mode 100644 index 7a45b4e82..000000000 --- a/351004/Lazuta/src/dao/LabelDAO.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "DAO.h" -#include - -class LabelDAO : public DAO