From e2fdd50deea6be4beb6a404ad03c0c2d4770bf7d Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 26 Mar 2026 18:54:22 +0500 Subject: [PATCH 01/13] chore(architecture): #55: create core level of architecture --- src/core/CMakeLists.txt | 6 + src/{data/models => core}/to-do.h | 0 src/data/models/odb-gen/to-do-odb.cxx | 791 -------------------------- src/data/models/odb-gen/to-do-odb.hxx | 335 ----------- src/data/models/odb-gen/to-do-odb.ixx | 63 -- src/data/models/to_do.py | 13 - 6 files changed, 6 insertions(+), 1202 deletions(-) create mode 100644 src/core/CMakeLists.txt rename src/{data/models => core}/to-do.h (100%) delete mode 100644 src/data/models/odb-gen/to-do-odb.cxx delete mode 100644 src/data/models/odb-gen/to-do-odb.hxx delete mode 100644 src/data/models/odb-gen/to-do-odb.ixx delete mode 100644 src/data/models/to_do.py diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt new file mode 100644 index 0000000..f016b90 --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(core INTERFACE) +target_include_directories(core INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(core + INTERFACE libodb::libodb +) \ No newline at end of file diff --git a/src/data/models/to-do.h b/src/core/to-do.h similarity index 100% rename from src/data/models/to-do.h rename to src/core/to-do.h diff --git a/src/data/models/odb-gen/to-do-odb.cxx b/src/data/models/odb-gen/to-do-odb.cxx deleted file mode 100644 index 9022752..0000000 --- a/src/data/models/odb-gen/to-do-odb.cxx +++ /dev/null @@ -1,791 +0,0 @@ -// -*- C++ -*- -// -// This file was generated by ODB, object-relational mapping (ORM) -// compiler for C++. -// - -#include - -#include "to-do-odb.hxx" - -#include -#include // std::memcpy - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace odb -{ - // ToDo - // - - const char access::object_traits_impl< ::ToDo, id_pgsql >:: - persist_statement_name[] = "persist_ToDo"; - - const char access::object_traits_impl< ::ToDo, id_pgsql >:: - find_statement_name[] = "find_ToDo"; - - const char access::object_traits_impl< ::ToDo, id_pgsql >:: - update_statement_name[] = "update_ToDo"; - - const char access::object_traits_impl< ::ToDo, id_pgsql >:: - erase_statement_name[] = "erase_ToDo"; - - const char access::object_traits_impl< ::ToDo, id_pgsql >:: - query_statement_name[] = "query_ToDo"; - - const char access::object_traits_impl< ::ToDo, id_pgsql >:: - erase_query_statement_name[] = "erase_query_ToDo"; - - const unsigned int access::object_traits_impl< ::ToDo, id_pgsql >:: - persist_statement_types[] = - { - pgsql::text_oid, - pgsql::int8_oid, - pgsql::int8_oid - }; - - const unsigned int access::object_traits_impl< ::ToDo, id_pgsql >:: - find_statement_types[] = - { - pgsql::int8_oid - }; - - const unsigned int access::object_traits_impl< ::ToDo, id_pgsql >:: - update_statement_types[] = - { - pgsql::text_oid, - pgsql::int8_oid, - pgsql::int8_oid, - pgsql::int8_oid - }; - - struct access::object_traits_impl< ::ToDo, id_pgsql >::extra_statement_cache_type - { - extra_statement_cache_type ( - pgsql::connection&, - image_type&, - id_image_type&, - pgsql::binding&, - pgsql::binding&, - pgsql::native_binding&, - const unsigned int*) - { - } - }; - - access::object_traits_impl< ::ToDo, id_pgsql >::id_type - access::object_traits_impl< ::ToDo, id_pgsql >:: - id (const id_image_type& i) - { - pgsql::database* db (0); - ODB_POTENTIALLY_UNUSED (db); - - id_type id; - { - pgsql::value_traits< - ::uint64_t, - pgsql::id_bigint >::set_value ( - id, - i.id_value, - i.id_null); - } - - return id; - } - - access::object_traits_impl< ::ToDo, id_pgsql >::id_type - access::object_traits_impl< ::ToDo, id_pgsql >:: - id (const image_type& i) - { - pgsql::database* db (0); - ODB_POTENTIALLY_UNUSED (db); - - id_type id; - { - pgsql::value_traits< - ::uint64_t, - pgsql::id_bigint >::set_value ( - id, - i.id_value, - i.id_null); - } - - return id; - } - - bool access::object_traits_impl< ::ToDo, id_pgsql >:: - grow (image_type& i, - bool* t) - { - ODB_POTENTIALLY_UNUSED (i); - ODB_POTENTIALLY_UNUSED (t); - - bool grew (false); - - // id_ - // - t[0UL] = 0; - - // name_ - // - if (t[1UL]) - { - i.name_value.capacity (i.name_size); - grew = true; - } - - // createdAtUtc_ - // - t[2UL] = 0; - - // deletedAtUtc_ - // - t[3UL] = 0; - - return grew; - } - - void access::object_traits_impl< ::ToDo, id_pgsql >:: - bind (pgsql::bind* b, - image_type& i, - pgsql::statement_kind sk) - { - ODB_POTENTIALLY_UNUSED (sk); - - using namespace pgsql; - - std::size_t n (0); - - // id_ - // - if (sk != statement_insert && sk != statement_update) - { - b[n].type = pgsql::bind::bigint; - b[n].buffer = &i.id_value; - b[n].is_null = &i.id_null; - n++; - } - - // name_ - // - b[n].type = pgsql::bind::text; - b[n].buffer = i.name_value.data_ptr (); - b[n].capacity = i.name_value.capacity (); - b[n].size = &i.name_size; - b[n].is_null = &i.name_null; - n++; - - // createdAtUtc_ - // - b[n].type = pgsql::bind::bigint; - b[n].buffer = &i.createdAtUtc_value; - b[n].is_null = &i.createdAtUtc_null; - n++; - - // deletedAtUtc_ - // - b[n].type = pgsql::bind::bigint; - b[n].buffer = &i.deletedAtUtc_value; - b[n].is_null = &i.deletedAtUtc_null; - n++; - } - - void access::object_traits_impl< ::ToDo, id_pgsql >:: - bind (pgsql::bind* b, id_image_type& i) - { - std::size_t n (0); - b[n].type = pgsql::bind::bigint; - b[n].buffer = &i.id_value; - b[n].is_null = &i.id_null; - } - - bool access::object_traits_impl< ::ToDo, id_pgsql >:: - init (image_type& i, - const object_type& o, - pgsql::statement_kind sk) - { - ODB_POTENTIALLY_UNUSED (i); - ODB_POTENTIALLY_UNUSED (o); - ODB_POTENTIALLY_UNUSED (sk); - - using namespace pgsql; - - bool grew (false); - - // name_ - // - { - ::std::string const& v = - o.name_; - - bool is_null (false); - std::size_t size (0); - std::size_t cap (i.name_value.capacity ()); - pgsql::value_traits< - ::std::string, - pgsql::id_string >::set_image ( - i.name_value, - size, - is_null, - v); - i.name_null = is_null; - i.name_size = size; - grew = grew || (cap != i.name_value.capacity ()); - } - - // createdAtUtc_ - // - { - ::time_t const& v = - o.createdAtUtc_; - - bool is_null (false); - pgsql::value_traits< - ::time_t, - pgsql::id_bigint >::set_image ( - i.createdAtUtc_value, is_null, v); - i.createdAtUtc_null = is_null; - } - - // deletedAtUtc_ - // - { - ::odb::nullable< long int > const& v = - o.deletedAtUtc_; - - bool is_null (true); - pgsql::value_traits< - ::odb::nullable< long int >, - pgsql::id_bigint >::set_image ( - i.deletedAtUtc_value, is_null, v); - i.deletedAtUtc_null = is_null; - } - - return grew; - } - - void access::object_traits_impl< ::ToDo, id_pgsql >:: - init (object_type& o, - const image_type& i, - database* db) - { - ODB_POTENTIALLY_UNUSED (o); - ODB_POTENTIALLY_UNUSED (i); - ODB_POTENTIALLY_UNUSED (db); - - // id_ - // - { - ::uint64_t& v = - o.id_; - - pgsql::value_traits< - ::uint64_t, - pgsql::id_bigint >::set_value ( - v, - i.id_value, - i.id_null); - } - - // name_ - // - { - ::std::string& v = - o.name_; - - pgsql::value_traits< - ::std::string, - pgsql::id_string >::set_value ( - v, - i.name_value, - i.name_size, - i.name_null); - } - - // createdAtUtc_ - // - { - ::time_t& v = - o.createdAtUtc_; - - pgsql::value_traits< - ::time_t, - pgsql::id_bigint >::set_value ( - v, - i.createdAtUtc_value, - i.createdAtUtc_null); - } - - // deletedAtUtc_ - // - { - ::odb::nullable< long int >& v = - o.deletedAtUtc_; - - pgsql::value_traits< - ::odb::nullable< long int >, - pgsql::id_bigint >::set_value ( - v, - i.deletedAtUtc_value, - i.deletedAtUtc_null); - } - } - - void access::object_traits_impl< ::ToDo, id_pgsql >:: - init (id_image_type& i, const id_type& id) - { - { - bool is_null (false); - pgsql::value_traits< - ::uint64_t, - pgsql::id_bigint >::set_image ( - i.id_value, is_null, id); - i.id_null = is_null; - } - } - - const char access::object_traits_impl< ::ToDo, id_pgsql >::persist_statement[] = - "INSERT INTO \"todo\" " - "(\"id\", " - "\"name\", " - "\"createdAtUtc\", " - "\"deletedAtUtc\") " - "VALUES " - "(DEFAULT, $1, $2, $3) " - "RETURNING \"id\""; - - const char access::object_traits_impl< ::ToDo, id_pgsql >::find_statement[] = - "SELECT " - "\"todo\".\"id\", " - "\"todo\".\"name\", " - "\"todo\".\"createdAtUtc\", " - "\"todo\".\"deletedAtUtc\" " - "FROM \"todo\" " - "WHERE \"todo\".\"id\"=$1"; - - const char access::object_traits_impl< ::ToDo, id_pgsql >::update_statement[] = - "UPDATE \"todo\" " - "SET " - "\"name\"=$1, " - "\"createdAtUtc\"=$2, " - "\"deletedAtUtc\"=$3 " - "WHERE \"id\"=$4"; - - const char access::object_traits_impl< ::ToDo, id_pgsql >::erase_statement[] = - "DELETE FROM \"todo\" " - "WHERE \"id\"=$1"; - - const char access::object_traits_impl< ::ToDo, id_pgsql >::query_statement[] = - "SELECT " - "\"todo\".\"id\", " - "\"todo\".\"name\", " - "\"todo\".\"createdAtUtc\", " - "\"todo\".\"deletedAtUtc\" " - "FROM \"todo\""; - - const char access::object_traits_impl< ::ToDo, id_pgsql >::erase_query_statement[] = - "DELETE FROM \"todo\""; - - const char access::object_traits_impl< ::ToDo, id_pgsql >::table_name[] = - "\"todo\""; - - void access::object_traits_impl< ::ToDo, id_pgsql >:: - persist (database& db, object_type& obj) - { - using namespace pgsql; - - pgsql::connection& conn ( - pgsql::transaction::current ().connection (db)); - statements_type& sts ( - conn.statement_cache ().find_object ()); - - callback (db, - static_cast (obj), - callback_event::pre_persist); - - image_type& im (sts.image ()); - binding& imb (sts.insert_image_binding ()); - - if (init (im, obj, statement_insert)) - im.version++; - - if (im.version != sts.insert_image_version () || - imb.version == 0) - { - bind (imb.bind, im, statement_insert); - sts.insert_image_version (im.version); - imb.version++; - } - - { - id_image_type& i (sts.id_image ()); - binding& b (sts.id_image_binding ()); - if (i.version != sts.id_image_version () || b.version == 0) - { - bind (b.bind, i); - sts.id_image_version (i.version); - b.version++; - } - } - - insert_statement& st (sts.persist_statement ()); - if (!st.execute ()) - throw object_already_persistent (); - - obj.id_ = id (sts.id_image ()); - - callback (db, - static_cast (obj), - callback_event::post_persist); - } - - void access::object_traits_impl< ::ToDo, id_pgsql >:: - update (database& db, const object_type& obj) - { - ODB_POTENTIALLY_UNUSED (db); - - using namespace pgsql; - using pgsql::update_statement; - - callback (db, obj, callback_event::pre_update); - - pgsql::transaction& tr (pgsql::transaction::current ()); - pgsql::connection& conn (tr.connection (db)); - statements_type& sts ( - conn.statement_cache ().find_object ()); - - id_image_type& idi (sts.id_image ()); - init (idi, id (obj)); - - image_type& im (sts.image ()); - if (init (im, obj, statement_update)) - im.version++; - - bool u (false); - binding& imb (sts.update_image_binding ()); - if (im.version != sts.update_image_version () || - imb.version == 0) - { - bind (imb.bind, im, statement_update); - sts.update_image_version (im.version); - imb.version++; - u = true; - } - - binding& idb (sts.id_image_binding ()); - if (idi.version != sts.update_id_image_version () || - idb.version == 0) - { - if (idi.version != sts.id_image_version () || - idb.version == 0) - { - bind (idb.bind, idi); - sts.id_image_version (idi.version); - idb.version++; - } - - sts.update_id_image_version (idi.version); - - if (!u) - imb.version++; - } - - update_statement& st (sts.update_statement ()); - if (st.execute () == 0) - throw object_not_persistent (); - - callback (db, obj, callback_event::post_update); - pointer_cache_traits::update (db, obj); - } - - void access::object_traits_impl< ::ToDo, id_pgsql >:: - erase (database& db, const id_type& id) - { - using namespace pgsql; - - pgsql::connection& conn ( - pgsql::transaction::current ().connection (db)); - statements_type& sts ( - conn.statement_cache ().find_object ()); - - id_image_type& i (sts.id_image ()); - init (i, id); - - binding& idb (sts.id_image_binding ()); - if (i.version != sts.id_image_version () || idb.version == 0) - { - bind (idb.bind, i); - sts.id_image_version (i.version); - idb.version++; - } - - if (sts.erase_statement ().execute () != 1) - throw object_not_persistent (); - - pointer_cache_traits::erase (db, id); - } - - access::object_traits_impl< ::ToDo, id_pgsql >::pointer_type - access::object_traits_impl< ::ToDo, id_pgsql >:: - find (database& db, const id_type& id) - { - using namespace pgsql; - - { - pointer_type p (pointer_cache_traits::find (db, id)); - - if (!pointer_traits::null_ptr (p)) - return p; - } - - pgsql::connection& conn ( - pgsql::transaction::current ().connection (db)); - statements_type& sts ( - conn.statement_cache ().find_object ()); - - statements_type::auto_lock l (sts); - - if (l.locked ()) - { - if (!find_ (sts, &id)) - return pointer_type (); - } - - pointer_type p ( - access::object_factory::create ()); - pointer_traits::guard pg (p); - - pointer_cache_traits::insert_guard ig ( - pointer_cache_traits::insert (db, id, p)); - - object_type& obj (pointer_traits::get_ref (p)); - - if (l.locked ()) - { - select_statement& st (sts.find_statement ()); - ODB_POTENTIALLY_UNUSED (st); - - callback (db, obj, callback_event::pre_load); - init (obj, sts.image (), &db); - load_ (sts, obj, false); - sts.load_delayed (0); - l.unlock (); - callback (db, obj, callback_event::post_load); - pointer_cache_traits::load (ig.position ()); - } - else - sts.delay_load (id, obj, ig.position ()); - - ig.release (); - pg.release (); - return p; - } - - bool access::object_traits_impl< ::ToDo, id_pgsql >:: - find (database& db, const id_type& id, object_type& obj) - { - using namespace pgsql; - - pgsql::connection& conn ( - pgsql::transaction::current ().connection (db)); - statements_type& sts ( - conn.statement_cache ().find_object ()); - - statements_type::auto_lock l (sts); - assert (l.locked ()) /* Must be a top-level call. */; - - if (!find_ (sts, &id)) - return false; - - select_statement& st (sts.find_statement ()); - ODB_POTENTIALLY_UNUSED (st); - - reference_cache_traits::position_type pos ( - reference_cache_traits::insert (db, id, obj)); - reference_cache_traits::insert_guard ig (pos); - - callback (db, obj, callback_event::pre_load); - init (obj, sts.image (), &db); - load_ (sts, obj, false); - sts.load_delayed (0); - l.unlock (); - callback (db, obj, callback_event::post_load); - reference_cache_traits::load (pos); - ig.release (); - return true; - } - - bool access::object_traits_impl< ::ToDo, id_pgsql >:: - reload (database& db, object_type& obj) - { - using namespace pgsql; - - pgsql::connection& conn ( - pgsql::transaction::current ().connection (db)); - statements_type& sts ( - conn.statement_cache ().find_object ()); - - statements_type::auto_lock l (sts); - assert (l.locked ()) /* Must be a top-level call. */; - - const id_type& id (object_traits_impl::id (obj)); - if (!find_ (sts, &id)) - return false; - - select_statement& st (sts.find_statement ()); - ODB_POTENTIALLY_UNUSED (st); - - callback (db, obj, callback_event::pre_load); - init (obj, sts.image (), &db); - load_ (sts, obj, true); - sts.load_delayed (0); - l.unlock (); - callback (db, obj, callback_event::post_load); - return true; - } - - bool access::object_traits_impl< ::ToDo, id_pgsql >:: - find_ (statements_type& sts, - const id_type* id) - { - using namespace pgsql; - - id_image_type& i (sts.id_image ()); - init (i, *id); - - binding& idb (sts.id_image_binding ()); - if (i.version != sts.id_image_version () || idb.version == 0) - { - bind (idb.bind, i); - sts.id_image_version (i.version); - idb.version++; - } - - image_type& im (sts.image ()); - binding& imb (sts.select_image_binding ()); - - if (im.version != sts.select_image_version () || - imb.version == 0) - { - bind (imb.bind, im, statement_select); - sts.select_image_version (im.version); - imb.version++; - } - - select_statement& st (sts.find_statement ()); - - st.execute (); - auto_result ar (st); - select_statement::result r (st.fetch ()); - - if (r == select_statement::truncated) - { - if (grow (im, sts.select_image_truncated ())) - im.version++; - - if (im.version != sts.select_image_version ()) - { - bind (imb.bind, im, statement_select); - sts.select_image_version (im.version); - imb.version++; - st.refetch (); - } - } - - return r != select_statement::no_data; - } - - result< access::object_traits_impl< ::ToDo, id_pgsql >::object_type > - access::object_traits_impl< ::ToDo, id_pgsql >:: - query (database& db, const query_base_type& q) - { - using namespace pgsql; - using odb::details::shared; - using odb::details::shared_ptr; - - pgsql::connection& conn ( - pgsql::transaction::current ().connection (db)); - - statements_type& sts ( - conn.statement_cache ().find_object ()); - - image_type& im (sts.image ()); - binding& imb (sts.select_image_binding ()); - - if (im.version != sts.select_image_version () || - imb.version == 0) - { - bind (imb.bind, im, statement_select); - sts.select_image_version (im.version); - imb.version++; - } - - std::string text (query_statement); - if (!q.empty ()) - { - text += " "; - text += q.clause (); - } - - q.init_parameters (); - shared_ptr st ( - new (shared) select_statement ( - sts.connection (), - query_statement_name, - text, - false, - true, - q.parameter_types (), - q.parameter_count (), - q.parameters_binding (), - imb)); - - st->execute (); - st->deallocate (); - - shared_ptr< odb::object_result_impl > r ( - new (shared) pgsql::object_result_impl ( - q, st, sts, 0)); - - return result (r); - } - - unsigned long long access::object_traits_impl< ::ToDo, id_pgsql >:: - erase_query (database& db, const query_base_type& q) - { - using namespace pgsql; - - pgsql::connection& conn ( - pgsql::transaction::current ().connection (db)); - - std::string text (erase_query_statement); - if (!q.empty ()) - { - text += ' '; - text += q.clause (); - } - - q.init_parameters (); - delete_statement st ( - conn, - erase_query_statement_name, - text, - q.parameter_types (), - q.parameter_count (), - q.parameters_binding ()); - - return st.execute (); - } -} - -#include diff --git a/src/data/models/odb-gen/to-do-odb.hxx b/src/data/models/odb-gen/to-do-odb.hxx deleted file mode 100644 index 6af34ad..0000000 --- a/src/data/models/odb-gen/to-do-odb.hxx +++ /dev/null @@ -1,335 +0,0 @@ -// -*- C++ -*- -// -// This file was generated by ODB, object-relational mapping (ORM) -// compiler for C++. -// - -#ifndef TO_DO_ODB_HXX -#define TO_DO_ODB_HXX - -#include - -#if ODB_VERSION != 20500UL -#error ODB runtime version mismatch -#endif - -#include - -#include "to-do.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace odb -{ - // ToDo - // - template <> - struct class_traits< ::ToDo > - { - static const class_kind kind = class_object; - }; - - template <> - class access::object_traits< ::ToDo > - { - public: - typedef ::ToDo object_type; - typedef ::ToDo* pointer_type; - typedef odb::pointer_traits pointer_traits; - - static const bool polymorphic = false; - - typedef ::uint64_t id_type; - - static const bool auto_id = true; - - static const bool abstract = false; - - static id_type - id (const object_type&); - - typedef - no_op_pointer_cache_traits - pointer_cache_traits; - - typedef - no_op_reference_cache_traits - reference_cache_traits; - - static void - callback (database&, object_type&, callback_event); - - static void - callback (database&, const object_type&, callback_event); - }; -} - -#include - -#include -#include -#include -#include -#include - -namespace odb -{ - // ToDo - // - template - struct query_columns< ::ToDo, id_pgsql, A > - { - // id - // - typedef - pgsql::query_column< - pgsql::value_traits< - ::uint64_t, - pgsql::id_bigint >::query_type, - pgsql::id_bigint > - id_type_; - - static const id_type_ id; - - // name - // - typedef - pgsql::query_column< - pgsql::value_traits< - ::std::string, - pgsql::id_string >::query_type, - pgsql::id_string > - name_type_; - - static const name_type_ name; - - // createdAtUtc - // - typedef - pgsql::query_column< - pgsql::value_traits< - ::time_t, - pgsql::id_bigint >::query_type, - pgsql::id_bigint > - createdAtUtc_type_; - - static const createdAtUtc_type_ createdAtUtc; - - // deletedAtUtc - // - typedef - pgsql::query_column< - pgsql::value_traits< - long int, - pgsql::id_bigint >::query_type, - pgsql::id_bigint > - deletedAtUtc_type_; - - static const deletedAtUtc_type_ deletedAtUtc; - }; - - template - const typename query_columns< ::ToDo, id_pgsql, A >::id_type_ - query_columns< ::ToDo, id_pgsql, A >:: - id (A::table_name, "\"id\"", 0); - - template - const typename query_columns< ::ToDo, id_pgsql, A >::name_type_ - query_columns< ::ToDo, id_pgsql, A >:: - name (A::table_name, "\"name\"", 0); - - template - const typename query_columns< ::ToDo, id_pgsql, A >::createdAtUtc_type_ - query_columns< ::ToDo, id_pgsql, A >:: - createdAtUtc (A::table_name, "\"createdAtUtc\"", 0); - - template - const typename query_columns< ::ToDo, id_pgsql, A >::deletedAtUtc_type_ - query_columns< ::ToDo, id_pgsql, A >:: - deletedAtUtc (A::table_name, "\"deletedAtUtc\"", 0); - - template - struct pointer_query_columns< ::ToDo, id_pgsql, A >: - query_columns< ::ToDo, id_pgsql, A > - { - }; - - template <> - class access::object_traits_impl< ::ToDo, id_pgsql >: - public access::object_traits< ::ToDo > - { - public: - struct id_image_type - { - long long id_value; - bool id_null; - - std::size_t version; - }; - - struct image_type - { - // id_ - // - long long id_value; - bool id_null; - - // name_ - // - details::buffer name_value; - std::size_t name_size; - bool name_null; - - // createdAtUtc_ - // - long long createdAtUtc_value; - bool createdAtUtc_null; - - // deletedAtUtc_ - // - long long deletedAtUtc_value; - bool deletedAtUtc_null; - - std::size_t version; - }; - - struct extra_statement_cache_type; - - using object_traits::id; - - static id_type - id (const id_image_type&); - - static id_type - id (const image_type&); - - static bool - grow (image_type&, - bool*); - - static void - bind (pgsql::bind*, - image_type&, - pgsql::statement_kind); - - static void - bind (pgsql::bind*, id_image_type&); - - static bool - init (image_type&, - const object_type&, - pgsql::statement_kind); - - static void - init (object_type&, - const image_type&, - database*); - - static void - init (id_image_type&, const id_type&); - - typedef pgsql::object_statements statements_type; - - typedef pgsql::query_base query_base_type; - - static const std::size_t column_count = 4UL; - static const std::size_t id_column_count = 1UL; - static const std::size_t inverse_column_count = 0UL; - static const std::size_t readonly_column_count = 0UL; - static const std::size_t managed_optimistic_column_count = 0UL; - - static const std::size_t separate_load_column_count = 0UL; - static const std::size_t separate_update_column_count = 0UL; - - static const bool versioned = false; - - static const char persist_statement[]; - static const char find_statement[]; - static const char update_statement[]; - static const char erase_statement[]; - static const char query_statement[]; - static const char erase_query_statement[]; - - static const char table_name[]; - - static void - persist (database&, object_type&); - - static pointer_type - find (database&, const id_type&); - - static bool - find (database&, const id_type&, object_type&); - - static bool - reload (database&, object_type&); - - static void - update (database&, const object_type&); - - static void - erase (database&, const id_type&); - - static void - erase (database&, const object_type&); - - static result - query (database&, const query_base_type&); - - static unsigned long long - erase_query (database&, const query_base_type&); - - static const char persist_statement_name[]; - static const char find_statement_name[]; - static const char update_statement_name[]; - static const char erase_statement_name[]; - static const char query_statement_name[]; - static const char erase_query_statement_name[]; - - static const unsigned int persist_statement_types[]; - static const unsigned int find_statement_types[]; - static const unsigned int update_statement_types[]; - - static const std::size_t batch = 1UL; - - public: - static bool - find_ (statements_type&, - const id_type*); - - static void - load_ (statements_type&, - object_type&, - bool reload); - }; - - template <> - class access::object_traits_impl< ::ToDo, id_common >: - public access::object_traits_impl< ::ToDo, id_pgsql > - { - }; - - // ToDo - // -} - -#include "to-do-odb.ixx" - -#include - -#endif // TO_DO_ODB_HXX diff --git a/src/data/models/odb-gen/to-do-odb.ixx b/src/data/models/odb-gen/to-do-odb.ixx deleted file mode 100644 index f19cf5c..0000000 --- a/src/data/models/odb-gen/to-do-odb.ixx +++ /dev/null @@ -1,63 +0,0 @@ -// -*- C++ -*- -// -// This file was generated by ODB, object-relational mapping (ORM) -// compiler for C++. -// - -namespace odb -{ - // ToDo - // - - inline - access::object_traits< ::ToDo >::id_type - access::object_traits< ::ToDo >:: - id (const object_type& o) - { - return o.id_; - } - - inline - void access::object_traits< ::ToDo >:: - callback (database& db, object_type& x, callback_event e) - { - ODB_POTENTIALLY_UNUSED (db); - ODB_POTENTIALLY_UNUSED (x); - ODB_POTENTIALLY_UNUSED (e); - } - - inline - void access::object_traits< ::ToDo >:: - callback (database& db, const object_type& x, callback_event e) - { - ODB_POTENTIALLY_UNUSED (db); - ODB_POTENTIALLY_UNUSED (x); - ODB_POTENTIALLY_UNUSED (e); - } -} - -namespace odb -{ - // ToDo - // - - inline - void access::object_traits_impl< ::ToDo, id_pgsql >:: - erase (database& db, const object_type& obj) - { - callback (db, obj, callback_event::pre_erase); - erase (db, id (obj)); - callback (db, obj, callback_event::post_erase); - } - - inline - void access::object_traits_impl< ::ToDo, id_pgsql >:: - load_ (statements_type& sts, - object_type& obj, - bool) - { - ODB_POTENTIALLY_UNUSED (sts); - ODB_POTENTIALLY_UNUSED (obj); - } -} - diff --git a/src/data/models/to_do.py b/src/data/models/to_do.py deleted file mode 100644 index de1e416..0000000 --- a/src/data/models/to_do.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import create_engine, Column, BigInteger, Integer, String, ForeignKey, DateTime -from sqlalchemy.orm import declarative_base - -Base = declarative_base() - -class ToDo(Base): - __tablename__ = "todo" - - id = Column(BigInteger, primary_key=True) - name = Column(String(255), nullable=False) - createdAtUtc = Column(BigInteger, nullable=False) - deletedAtUtc = Column(BigInteger, nullable=True) - From ce5c2c5ad8cd1a9bc5ed12f5a34c65a14a528fc0 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 26 Mar 2026 18:54:58 +0500 Subject: [PATCH 02/13] chore(architecture): #55: move out alembic functional --- alembic/alembic.ini | 141 ++++++++++++++++++ {src/data => alembic}/migrations/README | 0 {src/data => alembic}/migrations/env.py | 0 .../migrations/script.py.mako | 0 .../4305af8cc1e9_initial_migration.py | 0 alembic/models/to_do.py | 13 ++ 6 files changed, 154 insertions(+) create mode 100644 alembic/alembic.ini rename {src/data => alembic}/migrations/README (100%) rename {src/data => alembic}/migrations/env.py (100%) rename {src/data => alembic}/migrations/script.py.mako (100%) rename {src/data => alembic}/migrations/versions/4305af8cc1e9_initial_migration.py (100%) create mode 100644 alembic/models/to_do.py diff --git a/alembic/alembic.ini b/alembic/alembic.ini new file mode 100644 index 0000000..fe66372 --- /dev/null +++ b/alembic/alembic.ini @@ -0,0 +1,141 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = %(here)s/migrations + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = . + + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/src/data/migrations/README b/alembic/migrations/README similarity index 100% rename from src/data/migrations/README rename to alembic/migrations/README diff --git a/src/data/migrations/env.py b/alembic/migrations/env.py similarity index 100% rename from src/data/migrations/env.py rename to alembic/migrations/env.py diff --git a/src/data/migrations/script.py.mako b/alembic/migrations/script.py.mako similarity index 100% rename from src/data/migrations/script.py.mako rename to alembic/migrations/script.py.mako diff --git a/src/data/migrations/versions/4305af8cc1e9_initial_migration.py b/alembic/migrations/versions/4305af8cc1e9_initial_migration.py similarity index 100% rename from src/data/migrations/versions/4305af8cc1e9_initial_migration.py rename to alembic/migrations/versions/4305af8cc1e9_initial_migration.py diff --git a/alembic/models/to_do.py b/alembic/models/to_do.py new file mode 100644 index 0000000..de1e416 --- /dev/null +++ b/alembic/models/to_do.py @@ -0,0 +1,13 @@ +from sqlalchemy import create_engine, Column, BigInteger, Integer, String, ForeignKey, DateTime +from sqlalchemy.orm import declarative_base + +Base = declarative_base() + +class ToDo(Base): + __tablename__ = "todo" + + id = Column(BigInteger, primary_key=True) + name = Column(String(255), nullable=False) + createdAtUtc = Column(BigInteger, nullable=False) + deletedAtUtc = Column(BigInteger, nullable=True) + From 05c022519b7b77b950466737264af0866643bb37 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 26 Mar 2026 18:55:27 +0500 Subject: [PATCH 03/13] chore(architecture): #55: create application level of architecture --- src/application/CMakeLists.txt | 18 + src/{data => application}/db_connection.cpp | 0 src/{data => application}/db_connection.h | 0 .../create-to-do/create-to-do-command.cpp | 14 + .../create-to-do/create-to-do-command.h | 18 + .../create-to-do/create-to-do-handler.cpp | 15 + .../create-to-do/create-to-do-handler.h | 15 + .../create-to-do/create-to-do-request.h | 6 + .../create-to-do/create-to-do-response.h | 6 + .../get-all-to-dos/get-all-to-dos-handler.cpp | 26 + .../get-all-to-dos/get-all-to-dos-handler.h | 14 + .../get-all-to-dos/get-all-to-dos-query.cpp | 17 + .../get-all-to-dos/get-all-to-dos-query.h | 18 + .../get-all-to-dos/get-all-to-dos-response.h | 7 + .../get-to-do-by-id-handler.cpp | 19 + .../get-to-do-by-id/get-to-do-by-id-handler.h | 15 + .../get-to-do-by-id/get-to-do-by-id-query.cpp | 18 + .../get-to-do-by-id/get-to-do-by-id-query.h | 18 + .../get-to-do-by-id-response.h | 7 + .../hard-delete-to-do-command.cpp | 13 + .../hard-delete-to-do-command.h | 18 + .../hard-delete-to-do-handler.cpp | 8 + .../hard-delete-to-do-handler.h | 14 + .../soft-delete-to-do-command.cpp | 20 + .../soft-delete-to-do-command.h | 18 + .../soft-delete-to-do-handler.cpp | 8 + .../soft-delete-to-do-handler.h | 15 + .../soft-delete-to-do-response.h | 6 + src/application/odb-gen/to-do-odb.cxx | 791 ++++++++++++++++++ src/application/odb-gen/to-do-odb.hxx | 335 ++++++++ src/application/odb-gen/to-do-odb.ixx | 63 ++ .../shared-dtos/to-do-dtos.cpp} | 5 +- .../shared-dtos/to-do-dtos.h} | 4 +- src/data/alembic.ini | 141 ---- src/data/commands/todo-commands.cpp | 41 - src/data/commands/todo-commands.h | 20 - src/data/queries/todo-queries.cpp | 32 - src/data/queries/todo-queries.h | 19 - src/services/to-dos.service.cpp | 58 -- src/services/to-dos.service.h | 27 - 40 files changed, 1563 insertions(+), 344 deletions(-) create mode 100644 src/application/CMakeLists.txt rename src/{data => application}/db_connection.cpp (100%) rename src/{data => application}/db_connection.h (100%) create mode 100644 src/application/features/create-to-do/create-to-do-command.cpp create mode 100644 src/application/features/create-to-do/create-to-do-command.h create mode 100644 src/application/features/create-to-do/create-to-do-handler.cpp create mode 100644 src/application/features/create-to-do/create-to-do-handler.h create mode 100644 src/application/features/create-to-do/create-to-do-request.h create mode 100644 src/application/features/create-to-do/create-to-do-response.h create mode 100644 src/application/features/get-all-to-dos/get-all-to-dos-handler.cpp create mode 100644 src/application/features/get-all-to-dos/get-all-to-dos-handler.h create mode 100644 src/application/features/get-all-to-dos/get-all-to-dos-query.cpp create mode 100644 src/application/features/get-all-to-dos/get-all-to-dos-query.h create mode 100644 src/application/features/get-all-to-dos/get-all-to-dos-response.h create mode 100644 src/application/features/get-to-do-by-id/get-to-do-by-id-handler.cpp create mode 100644 src/application/features/get-to-do-by-id/get-to-do-by-id-handler.h create mode 100644 src/application/features/get-to-do-by-id/get-to-do-by-id-query.cpp create mode 100644 src/application/features/get-to-do-by-id/get-to-do-by-id-query.h create mode 100644 src/application/features/get-to-do-by-id/get-to-do-by-id-response.h create mode 100644 src/application/features/hard-delete-to-do/hard-delete-to-do-command.cpp create mode 100644 src/application/features/hard-delete-to-do/hard-delete-to-do-command.h create mode 100644 src/application/features/hard-delete-to-do/hard-delete-to-do-handler.cpp create mode 100644 src/application/features/hard-delete-to-do/hard-delete-to-do-handler.h create mode 100644 src/application/features/soft-delete-to-do/soft-delete-to-do-command.cpp create mode 100644 src/application/features/soft-delete-to-do/soft-delete-to-do-command.h create mode 100644 src/application/features/soft-delete-to-do/soft-delete-to-do-handler.cpp create mode 100644 src/application/features/soft-delete-to-do/soft-delete-to-do-handler.h create mode 100644 src/application/features/soft-delete-to-do/soft-delete-to-do-response.h create mode 100644 src/application/odb-gen/to-do-odb.cxx create mode 100644 src/application/odb-gen/to-do-odb.hxx create mode 100644 src/application/odb-gen/to-do-odb.ixx rename src/{services/dtos/to-dos-dto.cpp => application/shared-dtos/to-do-dtos.cpp} (68%) rename src/{services/dtos/to-dos-dto.h => application/shared-dtos/to-do-dtos.h} (87%) delete mode 100644 src/data/alembic.ini delete mode 100644 src/data/commands/todo-commands.cpp delete mode 100644 src/data/commands/todo-commands.h delete mode 100644 src/data/queries/todo-queries.cpp delete mode 100644 src/data/queries/todo-queries.h delete mode 100644 src/services/to-dos.service.cpp delete mode 100644 src/services/to-dos.service.h diff --git a/src/application/CMakeLists.txt b/src/application/CMakeLists.txt new file mode 100644 index 0000000..946ed0f --- /dev/null +++ b/src/application/CMakeLists.txt @@ -0,0 +1,18 @@ +file(GLOB_RECURSE application_sources + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/odb-gen/*.cxx +) + +add_library(application STATIC ${application_sources}) + +target_include_directories(application PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/odb-gen +) + +target_link_libraries(application + PUBLIC core + PRIVATE libodb::libodb + libodb-pgsql::libodb-pgsql + JsonCpp::JsonCpp +) \ No newline at end of file diff --git a/src/data/db_connection.cpp b/src/application/db_connection.cpp similarity index 100% rename from src/data/db_connection.cpp rename to src/application/db_connection.cpp diff --git a/src/data/db_connection.h b/src/application/db_connection.h similarity index 100% rename from src/data/db_connection.h rename to src/application/db_connection.h diff --git a/src/application/features/create-to-do/create-to-do-command.cpp b/src/application/features/create-to-do/create-to-do-command.cpp new file mode 100644 index 0000000..20137c3 --- /dev/null +++ b/src/application/features/create-to-do/create-to-do-command.cpp @@ -0,0 +1,14 @@ +#include "create-to-do-command.h" +#include "odb-gen/to-do-odb.hxx" +#include + +uint64_t CreateToDoCommand::execute(const std::string& name, std::time_t createdAtUtc) +{ + ToDo todo(name, createdAtUtc); + + odb::transaction t(db_.begin()); + db_.persist(todo); + t.commit(); + + return todo.id(); +} \ No newline at end of file diff --git a/src/application/features/create-to-do/create-to-do-command.h b/src/application/features/create-to-do/create-to-do-command.h new file mode 100644 index 0000000..2cbef86 --- /dev/null +++ b/src/application/features/create-to-do/create-to-do-command.h @@ -0,0 +1,18 @@ +#pragma once + +#include "to-do.h" +#include +#include + +class CreateToDoCommand +{ +public: + CreateToDoCommand(odb::database& db) + : db_(db) + {} + + uint64_t execute(const std::string& name, std::time_t createdAtUtc); + +private: + odb::database& db_; +}; \ No newline at end of file diff --git a/src/application/features/create-to-do/create-to-do-handler.cpp b/src/application/features/create-to-do/create-to-do-handler.cpp new file mode 100644 index 0000000..f2c7daf --- /dev/null +++ b/src/application/features/create-to-do/create-to-do-handler.cpp @@ -0,0 +1,15 @@ +#include "create-to-do-handler.h" +#include +#include + +using std::string; + +CreateToDoResponse CreateToDoHandler::handle(const CreateToDoRequest& request) +{ + const std::time_t now_utc = std::chrono::system_clock::to_time_t( + std::chrono::system_clock::now() + ); + + const uint64_t id = _createToDoCommand.execute(request.name, now_utc); + return { id }; +} \ No newline at end of file diff --git a/src/application/features/create-to-do/create-to-do-handler.h b/src/application/features/create-to-do/create-to-do-handler.h new file mode 100644 index 0000000..0755a9b --- /dev/null +++ b/src/application/features/create-to-do/create-to-do-handler.h @@ -0,0 +1,15 @@ +#pragma once +#include "create-to-do-command.h" +#include "create-to-do-request.h" +#include "create-to-do-response.h" + +class CreateToDoHandler { +private: + CreateToDoCommand& _createToDoCommand; +public: + explicit CreateToDoHandler(CreateToDoCommand& createToDoCommand) + : _createToDoCommand(createToDoCommand) + {} + + CreateToDoResponse handle(const CreateToDoRequest& request); +}; \ No newline at end of file diff --git a/src/application/features/create-to-do/create-to-do-request.h b/src/application/features/create-to-do/create-to-do-request.h new file mode 100644 index 0000000..c56bff1 --- /dev/null +++ b/src/application/features/create-to-do/create-to-do-request.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct CreateToDoRequest { + std::string name; +}; \ No newline at end of file diff --git a/src/application/features/create-to-do/create-to-do-response.h b/src/application/features/create-to-do/create-to-do-response.h new file mode 100644 index 0000000..c69c0bf --- /dev/null +++ b/src/application/features/create-to-do/create-to-do-response.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct CreateToDoResponse { + uint64_t id; +}; \ No newline at end of file diff --git a/src/application/features/get-all-to-dos/get-all-to-dos-handler.cpp b/src/application/features/get-all-to-dos/get-all-to-dos-handler.cpp new file mode 100644 index 0000000..61047f4 --- /dev/null +++ b/src/application/features/get-all-to-dos/get-all-to-dos-handler.cpp @@ -0,0 +1,26 @@ +#include "get-all-to-dos-handler.h" + +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.name = t.name(); + out.push_back(std::move(dto)); + } + } + return out; +} + +GetAllToDosResponse GetAllToDosHandler::handle() +{ + auto todos = _getAllToDosQuery.get(); + return { mapToDTOs(todos) }; +} \ No newline at end of file diff --git a/src/application/features/get-all-to-dos/get-all-to-dos-handler.h b/src/application/features/get-all-to-dos/get-all-to-dos-handler.h new file mode 100644 index 0000000..5d35f09 --- /dev/null +++ b/src/application/features/get-all-to-dos/get-all-to-dos-handler.h @@ -0,0 +1,14 @@ +#pragma once +#include "get-all-to-dos-query.h" +#include "get-all-to-dos-response.h" + +class GetAllToDosHandler { +private: + GetAllToDosQuery& _getAllToDosQuery; +public: + explicit GetAllToDosHandler(GetAllToDosQuery& getAllToDosQuery) + : _getAllToDosQuery(getAllToDosQuery) + {} + + GetAllToDosResponse handle(); +}; \ No newline at end of file diff --git a/src/application/features/get-all-to-dos/get-all-to-dos-query.cpp b/src/application/features/get-all-to-dos/get-all-to-dos-query.cpp new file mode 100644 index 0000000..4dffc3c --- /dev/null +++ b/src/application/features/get-all-to-dos/get-all-to-dos-query.cpp @@ -0,0 +1,17 @@ +#include "get-all-to-dos-query.h" +#include "odb-gen/to-do-odb.hxx" +#include + +std::shared_ptr> GetAllToDosQuery::get() +{ + odb::transaction t(db_.begin()); + + odb::result r = db_.query(odb::query()); + + auto todos = std::make_shared>(); + for (auto i = r.begin(); i != r.end(); ++i) + todos->push_back(*i); + + t.commit(); + return todos; +} \ No newline at end of file diff --git a/src/application/features/get-all-to-dos/get-all-to-dos-query.h b/src/application/features/get-all-to-dos/get-all-to-dos-query.h new file mode 100644 index 0000000..f4a0faa --- /dev/null +++ b/src/application/features/get-all-to-dos/get-all-to-dos-query.h @@ -0,0 +1,18 @@ +#pragma once + +#include "to-do.h" +#include +#include +#include + +class GetAllToDosQuery +{ +public: + GetAllToDosQuery(odb::database& db) + : db_(db) + {} + std::shared_ptr> get(); + +private: + odb::database& db_; +}; \ No newline at end of file diff --git a/src/application/features/get-all-to-dos/get-all-to-dos-response.h b/src/application/features/get-all-to-dos/get-all-to-dos-response.h new file mode 100644 index 0000000..51febd3 --- /dev/null +++ b/src/application/features/get-all-to-dos/get-all-to-dos-response.h @@ -0,0 +1,7 @@ +#pragma once +#include "../../shared-dtos/to-do-dtos.h" +#include + +struct GetAllToDosResponse { + std::vector todos; +}; \ No newline at end of file diff --git a/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.cpp b/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.cpp new file mode 100644 index 0000000..13bf86b --- /dev/null +++ b/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.cpp @@ -0,0 +1,19 @@ +#include "get-to-do-by-id-handler.h" + +static ToDoDTO mapToDTO(const ToDo& todo) +{ + ToDoDTO dto; + dto.id = static_cast(todo.id()); + dto.name = todo.name(); + return dto; +} + +GetToDoByIdResponse GetToDoByIdHandler::handle(uint64_t id) +{ + auto todo = _getToDoByIdQuery.get(static_cast(id)); + + if (!todo) + return { std::nullopt }; + + return { mapToDTO(*todo) }; +} \ No newline at end of file diff --git a/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.h b/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.h new file mode 100644 index 0000000..ba12c83 --- /dev/null +++ b/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.h @@ -0,0 +1,15 @@ +#pragma once +#include "get-to-do-by-id-query.h" +#include "get-to-do-by-id-response.h" +#include + +class GetToDoByIdHandler { +private: + GetToDoById& _getToDoByIdQuery; +public: + explicit GetToDoByIdHandler(GetToDoById& getToDoByIdQuery) + : _getToDoByIdQuery(getToDoByIdQuery) + {} + + GetToDoByIdResponse handle(uint64_t id); +}; \ No newline at end of file diff --git a/src/application/features/get-to-do-by-id/get-to-do-by-id-query.cpp b/src/application/features/get-to-do-by-id/get-to-do-by-id-query.cpp new file mode 100644 index 0000000..c3b2582 --- /dev/null +++ b/src/application/features/get-to-do-by-id/get-to-do-by-id-query.cpp @@ -0,0 +1,18 @@ +#include "get-to-do-by-id-query.h" +#include "odb-gen/to-do-odb.hxx" +#include +#include + +std::shared_ptr GetToDoById::get(int id) +{ + odb::transaction t(db_.begin()); + + odb::result r = db_.query(odb::query::id == id); + + std::shared_ptr todo; + if (!r.empty()) + todo = std::make_shared(*r.begin()); + + t.commit(); + return todo; +} \ No newline at end of file diff --git a/src/application/features/get-to-do-by-id/get-to-do-by-id-query.h b/src/application/features/get-to-do-by-id/get-to-do-by-id-query.h new file mode 100644 index 0000000..000e436 --- /dev/null +++ b/src/application/features/get-to-do-by-id/get-to-do-by-id-query.h @@ -0,0 +1,18 @@ +#pragma once + +#include "to-do.h" +#include +#include +#include + +class GetToDoById +{ +public: + GetToDoById(odb::database& db) + : db_(db) + {} + std::shared_ptr get(int id); + +private: + odb::database& db_; +}; \ No newline at end of file diff --git a/src/application/features/get-to-do-by-id/get-to-do-by-id-response.h b/src/application/features/get-to-do-by-id/get-to-do-by-id-response.h new file mode 100644 index 0000000..6bf2a3f --- /dev/null +++ b/src/application/features/get-to-do-by-id/get-to-do-by-id-response.h @@ -0,0 +1,7 @@ +#pragma once +#include "../../shared-dtos/to-do-dtos.h" +#include + +struct GetToDoByIdResponse { + std::optional todo; +}; \ No newline at end of file diff --git a/src/application/features/hard-delete-to-do/hard-delete-to-do-command.cpp b/src/application/features/hard-delete-to-do/hard-delete-to-do-command.cpp new file mode 100644 index 0000000..baa7c44 --- /dev/null +++ b/src/application/features/hard-delete-to-do/hard-delete-to-do-command.cpp @@ -0,0 +1,13 @@ +#include "hard-delete-to-do-command.h" +#include "odb-gen/to-do-odb.hxx" +#include + +uint64_t HardDeleteToDoCommand::execute(int id) +{ + odb::transaction t(db_.begin()); + const unsigned long long todo_id = db_.erase_query(odb::query::id == id); + t.commit(); + + // TODO: return exactly deleted todo id + return id; +} \ No newline at end of file diff --git a/src/application/features/hard-delete-to-do/hard-delete-to-do-command.h b/src/application/features/hard-delete-to-do/hard-delete-to-do-command.h new file mode 100644 index 0000000..ab08201 --- /dev/null +++ b/src/application/features/hard-delete-to-do/hard-delete-to-do-command.h @@ -0,0 +1,18 @@ +#pragma once + +#include "to-do.h" +#include +#include + +class HardDeleteToDoCommand +{ +public: + HardDeleteToDoCommand(odb::database& db) + : db_(db) + {} + + uint64_t execute(int id); + +private: + odb::database& db_; +}; \ No newline at end of file diff --git a/src/application/features/hard-delete-to-do/hard-delete-to-do-handler.cpp b/src/application/features/hard-delete-to-do/hard-delete-to-do-handler.cpp new file mode 100644 index 0000000..4caaf88 --- /dev/null +++ b/src/application/features/hard-delete-to-do/hard-delete-to-do-handler.cpp @@ -0,0 +1,8 @@ +#include "hard-delete-to-do-handler.h" + +// TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/38): add here a check for not found todo +bool HardDeleteToDoHandler::handle(uint64_t id) +{ + (void) _hardDeleteToDoCommand.execute(static_cast(id)); + return true; +} \ No newline at end of file diff --git a/src/application/features/hard-delete-to-do/hard-delete-to-do-handler.h b/src/application/features/hard-delete-to-do/hard-delete-to-do-handler.h new file mode 100644 index 0000000..7c494ce --- /dev/null +++ b/src/application/features/hard-delete-to-do/hard-delete-to-do-handler.h @@ -0,0 +1,14 @@ +#pragma once +#include "hard-delete-to-do-command.h" +#include + +class HardDeleteToDoHandler { +private: + HardDeleteToDoCommand& _hardDeleteToDoCommand; +public: + explicit HardDeleteToDoHandler(HardDeleteToDoCommand& hardDeleteToDoCommand) + : _hardDeleteToDoCommand(hardDeleteToDoCommand) + {} + + bool handle(uint64_t id); +}; \ No newline at end of file diff --git a/src/application/features/soft-delete-to-do/soft-delete-to-do-command.cpp b/src/application/features/soft-delete-to-do/soft-delete-to-do-command.cpp new file mode 100644 index 0000000..369750a --- /dev/null +++ b/src/application/features/soft-delete-to-do/soft-delete-to-do-command.cpp @@ -0,0 +1,20 @@ +#include "soft-delete-to-do-command.h" +#include "odb-gen/to-do-odb.hxx" +#include +#include +#include + +uint64_t SoftDeleteCommand::execute(int id) +{ + odb::transaction t(db_.begin()); + + std::unique_ptr todo(db_.load(id)); + const std::time_t now_utc = std::chrono::system_clock::to_time_t( + std::chrono::system_clock::now() + ); + todo->deletedAtUtc(now_utc); + db_.update(*todo); + + t.commit(); + return todo->id(); +} \ No newline at end of file diff --git a/src/application/features/soft-delete-to-do/soft-delete-to-do-command.h b/src/application/features/soft-delete-to-do/soft-delete-to-do-command.h new file mode 100644 index 0000000..af572b5 --- /dev/null +++ b/src/application/features/soft-delete-to-do/soft-delete-to-do-command.h @@ -0,0 +1,18 @@ +#pragma once + +#include "to-do.h" +#include +#include + +class SoftDeleteCommand +{ +public: + SoftDeleteCommand(odb::database& db) + : db_(db) + {} + + uint64_t execute(int id); + +private: + odb::database& db_; +}; \ No newline at end of file diff --git a/src/application/features/soft-delete-to-do/soft-delete-to-do-handler.cpp b/src/application/features/soft-delete-to-do/soft-delete-to-do-handler.cpp new file mode 100644 index 0000000..56c9d6a --- /dev/null +++ b/src/application/features/soft-delete-to-do/soft-delete-to-do-handler.cpp @@ -0,0 +1,8 @@ +#include "soft-delete-to-do-handler.h" + +// TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/38): add here a check for not found todo +SoftDeleteToDoResponse SoftDeleteToDoHandler::handle(uint64_t id) +{ + const uint64_t deletedId = _softDeleteToDoCommand.execute(static_cast(id)); + return { deletedId }; +} \ No newline at end of file diff --git a/src/application/features/soft-delete-to-do/soft-delete-to-do-handler.h b/src/application/features/soft-delete-to-do/soft-delete-to-do-handler.h new file mode 100644 index 0000000..c77c8d5 --- /dev/null +++ b/src/application/features/soft-delete-to-do/soft-delete-to-do-handler.h @@ -0,0 +1,15 @@ +#pragma once +#include "soft-delete-to-do-command.h" +#include "soft-delete-to-do-response.h" +#include + +class SoftDeleteToDoHandler { +private: + SoftDeleteCommand& _softDeleteToDoCommand; +public: + explicit SoftDeleteToDoHandler(SoftDeleteCommand& softDeleteToDoCommand) + : _softDeleteToDoCommand(softDeleteToDoCommand) + {} + + SoftDeleteToDoResponse handle(uint64_t id); +}; \ No newline at end of file diff --git a/src/application/features/soft-delete-to-do/soft-delete-to-do-response.h b/src/application/features/soft-delete-to-do/soft-delete-to-do-response.h new file mode 100644 index 0000000..2839b3f --- /dev/null +++ b/src/application/features/soft-delete-to-do/soft-delete-to-do-response.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct SoftDeleteToDoResponse { + uint64_t id; +}; \ No newline at end of file diff --git a/src/application/odb-gen/to-do-odb.cxx b/src/application/odb-gen/to-do-odb.cxx new file mode 100644 index 0000000..9022752 --- /dev/null +++ b/src/application/odb-gen/to-do-odb.cxx @@ -0,0 +1,791 @@ +// -*- C++ -*- +// +// This file was generated by ODB, object-relational mapping (ORM) +// compiler for C++. +// + +#include + +#include "to-do-odb.hxx" + +#include +#include // std::memcpy + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace odb +{ + // ToDo + // + + const char access::object_traits_impl< ::ToDo, id_pgsql >:: + persist_statement_name[] = "persist_ToDo"; + + const char access::object_traits_impl< ::ToDo, id_pgsql >:: + find_statement_name[] = "find_ToDo"; + + const char access::object_traits_impl< ::ToDo, id_pgsql >:: + update_statement_name[] = "update_ToDo"; + + const char access::object_traits_impl< ::ToDo, id_pgsql >:: + erase_statement_name[] = "erase_ToDo"; + + const char access::object_traits_impl< ::ToDo, id_pgsql >:: + query_statement_name[] = "query_ToDo"; + + const char access::object_traits_impl< ::ToDo, id_pgsql >:: + erase_query_statement_name[] = "erase_query_ToDo"; + + const unsigned int access::object_traits_impl< ::ToDo, id_pgsql >:: + persist_statement_types[] = + { + pgsql::text_oid, + pgsql::int8_oid, + pgsql::int8_oid + }; + + const unsigned int access::object_traits_impl< ::ToDo, id_pgsql >:: + find_statement_types[] = + { + pgsql::int8_oid + }; + + const unsigned int access::object_traits_impl< ::ToDo, id_pgsql >:: + update_statement_types[] = + { + pgsql::text_oid, + pgsql::int8_oid, + pgsql::int8_oid, + pgsql::int8_oid + }; + + struct access::object_traits_impl< ::ToDo, id_pgsql >::extra_statement_cache_type + { + extra_statement_cache_type ( + pgsql::connection&, + image_type&, + id_image_type&, + pgsql::binding&, + pgsql::binding&, + pgsql::native_binding&, + const unsigned int*) + { + } + }; + + access::object_traits_impl< ::ToDo, id_pgsql >::id_type + access::object_traits_impl< ::ToDo, id_pgsql >:: + id (const id_image_type& i) + { + pgsql::database* db (0); + ODB_POTENTIALLY_UNUSED (db); + + id_type id; + { + pgsql::value_traits< + ::uint64_t, + pgsql::id_bigint >::set_value ( + id, + i.id_value, + i.id_null); + } + + return id; + } + + access::object_traits_impl< ::ToDo, id_pgsql >::id_type + access::object_traits_impl< ::ToDo, id_pgsql >:: + id (const image_type& i) + { + pgsql::database* db (0); + ODB_POTENTIALLY_UNUSED (db); + + id_type id; + { + pgsql::value_traits< + ::uint64_t, + pgsql::id_bigint >::set_value ( + id, + i.id_value, + i.id_null); + } + + return id; + } + + bool access::object_traits_impl< ::ToDo, id_pgsql >:: + grow (image_type& i, + bool* t) + { + ODB_POTENTIALLY_UNUSED (i); + ODB_POTENTIALLY_UNUSED (t); + + bool grew (false); + + // id_ + // + t[0UL] = 0; + + // name_ + // + if (t[1UL]) + { + i.name_value.capacity (i.name_size); + grew = true; + } + + // createdAtUtc_ + // + t[2UL] = 0; + + // deletedAtUtc_ + // + t[3UL] = 0; + + return grew; + } + + void access::object_traits_impl< ::ToDo, id_pgsql >:: + bind (pgsql::bind* b, + image_type& i, + pgsql::statement_kind sk) + { + ODB_POTENTIALLY_UNUSED (sk); + + using namespace pgsql; + + std::size_t n (0); + + // id_ + // + if (sk != statement_insert && sk != statement_update) + { + b[n].type = pgsql::bind::bigint; + b[n].buffer = &i.id_value; + b[n].is_null = &i.id_null; + n++; + } + + // name_ + // + b[n].type = pgsql::bind::text; + b[n].buffer = i.name_value.data_ptr (); + b[n].capacity = i.name_value.capacity (); + b[n].size = &i.name_size; + b[n].is_null = &i.name_null; + n++; + + // createdAtUtc_ + // + b[n].type = pgsql::bind::bigint; + b[n].buffer = &i.createdAtUtc_value; + b[n].is_null = &i.createdAtUtc_null; + n++; + + // deletedAtUtc_ + // + b[n].type = pgsql::bind::bigint; + b[n].buffer = &i.deletedAtUtc_value; + b[n].is_null = &i.deletedAtUtc_null; + n++; + } + + void access::object_traits_impl< ::ToDo, id_pgsql >:: + bind (pgsql::bind* b, id_image_type& i) + { + std::size_t n (0); + b[n].type = pgsql::bind::bigint; + b[n].buffer = &i.id_value; + b[n].is_null = &i.id_null; + } + + bool access::object_traits_impl< ::ToDo, id_pgsql >:: + init (image_type& i, + const object_type& o, + pgsql::statement_kind sk) + { + ODB_POTENTIALLY_UNUSED (i); + ODB_POTENTIALLY_UNUSED (o); + ODB_POTENTIALLY_UNUSED (sk); + + using namespace pgsql; + + bool grew (false); + + // name_ + // + { + ::std::string const& v = + o.name_; + + bool is_null (false); + std::size_t size (0); + std::size_t cap (i.name_value.capacity ()); + pgsql::value_traits< + ::std::string, + pgsql::id_string >::set_image ( + i.name_value, + size, + is_null, + v); + i.name_null = is_null; + i.name_size = size; + grew = grew || (cap != i.name_value.capacity ()); + } + + // createdAtUtc_ + // + { + ::time_t const& v = + o.createdAtUtc_; + + bool is_null (false); + pgsql::value_traits< + ::time_t, + pgsql::id_bigint >::set_image ( + i.createdAtUtc_value, is_null, v); + i.createdAtUtc_null = is_null; + } + + // deletedAtUtc_ + // + { + ::odb::nullable< long int > const& v = + o.deletedAtUtc_; + + bool is_null (true); + pgsql::value_traits< + ::odb::nullable< long int >, + pgsql::id_bigint >::set_image ( + i.deletedAtUtc_value, is_null, v); + i.deletedAtUtc_null = is_null; + } + + return grew; + } + + void access::object_traits_impl< ::ToDo, id_pgsql >:: + init (object_type& o, + const image_type& i, + database* db) + { + ODB_POTENTIALLY_UNUSED (o); + ODB_POTENTIALLY_UNUSED (i); + ODB_POTENTIALLY_UNUSED (db); + + // id_ + // + { + ::uint64_t& v = + o.id_; + + pgsql::value_traits< + ::uint64_t, + pgsql::id_bigint >::set_value ( + v, + i.id_value, + i.id_null); + } + + // name_ + // + { + ::std::string& v = + o.name_; + + pgsql::value_traits< + ::std::string, + pgsql::id_string >::set_value ( + v, + i.name_value, + i.name_size, + i.name_null); + } + + // createdAtUtc_ + // + { + ::time_t& v = + o.createdAtUtc_; + + pgsql::value_traits< + ::time_t, + pgsql::id_bigint >::set_value ( + v, + i.createdAtUtc_value, + i.createdAtUtc_null); + } + + // deletedAtUtc_ + // + { + ::odb::nullable< long int >& v = + o.deletedAtUtc_; + + pgsql::value_traits< + ::odb::nullable< long int >, + pgsql::id_bigint >::set_value ( + v, + i.deletedAtUtc_value, + i.deletedAtUtc_null); + } + } + + void access::object_traits_impl< ::ToDo, id_pgsql >:: + init (id_image_type& i, const id_type& id) + { + { + bool is_null (false); + pgsql::value_traits< + ::uint64_t, + pgsql::id_bigint >::set_image ( + i.id_value, is_null, id); + i.id_null = is_null; + } + } + + const char access::object_traits_impl< ::ToDo, id_pgsql >::persist_statement[] = + "INSERT INTO \"todo\" " + "(\"id\", " + "\"name\", " + "\"createdAtUtc\", " + "\"deletedAtUtc\") " + "VALUES " + "(DEFAULT, $1, $2, $3) " + "RETURNING \"id\""; + + const char access::object_traits_impl< ::ToDo, id_pgsql >::find_statement[] = + "SELECT " + "\"todo\".\"id\", " + "\"todo\".\"name\", " + "\"todo\".\"createdAtUtc\", " + "\"todo\".\"deletedAtUtc\" " + "FROM \"todo\" " + "WHERE \"todo\".\"id\"=$1"; + + const char access::object_traits_impl< ::ToDo, id_pgsql >::update_statement[] = + "UPDATE \"todo\" " + "SET " + "\"name\"=$1, " + "\"createdAtUtc\"=$2, " + "\"deletedAtUtc\"=$3 " + "WHERE \"id\"=$4"; + + const char access::object_traits_impl< ::ToDo, id_pgsql >::erase_statement[] = + "DELETE FROM \"todo\" " + "WHERE \"id\"=$1"; + + const char access::object_traits_impl< ::ToDo, id_pgsql >::query_statement[] = + "SELECT " + "\"todo\".\"id\", " + "\"todo\".\"name\", " + "\"todo\".\"createdAtUtc\", " + "\"todo\".\"deletedAtUtc\" " + "FROM \"todo\""; + + const char access::object_traits_impl< ::ToDo, id_pgsql >::erase_query_statement[] = + "DELETE FROM \"todo\""; + + const char access::object_traits_impl< ::ToDo, id_pgsql >::table_name[] = + "\"todo\""; + + void access::object_traits_impl< ::ToDo, id_pgsql >:: + persist (database& db, object_type& obj) + { + using namespace pgsql; + + pgsql::connection& conn ( + pgsql::transaction::current ().connection (db)); + statements_type& sts ( + conn.statement_cache ().find_object ()); + + callback (db, + static_cast (obj), + callback_event::pre_persist); + + image_type& im (sts.image ()); + binding& imb (sts.insert_image_binding ()); + + if (init (im, obj, statement_insert)) + im.version++; + + if (im.version != sts.insert_image_version () || + imb.version == 0) + { + bind (imb.bind, im, statement_insert); + sts.insert_image_version (im.version); + imb.version++; + } + + { + id_image_type& i (sts.id_image ()); + binding& b (sts.id_image_binding ()); + if (i.version != sts.id_image_version () || b.version == 0) + { + bind (b.bind, i); + sts.id_image_version (i.version); + b.version++; + } + } + + insert_statement& st (sts.persist_statement ()); + if (!st.execute ()) + throw object_already_persistent (); + + obj.id_ = id (sts.id_image ()); + + callback (db, + static_cast (obj), + callback_event::post_persist); + } + + void access::object_traits_impl< ::ToDo, id_pgsql >:: + update (database& db, const object_type& obj) + { + ODB_POTENTIALLY_UNUSED (db); + + using namespace pgsql; + using pgsql::update_statement; + + callback (db, obj, callback_event::pre_update); + + pgsql::transaction& tr (pgsql::transaction::current ()); + pgsql::connection& conn (tr.connection (db)); + statements_type& sts ( + conn.statement_cache ().find_object ()); + + id_image_type& idi (sts.id_image ()); + init (idi, id (obj)); + + image_type& im (sts.image ()); + if (init (im, obj, statement_update)) + im.version++; + + bool u (false); + binding& imb (sts.update_image_binding ()); + if (im.version != sts.update_image_version () || + imb.version == 0) + { + bind (imb.bind, im, statement_update); + sts.update_image_version (im.version); + imb.version++; + u = true; + } + + binding& idb (sts.id_image_binding ()); + if (idi.version != sts.update_id_image_version () || + idb.version == 0) + { + if (idi.version != sts.id_image_version () || + idb.version == 0) + { + bind (idb.bind, idi); + sts.id_image_version (idi.version); + idb.version++; + } + + sts.update_id_image_version (idi.version); + + if (!u) + imb.version++; + } + + update_statement& st (sts.update_statement ()); + if (st.execute () == 0) + throw object_not_persistent (); + + callback (db, obj, callback_event::post_update); + pointer_cache_traits::update (db, obj); + } + + void access::object_traits_impl< ::ToDo, id_pgsql >:: + erase (database& db, const id_type& id) + { + using namespace pgsql; + + pgsql::connection& conn ( + pgsql::transaction::current ().connection (db)); + statements_type& sts ( + conn.statement_cache ().find_object ()); + + id_image_type& i (sts.id_image ()); + init (i, id); + + binding& idb (sts.id_image_binding ()); + if (i.version != sts.id_image_version () || idb.version == 0) + { + bind (idb.bind, i); + sts.id_image_version (i.version); + idb.version++; + } + + if (sts.erase_statement ().execute () != 1) + throw object_not_persistent (); + + pointer_cache_traits::erase (db, id); + } + + access::object_traits_impl< ::ToDo, id_pgsql >::pointer_type + access::object_traits_impl< ::ToDo, id_pgsql >:: + find (database& db, const id_type& id) + { + using namespace pgsql; + + { + pointer_type p (pointer_cache_traits::find (db, id)); + + if (!pointer_traits::null_ptr (p)) + return p; + } + + pgsql::connection& conn ( + pgsql::transaction::current ().connection (db)); + statements_type& sts ( + conn.statement_cache ().find_object ()); + + statements_type::auto_lock l (sts); + + if (l.locked ()) + { + if (!find_ (sts, &id)) + return pointer_type (); + } + + pointer_type p ( + access::object_factory::create ()); + pointer_traits::guard pg (p); + + pointer_cache_traits::insert_guard ig ( + pointer_cache_traits::insert (db, id, p)); + + object_type& obj (pointer_traits::get_ref (p)); + + if (l.locked ()) + { + select_statement& st (sts.find_statement ()); + ODB_POTENTIALLY_UNUSED (st); + + callback (db, obj, callback_event::pre_load); + init (obj, sts.image (), &db); + load_ (sts, obj, false); + sts.load_delayed (0); + l.unlock (); + callback (db, obj, callback_event::post_load); + pointer_cache_traits::load (ig.position ()); + } + else + sts.delay_load (id, obj, ig.position ()); + + ig.release (); + pg.release (); + return p; + } + + bool access::object_traits_impl< ::ToDo, id_pgsql >:: + find (database& db, const id_type& id, object_type& obj) + { + using namespace pgsql; + + pgsql::connection& conn ( + pgsql::transaction::current ().connection (db)); + statements_type& sts ( + conn.statement_cache ().find_object ()); + + statements_type::auto_lock l (sts); + assert (l.locked ()) /* Must be a top-level call. */; + + if (!find_ (sts, &id)) + return false; + + select_statement& st (sts.find_statement ()); + ODB_POTENTIALLY_UNUSED (st); + + reference_cache_traits::position_type pos ( + reference_cache_traits::insert (db, id, obj)); + reference_cache_traits::insert_guard ig (pos); + + callback (db, obj, callback_event::pre_load); + init (obj, sts.image (), &db); + load_ (sts, obj, false); + sts.load_delayed (0); + l.unlock (); + callback (db, obj, callback_event::post_load); + reference_cache_traits::load (pos); + ig.release (); + return true; + } + + bool access::object_traits_impl< ::ToDo, id_pgsql >:: + reload (database& db, object_type& obj) + { + using namespace pgsql; + + pgsql::connection& conn ( + pgsql::transaction::current ().connection (db)); + statements_type& sts ( + conn.statement_cache ().find_object ()); + + statements_type::auto_lock l (sts); + assert (l.locked ()) /* Must be a top-level call. */; + + const id_type& id (object_traits_impl::id (obj)); + if (!find_ (sts, &id)) + return false; + + select_statement& st (sts.find_statement ()); + ODB_POTENTIALLY_UNUSED (st); + + callback (db, obj, callback_event::pre_load); + init (obj, sts.image (), &db); + load_ (sts, obj, true); + sts.load_delayed (0); + l.unlock (); + callback (db, obj, callback_event::post_load); + return true; + } + + bool access::object_traits_impl< ::ToDo, id_pgsql >:: + find_ (statements_type& sts, + const id_type* id) + { + using namespace pgsql; + + id_image_type& i (sts.id_image ()); + init (i, *id); + + binding& idb (sts.id_image_binding ()); + if (i.version != sts.id_image_version () || idb.version == 0) + { + bind (idb.bind, i); + sts.id_image_version (i.version); + idb.version++; + } + + image_type& im (sts.image ()); + binding& imb (sts.select_image_binding ()); + + if (im.version != sts.select_image_version () || + imb.version == 0) + { + bind (imb.bind, im, statement_select); + sts.select_image_version (im.version); + imb.version++; + } + + select_statement& st (sts.find_statement ()); + + st.execute (); + auto_result ar (st); + select_statement::result r (st.fetch ()); + + if (r == select_statement::truncated) + { + if (grow (im, sts.select_image_truncated ())) + im.version++; + + if (im.version != sts.select_image_version ()) + { + bind (imb.bind, im, statement_select); + sts.select_image_version (im.version); + imb.version++; + st.refetch (); + } + } + + return r != select_statement::no_data; + } + + result< access::object_traits_impl< ::ToDo, id_pgsql >::object_type > + access::object_traits_impl< ::ToDo, id_pgsql >:: + query (database& db, const query_base_type& q) + { + using namespace pgsql; + using odb::details::shared; + using odb::details::shared_ptr; + + pgsql::connection& conn ( + pgsql::transaction::current ().connection (db)); + + statements_type& sts ( + conn.statement_cache ().find_object ()); + + image_type& im (sts.image ()); + binding& imb (sts.select_image_binding ()); + + if (im.version != sts.select_image_version () || + imb.version == 0) + { + bind (imb.bind, im, statement_select); + sts.select_image_version (im.version); + imb.version++; + } + + std::string text (query_statement); + if (!q.empty ()) + { + text += " "; + text += q.clause (); + } + + q.init_parameters (); + shared_ptr st ( + new (shared) select_statement ( + sts.connection (), + query_statement_name, + text, + false, + true, + q.parameter_types (), + q.parameter_count (), + q.parameters_binding (), + imb)); + + st->execute (); + st->deallocate (); + + shared_ptr< odb::object_result_impl > r ( + new (shared) pgsql::object_result_impl ( + q, st, sts, 0)); + + return result (r); + } + + unsigned long long access::object_traits_impl< ::ToDo, id_pgsql >:: + erase_query (database& db, const query_base_type& q) + { + using namespace pgsql; + + pgsql::connection& conn ( + pgsql::transaction::current ().connection (db)); + + std::string text (erase_query_statement); + if (!q.empty ()) + { + text += ' '; + text += q.clause (); + } + + q.init_parameters (); + delete_statement st ( + conn, + erase_query_statement_name, + text, + q.parameter_types (), + q.parameter_count (), + q.parameters_binding ()); + + return st.execute (); + } +} + +#include diff --git a/src/application/odb-gen/to-do-odb.hxx b/src/application/odb-gen/to-do-odb.hxx new file mode 100644 index 0000000..6af34ad --- /dev/null +++ b/src/application/odb-gen/to-do-odb.hxx @@ -0,0 +1,335 @@ +// -*- C++ -*- +// +// This file was generated by ODB, object-relational mapping (ORM) +// compiler for C++. +// + +#ifndef TO_DO_ODB_HXX +#define TO_DO_ODB_HXX + +#include + +#if ODB_VERSION != 20500UL +#error ODB runtime version mismatch +#endif + +#include + +#include "to-do.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace odb +{ + // ToDo + // + template <> + struct class_traits< ::ToDo > + { + static const class_kind kind = class_object; + }; + + template <> + class access::object_traits< ::ToDo > + { + public: + typedef ::ToDo object_type; + typedef ::ToDo* pointer_type; + typedef odb::pointer_traits pointer_traits; + + static const bool polymorphic = false; + + typedef ::uint64_t id_type; + + static const bool auto_id = true; + + static const bool abstract = false; + + static id_type + id (const object_type&); + + typedef + no_op_pointer_cache_traits + pointer_cache_traits; + + typedef + no_op_reference_cache_traits + reference_cache_traits; + + static void + callback (database&, object_type&, callback_event); + + static void + callback (database&, const object_type&, callback_event); + }; +} + +#include + +#include +#include +#include +#include +#include + +namespace odb +{ + // ToDo + // + template + struct query_columns< ::ToDo, id_pgsql, A > + { + // id + // + typedef + pgsql::query_column< + pgsql::value_traits< + ::uint64_t, + pgsql::id_bigint >::query_type, + pgsql::id_bigint > + id_type_; + + static const id_type_ id; + + // name + // + typedef + pgsql::query_column< + pgsql::value_traits< + ::std::string, + pgsql::id_string >::query_type, + pgsql::id_string > + name_type_; + + static const name_type_ name; + + // createdAtUtc + // + typedef + pgsql::query_column< + pgsql::value_traits< + ::time_t, + pgsql::id_bigint >::query_type, + pgsql::id_bigint > + createdAtUtc_type_; + + static const createdAtUtc_type_ createdAtUtc; + + // deletedAtUtc + // + typedef + pgsql::query_column< + pgsql::value_traits< + long int, + pgsql::id_bigint >::query_type, + pgsql::id_bigint > + deletedAtUtc_type_; + + static const deletedAtUtc_type_ deletedAtUtc; + }; + + template + const typename query_columns< ::ToDo, id_pgsql, A >::id_type_ + query_columns< ::ToDo, id_pgsql, A >:: + id (A::table_name, "\"id\"", 0); + + template + const typename query_columns< ::ToDo, id_pgsql, A >::name_type_ + query_columns< ::ToDo, id_pgsql, A >:: + name (A::table_name, "\"name\"", 0); + + template + const typename query_columns< ::ToDo, id_pgsql, A >::createdAtUtc_type_ + query_columns< ::ToDo, id_pgsql, A >:: + createdAtUtc (A::table_name, "\"createdAtUtc\"", 0); + + template + const typename query_columns< ::ToDo, id_pgsql, A >::deletedAtUtc_type_ + query_columns< ::ToDo, id_pgsql, A >:: + deletedAtUtc (A::table_name, "\"deletedAtUtc\"", 0); + + template + struct pointer_query_columns< ::ToDo, id_pgsql, A >: + query_columns< ::ToDo, id_pgsql, A > + { + }; + + template <> + class access::object_traits_impl< ::ToDo, id_pgsql >: + public access::object_traits< ::ToDo > + { + public: + struct id_image_type + { + long long id_value; + bool id_null; + + std::size_t version; + }; + + struct image_type + { + // id_ + // + long long id_value; + bool id_null; + + // name_ + // + details::buffer name_value; + std::size_t name_size; + bool name_null; + + // createdAtUtc_ + // + long long createdAtUtc_value; + bool createdAtUtc_null; + + // deletedAtUtc_ + // + long long deletedAtUtc_value; + bool deletedAtUtc_null; + + std::size_t version; + }; + + struct extra_statement_cache_type; + + using object_traits::id; + + static id_type + id (const id_image_type&); + + static id_type + id (const image_type&); + + static bool + grow (image_type&, + bool*); + + static void + bind (pgsql::bind*, + image_type&, + pgsql::statement_kind); + + static void + bind (pgsql::bind*, id_image_type&); + + static bool + init (image_type&, + const object_type&, + pgsql::statement_kind); + + static void + init (object_type&, + const image_type&, + database*); + + static void + init (id_image_type&, const id_type&); + + typedef pgsql::object_statements statements_type; + + typedef pgsql::query_base query_base_type; + + static const std::size_t column_count = 4UL; + static const std::size_t id_column_count = 1UL; + static const std::size_t inverse_column_count = 0UL; + static const std::size_t readonly_column_count = 0UL; + static const std::size_t managed_optimistic_column_count = 0UL; + + static const std::size_t separate_load_column_count = 0UL; + static const std::size_t separate_update_column_count = 0UL; + + static const bool versioned = false; + + static const char persist_statement[]; + static const char find_statement[]; + static const char update_statement[]; + static const char erase_statement[]; + static const char query_statement[]; + static const char erase_query_statement[]; + + static const char table_name[]; + + static void + persist (database&, object_type&); + + static pointer_type + find (database&, const id_type&); + + static bool + find (database&, const id_type&, object_type&); + + static bool + reload (database&, object_type&); + + static void + update (database&, const object_type&); + + static void + erase (database&, const id_type&); + + static void + erase (database&, const object_type&); + + static result + query (database&, const query_base_type&); + + static unsigned long long + erase_query (database&, const query_base_type&); + + static const char persist_statement_name[]; + static const char find_statement_name[]; + static const char update_statement_name[]; + static const char erase_statement_name[]; + static const char query_statement_name[]; + static const char erase_query_statement_name[]; + + static const unsigned int persist_statement_types[]; + static const unsigned int find_statement_types[]; + static const unsigned int update_statement_types[]; + + static const std::size_t batch = 1UL; + + public: + static bool + find_ (statements_type&, + const id_type*); + + static void + load_ (statements_type&, + object_type&, + bool reload); + }; + + template <> + class access::object_traits_impl< ::ToDo, id_common >: + public access::object_traits_impl< ::ToDo, id_pgsql > + { + }; + + // ToDo + // +} + +#include "to-do-odb.ixx" + +#include + +#endif // TO_DO_ODB_HXX diff --git a/src/application/odb-gen/to-do-odb.ixx b/src/application/odb-gen/to-do-odb.ixx new file mode 100644 index 0000000..f19cf5c --- /dev/null +++ b/src/application/odb-gen/to-do-odb.ixx @@ -0,0 +1,63 @@ +// -*- C++ -*- +// +// This file was generated by ODB, object-relational mapping (ORM) +// compiler for C++. +// + +namespace odb +{ + // ToDo + // + + inline + access::object_traits< ::ToDo >::id_type + access::object_traits< ::ToDo >:: + id (const object_type& o) + { + return o.id_; + } + + inline + void access::object_traits< ::ToDo >:: + callback (database& db, object_type& x, callback_event e) + { + ODB_POTENTIALLY_UNUSED (db); + ODB_POTENTIALLY_UNUSED (x); + ODB_POTENTIALLY_UNUSED (e); + } + + inline + void access::object_traits< ::ToDo >:: + callback (database& db, const object_type& x, callback_event e) + { + ODB_POTENTIALLY_UNUSED (db); + ODB_POTENTIALLY_UNUSED (x); + ODB_POTENTIALLY_UNUSED (e); + } +} + +namespace odb +{ + // ToDo + // + + inline + void access::object_traits_impl< ::ToDo, id_pgsql >:: + erase (database& db, const object_type& obj) + { + callback (db, obj, callback_event::pre_erase); + erase (db, id (obj)); + callback (db, obj, callback_event::post_erase); + } + + inline + void access::object_traits_impl< ::ToDo, id_pgsql >:: + load_ (statements_type& sts, + object_type& obj, + bool) + { + ODB_POTENTIALLY_UNUSED (sts); + ODB_POTENTIALLY_UNUSED (obj); + } +} + diff --git a/src/services/dtos/to-dos-dto.cpp b/src/application/shared-dtos/to-do-dtos.cpp similarity index 68% rename from src/services/dtos/to-dos-dto.cpp rename to src/application/shared-dtos/to-do-dtos.cpp index 3c1b7e3..47ef199 100644 --- a/src/services/dtos/to-dos-dto.cpp +++ b/src/application/shared-dtos/to-do-dtos.cpp @@ -1,10 +1,9 @@ -#include "to-dos-dto.h" +#include "to-do-dtos.h" Json::Value ToDoDTO::toJson() const { Json::Value json; - json["id"] = id; + json["id"] = id; json["name"] = name; - return json; } \ No newline at end of file diff --git a/src/services/dtos/to-dos-dto.h b/src/application/shared-dtos/to-do-dtos.h similarity index 87% rename from src/services/dtos/to-dos-dto.h rename to src/application/shared-dtos/to-do-dtos.h index b8c3837..d1b714f 100644 --- a/src/services/dtos/to-dos-dto.h +++ b/src/application/shared-dtos/to-do-dtos.h @@ -1,10 +1,8 @@ #pragma once - #include #include -struct ToDoDTO -{ +struct ToDoDTO { int id; std::string name; diff --git a/src/data/alembic.ini b/src/data/alembic.ini deleted file mode 100644 index fe66372..0000000 --- a/src/data/alembic.ini +++ /dev/null @@ -1,141 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts. -# this is typically a path given in POSIX (e.g. forward slashes) -# format, relative to the token %(here)s which refers to the location of this -# ini file -script_location = %(here)s/migrations - -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file -# for all available tokens -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. for multiple paths, the path separator -# is defined by "path_separator" below. -prepend_sys_path = . - - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library. -# Any required deps can installed by adding `alembic[tz]` to the pip requirements -# string value is passed to ZoneInfo() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to /versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "path_separator" -# below. -# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions - -# path_separator; This indicates what character is used to split lists of file -# paths, including version_locations and prepend_sys_path within configparser -# files such as alembic.ini. -# The default rendered in new alembic.ini files is "os", which uses os.pathsep -# to provide os-dependent path splitting. -# -# Note that in order to support legacy alembic.ini files, this default does NOT -# take place if path_separator is not present in alembic.ini. If this -# option is omitted entirely, fallback logic is as follows: -# -# 1. Parsing of the version_locations option falls back to using the legacy -# "version_path_separator" key, which if absent then falls back to the legacy -# behavior of splitting on spaces and/or commas. -# 2. Parsing of the prepend_sys_path option falls back to the legacy -# behavior of splitting on spaces, commas, or colons. -# -# Valid values for path_separator are: -# -# path_separator = : -# path_separator = ; -# path_separator = space -# path_separator = newline -# -# Use os.pathsep. Default configuration used for new projects. -path_separator = os - -# set to 'true' to search source files recursively -# in each "version_locations" directory -# new in Alembic version 1.10 -# recursive_version_locations = false - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module -# hooks = ruff -# ruff.type = module -# ruff.module = ruff -# ruff.options = check --fix REVISION_SCRIPT_FILENAME - -# Alternatively, use the exec runner to execute a binary found on your PATH -# hooks = ruff -# ruff.type = exec -# ruff.executable = ruff -# ruff.options = check --fix REVISION_SCRIPT_FILENAME - -# Logging configuration. This is also consumed by the user-maintained -# env.py script only. -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARNING -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARNING -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/src/data/commands/todo-commands.cpp b/src/data/commands/todo-commands.cpp deleted file mode 100644 index edf2423..0000000 --- a/src/data/commands/todo-commands.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "todo-commands.h" -#include "data/models/odb-gen/to-do-odb.hxx" -#include -#include - -uint64_t ToDoCommands::create_todo(const std::string& name, std::time_t createdAtUtc) -{ - ToDo todo(name, createdAtUtc); - - odb::transaction t(db_.begin()); - db_.persist(todo); - t.commit(); - - return todo.id(); -} - -uint64_t ToDoCommands::delete_todo(int id) -{ - odb::transaction t(db_.begin()); - - const unsigned long long todo_id = db_.erase_query(odb::query::id == id); - - t.commit(); - // TODO: return exactly deleted todo id - return id; -} - -uint64_t ToDoCommands::soft_delete_todo(int id) -{ - odb::transaction t(db_.begin()); - - std::unique_ptr todo(db_.load(id)); - - const std::time_t now_utc = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - - todo->deletedAtUtc(now_utc); - db_.update(*todo); - - t.commit(); - return todo->id(); -} diff --git a/src/data/commands/todo-commands.h b/src/data/commands/todo-commands.h deleted file mode 100644 index 1953476..0000000 --- a/src/data/commands/todo-commands.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "data/models/to-do.h" -#include -#include - -class ToDoCommands -{ -public: - ToDoCommands(odb::database& db) - : db_(db) - {} - - uint64_t create_todo(const std::string& name, std::time_t createdAtUtc); - uint64_t delete_todo(int id); - uint64_t soft_delete_todo(int id); - -private: - odb::database& db_; -}; \ No newline at end of file diff --git a/src/data/queries/todo-queries.cpp b/src/data/queries/todo-queries.cpp deleted file mode 100644 index aecb7ed..0000000 --- a/src/data/queries/todo-queries.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "todo-queries.h" -#include "data/models/odb-gen/to-do-odb.hxx" -#include -#include - -std::shared_ptr> ToDoQueries::get_all_todos() -{ - odb::transaction t(db_.begin()); - - odb::result r = db_.query(odb::query()); - - auto todos = std::make_shared>(); - for (auto i = r.begin(); i != r.end(); ++i) - todos->push_back(*i); - - t.commit(); - return todos; -} - -std::shared_ptr ToDoQueries::get_todo_by_id(int id) -{ - odb::transaction t(db_.begin()); - - odb::result r = db_.query(odb::query::id == id); - - std::shared_ptr todo; - if (!r.empty()) - todo = std::make_shared(*r.begin()); - - t.commit(); - return todo; -} diff --git a/src/data/queries/todo-queries.h b/src/data/queries/todo-queries.h deleted file mode 100644 index 504e571..0000000 --- a/src/data/queries/todo-queries.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "data/models/to-do.h" -#include -#include -#include - -class ToDoQueries -{ -public: - ToDoQueries(odb::database& db) - : db_(db) - {} - std::shared_ptr> get_all_todos(); - std::shared_ptr get_todo_by_id(int id); - -private: - odb::database& db_; -}; \ No newline at end of file diff --git a/src/services/to-dos.service.cpp b/src/services/to-dos.service.cpp deleted file mode 100644 index fb292ec..0000000 --- a/src/services/to-dos.service.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#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.name = t.name(); - out.push_back(std::move(dto)); - } - } - return out; -} - -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(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); - return true; -} - -// TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/38): add here a check for not found todos -bool ToDoService::deleteToDo(int id) -{ - (void) _commands.delete_todo(id); - return true; -} - -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) - { - toDosArray.append(dto.toJson()); - } - json["toDos"] = toDosArray; - return json; -} \ No newline at end of file diff --git a/src/services/to-dos.service.h b/src/services/to-dos.service.h deleted file mode 100644 index 1f836e0..0000000 --- a/src/services/to-dos.service.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "data/commands/todo-commands.h" -#include "data/queries/todo-queries.h" -#include "dtos/to-dos-dto.h" - -#include -#include -#include - -class ToDoService -{ -private: - ToDoQueries& _queries; - ToDoCommands& _commands; - -public: - explicit ToDoService(ToDoQueries& queries, ToDoCommands& commands) - : _queries(queries), - _commands(commands) - {} - - void addToDo(const std::string description); - bool completeToDo(int id); - bool deleteToDo(int id); - const Json::Value getToDos() const; -}; \ No newline at end of file From 98389b38dacb3e54eabac098542c9fd7bd1dfd75 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 26 Mar 2026 18:55:40 +0500 Subject: [PATCH 04/13] chore(architecture): #55: create api level of architecture --- src/api/CMakeLists.txt | 15 +++ src/api/features/todos/todos-controller.cpp | 141 ++++++++++++++++++++ src/api/features/todos/todos-controller.h | 46 +++++++ src/controllers/app-controller.cpp | 131 ------------------ src/controllers/app-controller.h | 38 ------ 5 files changed, 202 insertions(+), 169 deletions(-) create mode 100644 src/api/CMakeLists.txt create mode 100644 src/api/features/todos/todos-controller.cpp create mode 100644 src/api/features/todos/todos-controller.h delete mode 100644 src/controllers/app-controller.cpp delete mode 100644 src/controllers/app-controller.h diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt new file mode 100644 index 0000000..9e873da --- /dev/null +++ b/src/api/CMakeLists.txt @@ -0,0 +1,15 @@ +file(GLOB_RECURSE api_sources + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp +) + +add_library(api STATIC ${api_sources}) + +target_include_directories(api PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(api + PRIVATE application + Drogon::Drogon + JsonCpp::JsonCpp +) \ No newline at end of file diff --git a/src/api/features/todos/todos-controller.cpp b/src/api/features/todos/todos-controller.cpp new file mode 100644 index 0000000..1200959 --- /dev/null +++ b/src/api/features/todos/todos-controller.cpp @@ -0,0 +1,141 @@ +#include "todos-controller.h" +#include "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 ToDosController::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; +} + +ToDosController::ToDosController() +{ + db_ = std::move(DbConnection::get()); + + createToDoCommand_ = std::make_unique(*db_); + getAllToDosQuery_ = std::make_unique(*db_); + getToDoByIdQuery_ = std::make_unique(*db_); + hardDeleteToDoCommand_ = std::make_unique(*db_); + softDeleteCommand_ = std::make_unique(*db_); + + createToDoHandler_ = std::make_unique(*createToDoCommand_); + getAllToDosHandler_ = std::make_unique(*getAllToDosQuery_); + getToDoByIdHandler_ = std::make_unique(*getToDoByIdQuery_); + hardDeleteToDoHandler_ = std::make_unique(*hardDeleteToDoCommand_); + softDeleteToDoHandler_ = std::make_unique(*softDeleteCommand_); +} + +void ToDosController::getToDos(const HttpRequestPtr& req, std::function&& callback) +{ + try + { + auto result = getAllToDosHandler_->handle(); + + Json::Value toDosArray(Json::arrayValue); + for (const auto& dto : result.todos) + toDosArray.append(dto.toJson()); + + Json::Value json; + json["toDos"] = toDosArray; + + auto resp = HttpResponse::newHttpJsonResponse(json); + resp->setStatusCode(k200OK); + callback(resp); + } + catch (const std::exception& e) + { + callback(createInternalServerErrorResponse(e.what())); + } +} + +void ToDosController::addToDo(const HttpRequestPtr& req, std::function&& callback) +{ + try + { + auto json = req->getJsonObject(); + + if (!json || !json->isMember("name")) + { + Json::Value result; + result["status"] = "error"; + result["message"] = "Invalid JSON"; + + auto resp = HttpResponse::newHttpJsonResponse(result); + resp->setStatusCode(k400BadRequest); + callback(resp); + return; + } + + CreateToDoRequest request{ json->get("name", "").asString() }; + (void) createToDoHandler_->handle(request); + + auto resp = HttpResponse::newHttpResponse(); + resp->setStatusCode(k201Created); + callback(resp); + } + catch (const std::exception& e) + { + callback(createInternalServerErrorResponse(e.what())); + } +} + +void ToDosController::completeToDos(const HttpRequestPtr& req, std::function&& callback) +{ + try + { + auto json = req->getJsonObject(); + + if (!json || !json->isMember("toDosIds")) + { + Json::Value result; + result["status"] = "error"; + result["message"] = "Invalid JSON"; + + auto resp = HttpResponse::newHttpJsonResponse(result); + resp->setStatusCode(k400BadRequest); + callback(resp); + return; + } + + auto toDosIds = json->get("toDosIds", Json::Value(Json::arrayValue)); + for (const auto& id : toDosIds) + { + // TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/38): add here a check for not found todos + (void) softDeleteToDoHandler_->handle(static_cast(id.asInt())); + } + + auto resp = HttpResponse::newHttpResponse(); + resp->setStatusCode(k200OK); + callback(resp); + } + catch (const std::exception& e) + { + callback(createInternalServerErrorResponse(e.what())); + } +} + +void ToDosController::deleteToDo(const HttpRequestPtr& req, std::function&& callback) +{ + try + { + auto toDoIdStr = req->getParameter("toDoId"); + const uint64_t toDoId = static_cast(std::stoi(toDoIdStr)); + + // TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/38): add here a check for not found todo + (void) hardDeleteToDoHandler_->handle(toDoId); + + auto resp = HttpResponse::newHttpResponse(); + resp->setStatusCode(k200OK); + callback(resp); + } + catch (const std::exception& e) + { + callback(createInternalServerErrorResponse(e.what())); + } +} \ No newline at end of file diff --git a/src/api/features/todos/todos-controller.h b/src/api/features/todos/todos-controller.h new file mode 100644 index 0000000..408c5a7 --- /dev/null +++ b/src/api/features/todos/todos-controller.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include "features/create-to-do/create-to-do-handler.h" +#include "features/get-all-to-dos/get-all-to-dos-handler.h" +#include "features/get-to-do-by-id/get-to-do-by-id-handler.h" +#include "features/hard-delete-to-do/hard-delete-to-do-handler.h" +#include "features/soft-delete-to-do/soft-delete-to-do-handler.h" + +using namespace drogon; + +class ToDosController : public drogon::HttpController +{ +public: + explicit ToDosController(); + + METHOD_LIST_BEGIN + ADD_METHOD_TO(ToDosController::getToDos, "/to-dos", Get); + ADD_METHOD_TO(ToDosController::addToDo, "/to-dos", Post); + ADD_METHOD_TO(ToDosController::completeToDos, "/to-dos/complete", Post); + ADD_METHOD_TO(ToDosController::deleteToDo, "/to-dos", Delete); + 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); + +private: + std::shared_ptr db_; + + std::unique_ptr createToDoCommand_; + std::unique_ptr getAllToDosQuery_; + std::unique_ptr getToDoByIdQuery_; + std::unique_ptr hardDeleteToDoCommand_; + std::unique_ptr softDeleteCommand_; + + std::unique_ptr createToDoHandler_; + std::unique_ptr getAllToDosHandler_; + std::unique_ptr getToDoByIdHandler_; + std::unique_ptr hardDeleteToDoHandler_; + std::unique_ptr softDeleteToDoHandler_; +}; \ No newline at end of file diff --git a/src/controllers/app-controller.cpp b/src/controllers/app-controller.cpp deleted file mode 100644 index fa89f84..0000000 --- a/src/controllers/app-controller.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "app-controller.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 -{ - Json::Value jsonResponse; - jsonResponse["status"] = "Error"; - jsonResponse["message"] = "Internal server error"; - jsonResponse["error"] = error; - - auto resp = HttpResponse::newHttpJsonResponse(jsonResponse); - resp->setStatusCode(k500InternalServerError); - return resp; -} - -AppController::AppController() -{ - db_ = std::move(DbConnection::get()); - queries_ = std::make_unique(*db_); - commands_ = std::make_unique(*db_); - todo_service_ = std::make_unique(*queries_, *commands_); -} - -void AppController::getToDos(const HttpRequestPtr& req, std::function&& callback) -{ - Json::Value jsonResponse; - - try - { - auto resp = HttpResponse::newHttpJsonResponse(todo_service_->getToDos()); - resp->setStatusCode(k200OK); - callback(resp); - } - catch (const std::exception& e) - { - callback(createInternalServerErrorResponse(e.what())); - } -} - -void AppController::addToDo(const HttpRequestPtr& req, std::function&& callback) -{ - Json::Value jsonResponse; - - try - { - auto json = req->getJsonObject(); - - if (!json || !json->isMember("name")) - { - Json::Value result; - result["status"] = "error"; - result["message"] = "Invalid JSON"; - - auto resp = HttpResponse::newHttpJsonResponse(result); - resp->setStatusCode(k400BadRequest); - callback(resp); - - return; - } - - todo_service_->addToDo(json->get("name", "").asString()); - - auto resp = HttpResponse::newHttpResponse(); - resp->setStatusCode(k201Created); - callback(resp); - } - catch (const std::exception& e) - { - callback(createInternalServerErrorResponse(e.what())); - } -} - -void AppController::completeToDos(const HttpRequestPtr& req, std::function&& callback) -{ - Json::Value jsonResponse; - - try - { - auto json = req->getJsonObject(); - - if (!json || !json->isMember("toDosIds")) - { - Json::Value result; - result["status"] = "error"; - result["message"] = "Invalid JSON"; - - auto resp = HttpResponse::newHttpJsonResponse(result); - resp->setStatusCode(k400BadRequest); - callback(resp); - - return; - } - - auto toDosIds = json->get("toDosIds", Json::Value(Json::arrayValue)); - - for (const auto& id : toDosIds) - { - todo_service_->completeToDo(id.asInt()); - } - - auto resp = HttpResponse::newHttpResponse(); - resp->setStatusCode(k200OK); - callback(resp); - } - catch (const std::exception& e) - { - callback(createInternalServerErrorResponse(e.what())); - } -} - -void AppController::deleteToDo(const HttpRequestPtr& req, std::function&& callback) -{ - Json::Value jsonResponse; - - try - { - auto toDoIdStr = req->getParameter("toDoId"); - int toDoId = std::stoi(toDoIdStr); - - todo_service_->deleteToDo(toDoId); - - auto resp = HttpResponse::newHttpResponse(); - resp->setStatusCode(k200OK); - callback(resp); - } - catch (const std::exception& e) - { - callback(createInternalServerErrorResponse(e.what())); - } -} diff --git a/src/controllers/app-controller.h b/src/controllers/app-controller.h deleted file mode 100644 index 6528432..0000000 --- a/src/controllers/app-controller.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#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 -{ -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 - 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); - -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 2c0705afcdda3163a9cfd787ac901759179d6e43 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Thu, 26 Mar 2026 18:56:15 +0500 Subject: [PATCH 05/13] chore(architecture): #55: revrite cmakefile due new architecture --- CMakeLists.txt | 52 +++++++++----------------------------------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f56a1c..88a002d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,55 +1,21 @@ cmake_minimum_required(VERSION 3.15) -set(PROJECT_NAME "to-dos-api") -project(${PROJECT_NAME} CXX) - -file(GLOB_RECURSE sources CONFIGURE_DEPENDS - ${CMAKE_SOURCE_DIR}/src/*/*.cpp - ${CMAKE_SOURCE_DIR}/src/data/models/odb-gen/*.cxx -) - -set(PROJECT_OBJECTS ${PROJECT_NAME}_lib) - -add_library(${PROJECT_OBJECTS} OBJECT ${sources}) - -file(GLOB_RECURSE database_models - ${CMAKE_CURRENT_SOURCE_DIR}/src/data/models/*.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/data/models/*.hxx -) - -set(database_model_dirs "") -foreach(header ${database_models}) - get_filename_component(dir ${header} DIRECTORY) - list(APPEND database_model_dirs ${dir}) -endforeach() -list(REMOVE_DUPLICATES database_model_dirs) - -target_include_directories(${PROJECT_OBJECTS} PUBLIC - ${CMAKE_SOURCE_DIR}/src - /usr/include - ${database_model_dirs} -) +project(to-dos-api CXX) find_package(Drogon REQUIRED) find_package(jsoncpp REQUIRED) find_package(libodb REQUIRED) find_package(libodb-pgsql REQUIRED) -target_link_libraries(${PROJECT_OBJECTS} PUBLIC - Drogon::Drogon - JsonCpp::JsonCpp - libodb::libodb - libodb-pgsql::libodb-pgsql -) - -add_executable(${PROJECT_NAME} - ${CMAKE_SOURCE_DIR}/src/main.cpp - $ -) +add_subdirectory(src/core) +add_subdirectory(src/application) +add_subdirectory(src/api) -target_link_libraries(${PROJECT_NAME} PUBLIC - ${PROJECT_OBJECTS} +add_executable(${PROJECT_NAME} src/main.cpp) +target_link_libraries(${PROJECT_NAME} + PRIVATE + api + Drogon::Drogon ) -# Tests enable_testing() add_subdirectory(test) \ No newline at end of file From 5bc8be950ff22520bb91e25911f58c7f8e5b75c7 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Fri, 27 Mar 2026 10:59:15 +0500 Subject: [PATCH 06/13] fix(architecture): #55: add compiler flags for api Drogon is dropped METHOD_LIST_BEGIN if its not listed in main --- CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 88a002d..ce3a1a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,15 +6,17 @@ find_package(jsoncpp REQUIRED) find_package(libodb REQUIRED) find_package(libodb-pgsql REQUIRED) +set(API_LIB api) +set(APPLICATION_LIB application) +set(CORE_LIB core) + add_subdirectory(src/core) add_subdirectory(src/application) add_subdirectory(src/api) add_executable(${PROJECT_NAME} src/main.cpp) target_link_libraries(${PROJECT_NAME} - PRIVATE - api - Drogon::Drogon + PRIVATE -Wl,--whole-archive ${API_LIB} -Wl,--no-whole-archive ) enable_testing() From 0da52a371527d8e8e2f3fb06322111ccba9226b5 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Fri, 27 Mar 2026 10:59:42 +0500 Subject: [PATCH 07/13] chore(architecture): #55: add warn for future unit tests implementation --- test/addition-operation.cpp | 5 ++--- test/addition-operation.h | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/addition-operation.cpp b/test/addition-operation.cpp index 50489ed..05ba360 100644 --- a/test/addition-operation.cpp +++ b/test/addition-operation.cpp @@ -1,6 +1,5 @@ +// WARN: This included as example. In future please place unit tests in folder where the tested functionality is located. #include "addition-operation.h" int AdditionOperation(int a, int b) -{ - return a + b; -} \ No newline at end of file +{ return a + b; } \ No newline at end of file diff --git a/test/addition-operation.h b/test/addition-operation.h index 844dd78..cfff120 100644 --- a/test/addition-operation.h +++ b/test/addition-operation.h @@ -1,3 +1,4 @@ +// WARN: This included as example. In future please place unit tests in folder where the tested functionality is located. #pragma once int AdditionOperation(int a, int b); \ No newline at end of file From d23a7b7249bafb70884d75b14c0534da4a2c65f1 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Fri, 27 Mar 2026 11:00:19 +0500 Subject: [PATCH 08/13] ref(architecture): #55: add a variables for library names and cleanup dependencies --- src/api/CMakeLists.txt | 9 ++++----- src/application/CMakeLists.txt | 11 +++++------ src/core/CMakeLists.txt | 6 +++--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 9e873da..694ad75 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -2,14 +2,13 @@ file(GLOB_RECURSE api_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ) -add_library(api STATIC ${api_sources}) +add_library(${API_LIB} STATIC ${api_sources}) -target_include_directories(api PUBLIC +target_include_directories(${API_LIB} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) -target_link_libraries(api - PRIVATE application +target_link_libraries(${API_LIB} + PUBLIC ${APPLICATION_LIB} Drogon::Drogon - JsonCpp::JsonCpp ) \ No newline at end of file diff --git a/src/application/CMakeLists.txt b/src/application/CMakeLists.txt index 946ed0f..c4f1c1c 100644 --- a/src/application/CMakeLists.txt +++ b/src/application/CMakeLists.txt @@ -3,16 +3,15 @@ file(GLOB_RECURSE application_sources ${CMAKE_CURRENT_SOURCE_DIR}/odb-gen/*.cxx ) -add_library(application STATIC ${application_sources}) +add_library(${APPLICATION_LIB} STATIC ${application_sources}) -target_include_directories(application PUBLIC +target_include_directories(${APPLICATION_LIB} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/odb-gen ) -target_link_libraries(application - PUBLIC core - PRIVATE libodb::libodb - libodb-pgsql::libodb-pgsql +target_link_libraries(${APPLICATION_LIB} + PUBLIC ${CORE_LIB} JsonCpp::JsonCpp + PRIVATE libodb-pgsql::libodb-pgsql ) \ No newline at end of file diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f016b90..cba6b5c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,6 +1,6 @@ -add_library(core INTERFACE) -target_include_directories(core INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +add_library(${CORE_LIB} INTERFACE) +target_include_directories(${CORE_LIB} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(core +target_link_libraries(${CORE_LIB} INTERFACE libodb::libodb ) \ No newline at end of file From 1381d9e09f0b9991f9915027f4ac58e3e9b329da Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Fri, 27 Mar 2026 11:14:11 +0500 Subject: [PATCH 09/13] chore: #55: complete merge conflicts --- src/api/features/todos/todos-controller.cpp | 28 +++++++++---------- src/api/features/todos/todos-controller.h | 31 +++++++++++---------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/api/features/todos/todos-controller.cpp b/src/api/features/todos/todos-controller.cpp index 1200959..c326b25 100644 --- a/src/api/features/todos/todos-controller.cpp +++ b/src/api/features/todos/todos-controller.cpp @@ -5,9 +5,9 @@ HttpResponsePtr ToDosController::createInternalServerErrorResponse(const std::string& error) const { Json::Value jsonResponse; - jsonResponse["status"] = "Error"; + jsonResponse["status"] = "Error"; jsonResponse["message"] = "Internal server error"; - jsonResponse["error"] = error; + jsonResponse["error"] = error; auto resp = HttpResponse::newHttpJsonResponse(jsonResponse); resp->setStatusCode(k500InternalServerError); @@ -18,15 +18,15 @@ ToDosController::ToDosController() { db_ = std::move(DbConnection::get()); - createToDoCommand_ = std::make_unique(*db_); - getAllToDosQuery_ = std::make_unique(*db_); - getToDoByIdQuery_ = std::make_unique(*db_); + createToDoCommand_ = std::make_unique(*db_); + getAllToDosQuery_ = std::make_unique(*db_); + getToDoByIdQuery_ = std::make_unique(*db_); hardDeleteToDoCommand_ = std::make_unique(*db_); - softDeleteCommand_ = std::make_unique(*db_); + softDeleteCommand_ = std::make_unique(*db_); - createToDoHandler_ = std::make_unique(*createToDoCommand_); - getAllToDosHandler_ = std::make_unique(*getAllToDosQuery_); - getToDoByIdHandler_ = std::make_unique(*getToDoByIdQuery_); + createToDoHandler_ = std::make_unique(*createToDoCommand_); + getAllToDosHandler_ = std::make_unique(*getAllToDosQuery_); + getToDoByIdHandler_ = std::make_unique(*getToDoByIdQuery_); hardDeleteToDoHandler_ = std::make_unique(*hardDeleteToDoCommand_); softDeleteToDoHandler_ = std::make_unique(*softDeleteCommand_); } @@ -63,7 +63,7 @@ void ToDosController::addToDo(const HttpRequestPtr& req, std::functionisMember("name")) { Json::Value result; - result["status"] = "error"; + result["status"] = "error"; result["message"] = "Invalid JSON"; auto resp = HttpResponse::newHttpJsonResponse(result); @@ -72,7 +72,7 @@ void ToDosController::addToDo(const HttpRequestPtr& req, std::functionget("name", "").asString() }; + CreateToDoRequest request { json->get("name", "").asString() }; (void) createToDoHandler_->handle(request); auto resp = HttpResponse::newHttpResponse(); @@ -91,10 +91,10 @@ void ToDosController::completeToDos(const HttpRequestPtr& req, std::functiongetJsonObject(); - if (!json || !json->isMember("toDosIds")) + if (!json || !json->isMember("toDoIds")) { Json::Value result; - result["status"] = "error"; + result["status"] = "error"; result["message"] = "Invalid JSON"; auto resp = HttpResponse::newHttpJsonResponse(result); @@ -103,7 +103,7 @@ void ToDosController::completeToDos(const HttpRequestPtr& req, std::functionget("toDosIds", Json::Value(Json::arrayValue)); + auto toDosIds = json->get("toDoIds", Json::Value(Json::arrayValue)); for (const auto& id : toDosIds) { // TODO(https://github.com/TourmalineCore/to-dos-api-cpp/issues/38): add here a check for not found todos diff --git a/src/api/features/todos/todos-controller.h b/src/api/features/todos/todos-controller.h index 408c5a7..aa1496f 100644 --- a/src/api/features/todos/todos-controller.h +++ b/src/api/features/todos/todos-controller.h @@ -16,31 +16,34 @@ class ToDosController : public drogon::HttpController explicit ToDosController(); METHOD_LIST_BEGIN - ADD_METHOD_TO(ToDosController::getToDos, "/to-dos", Get); - ADD_METHOD_TO(ToDosController::addToDo, "/to-dos", Post); - ADD_METHOD_TO(ToDosController::completeToDos, "/to-dos/complete", Post); - ADD_METHOD_TO(ToDosController::deleteToDo, "/to-dos", Delete); + ADD_METHOD_TO(AppController::getToDos, "/api/to-dos", Get); // Getting a list of tasks + ADD_METHOD_TO(AppController::addToDo, "/api/to-dos", Post); // Adding a new task + ADD_METHOD_TO(AppController::completeToDos, "/api/to-dos/complete", Post); // Executing (deleting) a task list + ADD_METHOD_TO(AppController::deleteToDo, "/api/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 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 deleteToDo(const HttpRequestPtr& req, std::function&& callback); private: std::shared_ptr db_; - std::unique_ptr createToDoCommand_; - std::unique_ptr getAllToDosQuery_; - std::unique_ptr getToDoByIdQuery_; + std::unique_ptr createToDoCommand_; + std::unique_ptr getAllToDosQuery_; + std::unique_ptr getToDoByIdQuery_; std::unique_ptr hardDeleteToDoCommand_; - std::unique_ptr softDeleteCommand_; + std::unique_ptr softDeleteCommand_; - std::unique_ptr createToDoHandler_; - std::unique_ptr getAllToDosHandler_; - std::unique_ptr getToDoByIdHandler_; + std::unique_ptr createToDoHandler_; + std::unique_ptr getAllToDosHandler_; + std::unique_ptr getToDoByIdHandler_; std::unique_ptr hardDeleteToDoHandler_; std::unique_ptr softDeleteToDoHandler_; }; \ No newline at end of file From bcdebe5a01bac6fc0692998f7cd05b3c9b7cec06 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Fri, 27 Mar 2026 11:16:42 +0500 Subject: [PATCH 10/13] style: #55: run auto-format --- .../create-to-do/create-to-do-handler.cpp | 4 +-- .../create-to-do/create-to-do-handler.h | 4 ++- .../create-to-do/create-to-do-request.h | 3 +- .../create-to-do/create-to-do-response.h | 3 +- .../get-all-to-dos/get-all-to-dos-handler.cpp | 2 +- .../get-all-to-dos/get-all-to-dos-handler.h | 4 ++- .../get-all-to-dos/get-all-to-dos-response.h | 3 +- .../get-to-do-by-id-handler.cpp | 2 +- .../get-to-do-by-id/get-to-do-by-id-handler.h | 4 ++- .../get-to-do-by-id-response.h | 3 +- .../hard-delete-to-do-handler.h | 4 ++- .../soft-delete-to-do-command.cpp | 6 ++-- .../soft-delete-to-do-handler.h | 4 ++- .../soft-delete-to-do-response.h | 3 +- src/application/shared-dtos/to-do-dtos.cpp | 2 +- src/application/shared-dtos/to-do-dtos.h | 3 +- src/utils/app-config/app-config.cpp | 36 ++++++++++++++----- 17 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/application/features/create-to-do/create-to-do-handler.cpp b/src/application/features/create-to-do/create-to-do-handler.cpp index f2c7daf..cce9291 100644 --- a/src/application/features/create-to-do/create-to-do-handler.cpp +++ b/src/application/features/create-to-do/create-to-do-handler.cpp @@ -6,9 +6,7 @@ using std::string; CreateToDoResponse CreateToDoHandler::handle(const CreateToDoRequest& request) { - const std::time_t now_utc = std::chrono::system_clock::to_time_t( - std::chrono::system_clock::now() - ); + const std::time_t now_utc = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); const uint64_t id = _createToDoCommand.execute(request.name, now_utc); return { id }; diff --git a/src/application/features/create-to-do/create-to-do-handler.h b/src/application/features/create-to-do/create-to-do-handler.h index 0755a9b..0bd5a3f 100644 --- a/src/application/features/create-to-do/create-to-do-handler.h +++ b/src/application/features/create-to-do/create-to-do-handler.h @@ -3,9 +3,11 @@ #include "create-to-do-request.h" #include "create-to-do-response.h" -class CreateToDoHandler { +class CreateToDoHandler +{ private: CreateToDoCommand& _createToDoCommand; + public: explicit CreateToDoHandler(CreateToDoCommand& createToDoCommand) : _createToDoCommand(createToDoCommand) diff --git a/src/application/features/create-to-do/create-to-do-request.h b/src/application/features/create-to-do/create-to-do-request.h index c56bff1..21aec14 100644 --- a/src/application/features/create-to-do/create-to-do-request.h +++ b/src/application/features/create-to-do/create-to-do-request.h @@ -1,6 +1,7 @@ #pragma once #include -struct CreateToDoRequest { +struct CreateToDoRequest +{ std::string name; }; \ No newline at end of file diff --git a/src/application/features/create-to-do/create-to-do-response.h b/src/application/features/create-to-do/create-to-do-response.h index c69c0bf..75078b6 100644 --- a/src/application/features/create-to-do/create-to-do-response.h +++ b/src/application/features/create-to-do/create-to-do-response.h @@ -1,6 +1,7 @@ #pragma once #include -struct CreateToDoResponse { +struct CreateToDoResponse +{ uint64_t id; }; \ No newline at end of file diff --git a/src/application/features/get-all-to-dos/get-all-to-dos-handler.cpp b/src/application/features/get-all-to-dos/get-all-to-dos-handler.cpp index 61047f4..ab2bed2 100644 --- a/src/application/features/get-all-to-dos/get-all-to-dos-handler.cpp +++ b/src/application/features/get-all-to-dos/get-all-to-dos-handler.cpp @@ -11,7 +11,7 @@ static vector mapToDTOs(const std::shared_ptr>& todos) for (const auto& t : *todos) { ToDoDTO dto; - dto.id = static_cast(t.id()); + dto.id = static_cast(t.id()); dto.name = t.name(); out.push_back(std::move(dto)); } diff --git a/src/application/features/get-all-to-dos/get-all-to-dos-handler.h b/src/application/features/get-all-to-dos/get-all-to-dos-handler.h index 5d35f09..cb89db3 100644 --- a/src/application/features/get-all-to-dos/get-all-to-dos-handler.h +++ b/src/application/features/get-all-to-dos/get-all-to-dos-handler.h @@ -2,9 +2,11 @@ #include "get-all-to-dos-query.h" #include "get-all-to-dos-response.h" -class GetAllToDosHandler { +class GetAllToDosHandler +{ private: GetAllToDosQuery& _getAllToDosQuery; + public: explicit GetAllToDosHandler(GetAllToDosQuery& getAllToDosQuery) : _getAllToDosQuery(getAllToDosQuery) diff --git a/src/application/features/get-all-to-dos/get-all-to-dos-response.h b/src/application/features/get-all-to-dos/get-all-to-dos-response.h index 51febd3..db264fa 100644 --- a/src/application/features/get-all-to-dos/get-all-to-dos-response.h +++ b/src/application/features/get-all-to-dos/get-all-to-dos-response.h @@ -2,6 +2,7 @@ #include "../../shared-dtos/to-do-dtos.h" #include -struct GetAllToDosResponse { +struct GetAllToDosResponse +{ std::vector todos; }; \ No newline at end of file diff --git a/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.cpp b/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.cpp index 13bf86b..d4fbe21 100644 --- a/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.cpp +++ b/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.cpp @@ -3,7 +3,7 @@ static ToDoDTO mapToDTO(const ToDo& todo) { ToDoDTO dto; - dto.id = static_cast(todo.id()); + dto.id = static_cast(todo.id()); dto.name = todo.name(); return dto; } diff --git a/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.h b/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.h index ba12c83..2ae3994 100644 --- a/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.h +++ b/src/application/features/get-to-do-by-id/get-to-do-by-id-handler.h @@ -3,9 +3,11 @@ #include "get-to-do-by-id-response.h" #include -class GetToDoByIdHandler { +class GetToDoByIdHandler +{ private: GetToDoById& _getToDoByIdQuery; + public: explicit GetToDoByIdHandler(GetToDoById& getToDoByIdQuery) : _getToDoByIdQuery(getToDoByIdQuery) diff --git a/src/application/features/get-to-do-by-id/get-to-do-by-id-response.h b/src/application/features/get-to-do-by-id/get-to-do-by-id-response.h index 6bf2a3f..753d0da 100644 --- a/src/application/features/get-to-do-by-id/get-to-do-by-id-response.h +++ b/src/application/features/get-to-do-by-id/get-to-do-by-id-response.h @@ -2,6 +2,7 @@ #include "../../shared-dtos/to-do-dtos.h" #include -struct GetToDoByIdResponse { +struct GetToDoByIdResponse +{ std::optional todo; }; \ No newline at end of file diff --git a/src/application/features/hard-delete-to-do/hard-delete-to-do-handler.h b/src/application/features/hard-delete-to-do/hard-delete-to-do-handler.h index 7c494ce..af94b9d 100644 --- a/src/application/features/hard-delete-to-do/hard-delete-to-do-handler.h +++ b/src/application/features/hard-delete-to-do/hard-delete-to-do-handler.h @@ -2,9 +2,11 @@ #include "hard-delete-to-do-command.h" #include -class HardDeleteToDoHandler { +class HardDeleteToDoHandler +{ private: HardDeleteToDoCommand& _hardDeleteToDoCommand; + public: explicit HardDeleteToDoHandler(HardDeleteToDoCommand& hardDeleteToDoCommand) : _hardDeleteToDoCommand(hardDeleteToDoCommand) diff --git a/src/application/features/soft-delete-to-do/soft-delete-to-do-command.cpp b/src/application/features/soft-delete-to-do/soft-delete-to-do-command.cpp index 369750a..2404de4 100644 --- a/src/application/features/soft-delete-to-do/soft-delete-to-do-command.cpp +++ b/src/application/features/soft-delete-to-do/soft-delete-to-do-command.cpp @@ -1,7 +1,7 @@ #include "soft-delete-to-do-command.h" #include "odb-gen/to-do-odb.hxx" -#include #include +#include #include uint64_t SoftDeleteCommand::execute(int id) @@ -9,9 +9,7 @@ uint64_t SoftDeleteCommand::execute(int id) odb::transaction t(db_.begin()); std::unique_ptr todo(db_.load(id)); - const std::time_t now_utc = std::chrono::system_clock::to_time_t( - std::chrono::system_clock::now() - ); + const std::time_t now_utc = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); todo->deletedAtUtc(now_utc); db_.update(*todo); diff --git a/src/application/features/soft-delete-to-do/soft-delete-to-do-handler.h b/src/application/features/soft-delete-to-do/soft-delete-to-do-handler.h index c77c8d5..c64a096 100644 --- a/src/application/features/soft-delete-to-do/soft-delete-to-do-handler.h +++ b/src/application/features/soft-delete-to-do/soft-delete-to-do-handler.h @@ -3,9 +3,11 @@ #include "soft-delete-to-do-response.h" #include -class SoftDeleteToDoHandler { +class SoftDeleteToDoHandler +{ private: SoftDeleteCommand& _softDeleteToDoCommand; + public: explicit SoftDeleteToDoHandler(SoftDeleteCommand& softDeleteToDoCommand) : _softDeleteToDoCommand(softDeleteToDoCommand) diff --git a/src/application/features/soft-delete-to-do/soft-delete-to-do-response.h b/src/application/features/soft-delete-to-do/soft-delete-to-do-response.h index 2839b3f..a76940b 100644 --- a/src/application/features/soft-delete-to-do/soft-delete-to-do-response.h +++ b/src/application/features/soft-delete-to-do/soft-delete-to-do-response.h @@ -1,6 +1,7 @@ #pragma once #include -struct SoftDeleteToDoResponse { +struct SoftDeleteToDoResponse +{ uint64_t id; }; \ No newline at end of file diff --git a/src/application/shared-dtos/to-do-dtos.cpp b/src/application/shared-dtos/to-do-dtos.cpp index 47ef199..d1384ac 100644 --- a/src/application/shared-dtos/to-do-dtos.cpp +++ b/src/application/shared-dtos/to-do-dtos.cpp @@ -3,7 +3,7 @@ Json::Value ToDoDTO::toJson() const { Json::Value json; - json["id"] = id; + json["id"] = id; json["name"] = name; return json; } \ No newline at end of file diff --git a/src/application/shared-dtos/to-do-dtos.h b/src/application/shared-dtos/to-do-dtos.h index d1b714f..b56c07e 100644 --- a/src/application/shared-dtos/to-do-dtos.h +++ b/src/application/shared-dtos/to-do-dtos.h @@ -2,7 +2,8 @@ #include #include -struct ToDoDTO { +struct ToDoDTO +{ int id; std::string name; diff --git a/src/utils/app-config/app-config.cpp b/src/utils/app-config/app-config.cpp index f8c48b0..3699684 100644 --- a/src/utils/app-config/app-config.cpp +++ b/src/utils/app-config/app-config.cpp @@ -134,28 +134,46 @@ void AppConfig::setDatabasePassword(std::string databasePassword) }; const std::string& AppConfig::getApiHost() const -{ return apiHost_; } +{ + return apiHost_; +} const uint32_t& AppConfig::getApiPort() const -{ return apiPort_; } +{ + return apiPort_; +} const uint32_t& AppConfig::getApiNumThreads() const -{ return apiNumThreads_; } +{ + return apiNumThreads_; +} const trantor::Logger::LogLevel AppConfig::getApiLogLevel() -{ return parseLogLevel(getEnv("API_LOG_LEVEL", "INFO")); } +{ + return parseLogLevel(getEnv("API_LOG_LEVEL", "INFO")); +} const std::string& AppConfig::getDatabaseHost() const -{ return databaseHost_; } +{ + return databaseHost_; +} const std::string& AppConfig::getDatabasePort() const -{ return databasePort_; } +{ + return databasePort_; +} const std::string& AppConfig::getDatabaseName() const -{ return databaseName_; } +{ + return databaseName_; +} const std::string& AppConfig::getDatabaseUser() const -{ return databaseUser_; } +{ + return databaseUser_; +} const std::string& AppConfig::getDatabasePassword() const -{ return databasePassword_; } \ No newline at end of file +{ + return databasePassword_; +} \ No newline at end of file From 6ae79161dc064beec52ba9b0084209396054f5f3 Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Fri, 27 Mar 2026 14:15:11 +0500 Subject: [PATCH 11/13] chore: #55: complete merge conflicts with controller --- src/api/features/todos/todos-controller.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/features/todos/todos-controller.h b/src/api/features/todos/todos-controller.h index aa1496f..1557706 100644 --- a/src/api/features/todos/todos-controller.h +++ b/src/api/features/todos/todos-controller.h @@ -16,10 +16,10 @@ class ToDosController : public drogon::HttpController explicit ToDosController(); METHOD_LIST_BEGIN - ADD_METHOD_TO(AppController::getToDos, "/api/to-dos", Get); // Getting a list of tasks - ADD_METHOD_TO(AppController::addToDo, "/api/to-dos", Post); // Adding a new task - ADD_METHOD_TO(AppController::completeToDos, "/api/to-dos/complete", Post); // Executing (deleting) a task list - ADD_METHOD_TO(AppController::deleteToDo, "/api/to-dos", Delete); // Deleting a specific task + ADD_METHOD_TO(ToDosController::getToDos, "/api/to-dos", Get); // Getting a list of tasks + ADD_METHOD_TO(ToDosController::addToDo, "/api/to-dos", Post); // Adding a new task + ADD_METHOD_TO(ToDosController::completeToDos, "/api/to-dos/complete", Post); // Executing (deleting) a task list + ADD_METHOD_TO(ToDosController::deleteToDo, "/api/to-dos", Delete); // Deleting a specific task METHOD_LIST_END HttpResponsePtr createInternalServerErrorResponse(const std::string& error) const; From e0338c42dd2fd5e8daa7c96185c8cc1b6cd3b7bf Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Fri, 27 Mar 2026 14:15:49 +0500 Subject: [PATCH 12/13] fix(config): #55: add app-config to main cmake --- CMakeLists.txt | 11 +++++++++-- src/utils/app-config/app-config.h | 1 - 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf11e4f..cfdd0dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,12 +14,19 @@ add_subdirectory(src/core) add_subdirectory(src/application) add_subdirectory(src/api) -add_executable(${PROJECT_NAME} src/main.cpp) +add_executable(${PROJECT_NAME} + src/main.cpp + src/utils/app-config/app-config.cpp +) + +target_include_directories(${PROJECT_NAME} PRIVATE + src/utils +) + target_link_libraries(${PROJECT_NAME} PRIVATE -Wl,--whole-archive ${API_LIB} -Wl,--no-whole-archive ) - # Tests if (ENV{EXCLUDE_UNIT_TESTS_FROM_BUILD} STREQUAL "true") message(STATUS "UNIT TEST DISABLED") diff --git a/src/utils/app-config/app-config.h b/src/utils/app-config/app-config.h index 86fa4b3..d54d4c7 100644 --- a/src/utils/app-config/app-config.h +++ b/src/utils/app-config/app-config.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include From a215e5f6da040dcbed8271408c98096cb911c7af Mon Sep 17 00:00:00 2001 From: Oleg Kl Date: Fri, 27 Mar 2026 14:16:11 +0500 Subject: [PATCH 13/13] fix(config): #55: add drogon to application cmake due app config dependencies --- src/application/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/application/CMakeLists.txt b/src/application/CMakeLists.txt index c4f1c1c..7bc1f12 100644 --- a/src/application/CMakeLists.txt +++ b/src/application/CMakeLists.txt @@ -13,5 +13,7 @@ target_include_directories(${APPLICATION_LIB} PUBLIC target_link_libraries(${APPLICATION_LIB} PUBLIC ${CORE_LIB} JsonCpp::JsonCpp - PRIVATE libodb-pgsql::libodb-pgsql + PRIVATE libodb::libodb + libodb-pgsql::libodb-pgsql + Drogon::Drogon ) \ No newline at end of file