Create new interfaces for objects transmitted using the REST API

Changes:
- Create new IContact, IAccount, and IConversation interfaces in common/
    - These interfaces represent the serialized versions of the models which are transferred
    - The client models are classes which implement these interfaces
- Create new LookupResult interface for nameserver lookup results
- Create new IConversationMember interface for conversation members
    - The client interface ConversationMember extends this interface to have a Contact field rather than IContact
- Create new ConversationInfos interface for conversation infos
- Create new ContactDetails interface for contact details (used by contacts routes)
- Move request and response body interfaces into common/
- Merge AccountConfig into AccountDetails interface
- Create interfaces for server-only objects:
    - ConversationMemberInfos
    - ConversationRequestMetadata
- Ensure interfaces in jami-signal-interfaces.ts do not contain fields with JamiSwig types
- Rename models/ filenames to camelCase as they are not components
- Rewrite client models to have proper TypeScript accessors and remove unused getters
- Rewrite how client models are initialized from the serialized interface using .fromInterface static methods
- Make client models implement the interfaces in common/ for consistency
- Remove unneeded _next parameter for Express.js route handlers
- Use Partial<T> for all Express.js request body types on server
- Type all Axios response body types with interfaces

GitLab: #92
Change-Id: I4b2c75ac632ec5d9bf12a874a5ba04467c76fa6d
diff --git a/client/src/models/account.ts b/client/src/models/account.ts
new file mode 100644
index 0000000..60ac1d8
--- /dev/null
+++ b/client/src/models/account.ts
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { AccountDetails, Devices, IAccount, VolatileDetails } from 'jami-web-common';
+
+import { Contact } from './contact';
+import { Conversation } from './conversation';
+
+export type AccountType = 'RING' | 'SIP';
+
+export class Account implements IAccount {
+  readonly id: string;
+  details: AccountDetails;
+  volatileDetails: VolatileDetails;
+  defaultModerators: Contact[] = [];
+  devices: Devices = {};
+  contacts: Contact[] = [];
+
+  private _conversations: Record<string, Conversation> = {};
+
+  constructor(id: string, details: AccountDetails, volatileDetails: VolatileDetails) {
+    this.id = id;
+    this.details = details;
+    this.volatileDetails = volatileDetails;
+  }
+
+  static fromInterface(accountInterface: IAccount) {
+    const account = new Account(accountInterface.id, accountInterface.details, accountInterface.volatileDetails);
+    account.defaultModerators = accountInterface.defaultModerators.map(Contact.fromInterface);
+    return account;
+  }
+
+  getType(): AccountType {
+    return this.details['Account.type'] as AccountType;
+  }
+
+  getUri() {
+    return this.details['Account.username'];
+  }
+
+  getRegisteredName() {
+    return this.volatileDetails['Account.registeredName'];
+  }
+
+  isRendezVous() {
+    return this.details['Account.rendezVous'] === 'true';
+  }
+
+  isPublicIn() {
+    return this.details['DHT.PublicInCalls'] === 'true';
+  }
+
+  updateDetails(details: Partial<AccountDetails>) {
+    this.details = { ...this.details, ...details };
+  }
+
+  getDisplayUri() {
+    return this.getRegisteredName() ?? this.getUri();
+  }
+
+  getDisplayName() {
+    return this.details['Account.displayName'] ?? this.getDisplayUri();
+  }
+
+  getDisplayNameNoFallback() {
+    return this.details['Account.displayName'] ?? this.getRegisteredName();
+  }
+
+  get conversations() {
+    return this._conversations;
+  }
+
+  addConversation(conversation: Conversation) {
+    if (conversation.id === undefined) {
+      throw new Error('Conversation ID cannot be undefined');
+    }
+    this._conversations[conversation.id] = conversation;
+  }
+
+  removeConversation(conversationId: string) {
+    delete this.conversations[conversationId];
+  }
+}