blob: b306bf1a236c5166e76ad993886c4a98f7a76fd2 [file] [log] [blame]
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -04001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
18import { Request, Router } from 'express';
19import asyncHandler from 'express-async-handler';
20import { ParamsDictionary } from 'express-serve-static-core';
21import { HttpStatusCode } from 'jami-web-common';
22import { Container } from 'typedi';
23
24import { Jamid } from '../jamid/jamid.js';
25import { authenticateToken } from '../middleware/auth.js';
26
27interface ConversationMembers {
28 members: string[];
29}
30
31interface ConversationMessage {
32 message: string;
33}
34
35const jamid = Container.get(Jamid);
36
37// TODO: Create interface for return type in common/ when Records and interfaces are refactored
38async function createConversationResponseObject(accountId: string, accountUri: string, conversationId: string) {
39 const infos = jamid.getConversationInfos(accountId, conversationId);
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050040 if (Object.keys(infos).length === 0) {
41 return undefined;
42 }
43
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040044 const members = jamid.getConversationMembers(accountId, conversationId);
45
46 const namedMembers = [];
47 for (const member of members) {
48 // Exclude current user from returned conversation members
49 if (member.uri === accountUri) {
50 continue;
51 }
52
53 // Add usernames for conversation members
54 // TODO: Add caching in jamid to avoid too many address -> username lookups?
55 const { username } = await jamid.lookupAddress(member.uri, accountId);
56 namedMembers.push({
57 role: member.role,
58 contact: {
59 uri: member.uri,
60 registeredName: username,
61 },
62 });
63 }
64
65 // TODO: Check if messages actually need to be added to response
66 // (does the client really need it for all endpoints, or just the /conversations/conversationId/messages endpoint?)
67 const messages = await jamid.getConversationMessages(accountId, conversationId);
68
69 return {
70 id: conversationId,
71 messages: messages,
72 members: namedMembers,
73 infos: infos,
74 };
75}
76
77export const conversationRouter = Router();
78
79conversationRouter.use(authenticateToken);
80
81conversationRouter.get(
82 '/',
83 asyncHandler(async (_req, res) => {
84 const accountId = res.locals.accountId;
85
86 // Retrieve the URI of the current account (Account.username actually stores the URI rather than the username)
87 const accountUri = jamid.getAccountDetails(accountId)['Account.username'];
88
89 const conversationIds = jamid.getConversationIds(accountId);
90
91 const conversations = [];
92 for (const conversationId of conversationIds) {
93 const conversation = await createConversationResponseObject(accountId, accountUri, conversationId);
94 conversations.push(conversation);
95 }
96
97 res.send(conversations);
98 })
99);
100
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500101conversationRouter.post(
102 '/',
103 (req: Request<ParamsDictionary, Record<string, string> | string, ConversationMembers>, res) => {
104 const { members } = req.body;
105 if (members === undefined || members.length !== 1) {
106 res.status(HttpStatusCode.BadRequest).send('Missing members or more than one member in body');
107 return;
108 }
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400109
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500110 const accountId = res.locals.accountId;
111
112 const contactId = members[0];
113 jamid.addContact(accountId, contactId);
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500114 // We need to manually send a conversation request
115 jamid.sendTrustRequest(accountId, contactId);
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500116
117 const contactDetails = jamid.getContactDetails(accountId, contactId);
118 if (Object.keys(contactDetails).length === 0) {
119 res.status(HttpStatusCode.NotFound).send('No such member found');
120 return;
121 }
122
123 res.send(contactDetails);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400124 }
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500125);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400126
127// TODO: Check if we actually need this endpoint to return messages.
128// Verify by checking what is truly needed in the client when migrating, to clean up the API.
129// At the moment, /conversations does a lot of work returning all the conversations with the same
130// level of detail as this, and /conversations/messages returns just the messages. Check whether or not
131// this is what we want, and if so, if we can be more economical with client requests.
132conversationRouter.get(
133 '/:conversationId',
134 asyncHandler(async (req, res) => {
135 const accountId = res.locals.accountId;
136 const conversationId = req.params.conversationId;
137
138 // Retrieve the URI of the current account (Account.username actually stores the URI rather than the username)
139 const accountUri = jamid.getAccountDetails(accountId)['Account.username'];
140
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500141 const conversation = await createConversationResponseObject(accountId, accountUri, conversationId);
142 if (conversation === undefined) {
143 res.status(HttpStatusCode.NotFound).send('No such conversation found');
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400144 return;
145 }
146
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400147 res.send(conversation);
148 })
149);
150
151conversationRouter.get(
152 '/:conversationId/messages',
153 asyncHandler(async (req, res) => {
154 const accountId = res.locals.accountId;
155 const conversationId = req.params.conversationId;
156
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500157 const infos = jamid.getConversationInfos(accountId, conversationId);
158 if (Object.keys(infos).length === 0) {
159 res.status(HttpStatusCode.NotFound).send('No such conversation found');
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400160 return;
161 }
162
163 const messages = await jamid.getConversationMessages(accountId, conversationId);
164 res.send(messages);
165 })
166);
167
168conversationRouter.post(
169 '/:conversationId/messages',
170 (req: Request<ParamsDictionary, any, ConversationMessage>, res) => {
171 const { message } = req.body;
172 if (message === undefined) {
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500173 res.status(HttpStatusCode.BadRequest).send('Missing message in body');
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400174 return;
175 }
176
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500177 const accountId = res.locals.accountId;
178 const conversationId = req.params.conversationId;
179
180 const infos = jamid.getConversationInfos(accountId, conversationId);
181 if (Object.keys(infos).length === 0) {
182 res.status(HttpStatusCode.NotFound).send('No such conversation found');
183 return;
184 }
185
186 jamid.sendConversationMessage(accountId, conversationId, message);
187 res.sendStatus(HttpStatusCode.NoContent);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400188 }
189);