~aleteoryx/muditaos

853b0787df3a08d401faea35c3f87634029bd6ac — wojtekrzepecki 4 years ago 69965aa
[EGD-8227] Refactor quotes db

refactoring of quotes db layout to conform with
given requirements
A image/user/db/custom_quotes_001.sql => image/user/db/custom_quotes_001.sql +9 -0
@@ 0,0 1,9 @@
-- Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

CREATE TABLE IF NOT EXISTS quote_table(
    quote_id INTEGER PRIMARY KEY,
    quote TEXT NOT NULL,
    author TEXT,
    enabled BOOLEAN NOT NULL DEFAULT TRUE
);

A image/user/db/custom_quotes_002-devel.sql => image/user/db/custom_quotes_002-devel.sql +10 -0
@@ 0,0 1,10 @@
-- Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

INSERT OR REPLACE INTO quote_table (quote_id, quote, author) VALUES
    (1,  'Quote 1', 'Author 1'),
    (2,  'Quote 2', 'Author 2'),
    (3,  'Quote 3', 'Author 3'),
    (4,  'Quote 4', 'Author 4'),
    (5,  'Quote 5', 'Author 5')
;

R image/user/db/quotes_001.sql => image/user/db/predefined_quotes_001.sql +5 -6
@@ 9,19 9,18 @@ CREATE TABLE IF NOT EXISTS quote_languages (

CREATE TABLE IF NOT EXISTS category_table (
    category_id INTEGER NOT NULL,
    lang_id INTEGER NOT NULL,
    category_name TEXT NOT NULL UNIQUE,
    enabled BOOLEAN NOT NULL DEFAULT TRUE,
    PRIMARY KEY (category_id)
    PRIMARY KEY (category_id),
    FOREIGN KEY (lang_id) REFERENCES quote_languages(lang_id)
    );

CREATE TABLE IF NOT EXISTS quote_table (
    quote_id INTEGER NOT NULL,
    lang_id INTEGER NOT NULL,
    quote TEXT NOT NULL,
    author TEXT,
    enabled BOOLEAN NOT NULL DEFAULT TRUE,
    PRIMARY KEY (quote_id),
    FOREIGN KEY (lang_id) REFERENCES quote_languages(lang_id)
    PRIMARY KEY (quote_id)
    );

CREATE TABLE IF NOT EXISTS quote_category_map (


@@ 30,4 29,4 @@ CREATE TABLE IF NOT EXISTS quote_category_map (
    FOREIGN KEY (category_id) REFERENCES category_table(category_id),
    FOREIGN KEY (quote_id) REFERENCES quote_table(quote_id),
    CONSTRAINT quotes_unique UNIQUE(category_id, quote_id)
);
    );

A image/user/db/predefined_quotes_002-devel.sql => image/user/db/predefined_quotes_002-devel.sql +65 -0
@@ 0,0 1,65 @@
-- Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md


-- To add new language please add here string exactly the same as desired display language
INSERT OR REPLACE INTO quote_languages (lang_id, lang_name) VALUES
    (1, 'Polski'),
    (2, 'English'),
    (3, 'Espanol'),
    (4, 'Francais'),
    (5, 'Deutsch');

INSERT OR REPLACE INTO category_table (category_id, lang_id, category_name) VALUES
    (1, 1, 'Kategoria 1'),
    (2, 1, 'Kategoria 2'),
    (3, 1, 'Kategoria 3'),
    (4, 2, 'Category 1'),
    (5, 2, 'Category 2'),
    (6, 2, 'Category 3'),
    (7, 3, 'Categoria 1'),
    (8, 3, 'Categoria 2'),
    (9, 3, 'Categoria 3'),
    (10, 4, 'Catégorie 1'),
    (11, 4, 'Catégorie 2'),
    (12, 4, 'Catégorie 3'),
    (13, 5, 'Kategorie 1'),
    (14, 5, 'Kategorie 2'),
    (15, 5, 'Kategorie 3');

-- To add new quote add here next consecutive key with quote and the author
-- Every quote has to be linked with specific one or many categories in quote_category_map
-- Every category has to be assigned to specific language in category_table
INSERT OR REPLACE INTO quote_table (quote_id, quote, author) VALUES
    (1,  'Quote 1 PL', 'Author 1 PL'),
    (2,  'Quote 2 PL', 'Author 2 PL'),
    (3,  'Quote 3 PL', 'Author 3 PL'),
    (4,  'Quote 4 PL', 'Author 4 PL'),
    (5,  'Quote 5 PL', 'Author 5 PL'),
    (6,  'Quote 1 EN', 'Author 1 EN'),
    (7,  'Quote 2 EN', 'Author 2 EN'),
    (8,  'Quote 3 EN', 'Author 3 EN'),
    (9,  'Quote 4 EN', 'Author 4 EN'),
    (10,  'Quote 5 EN', 'Author 5 EN'),
    (11,  'Quote 1 ES', 'Author 1 ES'),
    (12,  'Quote 2 ES', 'Author 2 ES'),
    (13,  'Quote 3 ES', 'Author 3 ES'),
    (14,  'Quote 4 ES', 'Author 4 ES'),
    (15,  'Quote 5 ES', 'Author 5 ES'),
    (16,  'Quote 1 FR', 'Author 1 FR'),
    (17,  'Quote 2 FR', 'Author 2 FR'),
    (18,  'Quote 3 FR', 'Author 3 FR'),
    (19,  'Quote 4 FR', 'Author 4 FR'),
    (20,  'Quote 5 FR', 'Author 5 FR'),
    (21,  'Quote 1 DE', 'Author 1 DE'),
    (22,  'Quote 2 DE', 'Author 2 DE'),
    (23,  'Quote 3 DE', 'Author 3 DE'),
    (24,  'Quote 4 DE', 'Author 4 DE'),
    (25,  'Quote 5 DE', 'Author 5 DE');
    
INSERT OR REPLACE INTO quote_category_map (category_id, quote_id) VALUES
    (1, 1), (2, 2), (3, 3), (1, 4), (2, 5), (3, 4), (3, 5),
    (4, 6), (5, 7), (6, 8), (4, 9), (5, 10), (6, 9), (6, 10),
    (7, 11), (8, 12), (9, 13), (7, 14), (8, 15), (9, 14), (9, 15),
    (10, 16), (11, 17), (12, 18), (10, 19), (11, 20), (12, 19), (12, 20),
    (13, 21), (14, 22), (15, 23), (13, 24), (14, 25), (15, 24), (15, 25);

D image/user/db/quotes_002.sql => image/user/db/quotes_002.sql +0 -120
@@ 1,120 0,0 @@
-- Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

INSERT OR REPLACE INTO quote_languages (lang_id, lang_name) VALUES
    (1, 'Polish'),
    (2, 'English'),
    (3, 'Spanish'),
    (4, 'French'),
    (5, 'German');


INSERT OR REPLACE INTO category_table (category_id, category_name) VALUES
    (1, 'Category 1'),
    (2, 'Category 2'),
    (3, 'Category 3'),
    (4, 'Category 4'),
    (5, 'Category 5'),
    (6, 'Custom');


INSERT OR REPLACE INTO quote_table (quote_id, lang_id, quote, author) VALUES
    (1,  1, 'Nie żyj przeszłością, nie marz o przyszłości, skoncentruj umysł na chwili obecnej.', 'Buddha'),
    (2,  1, 'Tylko traktując każdą minutę jako niepowtarzalny cud, można naprawdę żyć.', 'Tara Brach'),
    (3,  1, 'Najprostsze rzeczy są często najprawdziwsze.', 'Richard Bach'),
    (4,  1, 'Zawsze mów »tak« chwili obecnej. Mów »tak« życiu.', 'Eckhart Tolle'),
    (5,  1, 'Zwolnij, uprość swoje życie i bądź życzliwy.', 'Naomi Judd'),
    (6,  1, 'Możesz zwolnić. Pozwól swoim marzeniom i celom się zmieniać, ale żyj rozmyślnie.', 'Kumail Nanjiani'),
    (7,  1, 'Myśli zakłócają spokój – medytacja to brak myśli.', 'Ramana Maharshi'),
    (8,  1, 'Miłość jest stanem istnienia. Twoja miłość nie jest na zewnątrz, tylko w głębi ciebie.', 'Eckhart Tolle'),
    (9,  1, 'Bądź świadomy. Bądź wdzięczny. Bądź pozytywny. Bądź prawdziwy. Bądź życzliwy.', 'Roy T. Bennett'),
    (10, 1, 'Najlepszym sposobem uchwytywania chwil jest bycie skupionym. Tak się pielęgnuje uważność.', 'Jon Kabat-Zinn'),
    (11, 1, 'Możemy żyć bez religii i medytacji, ale nie przetrwamy bez ludzkich uczuć.', 'Dalajlama'),
    (12, 1, 'Jesteś niebem. Wszystko inne to tylko pogoda.', 'Pema Chödrön'),
    (13, 1, 'Umiejętność upraszczania oznacza eliminowanie tego, co niepotrzebne, by mogło dojść do głosu to, co niezbędne.', 'Hans Hofmann'),
    (14, 1, 'Im bardziej się skupiasz na czasie – przeszłości i przyszłości – tym więcej tracisz Teraźniejszości, naszej najcenniejszej rzeczy.', 'Eckhart Tolle'),
    (15, 1, 'Aby naprawdę żyć, trzeba to robić w sposób uważny. Bądź tu teraz.', 'Akiroq Brost'),
    (16, 1, 'Wczoraj jest historią. Jutro jest tajemnicą. Dzisiaj jest darem. Nie można się nim cieszyć kiedy indziej.', 'Alice Morse Earle'),
    (17, 1, 'O powodzeniu w życiu można mówić wtedy, gdy chcesz tylko tego, czego naprawdę potrzebujesz.', 'Vernon Howard'),
    (18, 1, 'Wycisz szum, odnajdź prostotę.', 'Albert Einstein'),
    (19, 2, 'Do not dwell in the past, do not dream of the future, concentrate the mind on the present moment.', 'Buddha'),
    (20, 2, 'The only way to live is by accepting each minute as an unrepeatable miracle.', 'Tara Brach'),
    (21, 2, 'The simplest things are often the truest.', 'Richard Bach'),
    (22, 2, 'Always say ‘yes’ to the present moment. Say ‘yes’ to life.', 'Eckhart Tolle'),
    (23, 2, 'Slow down, simplify and be kind.', 'Naomi Judd'),
    (24, 2, 'You can go slow. Allow your dreams and goals to change, but live an intentional life.', 'Kumail Nanjiani'),
    (25, 2, 'When there are thoughts, it is distraction: when there are no thoughts, it is meditation.', 'Ramana Maharshi'),
    (26, 2, 'Love is a state of Being. Your love is not outside; it is deep within you.', 'Eckhart Tolle'),
    (27, 2, 'Be mindful. Be grateful. Be positive. Be true. Be kind.', 'Roy T. Bennett'),
    (28, 2, 'The best way to capture moments is to pay attention. This is how we cultivate mindfulness.', 'Jon Kabat-Zinn'),
    (29, 2, 'We can live without religion and meditation, but we cannot survive without human affection.', 'Dalajlama'),
    (30, 2, 'You are the sky. Everything else is just the weather.', 'Pema Chödrön'),
    (31, 2, 'The ability to simplify means to eliminate the unnecessary so that the necessary may speak.', 'Hans Hofmann'),
    (32, 2, 'The more you are focused on time — past and future — the more you miss the Now, the most precious thing there is.', 'Eckhart Tolle'),
    (33, 2, 'Your life requires your mindful presence in order to live it. Be here now.', 'Akiroq Brost'),
    (34, 2, 'Yesterday is history. Tomorrow is a mystery. Today is a gift. That is why it is called the present.', 'Alice Morse Earle'),
    (35, 2, 'You have succeeded in life when all you really want is only what you really need.', 'Vernon Howard'),
    (36, 2, 'Out of clutter, find simplicity.', 'Albert Einstein'),
    (37, 3, 'No habites en el pasado, no sueñes con el futuro, concéntrate en el momento presente.', 'Buddha'),
    (38, 3, 'La única manera de vivir es aceptar cada minuto como un milagro irrepetible.', 'Tara Brach'),
    (39, 3, 'Las cosas más simples son a menudo las más reales.', 'Richard Bach'),
    (40, 3, 'Di siempre "sí" al momento presente. Di "sí" a la vida.', 'Eckhart Tolle'),
    (41, 4, 'Ne t''attarde pas sur le passé, ne rêve pas de l''avenir, concentre-toi sur le moment présent.', 'Buddha'),
    (42, 4, 'La seule façon de vivre est d''accepter chaque minute comme un miracle non répétable.', 'Tara Brach'),
    (43, 4, 'Les choses les plus simples sont souvent les plus vraies.', 'Richard Bach'),
    (44, 4, 'Dites toujours “oui” au moment présent. Dites “oui” à la vie.', 'Eckhart Tolle'),
    (45, 5, 'Bleibe nicht in der Vergangenheit stecken und träume nicht von der Zukunft. Konzentriere dich auf das Jetzt.', 'Buddha'),
    (46, 5, 'Die einzige Art, zu leben, besteht darin, jede Minute als unwiederbringliches Wunder anzusehen.', 'Tara Brach'),
    (47, 5, 'Die einfachsten Dinge sind manchmal die echtesten', 'Richard Bach'),
    (48, 5, 'Sage stets ‚ja‘ zum Jetzt.Sage ‚ja‘ zum Leben.', 'Eckhart Tolle');


INSERT OR REPLACE INTO quote_category_map (category_id, quote_id) VALUES
    (1, 1),
    (1, 19),
    (1, 37),
    (1, 41),
    (1, 45),
    (2, 2),
    (2, 20),
    (2, 38),
    (2, 42),
    (2, 46),
    (3, 3),
    (3, 21),
    (3, 39),
    (3, 43),
    (3, 47),
    (4, 4),
    (4, 22),
    (4, 40),
    (4, 44),
    (4, 48),
    (5, 5),
    (5, 23),
    (6, 6),
    (6, 24),
    (1, 7),
    (1, 25),
    (2, 8),
    (2, 26),
    (3, 9),
    (3, 27),
    (4, 10),
    (4, 28),
    (5, 11),
    (5, 29),
    (6, 12),
    (6, 30),
    (1, 13),
    (1, 31),
    (2, 14),
    (2, 32),
    (3, 15),
    (3, 33),
    (4, 16),
    (4, 34),
    (5, 17),
    (5, 35),
    (6, 18),
    (6, 36);

M module-apps/application-settings/models/display-keypad/CategoriesModel.cpp => module-apps/application-settings/models/display-keypad/CategoriesModel.cpp +1 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CategoriesModel.hpp"

M module-apps/application-settings/models/display-keypad/QuotesModel.cpp => module-apps/application-settings/models/display-keypad/QuotesModel.cpp +4 -9
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesModel.hpp"


@@ 104,26 104,21 @@ namespace Quotes

    void QuotesModel::add(const Quotes::QuoteRecord &record)
    {
        LOG_DEBUG("Adding quote: lang_id = %u", static_cast<unsigned>(record.lang_id));
        auto query = std::make_unique<Messages::AddQuoteRequest>(record.lang_id, record.quote, record.author, true);
        auto query = std::make_unique<Messages::AddQuoteRequest>(record.quote, record.author, true);
        auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
        task->execute(app, this);
    }

    void QuotesModel::edit(const Quotes::QuoteRecord &record)
    {
        LOG_DEBUG("Saving quote: lang_id = %u", static_cast<unsigned>(record.lang_id));

        auto query = std::make_unique<Messages::WriteQuoteRequest>(
            record.quote_id, record.lang_id, record.quote, record.author, record.enabled);
        auto query =
            std::make_unique<Messages::WriteQuoteRequest>(record.quote_id, record.quote, record.author, record.enabled);
        auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
        task->execute(app, this);
    }

    void QuotesModel::remove(const Quotes::QuoteRecord &record)
    {
        LOG_DEBUG("Removing quote: lang_id = %u", static_cast<unsigned>(record.lang_id));

        auto query = std::make_unique<Messages::DeleteQuoteRequest>(record.quote_id);
        auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
        task->execute(app, this);

M module-services/service-appmgr/model/ApplicationManagerCommon.cpp => module-services/service-appmgr/model/ApplicationManagerCommon.cpp +2 -0
@@ 25,6 25,7 @@
#include <service-eink/ServiceEink.hpp>
#include <service-evtmgr/EventManagerCommon.hpp>
#include <AppWindowConstants.hpp>
#include <service-db/DBServiceAPI.hpp>

#include <algorithm>
#include <utility>


@@ 664,6 665,7 @@ namespace app::manager
        settings->setValue(
            settings::SystemProperties::displayLanguage, requestedLanguage, settings::SettingsScope::Global);
        rebuildActiveApplications();
        DBServiceAPI::InformLanguageChanged(this);
        return true;
    }


M module-services/service-db/DBServiceAPI.cpp => module-services/service-db/DBServiceAPI.cpp +8 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "service-db/DBServiceAPI.hpp"


@@ 6,6 6,7 @@
#include "service-db/DBCalllogMessage.hpp"
#include "service-db/DBServiceMessage.hpp"
#include "service-db/QueryMessage.hpp"
#include "service-db/QuotesMessages.hpp"
#include "service-db/DBServiceName.hpp"

#include <CalllogRecord.hpp>


@@ 287,3 288,9 @@ bool DBServiceAPI::AddSMS(sys::Service *serv, const SMSRecord &record, std::uniq
    const auto [succeed, _] = DBServiceAPI::GetQuery(serv, db::Interface::Name::SMS, std::move(query));
    return succeed;
}

void DBServiceAPI::InformLanguageChanged(sys::Service *serv)
{
    auto query = std::make_unique<Quotes::Messages::InformLanguageChangeRequest>();
    DBServiceAPI::GetQuery(serv, db::Interface::Name::Quotes, std::move(query));
}

M module-services/service-db/agents/quotes/QuotesAgent.cpp => module-services/service-db/agents/quotes/QuotesAgent.cpp +44 -79
@@ 9,8 9,11 @@

namespace Quotes
{
    QuotesAgent::QuotesAgent(Database *quotesDB, std::unique_ptr<settings::Settings> settings)
        : database(quotesDB), randomizedQuoteModel(std::move(settings), quotesDB)
    QuotesAgent::QuotesAgent(Database *predefinedQuotesDB,
                             Database *customQuotesDB,
                             std::unique_ptr<settings::Settings> settings)
        : predefinedDB(predefinedQuotesDB), customDB(customQuotesDB),
          randomizedQuoteModel(std::move(settings), predefinedQuotesDB, customQuotesDB)
    {
        constexpr auto forced = false;
        randomizedQuoteModel.updateList(forced);


@@ 24,30 27,22 @@ namespace Quotes
        else if (typeid(*query) == typeid(Messages::EnableCategoryByIdRequest)) {
            return handleEnableCategoryById(query);
        }
        else if (typeid(*query) == typeid(Messages::GetQuotesListRequest)) {
            return handleQuotesList(query);
        }
        else if (typeid(*query) == typeid(Messages::GetQuotesListByCategoryIdRequest)) {
            return handleQuotesListByCategoryId(query);
        }
        else if (typeid(*query) == typeid(Messages::GetQuotesListFromCustomCategoryRequest)) {
            return handleQuotesListFromCustomCategory(query);
        }
        else if (typeid(*query) == typeid(Messages::GetEnabledQuotesListRequest)) {
            return handleEnabledQuotesList(query);
        }
        else if (typeid(*query) == typeid(Messages::EnableQuoteByIdRequest)) {
            return handleEnableQuoteById(query);
        }
        else if (typeid(*query) == typeid(Messages::AddQuoteRequest)) {
            return handleAddQuote(query);
        }
        else if (typeid(*query) == typeid(Messages::ReadQuoteRequest)) {
            return handleReadQuote(query);
        }
        else if (typeid(*query) == typeid(Messages::ReadRandomizedQuoteRequest)) {
            return handleReadRandomizedQuote(query);
        }
        else if (typeid(*query) == typeid(Messages::InformLanguageChangeRequest)) {
            handleInformLanguageChange();
            return nullptr;
        }
        else if (typeid(*query) == typeid(Messages::WriteQuoteRequest)) {
            return handleWriteQuote(query);
        }


@@ 60,7 55,7 @@ namespace Quotes
    auto QuotesAgent::handleCategoryList(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req          = std::dynamic_pointer_cast<Messages::GetCategoryListRequest>(query);
        auto queryResult  = database->query(Queries::getAllCategories);
        auto queryResult  = predefinedDB->query(Queries::getAllCategories, utils::getDisplayLanguage().c_str());
        auto categoryList = getList<CategoryList, CategoryRecord>(req->offset, req->limit, std::move(queryResult));
        auto response     = std::make_unique<Messages::GetCategoryListResponse>(std::move(categoryList));
        response->setRequestQuery(query);


@@ 70,7 65,7 @@ namespace Quotes
    auto QuotesAgent::handleEnableCategoryById(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req         = std::dynamic_pointer_cast<Messages::EnableCategoryByIdRequest>(query);
        auto queryResult = database->execute(Queries::enableCategory, req->enable, req->categoryId);
        auto queryResult      = predefinedDB->execute(Queries::enableCategory, req->enable, req->categoryId);
        auto response    = std::make_unique<Messages::EnableCategoryByIdResponse>(std::move(queryResult));
        constexpr auto forced = true;
        randomizedQuoteModel.updateList(forced);


@@ 78,51 73,21 @@ namespace Quotes
        return response;
    }

    auto QuotesAgent::handleQuotesList(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req         = std::dynamic_pointer_cast<Messages::GetQuotesListRequest>(query);
        auto queryResult = database->query(Queries::getAllQuotes);
        auto quotesList  = getList<QuotesList, QuoteRecord>(req->offset, req->limit, std::move(queryResult));
        auto response    = std::make_unique<Messages::GetQuotesListResponse>(std::move(quotesList));
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleQuotesListByCategoryId(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req         = std::dynamic_pointer_cast<Messages::GetQuotesListByCategoryIdRequest>(query);
        auto queryResult = database->query(Queries::getQuotesByCategoryId, req->categoryId);
        auto quotesList  = getList<QuotesList, QuoteRecord>(req->offset, req->limit, std::move(queryResult));
        auto response    = std::make_unique<Messages::GetQuotesListByCategoryIdResponse>(std::move(quotesList));
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleQuotesListFromCustomCategory(std::shared_ptr<db::Query> query)
        -> std::unique_ptr<db::QueryResult>
    {
        auto req         = std::dynamic_pointer_cast<Messages::GetQuotesListFromCustomCategoryRequest>(query);
        auto queryResult = database->query(Queries::getQuotesFromCustomCategory);
        auto queryResult = customDB->query(Queries::getAllCustomQuotes);
        auto quotesList  = getList<QuotesList, QuoteRecord>(req->offset, req->limit, std::move(queryResult));
        auto response    = std::make_unique<Messages::GetQuotesListFromCustomCategoryResponse>(std::move(quotesList));
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleEnabledQuotesList(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req         = std::dynamic_pointer_cast<Messages::GetEnabledQuotesListRequest>(query);
        auto queryResult = database->query(Queries::getEnabledQuotes);
        auto quotesList  = getList<QuotesList, QuoteRecord>(req->offset, req->limit, std::move(queryResult));
        auto response    = std::make_unique<Messages::GetEnabledQuotesListResponse>(std::move(quotesList));
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleEnableQuoteById(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req         = std::dynamic_pointer_cast<Messages::EnableQuoteByIdRequest>(query);
        auto queryResult = database->execute(Queries::enableQuote, req->enable, req->quoteId);
        auto queryResult      = customDB->execute(Queries::enableQuote, req->enable, req->quoteId);
        auto response    = std::make_unique<Messages::EnableQuoteByIdResponse>(queryResult);
        constexpr auto forced = true;
        randomizedQuoteModel.updateList(forced);


@@ 134,54 99,55 @@ namespace Quotes
    {
        auto req = std::dynamic_pointer_cast<Messages::AddQuoteRequest>(query);

        database->execute(
            Queries::addQuoteToQuoteTable, req->langId, req->quote.c_str(), req->author.c_str(), req->enabled);

        auto quoteId = database->getLastInsertRowId();

        auto queryResult = database->query(Queries::getCustomCategoryId);
        CategoryRecord categoryRecord(queryResult.get());
        auto success = customDB->execute(Queries::addQuote, req->quote.c_str(), req->author.c_str(), req->enabled);

        auto success = database->execute(Queries::addQuoteToQuoteCategoryMapTable, categoryRecord.category_id, quoteId);
        if (success) {
            constexpr auto forced = true;
            randomizedQuoteModel.updateList(forced);
        }

        auto quoteId  = customDB->getLastInsertRowId();
        auto response = std::make_unique<Messages::AddQuoteResponse>(success, quoteId);
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleReadQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    auto QuotesAgent::handleReadRandomizedQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req         = std::dynamic_pointer_cast<Messages::ReadQuoteRequest>(query);
        auto queryResult = database->query(Queries::readQuote, req->quoteId);
        QuoteRecord quoteRecord(queryResult.get());
        auto response = std::make_unique<Messages::ReadQuoteResponse>(
            quoteRecord.quote_id, quoteRecord.lang_id, quoteRecord.quote, quoteRecord.author, quoteRecord.enabled);
        response->setRequestQuery(query);
        return response;
        auto req                  = std::dynamic_pointer_cast<Messages::ReadRandomizedQuoteRequest>(query);
        auto [type, randomizedId] = randomizedQuoteModel.getId();
        LOG_DEBUG("Randomized id: %d, type: %d", randomizedId, static_cast<int>(type));
        std::unique_ptr<QueryResult> queryResult;

        if (type == QuoteType::Predefined) {
            queryResult = predefinedDB->query(Queries::readPredefinedQuote, randomizedId);
        }
        else {
            queryResult = customDB->query(Queries::readCustomQuote, randomizedId);
        }

        if (queryResult->getRowCount()) {
            QuoteRecord quoteRecord(queryResult.get());
            auto response = std::make_unique<Messages::ReadRandomizedQuoteResponse>(
                quoteRecord.quote_id, quoteRecord.quote, quoteRecord.author);
            response->setRequestQuery(query);
            return response;
        }
        else {
            return nullptr;
        }
    }

    auto QuotesAgent::handleReadRandomizedQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    void QuotesAgent::handleInformLanguageChange()
    {
        auto req          = std::dynamic_pointer_cast<Messages::ReadRandomizedQuoteRequest>(query);
        auto randomizedId = randomizedQuoteModel.getId();
        LOG_DEBUG("Randomized id: %d", randomizedId);
        auto queryResult = database->query(Queries::readQuote, randomizedId);
        QuoteRecord quoteRecord(queryResult.get());
        auto response = std::make_unique<Messages::ReadRandomizedQuoteResponse>(
            quoteRecord.quote_id, quoteRecord.lang_id, quoteRecord.quote, quoteRecord.author, quoteRecord.enabled);
        response->setRequestQuery(query);
        return response;
        constexpr auto forced = true;
        randomizedQuoteModel.updateList(forced);
    }

    auto QuotesAgent::handleWriteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req         = std::dynamic_pointer_cast<Messages::WriteQuoteRequest>(query);
        auto queryResult = database->execute(
            Queries::writeQuote, req->langId, req->quote.c_str(), req->author.c_str(), req->enabled, req->quoteId);
        auto queryResult =
            customDB->execute(Queries::writeQuote, req->quote.c_str(), req->author.c_str(), req->enabled, req->quoteId);
        constexpr auto forced = true;
        randomizedQuoteModel.updateList(forced);
        auto response = std::make_unique<Messages::WriteQuoteResponse>(queryResult);


@@ 192,8 158,7 @@ namespace Quotes
    auto QuotesAgent::handleDeleteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req = std::dynamic_pointer_cast<Messages::DeleteQuoteRequest>(query);
        database->execute(Queries::deleteQuoteFromQuoteCategoryMapTable, req->quoteId);
        auto queryResult = database->execute(Queries::deleteQuoteFromQuoteTable, req->quoteId);
        auto queryResult      = customDB->execute(Queries::deleteQuote, req->quoteId);
        constexpr auto forced = true;
        randomizedQuoteModel.updateList(forced);
        auto response = std::make_unique<Messages::DeleteQuoteResponse>(queryResult);

M module-services/service-db/agents/quotes/QuotesAgent.hpp => module-services/service-db/agents/quotes/QuotesAgent.hpp +6 -6
@@ 16,7 16,9 @@ namespace Quotes
    class QuotesAgent : public RecordInterface<QuoteRecord, QuotesRecordField>
    {
      public:
        QuotesAgent(Database *quotesDB, std::unique_ptr<settings::Settings> settings);
        QuotesAgent(Database *predefinedQuotesDB,
                    Database *customQuotesDB,
                    std::unique_ptr<settings::Settings> settings);
        ~QuotesAgent() = default;

        auto runQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;


@@ 24,19 26,17 @@ namespace Quotes
      protected:
        auto handleCategoryList(std::shared_ptr<db::Query> msg) -> std::unique_ptr<db::QueryResult>;
        auto handleEnableCategoryById(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleQuotesList(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleQuotesListByCategoryId(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleQuotesListFromCustomCategory(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleEnabledQuotesList(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleEnableQuoteById(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleAddQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleReadQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleReadRandomizedQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleInformLanguageChange() -> void;
        auto handleWriteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleDeleteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;

      private:
        Database *database;
        Database *predefinedDB;
        Database *customDB;
        RandomizedQuoteModel randomizedQuoteModel;
    };
} // namespace Quotes

M module-services/service-db/agents/quotes/QuotesQueries.hpp => module-services/service-db/agents/quotes/QuotesQueries.hpp +50 -63
@@ 1,58 1,42 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

namespace Quotes::Queries
{
    constexpr auto getAllCategories = R"sql(
                        SELECT category_id, category_name, enabled
                        FROM category_table;
                        )sql";

    constexpr auto getAllQuotes = R"sql(
                        SELECT quote_id, lang_id, quote, author, enabled
                        FROM quote_table;
                        )sql";
    /// To predefined quotes table

    constexpr auto getQuotesByCategoryId = R"sql(
                        SELECT QT.quote_id, QT.lang_id, QT.quote, QT.author, QT.enabled
    constexpr auto getAllCategories = R"sql(
                        SELECT CT.category_id, CT.category_name, CT.enabled
                        FROM 
                            quote_table as QT,
                            quote_category_map as QCM,
                            category_table as CT
                            category_table as CT,
                            quote_languages as QL
                        WHERE
                            QCM.quote_id = QT.quote_id
                            and 
                            QCM.category_id = CT.category_id
                            and
                            QCM.category_id = '%lu'
                            QL.lang_name = '%q'
                            and
                            CT.enabled = TRUE
                            CT.lang_id = QL.lang_id 
                        )sql";

    constexpr auto getQuotesFromCustomCategory = R"sql(
                        SELECT QT.quote_id, QT.lang_id, QT.quote, QT.author, QT.enabled
    constexpr auto getEnabledPredefinedQuotes = R"sql(
                        SELECT QT.quote_id, QT.quote, QT.author, CT.enabled
                        FROM
                            quote_table as QT,
                            quote_category_map as QCM,
                            category_table as CT
                            category_table as CT,
                            quote_languages as QL
                        WHERE
                            QCM.quote_id = QT.quote_id
                            and 
                            QCM.category_id = CT.category_id
                            QL.lang_name = '%q'
                            and
                            CT.category_name = 'Custom'
                            CT.lang_id = QL.lang_id
                            and
                            CT.enabled = TRUE
                        )sql";

    constexpr auto getCustomCategoryId = R"sql(
                        SELECT category_id, category_name, enabled
                        FROM
                            category_table
                        WHERE
                            category_name = 'Custom'
                            and 
                            QCM.category_id = CT.category_id
                            and
                            QCM.quote_id = QT.quote_id
                        GROUP BY
                            QCM.quote_id   
                        )sql";

    constexpr auto enableCategory = R"sql(


@@ 60,55 44,58 @@ namespace Quotes::Queries
                        WHERE category_id = '%lu';
                        )sql";

    constexpr auto enableQuote = R"sql(
                        UPDATE quote_table SET enabled = '%d'
                        WHERE quote_id = '%lu';
                        )sql";

    constexpr auto getEnabledQuotes = R"sql(
                        SELECT QT.quote_id, QT.lang_id, QT.quote, QT.author, QT.enabled
    constexpr auto readPredefinedQuote = R"sql(
                        SELECT QT.quote_id, QT.quote, QT.author, CT.enabled
                        FROM
                            quote_table as QT,
                            quote_category_map as QCM,
                            category_table as CT
                        WHERE
                            QCM.quote_id = QT.quote_id
                            QT.quote_id = '%lu'
                            and
                            QCM.quote_id = QT.quote_id
                            and 
                            QCM.category_id = CT.category_id
                            and
                            QT.enabled = TRUE
                            and
                            CT.enabled = TRUE
                        )sql";

    constexpr auto addQuoteToQuoteTable = R"sql(
                        INSERT INTO quote_table (lang_id, quote, author, enabled)
                        VALUES ('%lu', '%q' , '%q', '%d');
    /// To custom quotes database

    constexpr auto getAllCustomQuotes = R"sql(
                    SELECT quote_id, quote, author, enabled
                    FROM quote_table
                    )sql";

    constexpr auto getEnabledCustomQuotes = R"sql(
                        SELECT quote_id, quote, author, enabled
                        FROM
                            quote_table
                        WHERE
                            enabled = TRUE
                        )sql";

    constexpr auto enableQuote = R"sql(
                        UPDATE quote_table SET enabled = '%d'
                        WHERE quote_id = '%lu';
                        )sql";

    constexpr auto addQuoteToQuoteCategoryMapTable = R"sql(
                        INSERT INTO quote_category_map (category_id, quote_id)
                        VALUES ('%lu', '%lu');
    constexpr auto addQuote = R"sql(
                        INSERT INTO quote_table (quote, author, enabled)
                        VALUES ('%q' , '%q', '%d');
                        )sql";

    constexpr auto readQuote = R"sql(
                        SELECT quote_id, lang_id, quote, author, enabled
    constexpr auto readCustomQuote = R"sql(
                        SELECT quote_id, quote, author, enabled
                        FROM quote_table
                        WHERE quote_id = '%lu';
                        )sql";

    constexpr auto writeQuote = R"sql(
                        UPDATE quote_table
                        SET lang_id = '%lu', quote = '%q', author = '%q', enabled = '%d'
                        WHERE quote_id = '%lu';
                        )sql";

    constexpr auto deleteQuoteFromQuoteCategoryMapTable = R"sql(
                        DELETE FROM quote_category_map
                        SET quote = '%q', author = '%q', enabled = '%d'
                        WHERE quote_id = '%lu';
                        )sql";

    constexpr auto deleteQuoteFromQuoteTable = R"sql(
    constexpr auto deleteQuote = R"sql(
                        DELETE FROM quote_table
                        WHERE quote_id = '%lu';
                        )sql";

M module-services/service-db/agents/quotes/QuotesSettingsSerializer.cpp => module-services/service-db/agents/quotes/QuotesSettingsSerializer.cpp +14 -3
@@ 11,8 11,9 @@ namespace Quotes
    auto QuotesSettingsSerializer::serialize(const IdList &list) -> std::string
    {
        std::string output;
        for (const auto &item : list) {
            output.append(utils::to_string(item) + ',');
        for (const auto &[type, id] : list) {
            output.append(utils::to_string(static_cast<int>(type)) + ',');
            output.append(utils::to_string(id) + ',');
        }
        return output;
    }


@@ 20,8 21,18 @@ namespace Quotes
    {
        std::stringstream ss(listString);
        IdList list;
        QuoteID quoteID;
        bool type = true;
        for (int i; ss >> i;) {
            list.push_back(i);
            if (type) {
                quoteID.first = static_cast<QuoteType>(i);
                type          = false;
            }
            else {
                quoteID.second = i;
                list.push_back(quoteID);
                type = true;
            }
            if (ss.peek() == ',' || ss.peek() == ' ') {
                ss.ignore();
            }

M module-services/service-db/agents/quotes/QuotesSettingsSerializer.hpp => module-services/service-db/agents/quotes/QuotesSettingsSerializer.hpp +8 -1
@@ 7,7 7,14 @@

namespace Quotes
{
    using IdList = std::deque<int>;
    enum class QuoteType
    {
        Predefined,
        Custom
    };

    using QuoteID = std::pair<QuoteType, int>;
    using IdList  = std::deque<QuoteID>;

    class QuotesSettingsSerializer
    {

M module-services/service-db/agents/quotes/RandomizedQuoteModel.cpp => module-services/service-db/agents/quotes/RandomizedQuoteModel.cpp +32 -15
@@ 2,8 2,9 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "RandomizedQuoteModel.hpp"
#include "log/log.hpp"
#include "QuotesQueries.hpp"

#include "log/log.hpp"
#include <service-db/agents/settings/SystemSettings.hpp>
#include <algorithm> // std::random_shuffle
#include <memory>


@@ 13,8 14,11 @@ namespace Quotes
{
    constexpr inline auto zeroOffset = 0;
    constexpr inline auto maxLimit   = std::numeric_limits<unsigned int>().max();
    RandomizedQuoteModel::RandomizedQuoteModel(std::unique_ptr<settings::Settings> settings, Database *quotesDB)
        : settings(std::move(settings)), quotesDB(quotesDB), serializer(std::make_unique<QuotesSettingsSerializer>())
    RandomizedQuoteModel::RandomizedQuoteModel(std::unique_ptr<settings::Settings> settings,
                                               Database *predefinedQuotesDB,
                                               Database *customQuotesDB)
        : settings(std::move(settings)), predefinedQuotesDB(predefinedQuotesDB), customQuotesDB(customQuotesDB),
          serializer(std::make_unique<QuotesSettingsSerializer>())
    {}

    void RandomizedQuoteModel::randomize(IdList &list)


@@ 24,15 28,24 @@ namespace Quotes
    }
    void RandomizedQuoteModel::updateList(bool forced)
    {
        auto queryResult = quotesDB->query(Queries::getEnabledQuotes);
        auto quotesList  = getList<QuotesList, QuoteRecord>(zeroOffset, maxLimit, std::move(queryResult));
        populateList(std::move(quotesList), forced);
        auto queryResult      = customQuotesDB->query(Queries::getEnabledCustomQuotes);
        auto customQuotesList = getList<QuotesList, QuoteRecord>(zeroOffset, maxLimit, std::move(queryResult));

        queryResult =
            predefinedQuotesDB->query(Queries::getEnabledPredefinedQuotes, utils::getDisplayLanguage().c_str());
        auto predefinedQuotesList = getList<QuotesList, QuoteRecord>(zeroOffset, maxLimit, std::move(queryResult));
        populateList(std::move(predefinedQuotesList), std::move(customQuotesList), forced);
    }
    void RandomizedQuoteModel::populateList(std::unique_ptr<QuotesList> quotesList, bool forcedUpdate)
    void RandomizedQuoteModel::populateList(std::unique_ptr<QuotesList> predefinedQuotesList,
                                            std::unique_ptr<QuotesList> customQuotesList,
                                            bool forcedUpdate)
    {
        IdList list;
        for (const auto &item : quotesList->data) {
            list.push_back(item.quote_id);
        for (const auto &item : predefinedQuotesList->data) {
            list.push_back({QuoteType::Predefined, item.quote_id});
        }
        for (const auto &item : customQuotesList->data) {
            list.push_back({QuoteType::Custom, item.quote_id});
        }

        randomize(list);


@@ 47,9 60,8 @@ namespace Quotes
        LOG_ERROR("ID queue length: %zu", list.size());
    }

    auto RandomizedQuoteModel::getId() -> int
    auto RandomizedQuoteModel::getId() -> QuoteID
    {

        if (isIdExpired()) {
            shiftIdList();
        }


@@ 67,9 79,14 @@ namespace Quotes
        if (list.empty()) {
            updateList(true);
        }
        settings->setValue(
            settings::Quotes::randomQuotesList, serializer->serialize(list), settings::SettingsScope::Global);
        LOG_DEBUG("Selected quote ID: %d, remaining quotes to next shuffle: %zu", list.front(), list.size());
        else {
            settings->setValue(
                settings::Quotes::randomQuotesList, serializer->serialize(list), settings::SettingsScope::Global);
        }
        LOG_DEBUG("Selected quote ID: %d, type: %d, remaining quotes to next shuffle: %zu",
                  list.front().second,
                  static_cast<int>(list.front().first),
                  list.size());
    }
    auto RandomizedQuoteModel::isIdExpired() -> bool
    {


@@ 89,7 106,7 @@ namespace Quotes
        if ((currentTimestamp - lastTimestamp) >= quotesChangingInterval) {
            lastTimestamp = currentTimestamp;
            settings->setValue(settings::Quotes::randomQuoteIDUpdateTime,
                               utils::to_string(lastTimestamp),
                               ::utils::to_string(lastTimestamp),
                               settings::SettingsScope::Global);
            return true;
        }

M module-services/service-db/agents/quotes/RandomizedQuoteModel.hpp => module-services/service-db/agents/quotes/RandomizedQuoteModel.hpp +9 -4
@@ 19,17 19,22 @@ namespace Quotes
      private:
        app::ApplicationCommon *app = nullptr;
        std::unique_ptr<settings::Settings> settings;
        Database *quotesDB = nullptr;
        void populateList(std::unique_ptr<QuotesList> quotesList, bool forcedUpdate = false);
        Database *predefinedQuotesDB = nullptr;
        Database *customQuotesDB     = nullptr;
        void populateList(std::unique_ptr<QuotesList> predefinedQuotesList,
                          std::unique_ptr<QuotesList> customQuotesList,
                          bool forcedUpdate = false);
        void shiftIdList();
        auto isIdExpired() -> bool;
        void randomize(IdList &list);
        std::unique_ptr<QuotesSettingsSerializer> serializer;

      public:
        RandomizedQuoteModel(std::unique_ptr<settings::Settings> settings, Database *quotesDB);
        RandomizedQuoteModel(std::unique_ptr<settings::Settings> settings,
                             Database *predefinedQuotesDB,
                             Database *customQuotesDB);
        void updateList(bool forced);
        [[nodiscard]] auto getId() -> int;
        [[nodiscard]] auto getId() -> QuoteID;
    };

} // namespace Quotes

M module-services/service-db/include/service-db/DBServiceAPI.hpp => module-services/service-db/include/service-db/DBServiceAPI.hpp +3 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 120,4 120,6 @@ class DBServiceAPI
     * @return true if adding sms operation succeed, otherwise false
     */
    static bool AddSMS(sys::Service *serv, const SMSRecord &record, std::unique_ptr<db::QueryListener> &&listener);

    static void InformLanguageChanged(sys::Service *serv);
};

M module-services/service-db/include/service-db/QuotesMessages.hpp => module-services/service-db/include/service-db/QuotesMessages.hpp +18 -120
@@ 40,7 40,6 @@ namespace Quotes
    struct QuoteRecord
    {
        unsigned int quote_id;
        unsigned int lang_id;
        std::string quote;
        std::string author;
        bool enabled;


@@ 50,10 49,9 @@ namespace Quotes
        explicit QuoteRecord(QueryResult *query)
        {
            quote_id = (*query)[0].getInt32();
            lang_id  = (*query)[1].getInt32();
            quote    = (*query)[2].getString();
            author   = (*query)[3].getString();
            enabled  = (*query)[4].getBool();
            quote    = (*query)[1].getString();
            author   = (*query)[2].getString();
            enabled  = (*query)[3].getBool();
        }
    };



@@ 103,21 101,6 @@ namespace Quotes
            std::unique_ptr<CategoryList> categoryList;
        };

        class GetQuotesListRequest : public db::Query
        {
          public:
            explicit GetQuotesListRequest(unsigned int offset, unsigned int limit)
                : Query(Query::Type::Read), offset(offset), limit(limit)
            {}
            const unsigned int offset;
            const unsigned int limit;

            auto debugInfo() const -> std::string
            {
                return "GetQuotesListRequest";
            }
        };

        class GetQuotesListResponse : public db::QueryResult
        {
          public:


@@ 143,22 126,6 @@ namespace Quotes
            std::unique_ptr<QuotesList> quotesList;
        };

        class GetQuotesListByCategoryIdRequest : public db::Query
        {
          public:
            explicit GetQuotesListByCategoryIdRequest(unsigned int offset, unsigned int limit, unsigned int categoryId)
                : Query(Query::Type::Read), offset(offset), limit(limit), categoryId(categoryId)
            {}
            const unsigned int offset;
            const unsigned int limit;
            const unsigned int categoryId;

            auto debugInfo() const -> std::string
            {
                return "GetQuotesListByCategoryIdRequest";
            }
        };

        class GetQuotesListByCategoryIdResponse : public db::QueryResult
        {
          public:


@@ 281,56 248,12 @@ namespace Quotes
                return "EnableQuoteByIdResponse";
            }
        };

        class GetEnabledQuotesListRequest : public db::Query
        {
          public:
            explicit GetEnabledQuotesListRequest(unsigned int offset, unsigned int limit)
                : Query(Query::Type::Read), offset(offset), limit(limit)
            {}
            const unsigned int offset;
            const unsigned int limit;

            auto debugInfo() const -> std::string
            {
                return "GetEnabledQuotesListRequest";
            }
        };

        class GetEnabledQuotesListResponse : public db::QueryResult
        {
          public:
            explicit GetEnabledQuotesListResponse(std::unique_ptr<QuotesList> quotesList)
                : quotesList(std::move(quotesList))
            {}

            [[nodiscard]] unsigned int getCount() const noexcept
            {
                return quotesList->count;
            }

            [[nodiscard]] auto getResults() const -> std::vector<QuoteRecord>
            {
                return quotesList->data;
            }

            auto debugInfo() const -> std::string
            {
                return "GetEnabledQuotesListResponse";
            }

          private:
            std::unique_ptr<QuotesList> quotesList;
        };

        class AddQuoteRequest : public db::Query
        {
          public:
            explicit AddQuoteRequest(unsigned int langId, std::string quote, std::string author, bool enabled)
                : Query(Query::Type::Create), langId(langId), quote(std::move(quote)), author(std::move(author)),
                  enabled(enabled)
            explicit AddQuoteRequest(std::string quote, std::string author, bool enabled)
                : Query(Query::Type::Create), quote(std::move(quote)), author(std::move(author)), enabled(enabled)
            {}
            const unsigned int langId;
            const std::string quote;
            const std::string author;
            const bool enabled;


@@ 355,79 278,54 @@ namespace Quotes
            }
        };

        class ReadQuoteRequest : public db::Query
        class ReadRandomizedQuoteRequest : public db::Query
        {
          public:
            explicit ReadQuoteRequest(unsigned int quoteId) : Query(Query::Type::Read), quoteId(quoteId)
            ReadRandomizedQuoteRequest() : Query(Query::Type::Read)
            {}
            const unsigned int quoteId;

            auto debugInfo() const -> std::string
            {
                return "ReadQuoteRequest";
                return "ReadRandomizedQuoteRequest";
            }
        };

        class ReadQuoteResponse : public db::QueryResult
        class ReadRandomizedQuoteResponse : public db::QueryResult
        {
          public:
            explicit ReadQuoteResponse(
                unsigned int quoteId, unsigned int langId, std::string quote, std::string author, bool enabled)
                : quoteId(quoteId), langId(langId), quote(std::move(quote)), author(std::move(author)), enabled(enabled)
            explicit ReadRandomizedQuoteResponse(unsigned int quoteId, std::string quote, std::string author)
                : quoteId(quoteId), quote(std::move(quote)), author(std::move(author))
            {}
            const unsigned int quoteId;
            const unsigned int langId;
            const std::string quote;
            const std::string author;
            const bool enabled;

            auto debugInfo() const -> std::string
            {
                return "ReadQuoteResponse";
            }
        };

        class ReadRandomizedQuoteRequest : public db::Query
        {
          public:
            ReadRandomizedQuoteRequest() : Query(Query::Type::Read)
            {}

            auto debugInfo() const -> std::string
            {
                return "ReadRandomizedQuoteRequest";
                return "ReadRandomizedQuoteResponse";
            }
        };

        class ReadRandomizedQuoteResponse : public db::QueryResult
        class InformLanguageChangeRequest : public db::Query
        {
          public:
            explicit ReadRandomizedQuoteResponse(
                unsigned int quoteId, unsigned int langId, std::string quote, std::string author, bool enabled)
                : quoteId(quoteId), langId(langId), quote(std::move(quote)), author(std::move(author)), enabled(enabled)
            InformLanguageChangeRequest() : Query(Query::Type::Read)
            {}
            const unsigned int quoteId;
            const unsigned int langId;
            const std::string quote;
            const std::string author;
            const bool enabled;

            auto debugInfo() const -> std::string
            {
                return "ReadRandomizedQuoteResponse";
                return "InformLanguageChangeRequest";
            }
        };

        class WriteQuoteRequest : public db::Query
        {
          public:
            explicit WriteQuoteRequest(
                unsigned int quoteId, unsigned int langId, std::string quote, std::string author, bool enabled)
                : Query(Query::Type::Update), quoteId(quoteId), langId(langId), quote(std::move(quote)),
                  author(std::move(author)), enabled(enabled)
            explicit WriteQuoteRequest(unsigned int quoteId, std::string quote, std::string author, bool enabled)
                : Query(Query::Type::Update), quoteId(quoteId), quote(std::move(quote)), author(std::move(author)),
                  enabled(enabled)
            {}
            const unsigned int quoteId;
            const unsigned int langId;
            const std::string quote;
            const std::string author;
            const bool enabled;

M module-services/service-db/test/test-service-db-quotes.cpp => module-services/service-db/test/test-service-db-quotes.cpp +56 -74
@@ 5,50 5,33 @@

#include "test-service-db-quotes.hpp"
#include <purefs/filesystem_paths.hpp>
#include <i18n/i18n.hpp>

#include <iostream>

using namespace Quotes;

constexpr auto totalNumOfCategoriesInDb = 6;
constexpr auto totalNumOfQuotesInDb     = 48;
constexpr auto numOfQuotesFromCustomCategory       = 6;
constexpr auto numOfQuotesWithCategoryIdEqualToOne = 9;
constexpr auto totalNumOfCategories          = 3;
constexpr auto totalNumOfQuotes              = 5;
constexpr auto numOfQuotesFromCustomCategory = 5;

TEST_CASE("Quotes")
{
    Database::initialize();
    auto database = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "quotes.db").string().c_str());
    auto predefinedDB =
        std::make_unique<Database>((purefs::dir::getUserDiskPath() / "predefined_quotes.db").string().c_str());
    auto customDB = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "custom_quotes.db").string().c_str());
    std::string timestampString{};
    std::string quotesString{};
    auto settings = std::make_unique<Quotes::SettingsMock>(quotesString, timestampString);
    auto tester   = std::make_unique<QuotesAgentTester>(database.get(), std::move(settings));

    SECTION("Get categories with limit and offset")
    {
        unsigned int offset = 1;
        unsigned int limit  = 2;

        auto request     = std::make_shared<Messages::GetCategoryListRequest>(offset, limit);
        auto queryResult = tester->handleCategoryList(request);
        auto response    = dynamic_cast<Messages::GetCategoryListResponse *>(queryResult.get());

        REQUIRE(response);
        REQUIRE(response->getCount() == totalNumOfCategoriesInDb);
        REQUIRE(response->getResults().size() == limit);
    }
    auto tester   = std::make_unique<QuotesAgentTester>(predefinedDB.get(), customDB.get(), std::move(settings));

    SECTION("Get all categories")
    {
        unsigned int offset = 0;
        unsigned int limit  = 0;

        auto request     = std::make_shared<Messages::GetCategoryListRequest>(offset, limit);
        auto queryResult = tester->handleCategoryList(request);
        auto response    = dynamic_cast<Messages::GetCategoryListResponse *>(queryResult.get());

        REQUIRE(response);
        REQUIRE(response->getCount() == totalNumOfCategoriesInDb);
        REQUIRE(response->getResults().size() == totalNumOfCategoriesInDb);
        utils::setDisplayLanguage("English");
        tester->informLanguageChange();
        auto categories = tester->getCategoriesList();
        REQUIRE(categories.size() == totalNumOfCategories);
    }

    SECTION("Get quotes from custom category")


@@ 60,7 43,7 @@ TEST_CASE("Quotes")
    SECTION("Enable category by id")
    {
        bool enable                   = false;
        const unsigned int categoryId = 1;
        const unsigned int categoryId = 5;

        // Get current random quote
        auto queryResult   = tester->readRandomizedQuote();


@@ 70,10 53,6 @@ TEST_CASE("Quotes")
        auto success = tester->enableCategory(categoryId, enable);
        REQUIRE(success);

        // Quotes in category one should be disabled
        auto quotes = tester->getQuotesByCategoryId(categoryId);
        REQUIRE(quotes.size() == 0);

        // verify rerandomizing the quotes list
        queryResult           = tester->readRandomizedQuote();
        auto newRandomQuoteId = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get())->quoteId;


@@ 84,10 63,6 @@ TEST_CASE("Quotes")

        success = tester->enableCategory(categoryId, enable);
        REQUIRE(success);

        // Quotes in category one should be enabled
        quotes = tester->getQuotesByCategoryId(categoryId);
        REQUIRE(quotes.size() == numOfQuotesWithCategoryIdEqualToOne);
    }

    SECTION("Enable quote by id")


@@ 103,10 78,12 @@ TEST_CASE("Quotes")
        auto success = tester->enableQuote(quoteId, enable);
        REQUIRE(success);

        // All quotes except quote with id=1 should be enabled
        auto quotes = tester->getEnabledQuotes();
        REQUIRE(quotes.size() == totalNumOfQuotesInDb - 1);

        auto customQuotes = tester->getQuotesFromCustomCategory();
        for (const auto &quote : customQuotes) {
            if (quote.quote_id == quoteId) {
                REQUIRE(!quote.enabled);
            }
        }
        // verify rerandomizing the quotes list
        queryResult           = tester->readRandomizedQuote();
        auto newRandomQuoteId = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get())->quoteId;


@@ 118,14 95,16 @@ TEST_CASE("Quotes")
        success = tester->enableQuote(quoteId, enable);
        REQUIRE(success);

        // All quotes should be enabled
        quotes = tester->getEnabledQuotes();
        REQUIRE(quotes.size() == totalNumOfQuotesInDb);
        customQuotes = tester->getQuotesFromCustomCategory();
        for (const auto &quote : customQuotes) {
            if (quote.quote_id == quoteId) {
                REQUIRE(quote.enabled);
            }
        }
    }

    SECTION("Add/Read/Write/Delete quote")
    {
        unsigned int langId = 1;
        std::string quote   = "TEST QUOTE";
        std::string author  = "TEST AUTHOR";
        bool enabled        = true;


@@ 136,12 115,11 @@ TEST_CASE("Quotes")
        REQUIRE(randomQuoteId != 0);

        // Add a new quote
        auto quoteId = tester->addQuote(langId, quote, author, enabled);
        auto quoteId = tester->addQuote(quote, author, enabled);

        // Check if quotes count has increased
        auto quotes = tester->getAllQuotes();

        REQUIRE(quotes.size() == totalNumOfQuotesInDb + 1);
        auto customQuotes = tester->getQuotesFromCustomCategory();
        REQUIRE(customQuotes.size() == numOfQuotesFromCustomCategory + 1);

        // verify rerandomizing the quotes list
        queryResult           = tester->readRandomizedQuote();


@@ 150,35 128,36 @@ TEST_CASE("Quotes")
        REQUIRE(randomQuoteId != newRandomQuoteId);

        // Read added quote
        queryResult            = tester->readQuote(quoteId);
        auto readQuoteResponse = dynamic_cast<Messages::ReadQuoteResponse *>(queryResult.get());

        REQUIRE(readQuoteResponse->quoteId == quoteId);
        REQUIRE(readQuoteResponse->quote == quote);
        REQUIRE(readQuoteResponse->author == author);
        customQuotes = tester->getQuotesFromCustomCategory();
        for (const auto &customQuote : customQuotes) {
            if (customQuote.quote_id == quoteId) {
                REQUIRE(customQuote.quote == quote);
                REQUIRE(customQuote.author == author);
            }
        }

        // Change added quote (overwrite)
        quote        = "TEST QUOTE CHANGED";
        author       = "TEST AUTHOR CHANGED";
        auto success = tester->writeQuote(quoteId, langId, quote, author, enabled);
        auto success = tester->writeQuote(quoteId, quote, author, enabled);
        REQUIRE(success);

        // Read quote if values have been properly updated
        queryResult       = tester->readQuote(quoteId);
        readQuoteResponse = dynamic_cast<Messages::ReadQuoteResponse *>(queryResult.get());

        REQUIRE(readQuoteResponse->quoteId == quoteId);
        REQUIRE(readQuoteResponse->quote == quote);
        REQUIRE(readQuoteResponse->author == author);
        customQuotes = tester->getQuotesFromCustomCategory();
        for (const auto &customQuote : customQuotes) {
            if (customQuote.quote_id == quoteId) {
                REQUIRE(customQuote.quote == quote);
                REQUIRE(customQuote.author == author);
            }
        }

        // Delete added quote
        success = tester->deleteQuote(quoteId);
        REQUIRE(success);

        // Check if quotes count match count before added quote
        quotes = tester->getAllQuotes();

        REQUIRE(quotes.size() == totalNumOfQuotesInDb);
        customQuotes = tester->getQuotesFromCustomCategory();
        REQUIRE(customQuotes.size() == numOfQuotesFromCustomCategory);
    }

    SECTION("Randomizer test - double request")


@@ 242,13 221,14 @@ TEST_CASE("Serializer test")

    SECTION("Serialize - correct input")
    {
        IdList list{1, 2, 3, 4};
        IdList list{
            {QuoteType::Predefined, 1}, {QuoteType::Custom, 2}, {QuoteType::Predefined, 3}, {QuoteType::Custom, 4}};
        auto result = serializer.serialize(list);
        REQUIRE(result == "1,2,3,4,");
        REQUIRE(result == "0,1,1,2,0,3,1,4,");

        IdList list2{1};
        IdList list2{{QuoteType::Custom, 1}};
        result = serializer.serialize(list2);
        REQUIRE(result == "1,");
        REQUIRE(result == "1,1,");
    }

    SECTION("Serialize - wrong input")


@@ 260,11 240,13 @@ TEST_CASE("Serializer test")

    SECTION("Deserialize - correct input")
    {
        std::string serializedData = "1,2,3,4,";
        std::string serializedData = "0,1,1,2,0,3,1,4,";
        auto result                = serializer.deserialize(serializedData);
        REQUIRE(result.size() == 4);
        REQUIRE(result.front() == 1);
        REQUIRE(result.back() == 4);
        REQUIRE(result.front().first == QuoteType::Predefined);
        REQUIRE(result.front().second == 1);
        REQUIRE(result.back().first == QuoteType::Custom);
        REQUIRE(result.back().second == 4);
    }

    SECTION("Deserialize - empty input")

M module-services/service-db/test/test-service-db-quotes.hpp => module-services/service-db/test/test-service-db-quotes.hpp +18 -45
@@ 12,21 12,18 @@ namespace Quotes
    class QuotesAgentTester : public QuotesAgent
    {
      public:
        QuotesAgentTester(Database *quotesDB, std::unique_ptr<settings::Settings> settings)
            : QuotesAgent(quotesDB, std::move(settings)){};
        QuotesAgentTester(Database *predefinedDB, Database *customDB, std::unique_ptr<settings::Settings> settings)
            : QuotesAgent(predefinedDB, customDB, std::move(settings)){};
        ~QuotesAgentTester() = default;

        auto handleCategoryList(std::shared_ptr<Messages::GetCategoryListRequest> query)
            -> std::unique_ptr<db::QueryResult>
        auto getCategoriesList() -> std::vector<CategoryRecord>
        {
            return QuotesAgent::handleCategoryList(query);
        }
            unsigned int offset = 0;
            unsigned int limit  = 0;

        auto getAllQuotes(unsigned int limit = 0, unsigned int offset = 0) -> std::vector<QuoteRecord>
        {
            auto request     = std::make_shared<Messages::GetQuotesListRequest>(offset, limit);
            auto queryResult = handleQuotesList(request);
            auto response    = dynamic_cast<Messages::GetQuotesListResponse *>(queryResult.get());
            auto request     = std::make_shared<Messages::GetCategoryListRequest>(offset, limit);
            auto queryResult = handleCategoryList(request);
            auto response    = dynamic_cast<Messages::GetCategoryListResponse *>(queryResult.get());
            return response->getResults();
        }



@@ 41,28 38,6 @@ namespace Quotes
            return response->getResults();
        }

        auto getQuotesByCategoryId(unsigned int categoryId) -> std::vector<QuoteRecord>
        {
            unsigned int offset = 0;
            unsigned int limit  = 0;

            auto request     = std::make_shared<Messages::GetQuotesListByCategoryIdRequest>(offset, limit, categoryId);
            auto queryResult = handleQuotesListByCategoryId(request);
            auto response    = dynamic_cast<Messages::GetQuotesListByCategoryIdResponse *>(queryResult.get());
            return response->getResults();
        }

        auto getEnabledQuotes() -> std::vector<QuoteRecord>
        {
            unsigned int offset = 0;
            unsigned int limit  = 0;

            auto request     = std::make_shared<Messages::GetEnabledQuotesListRequest>(offset, limit);
            auto queryResult = handleEnabledQuotesList(request);
            auto response    = dynamic_cast<Messages::GetEnabledQuotesListResponse *>(queryResult.get());
            return response->getResults();
        }

        auto enableCategory(unsigned int categoryId, bool enable) -> bool
        {
            auto request     = std::make_shared<Messages::EnableCategoryByIdRequest>(categoryId, enable);


@@ 79,31 54,29 @@ namespace Quotes
            return response->success;
        }

        auto addQuote(unsigned int langId, std::string quote, std::string author, bool enabled) -> unsigned int
        auto addQuote(std::string quote, std::string author, bool enabled) -> unsigned int
        {
            auto request =
                std::make_shared<Messages::AddQuoteRequest>(langId, std::move(quote), std::move(author), enabled);
            auto request = std::make_shared<Messages::AddQuoteRequest>(std::move(quote), std::move(author), enabled);
            auto queryResult = handleAddQuote(request);
            auto response    = dynamic_cast<Messages::AddQuoteResponse *>(queryResult.get());
            return response->quoteId;
        }

        auto readQuote(unsigned int quoteId) -> std::unique_ptr<db::QueryResult>
        {
            auto request = std::make_shared<Messages::ReadQuoteRequest>(quoteId);
            return handleReadQuote(request);
        }
        auto readRandomizedQuote() -> std::unique_ptr<db::QueryResult>
        {
            auto request = std::make_shared<Messages::ReadRandomizedQuoteRequest>();
            return handleReadRandomizedQuote(request);
        }

        auto writeQuote(unsigned int quoteId, unsigned int langId, std::string quote, std::string author, bool enabled)
            -> bool
        auto informLanguageChange() -> void
        {
            handleInformLanguageChange();
        }

        auto writeQuote(unsigned int quoteId, std::string quote, std::string author, bool enabled) -> bool
        {
            auto request = std::make_shared<Messages::WriteQuoteRequest>(
                quoteId, langId, std::move(quote), std::move(author), enabled);
            auto request =
                std::make_shared<Messages::WriteQuoteRequest>(quoteId, std::move(quote), std::move(author), enabled);
            auto queryResult = handleWriteQuote(request);
            auto response    = dynamic_cast<Messages::WriteQuoteResponse *>(queryResult.get());
            return response->success;

M products/PurePhone/services/db/ServiceDB.cpp => products/PurePhone/services/db/ServiceDB.cpp +15 -5
@@ 32,7 32,8 @@ ServiceDB::~ServiceDB()
    notesDB.reset();
    countryCodesDB.reset();
    notificationsDB.reset();
    quotesDB.reset();
    predefinedQuotesDB.reset();
    customQuotesDB.reset();
    multimediaFilesDB.reset();

    Database::deinitialize();


@@ 230,7 231,8 @@ sys::ReturnCodes ServiceDB::InitHandler()
    calllogDB       = std::make_unique<CalllogDB>((purefs::dir::getUserDiskPath() / "calllog.db").c_str());
    countryCodesDB  = std::make_unique<CountryCodesDB>("country-codes.db");
    notificationsDB = std::make_unique<NotificationsDB>((purefs::dir::getUserDiskPath() / "notifications.db").c_str());
    quotesDB        = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "quotes.db").c_str());
    predefinedQuotesDB = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "predefined_quotes.db").c_str());
    customQuotesDB     = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "custom_quotes.db").c_str());
    multimediaFilesDB = std::make_unique<db::multimedia_files::MultimediaFilesDB>(
        (purefs::dir::getUserDiskPath() / "multimedia.db").c_str());



@@ 257,7 259,8 @@ sys::ReturnCodes ServiceDB::InitHandler()
    auto settings = std::make_unique<settings::Settings>();
    settings->init(service::ServiceProxy(shared_from_this()));

    quotesRecordInterface = std::make_unique<Quotes::QuotesAgent>(quotesDB.get(), std::move(settings));
    quotesRecordInterface =
        std::make_unique<Quotes::QuotesAgent>(predefinedQuotesDB.get(), customQuotesDB.get(), std::move(settings));

    return sys::ReturnCodes::Success;
}


@@ 289,8 292,15 @@ bool ServiceDB::StoreIntoBackup(const std::filesystem::path &backupPath)
        return false;
    }

    if (quotesDB->storeIntoFile(backupPath / std::filesystem::path(quotesDB->getName()).filename()) == false) {
        LOG_ERROR("quotesDB backup failed");
    if (customQuotesDB->storeIntoFile(backupPath / std::filesystem::path(predefinedQuotesDB->getName()).filename()) ==
        false) {
        LOG_ERROR("predefinedQuotesDB backup failed");
        return false;
    }

    if (customQuotesDB->storeIntoFile(backupPath / std::filesystem::path(customQuotesDB->getName()).filename()) ==
        false) {
        LOG_ERROR("customQuotesDB backup failed");
        return false;
    }


M products/PurePhone/services/db/include/db/ServiceDB.hpp => products/PurePhone/services/db/include/db/ServiceDB.hpp +3 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 52,7 52,8 @@ class ServiceDB : public ServiceDBCommon
    std::unique_ptr<CalllogDB> calllogDB;
    std::unique_ptr<CountryCodesDB> countryCodesDB;
    std::unique_ptr<NotificationsDB> notificationsDB;
    std::unique_ptr<Database> quotesDB;
    std::unique_ptr<Database> predefinedQuotesDB;
    std::unique_ptr<Database> customQuotesDB;
    std::unique_ptr<db::multimedia_files::MultimediaFilesDB> multimediaFilesDB;

    std::unique_ptr<AlarmEventRecordInterface> alarmEventRecordInterface;