From f72cf41c2c8d112c355361ceb03110dc2a944270 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 25 Sep 2025 11:25:19 +0500 Subject: [PATCH 1/8] feat(service): #28: link service with db --- src/services/to-dos.service.cpp | 68 ++++++++++++++++++++------------- src/services/to-dos.service.h | 32 ++++++++-------- 2 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/services/to-dos.service.cpp b/src/services/to-dos.service.cpp index 4efd82a..ca48a7e 100644 --- a/src/services/to-dos.service.cpp +++ b/src/services/to-dos.service.cpp @@ -1,39 +1,55 @@ #include "to-dos.service.h" +#include +#include + +using std::string; +using std::vector; + +static vector mapToDTOs(const std::shared_ptr>& todos) +{ + vector out; + out.reserve(todos ? todos->size() : 0); + if (todos) + { + for (const auto& t : *todos) + { + ToDoDTO dto; + dto.id = static_cast(t.id()); + dto.description = t.name(); + out.push_back(std::move(dto)); + } + } + return out; +} -void ToDoService::addToDo(const std::string description) { - _toDos.push_back({ - _nextToDoId, - description - }); +void ToDoService::addToDo(const string description) +{ + const std::time_t now_utc = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + (void) _commands.create_todo(description, now_utc); +} - _nextToDoId++; +bool ToDoService::completeToDo(int id) +{ + (void) _commands.soft_delete_todo(id); + return true; } -const std::vector& ToDoService::getToDos() const { - return _toDos; +const vector ToDoService::getToDos() const +{ + auto todos = _queries.get_all_todos(); + return mapToDTOs(todos); } -Json::Value ToDoService::toJson() const { +Json::Value ToDoService::toJson() const +{ Json::Value json; Json::Value toDosArray(Json::arrayValue); - - for (const auto& toDo : _toDos) { - toDosArray.append(toDo.toJson()); + auto todos = _queries.get_all_todos(); + auto dtos = mapToDTOs(todos); + for (const auto& dto : dtos) + { + toDosArray.append(dto.toJson()); } - json["toDos"] = toDosArray; - return json; -} - -bool ToDoService::completeToDo(int id) { - for (auto it = _toDos.begin(); it != _toDos.end(); ++it) { - if (it->id == id) { - _toDos.erase(it); - - return true; - } - } - - return false; } \ No newline at end of file diff --git a/src/services/to-dos.service.h b/src/services/to-dos.service.h index 4a39f5e..bfba4b9 100644 --- a/src/services/to-dos.service.h +++ b/src/services/to-dos.service.h @@ -1,29 +1,27 @@ #pragma once +#include "data/commands/todo-commands.h" +#include "data/queries/todo-queries.h" #include "dtos/to-dos-dto.h" -#include #include +#include +#include class ToDoService { private: - std::vector _toDos{ - {0, "Read TDD book"}, - {1, "Read Mobx & React basic docs"}, - {2, "Explore Cypress"}, - }; - - int _nextToDoId = 3; + ToDoQueries& _queries; + ToDoCommands& _commands; public: - ToDoService() = default; - - void addToDo(const std::string description); - - bool completeToDo(int id); - - const std::vector &getToDos() const; - - Json::Value toJson() const; + explicit ToDoService(ToDoQueries& queries, ToDoCommands& commands) + : _queries(queries), + _commands(commands) + {} + + void addToDo(const std::string description); + bool completeToDo(int id); + const std::vector getToDos() const; + Json::Value toJson() const; }; \ No newline at end of file From 30f44b10adf65540d39a9f7d68da8ed91a54e174 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 25 Sep 2025 11:26:22 +0500 Subject: [PATCH 2/8] feat(connection): #28: add singletone class for db connection --- src/data/db_connection.cpp | 30 ++++++++++++++++++++++++++++++ src/data/db_connection.h | 14 ++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/data/db_connection.cpp create mode 100644 src/data/db_connection.h diff --git a/src/data/db_connection.cpp b/src/data/db_connection.cpp new file mode 100644 index 0000000..38ec95a --- /dev/null +++ b/src/data/db_connection.cpp @@ -0,0 +1,30 @@ +#include "db_connection.h" + +#include +#include + +#include + +std::shared_ptr DbConnection::instance_ = nullptr; + +std::shared_ptr DbConnection::get() +{ + static std::once_flag flag; + std::call_once( + flag, + []() + { + const std::string user = std::getenv("POSTGRES_USER"); + const std::string password = std::getenv("POSTGRES_PASSWORD"); + const std::string db_name = std::getenv("POSTGRES_DB"); + const std::string host = std::getenv("POSTGRES_HOST"); + const std::string port = std::getenv("POSTGRES_PORT"); + + std::string conninfo = "host=" + host + " port=" + port + " dbname=" + db_name + " user=" + user + " password=" + password; + + instance_ = std::shared_ptr(new odb::pgsql::database(conninfo)); + } + ); + + return instance_; +} \ No newline at end of file diff --git a/src/data/db_connection.h b/src/data/db_connection.h new file mode 100644 index 0000000..4b6d3be --- /dev/null +++ b/src/data/db_connection.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +class DbConnection +{ +public: + static std::shared_ptr get(); + +private: + DbConnection() = default; + static std::shared_ptr instance_; +}; \ No newline at end of file From c417ef18b247dcbd2aa2977eca7d54682117387f Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 25 Sep 2025 11:26:47 +0500 Subject: [PATCH 3/8] feat(connection): #28: expand controller constructor with service creation --- src/controllers/app-controller.cpp | 88 +++++++++++++++++------------- src/controllers/app-controller.h | 41 ++++++++------ 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/src/controllers/app-controller.cpp b/src/controllers/app-controller.cpp index 5bf135a..b7585eb 100644 --- a/src/controllers/app-controller.cpp +++ b/src/controllers/app-controller.cpp @@ -1,43 +1,53 @@ #include "app-controller.h" -#include "services/to-dos.service.h" +#include "data/db_connection.h" // TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/25): Create http exception handler or generic class for handling that type of errors -HttpResponsePtr AppController::createInternalServerErrorResponse(const std::string& error) const -{ +HttpResponsePtr AppController::createInternalServerErrorResponse(const std::string& error) const +{ Json::Value jsonResponse; jsonResponse["status"] = "Error"; jsonResponse["message"] = "Internal server error"; jsonResponse["error"] = error; - + auto resp = HttpResponse::newHttpJsonResponse(jsonResponse); resp->setStatusCode(k500InternalServerError); return resp; } -void AppController::getToDos(const HttpRequestPtr& req, - std::function&& callback) { - Json::Value jsonResponse; +AppController::AppController() +{ + db_ = std::move(DbConnection::get()); + queries_ = std::make_unique(*db_); + commands_ = std::make_unique(*db_); + todo_service_ = std::make_unique(*queries_, *commands_); +} - try { - ToDoService toDos; +void AppController::getToDos(const HttpRequestPtr& req, std::function&& callback) +{ + Json::Value jsonResponse; - auto resp = HttpResponse::newHttpJsonResponse(toDos.toJson()); + try + { + auto resp = HttpResponse::newHttpJsonResponse(todo_service_->toJson()); resp->setStatusCode(k200OK); callback(resp); - - } catch (const std::exception& e) { + } + catch (const std::exception& e) + { callback(createInternalServerErrorResponse(e.what())); } } -void AppController::addToDo(const HttpRequestPtr& req, - std::function&& callback) { +void AppController::addToDo(const HttpRequestPtr& req, std::function&& callback) +{ Json::Value jsonResponse; - try { + try + { auto json = req->getJsonObject(); - if (!json || !json->isMember("description")) { + if (!json || !json->isMember("description")) + { Json::Value result; result["status"] = "error"; result["message"] = "Invalid JSON"; @@ -49,26 +59,28 @@ void AppController::addToDo(const HttpRequestPtr& req, return; } - ToDoService toDos; - toDos.addToDo(json->get("description", "").asString()); + todo_service_->addToDo(json->get("description", "").asString()); auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k201Created); callback(resp); - - } catch (const std::exception& e) { + } + catch (const std::exception& e) + { callback(createInternalServerErrorResponse(e.what())); } } -void AppController::completeToDos(const HttpRequestPtr& req, - std::function&& callback) { +void AppController::completeToDos(const HttpRequestPtr& req, std::function&& callback) +{ Json::Value jsonResponse; - try { + try + { auto json = req->getJsonObject(); - if (!json || !json->isMember("toDosIds")) { + if (!json || !json->isMember("toDosIds")) + { Json::Value result; result["status"] = "error"; result["message"] = "Invalid JSON"; @@ -80,38 +92,40 @@ void AppController::completeToDos(const HttpRequestPtr& req, return; } - ToDoService toDos; auto toDosIds = json->get("toDosIds", Json::Value(Json::arrayValue)); - for (const auto& id : toDosIds) { - toDos.completeToDo(id.asInt()); + for (const auto& id : toDosIds) + { + todo_service_->completeToDo(id.asInt()); } auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k200OK); callback(resp); - - } catch (const std::exception& e) { + } + catch (const std::exception& e) + { callback(createInternalServerErrorResponse(e.what())); } } -void AppController::deleteToDo(const HttpRequestPtr& req, - std::function&& callback) { +void AppController::deleteToDo(const HttpRequestPtr& req, std::function&& callback) +{ Json::Value jsonResponse; - try { + try + { auto toDoIdStr = req->getParameter("toDoId"); int toDoId = std::stoi(toDoIdStr); - - ToDoService toDos; - toDos.completeToDo(toDoId); + + todo_service_->completeToDo(toDoId); auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k200OK); callback(resp); - - } catch (const std::exception& e) { + } + catch (const std::exception& e) + { callback(createInternalServerErrorResponse(e.what())); } } diff --git a/src/controllers/app-controller.h b/src/controllers/app-controller.h index 08f054a..6528432 100644 --- a/src/controllers/app-controller.h +++ b/src/controllers/app-controller.h @@ -2,28 +2,37 @@ #include +#include "data/commands/todo-commands.h" +#include "data/queries/todo-queries.h" +#include "services/to-dos.service.h" + using namespace drogon; -class AppController : public drogon::HttpController { +class AppController : public drogon::HttpController +{ public: + explicit AppController(); + METHOD_LIST_BEGIN - ADD_METHOD_TO(AppController::getToDos, "/to-dos", Get); // Getting a list of tasks - ADD_METHOD_TO(AppController::addToDo, "/to-dos", Post); // Adding a new task - ADD_METHOD_TO(AppController::completeToDos, "/to-dos/complete", Post); // Executing (deleting) a task list - ADD_METHOD_TO(AppController::deleteToDo, "/to-dos", Delete); // Deleting a specific task + ADD_METHOD_TO(AppController::getToDos, "/to-dos", Get); // Getting a list of tasks + ADD_METHOD_TO(AppController::addToDo, "/to-dos", Post); // Adding a new task + ADD_METHOD_TO(AppController::completeToDos, "/to-dos/complete", Post); // Executing (deleting) a task list + ADD_METHOD_TO(AppController::deleteToDo, "/to-dos", Delete); // Deleting a specific task METHOD_LIST_END HttpResponsePtr createInternalServerErrorResponse(const std::string& error) const; - void getToDos(const HttpRequestPtr& req, - std::function&& callback); - - void addToDo(const HttpRequestPtr& req, - std::function&& callback); - - void completeToDos(const HttpRequestPtr& req, - std::function&& callback); - - void deleteToDo(const HttpRequestPtr& req, - std::function&& callback); + void getToDos(const HttpRequestPtr& req, std::function&& callback); + + void addToDo(const HttpRequestPtr& req, std::function&& callback); + + void completeToDos(const HttpRequestPtr& req, std::function&& callback); + + void deleteToDo(const HttpRequestPtr& req, std::function&& callback); + +private: + std::shared_ptr db_; + std::unique_ptr queries_; + std::unique_ptr commands_; + std::unique_ptr todo_service_; }; \ No newline at end of file From 17a9428d45da030894ac811989e8a1816b479dff Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 25 Sep 2025 12:08:27 +0500 Subject: [PATCH 4/8] ref(dtos): #28: change field description --- src/controllers/app-controller.cpp | 4 ++-- src/services/dtos/to-dos-dto.cpp | 5 +++-- src/services/dtos/to-dos-dto.h | 8 ++++---- src/services/to-dos.service.cpp | 6 +++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/controllers/app-controller.cpp b/src/controllers/app-controller.cpp index b7585eb..4282541 100644 --- a/src/controllers/app-controller.cpp +++ b/src/controllers/app-controller.cpp @@ -46,7 +46,7 @@ void AppController::addToDo(const HttpRequestPtr& req, std::functiongetJsonObject(); - if (!json || !json->isMember("description")) + if (!json || !json->isMember("name")) { Json::Value result; result["status"] = "error"; @@ -59,7 +59,7 @@ void AppController::addToDo(const HttpRequestPtr& req, std::functionaddToDo(json->get("description", "").asString()); + todo_service_->addToDo(json->get("name", "").asString()); auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k201Created); diff --git a/src/services/dtos/to-dos-dto.cpp b/src/services/dtos/to-dos-dto.cpp index ae826e5..3c1b7e3 100644 --- a/src/services/dtos/to-dos-dto.cpp +++ b/src/services/dtos/to-dos-dto.cpp @@ -1,9 +1,10 @@ #include "to-dos-dto.h" -Json::Value ToDoDTO::toJson() const { +Json::Value ToDoDTO::toJson() const +{ Json::Value json; json["id"] = id; - json["description"] = description; + json["name"] = name; return json; } \ No newline at end of file diff --git a/src/services/dtos/to-dos-dto.h b/src/services/dtos/to-dos-dto.h index b880a0b..b8c3837 100644 --- a/src/services/dtos/to-dos-dto.h +++ b/src/services/dtos/to-dos-dto.h @@ -1,12 +1,12 @@ #pragma once -#include #include +#include struct ToDoDTO { - int id; - std::string description; + int id; + std::string name; - Json::Value toJson() const; + Json::Value toJson() const; }; \ No newline at end of file diff --git a/src/services/to-dos.service.cpp b/src/services/to-dos.service.cpp index ca48a7e..7649f59 100644 --- a/src/services/to-dos.service.cpp +++ b/src/services/to-dos.service.cpp @@ -15,17 +15,17 @@ static vector mapToDTOs(const std::shared_ptr>& todos { ToDoDTO dto; dto.id = static_cast(t.id()); - dto.description = t.name(); + dto.name = t.name(); out.push_back(std::move(dto)); } } return out; } -void ToDoService::addToDo(const string description) +void ToDoService::addToDo(const string name) { const std::time_t now_utc = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - (void) _commands.create_todo(description, now_utc); + (void) _commands.create_todo(name, now_utc); } bool ToDoService::completeToDo(int id) From cbe256af40d6314f10129899313b5942d2a45768 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 25 Sep 2025 12:09:32 +0500 Subject: [PATCH 5/8] fix(controller): #28: change service methods in correct calls --- src/controllers/app-controller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/app-controller.cpp b/src/controllers/app-controller.cpp index 4282541..fa89f84 100644 --- a/src/controllers/app-controller.cpp +++ b/src/controllers/app-controller.cpp @@ -28,7 +28,7 @@ void AppController::getToDos(const HttpRequestPtr& req, std::functiontoJson()); + auto resp = HttpResponse::newHttpJsonResponse(todo_service_->getToDos()); resp->setStatusCode(k200OK); callback(resp); } @@ -118,7 +118,7 @@ void AppController::deleteToDo(const HttpRequestPtr& req, std::functiongetParameter("toDoId"); int toDoId = std::stoi(toDoIdStr); - todo_service_->completeToDo(toDoId); + todo_service_->deleteToDo(toDoId); auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k200OK); From 852c1937d5b6da0da39062a92170bbf039e8b33a Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 25 Sep 2025 12:10:01 +0500 Subject: [PATCH 6/8] ref(dtos): #28: add uptask for checking not found todos --- src/services/to-dos.service.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/to-dos.service.cpp b/src/services/to-dos.service.cpp index 7649f59..58ffb8e 100644 --- a/src/services/to-dos.service.cpp +++ b/src/services/to-dos.service.cpp @@ -28,6 +28,7 @@ void ToDoService::addToDo(const string name) (void) _commands.create_todo(name, now_utc); } +// TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/38): add here a check for not found todos bool ToDoService::completeToDo(int id) { (void) _commands.soft_delete_todo(id); @@ -35,6 +36,7 @@ bool ToDoService::completeToDo(int id) } const vector ToDoService::getToDos() const +// TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/38): add here a check for not found todos { auto todos = _queries.get_all_todos(); return mapToDTOs(todos); From 6f7cd981a33b4e65afcb226c0050e73cfbf3836f Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 25 Sep 2025 12:10:56 +0500 Subject: [PATCH 7/8] ref(service): #28: add delete method and revrite getToDos method --- src/data/commands/todo-commands.h | 5 +++-- src/services/to-dos.service.cpp | 9 +++++---- src/services/to-dos.service.h | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/data/commands/todo-commands.h b/src/data/commands/todo-commands.h index a721250..1953476 100644 --- a/src/data/commands/todo-commands.h +++ b/src/data/commands/todo-commands.h @@ -7,8 +7,9 @@ class ToDoCommands { public: - ToDoCommands -(odb::database& db) : db_(db) {} + ToDoCommands(odb::database& db) + : db_(db) + {} uint64_t create_todo(const std::string& name, std::time_t createdAtUtc); uint64_t delete_todo(int id); diff --git a/src/services/to-dos.service.cpp b/src/services/to-dos.service.cpp index 58ffb8e..fb292ec 100644 --- a/src/services/to-dos.service.cpp +++ b/src/services/to-dos.service.cpp @@ -35,17 +35,18 @@ bool ToDoService::completeToDo(int id) return true; } -const vector ToDoService::getToDos() const // TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/38): add here a check for not found todos +bool ToDoService::deleteToDo(int id) { - auto todos = _queries.get_all_todos(); - return mapToDTOs(todos); + (void) _commands.delete_todo(id); + return true; } -Json::Value ToDoService::toJson() const +const Json::Value ToDoService::getToDos() const { Json::Value json; Json::Value toDosArray(Json::arrayValue); + auto todos = _queries.get_all_todos(); auto dtos = mapToDTOs(todos); for (const auto& dto : dtos) diff --git a/src/services/to-dos.service.h b/src/services/to-dos.service.h index bfba4b9..1f836e0 100644 --- a/src/services/to-dos.service.h +++ b/src/services/to-dos.service.h @@ -22,6 +22,6 @@ class ToDoService void addToDo(const std::string description); bool completeToDo(int id); - const std::vector getToDos() const; - Json::Value toJson() const; + bool deleteToDo(int id); + const Json::Value getToDos() const; }; \ No newline at end of file From 0e4630af2bc1465b916d0e8f60738d1dca8c04aa Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 25 Sep 2025 12:11:29 +0500 Subject: [PATCH 8/8] ref(main): #28: delete unusual code --- src/main.cpp | 52 ---------------------------------------------------- 1 file changed, 52 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6f08590..8bdedd1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,58 +10,6 @@ int main() { - try - { - // TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/27): add migration update on startup - - const std::string user = std::getenv("POSTGRES_USER"); - const std::string password = std::getenv("POSTGRES_PASSWORD"); - const std::string db_name = std::getenv("POSTGRES_DB"); - const std::string host = std::getenv("POSTGRES_HOST"); - const std::string port = std::getenv("POSTGRES_PORT"); - - std::string conninfo = "host=" + host + " port=" + port + " dbname=" + db_name + " user=" + user + " password=" + password; - - std::unique_ptr db(new odb::pgsql::database(conninfo)); - - ToDoQueries todo_queries(*db); - ToDoCommands todo_commands(*db); - const std::time_t now_utc = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - uint64_t added_todo = todo_commands.create_todo("random_todo", now_utc); - std::cout << "Added TODO:" << added_todo << std::endl; - uint64_t soft_removed_todo = todo_commands.soft_delete_todo(added_todo); - std::cout << "Soft Removed TODO:" << soft_removed_todo << std::endl; - uint64_t removed_todo = todo_commands.delete_todo(added_todo); - std::cout << "Removed TODO:" << removed_todo << std::endl; - - std::shared_ptr> todos = todo_queries.get_all_todos(); - std::shared_ptr todo = todo_queries.get_todo_by_id(1); - - - if (todos && todo) - { - std::cout << "Found TODOs:" << todos->size() << std::endl; - const std::time_t t = todo->createdAtUtc(); - std::tm tm {}; - gmtime_r(&t, &tm); - std::cout << "Found TODO id:" << todo->id() << " createdAtUtc: " << std::put_time(&tm, "%Y-%m-%d %H:%M:%S UTC") << std::endl; - } - else - { - std::cout << "TODO or TODOs not founded" << std::endl; - } - } - catch (const odb::exception& e) - { - std::cerr << "Error ODB: " << e.what() << std::endl; - return 1; - } - catch (const std::exception& e) - { - std::cerr << "Error: " << e.what() << std::endl; - return 1; - } - drogon::app().addListener("127.0.0.1", 8080); drogon::app().run();