From 40b4544e709318b133669f3ba886bbbb07dae571 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 13 Dec 2021 14:02:02 -0500 Subject: [PATCH] feat: `POST /api/v3/chats`, chat room creation, plus openAPI docs update --- public/language/en-GB/error.json | 1 + public/openapi/components/schemas/Chats.yaml | 14 ++++++++ public/openapi/write.yaml | 4 ++- public/openapi/write/chats.yaml | 29 +++++++++++++++ src/api/chats.js | 37 ++++++++++++++++++++ src/api/index.js | 1 + src/controllers/write/chats.js | 3 +- src/routes/write/chats.js | 2 +- src/socket.io/modules.js | 19 +++++----- 9 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 public/openapi/components/schemas/Chats.yaml create mode 100644 public/openapi/write/chats.yaml create mode 100644 src/api/chats.js diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index 0b5e714e26..9df89fe5cf 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -1,6 +1,7 @@ { "invalid-data": "Invalid Data", "invalid-json": "Invalid JSON", + "array-expected": "A value of type Array was expected for property `%1`, but %2 was received instead", "not-logged-in": "You don't seem to be logged in.", "account-locked": "Your account has been locked temporarily", diff --git a/public/openapi/components/schemas/Chats.yaml b/public/openapi/components/schemas/Chats.yaml new file mode 100644 index 0000000000..37fd49cc49 --- /dev/null +++ b/public/openapi/components/schemas/Chats.yaml @@ -0,0 +1,14 @@ +RoomObject: + type: object + properties: + owner: + type: number + description: the uid of the chat room owner (usually the user who created the room initially) + roomId: + type: string + description: unique identifier for the chat room + roomName: + type: string + groupChat: + type: boolean + description: whether the chat room is a group chat or not \ No newline at end of file diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index b28e265a4b..443b9866e6 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -19,7 +19,7 @@ info: # Authentication Please see the ["Authentication" section under the Read API](../read/#section/Overview/Authentication) for more information on how to authenticate against this API in order to make calls. - version: 1.15.0 + version: 1.19.0 contact: email: support@nodebb.org license: @@ -39,6 +39,8 @@ tags: description: Topic-based calls (create, modify, delete, etc.) - name: posts description: Individual post-related calls (create, modify, delete, etc.) + - name: chats + description: Calls related to the user private messaging system - name: admin description: Administrative calls - name: files diff --git a/public/openapi/write/chats.yaml b/public/openapi/write/chats.yaml new file mode 100644 index 0000000000..03deaad3ff --- /dev/null +++ b/public/openapi/write/chats.yaml @@ -0,0 +1,29 @@ +post: + tags: + - chats + summary: create a chat room + description: This operation creates a new chat room and adds users to the room, if provided. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + uids: + type: array + example: [2, 3] + required: + - uids + responses: + '200': + description: chat room successfully created + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../components/schemas/Status.yaml#/Status + response: + $ref: ../components/schemas/Chats.yaml#/RoomObject \ No newline at end of file diff --git a/src/api/chats.js b/src/api/chats.js new file mode 100644 index 0000000000..9e793577a4 --- /dev/null +++ b/src/api/chats.js @@ -0,0 +1,37 @@ +'use strict'; + +const meta = require('../meta'); +const privileges = require('../privileges'); +const messaging = require('../messaging'); + + +const websockets = require('../socket.io'); +const socketHelpers = require('../socket.io/helpers'); + +const chatsAPI = module.exports; + +function rateLimitExceeded(caller) { + const session = caller.request ? caller.request.session : caller.session; // socket vs req + const now = Date.now(); + session.lastChatMessageTime = session.lastChatMessageTime || 0; + if (now - session.lastChatMessageTime < meta.config.chatMessageDelay) { + return true; + } + session.lastChatMessageTime = now; + return false; +} + +chatsAPI.create = async function (caller, data) { + if (rateLimitExceeded(caller)) { + throw new Error('[[error:too-many-messages]]'); + } + + if (!data.uids || !Array.isArray(data.uids)) { + throw new Error(`[[error:array-expected, uids, ${typeof data.uids}]]`); + } + + await Promise.all(data.uids.map(async uid => messaging.canMessageUser(caller.uid, uid))); + const roomId = await messaging.newRoom(caller.uid, data.uids); + + return await messaging.getRoomData(roomId); +}; diff --git a/src/api/index.js b/src/api/index.js index de740b86ea..a4b5f3488b 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -5,6 +5,7 @@ module.exports = { groups: require('./groups'), topics: require('./topics'), posts: require('./posts'), + chats: require('./chats'), categories: require('./categories'), flags: require('./flags'), }; diff --git a/src/controllers/write/chats.js b/src/controllers/write/chats.js index d80ff20864..97c31aaee8 100644 --- a/src/controllers/write/chats.js +++ b/src/controllers/write/chats.js @@ -18,7 +18,8 @@ Chats.list = async (req, res) => { }; Chats.create = async (req, res) => { - // ... + const roomObj = await api.chats.create(req, req.body); + helpers.formatApiResponse(200, res, roomObj); }; Chats.exists = async (req, res) => { diff --git a/src/routes/write/chats.js b/src/routes/write/chats.js index 489004b3b1..5ecda4ddca 100644 --- a/src/routes/write/chats.js +++ b/src/routes/write/chats.js @@ -11,7 +11,7 @@ module.exports = function () { const middlewares = [middleware.ensureLoggedIn, middleware.canChat]; setupApiRoute(router, 'get', '/', [...middlewares], controllers.write.chats.list); - // setupApiRoute(router, 'post', '/', [...middlewares, middleware.checkRequired.bind(null, ['uids'])], controllers.write.chats.create); + setupApiRoute(router, 'post', '/', [...middlewares, middleware.checkRequired.bind(null, ['uids'])], controllers.write.chats.create); setupApiRoute(router, 'head', '/:roomId', [...middlewares, middleware.assert.room], controllers.write.chats.exists); // setupApiRoute(router, 'get', '/:roomId', [...middlewares, middleware.assert.room], controllers.write.chats.get); diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index f2804af9a4..6ecce578e2 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -12,6 +12,9 @@ const server = require('./index'); const user = require('../user'); const privileges = require('../privileges'); +const sockets = require('.'); +const api = require('../api'); + const SocketModules = module.exports; SocketModules.chats = {}; @@ -43,20 +46,16 @@ SocketModules.chats.isDnD = async function (socket, uid) { }; SocketModules.chats.newRoom = async function (socket, data) { + sockets.warnDeprecated(socket, 'POST /api/v3/chats'); + if (!data) { throw new Error('[[error:invalid-data]]'); } - if (rateLimitExceeded(socket)) { - throw new Error('[[error:too-many-messages]]'); - } - - const canChat = await privileges.global.can('chat', socket.uid); - if (!canChat) { - throw new Error('[[error:no-privileges]]'); - } - await Messaging.canMessageUser(socket.uid, data.touid); - return await Messaging.newRoom(socket.uid, [data.touid]); + const roomObj = await api.chats.create(socket, { + uids: [data.touid], + }); + return roomObj.roomId; }; SocketModules.chats.send = async function (socket, data) {