Merge branch 'v3.0.x-release' into cleanup/runuser
This commit is contained in:
commit
c9ce945aa5
2
.github/ISSUE_TEMPLATE/docs-issue.md
vendored
2
.github/ISSUE_TEMPLATE/docs-issue.md
vendored
@ -13,7 +13,7 @@ This issue tracker is only for bbb development or docs related issues.-->
|
||||
**Link to the portion of the docs that is out of date**
|
||||
If applicable, link to the section of the docs that is out of date.
|
||||
|
||||
**Describe what you belive the correct version should be**
|
||||
**Describe what you believe the correct version should be**
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your concern.
|
||||
|
106
.github/dependabot.yml
vendored
Normal file
106
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
# maintaining legacy branch
|
||||
# no configuration for now
|
||||
|
||||
# current branch
|
||||
## excluding bigbluebutton-tests/playwright, bigbluebutton-tests/puppeteer, docs
|
||||
- package-ecosystem: npm
|
||||
directory: "/bbb-export-annotations"
|
||||
target-branch: "v2.7.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 0 # zero means only security pull requests and no (optional) version updates
|
||||
- package-ecosystem: npm
|
||||
directory: "/bigbluebutton-html5"
|
||||
target-branch: "v2.7.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 0 # zero means only security pull requests and no (optional) version updates
|
||||
- package-ecosystem: npm
|
||||
directory: "/bbb-learning-dashboard"
|
||||
target-branch: "v2.7.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 0 # zero means only security pull requests and no (optional) version updates
|
||||
- package-ecosystem: gradle
|
||||
directory: "/bigbluebutton-web"
|
||||
target-branch: "v2.7.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 0 # zero means only security pull requests and no (optional) version updates
|
||||
- package-ecosystem: bundler
|
||||
directory: "/record-and-playback/core"
|
||||
target-branch: "v2.7.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 0 # zero means only security pull requests and no (optional) version updates
|
||||
vendor: true
|
||||
- package-ecosystem: maven
|
||||
directory: "/bbb-fsesl-client"
|
||||
target-branch: "v2.7.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 0 # zero means only security pull requests and no (optional) version updates
|
||||
|
||||
|
||||
# upcoming release branch
|
||||
## excluding bigbluebutton-tests/playwright, bigbluebutton-tests/puppeteer, docs, bbb-graphql-client-test
|
||||
- package-ecosystem: npm
|
||||
directory: "/bbb-graphql-actions"
|
||||
target-branch: "v3.0.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 20 # both security and versions updates
|
||||
- package-ecosystem: npm
|
||||
directory: "/bbb-export-annotations"
|
||||
target-branch: "v3.0.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 20 # both security and versions updates
|
||||
- package-ecosystem: npm
|
||||
directory: "/bigbluebutton-html5"
|
||||
target-branch: "v3.0.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 20 # both security and versions updates
|
||||
- package-ecosystem: npm
|
||||
directory: "/bbb-learning-dashboard"
|
||||
target-branch: "v3.0.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 20 # both security and versions updates
|
||||
- package-ecosystem: gradle
|
||||
directory: "/bigbluebutton-web"
|
||||
target-branch: "v3.0.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 20 # both security and versions updates
|
||||
- package-ecosystem: bundler
|
||||
directory: "/record-and-playback/core"
|
||||
target-branch: "v3.0.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 20 # both security and versions updates
|
||||
- package-ecosystem: maven
|
||||
directory: "/bbb-fsesl-client"
|
||||
target-branch: "v3.0.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 20 # both security and versions updates
|
||||
- package-ecosystem: gomod
|
||||
directory: "/bbb-graphql-middleware"
|
||||
target-branch: "v3.0.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 20 # both security and versions updates
|
||||
- package-ecosystem: gomod
|
||||
directory: "/bbb-graphql-server"
|
||||
target-branch: "v3.0.x-release"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 20 # both security and versions updates
|
||||
|
||||
# upstream (default) branch
|
||||
# no configuration for now
|
11
.github/workflows/automated-tests.yml
vendored
11
.github/workflows/automated-tests.yml
vendored
@ -54,7 +54,7 @@ jobs:
|
||||
build-list: bbb-playback bbb-playback-notes bbb-playback-podcast bbb-playback-presentation bbb-playback-screenshare bbb-playback-video bbb-record-core
|
||||
- package: bbb-graphql-server
|
||||
build-name: bbb-graphql-server
|
||||
build-list: bbb-graphql-server bbb-graphql-middleware bbb-graphql-actions-adapter-server
|
||||
build-list: bbb-graphql-server bbb-graphql-middleware bbb-graphql-actions
|
||||
- package: bbb-etherpad
|
||||
cache-files-list: bbb-etherpad.placeholder.sh
|
||||
cache-urls-list: https://api.github.com/repos/mconf/ep_pad_ttl/commits https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits https://api.github.com/repos/mconf/ep_redis_publisher/commits https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits
|
||||
@ -279,13 +279,18 @@ jobs:
|
||||
npx playwright install
|
||||
'
|
||||
- name: Run tests
|
||||
working-directory: ./bigbluebutton-tests/playwright
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_minutes: 25
|
||||
max_attempts: 3
|
||||
command: |
|
||||
cd ./bigbluebutton-tests/playwright
|
||||
npm run test-chromium-ci -- --shard ${{ matrix.shard }}
|
||||
env:
|
||||
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
|
||||
ACTIONS_RUNNER_DEBUG: true
|
||||
BBB_URL: https://bbb-ci.test/bigbluebutton/api
|
||||
BBB_SECRET: bbbci
|
||||
run: npm run test-chromium-ci -- --shard ${{ matrix.shard }}
|
||||
- name: Run Firefox tests
|
||||
working-directory: ./bigbluebutton-tests/playwright
|
||||
if: |
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,3 +23,4 @@ cache/*
|
||||
artifacts/*
|
||||
bbb-presentation-video.zip
|
||||
bbb-presentation-video
|
||||
bbb-graphql-actions-adapter-server/
|
||||
|
12
README.md
12
README.md
@ -1,20 +1,20 @@
|
||||
BigBlueButton
|
||||
=============
|
||||
BigBlueButton is an open source virtual classroom designed to help teachers teach and learners learn.
|
||||
BigBlueButton is an open-source virtual classroom designed to help teachers teach and learners learn.
|
||||
|
||||
BigBlueButton supports real-time sharing of audio, video, slides (with whiteboard annotations), chat, and the screen. Instructors can engage remote students with polling, emojis, multi-user whiteboard, shared notes, and breakout rooms. During the session, BigBlueButton generates analytics that are visible to moderators in the Learning Analytics Dashboard.
|
||||
BigBlueButton supports real-time sharing of audio, video, slides (with whiteboard annotations), chat, and the screen. Instructors can engage remote students with polling, emojis, multi-user whiteboards, shared notes, and breakout rooms. During the session, BigBlueButton generates analytics that are visible to moderators in the Learning Analytics Dashboard.
|
||||
|
||||
Presenters can record and playback content for later sharing with others.
|
||||
|
||||
We designed BigBlueButton for online learning, it can be used for many other applicationsas well). The educational use cases for BigBlueButton are
|
||||
We designed BigBlueButton for online learning, it can be used for many other applications as well. The educational use cases for BigBlueButton are
|
||||
|
||||
* Online tutoring (one-to-one)
|
||||
* Flipped classrooms (recording content ahead of your session)
|
||||
* Group collaboration (many-to-many)
|
||||
* Online classes (one-to-many)
|
||||
|
||||
The latest version is BigBlueButton 2.6. You can install BigBlueButton 2.6 on Ubuntu 20.04 using [bbb-install.sh](https://github.com/bigbluebutton/bbb-install) within 30 minutes (or your money back 😉).
|
||||
The latest version is BigBlueButton 2.7. You can install BigBlueButton 2.6 on Ubuntu 20.04 using [bbb-install.sh](https://github.com/bigbluebutton/bbb-install) within 30 minutes (or your money back 😉).
|
||||
|
||||
For full technical documentation BigBlueButton -- including architecture, features, API, and GreenLight (the default front-end) -- see [https://docs.bigbluebutton.org/](https://docs.bigbluebutton.org/).
|
||||
For full technical documentation of BigBlueButton -- including architecture, features, API, and GreenLight (the default front-end) -- see [https://docs.bigbluebutton.org/](https://docs.bigbluebutton.org/).
|
||||
|
||||
BigBlueButton and the BigBlueButton Logo are trademarks of [BigBlueButton Inc](https://bigbluebutton.org) .
|
||||
BigBlueButton and the BigBlueButton Logo are trademarks of [BigBlueButton Inc](https://bigbluebutton.org).
|
||||
|
@ -10,7 +10,7 @@ import org.bigbluebutton.core.bus._
|
||||
import org.bigbluebutton.core.pubsub.senders.ReceivedJsonMsgHandlerActor
|
||||
import org.bigbluebutton.core2.AnalyticsActor
|
||||
import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor
|
||||
import org.bigbluebutton.endpoint.redis.{AppsRedisSubscriberActor, ExportAnnotationsActor, GraphqlActionsActor, LearningDashboardActor, RedisRecorderActor}
|
||||
import org.bigbluebutton.endpoint.redis.{AppsRedisSubscriberActor, ExportAnnotationsActor, GraphqlConnectionsActor, LearningDashboardActor, RedisRecorderActor}
|
||||
import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
|
||||
import org.bigbluebutton.service.{HealthzService, MeetingInfoActor, MeetingInfoService}
|
||||
|
||||
@ -67,9 +67,9 @@ object Boot extends App with SystemConfiguration {
|
||||
"LearningDashboardActor"
|
||||
)
|
||||
|
||||
val graphqlActionsActor = system.actorOf(
|
||||
GraphqlActionsActor.props(system),
|
||||
"GraphqlActionsActor"
|
||||
val graphqlConnectionsActor = system.actorOf(
|
||||
GraphqlConnectionsActor.props(system, eventBus, outGW),
|
||||
"GraphqlConnectionsActor"
|
||||
)
|
||||
|
||||
ClientSettings.loadClientSettingsFromFile()
|
||||
@ -89,8 +89,8 @@ object Boot extends App with SystemConfiguration {
|
||||
outBus2.subscribe(learningDashboardActor, outBbbMsgMsgChannel)
|
||||
bbbMsgBus.subscribe(learningDashboardActor, analyticsChannel)
|
||||
|
||||
eventBus.subscribe(graphqlActionsActor, meetingManagerChannel)
|
||||
bbbMsgBus.subscribe(graphqlActionsActor, analyticsChannel)
|
||||
eventBus.subscribe(graphqlConnectionsActor, meetingManagerChannel)
|
||||
bbbMsgBus.subscribe(graphqlConnectionsActor, analyticsChannel)
|
||||
|
||||
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor")
|
||||
eventBus.subscribe(bbbActor, meetingManagerChannel)
|
||||
|
@ -52,6 +52,33 @@ object ClientSettings extends SystemConfiguration {
|
||||
} else clientSettingsFromFile
|
||||
}
|
||||
|
||||
def getConfigPropertyValueByPathAsIntOrElse(map: Map[String, Any], path: String, alternativeValue: Int): Int = {
|
||||
getConfigPropertyValueByPath(map, path) match {
|
||||
case Some(configValue: Int) => configValue
|
||||
case _ =>
|
||||
logger.debug(s"Config `$path` with type Integer not found in clientSettings.")
|
||||
alternativeValue
|
||||
}
|
||||
}
|
||||
|
||||
def getConfigPropertyValueByPathAsStringOrElse(map: Map[String, Any], path: String, alternativeValue: String): String = {
|
||||
getConfigPropertyValueByPath(map, path) match {
|
||||
case Some(configValue: String) => configValue
|
||||
case _ =>
|
||||
logger.debug(s"Config `$path` with type String not found in clientSettings.")
|
||||
alternativeValue
|
||||
}
|
||||
}
|
||||
|
||||
def getConfigPropertyValueByPathAsBooleanOrElse(map: Map[String, Any], path: String, alternativeValue: Boolean): Boolean = {
|
||||
getConfigPropertyValueByPath(map, path) match {
|
||||
case Some(configValue: Boolean) => configValue
|
||||
case _ =>
|
||||
logger.debug(s"Config `$path` with type Boolean found in clientSettings.")
|
||||
alternativeValue
|
||||
}
|
||||
}
|
||||
|
||||
def getConfigPropertyValueByPath(map: Map[String, Any], path: String): Option[Any] = {
|
||||
val keys = path.split("\\.")
|
||||
|
||||
@ -90,13 +117,37 @@ object ClientSettings extends SystemConfiguration {
|
||||
for {
|
||||
dataChannel <- dataChannels
|
||||
} yield {
|
||||
if (dataChannel.contains("name") && dataChannel.contains("writePermission")) {
|
||||
if (dataChannel.contains("name")) {
|
||||
val channelName = dataChannel("name").toString
|
||||
val writePermission = dataChannel("writePermission")
|
||||
writePermission match {
|
||||
case wPerm: List[String] => pluginDataChannels += (channelName -> DataChannel(channelName, wPerm))
|
||||
case _ => logger.warn(s"Invalid writePermission for channel $channelName in plugin $pluginName")
|
||||
val writePermission = {
|
||||
if (dataChannel.contains("writePermission")) {
|
||||
dataChannel("writePermission") match {
|
||||
case wPerm: List[String] => wPerm
|
||||
case _ => {
|
||||
logger.warn(s"Invalid writePermission for channel $channelName in plugin $pluginName")
|
||||
List()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn(s"Missing config writePermission for channel $channelName in plugin $pluginName")
|
||||
List()
|
||||
}
|
||||
}
|
||||
val deletePermission = {
|
||||
if (dataChannel.contains("deletePermission")) {
|
||||
dataChannel("deletePermission") match {
|
||||
case dPerm: List[String] => dPerm
|
||||
case _ => {
|
||||
logger.warn(s"Invalid deletePermission for channel $channelName in plugin $pluginName")
|
||||
List()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List()
|
||||
}
|
||||
}
|
||||
|
||||
pluginDataChannels += (channelName -> DataChannel(channelName, writePermission, deletePermission))
|
||||
}
|
||||
}
|
||||
case _ => logger.warn(s"Plugin $pluginName has an invalid dataChannels format")
|
||||
@ -112,7 +163,7 @@ object ClientSettings extends SystemConfiguration {
|
||||
pluginsFromConfig
|
||||
}
|
||||
|
||||
case class DataChannel(name: String, writePermission: List[String])
|
||||
case class DataChannel(name: String, writePermission: List[String], deletePermission: List[String])
|
||||
case class Plugin(name: String, url: String, dataChannels: Map[String, DataChannel])
|
||||
|
||||
}
|
||||
|
@ -75,15 +75,16 @@ class BigBlueButtonActor(
|
||||
private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
|
||||
msg.core match {
|
||||
|
||||
case m: CreateMeetingReqMsg => handleCreateMeetingReqMsg(m)
|
||||
case m: RegisterUserReqMsg => handleRegisterUserReqMsg(m)
|
||||
case m: GetAllMeetingsReqMsg => handleGetAllMeetingsReqMsg(m)
|
||||
case m: GetRunningMeetingsReqMsg => handleGetRunningMeetingsReqMsg(m)
|
||||
case m: CheckAlivePingSysMsg => handleCheckAlivePingSysMsg(m)
|
||||
case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
|
||||
case _: UserGraphqlConnectionStablishedSysMsg => //Ignore
|
||||
case _: UserGraphqlConnectionClosedSysMsg => //Ignore
|
||||
case _ => log.warning("Cannot handle " + msg.envelope.name)
|
||||
case m: CreateMeetingReqMsg => handleCreateMeetingReqMsg(m)
|
||||
case m: RegisterUserReqMsg => handleRegisterUserReqMsg(m)
|
||||
case m: GetAllMeetingsReqMsg => handleGetAllMeetingsReqMsg(m)
|
||||
case m: GetRunningMeetingsReqMsg => handleGetRunningMeetingsReqMsg(m)
|
||||
case m: CheckAlivePingSysMsg => handleCheckAlivePingSysMsg(m)
|
||||
case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
|
||||
case _: UserGraphqlConnectionEstablishedSysMsg => //Ignore
|
||||
case _: UserGraphqlConnectionClosedSysMsg => //Ignore
|
||||
case _: CheckGraphqlMiddlewareAlivePongSysMsg => //Ignore
|
||||
case _ => log.warning("Cannot handle " + msg.envelope.name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,9 +190,10 @@ class BigBlueButtonActor(
|
||||
context.stop(m.actorRef)
|
||||
}
|
||||
|
||||
MeetingDAO.delete(msg.meetingId)
|
||||
// MeetingDAO.delete(msg.meetingId)
|
||||
// MeetingDAO.setMeetingEnded(msg.meetingId)
|
||||
// Removing the meeting is enough, all other tables has "ON DELETE CASCADE"
|
||||
// UserDAO.deleteAllFromMeeting(msg.meetingId)
|
||||
// UserDAO.softDeleteAllFromMeeting(msg.meetingId)
|
||||
// MeetingRecordingDAO.updateStopped(msg.meetingId, "")
|
||||
|
||||
//Remove ColorPicker idx of the meeting
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.bigbluebutton.core.api
|
||||
|
||||
import org.bigbluebutton.core.apps.users.UserEstablishedGraphqlConnectionInternalMsgHdlr
|
||||
import org.bigbluebutton.core.domain.{ BreakoutUser, BreakoutVoiceUser }
|
||||
import spray.json.JsObject
|
||||
case class InMessageHeader(name: String)
|
||||
@ -126,6 +127,18 @@ case class SetPresenterInDefaultPodInternalMsg(presenterId: String) extends InMe
|
||||
*/
|
||||
case class CaptureSharedNotesReqInternalMsg(breakoutId: String, filename: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by GraphqlActionsActor to inform MeetingActor that user disconnected
|
||||
* @param userId
|
||||
*/
|
||||
case class UserClosedAllGraphqlConnectionsInternalMsg(userId: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by GraphqlActionsActor to inform MeetingActor that user came back from disconnection
|
||||
* @param userId
|
||||
*/
|
||||
case class UserEstablishedGraphqlConnectionInternalMsg(userId: String) extends InMessage
|
||||
|
||||
// DeskShare
|
||||
case class DeskShareStartedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage
|
||||
case class DeskShareStoppedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage
|
||||
|
@ -95,7 +95,7 @@ object PermissionCheck extends SystemConfiguration {
|
||||
for {
|
||||
regUser <- RegisteredUsers.findWithUserId(userId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, reason, outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, reason, outGW)
|
||||
}
|
||||
} else {
|
||||
// TODO: get this object a context so it can use the akka logging system
|
||||
|
@ -45,6 +45,14 @@ object TimerModel {
|
||||
}
|
||||
|
||||
def setRunning(model: TimerModel, running: Boolean): Unit = {
|
||||
|
||||
//If it is running and will stop, calculate new Accumulated
|
||||
if(getRunning(model) && !running) {
|
||||
val now = System.currentTimeMillis()
|
||||
val accumulated = getAccumulated(model) + Math.abs(now - getStartedAt(model)).toInt
|
||||
this.setAccumulated(model, accumulated)
|
||||
}
|
||||
|
||||
model.running = running
|
||||
}
|
||||
|
||||
|
@ -47,19 +47,31 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
}).toMap
|
||||
|
||||
def addAnnotations(wbId: String, userId: String, annotations: Array[AnnotationVO], isPresenter: Boolean, isModerator: Boolean): Array[AnnotationVO] = {
|
||||
var annotationsAdded = Array[AnnotationVO]()
|
||||
val wb = getWhiteboard(wbId)
|
||||
|
||||
var annotationsAdded = Array[AnnotationVO]()
|
||||
var newAnnotationsMap = wb.annotationsMap
|
||||
|
||||
for (annotation <- annotations) {
|
||||
val oldAnnotation = wb.annotationsMap.get(annotation.id)
|
||||
if (!oldAnnotation.isEmpty) {
|
||||
val hasPermission = isPresenter || isModerator || oldAnnotation.get.userId == userId
|
||||
if (hasPermission) {
|
||||
val newAnnotation = oldAnnotation.get.copy(annotationInfo = deepMerge(oldAnnotation.get.annotationInfo, annotation.annotationInfo))
|
||||
// Merge old and new annotation properties
|
||||
val mergedAnnotationInfo = deepMerge(oldAnnotation.get.annotationInfo, annotation.annotationInfo)
|
||||
|
||||
// Apply cleaning if it's an arrow annotation
|
||||
val finalAnnotationInfo = if (annotation.annotationInfo.get("type").contains("arrow")) {
|
||||
cleanArrowAnnotationProps(mergedAnnotationInfo)
|
||||
} else {
|
||||
mergedAnnotationInfo
|
||||
}
|
||||
|
||||
val newAnnotation = oldAnnotation.get.copy(annotationInfo = finalAnnotationInfo)
|
||||
newAnnotationsMap += (annotation.id -> newAnnotation)
|
||||
annotationsAdded :+= annotation
|
||||
PresAnnotationDAO.insertOrUpdate(newAnnotation, annotation)
|
||||
println(s"Updated annotation onpage [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||
annotationsAdded :+= newAnnotation
|
||||
PresAnnotationDAO.insertOrUpdate(newAnnotation, newAnnotation)
|
||||
println(s"Updated annotation on page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||
} else {
|
||||
println(s"User $userId doesn't have permission to edit annotation ${annotation.id}, ignoring...")
|
||||
}
|
||||
@ -69,40 +81,67 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
PresAnnotationDAO.insertOrUpdate(annotation, annotation)
|
||||
println(s"Adding annotation to page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||
} else {
|
||||
println(s"New annotation [${annotation.id}] with no type, ignoring (probably received a remove message before and now the shape is incomplete, ignoring...")
|
||||
println(s"New annotation [${annotation.id}] with no type, ignoring...")
|
||||
}
|
||||
}
|
||||
|
||||
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
|
||||
saveWhiteboard(newWb)
|
||||
annotationsAdded
|
||||
}
|
||||
|
||||
private def cleanArrowAnnotationProps(annotationInfo: Map[String, _]): Map[String, _] = {
|
||||
annotationInfo.get("props") match {
|
||||
case Some(props: Map[String, _]) =>
|
||||
val cleanedProps = props.map {
|
||||
case ("end", endProps: Map[String, _]) => "end" -> cleanEndOrStartProps(endProps)
|
||||
case ("start", startProps: Map[String, _]) => "start" -> cleanEndOrStartProps(startProps)
|
||||
case other => other
|
||||
}
|
||||
annotationInfo + ("props" -> cleanedProps)
|
||||
case _ => annotationInfo
|
||||
}
|
||||
}
|
||||
|
||||
private def cleanEndOrStartProps(props: Map[String, _]): Map[String, _] = {
|
||||
props.get("type") match {
|
||||
case Some("binding") => props - ("x", "y") // Remove 'x' and 'y' for 'binding' type
|
||||
case Some("point") => props - ("boundShapeId", "normalizedAnchor", "isExact") // Remove unwanted properties for 'point' type
|
||||
case _ => props
|
||||
}
|
||||
}
|
||||
|
||||
def getHistory(wbId: String): Array[AnnotationVO] = {
|
||||
val wb = getWhiteboard(wbId)
|
||||
wb.annotationsMap.values.toArray
|
||||
}
|
||||
|
||||
def deleteAnnotations(wbId: String, userId: String, annotationsIds: Array[String], isPresenter: Boolean, isModerator: Boolean): Array[String] = {
|
||||
var annotationsIdsRemoved = Array[String]()
|
||||
val wb = getWhiteboard(wbId)
|
||||
|
||||
var annotationsIdsRemoved = Array[String]()
|
||||
var newAnnotationsMap = wb.annotationsMap
|
||||
|
||||
for (annotationId <- annotationsIds) {
|
||||
val annotation = wb.annotationsMap.get(annotationId)
|
||||
|
||||
if (!annotation.isEmpty) {
|
||||
if (annotation.isDefined) {
|
||||
val hasPermission = isPresenter || isModerator || annotation.get.userId == userId
|
||||
if (hasPermission) {
|
||||
newAnnotationsMap -= annotationId
|
||||
println("Removing annotation on page [" + wb.id + "]. After numAnnotations=[" + newAnnotationsMap.size + "].")
|
||||
println(s"Removed annotation $annotationId on page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||
annotationsIdsRemoved :+= annotationId
|
||||
} else {
|
||||
println("User doesn't have permission to remove this annotation, ignoring...")
|
||||
println(s"User $userId doesn't have permission to remove annotation $annotationId, ignoring...")
|
||||
}
|
||||
} else {
|
||||
println(s"Annotation $annotationId not found while trying to delete it.")
|
||||
}
|
||||
}
|
||||
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
|
||||
saveWhiteboard(newWb)
|
||||
|
||||
// Update whiteboard and save
|
||||
val updatedWb = wb.copy(annotationsMap = newAnnotationsMap)
|
||||
saveWhiteboard(updatedWb)
|
||||
|
||||
annotationsIdsRemoved.map(PresAnnotationDAO.delete(wbId, userId, _))
|
||||
|
||||
@ -130,4 +169,4 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
}
|
||||
|
||||
def getChangedModeOn(wbId: String): Long = getWhiteboard(wbId).changedModeOn
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.ClientSettings.{getConfigPropertyValueByPath, getConfigPropertyValueByPathAsIntOrElse}
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{BreakoutModel, PermissionCheck, RightsManagementTrait}
|
||||
import org.bigbluebutton.core.db.BreakoutRoomDAO
|
||||
@ -16,6 +17,10 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
def handleCreateBreakoutRoomsCmdMsg(msg: CreateBreakoutRoomsCmdMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
|
||||
val minOfRooms = 2
|
||||
val maxOfRooms = getConfigPropertyValueByPathAsIntOrElse(liveMeeting.clientSettings, "public.app.breakouts.breakoutRoomLimit", 16)
|
||||
|
||||
if (liveMeeting.props.meetingProp.disabledFeatures.contains("breakoutRooms")) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "Breakout rooms is disabled for this meeting."
|
||||
@ -27,6 +32,15 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId,
|
||||
reason, outGW, liveMeeting)
|
||||
state
|
||||
} else if(msg.body.rooms.length > maxOfRooms || msg.body.rooms.length < minOfRooms) {
|
||||
log.warning(
|
||||
"Attempt to create breakout rooms with invalid number of rooms (rooms: {}, max: {}, min: {}) in meeting {}",
|
||||
msg.body.rooms.size,
|
||||
maxOfRooms,
|
||||
minOfRooms,
|
||||
liveMeeting.props.meetingProp.intId
|
||||
)
|
||||
state
|
||||
} else {
|
||||
state.breakout match {
|
||||
case Some(breakout) =>
|
||||
@ -54,8 +68,8 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
val voiceConf = BreakoutRoomsUtil.createVoiceConfId(liveMeeting.props.voiceProp.voiceConf, i)
|
||||
|
||||
val breakout = BreakoutModel.create(parentId, internalId, externalId, room.name, room.sequence, room.shortName,
|
||||
room.isDefaultName, room.freeJoin, voiceConf, room.users, msg.body.captureNotes,
|
||||
msg.body.captureSlides, room.captureNotesFilename, room.captureSlidesFilename)
|
||||
room.isDefaultName, room.freeJoin, voiceConf, room.users, msg.body.captureNotes,
|
||||
msg.body.captureSlides, room.captureNotesFilename, room.captureSlidesFilename)
|
||||
|
||||
rooms = rooms + (breakout.id -> breakout)
|
||||
}
|
||||
|
@ -30,13 +30,13 @@ trait EjectUserFromBreakoutInternalMsgHdlr {
|
||||
)
|
||||
|
||||
//TODO inform reason
|
||||
UserDAO.delete(registeredUser.id)
|
||||
UserDAO.softDelete(registeredUser.id)
|
||||
|
||||
// send a system message to force disconnection
|
||||
Sender.sendDisconnectClientSysMsg(msg.breakoutId, registeredUser.id, msg.ejectedBy, msg.reasonCode, outGW)
|
||||
|
||||
// Force reconnection with graphql to refresh permissions
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, registeredUser.id, registeredUser.sessionToken, msg.reasonCode, outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, registeredUser.id, registeredUser.sessionToken, msg.reasonCode, outGW)
|
||||
|
||||
//send users update to parent meeting
|
||||
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
|
||||
|
@ -18,8 +18,8 @@ trait SendMessageToBreakoutRoomInternalMsgHdlr {
|
||||
sender <- GroupChatApp.findGroupChatUser(SystemUser.ID, liveMeeting.users2x)
|
||||
chat <- state.groupChats.find(GroupChatApp.MAIN_PUBLIC_CHAT)
|
||||
} yield {
|
||||
val groupChatMsgFromUser = GroupChatMsgFromUser(sender.id, sender.copy(name = msg.senderName), true, msg.msg)
|
||||
val gcm = GroupChatApp.toGroupChatMessage(sender.copy(name = msg.senderName), groupChatMsgFromUser)
|
||||
val groupChatMsgFromUser = GroupChatMsgFromUser(sender.id, sender.copy(name = msg.senderName), msg.msg)
|
||||
val gcm = GroupChatApp.toGroupChatMessage(sender.copy(name = msg.senderName), groupChatMsgFromUser, emphasizedText = true)
|
||||
val gcs = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm, GroupChatMessageType.BREAKOUTROOM_MOD_MSG)
|
||||
|
||||
val event = buildGroupChatMessageBroadcastEvtMsg(
|
||||
|
@ -59,7 +59,7 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
|
||||
val newState = for {
|
||||
createdBy <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
|
||||
} yield {
|
||||
val msgs = msg.body.msg.map(m => GroupChatApp.toGroupChatMessage(createdBy, m))
|
||||
val msgs = msg.body.msg.map(m => GroupChatApp.toGroupChatMessage(createdBy, m, emphasizedText = false))
|
||||
val users = {
|
||||
if (msg.body.access == GroupChatAccess.PRIVATE) {
|
||||
val cu = msg.body.users.toSet + msg.header.userId
|
||||
|
@ -20,10 +20,10 @@ object GroupChatApp {
|
||||
GroupChatFactory.create(gcId, access, createBy, users, msgs)
|
||||
}
|
||||
|
||||
def toGroupChatMessage(sender: GroupChatUser, msg: GroupChatMsgFromUser): GroupChatMessage = {
|
||||
def toGroupChatMessage(sender: GroupChatUser, msg: GroupChatMsgFromUser, emphasizedText: Boolean): GroupChatMessage = {
|
||||
val now = System.currentTimeMillis()
|
||||
val id = GroupChatFactory.genId()
|
||||
GroupChatMessage(id, now, msg.correlationId, now, now, sender, msg.chatEmphasizedText, msg.message)
|
||||
GroupChatMessage(id, now, msg.correlationId, now, now, sender, emphasizedText, msg.message)
|
||||
}
|
||||
|
||||
def toMessageToUser(msg: GroupChatMessage): GroupChatMsgToUser = {
|
||||
@ -80,8 +80,8 @@ object GroupChatApp {
|
||||
sender <- GroupChatApp.findGroupChatUser(userId, liveMeeting.users2x)
|
||||
chat <- state.groupChats.find(chatId)
|
||||
} yield {
|
||||
|
||||
val gcm1 = GroupChatApp.toGroupChatMessage(sender, msg)
|
||||
val emphasizedText = sender.role == Roles.MODERATOR_ROLE
|
||||
val gcm1 = GroupChatApp.toGroupChatMessage(sender, msg, emphasizedText)
|
||||
val gcs1 = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm1)
|
||||
state.update(gcs1)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
import org.bigbluebutton.ClientSettings.{ getConfigPropertyValueByPath, getConfigPropertyValueByPathAsBooleanOrElse }
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.PermissionCheck
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
@ -48,7 +49,17 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
|
||||
val userIsAParticipant = chat.users.filter(u => u.id == sender.id).length > 0;
|
||||
|
||||
if ((chatIsPrivate && userIsAParticipant) || !chatIsPrivate) {
|
||||
val gcm = GroupChatApp.toGroupChatMessage(sender, msg.body.msg)
|
||||
val moderatorChatEmphasizedEnabled = getConfigPropertyValueByPathAsBooleanOrElse(
|
||||
liveMeeting.clientSettings,
|
||||
"public.chat.moderatorChatEmphasized",
|
||||
alternativeValue = true
|
||||
)
|
||||
|
||||
val emphasizedText = moderatorChatEmphasizedEnabled &&
|
||||
!chatIsPrivate &&
|
||||
sender.role == Roles.MODERATOR_ROLE
|
||||
|
||||
val gcm = GroupChatApp.toGroupChatMessage(sender, msg.body.msg, emphasizedText)
|
||||
val gcs = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm)
|
||||
|
||||
val event = buildGroupChatMessageBroadcastEvtMsg(
|
||||
|
@ -0,0 +1,60 @@
|
||||
package org.bigbluebutton.core.apps.plugin
|
||||
|
||||
import org.bigbluebutton.ClientSettings
|
||||
import org.bigbluebutton.common2.msgs.PluginDataChannelDeleteMessageMsg
|
||||
import org.bigbluebutton.core.db.PluginDataChannelMessageDAO
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ Roles, Users2x }
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
|
||||
|
||||
trait PluginDataChannelDeleteMessageMsgHdlr extends HandlerHelpers {
|
||||
|
||||
def handle(msg: PluginDataChannelDeleteMessageMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
|
||||
val pluginsDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("plugins")
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
|
||||
for {
|
||||
_ <- if (!pluginsDisabled) Some(()) else None
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
|
||||
} yield {
|
||||
val pluginsConfig = ClientSettings.getPluginsFromConfig(ClientSettings.clientSettingsFromFile)
|
||||
|
||||
if (!pluginsConfig.contains(msg.body.pluginName)) {
|
||||
println(s"Plugin '${msg.body.pluginName}' not found.")
|
||||
} else if (!pluginsConfig(msg.body.pluginName).dataChannels.contains(msg.body.dataChannel)) {
|
||||
println(s"Data channel '${msg.body.dataChannel}' not found in plugin '${msg.body.pluginName}'.")
|
||||
} else {
|
||||
val hasPermission = for {
|
||||
deletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.dataChannel).deletePermission
|
||||
} yield {
|
||||
deletePermission.toLowerCase match {
|
||||
case "all" => true
|
||||
case "moderator" => user.role == Roles.MODERATOR_ROLE
|
||||
case "presenter" => user.presenter
|
||||
case "sender" => {
|
||||
val senderUserId = PluginDataChannelMessageDAO.getMessageSender(
|
||||
meetingId,
|
||||
msg.body.pluginName,
|
||||
msg.body.dataChannel,
|
||||
msg.body.messageId
|
||||
)
|
||||
senderUserId == msg.header.userId
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission.contains(true)) {
|
||||
println(s"No permission to delete in plugin: '${msg.body.pluginName}', data channel: '${msg.body.dataChannel}'.")
|
||||
} else {
|
||||
PluginDataChannelMessageDAO.delete(
|
||||
meetingId,
|
||||
msg.body.pluginName,
|
||||
msg.body.dataChannel,
|
||||
msg.body.messageId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,9 +7,9 @@ import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ Roles, Users2x }
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
|
||||
|
||||
trait DispatchPluginDataChannelMessageMsgHdlr extends HandlerHelpers {
|
||||
trait PluginDataChannelDispatchMessageMsgHdlr extends HandlerHelpers {
|
||||
|
||||
def handle(msg: DispatchPluginDataChannelMessageMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
|
||||
def handle(msg: PluginDataChannelDispatchMessageMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
|
||||
val pluginsDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("plugins")
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
|
@ -0,0 +1,50 @@
|
||||
package org.bigbluebutton.core.apps.plugin
|
||||
|
||||
import org.bigbluebutton.ClientSettings
|
||||
import org.bigbluebutton.common2.msgs.PluginDataChannelResetMsg
|
||||
import org.bigbluebutton.core.db.PluginDataChannelMessageDAO
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ Roles, Users2x }
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
|
||||
|
||||
trait PluginDataChannelResetMsgHdlr extends HandlerHelpers {
|
||||
|
||||
def handle(msg: PluginDataChannelResetMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
|
||||
val pluginsDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("plugins")
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
|
||||
for {
|
||||
_ <- if (!pluginsDisabled) Some(()) else None
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
|
||||
} yield {
|
||||
val pluginsConfig = ClientSettings.getPluginsFromConfig(ClientSettings.clientSettingsFromFile)
|
||||
|
||||
if (!pluginsConfig.contains(msg.body.pluginName)) {
|
||||
println(s"Plugin '${msg.body.pluginName}' not found.")
|
||||
} else if (!pluginsConfig(msg.body.pluginName).dataChannels.contains(msg.body.dataChannel)) {
|
||||
println(s"Data channel '${msg.body.dataChannel}' not found in plugin '${msg.body.pluginName}'.")
|
||||
} else {
|
||||
val hasPermission = for {
|
||||
deletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.dataChannel).deletePermission
|
||||
} yield {
|
||||
deletePermission.toLowerCase match {
|
||||
case "all" => true
|
||||
case "moderator" => user.role == Roles.MODERATOR_ROLE
|
||||
case "presenter" => user.presenter
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission.contains(true)) {
|
||||
println(s"No permission to delete (reset) in plugin: '${msg.body.pluginName}', data channel: '${msg.body.dataChannel}'.")
|
||||
} else {
|
||||
PluginDataChannelMessageDAO.reset(
|
||||
meetingId,
|
||||
msg.body.pluginName,
|
||||
msg.body.dataChannel
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,9 @@ import org.apache.pekko.actor.ActorContext
|
||||
import org.apache.pekko.event.Logging
|
||||
|
||||
class PluginHdlrs(implicit val context: ActorContext)
|
||||
extends DispatchPluginDataChannelMessageMsgHdlr {
|
||||
extends PluginDataChannelDispatchMessageMsgHdlr
|
||||
with PluginDataChannelDeleteMessageMsgHdlr
|
||||
with PluginDataChannelResetMsgHdlr {
|
||||
|
||||
val log = Logging(context.system, getClass)
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
package org.bigbluebutton.core.apps.polls
|
||||
|
||||
import org.bigbluebutton.common2.domain.SimplePollResultOutVO
|
||||
import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, MessageTypes, PollUpdatedEvtMsg, PollUpdatedEvtMsgBody, Routing, UserRespondedToPollRecordMsg, UserRespondedToPollRecordMsgBody, UserRespondedToPollRespMsg, UserRespondedToPollRespMsgBody, UserRespondedToTypedPollRespMsg, UserRespondedToTypedPollRespMsgBody }
|
||||
import org.bigbluebutton.core.running.OutMsgRouter
|
||||
|
||||
object PollHdlrHelpers {
|
||||
|
||||
def broadcastPollUpdatedEvent(outGW: OutMsgRouter, meetingId: String, userId: String, pollId: String, poll: SimplePollResultOutVO): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(PollUpdatedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(PollUpdatedEvtMsg.NAME, meetingId, userId)
|
||||
|
||||
val body = PollUpdatedEvtMsgBody(pollId, poll)
|
||||
val event = PollUpdatedEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
def broadcastUserRespondedToTypedPollRespMsg(outGW: OutMsgRouter, meetingId: String, userId: String,
|
||||
pollId: String, answer: String, sendToId: String): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, sendToId)
|
||||
val envelope = BbbCoreEnvelope(UserRespondedToTypedPollRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserRespondedToTypedPollRespMsg.NAME, meetingId, sendToId)
|
||||
|
||||
val body = UserRespondedToTypedPollRespMsgBody(pollId, userId, answer)
|
||||
val event = UserRespondedToTypedPollRespMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
def broadcastUserRespondedToPollRecordMsg(outGW: OutMsgRouter, meetingId: String, userId: String,
|
||||
pollId: String, answerId: Int, answer: String, isSecret: Boolean): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(UserRespondedToPollRecordMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserRespondedToPollRecordMsg.NAME, meetingId, userId)
|
||||
|
||||
val body = UserRespondedToPollRecordMsgBody(pollId, answerId, answer, isSecret)
|
||||
val event = UserRespondedToPollRecordMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
def broadcastUserRespondedToPollRespMsg(outGW: OutMsgRouter, meetingId: String, userId: String,
|
||||
pollId: String, answerIds: Seq[Int], sendToId: String): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, sendToId)
|
||||
val envelope = BbbCoreEnvelope(UserRespondedToPollRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserRespondedToPollRespMsg.NAME, meetingId, sendToId)
|
||||
|
||||
val body = UserRespondedToPollRespMsgBody(pollId, userId, answerIds)
|
||||
val event = UserRespondedToPollRespMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,7 @@ import org.bigbluebutton.common2.domain.SimplePollResultOutVO
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.models.Polls
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
|
||||
trait RespondToPollReqMsgHdlr {
|
||||
@ -12,45 +12,12 @@ trait RespondToPollReqMsgHdlr {
|
||||
|
||||
def handle(msg: RespondToPollReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
|
||||
def broadcastPollUpdatedEvent(msg: RespondToPollReqMsg, pollId: String, poll: SimplePollResultOutVO): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
val envelope = BbbCoreEnvelope(PollUpdatedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(PollUpdatedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = PollUpdatedEvtMsgBody(pollId, poll)
|
||||
val event = PollUpdatedEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
def broadcastUserRespondedToPollRecordMsg(msg: RespondToPollReqMsg, pollId: String, answerId: Int, answer: String, isSecret: Boolean): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
val envelope = BbbCoreEnvelope(UserRespondedToPollRecordMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserRespondedToPollRecordMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = UserRespondedToPollRecordMsgBody(pollId, answerId, answer, isSecret)
|
||||
val event = UserRespondedToPollRecordMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
def broadcastUserRespondedToPollRespMsg(msg: RespondToPollReqMsg, pollId: String, answerIds: Seq[Int], sendToId: String): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, sendToId)
|
||||
val envelope = BbbCoreEnvelope(UserRespondedToPollRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserRespondedToPollRespMsg.NAME, liveMeeting.props.meetingProp.intId, sendToId)
|
||||
|
||||
val body = UserRespondedToPollRespMsgBody(pollId, msg.header.userId, answerIds)
|
||||
val event = UserRespondedToPollRespMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (Polls.checkUserResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false) {
|
||||
if (!Polls.hasUserAlreadyResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls)) {
|
||||
for {
|
||||
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToPollReqMsg(msg.header.userId, msg.body.pollId,
|
||||
msg.body.questionId, msg.body.answerIds, liveMeeting)
|
||||
} yield {
|
||||
broadcastPollUpdatedEvent(msg, pollId, updatedPoll)
|
||||
PollHdlrHelpers.broadcastPollUpdatedEvent(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, updatedPoll)
|
||||
for {
|
||||
poll <- Polls.getPoll(pollId, liveMeeting.polls)
|
||||
} yield {
|
||||
@ -58,14 +25,14 @@ trait RespondToPollReqMsgHdlr {
|
||||
answerId <- msg.body.answerIds
|
||||
} yield {
|
||||
val answerText = poll.questions(0).answers.get(answerId).key
|
||||
broadcastUserRespondedToPollRecordMsg(msg, pollId, answerId, answerText, poll.isSecret)
|
||||
PollHdlrHelpers.broadcastUserRespondedToPollRecordMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, answerId, answerText, poll.isSecret)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
presenter <- Users2x.findPresenter(liveMeeting.users2x)
|
||||
} yield {
|
||||
broadcastUserRespondedToPollRespMsg(msg, pollId, msg.body.answerIds, presenter.intId)
|
||||
PollHdlrHelpers.broadcastUserRespondedToPollRespMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, msg.body.answerIds, presenter.intId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1,10 +1,11 @@
|
||||
package org.bigbluebutton.core.apps.polls
|
||||
|
||||
import org.bigbluebutton.ClientSettings.getConfigPropertyValueByPathAsIntOrElse
|
||||
import org.bigbluebutton.common2.domain.SimplePollResultOutVO
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.models.Polls
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
|
||||
trait RespondToTypedPollReqMsgHdlr {
|
||||
@ -12,43 +13,60 @@ trait RespondToTypedPollReqMsgHdlr {
|
||||
|
||||
def handle(msg: RespondToTypedPollReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
|
||||
def broadcastPollUpdatedEvent(msg: RespondToTypedPollReqMsg, pollId: String, poll: SimplePollResultOutVO): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
val envelope = BbbCoreEnvelope(PollUpdatedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(PollUpdatedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = PollUpdatedEvtMsgBody(pollId, poll)
|
||||
val event = PollUpdatedEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
def broadcastUserRespondedToTypedPollRespMsg(msg: RespondToTypedPollReqMsg, pollId: String, answer: String, sendToId: String): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, sendToId)
|
||||
val envelope = BbbCoreEnvelope(UserRespondedToTypedPollRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserRespondedToTypedPollRespMsg.NAME, liveMeeting.props.meetingProp.intId, sendToId)
|
||||
|
||||
val body = UserRespondedToTypedPollRespMsgBody(pollId, msg.header.userId, answer)
|
||||
val event = UserRespondedToTypedPollRespMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (Polls.isResponsePollType(msg.body.pollId, liveMeeting.polls) &&
|
||||
Polls.checkUserResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false &&
|
||||
Polls.checkUserAddedQuestion(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false) {
|
||||
for {
|
||||
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToTypedPollReqMsg(msg.header.userId, msg.body.pollId,
|
||||
msg.body.questionId, msg.body.answer, liveMeeting)
|
||||
} yield {
|
||||
broadcastPollUpdatedEvent(msg, pollId, updatedPoll)
|
||||
!Polls.hasUserAlreadyResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls) &&
|
||||
!Polls.hasUserAlreadyAddedTypedAnswer(msg.body.pollId, msg.header.userId, liveMeeting.polls)) {
|
||||
|
||||
for {
|
||||
presenter <- Users2x.findPresenter(liveMeeting.users2x)
|
||||
} yield {
|
||||
broadcastUserRespondedToTypedPollRespMsg(msg, pollId, msg.body.answer, presenter.intId)
|
||||
//Truncate answer case it is longer than `maxTypedAnswerLength`
|
||||
val maxTypedAnswerLength = getConfigPropertyValueByPathAsIntOrElse(liveMeeting.clientSettings, "public.poll.maxTypedAnswerLength", 45)
|
||||
val answer = msg.body.answer.substring(0, Math.min(msg.body.answer.length, maxTypedAnswerLength))
|
||||
|
||||
val answerExists = Polls.findAnswerWithText(msg.body.pollId, msg.body.questionId, answer, liveMeeting.polls)
|
||||
|
||||
//Create answer if it doesn't exist
|
||||
answerExists match {
|
||||
case None => {
|
||||
for {
|
||||
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToTypedPollReqMsg(msg.header.userId, msg.body.pollId,
|
||||
msg.body.questionId, answer, liveMeeting)
|
||||
} yield {
|
||||
PollHdlrHelpers.broadcastPollUpdatedEvent(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, updatedPoll)
|
||||
|
||||
for {
|
||||
presenter <- Users2x.findPresenter(liveMeeting.users2x)
|
||||
} yield {
|
||||
PollHdlrHelpers.broadcastUserRespondedToTypedPollRespMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, answer, presenter.intId)
|
||||
}
|
||||
}
|
||||
}
|
||||
case _ => //Do nothing, answer with same text exists already
|
||||
}
|
||||
|
||||
//Submit the answer
|
||||
Polls.findAnswerWithText(msg.body.pollId, msg.body.questionId, answer, liveMeeting.polls) match {
|
||||
case Some(answerId) => {
|
||||
for {
|
||||
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToPollReqMsg(msg.header.userId, msg.body.pollId,
|
||||
msg.body.questionId, Seq(answerId), liveMeeting)
|
||||
} yield {
|
||||
PollHdlrHelpers.broadcastPollUpdatedEvent(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, updatedPoll)
|
||||
for {
|
||||
poll <- Polls.getPoll(pollId, liveMeeting.polls)
|
||||
} yield {
|
||||
val answerText = poll.questions(0).answers.get(answerId).key
|
||||
PollHdlrHelpers.broadcastUserRespondedToPollRecordMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, answerId, answerText, poll.isSecret)
|
||||
}
|
||||
|
||||
for {
|
||||
presenter <- Users2x.findPresenter(liveMeeting.users2x)
|
||||
} yield {
|
||||
PollHdlrHelpers.broadcastUserRespondedToPollRespMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, Seq(answerId), presenter.intId)
|
||||
}
|
||||
}
|
||||
}
|
||||
case None => log.error("Error while trying to answer the poll {} in meeting {}: Answer not found or something went wrong while trying to create the answer.", msg.body.pollId, msg.header.meetingId)
|
||||
}
|
||||
|
||||
} else {
|
||||
log.info("Ignoring typed answer from user {} once user already added an answer to this poll {} in meeting {}", msg.header.userId, msg.body.pollId, msg.header.meetingId)
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
def buildNewPresFileAvailable(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String,
|
||||
presId: String, fileStateType: String): NewPresFileAvailableMsg = {
|
||||
val header = BbbClientMsgHeader(NewPresFileAvailableMsg.NAME, "not-used", "not-used")
|
||||
val body = NewPresFileAvailableMsgBody(annotatedFileURI, originalFileURI, convertedFileURI, presId, fileStateType)
|
||||
val body = NewPresFileAvailableMsgBody(annotatedFileURI, originalFileURI, convertedFileURI, presId, fileStateType, "")
|
||||
|
||||
NewPresFileAvailableMsg(header, body)
|
||||
}
|
||||
@ -160,7 +160,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
val pages: List[Int] = m.body.pages // Desired presentation pages for export
|
||||
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else pages
|
||||
|
||||
val exportJob: ExportJob = new ExportJob(jobId, JobTypes.DOWNLOAD, "annotated_slides", presId, presLocation, allPages, pagesRange, meetingId, "");
|
||||
val exportJob: ExportJob = new ExportJob(jobId, JobTypes.DOWNLOAD, currentPres.get.name, "annotated_slides", presId, presLocation, allPages, pagesRange, meetingId, "");
|
||||
val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
|
||||
|
||||
val isPresentationOriginalOrConverted = m.body.fileStateType == "Original" || m.body.fileStateType == "Converted"
|
||||
@ -226,7 +226,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
val currentPage: PresentationPage = PresentationInPod.getCurrentPage(currentPres.get).get
|
||||
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else List(currentPage.num)
|
||||
|
||||
val exportJob: ExportJob = ExportJob(jobId, JobTypes.CAPTURE_PRESENTATION, filename, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken)
|
||||
val exportJob: ExportJob = ExportJob(jobId, JobTypes.CAPTURE_PRESENTATION, filename, filename, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken)
|
||||
val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
|
||||
|
||||
val annotationCount: Int = storeAnnotationPages.map(_.annotations.size).sum
|
||||
@ -252,11 +252,10 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
liveMeeting.props.meetingProp.intId, m.body.presId
|
||||
)
|
||||
|
||||
//TODO let frontend choose the name in favor of internationalization
|
||||
if (m.body.fileStateType == "Annotated") {
|
||||
val presentationDownloadInfo = Map(
|
||||
"fileURI" -> m.body.annotatedFileURI,
|
||||
"filename" -> "annotated_slides.pdf"
|
||||
"filename" -> m.body.fileName
|
||||
)
|
||||
ChatMessageDAO.insertSystemMsg(liveMeeting.props.meetingProp.intId, GroupChatApp.MAIN_PUBLIC_CHAT, "", GroupChatMessageType.PRESENTATION, presentationDownloadInfo, "")
|
||||
} else if (m.body.fileStateType == "Converted") {
|
||||
@ -295,7 +294,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(m.body.parentMeetingId, userId, presentationUploadToken, filename, presentationId))
|
||||
|
||||
val exportJob = new ExportJob(jobId, JobTypes.CAPTURE_NOTES, filename, m.body.padId, "", true, List(), m.body.parentMeetingId, presentationUploadToken)
|
||||
val exportJob = new ExportJob(jobId, JobTypes.CAPTURE_NOTES, filename, filename, m.body.padId, "", true, List(), m.body.parentMeetingId, presentationUploadToken)
|
||||
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
|
||||
|
||||
bus.outGW.send(job)
|
||||
|
@ -30,7 +30,10 @@ trait DeactivateTimerReqMsgHdlr extends RightsManagementTrait {
|
||||
val reason = "You need to be the presenter or moderator to deactivate timer"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
TimerModel.setIsActive(liveMeeting.timerModel, false)
|
||||
TimerModel.setRunning(liveMeeting.timerModel, running = false)
|
||||
TimerModel.setIsActive(liveMeeting.timerModel, active = false)
|
||||
TimerModel.setStopwatch(liveMeeting.timerModel, stopwatch = true)
|
||||
TimerModel.reset(liveMeeting.timerModel)
|
||||
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
|
||||
broadcastEvent()
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ trait StartTimerReqMsgHdlr extends RightsManagementTrait {
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
TimerModel.setStartedAt(liveMeeting.timerModel, System.currentTimeMillis())
|
||||
TimerModel.setRunning(liveMeeting.timerModel, true)
|
||||
TimerModel.setRunning(liveMeeting.timerModel, running = true)
|
||||
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
|
||||
broadcastEvent()
|
||||
}
|
||||
|
@ -33,10 +33,9 @@ trait StopTimerReqMsgHdlr extends RightsManagementTrait {
|
||||
val reason = "You need to be the presenter or moderator to stop timer"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
TimerModel.setAccumulated(liveMeeting.timerModel, msg.body.accumulated)
|
||||
TimerModel.setRunning(liveMeeting.timerModel, false)
|
||||
TimerModel.setRunning(liveMeeting.timerModel, running = false)
|
||||
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
|
||||
broadcastEvent(msg.body.accumulated)
|
||||
broadcastEvent(TimerModel.getAccumulated(liveMeeting.timerModel))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ trait SwitchTimerReqMsgHdlr extends RightsManagementTrait {
|
||||
} else {
|
||||
if (TimerModel.getStopwatch(liveMeeting.timerModel) != msg.body.stopwatch) {
|
||||
TimerModel.setStopwatch(liveMeeting.timerModel, msg.body.stopwatch)
|
||||
TimerModel.setRunning(liveMeeting.timerModel, running = false)
|
||||
TimerModel.reset(liveMeeting.timerModel) //Reset on switch Stopwatch/Timer
|
||||
if (msg.body.stopwatch) {
|
||||
TimerModel.setTrack(liveMeeting.timerModel, "noTrack")
|
||||
|
@ -85,7 +85,7 @@ object AssignPresenterActionHandler extends RightsManagementTrait {
|
||||
for {
|
||||
u <- RegisteredUsers.findWithUserId(oldPres.intId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, oldPres.intId, u.sessionToken, "role_changed", outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, oldPres.intId, u.sessionToken, "role_changed", outGW)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,7 +100,7 @@ object AssignPresenterActionHandler extends RightsManagementTrait {
|
||||
for {
|
||||
u <- RegisteredUsers.findWithUserId(newPres.intId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, newPres.intId, u.sessionToken, "role_changed", outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, newPres.intId, u.sessionToken, "role_changed", outGW)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import org.bigbluebutton.core.running.OutMsgRouter
|
||||
import org.bigbluebutton.core.running.MeetingActor
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
import org.bigbluebutton.core2.Permissions
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
|
||||
|
||||
trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
@ -237,6 +237,16 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
)
|
||||
|
||||
outGW.send(BbbCommonEnvCoreMsg(envelope, LockSettingsInMeetingChangedEvtMsg(header, body)))
|
||||
|
||||
//Refresh graphql session for all locked viewers
|
||||
for {
|
||||
user <- Users2x.findAll(liveMeeting.users2x)
|
||||
if user.locked
|
||||
if user.role == Roles.VIEWER_ROLE
|
||||
regUser <- RegisteredUsers.findWithUserId(user.intId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "lockSettings_changed", outGW)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.ClientSettings.{ getConfigPropertyValueByPath, getConfigPropertyValueByPathAsIntOrElse }
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.RightsManagementTrait
|
||||
import org.bigbluebutton.core.models.{ UserState, Users2x }
|
||||
@ -29,9 +30,11 @@ trait ChangeUserReactionEmojiReqMsgHdlr extends RightsManagementTrait {
|
||||
outGW.send(msgEventChange)
|
||||
}
|
||||
|
||||
//Get durationInSeconds from Client config
|
||||
val userReactionExpire = getConfigPropertyValueByPathAsIntOrElse(liveMeeting.clientSettings, "public.userReaction.expire", 30)
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
|
||||
newUserState <- Users2x.setReactionEmoji(liveMeeting.users2x, user.intId, msg.body.reactionEmoji)
|
||||
newUserState <- Users2x.setReactionEmoji(liveMeeting.users2x, user.intId, msg.body.reactionEmoji, userReactionExpire)
|
||||
} yield {
|
||||
if (user.reactionEmoji != msg.body.reactionEmoji) {
|
||||
broadcast(newUserState, msg.body.reactionEmoji)
|
||||
|
@ -73,7 +73,7 @@ trait ChangeUserRoleCmdMsgHdlr extends RightsManagementTrait {
|
||||
for {
|
||||
u <- RegisteredUsers.findWithUserId(uvo.intId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, uvo.intId, u.sessionToken, "role_changed", outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, uvo.intId, u.sessionToken, "role_changed", outGW)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ trait ClearAllUsersReactionCmdMsgHdlr extends RightsManagementTrait {
|
||||
user <- Users2x.findAll(liveMeeting.users2x)
|
||||
} yield {
|
||||
//Don't clear away and RaiseHand
|
||||
Users2x.setReactionEmoji(liveMeeting.users2x, user.intId, "none")
|
||||
Users2x.setReactionEmoji(liveMeeting.users2x, user.intId, "none", 0)
|
||||
}
|
||||
sendClearedAllUsersReactionEvtMsg(outGW, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
} else {
|
||||
|
@ -74,7 +74,7 @@ trait EjectUserFromMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
Sender.sendDisconnectClientSysMsg(meetingId, ru.id, ejectedBy, EjectReasonCode.EJECT_USER, outGW)
|
||||
|
||||
// Force reconnection with graphql to refresh permissions
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, registeredUser.id, registeredUser.sessionToken, EjectReasonCode.EJECT_USER, outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, registeredUser.id, registeredUser.sessionToken, EjectReasonCode.EJECT_USER, outGW)
|
||||
}
|
||||
} else {
|
||||
// User is ejecting self, so just eject this userid not all sessions if joined using multiple
|
||||
@ -93,7 +93,7 @@ trait EjectUserFromMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
Sender.sendDisconnectClientSysMsg(meetingId, userId, ejectedBy, EjectReasonCode.EJECT_USER, outGW)
|
||||
|
||||
// Force reconnection with graphql to refresh permissions
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, registeredUser.id, registeredUser.sessionToken, EjectReasonCode.EJECT_USER, outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, registeredUser.id, registeredUser.sessionToken, EjectReasonCode.EJECT_USER, outGW)
|
||||
}
|
||||
|
||||
}
|
||||
@ -129,7 +129,7 @@ trait EjectUserFromMeetingSysMsgHdlr {
|
||||
for {
|
||||
regUser <- RegisteredUsers.findWithUserId(userId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, EjectReasonCode.SYSTEM_EJECT_USER, outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, EjectReasonCode.SYSTEM_EJECT_USER, outGW)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ trait LockUserInMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
for {
|
||||
u <- RegisteredUsers.findWithUserId(uvo.intId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, uvo.intId, u.sessionToken, "lock_user_changed", outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, uvo.intId, u.sessionToken, "lock_user_changed", outGW)
|
||||
}
|
||||
|
||||
log.info("Lock user. meetingId=" + props.meetingProp.intId + " userId=" + uvo.intId + " locked=" + uvo.locked)
|
||||
|
@ -49,7 +49,7 @@ trait RegisterUserReqMsgHdlr {
|
||||
Sender.sendDisconnectClientSysMsg(meetingId, userToRemove.id, SystemUser.ID, EjectReasonCode.DUPLICATE_USER, outGW)
|
||||
|
||||
// Force reconnection with graphql to refresh permissions
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, userToRemove.id, userToRemove.sessionToken, EjectReasonCode.DUPLICATE_USER, outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, userToRemove.id, userToRemove.sessionToken, EjectReasonCode.DUPLICATE_USER, outGW)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.core.api.UserEstablishedGraphqlConnectionInternalMsg
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
|
||||
|
||||
trait UserEstablishedGraphqlConnectionInternalMsgHdlr extends HandlerHelpers {
|
||||
this: MeetingActor =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleUserEstablishedGraphqlConnectionInternalMsg(msg: UserEstablishedGraphqlConnectionInternalMsg, state: MeetingState2x): MeetingState2x = {
|
||||
log.info("Received user established a graphql connection. user {} meetingId={}", msg.userId, liveMeeting.props.meetingProp.intId)
|
||||
Users2x.findWithIntId(liveMeeting.users2x, msg.userId) match {
|
||||
case Some(reconnectingUser) =>
|
||||
if (reconnectingUser.userLeftFlag.left) {
|
||||
log.info("Resetting flag that user left meeting. user {}", msg.userId)
|
||||
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.userId, leftFlag = false)
|
||||
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.userId)
|
||||
}
|
||||
state
|
||||
case None =>
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with UserJo
|
||||
if (reconnectingUser.userLeftFlag.left) {
|
||||
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
|
||||
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018
|
||||
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false)
|
||||
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, leftFlag = false)
|
||||
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
|
||||
}
|
||||
state
|
||||
|
@ -51,7 +51,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
|
||||
notifyPreviousUsersWithSameExtId(regUser)
|
||||
clearCachedVoiceUser(regUser)
|
||||
clearExpiredUserState(regUser)
|
||||
invalidateUserGraphqlConnection(regUser)
|
||||
ForceUserGraphqlReconnection(regUser)
|
||||
|
||||
newState
|
||||
}
|
||||
@ -71,7 +71,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
|
||||
|
||||
private def resetUserLeftFlag(msg: UserJoinMeetingReqMsg) = {
|
||||
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
|
||||
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false)
|
||||
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, leftFlag = false)
|
||||
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
|
||||
}
|
||||
|
||||
@ -146,7 +146,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
|
||||
private def clearExpiredUserState(regUser: RegisteredUser) =
|
||||
UserStateDAO.updateExpired(regUser.id, false)
|
||||
|
||||
private def invalidateUserGraphqlConnection(regUser: RegisteredUser) =
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "user_joined", outGW)
|
||||
private def ForceUserGraphqlReconnection(regUser: RegisteredUser) =
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "user_joined", outGW)
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.common2.msgs.UserLeaveReqMsg
|
||||
import org.bigbluebutton.core.api.{ UserClosedAllGraphqlConnectionsInternalMsg }
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x }
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.Sender
|
||||
|
||||
trait UserLeaveReqMsgHdlr extends HandlerHelpers {
|
||||
this: MeetingActor =>
|
||||
@ -11,25 +13,36 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleUserLeaveReqMsg(msg: UserLeaveReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||
Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match {
|
||||
handleUserLeaveReq(msg.body.userId, msg.header.meetingId, msg.body.loggedOut, state)
|
||||
}
|
||||
|
||||
def handleUserClosedAllGraphqlConnectionsInternalMsg(msg: UserClosedAllGraphqlConnectionsInternalMsg, state: MeetingState2x): MeetingState2x = {
|
||||
log.info("Received user closed all graphql connections. user {} meetingId={}", msg.userId, liveMeeting.props.meetingProp.intId)
|
||||
|
||||
handleUserLeaveReq(msg.userId, liveMeeting.props.meetingProp.intId, loggedOut = false, state)
|
||||
}
|
||||
|
||||
def handleUserLeaveReq(userId: String, meetingId: String, loggedOut: Boolean, state: MeetingState2x): MeetingState2x = {
|
||||
Users2x.findWithIntId(liveMeeting.users2x, userId) match {
|
||||
case Some(reconnectingUser) =>
|
||||
log.info("Received user left meeting. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
|
||||
log.info("Received user left meeting. user {} meetingId={}", userId, meetingId)
|
||||
if (!reconnectingUser.userLeftFlag.left) {
|
||||
log.info("Setting user left flag. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
|
||||
log.info("Setting user left flag. user {} meetingId={}", userId, meetingId)
|
||||
// Just flag that user has left as the user might be reconnecting.
|
||||
// An audit will remove this user if it hasn't rejoined after a certain period of time.
|
||||
// ralam oct 23, 2018
|
||||
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, true)
|
||||
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, userId, leftFlag = true)
|
||||
|
||||
Users2x.setUserLeftFlag(liveMeeting.users2x, msg.body.userId)
|
||||
Users2x.setUserLeftFlag(liveMeeting.users2x, userId)
|
||||
}
|
||||
if (msg.body.loggedOut) {
|
||||
log.info("Setting user logged out flag. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
|
||||
if (loggedOut) {
|
||||
log.info("Setting user logged out flag. user {} meetingId={}", userId, meetingId)
|
||||
|
||||
for {
|
||||
ru <- RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers)
|
||||
ru <- RegisteredUsers.findWithUserId(userId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
RegisteredUsers.setUserLoggedOutFlag(liveMeeting.registeredUsers, ru)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, ru.id, ru.sessionToken, "user_loggedout", outGW)
|
||||
}
|
||||
}
|
||||
state
|
||||
@ -37,4 +50,5 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ trait UserReactionTimeExpiredCmdMsgHdlr extends RightsManagementTrait {
|
||||
def handleUserReactionTimeExpiredCmdMsg(msg: UserReactionTimeExpiredCmdMsg) {
|
||||
val isNodeUser = msg.header.userId.equals("nodeJSapp")
|
||||
if (isNodeUser) {
|
||||
Users2x.setReactionEmoji(liveMeeting.users2x, msg.body.userId, "none")
|
||||
Users2x.setReactionEmoji(liveMeeting.users2x, msg.body.userId, "none", 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ trait UserLeftVoiceConfEvtMsgHdlr {
|
||||
UsersApp.guestWaitingLeft(liveMeeting, user.intId, outGW)
|
||||
}
|
||||
Users2x.remove(liveMeeting.users2x, user.intId)
|
||||
UserDAO.delete(user.intId)
|
||||
UserDAO.softDelete(user.intId)
|
||||
VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, msg.body.voiceUserId)
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,9 @@ import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.PermissionCheck
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.db.MeetingUsersPoliciesDAO
|
||||
import org.bigbluebutton.core.models.{ RegisteredUsers, Roles, Users2x }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
|
||||
|
||||
trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
|
||||
this: WebcamApp2x =>
|
||||
@ -76,6 +77,16 @@ trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
|
||||
}
|
||||
|
||||
broadcastEvent(meetingId, msg.body.setBy, value)
|
||||
|
||||
//Refresh graphql session for all locked viewers
|
||||
for {
|
||||
user <- Users2x.findAll(liveMeeting.users2x)
|
||||
if user.locked
|
||||
if user.role == Roles.VIEWER_ROLE
|
||||
regUser <- RegisteredUsers.findWithUserId(user.intId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "webcamOnlyForMod_changed", bus.outGW)
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
|
@ -38,7 +38,16 @@ trait DeleteWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
}
|
||||
} else {
|
||||
val deletedAnnotations = deleteWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotationsIds, liveMeeting, isUserAmongPresenters, isUserModerator)
|
||||
|
||||
val annotationsIds = {
|
||||
if (msg.body.annotationsIds.size > 0) {
|
||||
msg.body.annotationsIds
|
||||
} else {
|
||||
getWhiteboardAnnotations(msg.body.whiteboardId, liveMeeting).map(a => a.id)
|
||||
}
|
||||
}
|
||||
|
||||
val deletedAnnotations = deleteWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, annotationsIds, liveMeeting, isUserAmongPresenters, isUserModerator)
|
||||
if (!deletedAnnotations.isEmpty) {
|
||||
broadcastEvent(msg, deletedAnnotations)
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ object BreakoutRoomUserDAO {
|
||||
|
||||
def insertBreakoutRoom(userId: String, room: BreakoutRoom2x, liveMeeting: LiveMeeting) = {
|
||||
for {
|
||||
(redirectToHtml5JoinURL, redirectJoinURL) <- BreakoutHdlrHelpers.getRedirectUrls(liveMeeting, userId, liveMeeting.props.meetingProp.extId, room.sequence.toString)
|
||||
(redirectToHtml5JoinURL, redirectJoinURL) <- BreakoutHdlrHelpers.getRedirectUrls(liveMeeting, userId, room.externalId, room.sequence.toString)
|
||||
} yield {
|
||||
DatabaseConnection.db.run(BreakoutRoomUserDAO.prepareInsert(room.id, userId, redirectToHtml5JoinURL))
|
||||
.onComplete {
|
||||
|
@ -19,8 +19,15 @@ case class MeetingDbModel(
|
||||
presentationUploadExternalDescription: String,
|
||||
presentationUploadExternalUrl: String,
|
||||
learningDashboardAccessToken: String,
|
||||
logoutUrl: String,
|
||||
customLogoUrl: Option[String],
|
||||
bannerText: Option[String],
|
||||
bannerColor: Option[String],
|
||||
createdTime: Long,
|
||||
durationInSeconds: Int
|
||||
durationInSeconds: Int,
|
||||
endedAt: Option[java.sql.Timestamp],
|
||||
endedReasonCode: Option[String],
|
||||
endedBy: Option[String],
|
||||
)
|
||||
|
||||
class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meeting") {
|
||||
@ -36,8 +43,15 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
|
||||
presentationUploadExternalDescription,
|
||||
presentationUploadExternalUrl,
|
||||
learningDashboardAccessToken,
|
||||
logoutUrl,
|
||||
customLogoUrl,
|
||||
bannerText,
|
||||
bannerColor,
|
||||
createdTime,
|
||||
durationInSeconds
|
||||
durationInSeconds,
|
||||
endedAt,
|
||||
endedReasonCode,
|
||||
endedBy
|
||||
) <> (MeetingDbModel.tupled, MeetingDbModel.unapply)
|
||||
val meetingId = column[String]("meetingId", O.PrimaryKey)
|
||||
val extId = column[String]("extId")
|
||||
@ -50,8 +64,15 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
|
||||
val presentationUploadExternalDescription = column[String]("presentationUploadExternalDescription")
|
||||
val presentationUploadExternalUrl = column[String]("presentationUploadExternalUrl")
|
||||
val learningDashboardAccessToken = column[String]("learningDashboardAccessToken")
|
||||
val logoutUrl = column[String]("logoutUrl")
|
||||
val customLogoUrl = column[Option[String]]("customLogoUrl")
|
||||
val bannerText = column[Option[String]]("bannerText")
|
||||
val bannerColor = column[Option[String]]("bannerColor")
|
||||
val createdTime = column[Long]("createdTime")
|
||||
val durationInSeconds = column[Int]("durationInSeconds")
|
||||
val endedAt = column[Option[java.sql.Timestamp]]("endedAt")
|
||||
val endedReasonCode = column[Option[String]]("endedReasonCode")
|
||||
val endedBy = column[Option[String]]("endedBy")
|
||||
}
|
||||
|
||||
object MeetingDAO {
|
||||
@ -70,28 +91,44 @@ object MeetingDAO {
|
||||
presentationUploadExternalDescription = meetingProps.meetingProp.presentationUploadExternalDescription,
|
||||
presentationUploadExternalUrl = meetingProps.meetingProp.presentationUploadExternalUrl,
|
||||
learningDashboardAccessToken = meetingProps.password.learningDashboardAccessToken,
|
||||
logoutUrl = meetingProps.systemProps.logoutUrl,
|
||||
customLogoUrl = meetingProps.systemProps.customLogoURL match {
|
||||
case "" => None
|
||||
case logoUrl => Some(logoUrl)
|
||||
},
|
||||
bannerText = meetingProps.systemProps.bannerText match {
|
||||
case "" => None
|
||||
case bannerText => Some(bannerText)
|
||||
},
|
||||
bannerColor = meetingProps.systemProps.bannerColor match {
|
||||
case "" => None
|
||||
case bannerColor => Some(bannerColor)
|
||||
},
|
||||
createdTime = meetingProps.durationProps.createdTime,
|
||||
durationInSeconds = meetingProps.durationProps.duration * 60
|
||||
durationInSeconds = meetingProps.durationProps.duration * 60,
|
||||
endedAt = None,
|
||||
endedReasonCode = None,
|
||||
endedBy = None
|
||||
)
|
||||
)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => {
|
||||
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Meeting table!")
|
||||
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
|
||||
MeetingUsersPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp)
|
||||
MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps)
|
||||
MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp)
|
||||
MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp)
|
||||
MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp)
|
||||
MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp)
|
||||
MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups)
|
||||
MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps)
|
||||
TimerDAO.insert(meetingProps.meetingProp.intId)
|
||||
LayoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp.meetingLayout)
|
||||
MeetingClientSettingsDAO.insert(meetingProps.meetingProp.intId, JsonUtils.mapToJson(clientSettings))
|
||||
}
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Meeting: $e")
|
||||
case Success(rowsAffected) => {
|
||||
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Meeting table!")
|
||||
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
|
||||
MeetingUsersPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp)
|
||||
MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps)
|
||||
MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp)
|
||||
MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp)
|
||||
MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp)
|
||||
MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp)
|
||||
MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups)
|
||||
MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps)
|
||||
TimerDAO.insert(meetingProps.meetingProp.intId)
|
||||
LayoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp.meetingLayout)
|
||||
MeetingClientSettingsDAO.insert(meetingProps.meetingProp.intId, JsonUtils.mapToJson(clientSettings))
|
||||
}
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Meeting: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def updateMeetingDurationByParentMeeting(parentMeetingId: String, newDurationInSeconds: Int) = {
|
||||
@ -106,9 +143,9 @@ object MeetingDAO {
|
||||
.map(u => u.durationInSeconds)
|
||||
.update(newDurationInSeconds)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated durationInSeconds on Meeting table")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating durationInSeconds on Meeting: $e")
|
||||
}
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated durationInSeconds on Meeting table")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating durationInSeconds on Meeting: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def delete(meetingId: String) = {
|
||||
@ -117,9 +154,32 @@ object MeetingDAO {
|
||||
.filter(_.meetingId === meetingId)
|
||||
.delete
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Meeting ${meetingId} deleted")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error deleting meeting ${meetingId}: $e")
|
||||
}
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Meeting ${meetingId} deleted")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error deleting meeting ${meetingId}: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def setMeetingEnded(meetingId: String, endedReasonCode: String, endedBy: String) = {
|
||||
|
||||
UserDAO.softDeleteAllFromMeeting(meetingId)
|
||||
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[MeetingDbTableDef]
|
||||
.filter(_.meetingId === meetingId)
|
||||
.map(a => (a.endedAt, a.endedReasonCode, a.endedBy))
|
||||
.update(
|
||||
(
|
||||
Some(new java.sql.Timestamp(System.currentTimeMillis())),
|
||||
Some(endedReasonCode),
|
||||
endedBy match {
|
||||
case "" => None
|
||||
case c => Some(c)
|
||||
}
|
||||
)
|
||||
)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated endedAt=now() on Meeting table!")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating endedAt=now() Meeting: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
package org.bigbluebutton.core.db
|
||||
|
||||
import PostgresProfile.api._
|
||||
import org.bigbluebutton.core.db.DatabaseConnection.{db, logger}
|
||||
import spray.json.JsValue
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.{Await, Future}
|
||||
import scala.util.{Failure, Success}
|
||||
import scala.concurrent.duration.Duration
|
||||
|
||||
object Permission {
|
||||
val allowedRoles = List("MODERATOR","VIEWER","PRESENTER")
|
||||
@ -19,6 +23,7 @@ case class PluginDataChannelMessageDbModel(
|
||||
toRoles: Option[List[String]],
|
||||
toUserIds: Option[List[String]],
|
||||
createdAt: java.sql.Timestamp,
|
||||
deletedAt: Option[java.sql.Timestamp],
|
||||
)
|
||||
|
||||
class PluginDataChannelMessageDbTableDef(tag: Tag) extends Table[PluginDataChannelMessageDbModel](tag, None, "pluginDataChannelMessage") {
|
||||
@ -31,7 +36,8 @@ class PluginDataChannelMessageDbTableDef(tag: Tag) extends Table[PluginDataChann
|
||||
val toRoles = column[Option[List[String]]]("toRoles")
|
||||
val toUserIds = column[Option[List[String]]]("toUserIds")
|
||||
val createdAt = column[java.sql.Timestamp]("createdAt")
|
||||
override def * = (meetingId, pluginName, dataChannel, payloadJson, fromUserId, toRoles, toUserIds, createdAt) <> (PluginDataChannelMessageDbModel.tupled, PluginDataChannelMessageDbModel.unapply)
|
||||
val deletedAt = column[Option[java.sql.Timestamp]]("deletedAt")
|
||||
override def * = (meetingId, pluginName, dataChannel, payloadJson, fromUserId, toRoles, toUserIds, createdAt, deletedAt) <> (PluginDataChannelMessageDbModel.tupled, PluginDataChannelMessageDbModel.unapply)
|
||||
}
|
||||
|
||||
object PluginDataChannelMessageDAO {
|
||||
@ -49,7 +55,8 @@ object PluginDataChannelMessageDAO {
|
||||
case filtered => Some(filtered)
|
||||
},
|
||||
toUserIds = if(toUserIds.isEmpty) None else Some(toUserIds),
|
||||
createdAt = new java.sql.Timestamp(System.currentTimeMillis())
|
||||
createdAt = new java.sql.Timestamp(System.currentTimeMillis()),
|
||||
deletedAt = None
|
||||
)
|
||||
)
|
||||
).onComplete {
|
||||
@ -57,4 +64,51 @@ object PluginDataChannelMessageDAO {
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PluginDataChannelMessage: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def reset(meetingId: String, pluginName: String, dataChannel: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PluginDataChannelMessageDbTableDef]
|
||||
.filter(_.meetingId === meetingId)
|
||||
.filter(_.pluginName === pluginName)
|
||||
.filter(_.dataChannel === dataChannel)
|
||||
.map(u => (u.deletedAt))
|
||||
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated deleted=now() on pluginDataChannelMessage table!")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating deleted=now() pluginDataChannelMessage: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def getMessageSender(meetingId: String, pluginName: String, dataChannel: String, messageId: String): String = {
|
||||
val query = sql"""SELECT "fromUserId"
|
||||
FROM "pluginDataChannelMessage"
|
||||
WHERE "deletedAt" is null
|
||||
AND "meetingId" = ${meetingId}
|
||||
AND "pluginName" = ${pluginName}
|
||||
AND "dataChannel" = ${dataChannel}
|
||||
AND "messageId" = ${messageId}""".as[String].headOption
|
||||
|
||||
Await.result(DatabaseConnection.db.run(query), Duration.Inf) match {
|
||||
case Some(userId) => userId
|
||||
case None => {
|
||||
logger.debug("Message {} not found in database (maybe it was deleted).", messageId)
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def delete(meetingId: String, pluginName: String, dataChannel: String, messageId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
sqlu"""UPDATE "pluginDataChannelMessage" SET
|
||||
"deletedAt" = current_timestamp
|
||||
WHERE "meetingId" = ${meetingId}
|
||||
AND "pluginName" = ${pluginName}
|
||||
AND "dataChannel" = ${dataChannel}
|
||||
AND "messageId" = ${messageId}"""
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated deleted=now() on pluginDataChannelMessage table!")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating deleted=now() pluginDataChannelMessage: $e")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package org.bigbluebutton.core.db
|
||||
|
||||
import com.github.tminglei.slickpg._
|
||||
import org.apache.pekko.http.scaladsl.model.ParsingException
|
||||
import org.bigbluebutton.common2.domain.SimpleVoteOutVO
|
||||
import spray.json.{ JsArray, JsBoolean, JsNumber, JsObject, JsString, JsValue, JsonWriter, _ }
|
||||
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
@ -29,16 +30,17 @@ object PostgresProfile extends PostgresProfile
|
||||
object JsonUtils {
|
||||
implicit object AnyJsonWriter extends JsonWriter[Any] {
|
||||
def write(x: Any): JsValue = x match {
|
||||
case n: Int => JsNumber(n)
|
||||
case s: String => JsString(s)
|
||||
case b: Boolean => JsBoolean(b)
|
||||
case f: Float => JsNumber(f)
|
||||
case d: Double => JsNumber(d)
|
||||
case m: Map[_, _] => JsObject(m.asInstanceOf[Map[String, Any]].map { case (k, v) => k -> write(v) })
|
||||
case l: List[_] => JsArray(l.map(write).toVector)
|
||||
case a: Array[_] => JsArray(a.map(write).toVector)
|
||||
case null => JsNull
|
||||
case _ => throw new IllegalArgumentException(s"Unsupported type: ${x.getClass.getName}")
|
||||
case n: Int => JsNumber(n)
|
||||
case s: String => JsString(s)
|
||||
case b: Boolean => JsBoolean(b)
|
||||
case f: Float => JsNumber(f)
|
||||
case d: Double => JsNumber(d)
|
||||
case m: Map[_, _] => JsObject(m.asInstanceOf[Map[String, Any]].map { case (k, v) => k -> write(v) })
|
||||
case l: List[_] => JsArray(l.map(write).toVector)
|
||||
case a: Array[_] => JsArray(a.map(write).toVector)
|
||||
case v: SimpleVoteOutVO => JsObject("id" -> JsNumber(v.id), "key" -> JsString(v.key), "numVotes" -> JsNumber(v.numVotes))
|
||||
case null => JsNull
|
||||
case _ => throw new IllegalArgumentException(s"Unsupported type: ${x.getClass.getName}")
|
||||
// case _ => JsNull
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ case class UserDbModel(
|
||||
avatar: String = "",
|
||||
color: String = "",
|
||||
sessionToken: String = "",
|
||||
authToken: String = "",
|
||||
authed: Boolean = false,
|
||||
joined: Boolean = false,
|
||||
joinErrorMessage: Option[String],
|
||||
@ -31,7 +32,7 @@ case class UserDbModel(
|
||||
|
||||
class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
|
||||
override def * = (
|
||||
userId,extId,meetingId,name,role,avatar,color, sessionToken, authed,joined,joinErrorCode, joinErrorMessage, banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard, enforceLayout) <> (UserDbModel.tupled, UserDbModel.unapply)
|
||||
userId,extId,meetingId,name,role,avatar,color, sessionToken, authToken, authed,joined,joinErrorCode, joinErrorMessage, banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard, enforceLayout) <> (UserDbModel.tupled, UserDbModel.unapply)
|
||||
val userId = column[String]("userId", O.PrimaryKey)
|
||||
val extId = column[String]("extId")
|
||||
val meetingId = column[String]("meetingId")
|
||||
@ -40,6 +41,7 @@ class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
|
||||
val avatar = column[String]("avatar")
|
||||
val color = column[String]("color")
|
||||
val sessionToken = column[String]("sessionToken")
|
||||
val authToken = column[String]("authToken")
|
||||
val authed = column[Boolean]("authed")
|
||||
val joined = column[Boolean]("joined")
|
||||
val joinErrorCode = column[Option[String]]("joinErrorCode")
|
||||
@ -60,6 +62,7 @@ object UserDAO {
|
||||
UserDbModel(
|
||||
userId = regUser.id,
|
||||
extId = regUser.externId,
|
||||
authToken = regUser.authToken,
|
||||
meetingId = meetingId,
|
||||
name = regUser.name,
|
||||
role = regUser.role,
|
||||
@ -132,7 +135,7 @@ object UserDAO {
|
||||
}
|
||||
|
||||
|
||||
def delete(intId: String) = {
|
||||
def softDelete(intId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserDbTableDef]
|
||||
.filter(_.userId === intId)
|
||||
@ -144,7 +147,19 @@ object UserDAO {
|
||||
}
|
||||
}
|
||||
|
||||
def deleteAllFromMeeting(meetingId: String) = {
|
||||
def softDeleteAllFromMeeting(meetingId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserDbTableDef]
|
||||
.filter(_.meetingId === meetingId)
|
||||
.map(u => (u.loggedOut))
|
||||
.update((true))
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated loggedOut=true on user table!")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating loggedOut=true user: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def permanentlyDeleteAllFromMeeting(meetingId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserDbTableDef]
|
||||
.filter(_.meetingId === meetingId)
|
||||
|
@ -9,32 +9,35 @@ import scala.util.{Failure, Success }
|
||||
case class UserGraphqlConnectionDbModel (
|
||||
graphqlConnectionId: Option[Int],
|
||||
sessionToken: String,
|
||||
middlewareUID: String,
|
||||
middlewareConnectionId: String,
|
||||
stablishedAt: java.sql.Timestamp,
|
||||
establishedAt: java.sql.Timestamp,
|
||||
closedAt: Option[java.sql.Timestamp],
|
||||
)
|
||||
|
||||
class UserGraphqlConnectionDbTableDef(tag: Tag) extends Table[UserGraphqlConnectionDbModel](tag, None, "user_graphqlConnection") {
|
||||
override def * = (
|
||||
graphqlConnectionId, sessionToken, middlewareConnectionId, stablishedAt, closedAt
|
||||
graphqlConnectionId, sessionToken, middlewareUID, middlewareConnectionId, establishedAt, closedAt
|
||||
) <> (UserGraphqlConnectionDbModel.tupled, UserGraphqlConnectionDbModel.unapply)
|
||||
val graphqlConnectionId = column[Option[Int]]("graphqlConnectionId", O.PrimaryKey, O.AutoInc)
|
||||
val sessionToken = column[String]("sessionToken")
|
||||
val middlewareUID = column[String]("middlewareUID")
|
||||
val middlewareConnectionId = column[String]("middlewareConnectionId")
|
||||
val stablishedAt = column[java.sql.Timestamp]("stablishedAt")
|
||||
val establishedAt = column[java.sql.Timestamp]("establishedAt")
|
||||
val closedAt = column[Option[java.sql.Timestamp]]("closedAt")
|
||||
}
|
||||
|
||||
|
||||
object UserGraphqlConnectionDAO {
|
||||
def insert(sessionToken: String, middlewareConnectionId: String) = {
|
||||
def insert(sessionToken: String, middlewareUID:String, middlewareConnectionId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserGraphqlConnectionDbTableDef].insertOrUpdate(
|
||||
UserGraphqlConnectionDbModel(
|
||||
graphqlConnectionId = None,
|
||||
sessionToken = sessionToken,
|
||||
middlewareUID = middlewareUID,
|
||||
middlewareConnectionId = middlewareConnectionId,
|
||||
stablishedAt = new java.sql.Timestamp(System.currentTimeMillis()),
|
||||
establishedAt = new java.sql.Timestamp(System.currentTimeMillis()),
|
||||
closedAt = None
|
||||
)
|
||||
)
|
||||
@ -46,11 +49,12 @@ object UserGraphqlConnectionDAO {
|
||||
}
|
||||
}
|
||||
|
||||
def updateClosed(sessionToken: String, middlewareConnectionId: String) = {
|
||||
def updateClosed(sessionToken: String, middlewareUID: String, middlewareConnectionId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserGraphqlConnectionDbTableDef]
|
||||
.filter(_.sessionToken === sessionToken)
|
||||
.filter(_.middlewareConnectionId === middlewareConnectionId)
|
||||
.filter(_.middlewareUID === middlewareUID)
|
||||
.filter(_.closedAt.isEmpty)
|
||||
.map(u => u.closedAt)
|
||||
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
|
||||
|
@ -23,13 +23,13 @@ class UserReactionDbTableDef(tag: Tag) extends Table[UserReactionDbModel](tag, "
|
||||
}
|
||||
|
||||
object UserReactionDAO {
|
||||
def insert(userId: String, reactionEmoji: String) = {
|
||||
def insert(userId: String, reactionEmoji: String, durationInSeconds: Int) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserReactionDbTableDef].forceInsert(
|
||||
UserReactionDbModel(
|
||||
userId = userId,
|
||||
reactionEmoji = reactionEmoji,
|
||||
durationInSeconds = 60,
|
||||
durationInSeconds = durationInSeconds,
|
||||
createdAt = new java.sql.Timestamp(System.currentTimeMillis())
|
||||
)
|
||||
)
|
||||
|
@ -104,7 +104,7 @@ object Polls {
|
||||
} yield {
|
||||
val pageId = if (poll.id.contains("deskshare")) "deskshare" else page.id
|
||||
val updatedShape = shape + ("whiteboardId" -> pageId)
|
||||
val annotation = new AnnotationVO(poll.id, updatedShape, pageId, requesterId)
|
||||
val annotation = new AnnotationVO(s"shape:poll-result-${poll.id}", updatedShape, pageId, requesterId)
|
||||
annotation
|
||||
}
|
||||
}
|
||||
@ -243,7 +243,6 @@ object Polls {
|
||||
|
||||
private def handleRespondToTypedPoll(poll: SimplePollResultOutVO, requesterId: String, pollId: String, questionId: Int,
|
||||
answer: String, lm: LiveMeeting): Option[SimplePollResultOutVO] = {
|
||||
|
||||
addQuestionResponse(poll.id, questionId, answer, requesterId, lm.polls)
|
||||
for {
|
||||
updatedPoll <- getSimplePollResult(poll.id, lm.polls)
|
||||
@ -254,12 +253,13 @@ object Polls {
|
||||
|
||||
private def pollResultToWhiteboardShape(result: SimplePollResultOutVO): scala.collection.immutable.Map[String, Object] = {
|
||||
val shape = new scala.collection.mutable.HashMap[String, Object]()
|
||||
shape += "numRespondents" -> new Integer(result.numRespondents)
|
||||
shape += "numResponders" -> new Integer(result.numResponders)
|
||||
shape += "numRespondents" -> Integer.valueOf(result.numRespondents)
|
||||
shape += "numResponders" -> Integer.valueOf(result.numResponders)
|
||||
shape += "questionType" -> result.questionType
|
||||
shape += "questionText" -> result.questionText
|
||||
shape += "id" -> result.id
|
||||
shape += "questionText" -> result.questionText.getOrElse("")
|
||||
shape += "id" -> s"shape:poll-result-${result.id}"
|
||||
shape += "answers" -> result.answers
|
||||
shape += "type" -> "geo"
|
||||
shape.toMap
|
||||
}
|
||||
|
||||
@ -362,10 +362,10 @@ object Polls {
|
||||
pvo
|
||||
}
|
||||
|
||||
def checkUserResponded(pollId: String, userId: String, polls: Polls): Boolean = {
|
||||
def hasUserAlreadyResponded(pollId: String, userId: String, polls: Polls): Boolean = {
|
||||
polls.polls.get(pollId) match {
|
||||
case Some(p) => {
|
||||
if (p.getResponders().filter(p => p.userId == userId).length > 0) {
|
||||
if (p.getResponders().exists(p => p.userId == userId)) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@ -375,10 +375,10 @@ object Polls {
|
||||
}
|
||||
}
|
||||
|
||||
def checkUserAddedQuestion(pollId: String, userId: String, polls: Polls): Boolean = {
|
||||
def hasUserAlreadyAddedTypedAnswer(pollId: String, userId: String, polls: Polls): Boolean = {
|
||||
polls.polls.get(pollId) match {
|
||||
case Some(p) => {
|
||||
if (p.getTypedPollResponders().filter(responderId => responderId == userId).length > 0) {
|
||||
if (p.getTypedPollResponders().contains(userId)) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@ -401,6 +401,17 @@ object Polls {
|
||||
}
|
||||
}
|
||||
|
||||
def findAnswerWithText(pollId: String, questionId: Int, answerText: String, polls: Polls): Option[Int] = {
|
||||
for {
|
||||
poll <- Polls.getPoll(pollId, polls)
|
||||
question <- poll.questions.find(q => q.id == questionId)
|
||||
answers <- question.answers
|
||||
equalAnswer <- answers.find(ans => ans.text.getOrElse("") == answerText)
|
||||
} yield {
|
||||
equalAnswer.id
|
||||
}
|
||||
}
|
||||
|
||||
def showPollResult(pollId: String, polls: Polls) {
|
||||
polls.get(pollId) foreach {
|
||||
p =>
|
||||
|
@ -176,8 +176,8 @@ case class PresentationPod(id: String, currentPresenter: String,
|
||||
// 100D-checkedWidth is the maximum the page can be moved over
|
||||
val checkedWidth = Math.min(widthRatio, 100D) //if (widthRatio <= 100D) widthRatio else 100D
|
||||
val checkedHeight = Math.min(heightRatio, 100D)
|
||||
val checkedXOffset = Math.min(xOffset, 0D)
|
||||
val checkedYOffset = Math.min(yOffset, 0D)
|
||||
val checkedXOffset = xOffset
|
||||
val checkedYOffset = yOffset
|
||||
|
||||
for {
|
||||
pres <- presentations.get(presentationId)
|
||||
|
@ -122,7 +122,7 @@ object RegisteredUsers {
|
||||
u
|
||||
} else {
|
||||
users.delete(ejectedUser.id)
|
||||
// UserDAO.delete(ejectedUser) it's being removed in User2x already
|
||||
// UserDAO.softDelete(ejectedUser) it's being removed in User2x already
|
||||
ejectedUser
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ object Users2x {
|
||||
}
|
||||
|
||||
def remove(users: Users2x, intId: String): Option[UserState] = {
|
||||
//UserDAO.delete(intId)
|
||||
//UserDAO.softDelete(intId)
|
||||
users.remove(intId)
|
||||
}
|
||||
|
||||
@ -125,7 +125,7 @@ object Users2x {
|
||||
_ <- users.remove(intId)
|
||||
ejectedUser <- users.removeFromCache(intId)
|
||||
} yield {
|
||||
// UserDAO.delete(intId) --it will keep the user on Db
|
||||
// UserDAO.softDelete(intId) --it will keep the user on Db
|
||||
ejectedUser
|
||||
}
|
||||
}
|
||||
@ -195,7 +195,7 @@ object Users2x {
|
||||
newUser
|
||||
}
|
||||
}
|
||||
def setReactionEmoji(users: Users2x, intId: String, reactionEmoji: String): Option[UserState] = {
|
||||
def setReactionEmoji(users: Users2x, intId: String, reactionEmoji: String, durationInSeconds: Int): Option[UserState] = {
|
||||
for {
|
||||
u <- findWithIntId(users, intId)
|
||||
} yield {
|
||||
@ -203,7 +203,7 @@ object Users2x {
|
||||
.modify(_.reactionChangedOn).setTo(System.currentTimeMillis())
|
||||
|
||||
users.save(newUser)
|
||||
UserReactionDAO.insert(intId, reactionEmoji)
|
||||
UserReactionDAO.insert(intId, reactionEmoji, durationInSeconds)
|
||||
newUser
|
||||
}
|
||||
}
|
||||
|
@ -416,8 +416,14 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[CreateGroupChatReqMsg](envelope, jsonNode)
|
||||
|
||||
//Plugin
|
||||
case DispatchPluginDataChannelMessageMsg.NAME =>
|
||||
routeGenericMsg[DispatchPluginDataChannelMessageMsg](envelope, jsonNode)
|
||||
case PluginDataChannelDispatchMessageMsg.NAME =>
|
||||
routeGenericMsg[PluginDataChannelDispatchMessageMsg](envelope, jsonNode)
|
||||
|
||||
case PluginDataChannelDeleteMessageMsg.NAME =>
|
||||
routeGenericMsg[PluginDataChannelDeleteMessageMsg](envelope, jsonNode)
|
||||
|
||||
case PluginDataChannelResetMsg.NAME =>
|
||||
routeGenericMsg[PluginDataChannelResetMsg](envelope, jsonNode)
|
||||
|
||||
// ExternalVideo
|
||||
case StartExternalVideoPubMsg.NAME =>
|
||||
@ -450,12 +456,15 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[TimerEndedPubMsg](envelope, jsonNode)
|
||||
|
||||
// Messages from Graphql Middleware
|
||||
case UserGraphqlConnectionStablishedSysMsg.NAME =>
|
||||
route[UserGraphqlConnectionStablishedSysMsg](meetingManagerChannel, envelope, jsonNode)
|
||||
case UserGraphqlConnectionEstablishedSysMsg.NAME =>
|
||||
route[UserGraphqlConnectionEstablishedSysMsg](meetingManagerChannel, envelope, jsonNode)
|
||||
|
||||
case UserGraphqlConnectionClosedSysMsg.NAME =>
|
||||
route[UserGraphqlConnectionClosedSysMsg](meetingManagerChannel, envelope, jsonNode)
|
||||
|
||||
case CheckGraphqlMiddlewareAlivePongSysMsg.NAME =>
|
||||
route[CheckGraphqlMiddlewareAlivePongSysMsg](meetingManagerChannel, envelope, jsonNode)
|
||||
|
||||
case _ =>
|
||||
log.error("Cannot route envelope name " + envelope.name)
|
||||
// do nothing
|
||||
|
@ -27,6 +27,10 @@ class StoreExportJobInRedisPresAnnEvent extends AbstractPresentationWithAnnotati
|
||||
|
||||
setEvent("StoreExportJobInRedisPresAnnEvent")
|
||||
|
||||
def setserverSideFilename(serverSideFilename: String) {
|
||||
eventMap.put(SERVER_SIDE_FILENAME, serverSideFilename)
|
||||
}
|
||||
|
||||
def setJobId(jobId: String) {
|
||||
eventMap.put(JOB_ID, jobId)
|
||||
}
|
||||
@ -68,6 +72,7 @@ object StoreExportJobInRedisPresAnnEvent {
|
||||
protected final val JOB_ID = "jobId"
|
||||
protected final val JOB_TYPE = "jobType"
|
||||
protected final val FILENAME = "filename"
|
||||
protected final val SERVER_SIDE_FILENAME = "serverSideFilename"
|
||||
protected final val PRES_ID = "presId"
|
||||
protected final val PRES_LOCATION = "presLocation"
|
||||
protected final val ALL_PAGES = "allPages"
|
||||
|
@ -7,7 +7,7 @@ import org.bigbluebutton.core.apps.groupchats.GroupChatApp
|
||||
import org.bigbluebutton.core.apps.users.UsersApp
|
||||
import org.bigbluebutton.core.apps.voice.VoiceApp
|
||||
import org.bigbluebutton.core.bus.{BigBlueButtonEvent, InternalEventBus}
|
||||
import org.bigbluebutton.core.db.{BreakoutRoomUserDAO, MeetingRecordingDAO, UserBreakoutRoomDAO}
|
||||
import org.bigbluebutton.core.db.{BreakoutRoomUserDAO, MeetingDAO, MeetingRecordingDAO, UserBreakoutRoomDAO}
|
||||
import org.bigbluebutton.core.domain.{MeetingEndReason, MeetingState2x}
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
@ -206,6 +206,8 @@ trait HandlerHelpers extends SystemConfiguration {
|
||||
|
||||
val endedEvnt = buildMeetingEndedEvtMsg(liveMeeting.props.meetingProp.intId)
|
||||
outGW.send(endedEvnt)
|
||||
|
||||
MeetingDAO.setMeetingEnded(liveMeeting.props.meetingProp.intId, reason, userId)
|
||||
}
|
||||
|
||||
def destroyMeeting(eventBus: InternalEventBus, meetingId: String): Unit = {
|
||||
|
@ -76,6 +76,7 @@ class MeetingActor(
|
||||
|
||||
with UserJoinMeetingReqMsgHdlr
|
||||
with UserJoinMeetingAfterReconnectReqMsgHdlr
|
||||
with UserEstablishedGraphqlConnectionInternalMsgHdlr
|
||||
with UserConnectedToGlobalAudioMsgHdlr
|
||||
with UserDisconnectedFromGlobalAudioMsgHdlr
|
||||
with MuteAllExceptPresentersCmdMsgHdlr
|
||||
@ -266,8 +267,14 @@ class MeetingActor(
|
||||
// internal messages
|
||||
case msg: MonitorNumberOfUsersInternalMsg => handleMonitorNumberOfUsers(msg)
|
||||
case msg: SetPresenterInDefaultPodInternalMsg => state = presentationPodsApp.handleSetPresenterInDefaultPodInternalMsg(msg, state, liveMeeting, msgBus)
|
||||
case msg: UserClosedAllGraphqlConnectionsInternalMsg =>
|
||||
state = handleUserClosedAllGraphqlConnectionsInternalMsg(msg, state)
|
||||
updateModeratorsPresence()
|
||||
case msg: UserEstablishedGraphqlConnectionInternalMsg =>
|
||||
state = handleUserEstablishedGraphqlConnectionInternalMsg(msg, state)
|
||||
updateModeratorsPresence()
|
||||
|
||||
case msg: ExtendMeetingDuration => handleExtendMeetingDuration(msg)
|
||||
case msg: ExtendMeetingDuration => handleExtendMeetingDuration(msg)
|
||||
case msg: SendTimeRemainingAuditInternalMsg =>
|
||||
if (!liveMeeting.props.meetingProp.isBreakout) {
|
||||
// Update users of meeting remaining time.
|
||||
@ -594,7 +601,9 @@ class MeetingActor(
|
||||
updateUserLastActivity(m.body.msg.sender.id)
|
||||
|
||||
// Plugin
|
||||
case m: DispatchPluginDataChannelMessageMsg => pluginHdlrs.handle(m, state, liveMeeting)
|
||||
case m: PluginDataChannelDispatchMessageMsg => pluginHdlrs.handle(m, state, liveMeeting)
|
||||
case m: PluginDataChannelDeleteMessageMsg => pluginHdlrs.handle(m, state, liveMeeting)
|
||||
case m: PluginDataChannelResetMsg => pluginHdlrs.handle(m, state, liveMeeting)
|
||||
|
||||
// Webcams
|
||||
case m: UserBroadcastCamStartMsg => webcamApp2x.handle(m, liveMeeting, msgBus)
|
||||
@ -1005,7 +1014,7 @@ class MeetingActor(
|
||||
for {
|
||||
regUser <- RegisteredUsers.findWithUserId(u.intId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, EjectReasonCode.USER_INACTIVITY, outGW)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, EjectReasonCode.USER_INACTIVITY, outGW)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -246,12 +246,22 @@ object MsgBuilder {
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildInvalidateUserGraphqlConnectionSysMsg(meetingId: String, userId: String, sessionToken: String, reason: String): BbbCommonEnvCoreMsg = {
|
||||
def buildForceUserGraphqlReconnectionSysMsg(meetingId: String, userId: String, sessionToken: String, reason: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.SYSTEM, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(InvalidateUserGraphqlConnectionSysMsg.NAME, routing)
|
||||
val header = BbbCoreHeaderWithMeetingId(InvalidateUserGraphqlConnectionSysMsg.NAME, meetingId)
|
||||
val body = InvalidateUserGraphqlConnectionSysMsgBody(meetingId, userId, sessionToken, reason)
|
||||
val event = InvalidateUserGraphqlConnectionSysMsg(header, body)
|
||||
val envelope = BbbCoreEnvelope(ForceUserGraphqlReconnectionSysMsg.NAME, routing)
|
||||
val header = BbbCoreHeaderWithMeetingId(ForceUserGraphqlReconnectionSysMsg.NAME, meetingId)
|
||||
val body = ForceUserGraphqlReconnectionSysMsgBody(meetingId, userId, sessionToken, reason)
|
||||
val event = ForceUserGraphqlReconnectionSysMsg(header, body)
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildCheckGraphqlMiddlewareAlivePingSysMsg(middlewareUid: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.SYSTEM, "", "")
|
||||
val envelope = BbbCoreEnvelope(CheckGraphqlMiddlewareAlivePingSysMsg.NAME, routing)
|
||||
val header = BbbCoreHeaderWithMeetingId(CheckGraphqlMiddlewareAlivePingSysMsg.NAME, "")
|
||||
val body = CheckGraphqlMiddlewareAlivePingSysMsgBody(middlewareUid)
|
||||
val event = CheckGraphqlMiddlewareAlivePingSysMsg(header, body)
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ object Sender {
|
||||
outGW.send(ejectFromMeetingSystemEvent)
|
||||
}
|
||||
|
||||
def sendInvalidateUserGraphqlConnectionSysMsg(meetingId: String, userId: String, sessionToken: String, reason: String, outGW: OutMsgRouter): Unit = {
|
||||
val invalidateUserGraphqlConnectionSysMsg = MsgBuilder.buildInvalidateUserGraphqlConnectionSysMsg(meetingId, userId, sessionToken, reason)
|
||||
outGW.send(invalidateUserGraphqlConnectionSysMsg)
|
||||
def sendForceUserGraphqlReconnectionSysMsg(meetingId: String, userId: String, sessionToken: String, reason: String, outGW: OutMsgRouter): Unit = {
|
||||
val ForceUserGraphqlReconnectionSysMsg = MsgBuilder.buildForceUserGraphqlReconnectionSysMsg(meetingId, userId, sessionToken, reason)
|
||||
outGW.send(ForceUserGraphqlReconnectionSysMsg)
|
||||
}
|
||||
|
||||
def sendUserInactivityInspectMsg(meetingId: String, userId: String, responseDelay: Long, outGW: OutMsgRouter): Unit = {
|
||||
|
@ -73,6 +73,7 @@ class ExportAnnotationsActor(
|
||||
private def handleStoreExportJobInRedisSysMsg(msg: StoreExportJobInRedisSysMsg) {
|
||||
val ev = new StoreExportJobInRedisPresAnnEvent()
|
||||
|
||||
ev.setserverSideFilename(msg.body.exportJob.serverSideFilename)
|
||||
ev.setJobId(msg.body.exportJob.jobId)
|
||||
ev.setJobType(msg.body.exportJob.jobType)
|
||||
ev.setFilename(msg.body.exportJob.filename)
|
||||
|
@ -1,43 +0,0 @@
|
||||
package org.bigbluebutton.endpoint.redis
|
||||
|
||||
import org.apache.pekko.actor.{Actor, ActorLogging, ActorSystem, Props}
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.db.UserGraphqlConnectionDAO
|
||||
|
||||
object GraphqlActionsActor {
|
||||
def props(system: ActorSystem): Props =
|
||||
Props(
|
||||
classOf[GraphqlActionsActor],
|
||||
system,
|
||||
)
|
||||
}
|
||||
|
||||
class GraphqlActionsActor(
|
||||
system: ActorSystem,
|
||||
) extends Actor with ActorLogging {
|
||||
|
||||
def receive = {
|
||||
//=============================
|
||||
// 2x messages
|
||||
case msg: BbbCommonEnvCoreMsg => handleBbbCommonEnvCoreMsg(msg)
|
||||
case _ => // do nothing
|
||||
}
|
||||
|
||||
private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
|
||||
msg.core match {
|
||||
// Messages from bbb-graphql-middleware
|
||||
case m: UserGraphqlConnectionStablishedSysMsg => handleUserGraphqlConnectionStablishedSysMsg(m)
|
||||
case m: UserGraphqlConnectionClosedSysMsg => handleUserGraphqlConnectionClosedSysMsg(m)
|
||||
case _ => // message not to be handled.
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserGraphqlConnectionStablishedSysMsg(msg: UserGraphqlConnectionStablishedSysMsg) {
|
||||
UserGraphqlConnectionDAO.insert(msg.body.sessionToken, msg.body.browserConnectionId)
|
||||
}
|
||||
|
||||
private def handleUserGraphqlConnectionClosedSysMsg(msg: UserGraphqlConnectionClosedSysMsg) {
|
||||
UserGraphqlConnectionDAO.updateClosed(msg.body.sessionToken, msg.body.browserConnectionId)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
package org.bigbluebutton.endpoint.redis
|
||||
|
||||
import org.apache.pekko.actor.{Actor, ActorLogging, ActorSystem, Props}
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.OutMessageGateway
|
||||
import org.bigbluebutton.core.api.{UserClosedAllGraphqlConnectionsInternalMsg, UserEstablishedGraphqlConnectionInternalMsg}
|
||||
import org.bigbluebutton.core.bus.{BigBlueButtonEvent, InternalEventBus}
|
||||
import org.bigbluebutton.core.db.UserGraphqlConnectionDAO
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
import scala.concurrent.ExecutionContext
|
||||
import scala.concurrent.duration._
|
||||
import ExecutionContext.Implicits.global
|
||||
|
||||
case object MiddlewareHealthCheckScheduler10Sec
|
||||
|
||||
object GraphqlConnectionsActor {
|
||||
def props(system: ActorSystem,
|
||||
eventBus: InternalEventBus,
|
||||
outGW: OutMessageGateway,
|
||||
): Props =
|
||||
Props(
|
||||
classOf[GraphqlConnectionsActor],
|
||||
system,
|
||||
eventBus,
|
||||
outGW,
|
||||
)
|
||||
}
|
||||
|
||||
case class GraphqlUser(
|
||||
intId: String,
|
||||
meetingId: String,
|
||||
sessionToken: String,
|
||||
)
|
||||
|
||||
case class GraphqlUserConnection(
|
||||
middlewareUID: String,
|
||||
browserConnectionId: String,
|
||||
sessionToken: String,
|
||||
user: GraphqlUser,
|
||||
)
|
||||
|
||||
|
||||
class GraphqlConnectionsActor(
|
||||
system: ActorSystem,
|
||||
val eventBus: InternalEventBus,
|
||||
val outGW: OutMessageGateway,
|
||||
) extends Actor with ActorLogging {
|
||||
|
||||
private var users: Map[String, GraphqlUser] = Map()
|
||||
private var graphqlConnections: Map[String, GraphqlUserConnection] = Map()
|
||||
private var pendingResponseMiddlewareUIDs: Map[String, BigInt] = Map()
|
||||
|
||||
system.scheduler.schedule(10.seconds, 10.seconds, self, MiddlewareHealthCheckScheduler10Sec)
|
||||
private val maxMiddlewareInactivityInMillis = 11000
|
||||
|
||||
def receive = {
|
||||
//=============================
|
||||
// 2x messages
|
||||
case msg: BbbCommonEnvCoreMsg => handleBbbCommonEnvCoreMsg(msg)
|
||||
case MiddlewareHealthCheckScheduler10Sec => runMiddlewareHealthCheck()
|
||||
case _ => // do nothing
|
||||
}
|
||||
|
||||
private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
|
||||
msg.core match {
|
||||
case m: RegisterUserReqMsg => handleUserRegisteredRespMsg(m)
|
||||
case m: DestroyMeetingSysCmdMsg => handleDestroyMeetingSysCmdMsg(m)
|
||||
// Messages from bbb-graphql-middleware
|
||||
case m: UserGraphqlConnectionEstablishedSysMsg => handleUserGraphqlConnectionEstablishedSysMsg(m)
|
||||
case m: UserGraphqlConnectionClosedSysMsg => handleUserGraphqlConnectionClosedSysMsg(m)
|
||||
case m: CheckGraphqlMiddlewareAlivePongSysMsg => handleCheckGraphqlMiddlewareAlivePongSysMsg(m)
|
||||
case _ => // message not to be handled.
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserRegisteredRespMsg(msg: RegisterUserReqMsg): Unit = {
|
||||
users += (msg.body.sessionToken -> GraphqlUser(
|
||||
msg.body.intUserId,
|
||||
msg.body.meetingId,
|
||||
msg.body.sessionToken
|
||||
))
|
||||
}
|
||||
|
||||
private def handleDestroyMeetingSysCmdMsg(msg: DestroyMeetingSysCmdMsg): Unit = {
|
||||
users = users.filter(u => u._2.meetingId != msg.body.meetingId)
|
||||
graphqlConnections = graphqlConnections.filter(c => c._2.user.meetingId != msg.body.meetingId)
|
||||
}
|
||||
|
||||
private def handleUserGraphqlConnectionEstablishedSysMsg(msg: UserGraphqlConnectionEstablishedSysMsg): Unit = {
|
||||
UserGraphqlConnectionDAO.insert(msg.body.sessionToken, msg.body.middlewareUID, msg.body.browserConnectionId)
|
||||
|
||||
for {
|
||||
user <- users.get(msg.body.sessionToken)
|
||||
} yield {
|
||||
|
||||
//Send internal message informing user has connected
|
||||
if (!graphqlConnections.values.exists(c => c.sessionToken == msg.body.sessionToken)) {
|
||||
eventBus.publish(BigBlueButtonEvent(user.meetingId, UserEstablishedGraphqlConnectionInternalMsg(user.intId)))
|
||||
}
|
||||
|
||||
graphqlConnections += (msg.body.browserConnectionId -> GraphqlUserConnection(
|
||||
msg.body.middlewareUID,
|
||||
msg.body.browserConnectionId,
|
||||
msg.body.sessionToken,
|
||||
user
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserGraphqlConnectionClosedSysMsg(msg: UserGraphqlConnectionClosedSysMsg): Unit = {
|
||||
handleUserGraphqlConnectionClosed(msg.body.sessionToken, msg.body.middlewareUID, msg.body.browserConnectionId)
|
||||
}
|
||||
|
||||
private def handleUserGraphqlConnectionClosed(sessionToken: String, middlewareUID: String, browserConnectionId: String): Unit = {
|
||||
UserGraphqlConnectionDAO.updateClosed(sessionToken, middlewareUID, browserConnectionId)
|
||||
|
||||
for {
|
||||
user <- users.get(sessionToken)
|
||||
} yield {
|
||||
graphqlConnections = graphqlConnections.-(browserConnectionId)
|
||||
|
||||
//Send internal message informing user disconnected
|
||||
if (!graphqlConnections.values.exists(c => c.sessionToken == sessionToken)) {
|
||||
eventBus.publish(BigBlueButtonEvent(user.meetingId, UserClosedAllGraphqlConnectionsInternalMsg(user.intId)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def runMiddlewareHealthCheck(): Unit = {
|
||||
removeInactiveConnections()
|
||||
sendPingMessageToAllMiddlewareServices()
|
||||
}
|
||||
|
||||
private def sendPingMessageToAllMiddlewareServices(): Unit = {
|
||||
graphqlConnections.map(c => {
|
||||
c._2.middlewareUID
|
||||
}).toVector.distinct.map(middlewareUID => {
|
||||
val event = MsgBuilder.buildCheckGraphqlMiddlewareAlivePingSysMsg(middlewareUID)
|
||||
outGW.send(event)
|
||||
log.debug(s"Sent ping message from graphql middleware ${middlewareUID}.")
|
||||
pendingResponseMiddlewareUIDs.get(middlewareUID) match {
|
||||
case None => pendingResponseMiddlewareUIDs += (middlewareUID -> System.currentTimeMillis)
|
||||
case _ => //Ignore
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private def removeInactiveConnections(): Unit = {
|
||||
for {
|
||||
(middlewareUid, pingSentAt) <- pendingResponseMiddlewareUIDs
|
||||
if (System.currentTimeMillis - pingSentAt) > maxMiddlewareInactivityInMillis
|
||||
} yield {
|
||||
log.info("Removing connections from the middleware {} due to inactivity of the service.",middlewareUid)
|
||||
for {
|
||||
(_, graphqlConn) <- graphqlConnections
|
||||
if graphqlConn.middlewareUID == middlewareUid
|
||||
} yield {
|
||||
handleUserGraphqlConnectionClosed(graphqlConn.sessionToken, graphqlConn.middlewareUID, graphqlConn.browserConnectionId)
|
||||
}
|
||||
|
||||
pendingResponseMiddlewareUIDs -= middlewareUid
|
||||
}
|
||||
}
|
||||
|
||||
private def handleCheckGraphqlMiddlewareAlivePongSysMsg(msg: CheckGraphqlMiddlewareAlivePongSysMsg): Unit = {
|
||||
log.debug(s"Received pong message from graphql middleware ${msg.body.middlewareUID}.")
|
||||
pendingResponseMiddlewareUIDs -= msg.body.middlewareUID
|
||||
}
|
||||
|
||||
}
|
@ -20,6 +20,7 @@ case class Meeting(
|
||||
intId: String,
|
||||
extId: String,
|
||||
name: String,
|
||||
downloadSessionDataEnabled: Boolean,
|
||||
users: Map[String, User] = Map(),
|
||||
polls: Map[String, Poll] = Map(),
|
||||
screenshares: Vector[Screenshare] = Vector(),
|
||||
@ -585,6 +586,7 @@ class LearningDashboardActor(
|
||||
msg.body.props.meetingProp.intId,
|
||||
msg.body.props.meetingProp.extId,
|
||||
msg.body.props.meetingProp.name,
|
||||
downloadSessionDataEnabled = !msg.body.props.meetingProp.disabledFeatures.contains("learningDashboardDownloadSessionData"),
|
||||
)
|
||||
|
||||
meetings += (newMeeting.intId -> newMeeting)
|
||||
|
@ -85,6 +85,9 @@ class RedisRecorderActor(
|
||||
case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m)
|
||||
case m: PresenterAssignedEvtMsg => handlePresenterAssignedEvtMsg(m)
|
||||
case m: UserEmojiChangedEvtMsg => handleUserEmojiChangedEvtMsg(m)
|
||||
case m: UserAwayChangedEvtMsg => handleUserAwayChangedEvtMsg(m)
|
||||
case m: UserRaiseHandChangedEvtMsg => handleUserRaiseHandChangedEvtMsg(m)
|
||||
case m: UserReactionEmojiChangedEvtMsg => handleUserReactionEmojiChangedEvtMsg(m)
|
||||
case m: UserRoleChangedEvtMsg => handleUserRoleChangedEvtMsg(m)
|
||||
case m: UserBroadcastCamStartedEvtMsg => handleUserBroadcastCamStartedEvtMsg(m)
|
||||
case m: UserBroadcastCamStoppedEvtMsg => handleUserBroadcastCamStoppedEvtMsg(m)
|
||||
@ -379,6 +382,18 @@ class RedisRecorderActor(
|
||||
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "emojiStatus", msg.body.emoji)
|
||||
}
|
||||
|
||||
private def handleUserAwayChangedEvtMsg(msg: UserAwayChangedEvtMsg) {
|
||||
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "away", if (msg.body.away) "true" else "false")
|
||||
}
|
||||
|
||||
private def handleUserRaiseHandChangedEvtMsg(msg: UserRaiseHandChangedEvtMsg) {
|
||||
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "raiseHand", if (msg.body.raiseHand) "true" else "false")
|
||||
}
|
||||
|
||||
private def handleUserReactionEmojiChangedEvtMsg(msg: UserReactionEmojiChangedEvtMsg) {
|
||||
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "reactionEmoji", msg.body.reactionEmoji)
|
||||
}
|
||||
|
||||
private def handleUserRoleChangedEvtMsg(msg: UserRoleChangedEvtMsg) {
|
||||
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "role", msg.body.role)
|
||||
}
|
||||
|
@ -68,7 +68,11 @@ case class LockSettingsProps(
|
||||
)
|
||||
|
||||
case class SystemProps(
|
||||
html5InstanceId: Int
|
||||
html5InstanceId: Int,
|
||||
logoutUrl: String,
|
||||
customLogoURL: String,
|
||||
bannerText: String,
|
||||
bannerColor: String,
|
||||
)
|
||||
|
||||
case class GroupProps(
|
||||
|
@ -15,7 +15,7 @@ object GroupChatMessageType {
|
||||
}
|
||||
|
||||
case class GroupChatUser(id: String, name: String = "", role: String = "VIEWER")
|
||||
case class GroupChatMsgFromUser(correlationId: String, sender: GroupChatUser, chatEmphasizedText: Boolean = false, message: String)
|
||||
case class GroupChatMsgFromUser(correlationId: String, sender: GroupChatUser, message: String)
|
||||
case class GroupChatMsgToUser(id: String, timestamp: Long, correlationId: String, sender: GroupChatUser, chatEmphasizedText: Boolean = false, message: String)
|
||||
case class GroupChatInfo(id: String, access: String, createdBy: GroupChatUser, users: Vector[GroupChatUser])
|
||||
|
||||
|
@ -5,12 +5,28 @@ package org.bigbluebutton.common2.msgs
|
||||
/**
|
||||
* Sent from graphql-actions to bbb-akka
|
||||
*/
|
||||
object DispatchPluginDataChannelMessageMsg { val NAME = "DispatchPluginDataChannelMessageMsg" }
|
||||
case class DispatchPluginDataChannelMessageMsg(header: BbbClientMsgHeader, body: DispatchPluginDataChannelMessageMsgBody) extends StandardMsg
|
||||
case class DispatchPluginDataChannelMessageMsgBody(
|
||||
object PluginDataChannelDispatchMessageMsg { val NAME = "PluginDataChannelDispatchMessageMsg" }
|
||||
case class PluginDataChannelDispatchMessageMsg(header: BbbClientMsgHeader, body: PluginDataChannelDispatchMessageMsgBody) extends StandardMsg
|
||||
case class PluginDataChannelDispatchMessageMsgBody(
|
||||
pluginName: String,
|
||||
dataChannel: String,
|
||||
payloadJson: String,
|
||||
toRoles: List[String],
|
||||
toUserIds: List[String],
|
||||
)
|
||||
|
||||
object PluginDataChannelDeleteMessageMsg { val NAME = "PluginDataChannelDeleteMessageMsg" }
|
||||
case class PluginDataChannelDeleteMessageMsg(header: BbbClientMsgHeader, body: PluginDataChannelDeleteMessageMsgBody) extends StandardMsg
|
||||
case class PluginDataChannelDeleteMessageMsgBody(
|
||||
pluginName: String,
|
||||
dataChannel: String,
|
||||
messageId: String
|
||||
)
|
||||
|
||||
|
||||
object PluginDataChannelResetMsg { val NAME = "PluginDataChannelResetMsg" }
|
||||
case class PluginDataChannelResetMsg(header: BbbClientMsgHeader, body: PluginDataChannelResetMsgBody) extends StandardMsg
|
||||
case class PluginDataChannelResetMsgBody(
|
||||
pluginName: String,
|
||||
dataChannel: String
|
||||
)
|
||||
|
@ -17,7 +17,7 @@ case class MakePresentationDownloadReqMsgBody(presId: String, allPages: Boolean,
|
||||
object NewPresFileAvailableMsg { val NAME = "NewPresFileAvailableMsg" }
|
||||
case class NewPresFileAvailableMsg(header: BbbClientMsgHeader, body: NewPresFileAvailableMsgBody) extends StandardMsg
|
||||
case class NewPresFileAvailableMsgBody(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String,
|
||||
presId: String, fileStateType: String)
|
||||
presId: String, fileStateType: String, fileName: String)
|
||||
|
||||
object PresAnnStatusMsg { val NAME = "PresAnnStatusMsg" }
|
||||
case class PresAnnStatusMsg(header: BbbClientMsgHeader, body: PresAnnStatusMsgBody) extends StandardMsg
|
||||
|
@ -235,37 +235,57 @@ case class DeletedRecordingSysMsgBody(recordId: String)
|
||||
/**
|
||||
* Sent from akka-apps to graphql-middleware
|
||||
*/
|
||||
object InvalidateUserGraphqlConnectionSysMsg { val NAME = "InvalidateUserGraphqlConnectionSysMsg" }
|
||||
case class InvalidateUserGraphqlConnectionSysMsg(
|
||||
object CheckGraphqlMiddlewareAlivePingSysMsg { val NAME = "CheckGraphqlMiddlewareAlivePingSysMsg" }
|
||||
case class CheckGraphqlMiddlewareAlivePingSysMsg(
|
||||
header: BbbCoreHeaderWithMeetingId,
|
||||
body: InvalidateUserGraphqlConnectionSysMsgBody
|
||||
body: CheckGraphqlMiddlewareAlivePingSysMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class InvalidateUserGraphqlConnectionSysMsgBody(meetingId: String, userId: String, sessionToken: String, reason: String)
|
||||
case class CheckGraphqlMiddlewareAlivePingSysMsgBody(middlewareUID: String)
|
||||
|
||||
/**
|
||||
* Sent from graphql-middleware to akka-apps
|
||||
*/
|
||||
object CheckGraphqlMiddlewareAlivePongSysMsg { val NAME = "CheckGraphqlMiddlewareAlivePongSysMsg" }
|
||||
case class CheckGraphqlMiddlewareAlivePongSysMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: CheckGraphqlMiddlewareAlivePongSysMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class CheckGraphqlMiddlewareAlivePongSysMsgBody(middlewareUID: String)
|
||||
|
||||
/**
|
||||
* Sent from akka-apps to graphql-middleware
|
||||
*/
|
||||
object ForceUserGraphqlReconnectionSysMsg { val NAME = "ForceUserGraphqlReconnectionSysMsg" }
|
||||
case class ForceUserGraphqlReconnectionSysMsg(
|
||||
header: BbbCoreHeaderWithMeetingId,
|
||||
body: ForceUserGraphqlReconnectionSysMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class ForceUserGraphqlReconnectionSysMsgBody(meetingId: String, userId: String, sessionToken: String, reason: String)
|
||||
|
||||
/**
|
||||
* Sent from graphql-middleware to akka-apps
|
||||
*/
|
||||
|
||||
object UserGraphqlConnectionInvalidatedEvtMsg { val NAME = "UserGraphqlConnectionInvalidatedEvtMsg" }
|
||||
case class UserGraphqlConnectionInvalidatedEvtMsg(
|
||||
object UserGraphqlReconnectionForcedEvtMsg { val NAME = "UserGraphqlReconnectionForcedEvtMsg" }
|
||||
case class UserGraphqlReconnectionForcedEvtMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: UserGraphqlConnectionInvalidatedEvtMsgBody
|
||||
body: UserGraphqlReconnectionForcedEvtMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class UserGraphqlConnectionInvalidatedEvtMsgBody(sessionToken: String, browserConnectionId: String)
|
||||
case class UserGraphqlReconnectionForcedEvtMsgBody(middlewareUID: String, sessionToken: String, browserConnectionId: String)
|
||||
|
||||
object UserGraphqlConnectionStablishedSysMsg { val NAME = "UserGraphqlConnectionStablishedSysMsg" }
|
||||
case class UserGraphqlConnectionStablishedSysMsg(
|
||||
object UserGraphqlConnectionEstablishedSysMsg { val NAME = "UserGraphqlConnectionEstablishedSysMsg" }
|
||||
case class UserGraphqlConnectionEstablishedSysMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: UserGraphqlConnectionStablishedSysMsgBody
|
||||
body: UserGraphqlConnectionEstablishedSysMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class UserGraphqlConnectionStablishedSysMsgBody(sessionToken: String, browserConnectionId: String)
|
||||
case class UserGraphqlConnectionEstablishedSysMsgBody(middlewareUID: String, sessionToken: String, browserConnectionId: String)
|
||||
|
||||
object UserGraphqlConnectionClosedSysMsg { val NAME = "UserGraphqlConnectionClosedSysMsg" }
|
||||
case class UserGraphqlConnectionClosedSysMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: UserGraphqlConnectionClosedSysMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class UserGraphqlConnectionClosedSysMsgBody(sessionToken: String, browserConnectionId: String)
|
||||
case class UserGraphqlConnectionClosedSysMsgBody(middlewareUID: String, sessionToken: String, browserConnectionId: String)
|
||||
|
||||
/**
|
||||
* Sent from akka-apps to bbb-web to inform a summary of the meeting activities
|
||||
|
@ -19,7 +19,7 @@ case class StartTimerReqMsgBody()
|
||||
|
||||
object StopTimerReqMsg { val NAME = "StopTimerReqMsg" }
|
||||
case class StopTimerReqMsg(header: BbbClientMsgHeader, body: StopTimerReqMsgBody) extends StandardMsg
|
||||
case class StopTimerReqMsgBody(accumulated: Int)
|
||||
case class StopTimerReqMsgBody()
|
||||
|
||||
object SwitchTimerReqMsg { val NAME = "SwitchTimerReqMsg" }
|
||||
case class SwitchTimerReqMsg(header: BbbClientMsgHeader, body: SwitchTimerReqMsgBody) extends StandardMsg
|
||||
|
@ -302,7 +302,7 @@ case class UserMobileFlagChangedEvtMsgBody(userId: String, mobile: Boolean)
|
||||
|
||||
object AssignPresenterReqMsg { val NAME = "AssignPresenterReqMsg" }
|
||||
case class AssignPresenterReqMsg(header: BbbClientMsgHeader, body: AssignPresenterReqMsgBody) extends StandardMsg
|
||||
case class AssignPresenterReqMsgBody(requesterId: String, newPresenterId: String, newPresenterName: String, assignedBy: String)
|
||||
case class AssignPresenterReqMsgBody(assignedBy: String, newPresenterId: String)
|
||||
|
||||
/**
|
||||
* Sent from client to change the video pin of the user in the meeting.
|
||||
|
@ -22,6 +22,7 @@ case class ExportJob(
|
||||
jobId: String,
|
||||
jobType: String,
|
||||
filename: String,
|
||||
serverSideFilename: String,
|
||||
presId: String,
|
||||
presLocation: String,
|
||||
allPages: Boolean,
|
||||
|
@ -103,7 +103,7 @@ homepage := Some(url("http://www.bigbluebutton.org"))
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
"javax.validation" % "validation-api" % "2.0.1.Final",
|
||||
"org.springframework.boot" % "spring-boot-starter-validation" % "2.7.12",
|
||||
"org.springframework.boot" % "spring-boot-starter-validation" % "2.7.17",
|
||||
"org.springframework.data" % "spring-data-commons" % "2.7.6",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.13",
|
||||
"org.postgresql" % "postgresql" % "42.4.3",
|
||||
|
@ -75,6 +75,7 @@ public class MeetingService implements MessageListener {
|
||||
*/
|
||||
private final ConcurrentMap<String, Meeting> meetings;
|
||||
private final ConcurrentMap<String, UserSession> sessions;
|
||||
private final ConcurrentMap<String, UserSessionBasicData> removedSessions;
|
||||
|
||||
private RecordingService recordingService;
|
||||
private LearningDashboardService learningDashboardService;
|
||||
@ -88,6 +89,7 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
private long usersTimeout;
|
||||
private long waitingGuestUsersTimeout;
|
||||
private int sessionsCleanupDelayInMinutes;
|
||||
private long enteredUsersTimeout;
|
||||
|
||||
private ParamsProcessorUtil paramsProcessorUtil;
|
||||
@ -100,6 +102,7 @@ public class MeetingService implements MessageListener {
|
||||
public MeetingService() {
|
||||
meetings = new ConcurrentHashMap<String, Meeting>(8, 0.9f, 1);
|
||||
sessions = new ConcurrentHashMap<String, UserSession>(8, 0.9f, 1);
|
||||
removedSessions = new ConcurrentHashMap<String, UserSessionBasicData>(8, 0.9f, 1);
|
||||
uploadAuthzTokens = new HashMap<String, PresentationUploadToken>();
|
||||
}
|
||||
|
||||
@ -149,12 +152,16 @@ public class MeetingService implements MessageListener {
|
||||
return null;
|
||||
}
|
||||
|
||||
public UserSession getUserSessionWithAuthToken(String token) {
|
||||
public UserSession getUserSessionWithSessionToken(String token) {
|
||||
return sessions.get(token);
|
||||
}
|
||||
|
||||
public UserSessionBasicData getRemovedUserSessionWithSessionToken(String sessionToken) {
|
||||
return removedSessions.get(sessionToken);
|
||||
}
|
||||
|
||||
public Boolean getAllowRequestsWithoutSession(String token) {
|
||||
UserSession us = getUserSessionWithAuthToken(token);
|
||||
UserSession us = getUserSessionWithSessionToken(token);
|
||||
if (us == null) {
|
||||
return false;
|
||||
} else {
|
||||
@ -164,12 +171,21 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
public UserSession removeUserSessionWithAuthToken(String token) {
|
||||
UserSession user = sessions.remove(token);
|
||||
if (user != null) {
|
||||
log.debug("Found user {} token={} to meeting {}", user.fullname, token, user.meetingID);
|
||||
public void removeUserSessionWithSessionToken(String token) {
|
||||
log.debug("Removing token={}", token);
|
||||
UserSession us = getUserSessionWithSessionToken(token);
|
||||
if (us != null) {
|
||||
log.debug("Found user {} token={} to meeting {}", us.fullname, token, us.meetingID);
|
||||
|
||||
UserSessionBasicData removedUser = new UserSessionBasicData();
|
||||
removedUser.meetingId = us.meetingID;
|
||||
removedUser.userId = us.internalUserId;
|
||||
removedUser.sessionToken = us.authToken;
|
||||
removedSessions.put(token, removedUser);
|
||||
sessions.remove(token);
|
||||
} else {
|
||||
log.debug("Not found token={}", token);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -295,16 +311,40 @@ public class MeetingService implements MessageListener {
|
||||
notifier.sendUploadFileTooLargeMessage(presUploadToken, uploadedFileSize, maxUploadFileSize);
|
||||
}
|
||||
|
||||
private void removeUserSessions(String meetingId) {
|
||||
Iterator<Map.Entry<String, UserSession>> iterator = sessions.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, UserSession> entry = iterator.next();
|
||||
UserSession userSession = entry.getValue();
|
||||
|
||||
private void removeUserSessionsFromMeeting(String meetingId) {
|
||||
for (String token : sessions.keySet()) {
|
||||
UserSession userSession = sessions.get(token);
|
||||
if (userSession.meetingID.equals(meetingId)) {
|
||||
iterator.remove();
|
||||
System.out.println(token + " = " + userSession.authToken);
|
||||
removeUserSessionWithSessionToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleRemovedSessionsCleanUp(meetingId);
|
||||
}
|
||||
|
||||
private void scheduleRemovedSessionsCleanUp(String meetingId) {
|
||||
Calendar cleanUpDelayCalendar = Calendar.getInstance();
|
||||
cleanUpDelayCalendar.add(Calendar.MINUTE, sessionsCleanupDelayInMinutes);
|
||||
|
||||
log.debug("Sessions for meeting={} will be removed within {} minutes.", meetingId, sessionsCleanupDelayInMinutes);
|
||||
new java.util.Timer().schedule(
|
||||
new java.util.TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Iterator<Map.Entry<String, UserSessionBasicData>> iterator = removedSessions.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, UserSessionBasicData> entry = iterator.next();
|
||||
UserSessionBasicData removedUserSession = entry.getValue();
|
||||
|
||||
if (removedUserSession.meetingId.equals(meetingId)) {
|
||||
log.debug("Removed user {} session for meeting {}.",removedUserSession.userId, removedUserSession.meetingId);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, cleanUpDelayCalendar.getTime()
|
||||
);
|
||||
}
|
||||
|
||||
private void destroyMeeting(String meetingId) {
|
||||
@ -411,8 +451,8 @@ public class MeetingService implements MessageListener {
|
||||
m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(),
|
||||
m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(),
|
||||
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
|
||||
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(),
|
||||
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
|
||||
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(), m.getLogoutUrl(), m.getCustomLogoURL(),
|
||||
m.getBannerText(), m.getBannerColor(), m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
|
||||
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(),
|
||||
m.getOverrideClientSettings());
|
||||
}
|
||||
@ -703,7 +743,7 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
destroyMeeting(m.getInternalId());
|
||||
meetings.remove(m.getInternalId());
|
||||
removeUserSessions(m.getInternalId());
|
||||
removeUserSessionsFromMeeting(m.getInternalId());
|
||||
|
||||
Map<String, Object> logData = new HashMap<>();
|
||||
logData.put("meetingId", m.getInternalId());
|
||||
@ -1111,7 +1151,7 @@ public class MeetingService implements MessageListener {
|
||||
user.setRole(message.role);
|
||||
String sessionToken = getTokenByUserId(user.getInternalUserId());
|
||||
if (sessionToken != null) {
|
||||
UserSession userSession = getUserSessionWithAuthToken(sessionToken);
|
||||
UserSession userSession = getUserSessionWithSessionToken(sessionToken);
|
||||
userSession.role = message.role;
|
||||
sessions.replace(sessionToken, userSession);
|
||||
}
|
||||
@ -1184,6 +1224,10 @@ public class MeetingService implements MessageListener {
|
||||
processGuestStatusChangedEventMsg((GuestStatusChangedEventMsg) message);
|
||||
} else if (message instanceof GuestPolicyChanged) {
|
||||
processGuestPolicyChanged((GuestPolicyChanged) message);
|
||||
} else if (message instanceof LockSettingsChanged) {
|
||||
processLockSettingsChanged((LockSettingsChanged) message);
|
||||
} else if (message instanceof WebcamsOnlyForModeratorChanged) {
|
||||
processWebcamsOnlyForModeratorChanged((WebcamsOnlyForModeratorChanged) message);
|
||||
} else if (message instanceof GuestLobbyMessageChanged) {
|
||||
processGuestLobbyMessageChanged((GuestLobbyMessageChanged) message);
|
||||
} else if (message instanceof PrivateGuestLobbyMessageChanged) {
|
||||
@ -1210,6 +1254,32 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
public void processLockSettingsChanged(LockSettingsChanged msg) {
|
||||
Meeting m = getMeeting(msg.meetingId);
|
||||
if (m != null) {
|
||||
m.setLockSettings(
|
||||
new LockSettingsParams(
|
||||
msg.disableCam,
|
||||
msg.disableMic,
|
||||
msg.disablePrivateChat,
|
||||
msg.disablePublicChat,
|
||||
msg.disableNotes,
|
||||
msg.hideUserList,
|
||||
msg.lockOnJoin,
|
||||
msg.lockOnJoinConfigurable,
|
||||
msg.hideViewersCursor,
|
||||
msg.hideViewersAnnotation)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void processWebcamsOnlyForModeratorChanged(WebcamsOnlyForModeratorChanged msg) {
|
||||
Meeting m = getMeeting(msg.meetingId);
|
||||
if (m != null) {
|
||||
m.setWebcamsOnlyForModerator(msg.webcamsOnlyForModerator);
|
||||
}
|
||||
}
|
||||
|
||||
public void processPositionInWaitingQueueUpdated(PositionInWaitingQueueUpdated msg) {
|
||||
Meeting m = getMeeting(msg.meetingId);
|
||||
HashMap<String,String> guestUsers = msg.guests;
|
||||
@ -1333,6 +1403,10 @@ public class MeetingService implements MessageListener {
|
||||
waitingGuestUsersTimeout = value;
|
||||
}
|
||||
|
||||
public void setSessionsCleanupDelayInMinutes(int value) {
|
||||
sessionsCleanupDelayInMinutes = value;
|
||||
}
|
||||
|
||||
public void setEnteredUsersTimeout(long value) {
|
||||
enteredUsersTimeout = value;
|
||||
}
|
||||
|
@ -70,6 +70,8 @@ public class ParamsProcessorUtil {
|
||||
private String defaultServerUrl;
|
||||
private int defaultNumDigitsForTelVoice;
|
||||
private String defaultHTML5ClientUrl;
|
||||
|
||||
private String graphqlWebsocketUrl;
|
||||
private String defaultGuestWaitURL;
|
||||
private Boolean allowRequestsWithoutSession = false;
|
||||
private Integer defaultHttpSessionTimeout = 14400;
|
||||
@ -864,6 +866,10 @@ public class ParamsProcessorUtil {
|
||||
return defaultHTML5ClientUrl;
|
||||
}
|
||||
|
||||
public String getGraphqlWebsocketUrl() {
|
||||
return graphqlWebsocketUrl;
|
||||
}
|
||||
|
||||
public String getDefaultGuestWaitURL() {
|
||||
return defaultGuestWaitURL;
|
||||
}
|
||||
@ -1217,6 +1223,10 @@ public class ParamsProcessorUtil {
|
||||
this.defaultHTML5ClientUrl = defaultHTML5ClientUrl;
|
||||
}
|
||||
|
||||
public void setGraphqlWebsocketUrl(String graphqlWebsocketUrl) {
|
||||
this.graphqlWebsocketUrl = graphqlWebsocketUrl.replace("https://","wss://");
|
||||
}
|
||||
|
||||
public void setDefaultGuestWaitURL(String url) {
|
||||
this.defaultGuestWaitURL = url;
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ public class Meeting {
|
||||
private Integer endWhenNoModeratorDelayInMinutes = 1;
|
||||
|
||||
public final BreakoutRoomsParams breakoutRoomsParams;
|
||||
public final LockSettingsParams lockSettingsParams;
|
||||
public LockSettingsParams lockSettingsParams;
|
||||
|
||||
public final Integer maxUserConcurrentAccesses;
|
||||
|
||||
@ -472,7 +472,15 @@ public class Meeting {
|
||||
}
|
||||
|
||||
public String getGuestPolicy() {
|
||||
return guestPolicy;
|
||||
return guestPolicy;
|
||||
}
|
||||
|
||||
public void setLockSettings(LockSettingsParams lockSettingsParams) {
|
||||
this.lockSettingsParams = lockSettingsParams;
|
||||
}
|
||||
|
||||
public void setWebcamsOnlyForModerator(Boolean webcamsOnlyForModerator) {
|
||||
this.webcamsOnlyForModerator = webcamsOnlyForModerator;
|
||||
}
|
||||
|
||||
public void setGuestLobbyMessage(String message) {
|
||||
|
@ -137,6 +137,10 @@ public class User {
|
||||
public boolean isModerator() {
|
||||
return "MODERATOR".equalsIgnoreCase(this.role);
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return this.locked;
|
||||
}
|
||||
|
||||
public void setStatus(String key, String value){
|
||||
this.status.put(key, value);
|
||||
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.api.domain;
|
||||
|
||||
public class UserSessionBasicData {
|
||||
public String sessionToken = null;
|
||||
public String userId = null;
|
||||
public String meetingId = null;
|
||||
|
||||
public String toString() {
|
||||
return meetingId + " " + userId + " " + sessionToken;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.bigbluebutton.api.messaging.messages;
|
||||
|
||||
public class LockSettingsChanged implements IMessage {
|
||||
|
||||
public final String meetingId;
|
||||
public final Boolean disableCam;
|
||||
public final Boolean disableMic;
|
||||
public final Boolean disablePrivateChat;
|
||||
public final Boolean disablePublicChat;
|
||||
public final Boolean disableNotes;
|
||||
public final Boolean hideUserList;
|
||||
public final Boolean lockOnJoin;
|
||||
public final Boolean lockOnJoinConfigurable;
|
||||
public final Boolean hideViewersCursor;
|
||||
public final Boolean hideViewersAnnotation;
|
||||
|
||||
public LockSettingsChanged(String meetingId,
|
||||
Boolean disableCam,
|
||||
Boolean disableMic,
|
||||
Boolean disablePrivateChat,
|
||||
Boolean disablePublicChat,
|
||||
Boolean disableNotes,
|
||||
Boolean hideUserList,
|
||||
Boolean lockOnJoin,
|
||||
Boolean lockOnJoinConfigurable,
|
||||
Boolean hideViewersCursor,
|
||||
Boolean hideViewersAnnotation) {
|
||||
this.meetingId = meetingId;
|
||||
this.disableCam = disableCam;
|
||||
this.disableMic = disableMic;
|
||||
this.disablePrivateChat = disablePrivateChat;
|
||||
this.disablePublicChat = disablePublicChat;
|
||||
this.disableNotes = disableNotes;
|
||||
this.hideUserList = hideUserList;
|
||||
this.lockOnJoin = lockOnJoin;
|
||||
this.lockOnJoinConfigurable = lockOnJoinConfigurable;
|
||||
this.hideViewersCursor = hideViewersCursor;
|
||||
this.hideViewersAnnotation = hideViewersAnnotation;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package org.bigbluebutton.api.messaging.messages;
|
||||
|
||||
public class WebcamsOnlyForModeratorChanged implements IMessage {
|
||||
public final String meetingId;
|
||||
public final Boolean webcamsOnlyForModerator;
|
||||
|
||||
public WebcamsOnlyForModeratorChanged(String meetingId, Boolean webcamsOnlyForModerator) {
|
||||
this.meetingId = meetingId;
|
||||
this.webcamsOnlyForModerator = webcamsOnlyForModerator;
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ public class GuestPolicyValidator implements ConstraintValidator<GuestPolicyCons
|
||||
}
|
||||
|
||||
MeetingService meetingService = ServiceUtils.getMeetingService();
|
||||
UserSession userSession = meetingService.getUserSessionWithAuthToken(sessionToken);
|
||||
UserSession userSession = meetingService.getUserSessionWithSessionToken(sessionToken);
|
||||
|
||||
if(userSession == null || !userSession.guestStatus.equals(GuestPolicy.ALLOW)) {
|
||||
return false;
|
||||
|
@ -19,7 +19,7 @@ public class UserSessionValidator implements ConstraintValidator<UserSessionCons
|
||||
return false;
|
||||
}
|
||||
|
||||
UserSession userSession = ServiceUtils.getMeetingService().getUserSessionWithAuthToken(sessionToken);
|
||||
UserSession userSession = ServiceUtils.getMeetingService().getUserSessionWithSessionToken(sessionToken);
|
||||
|
||||
if(userSession == null) {
|
||||
return false;
|
||||
|
@ -22,7 +22,7 @@ public class SessionService {
|
||||
|
||||
private void getUserSessionWithToken() {
|
||||
if(sessionToken != null) {
|
||||
userSession = meetingService.getUserSessionWithAuthToken(sessionToken);
|
||||
userSession = meetingService.getUserSessionWithSessionToken(sessionToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,9 @@ import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
@ -76,6 +79,11 @@ public class ValidationService {
|
||||
|
||||
if(request == null) {
|
||||
violations.put("validationError", "Request not recognized");
|
||||
} else if(params.containsKey("presentationUploadExternalUrl")) {
|
||||
String urlToValidate = params.get("presentationUploadExternalUrl")[0];
|
||||
if(!this.isValidURL(urlToValidate)) {
|
||||
violations.put("validationError", "Param 'presentationUploadExternalUrl' is not a valid URL");
|
||||
}
|
||||
} else {
|
||||
request.populateFromParamsMap(params);
|
||||
violations = performValidation(request);
|
||||
@ -84,6 +92,15 @@ public class ValidationService {
|
||||
return violations;
|
||||
}
|
||||
|
||||
boolean isValidURL(String url) {
|
||||
try {
|
||||
new URL(url).toURI();
|
||||
return true;
|
||||
} catch (MalformedURLException | URISyntaxException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Request initializeRequest(ApiCall apiCall, Map<String, String[]> params, String queryString) {
|
||||
Request request = null;
|
||||
Checksum checksum;
|
||||
|
@ -21,6 +21,10 @@ public class ParamsUtil {
|
||||
return text.replaceAll("\\p{Cc}", "").trim();
|
||||
}
|
||||
|
||||
public static String stripTags(String text) {
|
||||
return text.replaceAll("<[^>]*>", "");
|
||||
}
|
||||
|
||||
public static String escapeHTMLTags(String value) {
|
||||
return StringEscapeUtils.escapeHtml4(value);
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ public class ResponseBuilder {
|
||||
return new Date(timestamp).toString();
|
||||
}
|
||||
|
||||
public String buildMeetingVersion(String apiVersion, String bbbVersion, String returnCode) {
|
||||
public String buildMeetingVersion(String apiVersion, String bbbVersion, String graphqlWebsocketUrl, String returnCode) {
|
||||
StringWriter xmlText = new StringWriter();
|
||||
|
||||
Map<String, Object> data = new HashMap<String, Object>();
|
||||
@ -60,6 +60,7 @@ public class ResponseBuilder {
|
||||
data.put("version", apiVersion);
|
||||
data.put("apiVersion", apiVersion);
|
||||
data.put("bbbVersion", bbbVersion);
|
||||
data.put("graphqlWebsocketUrl", graphqlWebsocketUrl);
|
||||
|
||||
processData(getTemplate("api-version.ftlx"), data, xmlText);
|
||||
|
||||
|
@ -42,6 +42,10 @@ public interface IBbbWebApiGWApp {
|
||||
BreakoutRoomsParams breakoutParams,
|
||||
LockSettingsParams lockSettingsParams,
|
||||
Integer html5InstanceId,
|
||||
String logoutUrl,
|
||||
String customLogoURL,
|
||||
String bannerText,
|
||||
String bannerColor,
|
||||
ArrayList<Group> groups,
|
||||
ArrayList<String> disabledFeatures,
|
||||
Boolean notifyRecordingIsOn,
|
||||
|
@ -149,6 +149,10 @@ class BbbWebApiGWApp(
|
||||
breakoutParams: BreakoutRoomsParams,
|
||||
lockSettingsParams: LockSettingsParams,
|
||||
html5InstanceId: java.lang.Integer,
|
||||
logoutUrl: String,
|
||||
customLogoURL: String,
|
||||
bannerText: String,
|
||||
bannerColor: String,
|
||||
groups: java.util.ArrayList[Group],
|
||||
disabledFeatures: java.util.ArrayList[String],
|
||||
notifyRecordingIsOn: java.lang.Boolean,
|
||||
@ -230,7 +234,17 @@ class BbbWebApiGWApp(
|
||||
)
|
||||
|
||||
val systemProps = SystemProps(
|
||||
html5InstanceId
|
||||
html5InstanceId,
|
||||
logoutUrl,
|
||||
customLogoURL,
|
||||
bannerText match {
|
||||
case t: String => t
|
||||
case _ => ""
|
||||
},
|
||||
bannerColor match {
|
||||
case c: String => c
|
||||
case _ => ""
|
||||
},
|
||||
)
|
||||
|
||||
val groupsAsVector: Vector[GroupProps] = groups.asScala.toVector.map(g => GroupProps(g.getGroupId(), g.getName(), g.getUsersExtId().asScala.toVector))
|
||||
|
@ -100,6 +100,10 @@ class ReceivedJsonMsgHdlrActor(val msgFromAkkaAppsEventBus: MsgFromAkkaAppsEvent
|
||||
route[PosInWaitingQueueUpdatedRespMsg](envelope, jsonNode)
|
||||
case GuestPolicyChangedEvtMsg.NAME =>
|
||||
route[GuestPolicyChangedEvtMsg](envelope, jsonNode)
|
||||
case LockSettingsInMeetingChangedEvtMsg.NAME =>
|
||||
route[LockSettingsInMeetingChangedEvtMsg](envelope, jsonNode)
|
||||
case WebcamsOnlyForModeratorChangedEvtMsg.NAME =>
|
||||
route[WebcamsOnlyForModeratorChangedEvtMsg](envelope, jsonNode)
|
||||
case GuestLobbyMessageChangedEvtMsg.NAME =>
|
||||
route[GuestLobbyMessageChangedEvtMsg](envelope, jsonNode)
|
||||
case PrivateGuestLobbyMsgChangedEvtMsg.NAME =>
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.bigbluebutton.api2.meeting
|
||||
|
||||
import java.util
|
||||
|
||||
import org.apache.pekko.actor.{ Actor, ActorLogging, Props }
|
||||
import org.apache.pekko.actor.{Actor, ActorLogging, Props}
|
||||
import org.bigbluebutton.api.messaging.messages._
|
||||
import org.bigbluebutton.api2.bus.OldMessageReceivedGW
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
@ -41,6 +40,8 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW)
|
||||
case m: GuestsWaitingApprovedEvtMsg => handleGuestsWaitingApprovedEvtMsg(m)
|
||||
case m: PosInWaitingQueueUpdatedRespMsg => handlePosInWaitingQueueUpdatedRespMsg(m)
|
||||
case m: GuestPolicyChangedEvtMsg => handleGuestPolicyChangedEvtMsg(m)
|
||||
case m: LockSettingsInMeetingChangedEvtMsg => handleLockSettingsInMeetingChangedEvtMsg(m)
|
||||
case m: WebcamsOnlyForModeratorChangedEvtMsg => handleWebcamsOnlyForModeratorChangedEvtMsg(m)
|
||||
case m: GuestLobbyMessageChangedEvtMsg => handleGuestLobbyMessageChangedEvtMsg(m)
|
||||
case m: PrivateGuestLobbyMsgChangedEvtMsg => handlePrivateGuestLobbyMsgChangedEvtMsg(m)
|
||||
case m: RecordingChapterBreakSysMsg => handleRecordingChapterBreakSysMsg(m)
|
||||
@ -55,6 +56,25 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW)
|
||||
olgMsgGW.handle(new GuestPolicyChanged(msg.header.meetingId, msg.body.policy))
|
||||
}
|
||||
|
||||
def handleLockSettingsInMeetingChangedEvtMsg(msg: LockSettingsInMeetingChangedEvtMsg): Unit = {
|
||||
olgMsgGW.handle(new LockSettingsChanged(msg.header.meetingId,
|
||||
msg.body.disableCam,
|
||||
msg.body.disableMic,
|
||||
msg.body.disablePrivChat,
|
||||
msg.body.disablePubChat,
|
||||
msg.body.disableNotes,
|
||||
msg.body.hideUserList,
|
||||
msg.body.lockOnJoin,
|
||||
msg.body.lockOnJoinConfigurable,
|
||||
msg.body.hideViewersCursor,
|
||||
msg.body.hideViewersAnnotation,
|
||||
))
|
||||
}
|
||||
|
||||
def handleWebcamsOnlyForModeratorChangedEvtMsg(msg: WebcamsOnlyForModeratorChangedEvtMsg): Unit = {
|
||||
olgMsgGW.handle(new WebcamsOnlyForModeratorChanged(msg.header.meetingId, msg.body.webcamsOnlyForModerator))
|
||||
}
|
||||
|
||||
def handleGuestLobbyMessageChangedEvtMsg(msg: GuestLobbyMessageChangedEvtMsg): Unit = {
|
||||
olgMsgGW.handle(new GuestLobbyMessageChanged(msg.header.meetingId, msg.body.message))
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ class NewPresFileAvailableMsg {
|
||||
annotatedFileURI: link,
|
||||
originalFileURI: '',
|
||||
convertedFileURI: '',
|
||||
fileName: exportJob.filename,
|
||||
presId: exportJob.presId,
|
||||
fileStateType: 'Annotated',
|
||||
},
|
||||
|
60
bbb-export-annotations/package-lock.json
generated
60
bbb-export-annotations/package-lock.json
generated
@ -8,7 +8,7 @@
|
||||
"name": "bbb-export-annotations",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"axios": "^0.26.0",
|
||||
"axios": "^1.6.5",
|
||||
"form-data": "^4.0.0",
|
||||
"perfect-freehand": "^1.0.16",
|
||||
"probe-image-size": "^7.2.3",
|
||||
@ -21,8 +21,8 @@
|
||||
"eslint-config-google": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16",
|
||||
"npm": ">=8.5"
|
||||
"node": ">=18.16.0",
|
||||
"npm": ">=9.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
@ -287,11 +287,13 @@
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz",
|
||||
"integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
|
||||
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.8"
|
||||
"follow-redirects": "^1.15.4",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
@ -692,9 +694,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
|
||||
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@ -1067,6 +1069,11 @@
|
||||
"stream-parser": "~0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
@ -1319,9 +1326,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@ -1573,11 +1580,13 @@
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz",
|
||||
"integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
|
||||
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.8"
|
||||
"follow-redirects": "^1.15.4",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
@ -1881,9 +1890,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
|
||||
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
@ -2161,6 +2170,11 @@
|
||||
"stream-parser": "~0.3.1"
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
@ -2358,9 +2372,9 @@
|
||||
}
|
||||
},
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
|
@ -7,7 +7,7 @@
|
||||
"lint:fix": "eslint --fix **/*.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.26.0",
|
||||
"axios": "^1.6.5",
|
||||
"form-data": "^4.0.0",
|
||||
"perfect-freehand": "^1.0.16",
|
||||
"probe-image-size": "^7.2.3",
|
||||
@ -20,7 +20,7 @@
|
||||
"eslint-config-google": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.16.0",
|
||||
"npm": "^9.5.0"
|
||||
"node": ">=18.16.0",
|
||||
"npm": ">=9.5.0"
|
||||
}
|
||||
}
|
||||
|
@ -129,9 +129,9 @@ async function collectSharedNotes(retries = 3) {
|
||||
const padId = exportJob.presId;
|
||||
const notesFormat = 'pdf';
|
||||
|
||||
const filename = `${sanitize(exportJob.filename.replace(/\s/g, '_'))}.${notesFormat}`;
|
||||
const serverSideFilename = `${sanitize(exportJob.serverSideFilename.replace(/\s/g, '_'))}.${notesFormat}`;
|
||||
const notes_endpoint = `${config.bbbPadsAPI}/p/${padId}/export/${notesFormat}`;
|
||||
const filePath = path.join(dropbox, filename);
|
||||
const filePath = path.join(dropbox, serverSideFilename);
|
||||
|
||||
const finishedDownload = promisify(stream.finished);
|
||||
const writer = fs.createWriteStream(filePath);
|
||||
@ -157,7 +157,7 @@ async function collectSharedNotes(retries = 3) {
|
||||
}
|
||||
}
|
||||
|
||||
const notifier = new WorkerStarter({jobType, jobId, filename});
|
||||
const notifier = new WorkerStarter({jobType, jobId, serverSideFilename, filename: exportJob.filename});
|
||||
notifier.notify();
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ const path = require('path');
|
||||
const {NewPresFileAvailableMsg} = require('../lib/utils/message-builder');
|
||||
|
||||
const {workerData} = require('worker_threads');
|
||||
const [jobType, jobId, filename] = [workerData.jobType, workerData.jobId, workerData.filename];
|
||||
const [jobType, jobId, serverSideFilename] = [workerData.jobType, workerData.jobId, workerData.serverSideFilename];
|
||||
|
||||
const logger = new Logger('presAnn Notifier Worker');
|
||||
|
||||
@ -30,13 +30,14 @@ async function notifyMeetingActor() {
|
||||
|
||||
const link = path.join('presentation',
|
||||
exportJob.parentMeetingId, exportJob.parentMeetingId,
|
||||
exportJob.presId, 'pdf', jobId, filename);
|
||||
exportJob.presId, 'pdf', jobId, serverSideFilename);
|
||||
|
||||
const notification = new NewPresFileAvailableMsg(exportJob, link);
|
||||
|
||||
logger.info(`Annotated PDF available at ${link}`);
|
||||
await client.publish(config.redis.channels.publish, notification.build());
|
||||
client.disconnect();
|
||||
|
||||
}
|
||||
|
||||
/** Upload PDF to a BBB room
|
||||
@ -63,10 +64,10 @@ async function upload(filePath) {
|
||||
if (jobType == 'PresentationWithAnnotationDownloadJob') {
|
||||
notifyMeetingActor();
|
||||
} else if (jobType == 'PresentationWithAnnotationExportJob') {
|
||||
const filePath = `${exportJob.presLocation}/pdfs/${jobId}/${filename}`;
|
||||
const filePath = `${exportJob.presLocation}/pdfs/${jobId}/${serverSideFilename}`;
|
||||
upload(filePath);
|
||||
} else if (jobType == 'PadCaptureJob') {
|
||||
const filePath = `${dropbox}/${filename}`;
|
||||
const filePath = `${dropbox}/${serverSideFilename}`;
|
||||
upload(filePath);
|
||||
} else {
|
||||
logger.error(`Notifier received unknown job type ${jobType}`);
|
||||
|
@ -886,7 +886,7 @@ async function process_presentation_annotations() {
|
||||
fs.mkdirSync(outputDir, {recursive: true});
|
||||
}
|
||||
|
||||
const filename_with_extension = `${sanitize(exportJob.filename.replace(/\s/g, '_'))}.pdf`;
|
||||
const filename_with_extension = `${sanitize(exportJob.serverSideFilename.replace(/\s/g, '_'))}.pdf`;
|
||||
|
||||
const mergePDFs = [
|
||||
'-dNOPAUSE',
|
||||
@ -904,7 +904,8 @@ async function process_presentation_annotations() {
|
||||
// Launch Notifier Worker depending on job type
|
||||
logger.info(`Saved PDF at ${outputDir}/${jobId}/${filename_with_extension}`);
|
||||
|
||||
const notifier = new WorkerStarter({jobType: exportJob.jobType, jobId, filename: filename_with_extension});
|
||||
const notifier = new WorkerStarter({jobType: exportJob.jobType, jobId,
|
||||
serverSideFilename: filename_with_extension, filename: exportJob.filename});
|
||||
notifier.notify();
|
||||
await client.disconnect();
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user