blob: 164ea3e075ce57515fc124e13f81fbc6e77b2d0a [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);
40 const members = jamid.getConversationMembers(accountId, conversationId);
41
42 const namedMembers = [];
43 for (const member of members) {
44 // Exclude current user from returned conversation members
45 if (member.uri === accountUri) {
46 continue;
47 }
48
49 // Add usernames for conversation members
50 // TODO: Add caching in jamid to avoid too many address -> username lookups?
51 const { username } = await jamid.lookupAddress(member.uri, accountId);
52 namedMembers.push({
53 role: member.role,
54 contact: {
55 uri: member.uri,
56 registeredName: username,
57 },
58 });
59 }
60
61 // TODO: Check if messages actually need to be added to response
62 // (does the client really need it for all endpoints, or just the /conversations/conversationId/messages endpoint?)
63 const messages = await jamid.getConversationMessages(accountId, conversationId);
64
65 return {
66 id: conversationId,
67 messages: messages,
68 members: namedMembers,
69 infos: infos,
70 };
71}
72
73export const conversationRouter = Router();
74
75conversationRouter.use(authenticateToken);
76
77conversationRouter.get(
78 '/',
79 asyncHandler(async (_req, res) => {
80 const accountId = res.locals.accountId;
81
82 // Retrieve the URI of the current account (Account.username actually stores the URI rather than the username)
83 const accountUri = jamid.getAccountDetails(accountId)['Account.username'];
84
85 const conversationIds = jamid.getConversationIds(accountId);
86
87 const conversations = [];
88 for (const conversationId of conversationIds) {
89 const conversation = await createConversationResponseObject(accountId, accountUri, conversationId);
90 conversations.push(conversation);
91 }
92
93 res.send(conversations);
94 })
95);
96
97conversationRouter.post('/', (req: Request<ParamsDictionary, Record<string, string>, ConversationMembers>, res) => {
98 const accountId = res.locals.accountId;
99
100 const { members } = req.body;
101 if (members === undefined || members.length !== 1) {
102 res.sendStatus(HttpStatusCode.BadRequest);
103 return;
104 }
105
106 const contactId = members[0];
107 jamid.addContact(accountId, contactId);
108
109 const contactDetails = jamid.getContactDetails(accountId, contactId);
110 res.send(contactDetails);
111});
112
113// TODO: Check if we actually need this endpoint to return messages.
114// Verify by checking what is truly needed in the client when migrating, to clean up the API.
115// At the moment, /conversations does a lot of work returning all the conversations with the same
116// level of detail as this, and /conversations/messages returns just the messages. Check whether or not
117// this is what we want, and if so, if we can be more economical with client requests.
118conversationRouter.get(
119 '/:conversationId',
120 asyncHandler(async (req, res) => {
121 const accountId = res.locals.accountId;
122 const conversationId = req.params.conversationId;
123
124 // Retrieve the URI of the current account (Account.username actually stores the URI rather than the username)
125 const accountUri = jamid.getAccountDetails(accountId)['Account.username'];
126
127 const conversationIds = jamid.getConversationIds(accountId);
128 if (!conversationIds.includes(conversationId)) {
129 res.sendStatus(HttpStatusCode.NotFound);
130 return;
131 }
132
133 const conversation = await createConversationResponseObject(accountId, accountUri, conversationId);
134 res.send(conversation);
135 })
136);
137
138conversationRouter.get(
139 '/:conversationId/messages',
140 asyncHandler(async (req, res) => {
141 const accountId = res.locals.accountId;
142 const conversationId = req.params.conversationId;
143
144 const conversationIds = jamid.getConversationIds(accountId);
145 if (!conversationIds.includes(conversationId)) {
146 res.sendStatus(HttpStatusCode.NotFound);
147 return;
148 }
149
150 const messages = await jamid.getConversationMessages(accountId, conversationId);
151 res.send(messages);
152 })
153);
154
155conversationRouter.post(
156 '/:conversationId/messages',
157 (req: Request<ParamsDictionary, any, ConversationMessage>, res) => {
158 const { message } = req.body;
159 if (message === undefined) {
160 res.sendStatus(HttpStatusCode.BadRequest);
161 return;
162 }
163
164 jamid.sendConversationMessage(res.locals.accountId, req.params.conversationId, message);
165 res.end();
166 }
167);