Compare commits

...

1 Commits

8 changed files with 152 additions and 0 deletions

View File

@@ -172,6 +172,10 @@ paths:
$ref: 'write/admin/analytics.yaml'
/admin/analytics/{set}:
$ref: 'write/admin/analytics/set.yaml'
/admin/chats:
$ref: 'write/admin/chats.yaml'
/admin/chats/{roomId}:
$ref: 'write/admin/chats/roomId.yaml'
/files/:
$ref: 'write/files.yaml'
/files/folder:

View File

@@ -0,0 +1,46 @@
get:
tags:
- admin
summary: get chat rooms
description: This operation returns all chat rooms managed by NodeBB. **For privacy reasons**, only chat room metadata is shown.
parameters:
- in: query
name: perPage
schema:
type: number
description: The number of chat rooms displayed per page
example: 20
- in: query
name: page
schema:
type: number
description: The page number
example: 1
responses:
'200':
description: Chat rooms retrieved
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../components/schemas/Status.yaml#/Status
response:
type: object
properties:
rooms:
type: array
items:
type: object
properties:
owner:
type: number
roomId:
type: number
userCount:
type: number
roomName:
type: string
groupChat:
type: boolean

View File

@@ -0,0 +1,26 @@
delete:
tags:
- admin
summary: delete chat room
description: This operation deletes a chat room from the database
parameters:
- in: path
name: roomId
schema:
type: number
description: The roomId to be deleted
example: 1
required: true
responses:
'200':
description: Chat room deleted
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../../components/schemas/Status.yaml#/Status
response:
type: object
properties: {}

View File

@@ -1,8 +1,11 @@
'use strict';
const db = require('../../database');
const meta = require('../../meta');
const privileges = require('../../privileges');
const analytics = require('../../analytics');
const messaging = require('../../messaging');
const events = require('../../events');
const helpers = require('../helpers');
@@ -40,3 +43,28 @@ Admin.getAnalyticsData = async (req, res) => {
const getStats = req.query.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet;
helpers.formatApiResponse(200, res, await getStats(`analytics:${req.params.set}`, parseInt(req.query.until, 10) || Date.now(), req.query.amount));
};
Admin.chats = {};
Admin.chats.getRooms = async (req, res) => {
const page = (isFinite(req.query.page) && parseInt(req.query.page, 10)) || 1;
const perPage = (isFinite(req.query.perPage) && parseInt(req.query.perPage, 10)) || 20;
const start = Math.max(0, page - 1) * perPage;
const stop = start + perPage;
const roomIds = await db.getSortedSetRevRange('chat:rooms', start, stop);
helpers.formatApiResponse(200, res, {
rooms: await messaging.getRoomsData(roomIds),
});
};
Admin.chats.deleteRoom = async (req, res) => {
await messaging.deleteRooms([req.params.roomId]);
events.log({
type: 'chat-room-deleted',
uid: req.uid,
ip: req.ip,
});
helpers.formatApiResponse(200, res);
};

View File

@@ -75,6 +75,7 @@ events.types = [
'export:uploads',
'account-locked',
'getUsersCSV',
'chat-room-deleted',
// To add new types from plugins, just Array.push() to this array
];

View File

@@ -21,6 +21,12 @@ module.exports = function (Messaging) {
Messaging.getRoomsData = async (roomIds) => {
const roomData = await db.getObjects(roomIds.map(roomId => `chat:room:${roomId}`));
const userCounts = await db.sortedSetsCard(roomIds.map(roomId => `chat:room:${roomId}:uids`));
userCounts.forEach((count, idx) => {
roomData[idx].userCount = count;
});
modifyRoomData(roomData);
return roomData;
};
@@ -47,6 +53,7 @@ module.exports = function (Messaging) {
await Promise.all([
db.setObject(`chat:room:${roomId}`, room),
db.sortedSetAdd('chat:rooms', now, roomId),
db.sortedSetAdd(`chat:room:${roomId}:uids`, now, uid),
]);
await Promise.all([
@@ -59,6 +66,24 @@ module.exports = function (Messaging) {
return roomId;
};
Messaging.deleteRooms = async (roomIds) => {
// warning: uid:<uid>:chat:room:<roomId>:mids is left behind, along with each message:<mid> obj
// deleting them from db requires iterating through all messages; not performant
if (!roomIds) {
throw new Error('[[error:invalid-data]]');
}
if (!Array.isArray(roomIds)) {
roomIds = [roomIds];
}
await Promise.all(roomIds.map(async (roomId) => {
const uids = await db.getSortedSetMembers(`chat:room:${roomId}:uids`);
await Messaging.leaveRoom(uids, roomId);
await db.delete(`chat:room:${roomId}`);
}));
};
Messaging.isUserInRoom = async (uid, roomId) => {
const inRoom = await db.isSortedSetMember(`chat:room:${roomId}:uids`, uid);
const data = await plugins.hooks.fire('filter:messaging.isUserInRoom', { uid: uid, roomId: roomId, inRoom: inRoom });

View File

@@ -15,5 +15,8 @@ module.exports = function () {
setupApiRoute(router, 'get', '/analytics', [...middlewares], controllers.write.admin.getAnalyticsKeys);
setupApiRoute(router, 'get', '/analytics/:set', [...middlewares], controllers.write.admin.getAnalyticsData);
setupApiRoute(router, 'get', '/chats', [...middlewares], controllers.write.admin.chats.getRooms);
setupApiRoute(router, 'delete', '/chats/:roomId', [...middlewares, middleware.assert.room], controllers.write.admin.chats.deleteRoom);
return router;
};

View File

@@ -0,0 +1,19 @@
'use strict';
const db = require('../../database');
module.exports = {
name: 'Store list of chat rooms',
timestamp: Date.UTC(2022, 8, 30),
method: async () => {
const lastRoomId = await db.getObjectField('global', 'nextChatRoomId');
let keys = [];
for (let x = 1; x <= lastRoomId; x++) {
keys.push(`chat:room:${x}`);
}
const exists = await db.exists(keys);
keys = keys.filter((_, idx) => exists[idx]);
await db.sortedSetAdd('chat:rooms', keys.map(Date.now), keys.map(key => key.slice(10)));
},
};