From ad071d4edb4e61499c434c3d202b7b8f4b1d69b6 Mon Sep 17 00:00:00 2001 From: Oleksandr Zhurbenko <xaerok.od.ua@gmail.com> Date: Wed, 11 Oct 2017 17:52:57 -0700 Subject: [PATCH] Merged 2.0 and 1.1 annotations -> shapes api --- .../imports/api/1.1/shapes/index.js | 20 +- .../api/1.1/shapes/server/eventHandlers.js | 14 +- .../server/handlers/whiteboardCleared.js | 20 +- .../server/handlers/whiteboardGetReply.js | 38 +- .../shapes/server/handlers/whiteboardSend.js | 16 +- .../shapes/server/handlers/whiteboardUndo.js | 12 +- .../imports/api/1.1/shapes/server/index.js | 0 .../imports/api/1.1/shapes/server/methods.js | 4 + .../shapes/server/methods/clearWhiteboard.js | 21 +- .../shapes/server/methods/sendAnnotation.js | 55 ++- .../shapes/server/methods/undoAnnotation.js | 21 +- .../1.1/shapes/server/modifiers/addShape.js | 392 +++++++++++++++--- .../shapes/server/modifiers/clearShapes.js | 34 +- .../server/modifiers/clearShapesWhiteboard.js | 23 - .../shapes/server/modifiers/removeShape.js | 16 +- .../api/1.1/shapes/server/publishers.js | 14 +- .../imports/api/2.0/annotations/index.js | 19 - .../2.0/annotations/server/eventHandlers.js | 10 - .../server/handlers/whiteboardAnnotations.js | 22 - .../server/handlers/whiteboardCleared.js | 19 - .../server/handlers/whiteboardSend.js | 17 - .../server/handlers/whiteboardUndo.js | 13 - .../api/2.0/annotations/server/index.js | 3 - .../api/2.0/annotations/server/methods.js | 10 - .../server/methods/clearWhiteboard.js | 31 -- .../server/methods/sendAnnotation.js | 58 --- .../server/methods/undoAnnotation.js | 31 -- .../server/modifiers/addAnnotation.js | 372 ----------------- .../server/modifiers/clearAnnotations.js | 36 -- .../server/modifiers/removeAnnotation.js | 25 -- .../api/2.0/annotations/server/publishers.js | 24 -- 31 files changed, 525 insertions(+), 865 deletions(-) mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/index.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/eventHandlers.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardCleared.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardGetReply.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardSend.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardUndo.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/index.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/methods.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/methods/clearWhiteboard.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/methods/sendAnnotation.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/methods/undoAnnotation.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/addShape.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/clearShapes.js delete mode 100644 bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/clearShapesWhiteboard.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/removeShape.js mode change 100644 => 100755 bigbluebutton-html5/imports/api/1.1/shapes/server/publishers.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/index.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/eventHandlers.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardAnnotations.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardCleared.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardSend.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardUndo.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/index.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/methods.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/methods/clearWhiteboard.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/methods/sendAnnotation.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/methods/undoAnnotation.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/addAnnotation.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/clearAnnotations.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/removeAnnotation.js delete mode 100644 bigbluebutton-html5/imports/api/2.0/annotations/server/publishers.js diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/index.js b/bigbluebutton-html5/imports/api/1.1/shapes/index.js old mode 100644 new mode 100755 index f15f19a892..7241f893ae --- a/bigbluebutton-html5/imports/api/1.1/shapes/index.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/index.js @@ -1 +1,19 @@ -export default new Mongo.Collection('shapes'); +import { Meteor } from 'meteor/meteor'; + +const Annotations = new Mongo.Collection('annotations'); + +if (Meteor.isServer) { + // types of queries for the annotations: + // 1. meetingId, whiteboardId + // 2. meetingId, whiteboardId, userId + // 3. meetingId, id, userId + // 4. meetingId, whiteboardId, id + // These 2 indexes seem to cover all of the cases + // Either mongo uses a whole or a part of the compound index + // Or it uses 'id' and then matches other fields + + Annotations._ensureIndex({ id: 1 }); + Annotations._ensureIndex({ meetingId: 1, whiteboardId: 1, userId: 1 }); +} + +export default Annotations; diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/eventHandlers.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/eventHandlers.js old mode 100644 new mode 100755 index efe74211d8..99dbb3c799 --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/eventHandlers.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/eventHandlers.js @@ -1,10 +1,10 @@ -import RedisPubSub from '/imports/startup/server/redis'; -import handleWhiteboardGetReply from './handlers/whiteboardGetReply'; -import handleWhiteboardSend from './handlers/whiteboardSend'; +import RedisPubSub from '/imports/startup/server/redis2x'; import handleWhiteboardCleared from './handlers/whiteboardCleared'; import handleWhiteboardUndo from './handlers/whiteboardUndo'; +import handleWhiteboardSend from './handlers/whiteboardSend'; +import handleWhiteboardAnnotations from './handlers/whiteboardAnnotations'; -RedisPubSub.on('get_whiteboard_shapes_reply', handleWhiteboardGetReply); -RedisPubSub.on('send_whiteboard_shape_message', handleWhiteboardSend); -RedisPubSub.on('whiteboard_cleared_message', handleWhiteboardCleared); -RedisPubSub.on('undo_whiteboard_request', handleWhiteboardUndo); +RedisPubSub.on('ClearWhiteboardEvtMsg', handleWhiteboardCleared); +RedisPubSub.on('UndoWhiteboardEvtMsg', handleWhiteboardUndo); +RedisPubSub.on('SendWhiteboardAnnotationEvtMsg', handleWhiteboardSend); +RedisPubSub.on('GetWhiteboardAnnotationsRespMsg', handleWhiteboardAnnotations); diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardCleared.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardCleared.js old mode 100644 new mode 100755 index 52dcf8f1e3..7e6b25d151 --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardCleared.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardCleared.js @@ -1,13 +1,19 @@ import { check } from 'meteor/check'; -import clearShapesWhiteboard from '../modifiers/clearShapesWhiteboard'; +import clearAnnotations from '../modifiers/clearAnnotations'; -export default function handleWhiteboardCleared({ payload }) { - const meetingId = payload.meeting_id; - const whiteboardId = payload.whiteboard_id; +export default function handleWhiteboardCleared({ body }, meetingId) { + check(body, { + userId: String, + whiteboardId: String, + fullClear: Boolean, + }); - check(meetingId, String); - check(whiteboardId, String); + const { whiteboardId, fullClear, userId } = body; - return clearShapesWhiteboard(meetingId, whiteboardId); + if (fullClear) { + return clearAnnotations(meetingId, whiteboardId); + } + + return clearAnnotations(meetingId, whiteboardId, userId); } diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardGetReply.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardGetReply.js old mode 100644 new mode 100755 index d644ad43d4..e6f99e452a --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardGetReply.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardGetReply.js @@ -1,32 +1,22 @@ +import _ from 'lodash'; import { check } from 'meteor/check'; -import { inReplyToHTML5Client } from '/imports/api/common/server/helpers'; -import Shapes from './../../'; - -import addShape from '../modifiers/addShape'; -import removeShape from '../modifiers/removeShape'; - -export default function handleWhiteboardGetReply({ payload }) { - if (!inReplyToHTML5Client({ payload })) { - return; - } - - const meetingId = payload.meeting_id; - const shapes = payload.shapes; +import clearAnnotations from '../modifiers/clearAnnotations'; +import addAnnotation from '../modifiers/addAnnotation'; +export default function handleWhiteboardAnnotations({ body }, meetingId) { check(meetingId, String); - check(shapes, Array); + check(body, Object); - const shapesIds = shapes.map(_ => _.id); - const shapesToRemove = Shapes.find({ - meetingId, - 'shape.id': { $nin: shapesIds }, - }).fetch(); + const { annotations, whiteboardId } = body; - shapesToRemove.forEach(s => removeShape(meetingId, s.shape.wb_id, s.shape.id)); + check(whiteboardId, String); + clearAnnotations(meetingId, whiteboardId); - const shapesAdded = []; - shapes.forEach((shape) => { - const whiteboardId = shape.wb_id; - shapesAdded.push(addShape(meetingId, whiteboardId, shape)); + const annotationsAdded = []; + _.each(annotations, (annotation) => { + const { wbId, userId } = annotation; + annotationsAdded.push(addAnnotation(meetingId, wbId, userId, annotation)); }); + + return annotationsAdded; } diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardSend.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardSend.js old mode 100644 new mode 100755 index 4956381e53..2ca1d84964 --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardSend.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardSend.js @@ -1,17 +1,17 @@ import { check } from 'meteor/check'; -import addShape from '../modifiers/addShape'; +import addAnnotation from '../modifiers/addAnnotation'; -export default function handleWhiteboardSend({ payload }) { - const meetingId = payload.meeting_id; - const shape = payload.shape; +export default function handleWhiteboardSend({ header, body }, meetingId) { + const userId = header.userId; + const annotation = body.annotation; - check(meetingId, String); - check(shape, Object); + check(userId, String); + check(annotation, Object); - const whiteboardId = shape.wb_id; + const whiteboardId = annotation.wbId; check(whiteboardId, String); - return addShape(meetingId, whiteboardId, shape); + return addAnnotation(meetingId, whiteboardId, userId, annotation); } diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardUndo.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardUndo.js old mode 100644 new mode 100755 index e4e25a0647..3194362af3 --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardUndo.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/handlers/whiteboardUndo.js @@ -1,15 +1,13 @@ import { check } from 'meteor/check'; -import removeShape from '../modifiers/removeShape'; +import removeAnnotation from '../modifiers/removeAnnotation'; -export default function handleWhiteboardUndo({ payload }) { - const meetingId = payload.meeting_id; - const whiteboardId = payload.whiteboard_id; - const shapeId = payload.shape_id; +export default function handleWhiteboardUndo({ body }, meetingId) { + const whiteboardId = body.whiteboardId; + const shapeId = body.annotationId; - check(meetingId, String); check(whiteboardId, String); check(shapeId, String); - return removeShape(meetingId, whiteboardId, shapeId); + return removeAnnotation(meetingId, whiteboardId, shapeId); } diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/index.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/index.js old mode 100644 new mode 100755 diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/methods.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/methods.js old mode 100644 new mode 100755 index 1edfcdbf7e..57d5c4d0d4 --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/methods.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/methods.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; +import undoAnnotation from './methods/undoAnnotation'; +import clearWhiteboard from './methods/clearWhiteboard'; import sendAnnotation from './methods/sendAnnotation'; Meteor.methods({ + undoAnnotation, + clearWhiteboard, sendAnnotation, }); diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/clearWhiteboard.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/clearWhiteboard.js old mode 100644 new mode 100755 index bc1587996d..406329a2e2 --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/clearWhiteboard.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/clearWhiteboard.js @@ -1,11 +1,13 @@ -import RedisPubSub from '/imports/startup/server/redis'; +import Acl from '/imports/startup/acl'; +import { getMultiUserStatus } from '/imports/api/common/server/helpers'; +import RedisPubSub from '/imports/startup/server/redis2x'; import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; export default function clearWhiteboard(credentials, whiteboardId) { const REDIS_CONFIG = Meteor.settings.redis; - const CHANNEL = REDIS_CONFIG.channels.toBBBApps.whiteboard; - const EVENT_NAME = 'clear_whiteboard_request'; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'ClearWhiteboardPubMsg'; const { meetingId, requesterUserId, requesterToken } = credentials; @@ -14,11 +16,16 @@ export default function clearWhiteboard(credentials, whiteboardId) { check(requesterToken, String); check(whiteboardId, String); + const allowed = Acl.can('methods.clearWhiteboard', credentials) || getMultiUserStatus(meetingId); + if (!allowed) { + throw new Meteor.Error( + 'not-allowed', `User ${requesterUserId} is not allowed to clear the whiteboard`, + ); + } + const payload = { - requester_id: requesterUserId, - meeting_id: meetingId, - whiteboard_id: whiteboardId, + whiteboardId, }; - return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload); + return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/sendAnnotation.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/sendAnnotation.js old mode 100644 new mode 100755 index bda3acae4c..0a6fb16c27 --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/sendAnnotation.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/sendAnnotation.js @@ -1,23 +1,58 @@ -import RedisPubSub from '/imports/startup/server/redis'; +import Acl from '/imports/startup/acl'; +import { getMultiUserStatus } from '/imports/api/common/server/helpers'; +import RedisPubSub from '/imports/startup/server/redis2x'; import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import Logger from '/imports/startup/server/logger'; +import Annotations from '/imports/api/2.0/annotations'; -export default function sendAnnotation(credentials, payload) { +function isLastMessage(annotation, userId) { + const DRAW_END = Meteor.settings.public.whiteboard.annotations.status.end; + + if (annotation.status === DRAW_END) { + const selector = { + id: annotation.id, + userId, + }; + + const _annotation = Annotations.findOne(selector); + return _annotation !== null; + } + + return false; +} + +export default function sendAnnotation(credentials, annotation) { const REDIS_CONFIG = Meteor.settings.redis; - const CHANNEL = REDIS_CONFIG.channels.toBBBApps.whiteboard; - const EVENT_NAME = 'send_whiteboard_annotation_request'; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'SendWhiteboardAnnotationPubMsg'; const { meetingId, requesterUserId, requesterToken } = credentials; check(meetingId, String); check(requesterUserId, String); check(requesterToken, String); - check(payload, Object); + check(annotation, Object); + + // We allow messages to pass through in 3 cases: + // 1. When it's a standard message in presenter mode (Acl check) + // 2. When it's a standard message in multi-user mode (getMultUserStatus check) + // 3. When it's the last message, happens when the user is currently drawing + // and then slide/presentation changes, the user lost presenter rights, + // or multi-user whiteboard gets turned off + // So we allow the last "DRAW_END" message to pass through, to finish the shape. + const allowed = Acl.can('methods.sendAnnotation', credentials) || + getMultiUserStatus(meetingId) || + isLastMessage(annotation, requesterUserId); + + if (!allowed) { + throw new Meteor.Error( + 'not-allowed', `User ${requesterUserId} is not allowed to send an annotation`, + ); + } - payload.annotation.id = `${requesterUserId}-${payload.annotation.id}`; - payload.requester_id = requesterUserId; - payload.meeting_id = meetingId; + const payload = { + annotation, + }; - return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload); + return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/undoAnnotation.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/undoAnnotation.js old mode 100644 new mode 100755 index 2435344462..d7adf86b5e --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/undoAnnotation.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/methods/undoAnnotation.js @@ -1,11 +1,13 @@ -import RedisPubSub from '/imports/startup/server/redis'; +import Acl from '/imports/startup/acl'; +import { getMultiUserStatus } from '/imports/api/common/server/helpers'; +import RedisPubSub from '/imports/startup/server/redis2x'; import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; export default function undoAnnotation(credentials, whiteboardId) { const REDIS_CONFIG = Meteor.settings.redis; - const CHANNEL = REDIS_CONFIG.channels.toBBBApps.whiteboard; - const EVENT_NAME = 'undo_whiteboard_request'; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'UndoWhiteboardPubMsg'; const { meetingId, requesterUserId, requesterToken } = credentials; @@ -14,11 +16,16 @@ export default function undoAnnotation(credentials, whiteboardId) { check(requesterToken, String); check(whiteboardId, String); + const allowed = Acl.can('methods.undoAnnotation', credentials) || getMultiUserStatus(meetingId); + if (!allowed) { + throw new Meteor.Error( + 'not-allowed', `User ${requesterUserId} is not allowed to undo the annotation`, + ); + } + const payload = { - requester_id: requesterUserId, - meeting_id: meetingId, - whiteboard_id: whiteboardId, + whiteboardId, }; - return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload); + return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/addShape.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/addShape.js old mode 100644 new mode 100755 index 26b319aa2a..b1e8c26670 --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/addShape.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/addShape.js @@ -1,88 +1,372 @@ import { check } from 'meteor/check'; import Logger from '/imports/startup/server/logger'; -import Shapes from './../../'; +import Annotations from '/imports/api/2.0/annotations'; +const ANNOTATION_TYPE_TEXT = 'text'; +const ANNOTATION_TYPE_PENCIL = 'pencil'; -const SHAPE_TYPE_TEXT = 'text'; -const SHAPE_TYPE_POLL_RESULT = 'poll_result'; - -export default function addShape(meetingId, whiteboardId, shape) { - check(meetingId, String); - check(whiteboardId, String); - check(shape, Object); +// line, triangle, ellipse, rectangle +function handleCommonAnnotation(meetingId, whiteboardId, userId, annotation) { + const { id, status, annotationType, annotationInfo, wbId, position } = annotation; const selector = { meetingId, - 'shape.id': shape.id, + id, + userId, }; const modifier = { $set: { + whiteboardId, meetingId, + id, + status, + annotationType, + annotationInfo, + wbId, + position, + }, + $inc: { version: 1 }, + }; + + return { selector, modifier }; +} + +function handleTextUpdate(meetingId, whiteboardId, userId, annotation) { + const { id, status, annotationType, annotationInfo, wbId, position } = annotation; + + const selector = { + meetingId, + id, + userId, + }; + + annotationInfo.text = annotationInfo.text.replace(/[\r]/g, '\n'); + + const modifier = { + $set: { whiteboardId, - 'shape.id': shape.id, - 'shape.wb_id': shape.wb_id, - 'shape.shape_type': shape.shape_type, - 'shape.status': shape.status, - 'shape.shape.type': shape.shape.type, - 'shape.shape.status': shape.shape.status, + meetingId, + id, + status, + annotationType, + annotationInfo, + wbId, + position, }, - $inc: { 'shape.shape.version': 1 }, + $inc: { version: 1 }, }; - const shapeType = shape.shape_type; - - switch (shapeType) { - case SHAPE_TYPE_TEXT: - modifier.$set = Object.assign(modifier.$set, { - 'shape.shape.textBoxHeight': shape.shape.textBoxHeight, - 'shape.shape.fontColor': shape.shape.fontColor, - 'shape.shape.dataPoints': shape.shape.dataPoints, - 'shape.shape.x': shape.shape.x, - 'shape.shape.textBoxWidth': shape.shape.textBoxWidth, - 'shape.shape.whiteboardId': shape.shape.whiteboardId, - 'shape.shape.fontSize': shape.shape.fontSize, - 'shape.shape.id': shape.shape.id, - 'shape.shape.y': shape.shape.y, - 'shape.shape.calcedFontSize': shape.shape.calcedFontSize, - 'shape.shape.text': shape.shape.text.replace(/[\r]/g, '\n'), + return { selector, modifier }; +} + +function handlePencilUpdate(meetingId, whiteboardId, userId, annotation) { + // fetching annotation statuses from the config + const ANOTATION_STATUSES = Meteor.settings.public.whiteboard.annotations.status; + const DRAW_START = ANOTATION_STATUSES.start; + const DRAW_UPDATE = ANOTATION_STATUSES.update; + const DRAW_END = ANOTATION_STATUSES.end; + + const SERVER_CONFIG = Meteor.settings.app; + const PENCIL_CHUNK_SIZE = SERVER_CONFIG.pencilChunkLength || 100; + + const { id, status, annotationType, annotationInfo, wbId, position } = annotation; + + const baseSelector = { + meetingId, + id, + userId, + }; + let baseModifier; + let chunkSelector; + let chunkModifier; + + // fetching the Annotation object + const Annotation = Annotations.findOne(baseSelector); + + // a helper func, to split the initial annotation.points into subdocuments + // returns an array of { selector, modifier } objects for subdocuments. + const createPencilObjects = () => { + const chunks = []; + // if the length of the points < PENCIL_CHUNK_SIZE then we simply return an array with one chunk + if (annotationInfo.points.length < PENCIL_CHUNK_SIZE) { + const chunkId = `${id}--${1}`; + chunks.push({ + selector: { + meetingId, + userId, + id: chunkId, + }, + modifier: { + $set: { + whiteboardId, + meetingId, + id: chunkId, + status, + annotationType, + annotationInfo, + wbId, + position, + }, + $inc: { version: 1 }, + }, }); - break; + return chunks; + } - case SHAPE_TYPE_POLL_RESULT: - shape.shape.result = JSON.parse(shape.shape.result); - break; + // *default flow* + // length of the points >= PENCIL_CHUNK_SIZE, so we split them into subdocuments - default: - modifier.$set = Object.assign(modifier.$set, { - 'shape.shape.points': shape.shape.points, - 'shape.shape.whiteboardId': shape.shape.whiteboardId, - 'shape.shape.id': shape.shape.id, - 'shape.shape.square': shape.shape.square, - 'shape.shape.transparency': shape.shape.transparency, - 'shape.shape.thickness': shape.shape.thickness, - 'shape.shape.color': shape.shape.color, - 'shape.shape.result': shape.shape.result, - 'shape.shape.num_respondents': shape.shape.num_respondents, - 'shape.shape.num_responders': shape.shape.num_responders, + // counter is used for generating ids. + let i = 0; + let counter = 1; + for (; i <= annotationInfo.points.length; i += PENCIL_CHUNK_SIZE, counter += 1) { + const chunkId = `${id}--${counter}`; + + // we always need to attach the last coordinate from the previous subdocument + // to the front of the current subdocument, to connect the pencil path + const _annotationInfo = annotationInfo; + _annotationInfo.points = annotationInfo.points.slice(i === 0 ? 0 : i - 2, PENCIL_CHUNK_SIZE); + + chunks.push({ + selector: { + meetingId, + userId, + id: chunkId, + }, + modifier: { + $set: { + whiteboardId, + meetingId, + id: chunkId, + status, + annotationType, + annotationInfo: _annotationInfo, + wbId, + position, + }, + $inc: { version: 1 }, + }, }); + } + + return chunks; + }; + + switch (status) { + case DRAW_START: { + // on start we split the points + const chunks = createPencilObjects(); + + // create the 'pencil_base' + baseModifier = { + id, + userId, + meetingId, + position, + annotationType: 'pencil_base', + numberOfChunks: chunks.length, + lastChunkLength: chunks[chunks.length - 1].length, + lastCoordinate: [ + annotationInfo.points[annotationInfo.points.length - 2], + annotationInfo.points[annotationInfo.points.length - 1], + ], + }; + + // upserting all the chunks + for (let i = 0; i < chunks.length; i += 1) { + Annotations.upsert(chunks[i].selector, chunks[i].modifier); + } + + // base will be updated in the main addAnnotation event + return { selector: baseSelector, modifier: baseModifier }; + } + case DRAW_UPDATE: { + // checking if "pencil_base" exists + if (Annotation) { + const { numberOfChunks, lastChunkLength } = Annotation; + + // if lastChunkLength < PENCIL_CHUNK_SIZE then we can simply push points to the last object + if (lastChunkLength < PENCIL_CHUNK_SIZE) { + // creating a modifier for 'pencil_base' + baseModifier = { + $set: { + lastChunkLength: lastChunkLength + annotation.annotationInfo.points.length, + lastCoordinate: [ + annotationInfo.points[annotationInfo.points.length - 2], + annotationInfo.points[annotationInfo.points.length - 1], + ], + }, + }; + + const chunkId = `${id}--${numberOfChunks}`; + chunkSelector = { + meetingId, + userId, + id: chunkId, + }; + + // fetching the last pencil sub-document + const chunk = Annotations.findOne(chunkSelector); + // adding the coordinates to the end of the last sub-document + annotationInfo.points = chunk.annotationInfo.points.concat(annotationInfo.points); + + chunkModifier = { + $set: { + annotationInfo, + }, + $inc: { version: 1 }, + }; + + // if lastChunkLength > PENCIL_CHUNK_SIZE then we need to create another chunk + } else if (lastChunkLength >= PENCIL_CHUNK_SIZE) { + baseModifier = { + $set: { + numberOfChunks: numberOfChunks + 1, + lastChunkLength: annotationInfo.points.length, + lastCoordinate: [ + annotationInfo.points[annotationInfo.points.length - 2], + annotationInfo.points[annotationInfo.points.length - 1], + ], + }, + }; + + const chunkId = `${id}--${numberOfChunks + 1}`; + chunkSelector = { + meetingId, + userId, + id: chunkId, + }; + + // pushing the last coordinate to the front of the current chunk's points + annotationInfo.points.unshift(Annotation.lastCoordinate[0], Annotation.lastCoordinate[1]); + + chunkModifier = { + $set: { + whiteboardId, + meetingId, + userId, + id: chunkId, + status, + annotationType, + annotationInfo, + wbId, + position: Annotation.position, + }, + $inc: { version: 1 }, + }; + } + + // upserting the new subdocument + Annotations.upsert(chunkSelector, chunkModifier); + // base will be updated in the main AddAnnotation func + return { selector: baseSelector, modifier: baseModifier }; + } + + // **default flow** + // if we are here then it means that Annotation object is not in the db + // So creating everything similar to DRAW_START case + const _chunks = createPencilObjects(); + + // creating 'pencil_base' based on the info we received from createPencilObjects() + baseModifier = { + id, + userId, + meetingId, + position, + annotationType: 'pencil_base', + numberOfChunks: _chunks.length, + lastChunkLength: _chunks[_chunks.length - 1].length, + lastCoordinate: [ + annotationInfo.points[annotationInfo.points.length - 2], + annotationInfo.points[annotationInfo.points.length - 1], + ], + }; + + // upserting all the chunks + for (let i = 0; i < _chunks.length; i += 1) { + Annotations.upsert(_chunks[i].selector, _chunks[i].modifier); + } + + // base will be updated in the main AddAnnotation func + return { selector: baseSelector, modifier: baseModifier }; + } + case DRAW_END: { + // If a user just finished drawing with the pencil + // Removing all the sub-documents and replacing the 'pencil_base' + if (Annotation && Annotation.annotationType === 'pencil_base') { + // delete everything and replace base + const chunkIds = []; + for (let i = 0; i <= Annotation.numberOfChunks; i += 1) { + chunkIds.push(`${Annotation.id}--${i}`); + } + chunkSelector = { + meetingId, + userId, + id: { $in: chunkIds }, + }; + + Annotations.remove(chunkSelector); + } + + // Updating the main pencil object with the final info + baseModifier = { + $set: { + whiteboardId, + meetingId, + id, + status, + annotationType, + annotationInfo, + wbId, + position, + }, + $inc: { version: 1 }, + $unset: { + numberOfChunks: '', + lastChunkLength: '', + lastCoordinate: '', + }, + }; + return { selector: baseSelector, modifier: baseModifier }; + } + default: { + return {}; + } + } +} + +export default function addAnnotation(meetingId, whiteboardId, userId, annotation) { + check(meetingId, String); + check(whiteboardId, String); + check(annotation, Object); + + let query; + + switch (annotation.annotationType) { + case ANNOTATION_TYPE_TEXT: + query = handleTextUpdate(meetingId, whiteboardId, userId, annotation); + break; + case ANNOTATION_TYPE_PENCIL: + query = handlePencilUpdate(meetingId, whiteboardId, userId, annotation); + break; + default: + query = handleCommonAnnotation(meetingId, whiteboardId, userId, annotation); break; } const cb = (err, numChanged) => { if (err) { - return Logger.error(`Adding shape to collection: ${err}`); + return Logger.error(`Adding annotation2x to collection: ${err}`); } const { insertedId } = numChanged; if (insertedId) { - return Logger.info(`Added shape id=${shape.id} whiteboard=${whiteboardId}`); + return Logger.info(`Added annotation2x id=${annotation.id} whiteboard=${whiteboardId}`); } - if (numChanged) { - return Logger.info(`Upserted shape id=${shape.id} whiteboard=${whiteboardId}`); - } + return Logger.info(`Upserted annotation2x id=${annotation.id} whiteboard=${whiteboardId}`); }; - return Shapes.upsert(selector, modifier, cb); + return Annotations.upsert(query.selector, query.modifier, cb); } diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/clearShapes.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/clearShapes.js old mode 100644 new mode 100755 index e6b1442f0d..5917be30cd --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/clearShapes.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/clearShapes.js @@ -1,10 +1,36 @@ +import Annotations from '/imports/api/2.0/annotations'; import Logger from '/imports/startup/server/logger'; -import Shapes from './../../'; -export default function clearShapes(meetingId) { +export default function clearAnnotations(meetingId, whiteboardId, userId) { + const selector = {}; + if (meetingId) { - return Shapes.remove({ meetingId }, Logger.info(`Cleared Shapes (${meetingId})`)); + selector.meetingId = meetingId; + } + + if (whiteboardId) { + selector.whiteboardId = whiteboardId; + } + + if (userId) { + selector.userId = userId; } - return Shapes.remove({}, Logger.info('Cleared Shapes (all)')); + const cb = (err) => { + if (err) { + return Logger.error(`Removing Shapes2x from collection: ${err}`); + } + + if (!meetingId) { + return Logger.info('Cleared Annotations (all)'); + } + + if (userId) { + return Logger.info(`Removed Shapes2x for userId=${userId} where whiteboard=${whiteboardId}`); + } + + return Logger.info(`Removed Shapes2x where whiteboard=${whiteboardId}`); + }; + + return Annotations.remove(selector, cb); } diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/clearShapesWhiteboard.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/clearShapesWhiteboard.js deleted file mode 100644 index efaf0bf5f6..0000000000 --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/clearShapesWhiteboard.js +++ /dev/null @@ -1,23 +0,0 @@ -import { check } from 'meteor/check'; -import Logger from '/imports/startup/server/logger'; -import Shapes from './../../'; - -export default function clearShapesWhiteboard(meetingId, whiteboardId) { - check(meetingId, String); - check(whiteboardId, String); - - const selector = { - meetingId, - whiteboardId, - }; - - const cb = (err) => { - if (err) { - return Logger.error(`Removing Shapes from collection: ${err}`); - } - - return Logger.info(`Removed Shapes where whiteboard=${whiteboardId}`); - }; - - return Shapes.remove(selector, cb); -} diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/removeShape.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/removeShape.js old mode 100644 new mode 100755 index 16dbdbd594..04a8ec71ab --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/removeShape.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/modifiers/removeShape.js @@ -1,8 +1,8 @@ import { check } from 'meteor/check'; +import Annotations from '/imports/api/2.0/annotations'; import Logger from '/imports/startup/server/logger'; -import Shapes from './../../'; -export default function removeShape(meetingId, whiteboardId, shapeId) { +export default function removeAnnotation(meetingId, whiteboardId, shapeId) { check(meetingId, String); check(whiteboardId, String); check(shapeId, String); @@ -10,18 +10,16 @@ export default function removeShape(meetingId, whiteboardId, shapeId) { const selector = { meetingId, whiteboardId, - 'shape.id': shapeId, + id: shapeId, }; - const cb = (err, numChanged) => { + const cb = (err) => { if (err) { - return Logger.error(`Removing shape from collection: ${err}`); + return Logger.error(`Removing annotation from collection: ${err}`); } - if (numChanged) { - return Logger.info(`Removed shape id=${shapeId} whiteboard=${whiteboardId}`); - } + return Logger.info(`Removed annotation id=${shapeId} whiteboard=${whiteboardId}`); }; - return Shapes.remove(selector, cb); + return Annotations.remove(selector, cb); } diff --git a/bigbluebutton-html5/imports/api/1.1/shapes/server/publishers.js b/bigbluebutton-html5/imports/api/1.1/shapes/server/publishers.js old mode 100644 new mode 100755 index c4029f2dbe..429497ed52 --- a/bigbluebutton-html5/imports/api/1.1/shapes/server/publishers.js +++ b/bigbluebutton-html5/imports/api/1.1/shapes/server/publishers.js @@ -1,24 +1,24 @@ +import Annotations from '/imports/api/2.0/annotations'; import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import Logger from '/imports/startup/server/logger'; import mapToAcl from '/imports/startup/mapToAcl'; -import Shapes from './../'; -function shapes(credentials) { +function annotations(credentials) { const { meetingId, requesterUserId, requesterToken } = credentials; check(meetingId, String); check(requesterUserId, String); check(requesterToken, String); - Logger.info(`Publishing Shapes for ${meetingId} ${requesterUserId} ${requesterToken}`); + Logger.info(`Publishing Annotations2x for ${meetingId} ${requesterUserId} ${requesterToken}`); - return Shapes.find({ meetingId }); + return Annotations.find({ meetingId }); } function publish(...args) { - const boundShapes = shapes.bind(this); - return mapToAcl('subscriptions.shapes', boundShapes)(args); + const boundAnnotations = annotations.bind(this); + return mapToAcl('subscriptions.annotations', boundAnnotations)(args); } -Meteor.publish('shapes', publish); +Meteor.publish('annotations', publish); diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/index.js b/bigbluebutton-html5/imports/api/2.0/annotations/index.js deleted file mode 100644 index 7241f893ae..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -const Annotations = new Mongo.Collection('annotations'); - -if (Meteor.isServer) { - // types of queries for the annotations: - // 1. meetingId, whiteboardId - // 2. meetingId, whiteboardId, userId - // 3. meetingId, id, userId - // 4. meetingId, whiteboardId, id - // These 2 indexes seem to cover all of the cases - // Either mongo uses a whole or a part of the compound index - // Or it uses 'id' and then matches other fields - - Annotations._ensureIndex({ id: 1 }); - Annotations._ensureIndex({ meetingId: 1, whiteboardId: 1, userId: 1 }); -} - -export default Annotations; diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/eventHandlers.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/eventHandlers.js deleted file mode 100644 index 99dbb3c799..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/eventHandlers.js +++ /dev/null @@ -1,10 +0,0 @@ -import RedisPubSub from '/imports/startup/server/redis2x'; -import handleWhiteboardCleared from './handlers/whiteboardCleared'; -import handleWhiteboardUndo from './handlers/whiteboardUndo'; -import handleWhiteboardSend from './handlers/whiteboardSend'; -import handleWhiteboardAnnotations from './handlers/whiteboardAnnotations'; - -RedisPubSub.on('ClearWhiteboardEvtMsg', handleWhiteboardCleared); -RedisPubSub.on('UndoWhiteboardEvtMsg', handleWhiteboardUndo); -RedisPubSub.on('SendWhiteboardAnnotationEvtMsg', handleWhiteboardSend); -RedisPubSub.on('GetWhiteboardAnnotationsRespMsg', handleWhiteboardAnnotations); diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardAnnotations.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardAnnotations.js deleted file mode 100644 index e6f99e452a..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardAnnotations.js +++ /dev/null @@ -1,22 +0,0 @@ -import _ from 'lodash'; -import { check } from 'meteor/check'; -import clearAnnotations from '../modifiers/clearAnnotations'; -import addAnnotation from '../modifiers/addAnnotation'; - -export default function handleWhiteboardAnnotations({ body }, meetingId) { - check(meetingId, String); - check(body, Object); - - const { annotations, whiteboardId } = body; - - check(whiteboardId, String); - clearAnnotations(meetingId, whiteboardId); - - const annotationsAdded = []; - _.each(annotations, (annotation) => { - const { wbId, userId } = annotation; - annotationsAdded.push(addAnnotation(meetingId, wbId, userId, annotation)); - }); - - return annotationsAdded; -} diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardCleared.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardCleared.js deleted file mode 100644 index 7e6b25d151..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardCleared.js +++ /dev/null @@ -1,19 +0,0 @@ -import { check } from 'meteor/check'; - -import clearAnnotations from '../modifiers/clearAnnotations'; - -export default function handleWhiteboardCleared({ body }, meetingId) { - check(body, { - userId: String, - whiteboardId: String, - fullClear: Boolean, - }); - - const { whiteboardId, fullClear, userId } = body; - - if (fullClear) { - return clearAnnotations(meetingId, whiteboardId); - } - - return clearAnnotations(meetingId, whiteboardId, userId); -} diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardSend.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardSend.js deleted file mode 100644 index 2ca1d84964..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardSend.js +++ /dev/null @@ -1,17 +0,0 @@ -import { check } from 'meteor/check'; - -import addAnnotation from '../modifiers/addAnnotation'; - -export default function handleWhiteboardSend({ header, body }, meetingId) { - const userId = header.userId; - const annotation = body.annotation; - - check(userId, String); - check(annotation, Object); - - const whiteboardId = annotation.wbId; - - check(whiteboardId, String); - - return addAnnotation(meetingId, whiteboardId, userId, annotation); -} diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardUndo.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardUndo.js deleted file mode 100644 index 3194362af3..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/handlers/whiteboardUndo.js +++ /dev/null @@ -1,13 +0,0 @@ -import { check } from 'meteor/check'; - -import removeAnnotation from '../modifiers/removeAnnotation'; - -export default function handleWhiteboardUndo({ body }, meetingId) { - const whiteboardId = body.whiteboardId; - const shapeId = body.annotationId; - - check(whiteboardId, String); - check(shapeId, String); - - return removeAnnotation(meetingId, whiteboardId, shapeId); -} diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/index.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/index.js deleted file mode 100644 index 92451ac76b..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import './eventHandlers'; -import './methods'; -import './publishers'; diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/methods.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/methods.js deleted file mode 100644 index 57d5c4d0d4..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/methods.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import undoAnnotation from './methods/undoAnnotation'; -import clearWhiteboard from './methods/clearWhiteboard'; -import sendAnnotation from './methods/sendAnnotation'; - -Meteor.methods({ - undoAnnotation, - clearWhiteboard, - sendAnnotation, -}); diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/clearWhiteboard.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/clearWhiteboard.js deleted file mode 100644 index 406329a2e2..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/clearWhiteboard.js +++ /dev/null @@ -1,31 +0,0 @@ -import Acl from '/imports/startup/acl'; -import { getMultiUserStatus } from '/imports/api/common/server/helpers'; -import RedisPubSub from '/imports/startup/server/redis2x'; -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; - -export default function clearWhiteboard(credentials, whiteboardId) { - const REDIS_CONFIG = Meteor.settings.redis; - const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; - const EVENT_NAME = 'ClearWhiteboardPubMsg'; - - const { meetingId, requesterUserId, requesterToken } = credentials; - - check(meetingId, String); - check(requesterUserId, String); - check(requesterToken, String); - check(whiteboardId, String); - - const allowed = Acl.can('methods.clearWhiteboard', credentials) || getMultiUserStatus(meetingId); - if (!allowed) { - throw new Meteor.Error( - 'not-allowed', `User ${requesterUserId} is not allowed to clear the whiteboard`, - ); - } - - const payload = { - whiteboardId, - }; - - return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); -} diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/sendAnnotation.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/sendAnnotation.js deleted file mode 100644 index 0a6fb16c27..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/sendAnnotation.js +++ /dev/null @@ -1,58 +0,0 @@ -import Acl from '/imports/startup/acl'; -import { getMultiUserStatus } from '/imports/api/common/server/helpers'; -import RedisPubSub from '/imports/startup/server/redis2x'; -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; -import Annotations from '/imports/api/2.0/annotations'; - -function isLastMessage(annotation, userId) { - const DRAW_END = Meteor.settings.public.whiteboard.annotations.status.end; - - if (annotation.status === DRAW_END) { - const selector = { - id: annotation.id, - userId, - }; - - const _annotation = Annotations.findOne(selector); - return _annotation !== null; - } - - return false; -} - -export default function sendAnnotation(credentials, annotation) { - const REDIS_CONFIG = Meteor.settings.redis; - const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; - const EVENT_NAME = 'SendWhiteboardAnnotationPubMsg'; - - const { meetingId, requesterUserId, requesterToken } = credentials; - - check(meetingId, String); - check(requesterUserId, String); - check(requesterToken, String); - check(annotation, Object); - - // We allow messages to pass through in 3 cases: - // 1. When it's a standard message in presenter mode (Acl check) - // 2. When it's a standard message in multi-user mode (getMultUserStatus check) - // 3. When it's the last message, happens when the user is currently drawing - // and then slide/presentation changes, the user lost presenter rights, - // or multi-user whiteboard gets turned off - // So we allow the last "DRAW_END" message to pass through, to finish the shape. - const allowed = Acl.can('methods.sendAnnotation', credentials) || - getMultiUserStatus(meetingId) || - isLastMessage(annotation, requesterUserId); - - if (!allowed) { - throw new Meteor.Error( - 'not-allowed', `User ${requesterUserId} is not allowed to send an annotation`, - ); - } - - const payload = { - annotation, - }; - - return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); -} diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/undoAnnotation.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/undoAnnotation.js deleted file mode 100644 index d7adf86b5e..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/undoAnnotation.js +++ /dev/null @@ -1,31 +0,0 @@ -import Acl from '/imports/startup/acl'; -import { getMultiUserStatus } from '/imports/api/common/server/helpers'; -import RedisPubSub from '/imports/startup/server/redis2x'; -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; - -export default function undoAnnotation(credentials, whiteboardId) { - const REDIS_CONFIG = Meteor.settings.redis; - const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; - const EVENT_NAME = 'UndoWhiteboardPubMsg'; - - const { meetingId, requesterUserId, requesterToken } = credentials; - - check(meetingId, String); - check(requesterUserId, String); - check(requesterToken, String); - check(whiteboardId, String); - - const allowed = Acl.can('methods.undoAnnotation', credentials) || getMultiUserStatus(meetingId); - if (!allowed) { - throw new Meteor.Error( - 'not-allowed', `User ${requesterUserId} is not allowed to undo the annotation`, - ); - } - - const payload = { - whiteboardId, - }; - - return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); -} diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/addAnnotation.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/addAnnotation.js deleted file mode 100644 index b1e8c26670..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/addAnnotation.js +++ /dev/null @@ -1,372 +0,0 @@ -import { check } from 'meteor/check'; -import Logger from '/imports/startup/server/logger'; -import Annotations from '/imports/api/2.0/annotations'; - -const ANNOTATION_TYPE_TEXT = 'text'; -const ANNOTATION_TYPE_PENCIL = 'pencil'; - -// line, triangle, ellipse, rectangle -function handleCommonAnnotation(meetingId, whiteboardId, userId, annotation) { - const { id, status, annotationType, annotationInfo, wbId, position } = annotation; - - const selector = { - meetingId, - id, - userId, - }; - - const modifier = { - $set: { - whiteboardId, - meetingId, - id, - status, - annotationType, - annotationInfo, - wbId, - position, - }, - $inc: { version: 1 }, - }; - - return { selector, modifier }; -} - -function handleTextUpdate(meetingId, whiteboardId, userId, annotation) { - const { id, status, annotationType, annotationInfo, wbId, position } = annotation; - - const selector = { - meetingId, - id, - userId, - }; - - annotationInfo.text = annotationInfo.text.replace(/[\r]/g, '\n'); - - const modifier = { - $set: { - whiteboardId, - meetingId, - id, - status, - annotationType, - annotationInfo, - wbId, - position, - }, - $inc: { version: 1 }, - }; - - return { selector, modifier }; -} - -function handlePencilUpdate(meetingId, whiteboardId, userId, annotation) { - // fetching annotation statuses from the config - const ANOTATION_STATUSES = Meteor.settings.public.whiteboard.annotations.status; - const DRAW_START = ANOTATION_STATUSES.start; - const DRAW_UPDATE = ANOTATION_STATUSES.update; - const DRAW_END = ANOTATION_STATUSES.end; - - const SERVER_CONFIG = Meteor.settings.app; - const PENCIL_CHUNK_SIZE = SERVER_CONFIG.pencilChunkLength || 100; - - const { id, status, annotationType, annotationInfo, wbId, position } = annotation; - - const baseSelector = { - meetingId, - id, - userId, - }; - let baseModifier; - let chunkSelector; - let chunkModifier; - - // fetching the Annotation object - const Annotation = Annotations.findOne(baseSelector); - - // a helper func, to split the initial annotation.points into subdocuments - // returns an array of { selector, modifier } objects for subdocuments. - const createPencilObjects = () => { - const chunks = []; - // if the length of the points < PENCIL_CHUNK_SIZE then we simply return an array with one chunk - if (annotationInfo.points.length < PENCIL_CHUNK_SIZE) { - const chunkId = `${id}--${1}`; - chunks.push({ - selector: { - meetingId, - userId, - id: chunkId, - }, - modifier: { - $set: { - whiteboardId, - meetingId, - id: chunkId, - status, - annotationType, - annotationInfo, - wbId, - position, - }, - $inc: { version: 1 }, - }, - }); - return chunks; - } - - // *default flow* - // length of the points >= PENCIL_CHUNK_SIZE, so we split them into subdocuments - - // counter is used for generating ids. - let i = 0; - let counter = 1; - for (; i <= annotationInfo.points.length; i += PENCIL_CHUNK_SIZE, counter += 1) { - const chunkId = `${id}--${counter}`; - - // we always need to attach the last coordinate from the previous subdocument - // to the front of the current subdocument, to connect the pencil path - const _annotationInfo = annotationInfo; - _annotationInfo.points = annotationInfo.points.slice(i === 0 ? 0 : i - 2, PENCIL_CHUNK_SIZE); - - chunks.push({ - selector: { - meetingId, - userId, - id: chunkId, - }, - modifier: { - $set: { - whiteboardId, - meetingId, - id: chunkId, - status, - annotationType, - annotationInfo: _annotationInfo, - wbId, - position, - }, - $inc: { version: 1 }, - }, - }); - } - - return chunks; - }; - - switch (status) { - case DRAW_START: { - // on start we split the points - const chunks = createPencilObjects(); - - // create the 'pencil_base' - baseModifier = { - id, - userId, - meetingId, - position, - annotationType: 'pencil_base', - numberOfChunks: chunks.length, - lastChunkLength: chunks[chunks.length - 1].length, - lastCoordinate: [ - annotationInfo.points[annotationInfo.points.length - 2], - annotationInfo.points[annotationInfo.points.length - 1], - ], - }; - - // upserting all the chunks - for (let i = 0; i < chunks.length; i += 1) { - Annotations.upsert(chunks[i].selector, chunks[i].modifier); - } - - // base will be updated in the main addAnnotation event - return { selector: baseSelector, modifier: baseModifier }; - } - case DRAW_UPDATE: { - // checking if "pencil_base" exists - if (Annotation) { - const { numberOfChunks, lastChunkLength } = Annotation; - - // if lastChunkLength < PENCIL_CHUNK_SIZE then we can simply push points to the last object - if (lastChunkLength < PENCIL_CHUNK_SIZE) { - // creating a modifier for 'pencil_base' - baseModifier = { - $set: { - lastChunkLength: lastChunkLength + annotation.annotationInfo.points.length, - lastCoordinate: [ - annotationInfo.points[annotationInfo.points.length - 2], - annotationInfo.points[annotationInfo.points.length - 1], - ], - }, - }; - - const chunkId = `${id}--${numberOfChunks}`; - chunkSelector = { - meetingId, - userId, - id: chunkId, - }; - - // fetching the last pencil sub-document - const chunk = Annotations.findOne(chunkSelector); - // adding the coordinates to the end of the last sub-document - annotationInfo.points = chunk.annotationInfo.points.concat(annotationInfo.points); - - chunkModifier = { - $set: { - annotationInfo, - }, - $inc: { version: 1 }, - }; - - // if lastChunkLength > PENCIL_CHUNK_SIZE then we need to create another chunk - } else if (lastChunkLength >= PENCIL_CHUNK_SIZE) { - baseModifier = { - $set: { - numberOfChunks: numberOfChunks + 1, - lastChunkLength: annotationInfo.points.length, - lastCoordinate: [ - annotationInfo.points[annotationInfo.points.length - 2], - annotationInfo.points[annotationInfo.points.length - 1], - ], - }, - }; - - const chunkId = `${id}--${numberOfChunks + 1}`; - chunkSelector = { - meetingId, - userId, - id: chunkId, - }; - - // pushing the last coordinate to the front of the current chunk's points - annotationInfo.points.unshift(Annotation.lastCoordinate[0], Annotation.lastCoordinate[1]); - - chunkModifier = { - $set: { - whiteboardId, - meetingId, - userId, - id: chunkId, - status, - annotationType, - annotationInfo, - wbId, - position: Annotation.position, - }, - $inc: { version: 1 }, - }; - } - - // upserting the new subdocument - Annotations.upsert(chunkSelector, chunkModifier); - // base will be updated in the main AddAnnotation func - return { selector: baseSelector, modifier: baseModifier }; - } - - // **default flow** - // if we are here then it means that Annotation object is not in the db - // So creating everything similar to DRAW_START case - const _chunks = createPencilObjects(); - - // creating 'pencil_base' based on the info we received from createPencilObjects() - baseModifier = { - id, - userId, - meetingId, - position, - annotationType: 'pencil_base', - numberOfChunks: _chunks.length, - lastChunkLength: _chunks[_chunks.length - 1].length, - lastCoordinate: [ - annotationInfo.points[annotationInfo.points.length - 2], - annotationInfo.points[annotationInfo.points.length - 1], - ], - }; - - // upserting all the chunks - for (let i = 0; i < _chunks.length; i += 1) { - Annotations.upsert(_chunks[i].selector, _chunks[i].modifier); - } - - // base will be updated in the main AddAnnotation func - return { selector: baseSelector, modifier: baseModifier }; - } - case DRAW_END: { - // If a user just finished drawing with the pencil - // Removing all the sub-documents and replacing the 'pencil_base' - if (Annotation && Annotation.annotationType === 'pencil_base') { - // delete everything and replace base - const chunkIds = []; - for (let i = 0; i <= Annotation.numberOfChunks; i += 1) { - chunkIds.push(`${Annotation.id}--${i}`); - } - chunkSelector = { - meetingId, - userId, - id: { $in: chunkIds }, - }; - - Annotations.remove(chunkSelector); - } - - // Updating the main pencil object with the final info - baseModifier = { - $set: { - whiteboardId, - meetingId, - id, - status, - annotationType, - annotationInfo, - wbId, - position, - }, - $inc: { version: 1 }, - $unset: { - numberOfChunks: '', - lastChunkLength: '', - lastCoordinate: '', - }, - }; - return { selector: baseSelector, modifier: baseModifier }; - } - default: { - return {}; - } - } -} - -export default function addAnnotation(meetingId, whiteboardId, userId, annotation) { - check(meetingId, String); - check(whiteboardId, String); - check(annotation, Object); - - let query; - - switch (annotation.annotationType) { - case ANNOTATION_TYPE_TEXT: - query = handleTextUpdate(meetingId, whiteboardId, userId, annotation); - break; - case ANNOTATION_TYPE_PENCIL: - query = handlePencilUpdate(meetingId, whiteboardId, userId, annotation); - break; - default: - query = handleCommonAnnotation(meetingId, whiteboardId, userId, annotation); - break; - } - - const cb = (err, numChanged) => { - if (err) { - return Logger.error(`Adding annotation2x to collection: ${err}`); - } - - const { insertedId } = numChanged; - if (insertedId) { - return Logger.info(`Added annotation2x id=${annotation.id} whiteboard=${whiteboardId}`); - } - - return Logger.info(`Upserted annotation2x id=${annotation.id} whiteboard=${whiteboardId}`); - }; - - return Annotations.upsert(query.selector, query.modifier, cb); -} diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/clearAnnotations.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/clearAnnotations.js deleted file mode 100644 index 5917be30cd..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/clearAnnotations.js +++ /dev/null @@ -1,36 +0,0 @@ -import Annotations from '/imports/api/2.0/annotations'; -import Logger from '/imports/startup/server/logger'; - -export default function clearAnnotations(meetingId, whiteboardId, userId) { - const selector = {}; - - if (meetingId) { - selector.meetingId = meetingId; - } - - if (whiteboardId) { - selector.whiteboardId = whiteboardId; - } - - if (userId) { - selector.userId = userId; - } - - const cb = (err) => { - if (err) { - return Logger.error(`Removing Shapes2x from collection: ${err}`); - } - - if (!meetingId) { - return Logger.info('Cleared Annotations (all)'); - } - - if (userId) { - return Logger.info(`Removed Shapes2x for userId=${userId} where whiteboard=${whiteboardId}`); - } - - return Logger.info(`Removed Shapes2x where whiteboard=${whiteboardId}`); - }; - - return Annotations.remove(selector, cb); -} diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/removeAnnotation.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/removeAnnotation.js deleted file mode 100644 index 04a8ec71ab..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/modifiers/removeAnnotation.js +++ /dev/null @@ -1,25 +0,0 @@ -import { check } from 'meteor/check'; -import Annotations from '/imports/api/2.0/annotations'; -import Logger from '/imports/startup/server/logger'; - -export default function removeAnnotation(meetingId, whiteboardId, shapeId) { - check(meetingId, String); - check(whiteboardId, String); - check(shapeId, String); - - const selector = { - meetingId, - whiteboardId, - id: shapeId, - }; - - const cb = (err) => { - if (err) { - return Logger.error(`Removing annotation from collection: ${err}`); - } - - return Logger.info(`Removed annotation id=${shapeId} whiteboard=${whiteboardId}`); - }; - - return Annotations.remove(selector, cb); -} diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/publishers.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/publishers.js deleted file mode 100644 index 429497ed52..0000000000 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/publishers.js +++ /dev/null @@ -1,24 +0,0 @@ -import Annotations from '/imports/api/2.0/annotations'; -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; -import Logger from '/imports/startup/server/logger'; -import mapToAcl from '/imports/startup/mapToAcl'; - -function annotations(credentials) { - const { meetingId, requesterUserId, requesterToken } = credentials; - - check(meetingId, String); - check(requesterUserId, String); - check(requesterToken, String); - - Logger.info(`Publishing Annotations2x for ${meetingId} ${requesterUserId} ${requesterToken}`); - - return Annotations.find({ meetingId }); -} - -function publish(...args) { - const boundAnnotations = annotations.bind(this); - return mapToAcl('subscriptions.annotations', boundAnnotations)(args); -} - -Meteor.publish('annotations', publish); -- GitLab