blob: aa657236c7c458e6eb47acb23a158283d8589b70 [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';
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050021import {
22 ContactDetails,
23 HttpStatusCode,
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050024 IConversationMember,
idillon07d31cc2022-12-06 22:40:14 -050025 IConversationSummary,
idillon9e542ca2022-12-15 17:54:07 -050026 Message,
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050027 NewConversationRequestBody,
28 NewMessageRequestBody,
29} from 'jami-web-common';
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040030import { Container } from 'typedi';
31
32import { Jamid } from '../jamid/jamid.js';
33import { authenticateToken } from '../middleware/auth.js';
34
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040035const jamid = Container.get(Jamid);
36
idillon07d31cc2022-12-06 22:40:14 -050037async function createConversationSummary(
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050038 accountId: string,
39 accountUri: string,
40 conversationId: string
idillon07d31cc2022-12-06 22:40:14 -050041): Promise<IConversationSummary | undefined> {
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040042 const infos = jamid.getConversationInfos(accountId, conversationId);
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050043 if (Object.keys(infos).length === 0) {
44 return undefined;
45 }
46
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040047 const members = jamid.getConversationMembers(accountId, conversationId);
48
idillon07d31cc2022-12-06 22:40:14 -050049 const membersNames = [];
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040050 for (const member of members) {
51 // Exclude current user from returned conversation members
52 if (member.uri === accountUri) {
53 continue;
54 }
55
56 // Add usernames for conversation members
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040057 const { username } = await jamid.lookupAddress(member.uri, accountId);
idillon9e542ca2022-12-15 17:54:07 -050058 membersNames.push(username || member.uri);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040059 }
60
idillon9e542ca2022-12-15 17:54:07 -050061 let lastMessage: Message | undefined;
62 // Skip "merge" type since they are of no interest for the user
63 // Should we add some protection to prevent infinite loop?
64 while (!lastMessage || lastMessage.type === 'merge') {
65 lastMessage = (
66 await jamid.getConversationMessages(accountId, conversationId, lastMessage?.linearizedParent || '', 1)
67 )[0];
68 }
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040069
70 return {
71 id: conversationId,
idillon07d31cc2022-12-06 22:40:14 -050072 avatar: infos.avatar,
73 title: infos.title,
74 membersNames,
75 lastMessage,
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040076 };
77}
78
79export const conversationRouter = Router();
80
81conversationRouter.use(authenticateToken);
82
83conversationRouter.get(
84 '/',
85 asyncHandler(async (_req, res) => {
86 const accountId = res.locals.accountId;
87
88 // Retrieve the URI of the current account (Account.username actually stores the URI rather than the username)
89 const accountUri = jamid.getAccountDetails(accountId)['Account.username'];
90
91 const conversationIds = jamid.getConversationIds(accountId);
92
idillon07d31cc2022-12-06 22:40:14 -050093 const conversationsSummaries = [];
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040094 for (const conversationId of conversationIds) {
idillon07d31cc2022-12-06 22:40:14 -050095 const conversationSummary = await createConversationSummary(accountId, accountUri, conversationId);
96 conversationsSummaries.push(conversationSummary);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -040097 }
98
idillon07d31cc2022-12-06 22:40:14 -050099 res.send(conversationsSummaries);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400100 })
101);
102
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500103conversationRouter.post(
104 '/',
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500105 (req: Request<ParamsDictionary, ContactDetails | string, Partial<NewConversationRequestBody>>, res) => {
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500106 const { members } = req.body;
107 if (members === undefined || members.length !== 1) {
108 res.status(HttpStatusCode.BadRequest).send('Missing members or more than one member in body');
109 return;
110 }
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400111
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500112 const accountId = res.locals.accountId;
113
114 const contactId = members[0];
115 jamid.addContact(accountId, contactId);
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500116 // We need to manually send a conversation request
117 jamid.sendTrustRequest(accountId, contactId);
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500118
119 const contactDetails = jamid.getContactDetails(accountId, contactId);
120 if (Object.keys(contactDetails).length === 0) {
121 res.status(HttpStatusCode.NotFound).send('No such member found');
122 return;
123 }
124
125 res.send(contactDetails);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400126 }
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500127);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400128
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400129conversationRouter.get(
130 '/:conversationId',
131 asyncHandler(async (req, res) => {
132 const accountId = res.locals.accountId;
133 const conversationId = req.params.conversationId;
134
135 // Retrieve the URI of the current account (Account.username actually stores the URI rather than the username)
136 const accountUri = jamid.getAccountDetails(accountId)['Account.username'];
137
idillon07d31cc2022-12-06 22:40:14 -0500138 const conversationSummary = await createConversationSummary(accountId, accountUri, conversationId);
139 if (conversationSummary === undefined) {
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500140 res.status(HttpStatusCode.NotFound).send('No such conversation found');
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400141 return;
142 }
143
idillon07d31cc2022-12-06 22:40:14 -0500144 res.send(conversationSummary);
145 })
146);
147
148conversationRouter.get(
149 '/:conversationId/infos',
150 asyncHandler(async (req, res) => {
151 const accountId = res.locals.accountId;
152 const conversationId = req.params.conversationId;
153
154 const infos = jamid.getConversationInfos(accountId, conversationId);
155 if (Object.keys(infos).length === 0) {
156 res.status(HttpStatusCode.NotFound).send('No such conversation found');
157 }
158
159 res.send(infos);
160 })
161);
162
163conversationRouter.get(
164 '/:conversationId/members',
165 asyncHandler(async (req, res) => {
166 const accountId = res.locals.accountId;
167 const conversationId = req.params.conversationId;
168
169 // Retrieve the URI of the current account (Account.username actually stores the URI rather than the username)
170 const accountUri = jamid.getAccountDetails(accountId)['Account.username'];
171
172 const infos = jamid.getConversationInfos(accountId, conversationId);
173 if (Object.keys(infos).length === 0) {
174 res.status(HttpStatusCode.NotFound).send('No such conversation found');
175 return;
176 }
177
178 const members = jamid.getConversationMembers(accountId, conversationId);
179
180 const namedMembers: IConversationMember[] = [];
181 for (const member of members) {
182 // Exclude current user from returned conversation members
183 if (member.uri === accountUri) {
184 continue;
185 }
186
187 // Add usernames for conversation members
188 const { username } = await jamid.lookupAddress(member.uri, accountId);
189 namedMembers.push({
190 role: member.role,
191 contact: {
192 uri: member.uri,
193 registeredName: username,
194 },
195 });
196 }
197
198 res.send(namedMembers);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400199 })
200);
201
202conversationRouter.get(
203 '/:conversationId/messages',
204 asyncHandler(async (req, res) => {
205 const accountId = res.locals.accountId;
206 const conversationId = req.params.conversationId;
207
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500208 const infos = jamid.getConversationInfos(accountId, conversationId);
209 if (Object.keys(infos).length === 0) {
210 res.status(HttpStatusCode.NotFound).send('No such conversation found');
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400211 return;
212 }
213
214 const messages = await jamid.getConversationMessages(accountId, conversationId);
215 res.send(messages);
216 })
217);
218
219conversationRouter.post(
220 '/:conversationId/messages',
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500221 (req: Request<ParamsDictionary, string, Partial<NewMessageRequestBody>>, res) => {
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400222 const { message } = req.body;
223 if (message === undefined) {
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500224 res.status(HttpStatusCode.BadRequest).send('Missing message in body');
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400225 return;
226 }
227
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500228 const accountId = res.locals.accountId;
229 const conversationId = req.params.conversationId;
230
231 const infos = jamid.getConversationInfos(accountId, conversationId);
232 if (Object.keys(infos).length === 0) {
233 res.status(HttpStatusCode.NotFound).send('No such conversation found');
234 return;
235 }
236
237 jamid.sendConversationMessage(accountId, conversationId, message);
238 res.sendStatus(HttpStatusCode.NoContent);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400239 }
240);
idillon07d31cc2022-12-06 22:40:14 -0500241
242conversationRouter.delete('/:conversationId', (req, res) => {
243 jamid.removeConversation(res.locals.accountId, req.params.conversationId);
244 res.sendStatus(HttpStatusCode.NoContent);
245});