blob: 224962c43714b056e12ddd6672af021d37a20717 [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);
114
115 const contactDetails = jamid.getContactDetails(accountId, contactId);
116 if (Object.keys(contactDetails).length === 0) {
117 res.status(HttpStatusCode.NotFound).send('No such member found');
118 return;
119 }
120
121 res.send(contactDetails);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400122 }
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500123);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400124
125// TODO: Check if we actually need this endpoint to return messages.
126// Verify by checking what is truly needed in the client when migrating, to clean up the API.
127// At the moment, /conversations does a lot of work returning all the conversations with the same
128// level of detail as this, and /conversations/messages returns just the messages. Check whether or not
129// this is what we want, and if so, if we can be more economical with client requests.
130conversationRouter.get(
131 '/:conversationId',
132 asyncHandler(async (req, res) => {
133 const accountId = res.locals.accountId;
134 const conversationId = req.params.conversationId;
135
136 // Retrieve the URI of the current account (Account.username actually stores the URI rather than the username)
137 const accountUri = jamid.getAccountDetails(accountId)['Account.username'];
138
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500139 const conversation = await createConversationResponseObject(accountId, accountUri, conversationId);
140 if (conversation === undefined) {
141 res.status(HttpStatusCode.NotFound).send('No such conversation found');
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400142 return;
143 }
144
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400145 res.send(conversation);
146 })
147);
148
149conversationRouter.get(
150 '/:conversationId/messages',
151 asyncHandler(async (req, res) => {
152 const accountId = res.locals.accountId;
153 const conversationId = req.params.conversationId;
154
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500155 const infos = jamid.getConversationInfos(accountId, conversationId);
156 if (Object.keys(infos).length === 0) {
157 res.status(HttpStatusCode.NotFound).send('No such conversation found');
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400158 return;
159 }
160
161 const messages = await jamid.getConversationMessages(accountId, conversationId);
162 res.send(messages);
163 })
164);
165
166conversationRouter.post(
167 '/:conversationId/messages',
168 (req: Request<ParamsDictionary, any, ConversationMessage>, res) => {
169 const { message } = req.body;
170 if (message === undefined) {
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500171 res.status(HttpStatusCode.BadRequest).send('Missing message in body');
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400172 return;
173 }
174
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500175 const accountId = res.locals.accountId;
176 const conversationId = req.params.conversationId;
177
178 const infos = jamid.getConversationInfos(accountId, conversationId);
179 if (Object.keys(infos).length === 0) {
180 res.status(HttpStatusCode.NotFound).send('No such conversation found');
181 return;
182 }
183
184 jamid.sendConversationMessage(accountId, conversationId, message);
185 res.sendStatus(HttpStatusCode.NoContent);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400186 }
187);