From ce0fca8a70938c68aef766ea22b36409867a2124 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Fri, 17 Dec 2021 16:59:44 -0500
Subject: [PATCH] conversation: save role for each members

Change-Id: Ie113db781e50d72791558dbb8261c4233a07dbc9
---
 CMakeLists.txt                  | 25 +++++++-------
 src/api/conversation.h          | 12 +++++--
 src/api/conversationmodel.h     |  8 +++++
 src/api/member.h                | 61 +++++++++++++++++++++++++++++++++
 src/authority/storagehelper.cpp |  2 +-
 src/conversationmodel.cpp       | 52 ++++++++++++++++++----------
 6 files changed, 127 insertions(+), 33 deletions(-)
 create mode 100644 src/api/member.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5bfb7645..e57e9209 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -327,26 +327,27 @@ SET( libringclient_LIB_HDRS
 )
 
 SET(libringclient_api_LIB_HDRS
-  src/api/interaction.h
-  src/api/conversation.h
-  src/api/contact.h
-  src/api/call.h
   src/api/account.h
+  src/api/avmodel.h
+  src/api/behaviorcontroller.h
   src/api/chatview.h
+  src/api/call.h
+  src/api/contact.h
+  src/api/conversation.h
+  src/api/contactmodel.h
+  src/api/conversationmodel.h
+  src/api/datatransfermodel.h
+  src/api/datatransfer.h
+  src/api/interaction.h
   src/api/lrc.h
-  src/api/avmodel.h
-  src/api/pluginmodel.h
+  src/api/member.h
   src/api/newaccountmodel.h
-  src/api/peerdiscoverymodel.h
   src/api/newcallmodel.h
   src/api/newcodecmodel.h
   src/api/newdevicemodel.h
-  src/api/contactmodel.h
-  src/api/conversationmodel.h
+  src/api/pluginmodel.h
+  src/api/peerdiscoverymodel.h
   src/api/profile.h
-  src/api/behaviorcontroller.h
-  src/api/datatransfermodel.h
-  src/api/datatransfer.h
   src/api/video.h
 )
 
diff --git a/src/api/conversation.h b/src/api/conversation.h
index f1688e12..4608b201 100644
--- a/src/api/conversation.h
+++ b/src/api/conversation.h
@@ -20,6 +20,7 @@
 
 #include "interaction.h"
 #include "messagelistmodel.h"
+#include "member.h"
 #include "typedefs.h"
 
 #include <map>
@@ -69,11 +70,10 @@ struct Info
     bool allMessagesLoaded = false;
     QString uid = "";
     QString accountId;
-    VectorString participants;
+    QVector<member::Member> participants;
     QString callId;
     QString confId;
     std::unique_ptr<MessageListModel> interactions;
-    // MessageListModel interactions;
     QString lastMessageUid = 0;
     QHash<QString, QString> parentsId; // pair messageid/parentid for messages without parent loaded
     unsigned int unreadMessages = 0;
@@ -90,6 +90,14 @@ struct Info
     // conversation. Where active means peer did not leave the conversation.
     inline bool isCoreDialog() const { return isLegacy() || mode == Mode::ONE_TO_ONE; };
 
+    inline QStringList participantsUris() const
+    {
+        QStringList result;
+        for (const auto& p : participants)
+            result.append(p.uri);
+        return result;
+    }
+
     Mode mode = Mode::NON_SWARM;
     bool needsSyncing = false;
     bool isRequest = false;
diff --git a/src/api/conversationmodel.h b/src/api/conversationmodel.h
index 721896ea..98dd8107 100644
--- a/src/api/conversationmodel.h
+++ b/src/api/conversationmodel.h
@@ -386,6 +386,14 @@ public:
      */
     QString description(const QString& conversationId) const;
 
+    /**
+     * Get member's role in conversation
+     * @param conversationId
+     * @param memberUri
+     * @return role
+     */
+    member::Role memberRole(const QString& conversationId, const QString& memberUri) const;
+
 Q_SIGNALS:
     /**
      * Emitted when a conversation receives a new interaction
diff --git a/src/api/member.h b/src/api/member.h
new file mode 100644
index 00000000..91fe42e0
--- /dev/null
+++ b/src/api/member.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+ *    Copyright (C) 2021 Savoir-faire Linux Inc.                            *
+ *   Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>           *
+ *                                                                          *
+ *   This library is free software; you can redistribute it and/or          *
+ *   modify it under the terms of the GNU Lesser General Public             *
+ *   License as published by the Free Software Foundation; either           *
+ *   version 2.1 of the License, or (at your option) any later version.     *
+ *                                                                          *
+ *   This library is distributed in the hope that it will be useful,        *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      *
+ *   Lesser General Public License for more details.                        *
+ *                                                                          *
+ *   You should have received a copy of the GNU General Public License      *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+#pragma once
+
+#include "typedefs.h"
+
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace lrc {
+
+namespace api {
+
+namespace member {
+Q_NAMESPACE
+Q_CLASSINFO("RegisterEnumClassesUnscoped", "false")
+
+enum class Role { ADMIN, MEMBER, INVITED, BANNED, LEFT };
+Q_ENUM_NS(Role)
+
+static inline Role
+to_role(const QString& roleStr)
+{
+    if (roleStr == "admin")
+        return Role::ADMIN;
+    if (roleStr == "member")
+        return Role::MEMBER;
+    if (roleStr == "invited")
+        return Role::INVITED;
+    if (roleStr == "banned")
+        return Role::BANNED;
+    if (roleStr == "left")
+        return Role::LEFT;
+    return Role::MEMBER;
+}
+
+struct Member
+{
+    QString uri = "";
+    Role role = Role::MEMBER;
+};
+
+} // namespace member
+} // namespace api
+} // namespace lrc
diff --git a/src/authority/storagehelper.cpp b/src/authority/storagehelper.cpp
index dfd15af4..41ae87ce 100644
--- a/src/authority/storagehelper.cpp
+++ b/src/authority/storagehelper.cpp
@@ -517,7 +517,7 @@ getHistory(Database& db, api::conversation::Info& conversation)
             if (status != api::interaction::Status::DISPLAYED || !payloads[i + 1].isEmpty()) {
                 continue;
             }
-            conversation.interactions->setRead(conversation.participants.front(), payloads[i]);
+            conversation.interactions->setRead(conversation.participants.front().uri, payloads[i]);
         }
     }
 }
diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp
index ef3ff043..72048e0d 100644
--- a/src/conversationmodel.cpp
+++ b/src/conversationmodel.cpp
@@ -1043,10 +1043,10 @@ ConversationModel::title(const QString& conversationId) const
     QString title;
     auto idx = 0;
     for (const auto& member : conversation.participants) {
-        if (member == owner.profileInfo.uri) {
+        if (member.uri == owner.profileInfo.uri) {
             title += owner.accountModel->bestNameForAccount(owner.id);
         } else {
-            title += owner.contactModel->bestNameForContact(member);
+            title += owner.contactModel->bestNameForContact(member.uri);
         }
         idx += 1;
         if (idx != conversation.participants.size()) {
@@ -1056,6 +1056,20 @@ ConversationModel::title(const QString& conversationId) const
     return title;
 }
 
+member::Role
+ConversationModel::memberRole(const QString& conversationId, const QString& memberUri) const
+{
+    auto conversationOpt = getConversationForUid(conversationId);
+    if (!conversationOpt.has_value())
+        throw std::out_of_range("Member out of range");
+    auto& conversation = conversationOpt->get();
+    for (const auto& p : conversation.participants) {
+        if (p.uri == memberUri)
+            return p.role;
+    }
+    throw std::out_of_range("Member out of range");
+}
+
 QString
 ConversationModel::description(const QString& conversationId) const
 {
@@ -2024,18 +2038,18 @@ ConversationModelPimpl::peersForConversation(const conversation::Info& conversat
     VectorString result {};
     switch (conversation.mode) {
     case conversation::Mode::NON_SWARM:
-        return conversation.participants;
+        return {conversation.participants[0].uri};
     default:
         break;
     }
     // Note: for one to one, we must return self
     if (conversation.participants.size() == 1)
-        return conversation.participants;
+        return {conversation.participants[0].uri};
     for (const auto& participant : conversation.participants) {
-        if (participant.isNull())
+        if (participant.uri.isNull())
             continue;
-        if (participant != linked.owner.profileInfo.uri)
-            result.push_back(participant);
+        if (participant.uri != linked.owner.profileInfo.uri)
+            result.push_back(participant.uri);
     }
     return result;
 }
@@ -2446,11 +2460,11 @@ ConversationModelPimpl::slotConversationReady(const QString& accountId,
     // remove non swarm conversation that was added from slotContactAdded
     const VectorMapStringString& members = ConfigurationManager::instance()
                                                .getConversationMembers(accountId, conversationId);
-    VectorString participants;
+    QVector<member::Member> participants;
     // it means conversation with one participant. In this case we could have non swarm conversation
     bool shouldRemoveNonSwarmConversation = members.size() == 2;
     for (const auto& member : members) {
-        participants.append(member["uri"]);
+        participants.append({member["uri"], api::member::to_role(member["role"])});
         if (shouldRemoveNonSwarmConversation) {
             try {
                 auto& conversation = getConversationForPeerUri(member["uri"]).get();
@@ -2575,14 +2589,14 @@ ConversationModelPimpl::slotConversationMemberEvent(const QString& accountId,
     auto& conversation = getConversationForUid(conversationId).get();
     const VectorMapStringString& members
         = ConfigurationManager::instance().getConversationMembers(linked.owner.id, conversationId);
-    VectorString uris;
+    QVector<member::Member> participants;
     VectorString membersRemaining;
     for (auto& member : members) {
-        uris.append(member["uri"]);
+        participants.append(member::Member {member["uri"], member::to_role(member["role"])});
         if (member["role"] != "left")
             membersRemaining.append(member["uri"]);
     }
-    conversation.participants = uris;
+    conversation.participants = participants;
     conversation.readOnly = membersRemaining == VectorString(1, linked.owner.profileInfo.uri);
     invalidateModel();
     Q_EMIT linked.modelChanged();
@@ -2675,7 +2689,7 @@ ConversationModelPimpl::addContactRequest(const QString& contactUri)
         conversation::Info conversation;
         conversation.uid = contactUri;
         conversation.accountId = linked.owner.id;
-        conversation.participants = {contactUri};
+        conversation.participants = {{contactUri, member::Role::INVITED}};
         conversation.mode = conversation::Mode::NON_SWARM;
         conversation.isRequest = true;
         emplaceBackConversation(std::move(conversation));
@@ -2723,7 +2737,8 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
     conversation::Info conversation;
     conversation.uid = convId;
     conversation.accountId = linked.owner.id;
-    conversation.participants = {linked.owner.profileInfo.uri, peerUri};
+    conversation.participants = {{linked.owner.profileInfo.uri, member::Role::INVITED},
+                                 {peerUri, member::Role::MEMBER}};
     conversation.mode = mode;
     conversation.isRequest = true;
     emplaceBackConversation(std::move(conversation));
@@ -2814,7 +2829,8 @@ ConversationModelPimpl::slotContactModelUpdated(const QString& uri)
     for (auto& user : users) {
         conversation::Info conversationInfo;
         conversationInfo.uid = user.profileInfo.uri;
-        conversationInfo.participants.push_back(user.profileInfo.uri);
+        conversationInfo.participants.append(
+            member::Member {user.profileInfo.uri, member::Role::MEMBER});
         conversationInfo.accountId = linked.owner.id;
         searchResults.emplace_front(std::move(conversationInfo));
     }
@@ -2824,7 +2840,7 @@ ConversationModelPimpl::slotContactModelUpdated(const QString& uri)
 void
 ConversationModelPimpl::addSwarmConversation(const QString& convId)
 {
-    VectorString participants;
+    QVector<member::Member> participants;
     const VectorMapStringString& members = ConfigurationManager::instance()
                                                .getConversationMembers(linked.owner.id, convId);
     auto accountURI = linked.owner.profileInfo.uri;
@@ -2842,7 +2858,7 @@ ConversationModelPimpl::addSwarmConversation(const QString& convId)
         // this check should be removed once all usage of participants replaced by
         // peersForConversation. We should have ourself in participants list
         // Note: if members.size() == 1, it's a conv with self so we're also the peer
-        participants.append(member["uri"]);
+        participants.append(member::Member {member["uri"], member::to_role(member["role"])});
         if (mode == conversation::Mode::ONE_TO_ONE && member["uri"] != accountURI) {
             otherMember = member["uri"];
         } else if (member["uri"] == accountURI) {
@@ -2906,7 +2922,7 @@ ConversationModelPimpl::addConversationWith(const QString& convId,
     conversation::Info conversation;
     conversation.uid = convId;
     conversation.accountId = linked.owner.id;
-    conversation.participants = {contactUri};
+    conversation.participants = {{contactUri, member::Role::MEMBER}};
     conversation.mode = conversation::Mode::NON_SWARM;
     conversation.needsSyncing = false;
     conversation.isRequest = isRequest;
-- 
GitLab