mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-16 10:46:14 +01:00
feat: POST /chats/:roomId/users
This commit is contained in:
@@ -94,6 +94,44 @@ MessageObject:
|
||||
type: boolean
|
||||
cleanedContent:
|
||||
type: string
|
||||
RoomUserList:
|
||||
type: object
|
||||
properties:
|
||||
users:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
uid:
|
||||
type: number
|
||||
description: A user identifier
|
||||
username:
|
||||
type: string
|
||||
description: A friendly name for a given user account
|
||||
picture:
|
||||
nullable: true
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
displayname:
|
||||
type: string
|
||||
description: This is either username or fullname depending on forum and user settings
|
||||
icon:text:
|
||||
type: string
|
||||
description: A single-letter representation of a username. This is used in the
|
||||
auto-generated icon given to users
|
||||
without an avatar
|
||||
icon:bgColor:
|
||||
type: string
|
||||
description: A six-character hexadecimal colour code assigned to the user. This
|
||||
value is used in conjunction with
|
||||
`icon:text` for the user's
|
||||
auto-generated icon
|
||||
example: "#f44336"
|
||||
isOwner:
|
||||
type: boolean
|
||||
canKick:
|
||||
type: boolean
|
||||
RoomObjectFull:
|
||||
# Messaging.loadRoom
|
||||
allOf:
|
||||
|
||||
@@ -22,40 +22,43 @@ get:
|
||||
status:
|
||||
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||
response:
|
||||
type: object
|
||||
properties:
|
||||
users:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
uid:
|
||||
type: number
|
||||
description: A user identifier
|
||||
username:
|
||||
type: string
|
||||
description: A friendly name for a given user account
|
||||
picture:
|
||||
nullable: true
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
displayname:
|
||||
type: string
|
||||
description: This is either username or fullname depending on forum and user settings
|
||||
icon:text:
|
||||
type: string
|
||||
description: A single-letter representation of a username. This is used in the
|
||||
auto-generated icon given to users
|
||||
without an avatar
|
||||
icon:bgColor:
|
||||
type: string
|
||||
description: A six-character hexadecimal colour code assigned to the user. This
|
||||
value is used in conjunction with
|
||||
`icon:text` for the user's
|
||||
auto-generated icon
|
||||
example: "#f44336"
|
||||
isOwner:
|
||||
type: boolean
|
||||
canKick:
|
||||
type: boolean
|
||||
$ref: ../../../components/schemas/Chats.yaml#/RoomUserList
|
||||
post:
|
||||
tags:
|
||||
- chats
|
||||
summary: add users to chat room
|
||||
description: This operation invites users to a chat room
|
||||
parameters:
|
||||
- in: path
|
||||
name: roomId
|
||||
schema:
|
||||
type: number
|
||||
required: true
|
||||
description: a valid chat room id
|
||||
example: 1
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
uids:
|
||||
type: array
|
||||
description: A list of valid uids
|
||||
example: [2]
|
||||
items:
|
||||
type: number
|
||||
description: A valid uid
|
||||
responses:
|
||||
'200':
|
||||
description: users successfully invited to chat room
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||
response:
|
||||
$ref: ../../../components/schemas/Chats.yaml#/RoomUserList
|
||||
@@ -244,18 +244,15 @@ define('forum/chats', [
|
||||
require(['autocomplete', 'translator'], function (autocomplete, translator) {
|
||||
autocomplete.user(searchInput, function (event, selected) {
|
||||
errorEl.text('');
|
||||
socket.emit('modules.chats.addUserToRoom', {
|
||||
roomId: roomId,
|
||||
username: selected.item.user.name,
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
translator.translate(err.message, function (translated) {
|
||||
errorEl.text(translated);
|
||||
});
|
||||
}
|
||||
|
||||
Chats.refreshParticipantsList(roomId, modal);
|
||||
api.post(`/chats/${roomId}/users`, {
|
||||
uids: [selected.item.user.uid],
|
||||
}).then((body) => {
|
||||
Chats.refreshParticipantsList(roomId, modal, body);
|
||||
searchInput.val('');
|
||||
}).catch((err) => {
|
||||
translator.translate(err.message, function (translated) {
|
||||
errorEl.text(translated);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -307,16 +304,21 @@ define('forum/chats', [
|
||||
});
|
||||
};
|
||||
|
||||
Chats.refreshParticipantsList = function (roomId, modal) {
|
||||
Chats.refreshParticipantsList = async (roomId, modal, data) => {
|
||||
const listEl = modal.find('.list-group');
|
||||
api.get(`/chats/${roomId}/users`, {}).then(({ users }) => {
|
||||
app.parseAndTranslate('partials/modals/manage_room_users', { users }, function (html) {
|
||||
listEl.html(html);
|
||||
});
|
||||
}).catch(() => {
|
||||
translator.translate('[[error:invalid-data]]', function (translated) {
|
||||
listEl.find('li').text(translated);
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
try {
|
||||
data = await api.get(`/chats/${roomId}/users`, {});
|
||||
} catch (err) {
|
||||
translator.translate('[[error:invalid-data]]', function (translated) {
|
||||
listEl.find('li').text(translated);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app.parseAndTranslate('partials/modals/manage_room_users', data, function (html) {
|
||||
listEl.html(html);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -83,3 +83,21 @@ chatsAPI.users = async (caller, data) => {
|
||||
});
|
||||
return { users };
|
||||
};
|
||||
|
||||
chatsAPI.invite = async (caller, data) => {
|
||||
const userCount = await messaging.getUserCountInRoom(data.roomId);
|
||||
const maxUsers = meta.config.maximumUsersInChatRoom;
|
||||
if (maxUsers && userCount >= maxUsers) {
|
||||
throw new Error('[[error:cant-add-more-users-to-chat-room]]');
|
||||
}
|
||||
|
||||
const uidsExist = await user.exists(data.uids);
|
||||
if (!uidsExist.every(Boolean)) {
|
||||
throw new Error('[[error:no-user]]');
|
||||
}
|
||||
await Promise.all(data.uids.map(async uid => messaging.canMessageUser(caller.uid, uid)));
|
||||
await messaging.addUsersToRoom(caller.uid, data.uids, data.roomId);
|
||||
|
||||
delete data.uids;
|
||||
return chatsAPI.users(caller, data);
|
||||
};
|
||||
|
||||
@@ -61,7 +61,12 @@ Chats.users = async (req, res) => {
|
||||
};
|
||||
|
||||
Chats.invite = async (req, res) => {
|
||||
// ...
|
||||
const users = await api.chats.invite(req, {
|
||||
...req.body,
|
||||
roomId: req.params.roomId,
|
||||
});
|
||||
|
||||
helpers.formatApiResponse(200, res, users);
|
||||
};
|
||||
|
||||
Chats.kick = async (req, res) => {
|
||||
|
||||
@@ -20,7 +20,7 @@ module.exports = function () {
|
||||
// no route for room deletion, noted here just in case...
|
||||
|
||||
setupApiRoute(router, 'get', '/:roomId/users', [...middlewares, middleware.assert.room], controllers.write.chats.users);
|
||||
// setupApiRoute(router, 'put', '/:roomId/users', [...middlewares, middleware.assert.room, middleware.checkRequired.bind(null, ['uids'])], controllers.write.chats.invite);
|
||||
setupApiRoute(router, 'post', '/:roomId/users', [...middlewares, middleware.assert.room, middleware.checkRequired.bind(null, ['uids'])], controllers.write.chats.invite);
|
||||
// setupApiRoute(router, 'delete', '/:roomId/users', [...middlewares, middleware.assert.room, middleware.checkRequired.bind(null, ['uids'])], controllers.write.chats.kick);
|
||||
|
||||
setupApiRoute(router, 'get', '/:roomId/:mid', [...middlewares, middleware.assert.room, middleware.assert.message], controllers.write.chats.messages.get);
|
||||
|
||||
@@ -84,7 +84,7 @@ SocketModules.chats.loadRoom = async function (socket, data) {
|
||||
};
|
||||
|
||||
SocketModules.chats.getUsersInRoom = async function (socket, data) {
|
||||
sockets.warnDeprecated(socket, 'GET /api/v3/chats/:roomId/user');
|
||||
sockets.warnDeprecated(socket, 'GET /api/v3/chats/:roomId/users');
|
||||
|
||||
if (!data || !data.roomId) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
@@ -98,6 +98,8 @@ SocketModules.chats.getUsersInRoom = async function (socket, data) {
|
||||
};
|
||||
|
||||
SocketModules.chats.addUserToRoom = async function (socket, data) {
|
||||
sockets.warnDeprecated(socket, 'POST /api/v3/chats/:roomId/users');
|
||||
|
||||
if (!data || !data.roomId || !data.username) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
@@ -107,18 +109,11 @@ SocketModules.chats.addUserToRoom = async function (socket, data) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
|
||||
const userCount = await Messaging.getUserCountInRoom(data.roomId);
|
||||
const maxUsers = meta.config.maximumUsersInChatRoom;
|
||||
if (maxUsers && userCount >= maxUsers) {
|
||||
throw new Error('[[error:cant-add-more-users-to-chat-room]]');
|
||||
}
|
||||
// Revised API now takes uids, not usernames
|
||||
data.uids = [await user.getUidByUsername(data.username)];
|
||||
delete data.username;
|
||||
|
||||
const uid = await user.getUidByUsername(data.username);
|
||||
if (!uid) {
|
||||
throw new Error('[[error:no-user]]');
|
||||
}
|
||||
await Messaging.canMessageUser(socket.uid, uid);
|
||||
await Messaging.addUsersToRoom(socket.uid, [uid], data.roomId);
|
||||
await api.chats.invite(socket, data);
|
||||
};
|
||||
|
||||
SocketModules.chats.removeUserFromRoom = async function (socket, data) {
|
||||
|
||||
@@ -88,17 +88,13 @@ describe('Messaging Library', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT allow messages to be sent to a restricted user', (done) => {
|
||||
User.setSetting(mocks.users.baz.uid, 'restrictChat', '1', (err) => {
|
||||
assert.ifError(err);
|
||||
Messaging.canMessageUser(mocks.users.herp.uid, mocks.users.baz.uid, (err) => {
|
||||
assert.strictEqual(err.message, '[[error:chat-restricted]]');
|
||||
socketModules.chats.addUserToRoom({ uid: mocks.users.herp.uid }, { roomId: 1, username: 'baz' }, (err) => {
|
||||
assert.equal(err.message, '[[error:chat-restricted]]');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should NOT allow messages to be sent to a restricted user', async () => {
|
||||
await User.setSetting(mocks.users.baz.uid, 'restrictChat', '1');
|
||||
try {
|
||||
await Messaging.canMessageUser(mocks.users.herp.uid, mocks.users.baz.uid);
|
||||
} catch (err) {
|
||||
assert.strictEqual(err.message, '[[error:chat-restricted]]');
|
||||
}
|
||||
});
|
||||
|
||||
it('should always allow admins through', (done) => {
|
||||
@@ -169,35 +165,26 @@ describe('Messaging Library', () => {
|
||||
assert.equal(body2.status.message, await translator.translate('[[error:cant-edit-chat-message]]'));
|
||||
});
|
||||
|
||||
it('should fail to add user to room with invalid data', (done) => {
|
||||
socketModules.chats.addUserToRoom({ uid: mocks.users.foo.uid }, null, (err) => {
|
||||
assert.equal(err.message, '[[error:invalid-data]]');
|
||||
socketModules.chats.addUserToRoom({ uid: mocks.users.foo.uid }, { roomId: null }, (err) => {
|
||||
assert.equal(err.message, '[[error:invalid-data]]');
|
||||
socketModules.chats.addUserToRoom({ uid: mocks.users.foo.uid }, { roomId: roomId, username: null }, (err) => {
|
||||
assert.equal(err.message, '[[error:invalid-data]]');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should fail to add user to room with invalid data', async () => {
|
||||
let { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, {}, 'foo');
|
||||
assert.strictEqual(statusCode, 400);
|
||||
assert.strictEqual(body.status.message, await translator.translate('[[error:required-parameters-missing, uids]]'));
|
||||
|
||||
({ statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [null] }, 'foo'));
|
||||
assert.strictEqual(statusCode, 400);
|
||||
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
|
||||
});
|
||||
|
||||
it('should add a user to room', (done) => {
|
||||
socketModules.chats.addUserToRoom({ uid: mocks.users.foo.uid }, { roomId: roomId, username: 'herp' }, (err) => {
|
||||
assert.ifError(err);
|
||||
Messaging.isUserInRoom(mocks.users.herp.uid, roomId, (err, isInRoom) => {
|
||||
assert.ifError(err);
|
||||
assert(isInRoom);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should add a user to room', async () => {
|
||||
await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.herp.uid] }, 'foo');
|
||||
const isInRoom = await Messaging.isUserInRoom(mocks.users.herp.uid, roomId);
|
||||
assert(isInRoom);
|
||||
});
|
||||
|
||||
it('should get users in room', async () => {
|
||||
const { body } = await callv3API('get', `/chats/${roomId}/users`, {}, 'foo');
|
||||
assert(Array.isArray(body.response.users));
|
||||
assert.strictEqual(body.response.users.length, 3);
|
||||
console.log(body.response.users);
|
||||
});
|
||||
|
||||
it('should throw error if user is not in room', async () => {
|
||||
@@ -206,27 +193,24 @@ describe('Messaging Library', () => {
|
||||
assert.equal(body.status.message, await translator.translate('[[error:no-privileges]]'));
|
||||
});
|
||||
|
||||
it('should fail to add users to room if max is reached', (done) => {
|
||||
it('should fail to add users to room if max is reached', async () => {
|
||||
meta.config.maximumUsersInChatRoom = 2;
|
||||
socketModules.chats.addUserToRoom({ uid: mocks.users.foo.uid }, { roomId: roomId, username: 'test' }, (err) => {
|
||||
assert.equal(err.message, '[[error:cant-add-more-users-to-chat-room]]');
|
||||
meta.config.maximumUsersInChatRoom = 0;
|
||||
done();
|
||||
});
|
||||
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.bar.uid] }, 'foo');
|
||||
assert.strictEqual(statusCode, 400);
|
||||
assert.equal(body.status.message, await translator.translate('[[error:cant-add-more-users-to-chat-room]]'));
|
||||
meta.config.maximumUsersInChatRoom = 0;
|
||||
});
|
||||
|
||||
it('should fail to add users to room if user does not exist', (done) => {
|
||||
socketModules.chats.addUserToRoom({ uid: mocks.users.foo.uid }, { roomId: roomId, username: 'doesnotexist' }, (err) => {
|
||||
assert.equal(err.message, '[[error:no-user]]');
|
||||
done();
|
||||
});
|
||||
it('should fail to add users to room if user does not exist', async () => {
|
||||
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [98237498234] }, 'foo');
|
||||
assert.strictEqual(statusCode, 400);
|
||||
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
|
||||
});
|
||||
|
||||
it('should fail to add self to room', (done) => {
|
||||
socketModules.chats.addUserToRoom({ uid: mocks.users.foo.uid }, { roomId: roomId, username: 'foo' }, (err) => {
|
||||
assert.equal(err.message, '[[error:cant-chat-with-yourself]]');
|
||||
done();
|
||||
});
|
||||
it('should fail to add self to room', async () => {
|
||||
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.foo.uid] }, 'foo');
|
||||
assert.strictEqual(statusCode, 400);
|
||||
assert.strictEqual(body.status.message, await translator.translate('[[error:cant-chat-with-yourself]]'));
|
||||
});
|
||||
|
||||
it('should fail to leave room with invalid data', (done) => {
|
||||
@@ -286,7 +270,7 @@ describe('Messaging Library', () => {
|
||||
uids: [mocks.users.foo.uid],
|
||||
}, 'herp');
|
||||
|
||||
await util.promisify(socketModules.chats.addUserToRoom)({ uid: mocks.users.herp.uid }, { roomId: body.response.roomId, username: 'baz' });
|
||||
await callv3API('post', `/chats/${body.response.roomId}/users`, { uids: [mocks.users.baz.uid] }, 'herp');
|
||||
await util.promisify(socketModules.chats.leave)({ uid: mocks.users.herp.uid }, body.response.roomId);
|
||||
|
||||
const data = await Messaging.getRoomData(body.response.roomId);
|
||||
@@ -348,9 +332,7 @@ describe('Messaging Library', () => {
|
||||
assert.equal(err.message, '[[error:cant-remove-last-user]]');
|
||||
}
|
||||
|
||||
await util.promisify(
|
||||
socketModules.chats.addUserToRoom
|
||||
)({ uid: mocks.users.foo.uid }, { roomId: roomId, username: 'baz' });
|
||||
await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.baz.uid] }, 'foo');
|
||||
await util.promisify(
|
||||
socketModules.chats.removeUserFromRoom
|
||||
)({ uid: mocks.users.foo.uid }, { roomId: roomId, uid: mocks.users.herp.uid });
|
||||
@@ -437,7 +419,7 @@ describe('Messaging Library', () => {
|
||||
const { roomId } = body.response;
|
||||
assert(roomId);
|
||||
|
||||
await socketModules.chats.addUserToRoom({ uid: mocks.users.foo.uid }, { roomId: roomId, username: 'herp' });
|
||||
await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.herp.uid] }, 'foo');
|
||||
await db.sortedSetAdd('users:online', Date.now() - ((meta.config.onlineCutoff * 60000) + 50000), mocks.users.herp.uid);
|
||||
|
||||
await callv3API('post', `/chats/${roomId}`, { roomId: roomId, message: 'second chat message **bold** text' }, 'foo');
|
||||
@@ -626,7 +608,7 @@ describe('Messaging Library', () => {
|
||||
let mid;
|
||||
let mid2;
|
||||
before(async () => {
|
||||
await socketModules.chats.addUserToRoom({ uid: mocks.users.foo.uid }, { roomId: roomId, username: 'baz' });
|
||||
await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.baz.uid] }, 'foo');
|
||||
let { body } = await callv3API('post', `/chats/${roomId}`, { roomId: roomId, message: 'first chat message' }, 'foo');
|
||||
mid = body.response.mid;
|
||||
({ body } = await callv3API('post', `/chats/${roomId}`, { roomId: roomId, message: 'second chat message' }, 'baz'));
|
||||
|
||||
Reference in New Issue
Block a user