~aleteoryx/muditaos

204dd621ef4da31d1128fe57de7cc91be9757f6a — Marcin Smoczyński 5 years ago 9566c07
[EGD-3532] identify thread with a number

Use phone number instead of contact number to identify a message thread
to allow having multiple threads for a contact if contact has multiple
numbers.
M changelog.md => changelog.md +2 -2
@@ 8,6 8,8 @@

### Changed

* `[messages]` Use different thread for each of contact's number.

### Fixed

* `[desktopApp]` Fixed endpoint handling.


@@ 20,8 22,6 @@
### Other
* `[desktopApp]` Added functional tests.



## [0.36.1 2020-09-04]

### Added

M image/user/sms.db => image/user/sms.db +0 -0
M module-apps/application-messages/windows/NewMessage.cpp => module-apps/application-messages/windows/NewMessage.cpp +18 -20
@@ 32,6 32,7 @@ namespace gui
        if (data == nullptr) {
            return;
        }

        if (auto pdata = dynamic_cast<PhonebookSearchReuqest *>(data); pdata != nullptr) {
            LOG_INFO("received search results");
            recipient->setText(pdata->result->getFormattedName());


@@ 45,11 46,12 @@ namespace gui
        }
        if (auto pdata = dynamic_cast<SMSSendRequest *>(data); pdata != nullptr) {
            LOG_INFO("recieved sms send request");
            auto number     = pdata->getPhoneNumber();
            auto retContact = DBServiceAPI::MatchContactByPhoneNumber(application, number);
            phoneNumber = pdata->getPhoneNumber();
            LOG_INFO("Number to send sms to: %s", phoneNumber.getFormatted().c_str());
            auto retContact = DBServiceAPI::MatchContactByPhoneNumber(application, phoneNumber);
            if (!retContact) {
                LOG_WARN("not valid contact for number %s", number.getEntered().c_str());
                recipient->setText(number.getFormatted());
                LOG_WARN("not valid contact for number %s", phoneNumber.getEntered().c_str());
                recipient->setText(phoneNumber.getFormatted());
                message->setText(pdata->textData);
                return;
            }


@@ 77,9 79,16 @@ namespace gui
    {
        auto app = dynamic_cast<app::ApplicationMessages *>(application);
        assert(app != nullptr);
        // if a valid contact was found, choose it. Otherwise, get a raw entered number
        auto number = (contact && contact->numbers.size() != 0) ? contact->numbers[0].number
                                                                : utils::PhoneNumber(recipient->getText()).getView();
        utils::PhoneNumber::View number;

        if (phoneNumber.getEntered().size() > 0) {
            number = phoneNumber;
        }
        else {
            number = (contact && contact->numbers.size() != 0) ? contact->numbers[0].number
                                                               : utils::PhoneNumber(recipient->getText()).getView();
        }

        auto ret = app->sendSms(number, message->getText());
        if (!ret) {
            LOG_ERROR("sendSms failed");


@@ 102,22 111,11 @@ namespace gui

    bool NewSMS_Window::switchToThreadWindow(const utils::PhoneNumber::View &number)
    {
        uint32_t contactId;
        if (!contact || contact->numbers.size() == 0) {
            // once the sms is send, there is assumption that contact exists
            auto retContact = DBServiceAPI::MatchContactByPhoneNumber(application, number);
            if (!retContact) {
                LOG_ERROR("not valid contact for number %s", number.getFormatted().c_str());
                return false;
            }
            contact = std::move(retContact);
        }
        contactId = contact->ID;

        auto thread = DBServiceAPI::ThreadGetByContact(application, contactId);
        auto thread = DBServiceAPI::ThreadGetByNumber(application, number, getThreadTimeout);
        if (thread) {
            // clear data only when message is sent
            contact = nullptr;
            phoneNumber.clear();
            recipient->setText("");
            message->setText("");
            setFocusItem(body);

M module-apps/application-messages/windows/NewMessage.hpp => module-apps/application-messages/windows/NewMessage.hpp +4 -0
@@ 12,10 12,14 @@ namespace gui
    class NewSMS_Window : public AppWindow
    {
      private:
        inline static const std::uint32_t getThreadTimeout = 1000;

        gui::Text *recipient = nullptr;
        gui::Text *message   = nullptr;
        gui::VBox *body      = nullptr;
        std::shared_ptr<ContactRecord> contact;
        utils::PhoneNumber::View phoneNumber;
        std::uint32_t numberId = 0;

        bool selectContact();
        bool sendSms();

M module-apps/application-messages/windows/ThreadViewWindow.cpp => module-apps/application-messages/windows/ThreadViewWindow.cpp +5 -1
@@ 70,7 70,7 @@ namespace gui
        inputMessage->activatedCallback = [&](gui::Item &item) {
            auto app = dynamic_cast<app::ApplicationMessages *>(application);
            assert(app != nullptr);
            if (app->handleSendSmsFromThread(contact->numbers[0].number, inputMessage->inputText->getText())) {
            if (app->handleSendSmsFromThread(*number, inputMessage->inputText->getText())) {
                LOG_ERROR("handleSendSmsFromThread failed");
            }
            inputMessage->inputText->clear();


@@ 372,6 372,10 @@ namespace gui
                contact  = std::make_shared<ContactRecord>(ret->front());
                // should be name number for now - easier to handle
                setTitle(ret->front().getFormattedName());
                auto retNumber = DBServiceAPI::GetNumberById(application, pdata->thread->numberID, numberIdTimeout);
                assert(retNumber != nullptr);
                number = std::move(retNumber);
                LOG_INFO("Phone number for thread: %s", number->getFormatted().c_str());
            }
        }
        if (auto pdata = dynamic_cast<SMSTextData *>(data)) {

M module-apps/application-messages/windows/ThreadViewWindow.hpp => module-apps/application-messages/windows/ThreadViewWindow.hpp +14 -11
@@ 1,18 1,19 @@
#pragma once

#include <AppWindow.hpp>
#include <application-messages/widgets/SMSInputWidget.hpp>
#include <gui/widgets/BoxLayout.hpp>
#include <gui/widgets/Image.hpp>
#include <gui/widgets/Label.hpp>
#include <gui/widgets/Window.hpp>
#include <ListView.hpp>
#include <PhoneNumber.hpp>
#include <service-db/api/DBServiceAPI.hpp>
#include <Text.hpp>

#include <functional>
#include <string>

#include "AppWindow.hpp"
#include "ListView.hpp"
#include "gui/widgets/Image.hpp"
#include "gui/widgets/Label.hpp"
#include "gui/widgets/Window.hpp"
#include "service-db/api/DBServiceAPI.hpp"
#include <Text.hpp>
#include <gui/widgets/BoxLayout.hpp>
#include "application-messages/widgets/SMSInputWidget.hpp"

namespace gui
{
    class ThreadViewWindow : public AppWindow


@@ 38,6 39,7 @@ namespace gui
        const ssize_t maxsmsinwindow = 7;

        std::shared_ptr<ContactRecord> contact;
        std::unique_ptr<utils::PhoneNumber::View> number;

        struct
        {


@@ 48,7 50,8 @@ namespace gui
            std::unique_ptr<std::vector<SMSRecord>> sms = nullptr; // loaded sms from db
        } SMS;

        gui::SMSInputWidget *inputMessage = nullptr;
        gui::SMSInputWidget *inputMessage                 = nullptr;
        inline static const std::uint32_t numberIdTimeout = 1000;

      public:
        ThreadViewWindow(app::Application *app);

M module-db/CMakeLists.txt => module-db/CMakeLists.txt +2 -0
@@ 76,6 76,7 @@ set(SOURCES
        queries/sms/QuerySMSTemplateGetCount.cpp
        queries/sms/QuerySMSTemplateRemove.cpp
        queries/sms/QuerySMSTemplateUpdate.cpp
        queries/sms/QueryThreadGetByNumber.cpp
        queries/calllog/QueryCalllogSetAllRead.cpp
        queries/calllog/QueryCalllogGet.cpp
        queries/calllog/QueryCalllogGetCount.cpp


@@ 90,6 91,7 @@ set(SOURCES
        queries/phonebook/QueryContactAdd.cpp
        queries/phonebook/QueryContactUpdate.cpp
        queries/phonebook/QueryContactRemove.cpp
        queries/phonebook/QueryNumberGetByID.cpp
        queries/calendar/QueryEventsRemove.cpp
        queries/calendar/QueryEventsGet.cpp
        queries/calendar/QueryEventsGetAll.cpp

M module-db/Interface/CalllogRecord.cpp => module-db/Interface/CalllogRecord.cpp +7 -4
@@ 62,14 62,17 @@ CalllogRecordInterface::~CalllogRecordInterface()
bool CalllogRecordInterface::Add(const CalllogRecord &rec)
{
    ContactRecordInterface contactInterface(contactsDB);
    auto contactRec = contactInterface.MatchByNumber(rec.phoneNumber, ContactRecordInterface::CreateTempContact::True);
    if (!contactRec) {
    auto contactMatch =
        contactInterface.MatchByNumber(rec.phoneNumber, ContactRecordInterface::CreateTempContact::True);
    if (!contactMatch) {
        LOG_ERROR("Cannot get contact, for number %s", rec.phoneNumber.getNonEmpty().c_str());
        return false;
    }
    auto &contactRec = contactMatch->contact;

    auto localRec      = rec;
    localRec.contactId = std::to_string(contactRec->ID);
    localRec.name      = contactRec->getFormattedName();
    localRec.contactId = std::to_string(contactRec.ID);
    localRec.name      = contactRec.getFormattedName();
    LOG_DEBUG("Adding calllog record %s", utils::to_string(localRec).c_str());

    return calllogDB->calls.add(CalllogTableRow{{.ID = localRec.ID}, // this is only to remove warning

M module-db/Interface/ContactRecord.cpp => module-db/Interface/ContactRecord.cpp +39 -5
@@ 6,6 6,7 @@
#include <Utils.hpp>

#include <queries/phonebook/QueryContactGet.hpp>
#include <queries/phonebook/QueryNumberGetByID.hpp>

#include <PhoneNumber.hpp>
#include <NumberHolderMatcher.hpp>


@@ 164,6 165,9 @@ std::unique_ptr<db::QueryResult> ContactRecordInterface::runQuery(std::shared_pt
    else if (typeid(*query) == typeid(db::query::ContactRemove)) {
        return removeQuery(query);
    }
    else if (typeid(*query) == typeid(db::query::NumberGetByID)) {
        return numberGetByIdQuery(query);
    }
    else {
        LOG_ERROR("Unexpected query type.");
        return nullptr;


@@ 274,6 278,7 @@ std::unique_ptr<db::QueryResult> ContactRecordInterface::updateQuery(std::shared
    response->setRequestQuery(query);
    return response;
}

std::unique_ptr<db::QueryResult> ContactRecordInterface::removeQuery(std::shared_ptr<db::Query> query)
{
    auto removeQuery = static_cast<db::query::ContactRemove *>(query.get());


@@ 283,6 288,15 @@ std::unique_ptr<db::QueryResult> ContactRecordInterface::removeQuery(std::shared
    return response;
}

std::unique_ptr<db::QueryResult> ContactRecordInterface::numberGetByIdQuery(std::shared_ptr<db::Query> query)
{
    auto numberQuery = static_cast<db::query::NumberGetByID *>(query.get());
    auto ret         = ContactRecordInterface::GetNumberById(numberQuery->getID());
    auto response    = std::make_unique<db::query::NumberGetByIDResult>(ret);
    response->setRequestQuery(query);
    return response;
}

std::vector<std::uint32_t> ContactRecordInterface::splitNumberIDs(const std::string &numberIDs)
{
    std::stringstream source(numberIDs);


@@ 874,9 888,10 @@ utils::NumberHolderMatcher<std::vector, ContactNumberHolder> ContactRecordInterf
                                                                        std::cend(contactNumberHolders));
}

std::optional<ContactRecord> ContactRecordInterface::MatchByNumber(const utils::PhoneNumber::View &numberView,
                                                                   CreateTempContact createTempContact,
                                                                   utils::PhoneNumber::Match matchLevel)
std::optional<ContactRecordInterface::ContactNumberMatch> ContactRecordInterface::MatchByNumber(
    const utils::PhoneNumber::View &numberView,
    CreateTempContact createTempContact,
    utils::PhoneNumber::Match matchLevel)
{
    std::vector<ContactNumberHolder> contactNumberHolders;
    auto numberMatcher = buildNumberMatcher(contactNumberHolders);


@@ 898,10 913,17 @@ std::optional<ContactRecord> ContactRecordInterface::MatchByNumber(const utils::
            return std::nullopt;
        }

        return GetByID(contactDB->getLastInsertRowId());
        auto contactID       = contactDB->getLastInsertRowId();
        auto contactTableRow = contactDB->contacts.getById(contactID);
        auto numberIDs       = splitNumberIDs(contactTableRow.numbersID);
        assert(numberIDs.size() == 1);
        auto numberID = numberIDs[0];
        return ContactRecordInterface::ContactNumberMatch(GetByID(contactID), contactID, numberID);
    }

    return GetByID(matchedNumber->getContactID());
    auto contactID = matchedNumber->getContactID();
    auto numberID  = matchedNumber->getNumberID();
    return ContactRecordInterface::ContactNumberMatch(GetByID(contactID), contactID, numberID);
}

std::unique_ptr<std::vector<ContactRecord>> ContactRecordInterface::GetBySpeedDial(UTF8 speedDial)


@@ 1041,3 1063,15 @@ bool ContactRecord::isOnGroup(uint32_t groupId)
{
    return groups.find(groupId) != groups.end();
}

ContactRecordInterface::ContactNumberMatch::ContactNumberMatch(ContactRecord rec,
                                                               std::uint32_t contactId,
                                                               std::uint32_t numberId)
    : contact(std::move(rec)), contactId(contactId), numberId(numberId)
{}

utils::PhoneNumber::View ContactRecordInterface::GetNumberById(std::uint32_t numberId)
{
    auto row = contactDB->number.getById(numberId);
    return utils::PhoneNumber(row.numberUser, row.numbere164).getView();
}

M module-db/Interface/ContactRecord.hpp => module-db/Interface/ContactRecord.hpp +13 -2
@@ 113,8 113,16 @@ class ContactNumberHolder

class ContactRecordInterface : public RecordInterface<ContactRecord, ContactRecordField>
{

  public:
    struct ContactNumberMatch
    {
        ContactRecord contact;
        std::uint32_t contactId = DB_ID_NONE;
        std::uint32_t numberId  = DB_ID_NONE;

        ContactNumberMatch(ContactRecord rec, std::uint32_t contactId, std::uint32_t numberId);
    };

    ContactRecordInterface(ContactsDB *db);
    ~ContactRecordInterface();



@@ 153,7 161,7 @@ class ContactRecordInterface : public RecordInterface<ContactRecord, ContactReco
    std::unique_ptr<std::vector<ContactRecord>> GetByNumber(
        const utils::PhoneNumber::View &numberView, CreateTempContact createTempContact = CreateTempContact::False);

    std::optional<ContactRecord> MatchByNumber(
    std::optional<ContactNumberMatch> MatchByNumber(
        const utils::PhoneNumber::View &numberView,
        CreateTempContact createTempContact  = CreateTempContact::False,
        utils::PhoneNumber::Match matchLevel = utils::PhoneNumber::Match::POSSIBLE);


@@ 166,6 174,8 @@ class ContactRecordInterface : public RecordInterface<ContactRecord, ContactReco

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

    utils::PhoneNumber::View GetNumberById(std::uint32_t numberId);

  private:
    ContactsDB *contactDB;



@@ 192,4 202,5 @@ class ContactRecordInterface : public RecordInterface<ContactRecord, ContactReco
    std::unique_ptr<db::QueryResult> addQuery(std::shared_ptr<db::Query> query);
    std::unique_ptr<db::QueryResult> updateQuery(std::shared_ptr<db::Query> query);
    std::unique_ptr<db::QueryResult> removeQuery(std::shared_ptr<db::Query> query);
    std::unique_ptr<db::QueryResult> numberGetByIdQuery(std::shared_ptr<db::Query> query);
};

M module-db/Interface/SMSRecord.cpp => module-db/Interface/SMSRecord.cpp +10 -9
@@ 29,29 29,32 @@ SMSRecordInterface::SMSRecordInterface(SmsDB *smsDb, ContactsDB *contactsDb) : s
bool SMSRecordInterface::Add(const SMSRecord &rec)
{
    ContactRecordInterface contactInterface(contactsDB);
    auto contactRec = contactInterface.MatchByNumber(rec.number, ContactRecordInterface::CreateTempContact::True);
    if (contactRec == std::nullopt) {
    auto contactMatch = contactInterface.MatchByNumber(rec.number, ContactRecordInterface::CreateTempContact::True);
    if (contactMatch == std::nullopt) {
        LOG_ERROR("Cannot find contact, for number %s", rec.number.getFormatted().c_str());
        return false;
    }
    uint32_t contactID = contactRec->ID;
    auto contactID = contactMatch->contactId;
    auto numberID  = contactMatch->numberId;

    // Search for a thread with specified contactID
    ThreadRecordInterface threadInterface(smsDB, contactsDB);
    auto threadRec =
        threadInterface.GetLimitOffsetByField(0, 1, ThreadRecordField::ContactID, std::to_string(contactID).c_str());
        threadInterface.GetLimitOffsetByField(0, 1, ThreadRecordField::NumberID, std::to_string(numberID).c_str());

    // Thread not found, create one
    if (threadRec->size() == 0) {

        ThreadRecord re;
        re.contactID = contactID;
        re.numberID  = numberID;
        if (!threadInterface.Add(re)) {
            LOG_ERROR("Cannot create new thread");
            return false;
        }

        threadRec = threadInterface.GetLimitOffsetByField(
            0, 1, ThreadRecordField::ContactID, std::to_string(contactID).c_str());
        threadRec =
            threadInterface.GetLimitOffsetByField(0, 1, ThreadRecordField::NumberID, std::to_string(numberID).c_str());
        if (threadRec->size() == 0) {
            LOG_ERROR("Thread not found");
            return false;


@@ 289,8 292,7 @@ SMSRecord SMSRecordInterface::GetByID(uint32_t id)

    ContactRecordInterface contactInterface(contactsDB);
    auto contactRec = contactInterface.GetByID(sms.contactID);
    // TODO: or numberUser?
    auto number = contactRec.numbers.size() != 0 ? contactRec.numbers[0].number : utils::PhoneNumber::View();
    auto number     = contactRec.numbers.size() != 0 ? contactRec.numbers[0].number : utils::PhoneNumber::View();

    return SMSRecord{sms, number};
}


@@ 337,7 339,6 @@ std::unique_ptr<db::query::SMSSearchByTypeResult> SMSRecordInterface::runQueryIm

        auto contactRec = contactInterface.GetByID(w.contactID);
        if (contactRec.numbers.size() != 0) {
            // TODO: or numberUser? or other number
            records->push_back({w, contactRec.numbers[0].number});
        }
    }

M module-db/Interface/ThreadRecord.cpp => module-db/Interface/ThreadRecord.cpp +41 -5
@@ 1,5 1,9 @@
#include "ThreadRecord.hpp"
#include "SMSRecord.hpp"
#include "ContactRecord.hpp"

#include <queries/sms/QueryThreadGetByNumber.hpp>

#include <cassert>
#include <log/log.hpp>



@@ 14,6 18,7 @@ bool ThreadRecordInterface::Add(const ThreadRecord &rec)
                                                  .msgCount       = rec.msgCount,
                                                  .unreadMsgCount = rec.unreadMsgCount,
                                                  .contactID      = rec.contactID,
                                                  .numberID       = rec.numberID,
                                                  .snippet        = rec.snippet,
                                                  .type           = rec.type});



@@ 38,6 43,7 @@ bool ThreadRecordInterface::Update(const ThreadRecord &rec)
                                                 .msgCount       = rec.msgCount,
                                                 .unreadMsgCount = rec.unreadMsgCount,
                                                 .contactID      = rec.contactID,
                                                 .numberID       = rec.numberID,
                                                 .snippet        = rec.snippet,
                                                 .type           = rec.type



@@ 74,16 80,21 @@ std::unique_ptr<std::vector<ThreadRecord>> ThreadRecordInterface::GetLimitOffset
{
    auto records = std::make_unique<std::vector<ThreadRecord>>();

    ThreadsTableFields threadsField;
    switch (field) {
    case ThreadRecordField::ContactID: {
        auto ret = smsDB->threads.getLimitOffsetByField(offset, limit, ThreadsTableFields::ContactID, str);

        for (const auto &w : ret) {
            records->push_back(w);
        }
        threadsField = ThreadsTableFields::ContactID;
    } break;
    case ThreadRecordField::NumberID: {
        threadsField = ThreadsTableFields::NumberID;
    } break;
    }

    auto ret = smsDB->threads.getLimitOffsetByField(offset, limit, threadsField, str);
    for (const auto &w : ret) {
        records->push_back(w);
    }

    return records;
}



@@ 119,6 130,24 @@ ThreadRecord ThreadRecordInterface::GetByContact(uint32_t contact_id)
    return ThreadRecord(ret[0]);
}

ThreadRecord ThreadRecordInterface::GetByNumber(const utils::PhoneNumber::View &phoneNumber)
{
    auto contactInterface = ContactRecordInterface(contactsDB);
    auto match            = contactInterface.MatchByNumber(phoneNumber);

    if (!match.has_value()) {
        return ThreadRecord();
    }

    auto threadRec = GetLimitOffsetByField(0, 1, ThreadRecordField::NumberID, std::to_string(match->numberId).c_str());

    if (threadRec->size() == 0) {
        return ThreadRecord();
    }

    return threadRec->at(0);
}

std::unique_ptr<db::QueryResult> ThreadRecordInterface::runQuery(std::shared_ptr<db::Query> query)
{
    if (const auto localQuery = dynamic_cast<const db::query::SMSSearch *>(query.get())) {


@@ 142,6 171,13 @@ std::unique_ptr<db::QueryResult> ThreadRecordInterface::runQuery(std::shared_ptr
        response->setRequestQuery(query);
        return response;
    }

    if (const auto local_query = dynamic_cast<const db::query::ThreadGetByNumber *>(query.get())) {
        auto response = std::make_unique<db::query::ThreadGetByNumberResult>(GetByNumber(local_query->getNumber()));
        response->setRequestQuery(query);
        return response;
    }

    return nullptr;
}


M module-db/Interface/ThreadRecord.hpp => module-db/Interface/ThreadRecord.hpp +5 -1
@@ 7,6 7,7 @@
#include "module-db/queries/sms/QuerySMSSearch.hpp"
#include "module-db/queries/sms/QuerySMSThreadsGet.hpp"
#include "module-db/queries/sms/QuerySmsThreadMarkAsRead.hpp"
#include <PhoneNumber.hpp>

#include <utf8/UTF8.hpp>



@@ 19,11 20,12 @@ struct ThreadRecord : Record
    UTF8 snippet;
    SMSType type       = SMSType::UNKNOWN;
    uint32_t contactID = DB_ID_NONE;
    uint32_t numberID  = DB_ID_NONE;

    ThreadRecord() = default;
    ThreadRecord(const ThreadsTableRow &rec)
        : date(rec.date), msgCount(rec.msgCount), unreadMsgCount(rec.unreadMsgCount), snippet(rec.snippet),
          type(rec.type), contactID(rec.contactID)
          type(rec.type), contactID(rec.contactID), numberID(rec.numberID)
    {
        ID = rec.ID;
    }


@@ 37,6 39,7 @@ struct ThreadRecord : Record
enum class ThreadRecordField
{
    ContactID,
    NumberID,
};

class ThreadRecordInterface : public RecordInterface<ThreadRecord, ThreadRecordField>


@@ 50,6 53,7 @@ class ThreadRecordInterface : public RecordInterface<ThreadRecord, ThreadRecordF
    bool Update(const ThreadRecord &rec) override final;
    ThreadRecord GetByID(uint32_t id) override final;
    ThreadRecord GetByContact(uint32_t contact_id);
    ThreadRecord GetByNumber(const utils::PhoneNumber::View &phoneNumber);

    uint32_t GetCount() override final;
    uint32_t GetCount(EntryState state);

M module-db/Tables/ThreadsTable.cpp => module-db/Tables/ThreadsTable.cpp +22 -13
@@ 24,14 24,16 @@ bool ThreadsTable::create()
bool ThreadsTable::add(ThreadsTableRow entry)
{

    return db->execute("INSERT or ignore INTO threads ( date, msg_count, read, contact_id, snippet, last_dir ) VALUES "
                       "( %lu, %lu, %lu, %lu, '%q', %lu );",
                       entry.date,
                       entry.msgCount,
                       entry.unreadMsgCount,
                       entry.contactID,
                       entry.snippet.c_str(),
                       entry.type);
    return db->execute(
        "INSERT or ignore INTO threads ( date, msg_count, read, contact_id, number_id, snippet, last_dir ) VALUES "
        "( %lu, %lu, %lu, %lu, %lu, '%q', %lu );",
        entry.date,
        entry.msgCount,
        entry.unreadMsgCount,
        entry.contactID,
        entry.numberID,
        entry.snippet.c_str(),
        entry.type);
}

bool ThreadsTable::removeById(uint32_t id)


@@ 41,12 43,14 @@ bool ThreadsTable::removeById(uint32_t id)

bool ThreadsTable::update(ThreadsTableRow entry)
{
    return db->execute("UPDATE threads SET date = %lu, msg_count = %lu ,read = %lu, contact_id = %lu, snippet = '%q', "
    return db->execute("UPDATE threads SET date = %lu, msg_count = %lu ,read = %lu, contact_id = %lu, number_id = %lu, "
                       "snippet = '%q', "
                       "last_dir = %lu WHERE _id=%lu;",
                       entry.date,
                       entry.msgCount,
                       entry.unreadMsgCount,
                       entry.contactID,
                       entry.numberID,
                       entry.snippet.c_str(),
                       entry.type,
                       entry.ID);


@@ 66,8 70,9 @@ ThreadsTableRow ThreadsTable::getById(uint32_t id)
        (*retQuery)[2].getUInt32(),                       // msgCount
        (*retQuery)[3].getUInt32(),                       // unreadMsgCount
        (*retQuery)[4].getUInt32(),                       // contactID
        (*retQuery)[5].getString(),                       // snippet
        static_cast<SMSType>((*retQuery)[6].getUInt32()), // type/last-dir
        (*retQuery)[5].getUInt32(),                       // numberID
        (*retQuery)[6].getString(),                       // snippet
        static_cast<SMSType>((*retQuery)[7].getUInt32()), // type/last-dir
    };
}



@@ 80,8 85,9 @@ void fillRetQuery(std::vector<ThreadsTableRow> &ret, const std::unique_ptr<Query
            (*retQuery)[2].getUInt32(),                       // msgCount
            (*retQuery)[3].getUInt32(),                       // unreadMsgCount
            (*retQuery)[4].getUInt32(),                       // contactID
            (*retQuery)[5].getString(),                       // snippet
            static_cast<SMSType>((*retQuery)[6].getUInt32()), // type/last-dir
            (*retQuery)[5].getUInt32(),                       // numberID
            (*retQuery)[6].getString(),                       // snippet
            static_cast<SMSType>((*retQuery)[7].getUInt32()), // type/last-dir
        });
    } while (retQuery->nextRow());
}


@@ 117,6 123,9 @@ std::vector<ThreadsTableRow> ThreadsTable::getLimitOffsetByField(uint32_t offset
    case ThreadsTableFields ::ContactID:
        fieldName = "contact_id";
        break;
    case ThreadsTableFields ::NumberID:
        fieldName = "number_id";
        break;
    default:
        return std::vector<ThreadsTableRow>();
    }

M module-db/Tables/ThreadsTable.hpp => module-db/Tables/ThreadsTable.hpp +3 -0
@@ 13,6 13,7 @@ struct ThreadsTableRow : public Record
    uint32_t msgCount       = 0;
    uint32_t unreadMsgCount = 0;
    uint32_t contactID      = DB_ID_NONE;
    uint32_t numberID       = DB_ID_NONE;
    UTF8 snippet;
    SMSType type = SMSType::UNKNOWN;
};


@@ 23,6 24,7 @@ enum class ThreadsTableFields
    MsgCount,
    MsgRead,
    ContactID,
    NumberID,
    Type
};



@@ 58,6 60,7 @@ class ThreadsTable : public Table<ThreadsTableRow, ThreadsTableFields>
                                   "msg_count INTEGER,"
                                   "read INTEGER,"
                                   "contact_id INTEGER,"
                                   "number_id INTEGER,"
                                   "snippet TEXT NOT NULL,"
                                   "last_dir INTEGER"
                                   ");";

A module-db/queries/phonebook/QueryNumberGetByID.cpp => module-db/queries/phonebook/QueryNumberGetByID.cpp +34 -0
@@ 0,0 1,34 @@
#include "QueryNumberGetByID.hpp"

#include <Common/Query.hpp>
#include <PhoneNumber.hpp>

#include <string>

using namespace db::query;

NumberGetByID::NumberGetByID(std::uint32_t id) : Query(Query::Type::Read), id(id)
{}

NumberGetByIDResult::NumberGetByIDResult(utils::PhoneNumber::View number) : number(std::move(number))
{}

[[nodiscard]] auto NumberGetByID::debugInfo() const -> std::string
{
    return "NumberGetByID";
}

[[nodiscard]] auto NumberGetByIDResult::debugInfo() const -> std::string
{
    return "NumberGetByIDResult";
}

auto NumberGetByID::getID() const noexcept -> std::uint32_t
{
    return id;
}

auto NumberGetByIDResult::getNumber() -> utils::PhoneNumber::View
{
    return std::move(number);
}

A module-db/queries/phonebook/QueryNumberGetByID.hpp => module-db/queries/phonebook/QueryNumberGetByID.hpp +33 -0
@@ 0,0 1,33 @@
#pragma once

#include <Common/Query.hpp>
#include <PhoneNumber.hpp>

#include <string>

namespace db::query
{

    class NumberGetByID : public Query
    {
      public:
        NumberGetByID(std::uint32_t id);
        [[nodiscard]] auto debugInfo() const -> std::string override;
        auto getID() const noexcept -> std::uint32_t;

      private:
        std::uint32_t id;
    };

    class NumberGetByIDResult : public QueryResult
    {
      public:
        NumberGetByIDResult(utils::PhoneNumber::View number);
        [[nodiscard]] auto debugInfo() const -> std::string override;
        auto getNumber() -> utils::PhoneNumber::View;

      private:
        utils::PhoneNumber::View number;
    };

}; // namespace db::query

A module-db/queries/sms/QueryThreadGetByNumber.cpp => module-db/queries/sms/QueryThreadGetByNumber.cpp +36 -0
@@ 0,0 1,36 @@
#include "QueryThreadGetByNumber.hpp"

#include <Interface/ThreadRecord.hpp>
#include <PhoneNumber.hpp>

#include <string>

namespace db::query
{
    ThreadGetByNumber::ThreadGetByNumber(utils::PhoneNumber::View number)
        : Query(Query::Type::Read), number(std::move(number))
    {}

    auto ThreadGetByNumber::getNumber() const -> const utils::PhoneNumber::View &
    {
        return number;
    }

    [[nodiscard]] auto ThreadGetByNumber::debugInfo() const -> std::string
    {
        return "ThreadGetByNumber";
    }

    ThreadGetByNumberResult::ThreadGetByNumberResult(ThreadRecord thread) : thread(std::move(thread))
    {}

    auto ThreadGetByNumberResult::getThread() -> ThreadRecord
    {
        return std::move(thread);
    }

    [[nodiscard]] auto ThreadGetByNumberResult::debugInfo() const -> std::string
    {
        return "ThreadGetByNumberResult";
    }
} // namespace db::query

A module-db/queries/sms/QueryThreadGetByNumber.hpp => module-db/queries/sms/QueryThreadGetByNumber.hpp +33 -0
@@ 0,0 1,33 @@
#pragma once

#include <Common/Query.hpp>
#include <Interface/ThreadRecord.hpp>
#include <PhoneNumber.hpp>

#include <string>

namespace db::query
{

    class ThreadGetByNumber : public Query
    {
        utils::PhoneNumber::View number;

      public:
        ThreadGetByNumber(utils::PhoneNumber::View number);
        auto getNumber() const -> const utils::PhoneNumber::View &;

        [[nodiscard]] auto debugInfo() const -> std::string override;
    };

    class ThreadGetByNumberResult : public QueryResult
    {
        ThreadRecord thread;

      public:
        ThreadGetByNumberResult(ThreadRecord record);
        [[nodiscard]] auto getThread() -> ThreadRecord;
        [[nodiscard]] auto debugInfo() const -> std::string override;
    };

}; // namespace db::query

M module-services/service-db/ServiceDB.cpp => module-services/service-db/ServiceDB.cpp +2 -2
@@ 341,8 341,8 @@ sys::Message_t ServiceDB::DataReceivedHandler(sys::DataMessage *msgl, sys::Respo
        auto ret  = contactRecordInterface->MatchByNumber(msg->numberView);

        if (ret.has_value()) {
            responseMsg = std::make_shared<DBContactNumberResponseMessage>(sys::ReturnCodes::Success,
                                                                           std::make_unique<ContactRecord>(*ret));
            responseMsg = std::make_shared<DBContactNumberResponseMessage>(
                sys::ReturnCodes::Success, std::make_unique<ContactRecord>(ret->contact));
        }
        else {
            responseMsg = std::make_shared<DBContactNumberResponseMessage>(sys::ReturnCodes::Failure,

M module-services/service-db/api/DBServiceAPI.cpp => module-services/service-db/api/DBServiceAPI.cpp +47 -0
@@ 10,6 10,11 @@
#include <messages/DBCalllogMessage.hpp>
#include <messages/DBCountryCodeMessage.hpp>
#include <messages/DBServiceMessage.hpp>
#include <messages/QueryMessage.hpp>

#include <Common/Query.hpp>
#include <queries/phonebook/QueryNumberGetByID.hpp>
#include <queries/sms/QueryThreadGetByNumber.hpp>

#include <ServiceDB.hpp>
#include <includes/DBServiceName.hpp>


@@ 191,6 196,27 @@ std::unique_ptr<ThreadRecord> DBServiceAPI::ThreadGetByContact(sys::Service *ser
    }
}

std::unique_ptr<ThreadRecord> DBServiceAPI::ThreadGetByNumber(sys::Service *serv,
                                                              const utils::PhoneNumber::View &phoneNumber,
                                                              std::uint32_t timeout)
{
    auto [code, msg] = DBServiceAPI::GetQueryWithReply(
        serv, db::Interface::Name::SMSThread, std::make_unique<db::query::ThreadGetByNumber>(phoneNumber), timeout);

    if (code == sys::ReturnCodes::Success && msg != nullptr) {
        auto queryResponse = dynamic_cast<db::QueryResponse *>(msg.get());
        assert(queryResponse != nullptr);

        auto threadResponse = queryResponse->getResult();
        auto threadResult   = dynamic_cast<db::query::ThreadGetByNumberResult *>(threadResponse.get());
        assert(threadResult != nullptr);

        return std::make_unique<ThreadRecord>(std::move(threadResult->getThread()));
    }

    return nullptr;
}

bool DBServiceAPI::ThreadRemove(sys::Service *serv, uint32_t id)
{
    std::shared_ptr<DBThreadMessage> msg = std::make_shared<DBThreadMessage>(MessageType::DBThreadRemove);


@@ 805,3 831,24 @@ bool DBServiceAPI::DBBackup(sys::Service *serv, std::string backupPath)
        return false;
    }
}

std::unique_ptr<utils::PhoneNumber::View> DBServiceAPI::GetNumberById(sys::Service *serv,
                                                                      std::uint32_t numberId,
                                                                      std::uint32_t timeout)
{
    auto [code, msg] = DBServiceAPI::GetQueryWithReply(
        serv, db::Interface::Name::Contact, std::make_unique<db::query::NumberGetByID>(numberId), timeout);

    if (code == sys::ReturnCodes::Success && msg != nullptr) {
        auto queryResponse = dynamic_cast<db::QueryResponse *>(msg.get());
        assert(queryResponse != nullptr);

        auto numberResponse = queryResponse->getResult();
        auto numberResult   = dynamic_cast<db::query::NumberGetByIDResult *>(numberResponse.get());
        assert(numberResult != nullptr);

        return std::make_unique<utils::PhoneNumber::View>(std::move(numberResult->getNumber()));
    }

    return nullptr;
}

M module-services/service-db/api/DBServiceAPI.hpp => module-services/service-db/api/DBServiceAPI.hpp +6 -0
@@ 56,6 56,9 @@ class DBServiceAPI

    static std::unique_ptr<ThreadRecord> ThreadGet(sys::Service *serv, uint32_t id);
    static std::unique_ptr<ThreadRecord> ThreadGetByContact(sys::Service *serv, uint32_t contactID);
    static std::unique_ptr<ThreadRecord> ThreadGetByNumber(sys::Service *serv,
                                                           const utils::PhoneNumber::View &phoneNumber,
                                                           std::uint32_t timeout);
    static bool ThreadRemove(sys::Service *serv, uint32_t id);
    static bool ThreadGetLimitOffset(sys::Service *serv, uint32_t offset, uint32_t limit);
    static uint32_t ThreadGetCount(sys::Service *serv, EntryState state = EntryState::ALL);


@@ 113,6 116,9 @@ class DBServiceAPI
                                                                     UTF8 primaryName,
                                                                     UTF8 alternativeName,
                                                                     UTF8 number);
    static std::unique_ptr<utils::PhoneNumber::View> GetNumberById(sys::Service *serv,
                                                                   std::uint32_t numberId,
                                                                   std::uint32_t timeout);
    static bool AlarmAdd(sys::Service *serv, const AlarmsRecord &rec);
    static bool AlarmRemove(sys::Service *serv, uint32_t id);
    static bool AlarmUpdate(sys::Service *serv, const AlarmsRecord &rec);