M module-db/CMakeLists.txt => module-db/CMakeLists.txt +2 -0
@@ 102,6 102,8 @@ set(SOURCES
queries/phonebook/QueryContactUpdate.cpp
queries/phonebook/QueryContactRemove.cpp
queries/phonebook/QueryNumberGetByID.cpp
+ queries/phonebook/QueryMergeContactsList.cpp
+ queries/phonebook/QueryCheckContactsListDuplicates.cpp
queries/alarms/QueryAlarmsAdd.cpp
queries/alarms/QueryAlarmsRemove.cpp
queries/alarms/QueryAlarmsEdit.cpp
M module-db/Interface/ContactRecord.cpp => module-db/Interface/ContactRecord.cpp +77 -0
@@ 6,6 6,8 @@
#include "queries/phonebook/QueryContactGetByID.hpp"
#include "queries/phonebook/QueryContactUpdate.hpp"
#include "queries/phonebook/QueryContactRemove.hpp"
+#include "queries/phonebook/QueryMergeContactsList.hpp"
+#include "queries/phonebook/QueryCheckContactsListDuplicates.hpp"
#include <Utils.hpp>
#include <queries/phonebook/QueryContactGet.hpp>
@@ 159,6 161,12 @@ auto ContactRecordInterface::runQuery(std::shared_ptr<db::Query> query) -> std::
else if (typeid(*query) == typeid(db::query::NumberGetByID)) {
return numberGetByIdQuery(query);
}
+ else if (typeid(*query) == typeid(db::query::MergeContactsList)) {
+ return mergeContactsListQuery(query);
+ }
+ else if (typeid(*query) == typeid(db::query::CheckContactsListDuplicates)) {
+ return checkContactsListDuplicatesQuery(query);
+ }
else {
error_db_data("Unexpected query type.");
return nullptr;
@@ 357,6 365,26 @@ auto ContactRecordInterface::numberGetByIdQuery(std::shared_ptr<db::Query> query
return response;
}
+auto ContactRecordInterface::mergeContactsListQuery(std::shared_ptr<db::Query> query)
+ -> std::unique_ptr<db::QueryResult>
+{
+ auto mergeQuery = static_cast<db::query::MergeContactsList *>(query.get());
+ auto ret = ContactRecordInterface::MergeContactsList(mergeQuery->getContactsList());
+ auto response = std::make_unique<db::query::MergeContactsListResult>(ret);
+ response->setRequestQuery(query);
+ return response;
+}
+
+auto ContactRecordInterface::checkContactsListDuplicatesQuery(std::shared_ptr<db::Query> query)
+ -> std::unique_ptr<db::QueryResult>
+{
+ auto mergeQuery = static_cast<db::query::CheckContactsListDuplicates *>(query.get());
+ auto response = std::make_unique<db::query::CheckContactsListDuplicatesResult>(
+ std::move(ContactRecordInterface::CheckContactsListDuplicates(mergeQuery->getContactsList())));
+ response->setRequestQuery(query);
+ return response;
+}
+
auto ContactRecordInterface::splitNumberIDs(const std::string &numberIDs) -> std::vector<std::uint32_t>
{
std::stringstream source(numberIDs);
@@ 1172,3 1200,52 @@ auto ContactRecordInterface::GetNumbersIdsByContact(std::uint32_t contactId) ->
}
return numbersIds;
}
+
+auto ContactRecordInterface::MergeContactsList(std::vector<ContactRecord> &contacts) -> bool
+{
+ std::vector<ContactNumberHolder> contactNumberHolders;
+ auto numberMatcher = buildNumberMatcher(contactNumberHolders);
+
+ for (auto &contact : contacts) {
+ // Important: Comparing only single number contacts
+ if (contact.numbers.size() > 1) {
+ LOG_WARN("Contact with multiple numbers detected - ignoring all numbers except first");
+ }
+ auto matchedNumber = numberMatcher.bestMatch(contact.numbers[0].number, utils::PhoneNumber::Match::POSSIBLE);
+
+ if (matchedNumber == numberMatcher.END) {
+ if (!Add(contact)) {
+ LOG_ERROR("Contacts list merge fail when adding the contact.");
+ return false;
+ }
+ }
+ else {
+ // Complete override of the contact data
+ contact.ID = matchedNumber->getContactID();
+ Update(contact);
+ // Rebuild number matcher
+ numberMatcher = buildNumberMatcher(contactNumberHolders);
+ }
+ }
+ return true;
+}
+
+auto ContactRecordInterface::CheckContactsListDuplicates(std::vector<ContactRecord> &contacts)
+ -> std::vector<ContactRecord>
+{
+ std::vector<ContactRecord> duplicates;
+ std::vector<ContactNumberHolder> contactNumberHolders;
+ auto numberMatcher = buildNumberMatcher(contactNumberHolders);
+
+ for (auto &contact : contacts) {
+ // Important: Comparing only single number contacts
+ if (contact.numbers.size() > 1) {
+ LOG_WARN("Contact with multiple numbers detected - ignoring all numbers except first");
+ }
+ auto matchedNumber = numberMatcher.bestMatch(contact.numbers[0].number, utils::PhoneNumber::Match::POSSIBLE);
+ if (matchedNumber != numberMatcher.END) {
+ duplicates.push_back(contact);
+ }
+ }
+ return duplicates;
+}
M module-db/Interface/ContactRecord.hpp => module-db/Interface/ContactRecord.hpp +18 -0
@@ 220,6 220,22 @@ class ContactRecordInterface : public RecordInterface<ContactRecord, ContactReco
auto GetNumbersIdsByContact(std::uint32_t contactId) -> std::vector<std::uint32_t>;
+ /**
+ * @brief Merge contacts list with overriding the duplicates in contacts DB
+ *
+ * @param contacts vector of contacts with single number
+ * @return boolean status
+ */
+ auto MergeContactsList(std::vector<ContactRecord> &contacts) -> bool;
+
+ /**
+ * @brief Check which contacts in vector are duplicating contacts in DB
+ *
+ * @param contacts vector of contacts with single number
+ * @return vector of contacts with numbers appearing in contacts DB
+ */
+ auto CheckContactsListDuplicates(std::vector<ContactRecord> &contacts) -> std::vector<ContactRecord>;
+
private:
ContactsDB *contactDB;
@@ 248,4 264,6 @@ class ContactRecordInterface : public RecordInterface<ContactRecord, ContactReco
auto updateQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
auto removeQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
auto numberGetByIdQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
+ auto mergeContactsListQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
+ auto checkContactsListDuplicatesQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
};
A module-db/doc/contacts_import.md => module-db/doc/contacts_import.md +3 -0
@@ 0,0 1,3 @@
+## Sequence of contacts import to contacts DB
+
+<
\ No newline at end of file
A module-db/doc/contacts_import.puml => module-db/doc/contacts_import.puml +30 -0
@@ 0,0 1,30 @@
+@startuml
+
+participant "Application" as app
+participant "ContactRecord" as rec
+participant "contacts DB" as db
+
+== Checking for duplicates ==
+
+app -> rec : contacts to check\n(query::CheckContactsListDuplicates)
+rec -> db : get numbers
+db -> rec
+rec -> rec : numbers comparison
+rec -> app : duplicated contacts
+
+== Merging contacts list to DB ==
+
+app -> rec : contacts to merge\n(query::MergeContactsList)
+rec -> db : get numbers
+db -> rec
+rec -> rec : numbers comparison
+
+group number not found
+ rec -> db : Add contact
+end
+group number found in db
+ rec -> db : Update contact by overriding old data
+end
+rec -> app : status response
+
+@enduml<
\ No newline at end of file
A module-db/doc/contacts_import.svg => module-db/doc/contacts_import.svg +40 -0
@@ 0,0 1,40 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="627px" preserveAspectRatio="none" style="width:658px;height:627px;background:#FFFFFF;" version="1.1" viewBox="0 0 658 627" width="658px" zoomAndPan="magnify"><defs><filter height="300%" id="f12hef58xpqd4m" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><rect fill="#FFFFFF" filter="url(#f12hef58xpqd4m)" height="46.2656" style="stroke:#000000;stroke-width:2.0;" width="397.5" x="243.5" y="431.7578"/><rect fill="#FFFFFF" filter="url(#f12hef58xpqd4m)" height="46.2656" style="stroke:#000000;stroke-width:2.0;" width="397.5" x="243.5" y="492.0234"/><line style="stroke:#A80036;stroke-width:1.0;stroke-dasharray:5.0,5.0;" x1="51" x2="51" y1="40.2969" y2="584.4219"/><line style="stroke:#A80036;stroke-width:1.0;stroke-dasharray:5.0,5.0;" x1="314.5" x2="314.5" y1="40.2969" y2="584.4219"/><line style="stroke:#A80036;stroke-width:1.0;stroke-dasharray:5.0,5.0;" x1="579" x2="579" y1="40.2969" y2="584.4219"/><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="89" x="5" y="5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="75" x="12" y="24.9951">Application</text><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="89" x="5" y="583.4219"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="75" x="12" y="603.417">Application</text><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="118" x="253.5" y="5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="104" x="260.5" y="24.9951">ContactRecord</text><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="118" x="253.5" y="583.4219"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="104" x="260.5" y="603.417">ContactRecord</text><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="99" x="528" y="5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="85" x="535" y="24.9951">contacts DB</text><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="99" x="528" y="583.4219"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="85" x="535" y="603.417">contacts DB</text><rect fill="#EEEEEE" filter="url(#f12hef58xpqd4m)" height="3" style="stroke:#EEEEEE;stroke-width:1.0;" width="651" x="0" y="70.8633"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="651" y1="70.8633" y2="70.8633"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="651" y1="73.8633" y2="73.8633"/><rect fill="#EEEEEE" filter="url(#f12hef58xpqd4m)" height="23.1328" style="stroke:#000000;stroke-width:2.0;" width="195" x="228" y="60.2969"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="176" x="234" y="76.3638">Checking for duplicates</text><polygon fill="#A80036" points="302.5,125.6953,312.5,129.6953,302.5,133.6953,306.5,129.6953" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="51.5" x2="308.5" y1="129.6953" y2="129.6953"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="113" x="58.5" y="109.4966">contacts to check</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="239" x="58.5" y="124.6294">(query::CheckContactsListDuplicates)</text><polygon fill="#A80036" points="567.5,154.8281,577.5,158.8281,567.5,162.8281,571.5,158.8281" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="573.5" y1="158.8281" y2="158.8281"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="82" x="321.5" y="153.7622">get numbers</text><polygon fill="#A80036" points="325.5,168.8281,315.5,172.8281,325.5,176.8281,321.5,172.8281" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="319.5" x2="578.5" y1="172.8281" y2="172.8281"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="356.5" y1="201.9609" y2="201.9609"/><line style="stroke:#A80036;stroke-width:1.0;" x1="356.5" x2="356.5" y1="201.9609" y2="214.9609"/><line style="stroke:#A80036;stroke-width:1.0;" x1="315.5" x2="356.5" y1="214.9609" y2="214.9609"/><polygon fill="#A80036" points="325.5,210.9609,315.5,214.9609,325.5,218.9609,321.5,214.9609" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="136" x="321.5" y="196.895">numbers comparison</text><polygon fill="#A80036" points="62.5,240.0938,52.5,244.0938,62.5,248.0938,58.5,244.0938" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="56.5" x2="313.5" y1="244.0938" y2="244.0938"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="125" x="68.5" y="239.0278">duplicated contacts</text><rect fill="#EEEEEE" filter="url(#f12hef58xpqd4m)" height="3" style="stroke:#EEEEEE;stroke-width:1.0;" width="651" x="0" y="272.6602"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="651" y1="272.6602" y2="272.6602"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="651" y1="275.6602" y2="275.6602"/><rect fill="#EEEEEE" filter="url(#f12hef58xpqd4m)" height="23.1328" style="stroke:#000000;stroke-width:2.0;" width="221" x="215" y="262.0938"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="202" x="221" y="278.1606">Merging contacts list to DB</text><polygon fill="#A80036" points="302.5,327.4922,312.5,331.4922,302.5,335.4922,306.5,331.4922" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="51.5" x2="308.5" y1="331.4922" y2="331.4922"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="118" x="58.5" y="311.2935">contacts to merge</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="173" x="58.5" y="326.4263">(query::MergeContactsList)</text><polygon fill="#A80036" points="567.5,356.625,577.5,360.625,567.5,364.625,571.5,360.625" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="573.5" y1="360.625" y2="360.625"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="82" x="321.5" y="355.5591">get numbers</text><polygon fill="#A80036" points="325.5,370.625,315.5,374.625,325.5,378.625,321.5,374.625" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="319.5" x2="578.5" y1="374.625" y2="374.625"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="356.5" y1="403.7578" y2="403.7578"/><line style="stroke:#A80036;stroke-width:1.0;" x1="356.5" x2="356.5" y1="403.7578" y2="416.7578"/><line style="stroke:#A80036;stroke-width:1.0;" x1="315.5" x2="356.5" y1="416.7578" y2="416.7578"/><polygon fill="#A80036" points="325.5,412.7578,315.5,416.7578,325.5,420.7578,321.5,416.7578" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="136" x="321.5" y="398.6919">numbers comparison</text><path d="M243.5,431.7578 L422.5,431.7578 L422.5,438.7578 L412.5,448.7578 L243.5,448.7578 L243.5,431.7578 " fill="#EEEEEE" style="stroke:#000000;stroke-width:1.0;"/><rect fill="none" height="46.2656" style="stroke:#000000;stroke-width:2.0;" width="397.5" x="243.5" y="431.7578"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="134" x="258.5" y="444.8247">number not found</text><polygon fill="#A80036" points="567.5,466.0234,577.5,470.0234,567.5,474.0234,571.5,470.0234" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="573.5" y1="470.0234" y2="470.0234"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="77" x="321.5" y="464.9575">Add contact</text><path d="M243.5,492.0234 L436.5,492.0234 L436.5,499.0234 L426.5,509.0234 L243.5,509.0234 L243.5,492.0234 " fill="#EEEEEE" style="stroke:#000000;stroke-width:1.0;"/><rect fill="none" height="46.2656" style="stroke:#000000;stroke-width:2.0;" width="397.5" x="243.5" y="492.0234"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="148" x="258.5" y="505.0903">number found in db</text><polygon fill="#A80036" points="567.5,526.2891,577.5,530.2891,567.5,534.2891,571.5,530.2891" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="573.5" y1="530.2891" y2="530.2891"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="241" x="321.5" y="525.2231">Update contact by overriding old data</text><polygon fill="#A80036" points="62.5,562.4219,52.5,566.4219,62.5,570.4219,58.5,566.4219" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="56.5" x2="313.5" y1="566.4219" y2="566.4219"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="103" x="68.5" y="561.356">status response</text><!--MD5=[921bcca0682cb10fe9b051cd54a58a3c]
+@startuml
+
+participant "Application" as app
+participant "ContactRecord" as rec
+participant "contacts DB" as db
+
+== Checking for duplicates ==
+
+app -> rec : contacts to check\n(query::CheckContactsListDuplicates)
+rec -> db : get numbers
+db -> rec
+rec -> rec : numbers comparison
+rec -> app : duplicated contacts
+
+== Merging contacts list to DB ==
+
+app -> rec : contacts to merge\n(query::MergeContactsList)
+rec -> db : get numbers
+db -> rec
+rec -> rec : numbers comparison
+
+group number not found
+ rec -> db : Add contact
+end
+group number found in db
+ rec -> db : Update contact by overriding old data
+end
+rec -> app : status response
+
+@enduml
+
+PlantUML version 1.2021.7(Sun May 23 14:40:07 CEST 2021)
+(GPL source distribution)
+Java Runtime: OpenJDK Runtime Environment
+JVM: OpenJDK 64-Bit Server VM
+Default Encoding: UTF-8
+Language: pl
+Country: PL
+--></g></svg><
\ No newline at end of file
A module-db/queries/phonebook/QueryCheckContactsListDuplicates.cpp => module-db/queries/phonebook/QueryCheckContactsListDuplicates.cpp +35 -0
@@ 0,0 1,35 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "QueryCheckContactsListDuplicates.hpp"
+#include <string>
+
+using namespace db::query;
+
+CheckContactsListDuplicates::CheckContactsListDuplicates(std::vector<ContactRecord> contacts)
+ : Query(Query::Type::Read), contacts(std::move(contacts))
+{}
+
+std::vector<ContactRecord> &CheckContactsListDuplicates::getContactsList()
+{
+ return contacts;
+}
+
+[[nodiscard]] auto CheckContactsListDuplicates::debugInfo() const -> std::string
+{
+ return "CheckContactsListDuplicates";
+}
+
+CheckContactsListDuplicatesResult::CheckContactsListDuplicatesResult(std::vector<ContactRecord> duplicates)
+ : duplicates(std::move(duplicates))
+{}
+
+std::vector<ContactRecord> &CheckContactsListDuplicatesResult::getDuplicates()
+{
+ return duplicates;
+}
+
+[[nodiscard]] auto CheckContactsListDuplicatesResult::debugInfo() const -> std::string
+{
+ return "CheckContactsListDuplicatesResult";
+}
A module-db/queries/phonebook/QueryCheckContactsListDuplicates.hpp => module-db/queries/phonebook/QueryCheckContactsListDuplicates.hpp +40 -0
@@ 0,0 1,40 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "Common/Query.hpp"
+#include <queries/RecordQuery.hpp>
+#include <queries/Filter.hpp>
+#include <Interface/ContactRecord.hpp>
+
+#include <string>
+
+namespace db::query
+{
+
+ class CheckContactsListDuplicates : public Query
+ {
+ public:
+ CheckContactsListDuplicates(std::vector<ContactRecord> contacts);
+ [[nodiscard]] auto debugInfo() const -> std::string override;
+
+ std::vector<ContactRecord> &getContactsList();
+
+ private:
+ std::vector<ContactRecord> contacts;
+ };
+
+ class CheckContactsListDuplicatesResult : public QueryResult
+ {
+ public:
+ CheckContactsListDuplicatesResult(std::vector<ContactRecord> duplicates);
+ std::vector<ContactRecord> &getDuplicates();
+
+ [[nodiscard]] auto debugInfo() const -> std::string override;
+
+ private:
+ std::vector<ContactRecord> duplicates;
+ };
+
+}; // namespace db::query
A module-db/queries/phonebook/QueryMergeContactsList.cpp => module-db/queries/phonebook/QueryMergeContactsList.cpp +29 -0
@@ 0,0 1,29 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "QueryMergeContactsList.hpp"
+#include <string>
+
+using namespace db::query;
+
+MergeContactsList::MergeContactsList(std::vector<ContactRecord> contacts)
+ : Query(Query::Type::Read), contacts(std::move(contacts))
+{}
+
+std::vector<ContactRecord> &MergeContactsList::getContactsList()
+{
+ return contacts;
+}
+
+MergeContactsListResult::MergeContactsListResult(bool result) : result(result)
+{}
+
+[[nodiscard]] auto MergeContactsList::debugInfo() const -> std::string
+{
+ return "MergeContactsList";
+}
+
+[[nodiscard]] auto MergeContactsListResult::debugInfo() const -> std::string
+{
+ return "MergeContactsListResult";
+}
A module-db/queries/phonebook/QueryMergeContactsList.hpp => module-db/queries/phonebook/QueryMergeContactsList.hpp +42 -0
@@ 0,0 1,42 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "Common/Query.hpp"
+#include <queries/RecordQuery.hpp>
+#include <queries/Filter.hpp>
+#include <Interface/ContactRecord.hpp>
+
+#include <string>
+
+namespace db::query
+{
+
+ class MergeContactsList : public Query
+ {
+ public:
+ MergeContactsList(std::vector<ContactRecord> contacts);
+ [[nodiscard]] auto debugInfo() const -> std::string override;
+
+ std::vector<ContactRecord> &getContactsList();
+
+ private:
+ std::vector<ContactRecord> contacts;
+ };
+
+ class MergeContactsListResult : public QueryResult
+ {
+ public:
+ MergeContactsListResult(bool result);
+ [[nodiscard]] auto getResult() const noexcept -> bool
+ {
+ return result;
+ }
+ [[nodiscard]] auto debugInfo() const -> std::string override;
+
+ private:
+ bool result = false;
+ };
+
+}; // namespace db::query
M module-db/tests/ContactsRecord_tests.cpp => module-db/tests/ContactsRecord_tests.cpp +186 -0
@@ 396,3 396,189 @@ TEST_CASE("Contact record numbers update")
Database::deinitialize();
}
+
+TEST_CASE("Contacts list merge")
+{
+ Database::initialize();
+ const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
+ RemoveDbFiles(contactsPath.stem());
+
+ ContactsDB contactDB(contactsPath.c_str());
+ REQUIRE(contactDB.isInitialized());
+
+ // Preparation of DB initial state
+ auto records = ContactRecordInterface(&contactDB);
+ std::array<std::pair<std::string, std::string>, 3> rawContactsInitial = {
+ {{"600100100", "test1"}, {"600100200", "test2"}, {"600100300", "test3"}}};
+ for (auto &rawContact : rawContactsInitial) {
+ ContactRecord record;
+ record.primaryName = rawContact.second;
+ record.numbers = std::vector<ContactRecord::Number>({ContactRecord::Number(rawContact.first, std::string(""))});
+ REQUIRE(records.Add(record));
+ }
+
+ SECTION("Merge contacts without overlaps")
+ {
+ std::array<std::pair<std::string, std::string>, 3> rawContactsToAdd = {
+ {{"600100400", "test4"}, {"600100500", "test5"}, {"600100600", "test6"}}};
+
+ // Prepare contacts list to merge
+ std::vector<ContactRecord> contacts;
+ for (auto &rawContact : rawContactsToAdd) {
+ ContactRecord record;
+ record.primaryName = rawContact.second;
+ record.numbers =
+ std::vector<ContactRecord::Number>({ContactRecord::Number(rawContact.first, std::string(""))});
+ contacts.push_back(record);
+ }
+ REQUIRE(records.MergeContactsList(contacts));
+
+ // Validate if non-overlapping were appended to DB
+ REQUIRE(records.GetCount() == (rawContactsInitial.size() + rawContactsToAdd.size()));
+
+ auto validatationRecord = records.GetByID(1);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsInitial[0].first);
+ REQUIRE(validatationRecord.primaryName == rawContactsInitial[0].second);
+ validatationRecord = records.GetByID(2);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsInitial[1].first);
+ REQUIRE(validatationRecord.primaryName == rawContactsInitial[1].second);
+ validatationRecord = records.GetByID(3);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsInitial[2].first);
+ REQUIRE(validatationRecord.primaryName == rawContactsInitial[2].second);
+ validatationRecord = records.GetByID(4);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsToAdd[0].first);
+ REQUIRE(validatationRecord.primaryName == rawContactsToAdd[0].second);
+ validatationRecord = records.GetByID(5);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsToAdd[1].first);
+ REQUIRE(validatationRecord.primaryName == rawContactsToAdd[1].second);
+ validatationRecord = records.GetByID(6);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsToAdd[2].first);
+ REQUIRE(validatationRecord.primaryName == rawContactsToAdd[2].second);
+ }
+
+ SECTION("Merge contacts with numbers overlaps")
+ {
+ REQUIRE(records.GetCount() == rawContactsInitial.size());
+
+ std::array<std::pair<std::string, std::string>, 3> rawContactsOverlapping = {
+ {{rawContactsInitial[1].first, "test7"}, {"600100800", "test8"}, {rawContactsInitial[0].first, "test9"}}};
+ constexpr auto numberOfNewContacts = 1;
+
+ // Prepare contacts list to merge
+ std::vector<ContactRecord> contacts;
+ for (auto &rawContact : rawContactsOverlapping) {
+ ContactRecord record;
+ record.primaryName = rawContact.second;
+ record.numbers =
+ std::vector<ContactRecord::Number>({ContactRecord::Number(rawContact.first, std::string(""))});
+ contacts.push_back(record);
+ }
+ REQUIRE(records.MergeContactsList(contacts));
+
+ REQUIRE(records.GetCount() == (rawContactsInitial.size() + numberOfNewContacts));
+
+ // Overlapping contacts replaced with same ID
+ auto validatationRecord = records.GetByID(1);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsInitial[0].first);
+ REQUIRE(validatationRecord.primaryName == rawContactsOverlapping[2].second);
+ validatationRecord = records.GetByID(2);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsInitial[1].first);
+ REQUIRE(validatationRecord.primaryName == rawContactsOverlapping[0].second);
+ // Non-overlapping contact left untouched
+ validatationRecord = records.GetByID(3);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsInitial[2].first);
+ REQUIRE(validatationRecord.primaryName == rawContactsInitial[2].second);
+ // Non-overlapping new contact added
+ validatationRecord = records.GetByID(4);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsOverlapping[1].first);
+ REQUIRE(validatationRecord.primaryName == rawContactsOverlapping[1].second);
+ }
+
+ Database::deinitialize();
+}
+
+TEST_CASE("Contacts list merge - advanced cases")
+{
+ Database::initialize();
+ const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
+ RemoveDbFiles(contactsPath.stem());
+
+ ContactsDB contactDB(contactsPath.c_str());
+ REQUIRE(contactDB.isInitialized());
+
+ // Preparation of DB initial state
+ auto records = ContactRecordInterface(&contactDB);
+ // 3 numbers in single contact
+ std::array<std::string, 3> numbers = {"600100100", "600100200", "600100300"};
+ ContactRecord record;
+ record.primaryName = "test";
+ for (auto &number : numbers) {
+ record.numbers.push_back({ContactRecord::Number(number, std::string(""))});
+ }
+ REQUIRE(records.Add(record));
+
+ SECTION("Compared number is secondary number")
+ {
+ std::pair<std::string, std::string> rawContact = {"600100200", "test2"};
+ std::vector<ContactRecord> contacts;
+
+ // Prepare contacts list to merge
+ ContactRecord record;
+ record.primaryName = rawContact.second;
+ record.numbers = std::vector<ContactRecord::Number>({ContactRecord::Number(rawContact.first, std::string(""))});
+ contacts.push_back(record);
+ REQUIRE(records.MergeContactsList(contacts));
+
+ REQUIRE(records.GetCount() == 1);
+
+ // First contact replaced
+ auto validatationRecord = records.GetByID(1);
+ REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContact.first);
+ REQUIRE(validatationRecord.primaryName == rawContact.second);
+ }
+}
+
+TEST_CASE("Contacts list duplicates search")
+{
+ Database::initialize();
+ const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
+ RemoveDbFiles(contactsPath.stem());
+
+ ContactsDB contactDB(contactsPath.c_str());
+ REQUIRE(contactDB.isInitialized());
+
+ // Preparation of DB initial state
+ auto records = ContactRecordInterface(&contactDB);
+ std::array<std::pair<std::string, std::string>, 3> rawContactsInitial = {
+ {{"600100100", "test1"}, {"600100200", "test2"}, {"600100300", "test3"}}};
+ for (auto &rawContact : rawContactsInitial) {
+ ContactRecord record;
+ record.primaryName = rawContact.second;
+ record.numbers = std::vector<ContactRecord::Number>({ContactRecord::Number(rawContact.first, std::string(""))});
+ REQUIRE(records.Add(record));
+ }
+
+ // Prepare contacts list to compare with DB
+ std::array<std::pair<std::string, std::string>, 3> rawContactsToCheck = {
+ {rawContactsInitial[2], {"600100500", "test5"}, rawContactsInitial[0]}};
+ constexpr auto numOfDuplicatedContacts = 2;
+
+ std::vector<ContactRecord> contacts;
+ for (auto &rawContact : rawContactsToCheck) {
+ ContactRecord record;
+ record.primaryName = rawContact.second;
+ record.numbers = std::vector<ContactRecord::Number>({ContactRecord::Number(rawContact.first, std::string(""))});
+ contacts.push_back(record);
+ }
+ auto duplicates = records.CheckContactsListDuplicates(contacts);
+
+ REQUIRE(duplicates.size() == numOfDuplicatedContacts);
+
+ REQUIRE(duplicates[0].numbers[0].number.getEntered() == rawContactsToCheck[0].first);
+ REQUIRE(duplicates[0].primaryName == rawContactsToCheck[0].second);
+
+ REQUIRE(duplicates[1].numbers[0].number.getEntered() == rawContactsToCheck[2].first);
+ REQUIRE(duplicates[1].primaryName == rawContactsToCheck[2].second);
+
+ Database::deinitialize();
+}
A out/module-db/doc/contacts_import/contacts_import.svg => out/module-db/doc/contacts_import/contacts_import.svg +40 -0
@@ 0,0 1,40 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="627px" preserveAspectRatio="none" style="width:658px;height:627px;background:#FFFFFF;" version="1.1" viewBox="0 0 658 627" width="658px" zoomAndPan="magnify"><defs><filter height="300%" id="f12hef58xpqd4m" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><rect fill="#FFFFFF" filter="url(#f12hef58xpqd4m)" height="46.2656" style="stroke:#000000;stroke-width:2.0;" width="397.5" x="243.5" y="431.7578"/><rect fill="#FFFFFF" filter="url(#f12hef58xpqd4m)" height="46.2656" style="stroke:#000000;stroke-width:2.0;" width="397.5" x="243.5" y="492.0234"/><line style="stroke:#A80036;stroke-width:1.0;stroke-dasharray:5.0,5.0;" x1="51" x2="51" y1="40.2969" y2="584.4219"/><line style="stroke:#A80036;stroke-width:1.0;stroke-dasharray:5.0,5.0;" x1="314.5" x2="314.5" y1="40.2969" y2="584.4219"/><line style="stroke:#A80036;stroke-width:1.0;stroke-dasharray:5.0,5.0;" x1="579" x2="579" y1="40.2969" y2="584.4219"/><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="89" x="5" y="5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="75" x="12" y="24.9951">Application</text><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="89" x="5" y="583.4219"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="75" x="12" y="603.417">Application</text><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="118" x="253.5" y="5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="104" x="260.5" y="24.9951">ContactRecord</text><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="118" x="253.5" y="583.4219"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="104" x="260.5" y="603.417">ContactRecord</text><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="99" x="528" y="5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="85" x="535" y="24.9951">contacts DB</text><rect fill="#FEFECE" filter="url(#f12hef58xpqd4m)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="99" x="528" y="583.4219"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="85" x="535" y="603.417">contacts DB</text><rect fill="#EEEEEE" filter="url(#f12hef58xpqd4m)" height="3" style="stroke:#EEEEEE;stroke-width:1.0;" width="651" x="0" y="70.8633"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="651" y1="70.8633" y2="70.8633"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="651" y1="73.8633" y2="73.8633"/><rect fill="#EEEEEE" filter="url(#f12hef58xpqd4m)" height="23.1328" style="stroke:#000000;stroke-width:2.0;" width="195" x="228" y="60.2969"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="176" x="234" y="76.3638">Checking for duplicates</text><polygon fill="#A80036" points="302.5,125.6953,312.5,129.6953,302.5,133.6953,306.5,129.6953" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="51.5" x2="308.5" y1="129.6953" y2="129.6953"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="113" x="58.5" y="109.4966">contacts to check</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="239" x="58.5" y="124.6294">(query::CheckContactsListDuplicates)</text><polygon fill="#A80036" points="567.5,154.8281,577.5,158.8281,567.5,162.8281,571.5,158.8281" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="573.5" y1="158.8281" y2="158.8281"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="82" x="321.5" y="153.7622">get numbers</text><polygon fill="#A80036" points="325.5,168.8281,315.5,172.8281,325.5,176.8281,321.5,172.8281" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="319.5" x2="578.5" y1="172.8281" y2="172.8281"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="356.5" y1="201.9609" y2="201.9609"/><line style="stroke:#A80036;stroke-width:1.0;" x1="356.5" x2="356.5" y1="201.9609" y2="214.9609"/><line style="stroke:#A80036;stroke-width:1.0;" x1="315.5" x2="356.5" y1="214.9609" y2="214.9609"/><polygon fill="#A80036" points="325.5,210.9609,315.5,214.9609,325.5,218.9609,321.5,214.9609" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="136" x="321.5" y="196.895">numbers comparison</text><polygon fill="#A80036" points="62.5,240.0938,52.5,244.0938,62.5,248.0938,58.5,244.0938" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="56.5" x2="313.5" y1="244.0938" y2="244.0938"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="125" x="68.5" y="239.0278">duplicated contacts</text><rect fill="#EEEEEE" filter="url(#f12hef58xpqd4m)" height="3" style="stroke:#EEEEEE;stroke-width:1.0;" width="651" x="0" y="272.6602"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="651" y1="272.6602" y2="272.6602"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="651" y1="275.6602" y2="275.6602"/><rect fill="#EEEEEE" filter="url(#f12hef58xpqd4m)" height="23.1328" style="stroke:#000000;stroke-width:2.0;" width="221" x="215" y="262.0938"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="202" x="221" y="278.1606">Merging contacts list to DB</text><polygon fill="#A80036" points="302.5,327.4922,312.5,331.4922,302.5,335.4922,306.5,331.4922" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="51.5" x2="308.5" y1="331.4922" y2="331.4922"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="118" x="58.5" y="311.2935">contacts to merge</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="173" x="58.5" y="326.4263">(query::MergeContactsList)</text><polygon fill="#A80036" points="567.5,356.625,577.5,360.625,567.5,364.625,571.5,360.625" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="573.5" y1="360.625" y2="360.625"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="82" x="321.5" y="355.5591">get numbers</text><polygon fill="#A80036" points="325.5,370.625,315.5,374.625,325.5,378.625,321.5,374.625" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="319.5" x2="578.5" y1="374.625" y2="374.625"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="356.5" y1="403.7578" y2="403.7578"/><line style="stroke:#A80036;stroke-width:1.0;" x1="356.5" x2="356.5" y1="403.7578" y2="416.7578"/><line style="stroke:#A80036;stroke-width:1.0;" x1="315.5" x2="356.5" y1="416.7578" y2="416.7578"/><polygon fill="#A80036" points="325.5,412.7578,315.5,416.7578,325.5,420.7578,321.5,416.7578" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="136" x="321.5" y="398.6919">numbers comparison</text><path d="M243.5,431.7578 L422.5,431.7578 L422.5,438.7578 L412.5,448.7578 L243.5,448.7578 L243.5,431.7578 " fill="#EEEEEE" style="stroke:#000000;stroke-width:1.0;"/><rect fill="none" height="46.2656" style="stroke:#000000;stroke-width:2.0;" width="397.5" x="243.5" y="431.7578"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="134" x="258.5" y="444.8247">number not found</text><polygon fill="#A80036" points="567.5,466.0234,577.5,470.0234,567.5,474.0234,571.5,470.0234" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="573.5" y1="470.0234" y2="470.0234"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="77" x="321.5" y="464.9575">Add contact</text><path d="M243.5,492.0234 L436.5,492.0234 L436.5,499.0234 L426.5,509.0234 L243.5,509.0234 L243.5,492.0234 " fill="#EEEEEE" style="stroke:#000000;stroke-width:1.0;"/><rect fill="none" height="46.2656" style="stroke:#000000;stroke-width:2.0;" width="397.5" x="243.5" y="492.0234"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="148" x="258.5" y="505.0903">number found in db</text><polygon fill="#A80036" points="567.5,526.2891,577.5,530.2891,567.5,534.2891,571.5,530.2891" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="314.5" x2="573.5" y1="530.2891" y2="530.2891"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="241" x="321.5" y="525.2231">Update contact by overriding old data</text><polygon fill="#A80036" points="62.5,562.4219,52.5,566.4219,62.5,570.4219,58.5,566.4219" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="56.5" x2="313.5" y1="566.4219" y2="566.4219"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="103" x="68.5" y="561.356">status response</text><!--MD5=[921bcca0682cb10fe9b051cd54a58a3c]
+@startuml
+
+participant "Application" as app
+participant "ContactRecord" as rec
+participant "contacts DB" as db
+
+== Checking for duplicates ==
+
+app -> rec : contacts to check\n(query::CheckContactsListDuplicates)
+rec -> db : get numbers
+db -> rec
+rec -> rec : numbers comparison
+rec -> app : duplicated contacts
+
+== Merging contacts list to DB ==
+
+app -> rec : contacts to merge\n(query::MergeContactsList)
+rec -> db : get numbers
+db -> rec
+rec -> rec : numbers comparison
+
+group number not found
+ rec -> db : Add contact
+end
+group number found in db
+ rec -> db : Update contact by overriding old data
+end
+rec -> app : status response
+
+@enduml
+
+PlantUML version 1.2021.7(Sun May 23 14:40:07 CEST 2021)
+(GPL source distribution)
+Java Runtime: OpenJDK Runtime Environment
+JVM: OpenJDK 64-Bit Server VM
+Default Encoding: UTF-8
+Language: pl
+Country: PL
+--></g></svg><
\ No newline at end of file