Divide Conversation into ConversationInfos, ConversationMember, and ConversationSummary

- ConversationSummary is used to display ConversationList.
- Having the three separated will help managing queries.
- Adding ConversationSummary required to solve some inconsistencies in ConversationList, which was mixing contacts and conversations. ContactSearchResultList has been added as a quick fix . It will need more work.
- Some tools to uniformize conversation names have been introduced. They will need more work.

Note the diplaying of ConversationList is left broken in this commit.

Change-Id: I29337906cc43781a9c4790735490a6ee2cc51cb0
diff --git a/server/src/jamid/jami-swig.ts b/server/src/jamid/jami-swig.ts
index df9dd3d..d8f0dcb 100644
--- a/server/src/jamid/jami-swig.ts
+++ b/server/src/jamid/jami-swig.ts
@@ -117,6 +117,7 @@
   conversationInfos(accountId: string, conversationId: string): StringMap;
   getConversationMembers(accountId: string, conversationId: string): VectMap;
   acceptConversationRequest(accountId: string, conversationId: string): void;
+  removeConversation(accountId: string, conversationId: string): void;
 
   sendMessage(accountId: string, conversationId: string, message: string, replyTo: string, flag: number): void;
   loadConversationMessages(accountId: string, conversationId: string, fromMessage: string, n: number): number;
diff --git a/server/src/jamid/jamid.ts b/server/src/jamid/jamid.ts
index b6a728b..2476e72 100644
--- a/server/src/jamid/jamid.ts
+++ b/server/src/jamid/jamid.ts
@@ -345,8 +345,13 @@
     ) as unknown as ConversationMemberInfos[];
   }
 
-  async getConversationMessages(accountId: string, conversationId: string, fromMessage?: string): Promise<Message[]> {
-    const requestId = this.jamiSwig.loadConversationMessages(accountId, conversationId, fromMessage || '', 32);
+  async getConversationMessages(
+    accountId: string,
+    conversationId: string,
+    fromMessage = '',
+    n = 32
+  ): Promise<Message[]> {
+    const requestId = this.jamiSwig.loadConversationMessages(accountId, conversationId, fromMessage, n);
     return firstValueFrom(
       this.events.onConversationLoaded.pipe(
         filter((value) => value.id === requestId),
@@ -355,6 +360,10 @@
     );
   }
 
+  removeConversation(accountId: string, conversationId: string) {
+    this.jamiSwig.removeConversation(accountId, conversationId);
+  }
+
   sendConversationMessage(
     accountId: string,
     conversationId: string,
diff --git a/server/src/routers/conversation-router.ts b/server/src/routers/conversation-router.ts
index bd3a900..0a6b0b2 100644
--- a/server/src/routers/conversation-router.ts
+++ b/server/src/routers/conversation-router.ts
@@ -21,8 +21,8 @@
 import {
   ContactDetails,
   HttpStatusCode,
-  IConversation,
   IConversationMember,
+  IConversationSummary,
   NewConversationRequestBody,
   NewMessageRequestBody,
 } from 'jami-web-common';
@@ -33,11 +33,11 @@
 
 const jamid = Container.get(Jamid);
 
-async function createConversationResponseObject(
+async function createConversationSummary(
   accountId: string,
   accountUri: string,
   conversationId: string
-): Promise<IConversation | undefined> {
+): Promise<IConversationSummary | undefined> {
   const infos = jamid.getConversationInfos(accountId, conversationId);
   if (Object.keys(infos).length === 0) {
     return undefined;
@@ -45,7 +45,7 @@
 
   const members = jamid.getConversationMembers(accountId, conversationId);
 
-  const namedMembers: IConversationMember[] = [];
+  const membersNames = [];
   for (const member of members) {
     // Exclude current user from returned conversation members
     if (member.uri === accountUri) {
@@ -54,24 +54,17 @@
 
     // Add usernames for conversation members
     const { username } = await jamid.lookupAddress(member.uri, accountId);
-    namedMembers.push({
-      role: member.role,
-      contact: {
-        uri: member.uri,
-        registeredName: username,
-      },
-    });
+    membersNames.push(username ?? member.uri);
   }
 
-  // TODO: Check if messages actually need to be added to response
-  // (does the client really need it for all endpoints, or just the /conversations/conversationId/messages endpoint?)
-  const messages = await jamid.getConversationMessages(accountId, conversationId);
+  const lastMessage = (await jamid.getConversationMessages(accountId, conversationId, '', 1))[0];
 
   return {
     id: conversationId,
-    members: namedMembers,
-    messages: messages,
-    infos: infos,
+    avatar: infos.avatar,
+    title: infos.title,
+    membersNames,
+    lastMessage,
   };
 }
 
@@ -89,13 +82,13 @@
 
     const conversationIds = jamid.getConversationIds(accountId);
 
-    const conversations = [];
+    const conversationsSummaries = [];
     for (const conversationId of conversationIds) {
-      const conversation = await createConversationResponseObject(accountId, accountUri, conversationId);
-      conversations.push(conversation);
+      const conversationSummary = await createConversationSummary(accountId, accountUri, conversationId);
+      conversationsSummaries.push(conversationSummary);
     }
 
-    res.send(conversations);
+    res.send(conversationsSummaries);
   })
 );
 
@@ -125,10 +118,6 @@
   }
 );
 
-// TODO: Check if we actually need this endpoint to return messages.
-// At the moment, /conversations does a lot of work returning all the conversations with the same
-// level of detail as this, and /conversations/messages returns just the messages. Check whether or not
-// this is what we want, and if so, if we can be more economical with client requests.
 conversationRouter.get(
   '/:conversationId',
   asyncHandler(async (req, res) => {
@@ -138,13 +127,67 @@
     // Retrieve the URI of the current account (Account.username actually stores the URI rather than the username)
     const accountUri = jamid.getAccountDetails(accountId)['Account.username'];
 
-    const conversation = await createConversationResponseObject(accountId, accountUri, conversationId);
-    if (conversation === undefined) {
+    const conversationSummary = await createConversationSummary(accountId, accountUri, conversationId);
+    if (conversationSummary === undefined) {
       res.status(HttpStatusCode.NotFound).send('No such conversation found');
       return;
     }
 
-    res.send(conversation);
+    res.send(conversationSummary);
+  })
+);
+
+conversationRouter.get(
+  '/:conversationId/infos',
+  asyncHandler(async (req, res) => {
+    const accountId = res.locals.accountId;
+    const conversationId = req.params.conversationId;
+
+    const infos = jamid.getConversationInfos(accountId, conversationId);
+    if (Object.keys(infos).length === 0) {
+      res.status(HttpStatusCode.NotFound).send('No such conversation found');
+    }
+
+    res.send(infos);
+  })
+);
+
+conversationRouter.get(
+  '/:conversationId/members',
+  asyncHandler(async (req, res) => {
+    const accountId = res.locals.accountId;
+    const conversationId = req.params.conversationId;
+
+    // Retrieve the URI of the current account (Account.username actually stores the URI rather than the username)
+    const accountUri = jamid.getAccountDetails(accountId)['Account.username'];
+
+    const infos = jamid.getConversationInfos(accountId, conversationId);
+    if (Object.keys(infos).length === 0) {
+      res.status(HttpStatusCode.NotFound).send('No such conversation found');
+      return;
+    }
+
+    const members = jamid.getConversationMembers(accountId, conversationId);
+
+    const namedMembers: IConversationMember[] = [];
+    for (const member of members) {
+      // Exclude current user from returned conversation members
+      if (member.uri === accountUri) {
+        continue;
+      }
+
+      // Add usernames for conversation members
+      const { username } = await jamid.lookupAddress(member.uri, accountId);
+      namedMembers.push({
+        role: member.role,
+        contact: {
+          uri: member.uri,
+          registeredName: username,
+        },
+      });
+    }
+
+    res.send(namedMembers);
   })
 );
 
@@ -187,3 +230,8 @@
     res.sendStatus(HttpStatusCode.NoContent);
   }
 );
+
+conversationRouter.delete('/:conversationId', (req, res) => {
+  jamid.removeConversation(res.locals.accountId, req.params.conversationId);
+  res.sendStatus(HttpStatusCode.NoContent);
+});