Merge branch 'v3.0.x-release' into docs-layout

This commit is contained in:
Anton Georgiev 2024-05-09 22:13:38 -04:00 committed by GitHub
commit a441d70226
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1215 changed files with 26982 additions and 29972 deletions

View File

@ -6,7 +6,7 @@ runs:
using: "composite"
steps:
- name: Checkout ${{ github.event.pull_request.base.ref || 'master' }}
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref || '' }}
fetch-depth: 0 # Fetch all history

View File

@ -75,7 +75,7 @@ jobs:
- package: others
build-list: bbb-mkclean bbb-pads bbb-libreoffice-docker bbb-transcription-controller bigbluebutton bbb-livekit
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Merge branches
uses: ./.github/actions/merge-branches
- name: Set cache-key vars
@ -90,7 +90,7 @@ jobs:
- name: Handle cache
if: matrix.cache-files-list != ''
id: cache-action
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: artifacts.tar
key: ${{ runner.os }}-${{ matrix.package }}-${{ env.BIGBLUEBUTTON_RELEASE }}-commits-${{ env.CACHE_KEY_FILES }}-urls-${{ env.CACHE_KEY_URLS }}
@ -102,7 +102,7 @@ jobs:
echo "${{ matrix.build-list || matrix.package }}" | xargs -n 1 ./build/setup.sh
tar cvf artifacts.tar artifacts/
- name: Archive packages
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: artifacts_${{ matrix.package }}.tar
path: artifacts.tar
@ -112,74 +112,76 @@ jobs:
strategy:
fail-fast: false
matrix:
shard: [1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8, 8/8]
shard: [1, 2, 3, 4, 5, 6, 7, 8]
env:
shard: ${{ matrix.shard }}/8
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Merge branches
uses: ./.github/actions/merge-branches
- run: ./build/get_external_dependencies.sh
- name: Download artifacts_bbb-apps-akka
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-apps-akka.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-config
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-config.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-export-annotations
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-export-annotations.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-learning-dashboard
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-learning-dashboard.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-playback-record
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-playback-record.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-graphql-server
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-graphql-server.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-etherpad
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-etherpad.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-freeswitch
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-freeswitch.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-webrtc
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-webrtc.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-web
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-web.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-fsesl-akka
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-fsesl-akka.tar
- run: tar xf artifacts.tar
- name: Download artifacts_bbb-html5
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_bbb-html5.tar
- run: tar xf artifacts.tar
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifacts_others.tar
- run: tar xf artifacts.tar
@ -253,14 +255,19 @@ jobs:
apt --purge -y remove apache2-bin
'
- name: Install BBB
timeout-minutes: 25
run: |
uses: nick-fields/retry@v3
env:
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
ACTIONS_RUNNER_DEBUG: true
with:
timeout_minutes: 25
max_attempts: 2
command: |
sudo -i <<EOF
set -e
cd /root/ && wget -nv https://raw.githubusercontent.com/bigbluebutton/bbb-install/v3.0.x-release/bbb-install.sh -O bbb-install.sh
cat bbb-install.sh | sed "s|> /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v jammy-30-dev -s bbb-ci.test -j -d /certs/
bbb-conf --salt bbbci
echo "NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt" >> /usr/share/meteor/bundle/bbb-html5-with-roles.conf
sed -i "s/\"minify\": true,/\"minify\": false,/" /usr/share/etherpad-lite/settings.json
bbb-conf --restart
EOF
@ -279,13 +286,13 @@ jobs:
npx playwright install
'
- name: Run tests
uses: nick-fields/retry@v2
uses: nick-fields/retry@v3
with:
timeout_minutes: 25
max_attempts: 3
max_attempts: 2
command: |
cd ./bigbluebutton-tests/playwright
npm run test-chromium-ci -- --shard ${{ matrix.shard }}
npm run test-chromium-ci -- --shard ${{ env.shard }}
env:
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
ACTIONS_RUNNER_DEBUG: true
@ -305,13 +312,13 @@ jobs:
run: |
sh -c '
find $HOME/.cache/ms-playwright -name libnssckbi.so -exec rm {} \; -exec ln -s /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so {} \;
npm run test-firefox-ci -- --shard ${{ matrix.shard }}
npm run test-firefox-ci -- --shard ${{ env.shard }}
'
- if: always() && github.event_name == 'pull_request'
name: Upload blob report to GitHub Actions Artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: all-blob-reports
name: blob-report-${{ matrix.shard }}
path: bigbluebutton-tests/playwright/blob-report
- if: failure()
name: Prepare artifacts (configs and logs)
@ -337,42 +344,55 @@ jobs:
chmod a+r -R /home/runner/work/bigbluebutton/bigbluebutton/configs
bbb-conf --zip
ls -t /root/*.tar.gz | head -1 | xargs -I '{}' cp '{}' /home/runner/work/bigbluebutton/bigbluebutton/bbb-logs.tar.gz
echo "MATRIX_SHARD=$(echo ${{ matrix.shard }} | tr '/' '_')" >> $GITHUB_ENV
echo "MATRIX_SHARD=${{ matrix.shard }}_8" >> $GITHUB_ENV
EOF
- if: failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: bbb-configs-${{ env.MATRIX_SHARD }}
path: configs
- if: failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: bbb-logs-${{ env.MATRIX_SHARD }}
path: ./bbb-logs.tar.gz
upload-report:
if: always()
if: always() && !contains(github.event.head_commit.message, 'Merge pull request')
needs: install-and-run-tests
runs-on: ubuntu-latest
env:
hasReportData: ${{ needs.install-and-run-tests.result == 'success' || needs.install-and-run-tests.result == 'failure' }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Install dependencies
if: ${{ env.hasReportData }}
working-directory: ./bigbluebutton-tests/playwright
run: npm ci
- name: Merge artifacts
uses: actions/upload-artifact/merge@v4
with:
name: all-blob-reports
pattern: blob-report-*
delete-merged: true
- name: Download all blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v3
if: ${{ env.hasReportData }}
uses: actions/download-artifact@v4
with:
name: all-blob-reports
path: bigbluebutton-tests/playwright/all-blob-reports
- name: Merge into HTML Report
if: ${{ env.hasReportData }}
working-directory: ./bigbluebutton-tests/playwright
run: npx playwright merge-reports --reporter html ./all-blob-reports
- name: Upload HTML tests report
uses: actions/upload-artifact@v3
if: ${{ env.hasReportData }}
uses: actions/upload-artifact@v4
with:
name: tests-report
overwrite: true
path: |
bigbluebutton-tests/playwright/playwright-report
bigbluebutton-tests/playwright/test-results
@ -385,7 +405,7 @@ jobs:
echo ${{ github.run_id }} > ./pr-comment-data/workflow_id
- name: Upload PR data for auto-comment
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: pr-comment-data
path: pr-comment-data

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check for dirty pull requests
uses: eps1lon/actions-label-merge-conflict@releases/2.x
uses: eps1lon/actions-label-merge-conflict@v3
with:
dirtyLabel: "status: conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"

View File

@ -22,8 +22,8 @@ jobs:
working-directory: ./docs
steps:
# Setup
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: yarn

View File

@ -23,7 +23,7 @@ jobs:
ts-code-compilation:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Merge branches

View File

@ -23,7 +23,7 @@ jobs:
ts-code-validation:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Merge branches

1
.gitignore vendored
View File

@ -24,3 +24,4 @@ artifacts/*
bbb-presentation-video.zip
bbb-presentation-video
bbb-graphql-actions-adapter-server/
bigbluebutton-html5/public/locales/index.json

View File

@ -5,7 +5,7 @@ import org.slf4j.LoggerFactory
import java.io.{ ByteArrayInputStream, File }
import scala.io.BufferedSource
import scala.util.{ Failure, Success, Try }
import scala.util.{ Failure, Success }
object ClientSettings extends SystemConfiguration {
var clientSettingsFromFile: Map[String, Object] = Map("" -> "")
@ -82,6 +82,24 @@ object ClientSettings extends SystemConfiguration {
}
}
def getConfigPropertyValueByPathAsListOfStringOrElse(map: Map[String, Any], path: String, alternativeValue: List[String]): List[String] = {
getConfigPropertyValueByPath(map, path) match {
case Some(configValue: List[String]) => configValue
case _ =>
logger.debug(s"Config `$path` with type List[String] not found in clientSettings.")
alternativeValue
}
}
def getConfigPropertyValueByPathAsListOfIntOrElse(map: Map[String, Any], path: String, alternativeValue: List[Int]): List[Int] = {
getConfigPropertyValueByPath(map, path) match {
case Some(configValue: List[Int]) => configValue
case _ =>
logger.debug(s"Config `$path` with type List[Int] not found in clientSettings.")
alternativeValue
}
}
def getConfigPropertyValueByPath(map: Map[String, Any], path: String): Option[Any] = {
val keys = path.split("\\.")

View File

@ -14,6 +14,7 @@ import org.bigbluebutton.SystemConfiguration
import java.util.concurrent.TimeUnit
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.db.{ DatabaseConnection, MeetingDAO }
import org.bigbluebutton.core.domain.MeetingEndReason
import org.bigbluebutton.core.running.RunningMeeting
import org.bigbluebutton.core.util.ColorPicker
import org.bigbluebutton.core2.RunningMeetings
@ -57,6 +58,9 @@ class BigBlueButtonActor(
override def preStart() {
bbbMsgBus.subscribe(self, meetingManagerChannel)
DatabaseConnection.initialize()
//Terminate all previous meetings, as they will not function following the akka-apps restart
MeetingDAO.setAllMeetingsEnded(MeetingEndReason.ENDED_DUE_TO_SERVICE_INTERRUPTION, "system")
}
override def postStop() {
@ -150,7 +154,7 @@ class BigBlueButtonActor(
}
private def handleGetAllMeetingsReqMsg(msg: GetAllMeetingsReqMsg): Unit = {
RunningMeetings.meetings(meetings).filter(_.props.systemProps.html5InstanceId == msg.body.html5InstanceId).foreach(m => {
RunningMeetings.meetings(meetings).foreach(m => {
m.actorRef ! msg
})
}

View File

@ -120,13 +120,6 @@ case class CapturePresentationReqInternalMsg(userId: String, parentMeetingId: St
*/
case class SetPresenterInDefaultPodInternalMsg(presenterId: String) extends InMessage
/**
* Sent by breakout room to parent meeting to obtain padId
* @param breakoutId
* @param filename
*/
case class CaptureSharedNotesReqInternalMsg(breakoutId: String, filename: String) extends InMessage
/**
* Sent by GraphqlActionsActor to inform MeetingActor that user disconnected
* @param userId
@ -138,10 +131,3 @@ case class UserClosedAllGraphqlConnectionsInternalMsg(userId: String) extends In
* @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
case class DeskShareRTMPBroadcastStartedRequest(conferenceName: String, streamname: String, videoWidth: Int, videoHeight: Int, timestamp: String) extends InMessage
case class DeskShareRTMPBroadcastStoppedRequest(conferenceName: String, streamname: String, videoWidth: Int, videoHeight: Int, timestamp: String) extends InMessage
case class DeskShareGetDeskShareInfoRequest(conferenceName: String, requesterID: String, replyTo: String) extends InMessage

View File

@ -46,7 +46,7 @@ class WhiteboardModel extends SystemConfiguration {
k -> newValue
}).toMap
def addAnnotations(wbId: String, userId: String, annotations: Array[AnnotationVO], isPresenter: Boolean, isModerator: Boolean): Array[AnnotationVO] = {
def addAnnotations(wbId: String, meetingId: String, userId: String, annotations: Array[AnnotationVO], isPresenter: Boolean, isModerator: Boolean): Array[AnnotationVO] = {
val wb = getWhiteboard(wbId)
var annotationsAdded = Array[AnnotationVO]()
@ -70,7 +70,6 @@ class WhiteboardModel extends SystemConfiguration {
val newAnnotation = oldAnnotation.get.copy(annotationInfo = finalAnnotationInfo)
newAnnotationsMap += (annotation.id -> newAnnotation)
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...")
@ -78,13 +77,14 @@ class WhiteboardModel extends SystemConfiguration {
} else if (annotation.annotationInfo.contains("type")) {
newAnnotationsMap += (annotation.id -> annotation)
annotationsAdded :+= annotation
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...")
}
}
PresAnnotationDAO.insertOrUpdateMap(meetingId, annotationsAdded)
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(newWb)
annotationsAdded
@ -106,7 +106,7 @@ class WhiteboardModel extends SystemConfiguration {
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 Some("point") => props - ("boundShapeId", "normalizedAnchor", "isExact", "isPrecise") // Remove unwanted properties for 'point' type
case _ => props
}
}
@ -116,7 +116,7 @@ class WhiteboardModel extends SystemConfiguration {
wb.annotationsMap.values.toArray
}
def deleteAnnotations(wbId: String, userId: String, annotationsIds: Array[String], isPresenter: Boolean, isModerator: Boolean): Array[String] = {
def deleteAnnotations(wbId: String, meetingId: String, userId: String, annotationsIds: Array[String], isPresenter: Boolean, isModerator: Boolean): Array[String] = {
val wb = getWhiteboard(wbId)
var annotationsIdsRemoved = Array[String]()
@ -143,15 +143,15 @@ class WhiteboardModel extends SystemConfiguration {
val updatedWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(updatedWb)
annotationsIdsRemoved.map(PresAnnotationDAO.delete(wbId, userId, _))
PresAnnotationDAO.delete(meetingId, userId, annotationsIdsRemoved)
annotationsIdsRemoved
}
def modifyWhiteboardAccess(wbId: String, multiUser: Array[String]) {
def modifyWhiteboardAccess(meetingId: String, wbId: String, multiUser: Array[String]) {
val wb = getWhiteboard(wbId)
val newWb = wb.copy(multiUser = multiUser, oldMultiUser = wb.multiUser, changedModeOn = System.currentTimeMillis())
PresPageWritersDAO.updateMultiuser(newWb)
PresPageWritersDAO.updateMultiuser(meetingId, newWb)
saveWhiteboard(newWb)
}

View File

@ -4,6 +4,7 @@ import org.apache.pekko.actor.ActorContext
class AudioCaptionsApp2x(implicit val context: ActorContext)
extends UpdateTranscriptPubMsgHdlr
with TranscriptionProviderErrorMsgHdlr
with AudioFloorChangedVoiceConfEvtMsgHdlr {
}

View File

@ -0,0 +1,31 @@
package org.bigbluebutton.core.apps.audiocaptions
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.UserTranscriptionErrorDAO
import org.bigbluebutton.core.models.AudioCaptions
import org.bigbluebutton.core.running.LiveMeeting
trait TranscriptionProviderErrorMsgHdlr {
this: AudioCaptionsApp2x =>
def handleTranscriptionProviderErrorMsg(msg: TranscriptionProviderErrorMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val meetingId = liveMeeting.props.meetingProp.intId
def broadcastEvent(userId: String, errorCode: String, errorMessage: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp")
val envelope = BbbCoreEnvelope(TranscriptionProviderErrorEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(TranscriptionProviderErrorEvtMsg.NAME, meetingId, userId)
val body = TranscriptionProviderErrorEvtMsgBody(errorCode, errorMessage)
val event = TranscriptionProviderErrorEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
broadcastEvent(msg.header.userId, msg.body.errorCode, msg.body.errorMessage)
UserTranscriptionErrorDAO.insert(msg.header.userId, msg.header.meetingId, msg.body.errorCode, msg.body.errorMessage)
}
}

View File

@ -1,12 +1,13 @@
package org.bigbluebutton.core.apps.audiocaptions
import org.bigbluebutton.ClientSettings.getConfigPropertyValueByPathAsStringOrElse
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.CaptionDAO
import org.bigbluebutton.core.models.{AudioCaptions, Users2x}
import org.bigbluebutton.core.models.{AudioCaptions, UserState, Users2x}
import org.bigbluebutton.core.running.LiveMeeting
import java.sql.Timestamp
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
trait UpdateTranscriptPubMsgHdlr {
this: AudioCaptionsApp2x =>
@ -25,6 +26,17 @@ trait UpdateTranscriptPubMsgHdlr {
bus.outGW.send(msgEvent)
}
def sendPadUpdatePubMsg(userId: String, defaultPad: String, text: String, transcript: Boolean): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp")
val envelope = BbbCoreEnvelope(PadUpdatePubMsg.NAME, routing)
val header = BbbClientMsgHeader(PadUpdatePubMsg.NAME, meetingId, userId)
val body = PadUpdatePubMsgBody(defaultPad, text, transcript)
val event = PadUpdatePubMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
// Adapt to the current captions' recording process
def editTranscript(
userId: String,
@ -80,6 +92,28 @@ trait UpdateTranscriptPubMsgHdlr {
msg.body.locale,
msg.body.result,
)
if(msg.body.result) {
val userName = Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId).get match {
case u: UserState => u.name
case _ => "???"
}
val now = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("HH:mm:ss")
val formattedTime = now.format(formatter)
val userSpoke = s"\n $userName ($formattedTime): $transcript"
val defaultPad = getConfigPropertyValueByPathAsStringOrElse(
liveMeeting.clientSettings,
"public.captions.defaultPad",
alternativeValue = ""
)
sendPadUpdatePubMsg(msg.header.userId, defaultPad, userSpoke, transcript = true)
}
}
}
}

View File

@ -1,7 +1,7 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.BreakoutRoomEndedInternalMsg
import org.bigbluebutton.core.db.BreakoutRoomDAO
import org.bigbluebutton.core.db.{ BreakoutRoomDAO, NotificationDAO }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
@ -37,6 +37,7 @@ trait BreakoutRoomEndedInternalMsgHdlr {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
BreakoutRoomDAO.updateRoomsEnded(liveMeeting.props.meetingProp.intId)
state.update(None)

View File

@ -53,8 +53,8 @@ trait BreakoutRoomUsersUpdateMsgHdlr {
breakoutRoomUser <- updatedRoom.users
u <- RegisteredUsers.findWithBreakoutRoomId(breakoutRoomUser.id, liveMeeting.registeredUsers)
} yield u.id
UserBreakoutRoomDAO.updateLastBreakoutRoom(usersInRoom, updatedRoom)
BreakoutRoomUserDAO.updateUserJoined(usersInRoom, updatedRoom)
UserBreakoutRoomDAO.updateLastBreakoutRoom(props.meetingProp.intId, usersInRoom, updatedRoom)
BreakoutRoomUserDAO.updateUserJoined(props.meetingProp.intId, usersInRoom, updatedRoom)
model.update(updatedRoom)
}

View File

@ -5,7 +5,7 @@ import org.bigbluebutton.core.api.EjectUserFromBreakoutInternalMsg
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers.getRedirectUrls
import org.bigbluebutton.core.apps.{PermissionCheck, RightsManagementTrait}
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.db.BreakoutRoomUserDAO
import org.bigbluebutton.core.db.{BreakoutRoomUserDAO, NotificationDAO}
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.EjectReasonCode
import org.bigbluebutton.core.running.{MeetingActor, OutMsgRouter}
@ -57,7 +57,7 @@ trait ChangeUserBreakoutReqMsgHdlr extends RightsManagementTrait {
)
//Update database
BreakoutRoomUserDAO.updateRoomChanged(msg.body.userId, msg.body.fromBreakoutId, msg.body.toBreakoutId, redirectToHtml5JoinURL)
BreakoutRoomUserDAO.updateRoomChanged(meetingId, msg.body.userId, msg.body.fromBreakoutId, msg.body.toBreakoutId, redirectToHtml5JoinURL)
//Send notification to moved User
@ -75,6 +75,7 @@ trait ChangeUserBreakoutReqMsgHdlr extends RightsManagementTrait {
Vector(roomTo.shortName)
)
outGW.send(notifyUserEvent)
NotificationDAO.insert(notifyUserEvent)
}
}

View File

@ -30,7 +30,7 @@ trait EjectUserFromBreakoutInternalMsgHdlr {
)
//TODO inform reason
UserDAO.softDelete(registeredUser.id)
UserDAO.softDelete(registeredUser.meetingId, registeredUser.id)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(msg.breakoutId, registeredUser.id, msg.ejectedBy, msg.reasonCode, outGW)

View File

@ -6,9 +6,8 @@ import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.domain.{ MeetingEndReason, MeetingState2x }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.UserBreakoutRoomDAO
import org.bigbluebutton.core.db.{ BreakoutRoomDAO, NotificationDAO, UserBreakoutRoomDAO }
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.core.db.BreakoutRoomDAO
trait EndAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
this: MeetingActor =>
@ -22,13 +21,7 @@ trait EndAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
state
} else {
for {
model <- state.breakout
} yield {
model.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(meetingId, room.id, MeetingEndReason.BREAKOUT_ENDED_BY_MOD)))
UserBreakoutRoomDAO.updateLastBreakoutRoom(Vector(), room)
}
endAllBreakoutRooms(eventBus, liveMeeting, state, MeetingEndReason.BREAKOUT_ENDED_BY_MOD)
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
meetingId,
@ -39,7 +32,8 @@ trait EndAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
}
NotificationDAO.insert(notifyEvent)
BreakoutRoomDAO.updateRoomsEnded(meetingId)
state.update(None)
}

View File

@ -1,7 +1,10 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.{ CaptureSharedNotesReqInternalMsg, CapturePresentationReqInternalMsg, EndBreakoutRoomInternalMsg }
import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, ExportJob, MessageTypes, PresentationConversionUpdateEvtMsg, PresentationConversionUpdateEvtMsgBody, PresentationConversionUpdateSysPubMsg, PresentationUploadTokenSysPubMsg, PresentationUploadTokenSysPubMsgBody, Routing, StoreExportJobInRedisSysMsg, StoreExportJobInRedisSysMsgBody }
import org.bigbluebutton.core.api.{ CapturePresentationReqInternalMsg, EndBreakoutRoomInternalMsg }
import org.bigbluebutton.core.apps.presentationpod.PresentationPodsApp
import org.bigbluebutton.core.bus.{ BigBlueButtonEvent, InternalEventBus }
import org.bigbluebutton.core.models.Pads
import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
@ -19,12 +22,68 @@ trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
}
if (liveMeeting.props.breakoutProps.captureNotes) {
for {
group <- Pads.getGroup(liveMeeting.pads, "notes")
} yield {
val filename = liveMeeting.props.breakoutProps.captureNotesFilename
val captureNotesEvent = BigBlueButtonEvent(msg.parentId, CaptureSharedNotesReqInternalMsg(msg.breakoutId, filename))
eventBus.publish(captureNotesEvent)
val userId: String = "system"
val jobId: String = s"${msg.breakoutId}-notes" // Used as the temporaryPresentationId upon upload
val presentationId = PresentationPodsApp.generatePresentationId(filename)
if (group.rev > 0) {
//Request upload of the sharedNotes of breakoutRoom
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId)
outGW.send(buildPresentationUploadTokenSysPubMsg(msg.parentId, userId, presentationUploadToken, filename, presentationId))
val exportJob = ExportJob(jobId, "PadCaptureJob", filename, filename, group.padId, "", allPages = true, List(), msg.parentId, presentationUploadToken)
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
outGW.send(job)
} else {
// Notify that no content is available
val event = buildPresentationConversionUpdateEvtMsg(msg.parentId, presentationId, filename, jobId)
outGW.send(event)
}
}
}
log.info("Breakout room {} ended by parent meeting {}.", msg.breakoutId, msg.parentId)
sendEndMeetingDueToExpiry(msg.reason, eventBus, outGW, liveMeeting, "system")
}
def buildStoreExportJobInRedisSysMsg(exportJob: ExportJob, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(StoreExportJobInRedisSysMsg.NAME, routing)
val body = StoreExportJobInRedisSysMsgBody(exportJob)
val header = BbbCoreHeaderWithMeetingId(StoreExportJobInRedisSysMsg.NAME, liveMeeting.props.meetingProp.intId)
val event = StoreExportJobInRedisSysMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildPresentationUploadTokenSysPubMsg(parentMeetingId: String, userId: String, presentationUploadToken: String, filename: String, presId: String): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, parentMeetingId, userId)
val body = PresentationUploadTokenSysPubMsgBody("DEFAULT_PRESENTATION_POD", presentationUploadToken, filename, parentMeetingId, presId)
val event = PresentationUploadTokenSysPubMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildPresentationConversionUpdateEvtMsg(meetingId: String, presentationId: String, presName: String, temporaryPresentationId: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "system")
val envelope = BbbCoreEnvelope(PresentationConversionUpdateEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationConversionUpdateEvtMsg.NAME, meetingId, "system")
val body = PresentationConversionUpdateEvtMsgBody(
"DEFAULT_PRESENTATION_POD",
"204",
"not-used",
presentationId,
presName,
temporaryPresentationId
)
val event = PresentationConversionUpdateEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
}

View File

@ -1,13 +1,14 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{ SendMessageToBreakoutRoomInternalMsg }
import org.bigbluebutton.core.api.SendMessageToBreakoutRoomInternalMsg
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.db.NotificationDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ RegisteredUsers }
import org.bigbluebutton.core.models.RegisteredUsers
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait SendMessageToAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
this: MeetingActor =>
@ -32,7 +33,7 @@ trait SendMessageToAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
val event = buildSendMessageToAllBreakoutRoomsEvtMsg(msg.header.userId, msg.body.msg, breakoutModel.rooms.size)
outGW.send(event)
val notifyModeratorEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
msg.header.userId,
liveMeeting.props.meetingProp.intId,
"info",
@ -41,7 +42,8 @@ trait SendMessageToAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
"Message for chat sent successfully",
Vector(s"${breakoutModel.rooms.size}")
)
outGW.send(notifyModeratorEvent)
outGW.send(notifyUserEvent)
NotificationDAO.insert(notifyUserEvent)
log.debug("Sending message '{}' to all breakout rooms in meeting {}", msg.body.msg, props.meetingProp.intId)
}

View File

@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ BreakoutModel, PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.UserDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.VoiceUsers
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
@ -17,13 +18,13 @@ trait TransferUserToMeetingRequestHdlr extends RightsManagementTrait {
val reason = "No permission to transfer user to voice breakout."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
} else {
processRequest(msg)
processTransferUserToMeetingRequest(msg)
}
state
}
def processRequest(msg: TransferUserToMeetingRequestMsg) {
def processTransferUserToMeetingRequest(msg: TransferUserToMeetingRequestMsg) {
if (msg.body.fromMeetingId == liveMeeting.props.meetingProp.intId) {
// want to transfer from parent meeting to breakout
for {
@ -32,6 +33,7 @@ trait TransferUserToMeetingRequestHdlr extends RightsManagementTrait {
from <- getVoiceConf(msg.body.fromMeetingId, model)
voiceUser <- VoiceUsers.findWithIntId(liveMeeting.voiceUsers, msg.body.userId)
} yield {
UserDAO.transferUserToBreakoutRoomAsAudioOnly(msg.body.userId, msg.body.fromMeetingId, msg.body.toMeetingId)
val event = buildTransferUserToVoiceConfSysMsg(from, to, voiceUser.voiceUserId)
outGW.send(event)
}
@ -53,6 +55,7 @@ trait TransferUserToMeetingRequestHdlr extends RightsManagementTrait {
room <- model.find(msg.body.fromMeetingId)
voiceUser <- room.voiceUsers.find(p => p.id == msg.body.userId)
} yield {
UserDAO.transferUserToBreakoutRoomAsAudioOnly(msg.body.userId, msg.body.fromMeetingId, msg.body.toMeetingId)
val event = buildTransferUserToVoiceConfSysMsg(from, to, voiceUser.voiceUserId)
outGW.send(event)
}

View File

@ -4,7 +4,7 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{ SendTimeRemainingAuditInternalMsg, UpdateBreakoutRoomTimeInternalMsg }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.db.{ BreakoutRoomDAO, MeetingDAO }
import org.bigbluebutton.core.db.{ BreakoutRoomDAO, MeetingDAO, NotificationDAO }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
@ -63,9 +63,10 @@ trait UpdateBreakoutRoomsTimeMsgHdlr extends RightsManagementTrait {
Vector(s"${msg.body.timeInMinutes}")
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
}
val notifyModeratorEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
msg.header.userId,
liveMeeting.props.meetingProp.intId,
"info",
@ -74,7 +75,8 @@ trait UpdateBreakoutRoomsTimeMsgHdlr extends RightsManagementTrait {
"Sent to the moderator that requested breakout duration change",
Vector(s"${msg.body.timeInMinutes}")
)
outGW.send(notifyModeratorEvent)
outGW.send(notifyUserEvent)
NotificationDAO.insert(notifyUserEvent)
log.debug("Updating {} minutes for breakout rooms time in meeting {}", msg.body.timeInMinutes, props.meetingProp.intId)
BreakoutRoomDAO.updateRoomsDuration(props.meetingProp.intId, newDurationInSeconds)

View File

@ -4,8 +4,9 @@ import org.apache.pekko.actor.ActorContext
import org.apache.pekko.event.Logging
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.{ LiveMeeting }
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.{ CaptionLocaleDAO, CaptionTypes }
class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementTrait {
val log = Logging(context.system, getClass)
@ -84,6 +85,7 @@ class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementT
val event = UpdateCaptionOwnerEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
CaptionLocaleDAO.insertOrUpdateCaptionLocale(liveMeeting.props.meetingProp.intId, locale, CaptionTypes.TYPED, newOwnerId)
}
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {

View File

@ -11,7 +11,7 @@ trait SyncGetGroupChatsInfoMsgHdlr {
def handleSyncGetGroupChatsInfo(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def buildSyncGetGroupChatsRespMsg(allChats: Vector[GroupChatInfo]): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetGroupChatsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetGroupChatsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetGroupChatsRespMsgBody(allChats)
@ -21,7 +21,7 @@ trait SyncGetGroupChatsInfoMsgHdlr {
}
def buildSyncGetGroupChatMsgsRespMsg(msgs: Vector[GroupChatMsgToUser], chatId: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetGroupChatMsgsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetGroupChatMsgsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetGroupChatMsgsRespMsgBody(chatId, msgs)

View File

@ -5,8 +5,8 @@ import org.bigbluebutton.core.models.{ Layouts, LayoutsType }
import org.bigbluebutton.core.running.OutMsgRouter
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.LayoutDAO
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
import org.bigbluebutton.core.db.{ LayoutDAO, NotificationDAO }
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait BroadcastLayoutMsgHdlr extends RightsManagementTrait {
this: LayoutApp2x =>
@ -73,6 +73,7 @@ trait BroadcastLayoutMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
}
}
}

View File

@ -9,7 +9,7 @@ trait SyncGetMeetingInfoRespMsgHdlr {
val outGW: OutMsgRouter
def handleSyncGetMeetingInfoRespMsg(props: DefaultProps): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(props.meetingProp.intId, props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetMeetingInfoRespMsg.NAME, routing)
val header = BbbCoreBaseHeader(SyncGetMeetingInfoRespMsg.NAME)

View File

@ -10,17 +10,6 @@ trait PadCreateGroupReqMsgHdlr {
def handle(msg: PadCreateGroupReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(externalId: String, model: String): Unit = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PadCreateGroupCmdMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(PadCreateGroupCmdMsg.NAME, liveMeeting.props.meetingProp.intId)
val body = PadCreateGroupCmdMsgBody(externalId, model)
val event = PadCreateGroupCmdMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
val padEnabled = msg.body.model match {
case "notes" => !liveMeeting.props.meetingProp.disabledFeatures.contains("sharedNotes")
case "captions" => !liveMeeting.props.meetingProp.disabledFeatures.contains("captions")
@ -29,7 +18,7 @@ trait PadCreateGroupReqMsgHdlr {
if (padEnabled && !Pads.hasGroup(liveMeeting.pads, msg.body.externalId)) {
Pads.addGroup(liveMeeting.pads, msg.body.externalId, msg.body.model, msg.body.name, msg.header.userId)
broadcastEvent(msg.body.externalId, msg.body.model)
PadslHdlrHelpers.broadcastPadCreateGroupCmdMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.body.externalId, msg.body.model)
}
}
}

View File

@ -24,6 +24,7 @@ trait PadCreatedEvtMsgHdlr {
Pads.getGroupById(liveMeeting.pads, msg.body.groupId) match {
case Some(group) => {
Pads.setPadId(liveMeeting.pads, group.externalId, msg.body.padId)
SharedNotesDAO.insert(liveMeeting.props.meetingProp.intId, group, msg.body.padId, msg.body.name)
broadcastEvent(group.externalId, group.userId, msg.body.padId, msg.body.name)
}

View File

@ -9,22 +9,12 @@ trait PadGroupCreatedEvtMsgHdlr {
this: PadsApp2x =>
def handle(msg: PadGroupCreatedEvtMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(externalId: String, model: String, name: String, userId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, userId)
val envelope = BbbCoreEnvelope(PadGroupCreatedRespMsg.NAME, routing)
val header = BbbClientMsgHeader(PadGroupCreatedRespMsg.NAME, liveMeeting.props.meetingProp.intId, userId)
val body = PadGroupCreatedRespMsgBody(externalId, model, name)
val event = PadGroupCreatedRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
Pads.getGroup(liveMeeting.pads, msg.body.externalId) match {
case Some(group) => {
Pads.setGroupId(liveMeeting.pads, msg.body.externalId, msg.body.groupId)
broadcastEvent(msg.body.externalId, group.model, group.name, group.userId)
//Group was created, now request to create the pad
PadslHdlrHelpers.broadcastPadCreateCmdMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.body.groupId, msg.body.externalId)
}
case _ =>
}

View File

@ -24,7 +24,7 @@ trait PadSessionDeletedSysMsgHdlr {
Pads.getGroupById(liveMeeting.pads, msg.body.groupId) match {
case Some(group) => {
SharedNotesSessionDAO.delete(msg.body.userId, msg.body.sessionId)
SharedNotesSessionDAO.delete(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.sessionId)
broadcastEvent(group.externalId, msg.body.userId, msg.body.sessionId)
}
case _ =>

View File

@ -21,7 +21,7 @@ trait PadUpdatePubMsgHdlr {
bus.outGW.send(msgEvent)
}
if (Pads.hasAccess(liveMeeting, msg.body.externalId, msg.header.userId)) {
if (Pads.hasAccess(liveMeeting, msg.body.externalId, msg.header.userId) || msg.body.transcript == true) {
Pads.getGroup(liveMeeting.pads, msg.body.externalId) match {
case Some(group) => broadcastEvent(group.groupId, msg.body.externalId, msg.body.text)
case _ =>

View File

@ -24,6 +24,7 @@ trait PadUpdatedSysMsgHdlr {
Pads.getGroupById(liveMeeting.pads, msg.body.groupId) match {
case Some(group) => {
Pads.setRev(liveMeeting.pads, group.externalId, msg.body.rev)
SharedNotesRevDAO.insert(liveMeeting.props.meetingProp.intId, group.externalId, msg.body.rev, msg.body.userId, msg.body.changeset)
broadcastEvent(group.externalId, msg.body.padId, msg.body.userId, msg.body.rev, msg.body.changeset)
}

View File

@ -0,0 +1,30 @@
package org.bigbluebutton.core.apps.pads
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, PadCreateCmdMsg, PadCreateCmdMsgBody, PadCreateGroupCmdMsg, PadCreateGroupCmdMsgBody }
import org.bigbluebutton.core.running.OutMsgRouter
object PadslHdlrHelpers {
def broadcastPadCreateGroupCmdMsg(outGW: OutMsgRouter, meetingId: String, externalId: String, model: String): Unit = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PadCreateGroupCmdMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(PadCreateGroupCmdMsg.NAME, meetingId)
val body = PadCreateGroupCmdMsgBody(externalId, model)
val event = PadCreateGroupCmdMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
def broadcastPadCreateCmdMsg(outGW: OutMsgRouter, meetingId: String, groupId: String, name: String): Unit = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PadCreateCmdMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(PadCreateCmdMsg.NAME, meetingId)
val body = PadCreateCmdMsgBody(groupId, name)
val event = PadCreateCmdMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
}

View File

@ -1,15 +1,15 @@
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.common2.msgs.PluginDataChannelDeleteEntryMsg
import org.bigbluebutton.core.db.PluginDataChannelEntryDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ Roles, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
trait PluginDataChannelDeleteMessageMsgHdlr extends HandlerHelpers {
trait PluginDataChannelDeleteEntryMsgHdlr extends HandlerHelpers {
def handle(msg: PluginDataChannelDeleteMessageMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
def handle(msg: PluginDataChannelDeleteEntryMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
val pluginsDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("plugins")
val meetingId = liveMeeting.props.meetingProp.intId
@ -21,22 +21,23 @@ trait PluginDataChannelDeleteMessageMsgHdlr extends HandlerHelpers {
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 if (!pluginsConfig(msg.body.pluginName).dataChannels.contains(msg.body.channelName)) {
println(s"Data channel '${msg.body.channelName}' not found in plugin '${msg.body.pluginName}'.")
} else {
val hasPermission = for {
deletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.dataChannel).deletePermission
deletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.channelName).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(
val senderUserId = PluginDataChannelEntryDAO.getMessageSender(
meetingId,
msg.body.pluginName,
msg.body.dataChannel,
msg.body.messageId
msg.body.channelName,
msg.body.subChannelName,
msg.body.entryId
)
senderUserId == msg.header.userId
}
@ -45,13 +46,14 @@ trait PluginDataChannelDeleteMessageMsgHdlr extends HandlerHelpers {
}
if (!hasPermission.contains(true)) {
println(s"No permission to delete in plugin: '${msg.body.pluginName}', data channel: '${msg.body.dataChannel}'.")
println(s"No permission to delete in plugin: '${msg.body.pluginName}', data channel: '${msg.body.channelName}'.")
} else {
PluginDataChannelMessageDAO.delete(
PluginDataChannelEntryDAO.delete(
meetingId,
msg.body.pluginName,
msg.body.dataChannel,
msg.body.messageId
msg.body.channelName,
msg.body.subChannelName,
msg.body.entryId
)
}
}

View File

@ -1,15 +1,15 @@
package org.bigbluebutton.core.apps.plugin
import org.bigbluebutton.common2.msgs.PluginDataChannelPushEntryMsg
import org.bigbluebutton.ClientSettings
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.db.{ PluginDataChannelMessageDAO }
import org.bigbluebutton.core.db.PluginDataChannelEntryDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ Roles, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
trait PluginDataChannelDispatchMessageMsgHdlr extends HandlerHelpers {
trait PluginDataChannelPushEntryMsgHdlr extends HandlerHelpers {
def handle(msg: PluginDataChannelDispatchMessageMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
def handle(msg: PluginDataChannelPushEntryMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
val pluginsDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("plugins")
val meetingId = liveMeeting.props.meetingProp.intId
@ -21,11 +21,11 @@ trait PluginDataChannelDispatchMessageMsgHdlr extends HandlerHelpers {
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 if (!pluginsConfig(msg.body.pluginName).dataChannels.contains(msg.body.channelName)) {
println(s"Data channel '${msg.body.channelName}' not found in plugin '${msg.body.pluginName}'.")
} else {
val hasPermission = for {
writePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.dataChannel).writePermission
writePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.channelName).writePermission
} yield {
writePermission.toLowerCase match {
case "all" => true
@ -36,12 +36,13 @@ trait PluginDataChannelDispatchMessageMsgHdlr extends HandlerHelpers {
}
if (!hasPermission.contains(true)) {
println(s"No permission to write in plugin: '${msg.body.pluginName}', data channel: '${msg.body.dataChannel}'.")
println(s"No permission to write in plugin: '${msg.body.pluginName}', data channel: '${msg.body.channelName}'.")
} else {
PluginDataChannelMessageDAO.insert(
PluginDataChannelEntryDAO.insert(
meetingId,
msg.body.pluginName,
msg.body.dataChannel,
msg.body.channelName,
msg.body.subChannelName,
msg.header.userId,
msg.body.payloadJson,
msg.body.toRoles,

View File

@ -2,7 +2,7 @@ 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.db.PluginDataChannelEntryDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ Roles, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
@ -21,11 +21,11 @@ trait PluginDataChannelResetMsgHdlr extends HandlerHelpers {
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 if (!pluginsConfig(msg.body.pluginName).dataChannels.contains(msg.body.channelName)) {
println(s"Data channel '${msg.body.channelName}' not found in plugin '${msg.body.pluginName}'.")
} else {
val hasPermission = for {
deletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.dataChannel).deletePermission
deletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.channelName).deletePermission
} yield {
deletePermission.toLowerCase match {
case "all" => true
@ -36,12 +36,13 @@ trait PluginDataChannelResetMsgHdlr extends HandlerHelpers {
}
if (!hasPermission.contains(true)) {
println(s"No permission to delete (reset) in plugin: '${msg.body.pluginName}', data channel: '${msg.body.dataChannel}'.")
println(s"No permission to delete (reset) in plugin: '${msg.body.pluginName}', data channel: '${msg.body.channelName}'.")
} else {
PluginDataChannelMessageDAO.reset(
PluginDataChannelEntryDAO.reset(
meetingId,
msg.body.pluginName,
msg.body.dataChannel
msg.body.channelName,
msg.body.subChannelName
)
}
}

View File

@ -2,10 +2,11 @@ package org.bigbluebutton.core.apps.plugin
import org.apache.pekko.actor.ActorContext
import org.apache.pekko.event.Logging
import org.bigbluebutton.common2.msgs.PluginDataChannelDeleteEntryMsgBody
class PluginHdlrs(implicit val context: ActorContext)
extends PluginDataChannelDispatchMessageMsgHdlr
with PluginDataChannelDeleteMessageMsgHdlr
extends PluginDataChannelPushEntryMsgHdlr
with PluginDataChannelDeleteEntryMsgHdlr
with PluginDataChannelResetMsgHdlr {
val log = Logging(context.system, getClass)

View File

@ -8,7 +8,7 @@ import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.Polls
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.{PermissionCheck, RightsManagementTrait}
import org.bigbluebutton.core.db.{ ChatMessageDAO, JsonUtils }
import org.bigbluebutton.core.db.{ChatMessageDAO, JsonUtils, NotificationDAO}
import org.bigbluebutton.core2.message.senders.MsgBuilder
import spray.json.DefaultJsonProtocol.jsonFormat2
@ -37,6 +37,7 @@ trait ShowPollResultReqMsgHdlr extends RightsManagementTrait {
Vector()
)
bus.outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
// SendWhiteboardAnnotationPubMsg
val annotationRouting = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -1,10 +1,9 @@
package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{ CapturePresentationReqInternalMsg, CaptureSharedNotesReqInternalMsg }
import org.bigbluebutton.core.api.{ CapturePresentationReqInternalMsg }
import org.bigbluebutton.core.apps.groupchats.GroupChatApp
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.apps.presentationpod.PresentationSender
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.{ ChatMessageDAO, PresPresentationDAO }
import org.bigbluebutton.core.domain.MeetingState2x
@ -268,35 +267,8 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
bus.outGW.send(buildBroadcastNewPresFileAvailable(m, liveMeeting))
}
def handle(m: CaptureSharedNotesReqInternalMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val parentMeetingId = liveMeeting.props.meetingProp.intId
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, parentMeetingId, "not-used")
val envelope = BbbCoreEnvelope(PresentationPageConversionStartedEventMsg.NAME, routing)
val header = BbbClientMsgHeader(CaptureSharedNotesReqEvtMsg.NAME, parentMeetingId, "not-used")
val body = CaptureSharedNotesReqEvtMsgBody(m.breakoutId, m.filename)
val event = CaptureSharedNotesReqEvtMsg(header, body)
bus.outGW.send(BbbCommonEnvCoreMsg(envelope, event))
}
def handle(m: PresAnnStatusMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
PresPresentationDAO.updateExportToChat(m.body.presId, m.body.status, m.body.pageNumber, m.body.error)
bus.outGW.send(buildBroadcastPresAnnStatusMsg(m, liveMeeting))
}
def handle(m: PadCapturePubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val userId: String = "system"
val jobId: String = s"${m.body.breakoutId}-notes" // Used as the temporaryPresentationId upon upload
val filename = m.body.filename
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId)
val presentationId = PresentationPodsApp.generatePresentationId(m.body.filename)
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(m.body.parentMeetingId, userId, presentationUploadToken, filename, presentationId))
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)
}
}

View File

@ -13,31 +13,7 @@ trait PresentationConversionUpdatePubMsgHdlr {
def handle(msg: PresentationConversionUpdateSysPubMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def broadcastEvent(msg: PresentationConversionUpdateSysPubMsg): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, msg.header.userId
)
val envelope = BbbCoreEnvelope(PresentationConversionUpdateEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(
PresentationConversionUpdateEvtMsg.NAME,
liveMeeting.props.meetingProp.intId, msg.header.userId
)
val body = PresentationConversionUpdateEvtMsgBody(
msg.body.podId,
msg.body.messageKey,
msg.body.code,
msg.body.presentationId,
msg.body.presName,
msg.body.temporaryPresentationId
)
val event = PresentationConversionUpdateEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
broadcastEvent(msg)
// broadcastEvent(msg)
state
}
}

View File

@ -12,7 +12,7 @@ trait SyncGetPresentationPodsMsgHdlr {
def handleSyncGetPresentationPods(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def buildSyncGetPresentationPodsRespMsg(pods: Vector[PresentationPodVO]): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetPresentationPodsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetPresentationPodsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")

View File

@ -9,9 +9,10 @@ trait SyncGetScreenshareInfoRespMsgHdlr {
this: ScreenshareApp2x =>
def handleSyncGetScreenshareInfoRespMsg(liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId,
liveMeeting.props.systemProps.html5InstanceId.toString
"nodeJSapp"
)
val envelope = BbbCoreEnvelope(SyncGetScreenshareInfoRespMsg.NAME, routing)
val header = BbbClientMsgHeader(

View File

@ -3,7 +3,7 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.LockSettingsUtil
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.MeetingLockSettingsDAO
import org.bigbluebutton.core.db.{ MeetingLockSettingsDAO, NotificationDAO }
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.OutMsgRouter
import org.bigbluebutton.core.running.MeetingActor
@ -66,6 +66,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
LockSettingsUtil.enforceCamLockSettingsForAllUsers(liveMeeting, outGW)
} else {
@ -78,6 +79,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
}
}
@ -92,6 +94,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { vu =>
if (vu.intId.startsWith(IntIdPrefixType.DIAL_IN)) { // only Dial-in users need this
val eventExplicitLock = buildLockMessage(liveMeeting.props.meetingProp.intId, vu.intId, msg.body.setBy, settings.disableMic)
@ -109,6 +112,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
}
}
@ -123,6 +127,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
} else {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
liveMeeting.props.meetingProp.intId,
@ -133,6 +138,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
}
}
@ -147,6 +153,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
} else {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
liveMeeting.props.meetingProp.intId,
@ -157,6 +164,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
}
}
@ -171,6 +179,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
} else {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
liveMeeting.props.meetingProp.intId,
@ -181,6 +190,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
}
}
@ -195,6 +205,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
} else {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
liveMeeting.props.meetingProp.intId,
@ -205,6 +216,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
}
}

View File

@ -5,6 +5,7 @@ import org.bigbluebutton.core.models.{ RegisteredUsers, Roles, UserState, Users2
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.LockSettingsUtil
import org.bigbluebutton.core.db.NotificationDAO
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
trait ChangeUserRoleCmdMsgHdlr extends RightsManagementTrait {
@ -43,6 +44,7 @@ trait ChangeUserRoleCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
Users2x.changeRole(liveMeeting.users2x, uvo, msg.body.role)
val event = buildUserRoleChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId,
@ -59,6 +61,7 @@ trait ChangeUserRoleCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
val newUvo: UserState = Users2x.changeRole(liveMeeting.users2x, uvo, msg.body.role)
val event = buildUserRoleChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId,

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.db.NotificationDAO
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.util.ColorPicker
@ -58,7 +59,7 @@ trait RegisterUserReqMsgHdlr {
val guestStatus = msg.body.guestStatus
val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId,
val regUser = RegisteredUsers.create(liveMeeting.props.meetingProp.intId, msg.body.intUserId, msg.body.extUserId,
msg.body.name, msg.body.role, msg.body.authToken, msg.body.sessionToken,
msg.body.avatarURL, ColorPicker.nextColor(liveMeeting.props.meetingProp.intId), msg.body.guest, msg.body.authed,
guestStatus, msg.body.excludeFromDashboard, msg.body.enforceLayout, msg.body.customParameters, false)
@ -107,6 +108,7 @@ trait RegisterUserReqMsgHdlr {
Vector(s"${regUser.name}")
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
case GuestStatus.DENY =>
val g = GuestApprovedVO(regUser.id, GuestStatus.DENY)
UsersApp.approveOrRejectGuest(liveMeeting, outGW, g, SystemUser.ID)

View File

@ -1,63 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.SystemConfiguration
import scala.util.Random
trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val outGW: OutMsgRouter
def handleSelectRandomViewerReqMsg(msg: SelectRandomViewerReqMsg): Unit = {
log.debug("Received SelectRandomViewerReqMsg {}", SelectRandomViewerReqMsg)
def broadcastEvent(msg: SelectRandomViewerReqMsg, users: Vector[String], choice: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SelectRandomViewerRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SelectRandomViewerRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = SelectRandomViewerRespMsgBody(msg.header.userId, users, choice)
val event = SelectRandomViewerRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to select random user."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
} else {
val users = Users2x.getRandomlyPickableUsers(liveMeeting.users2x, false)
val usersPicked = Users2x.getRandomlyPickableUsers(liveMeeting.users2x, reduceDuplicatedPick)
val randNum = new scala.util.Random
var pickedUser = if (usersPicked.size == 0) "" else usersPicked(randNum.nextInt(usersPicked.size)).intId
if (reduceDuplicatedPick) {
if (usersPicked.size <= 1) {
// Initialise the exemption
val usersToUnexempt = Users2x.findAll(liveMeeting.users2x)
usersToUnexempt foreach { u =>
Users2x.setUserExempted(liveMeeting.users2x, u.intId, false)
}
if (usersPicked.size == 0) {
// Pick again
val usersRepicked = Users2x.getRandomlyPickableUsers(liveMeeting.users2x, reduceDuplicatedPick)
pickedUser = if (usersRepicked.size == 0) "" else usersRepicked(randNum.nextInt(usersRepicked.size)).intId
Users2x.setUserExempted(liveMeeting.users2x, pickedUser, true)
}
} else if (usersPicked.size > 1) {
Users2x.setUserExempted(liveMeeting.users2x, pickedUser, true)
}
}
val userIds = users.map { case (v) => v.intId }
broadcastEvent(msg, userIds, pickedUser)
}
}
}

View File

@ -10,7 +10,7 @@ import org.bigbluebutton.core.api.SendRecordingTimerInternalMsg
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.core.apps.voice.VoiceApp
import org.bigbluebutton.core.db.MeetingRecordingDAO
import org.bigbluebutton.core.db.{ MeetingRecordingDAO, NotificationDAO }
trait SetRecordingStatusCmdMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
@ -49,6 +49,7 @@ trait SetRecordingStatusCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
MeetingStatus2x.recordingStarted(liveMeeting.status)
MeetingRecordingDAO.insertRecording(liveMeeting.props.meetingProp.intId, msg.body.setBy)
@ -75,6 +76,7 @@ trait SetRecordingStatusCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
MeetingStatus2x.recordingStopped(liveMeeting.status)
MeetingRecordingDAO.updateStopped(liveMeeting.props.meetingProp.intId, msg.body.setBy)

View File

@ -0,0 +1,41 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.domain.MeetingState2x
trait SetUserSpeechOptionsMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleSetUserSpeechOptionsReqMsg(msg: SetUserSpeechOptionsReqMsg): Unit = {
log.info("handleSetUserSpeechOptionsReqMsg: partialUtterances={} minUtteranceLength={} userId={}", msg.body.partialUtterances, msg.body.minUtteranceLength, msg.header.userId)
def broadcastUserSpeechOptionsChanged(user: UserState, partialUtterances: Boolean, minUtteranceLength: Int): Unit = {
val routingChange = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, user.intId
)
val envelopeChange = BbbCoreEnvelope(UserSpeechOptionsChangedEvtMsg.NAME, routingChange)
val headerChange = BbbClientMsgHeader(UserSpeechOptionsChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, user.intId)
val bodyChange = UserSpeechOptionsChangedEvtMsgBody(partialUtterances, minUtteranceLength)
val eventChange = UserSpeechOptionsChangedEvtMsg(headerChange, bodyChange)
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
outGW.send(msgEventChange)
}
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
var changeLocale: Option[UserState] = None;
//changeLocale = Users2x.setUserSpeechLocale(liveMeeting.users2x, msg.header.userId, msg.body.locale)
broadcastUserSpeechOptionsChanged(user, msg.body.partialUtterances, msg.body.minUtteranceLength)
}
}
}

View File

@ -11,7 +11,7 @@ trait SyncGetUsersMeetingRespMsgHdlr {
val outGW: OutMsgRouter
def handleSyncGetUsersMeetingRespMsg(): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetUsersMeetingRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetUsersMeetingRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.db.UserDAO
import org.bigbluebutton.core.models.{ Users2x, VoiceUserState, VoiceUsers }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
@ -29,10 +30,10 @@ trait UserConnectedToGlobalAudioMsgHdlr {
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
} yield {
val vu = VoiceUserState(
intId = user.intId,
voiceUserId = user.intId,
meetingId = props.meetingProp.intId,
callingWith = "flash",
callerName = user.name,
callerNum = user.name,

View File

@ -1,9 +1,10 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.ClientSettings.{ getConfigPropertyValueByPathAsListOfIntOrElse, getConfigPropertyValueByPathAsListOfStringOrElse }
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.db.UserConnectionStatusDAO
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
trait UserConnectionAliveReqMsgHdlr extends RightsManagementTrait {
@ -13,13 +14,42 @@ trait UserConnectionAliveReqMsgHdlr extends RightsManagementTrait {
val outGW: OutMsgRouter
def handleUserConnectionAliveReqMsg(msg: UserConnectionAliveReqMsg): Unit = {
log.info("handleUserConnectionAliveReqMsg: userId={}", msg.body.userId)
log.info("handleUserConnectionAliveReqMsg: networkRttInMs={} userId={}", msg.body.networkRttInMs, msg.body.userId)
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
} yield {
UserConnectionStatusDAO.updateUserAlive(user.intId)
val rtt: Option[Double] = msg.body.networkRttInMs match {
case 0 => None
case rtt: Double => Some(rtt)
}
val status = getLevelFromRtt(msg.body.networkRttInMs)
UserConnectionStatusDAO.updateUserAlive(user.meetingId, user.intId, rtt, status)
}
}
def getLevelFromRtt(networkRttInMs: Double): String = {
val levelOptions = getConfigPropertyValueByPathAsListOfStringOrElse(
liveMeeting.clientSettings,
"public.stats.level",
List("warning", "danger", "critical")
)
val rttOptions = getConfigPropertyValueByPathAsListOfIntOrElse(
liveMeeting.clientSettings,
"public.stats.rtt",
List(500, 1000, 2000)
)
val statusRttXLevel = levelOptions.zip(rttOptions).reverse
val statusFound = statusRttXLevel.collectFirst {
case (level, rtt) if networkRttInMs > rtt => level
}
statusFound.getOrElse("normal")
}
}
}

View File

@ -1,25 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.db.UserConnectionStatusDAO
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
trait UserConnectionUpdateRttReqMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleUserConnectionUpdateRttReqMsg(msg: UserConnectionUpdateRttReqMsg): Unit = {
log.info("handleUserConnectionUpdateRttReqMsg: networkRttInMs={} userId={}", msg.body.networkRttInMs, msg.body.userId)
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
} yield {
UserConnectionStatusDAO.updateUserRtt(user.intId, msg.body.networkRttInMs)
}
}
}

View File

@ -28,7 +28,7 @@ trait UserDisconnectedFromGlobalAudioMsgHdlr {
for {
user <- VoiceUsers.findWithIntId(liveMeeting.voiceUsers, msg.body.userId)
} yield {
VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, user.intId)
VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, liveMeeting.props.meetingProp.intId, user.intId)
broadcastEvent(user)
}
}

View File

@ -2,7 +2,7 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
import org.bigbluebutton.core.db.{ UserDAO, UserStateDAO }
import org.bigbluebutton.core.db.{ NotificationDAO, UserDAO, UserStateDAO }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running._
@ -58,7 +58,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
private def handleFailedUserJoin(msg: UserJoinMeetingReqMsg, failReason: String, failReasonCode: String) = {
log.info("Ignoring user {} attempt to join in meeting {}. Reason Code: {}, Reason Message: {}", msg.body.userId, msg.header.meetingId, failReasonCode, failReason)
UserDAO.updateJoinError(msg.body.userId, failReasonCode, failReason)
UserDAO.updateJoinError(msg.header.meetingId, msg.body.userId, failReasonCode, failReason)
state
}
@ -137,6 +137,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
Vector(newUser.name)
)
outGW.send(notifyUserEvent)
NotificationDAO.insert(notifyUserEvent)
}
private def clearCachedVoiceUser(regUser: RegisteredUser) =
@ -144,7 +145,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, regUser.id)
private def clearExpiredUserState(regUser: RegisteredUser) =
UserStateDAO.updateExpired(regUser.id, false)
UserStateDAO.updateExpired(regUser.meetingId, regUser.id, false)
private def ForceUserGraphqlReconnection(regUser: RegisteredUser) =
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "user_joined", outGW)

View File

@ -44,7 +44,7 @@ object UsersApp {
u <- RegisteredUsers.findWithUserId(guest.guest, liveMeeting.registeredUsers)
} yield {
RegisteredUsers.setWaitingForApproval(liveMeeting.registeredUsers, u, guest.status)
UserStateDAO.updateGuestStatus(guest.guest, guest.status, approvedBy)
UserStateDAO.updateGuestStatus(liveMeeting.props.meetingProp.intId, guest.guest, guest.status, approvedBy)
// send message to user that he has been approved
val event = MsgBuilder.buildGuestApprovedEvtMsg(
@ -131,7 +131,7 @@ object UsersApp {
// println(s"ejectUserFromMeeting will cause a automaticallyAssignPresenter for user=${user}")
automaticallyAssignPresenter(outGW, liveMeeting)
}
UserStateDAO.updateEjected(userId, reason, reasonCode, ejectedBy)
UserStateDAO.updateEjected(meetingId, userId, reason, reasonCode, ejectedBy)
}
for {
@ -158,18 +158,17 @@ class UsersApp(
with RegisterUserReqMsgHdlr
with ChangeUserRoleCmdMsgHdlr
with SetUserSpeechLocaleMsgHdlr
with SetUserSpeechOptionsMsgHdlr
with SyncGetUsersMeetingRespMsgHdlr
with LogoutAndEndMeetingCmdMsgHdlr
with SetRecordingStatusCmdMsgHdlr
with RecordAndClearPreviousMarkersCmdMsgHdlr
with SendRecordingTimerInternalMsgHdlr
with GetRecordingStatusReqMsgHdlr
with SelectRandomViewerReqMsgHdlr
with AssignPresenterReqMsgHdlr
with ChangeUserPinStateReqMsgHdlr
with ChangeUserMobileFlagReqMsgHdlr
with UserConnectionAliveReqMsgHdlr
with UserConnectionUpdateRttReqMsgHdlr
with ChangeUserReactionEmojiReqMsgHdlr
with ChangeUserRaiseHandReqMsgHdlr
with ChangeUserAwayReqMsgHdlr

View File

@ -75,7 +75,7 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
}
private def sendFailedValidateAuthTokenRespMsg(msg: ValidateAuthTokenReqMsg, failReason: String, failReasonCode: String) = {
UserDAO.updateJoinError(msg.body.userId, failReasonCode, failReason)
UserDAO.updateJoinError(msg.header.meetingId, msg.body.userId, failReasonCode, failReason)
val event = MsgBuilder.buildValidateAuthTokenRespMsg(liveMeeting.props.meetingProp.intId, msg.header.userId, msg.body.authToken, false, false, 0,
0, failReasonCode, failReason)

View File

@ -17,7 +17,7 @@ trait SyncGetVoiceUsersMsgHdlr {
callerNum = u.callerNum, color = u.color, muted = u.muted, talking = u.talking, listenOnly = u.listenOnly)
}
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetVoiceUsersRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetVoiceUsersRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetVoiceUsersRespMsgBody(voiceUsers)

View File

@ -32,7 +32,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
}
def registerUserInRegisteredUsers() = {
val regUser = RegisteredUsers.create(msg.body.intId, msg.body.voiceUserId,
val regUser = RegisteredUsers.create(liveMeeting.props.meetingProp.intId, msg.body.intId, msg.body.voiceUserId,
msg.body.callerIdName, Roles.VIEWER_ROLE, msg.body.intId, "", "", userColor,
true, true, GuestStatus.WAIT, true, "", Map(), false)
RegisteredUsers.add(liveMeeting.registeredUsers, regUser, liveMeeting.props.meetingProp.intId)
@ -42,6 +42,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
val newUser = UserState(
intId = msg.body.intId,
extId = msg.body.voiceUserId,
meetingId = liveMeeting.props.meetingProp.intId,
name = msg.body.callerIdName,
role = Roles.VIEWER_ROLE,
guest = true,
@ -58,7 +59,6 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
avatar = "",
color = userColor,
clientType = if (isDialInUser) "dial-in-user" else "",
pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0)
)
Users2x.add(liveMeeting.users2x, newUser)

View File

@ -40,14 +40,14 @@ trait UserLeftVoiceConfEvtMsgHdlr {
UsersApp.guestWaitingLeft(liveMeeting, user.intId, outGW)
}
Users2x.remove(liveMeeting.users2x, user.intId)
UserDAO.softDelete(user.intId)
UserDAO.softDelete(user.meetingId, user.intId)
VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, msg.body.voiceUserId)
}
for {
user <- VoiceUsers.findWithVoiceUserId(liveMeeting.voiceUsers, msg.body.voiceUserId)
} yield {
VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, user.intId)
VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, liveMeeting.props.meetingProp.intId, user.intId)
broadcastEvent(user)
}

View File

@ -238,7 +238,7 @@ object VoiceApp extends SystemConfiguration {
): Unit = {
for {
u <- VoiceUsers.findWithIntId(liveMeeting.voiceUsers, userid)
oldU <- VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, userid)
oldU <- VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, u.meetingId, u.intId)
} yield {
val event = MsgBuilder.buildEjectUserFromVoiceConfSysMsg(liveMeeting.props.meetingProp.intId, liveMeeting.props.voiceProp.voiceConf, oldU.voiceUserId)
outGW.send(event)
@ -302,7 +302,6 @@ object VoiceApp extends SystemConfiguration {
)
outGW.send(msgEvent)
}
checkAndEjectOldDuplicateVoiceConfUser(intId, liveMeeting, outGW)
val isListenOnly = if (callerIdName.startsWith("LISTENONLY")) true else false
@ -310,6 +309,7 @@ object VoiceApp extends SystemConfiguration {
val voiceUserState = VoiceUserState(
intId,
voiceUserId,
meetingId = liveMeeting.props.meetingProp.intId,
callingWith,
callerIdName,
callerIdNum,
@ -393,7 +393,7 @@ object VoiceApp extends SystemConfiguration {
for {
user <- VoiceUsers.findWithVoiceUserId(liveMeeting.voiceUsers, voiceUserId)
} yield {
VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, user.intId)
VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, user.meetingId, user.intId)
broadcastEvent(user)
}

View File

@ -40,7 +40,7 @@ trait VoiceConfCallStateEvtMsgHdlr {
outGW.send(msgEvent)
if (msg.body.userId.nonEmpty) {
UserVoiceConfStateDAO.insertOrUpdate(msg.body.userId, msg.body.voiceConf, msg.body.callSession, msg.body.clientSession, msg.body.callState)
UserVoiceConfStateDAO.insertOrUpdate(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.voiceConf, msg.body.callSession, msg.body.clientSession, msg.body.callState)
}
}
}

View File

@ -13,9 +13,10 @@ trait SyncGetWebcamInfoRespMsgHdlr {
this: WebcamApp2x =>
def handleSyncGetWebcamInfoRespMsg(liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId,
liveMeeting.props.systemProps.html5InstanceId.toString
"nodeJSapp"
)
val envelope = BbbCoreEnvelope(SyncGetWebcamInfoRespMsg.NAME, routing)
val header = BbbClientMsgHeader(

View File

@ -3,7 +3,7 @@ package org.bigbluebutton.core.apps.webcam
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.db.{ MeetingUsersPoliciesDAO, NotificationDAO }
import org.bigbluebutton.core.models.{ RegisteredUsers, Roles, Users2x }
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
@ -64,6 +64,7 @@ trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
Vector()
)
bus.outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
} else {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
meetingId,
@ -74,6 +75,7 @@ trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
Vector()
)
bus.outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
}
broadcastEvent(meetingId, msg.body.setBy, value)

View File

@ -47,7 +47,7 @@ trait UserBroadcastCamStartMsgHdlr {
val webcam = new WebcamStream(msg.body.stream, msg.header.userId, Set.empty)
for {
_ <- Webcams.addWebcamStream(liveMeeting.webcams, webcam)
_ <- Webcams.addWebcamStream(liveMeeting.props.meetingProp.intId, liveMeeting.webcams, webcam)
} yield broadcastEvent(meetingId, msg.header.userId, msg.body.stream)
}
}

View File

@ -11,7 +11,7 @@ trait ClearWhiteboardPubMsgHdlr extends RightsManagementTrait {
def handle(msg: ClearWhiteboardPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: ClearWhiteboardPubMsg, fullClear: Boolean): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(ClearWhiteboardEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(ClearWhiteboardEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -11,7 +11,7 @@ trait DeleteWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
def handle(msg: DeleteWhiteboardAnnotationsPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: DeleteWhiteboardAnnotationsPubMsg, removedAnnotationsIds: Array[String]): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(DeleteWhiteboardAnnotationsEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(DeleteWhiteboardAnnotationsEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -10,7 +10,7 @@ trait GetWhiteboardAnnotationsReqMsgHdlr {
def handle(msg: GetWhiteboardAnnotationsReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: GetWhiteboardAnnotationsReqMsg, history: Array[AnnotationVO], multiUser: Array[String]): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(GetWhiteboardAnnotationsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(GetWhiteboardAnnotationsRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -11,7 +11,7 @@ trait ModifyWhiteboardAccessPubMsgHdlr extends RightsManagementTrait {
def handle(msg: ModifyWhiteboardAccessPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: ModifyWhiteboardAccessPubMsg): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(ModifyWhiteboardAccessEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(ModifyWhiteboardAccessEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -12,7 +12,7 @@ trait SendCursorPositionPubMsgHdlr extends RightsManagementTrait {
def handle(msg: SendCursorPositionPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: SendCursorPositionPubMsg): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SendCursorPositionEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(SendCursorPositionEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
@ -30,7 +30,7 @@ trait SendCursorPositionPubMsgHdlr extends RightsManagementTrait {
//PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else {
broadcastEvent(msg)
PresPageCursorDAO.insertOrUpdate(msg.body.whiteboardId, msg.header.userId, msg.body.xPercent, msg.body.yPercent)
PresPageCursorDAO.insertOrUpdate(msg.body.whiteboardId, liveMeeting.props.meetingProp.intId, msg.header.userId, msg.body.xPercent, msg.body.yPercent)
}
}
}

View File

@ -10,8 +10,8 @@ trait SendWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
def handle(msg: SendWhiteboardAnnotationsPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: SendWhiteboardAnnotationsPubMsg, whiteboardId: String, annotations: Array[AnnotationVO], html5InstanceId: String): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, html5InstanceId)
def broadcastEvent(msg: SendWhiteboardAnnotationsPubMsg, whiteboardId: String, annotations: Array[AnnotationVO]): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SendWhiteboardAnnotationsEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(SendWhiteboardAnnotationsEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
@ -58,7 +58,7 @@ trait SendWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
// }
// println("============= Printed Sanitized annotations ============")
val annotations = sendWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotations, liveMeeting, isUserAmongPresenters, isUserModerator)
broadcastEvent(msg, msg.body.whiteboardId, annotations, msg.body.html5InstanceId)
broadcastEvent(msg, msg.body.whiteboardId, annotations)
} else {
//val meetingId = liveMeeting.props.meetingProp.intId
//val reason = "No permission to send a whiteboard annotation."

View File

@ -33,7 +33,7 @@ class WhiteboardApp2x(implicit val context: ActorContext)
isModerator: Boolean
): Array[AnnotationVO] = {
// println("Received whiteboard annotation. status=[" + status + "], annotationType=[" + annotationType + "]")
liveMeeting.wbModel.addAnnotations(whiteboardId, requesterId, annotations, isPresenter, isModerator)
liveMeeting.wbModel.addAnnotations(whiteboardId, liveMeeting.props.meetingProp.intId, requesterId, annotations, isPresenter, isModerator)
}
def getWhiteboardAnnotations(whiteboardId: String, liveMeeting: LiveMeeting): Array[AnnotationVO] = {
@ -49,7 +49,7 @@ class WhiteboardApp2x(implicit val context: ActorContext)
isPresenter: Boolean,
isModerator: Boolean
): Array[String] = {
liveMeeting.wbModel.deleteAnnotations(whiteboardId, requesterId, annotationsIds, isPresenter, isModerator)
liveMeeting.wbModel.deleteAnnotations(whiteboardId, liveMeeting.props.meetingProp.intId, requesterId, annotationsIds, isPresenter, isModerator)
}
def getWhiteboardAccess(whiteboardId: String, liveMeeting: LiveMeeting): Array[String] = {
@ -57,7 +57,7 @@ class WhiteboardApp2x(implicit val context: ActorContext)
}
def modifyWhiteboardAccess(whiteboardId: String, multiUser: Array[String], liveMeeting: LiveMeeting) {
liveMeeting.wbModel.modifyWhiteboardAccess(whiteboardId, multiUser)
liveMeeting.wbModel.modifyWhiteboardAccess(liveMeeting.props.meetingProp.intId, whiteboardId, multiUser)
}
def filterWhiteboardMessage(whiteboardId: String, userId: String, liveMeeting: LiveMeeting): Boolean = {

View File

@ -67,7 +67,7 @@ object BreakoutRoomDAO {
userId <- room.assignedUsers
(redirectToHtml5JoinURL, redirectJoinURL) <- BreakoutHdlrHelpers.getRedirectUrls(liveMeeting, userId, room.externalId, room.sequence.toString())
} yield {
BreakoutRoomUserDAO.prepareInsert(room.id, userId, redirectToHtml5JoinURL)
BreakoutRoomUserDAO.prepareInsert(room.id, liveMeeting.props.meetingProp.intId, userId, redirectToHtml5JoinURL)
}
).transactionally)
.onComplete {

View File

@ -11,6 +11,7 @@ import scala.util.{Failure, Success}
case class BreakoutRoomUserDbModel(
breakoutRoomId: String,
meetingId: String,
userId: String,
joinURL: String,
joinedAt: Option[java.sql.Timestamp],
@ -19,19 +20,21 @@ case class BreakoutRoomUserDbModel(
class BreakoutRoomUserDbTableDef(tag: Tag) extends Table[BreakoutRoomUserDbModel](tag, None, "breakoutRoom_user") {
val breakoutRoomId = column[String]("breakoutRoomId", O.PrimaryKey)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val joinURL = column[String]("joinURL")
val joinedAt = column[Option[java.sql.Timestamp]]("joinedAt")
val assignedAt = column[Option[java.sql.Timestamp]]("assignedAt")
override def * = (breakoutRoomId, userId, joinURL, joinedAt, assignedAt) <> (BreakoutRoomUserDbModel.tupled, BreakoutRoomUserDbModel.unapply)
override def * = (breakoutRoomId, meetingId, userId, joinURL, joinedAt, assignedAt) <> (BreakoutRoomUserDbModel.tupled, BreakoutRoomUserDbModel.unapply)
}
object BreakoutRoomUserDAO {
def prepareInsert(breakoutRoomId: String, userId: String, joinURL: String) = {
def prepareInsert(breakoutRoomId: String, meetingId: String, userId: String, joinURL: String) = {
TableQuery[BreakoutRoomUserDbTableDef].insertOrUpdate(
BreakoutRoomUserDbModel(
breakoutRoomId = breakoutRoomId,
meetingId = meetingId,
userId = userId,
joinURL = joinURL,
joinedAt = None,
@ -40,8 +43,9 @@ object BreakoutRoomUserDAO {
)
}
def prepareDelete(breakoutRoomId: String, userId: String) = {
def prepareDelete(breakoutRoomId: String, meetingId: String, userId: String) = {
var query = TableQuery[BreakoutRoomUserDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
//Sometimes the user is moved before he joined in any room, in this case remove all assignments
@ -51,10 +55,10 @@ object BreakoutRoomUserDAO {
query.delete
}
def updateRoomChanged(userId: String, fromBreakoutRoomId: String, toBreakoutRoomId: String, joinUrl: String) = {
def updateRoomChanged(meetingId: String, userId: String, fromBreakoutRoomId: String, toBreakoutRoomId: String, joinUrl: String) = {
DatabaseConnection.db.run(DBIO.seq(
BreakoutRoomUserDAO.prepareDelete(fromBreakoutRoomId, userId),
BreakoutRoomUserDAO.prepareInsert(toBreakoutRoomId, userId, joinUrl)
BreakoutRoomUserDAO.prepareDelete(fromBreakoutRoomId, meetingId, userId),
BreakoutRoomUserDAO.prepareInsert(toBreakoutRoomId, meetingId, userId, joinUrl)
).transactionally)
.onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) changed on breakoutRoom_user table!")
@ -62,9 +66,10 @@ object BreakoutRoomUserDAO {
}
}
def updateUserJoined(usersInRoom: Vector[String], breakoutRoom: BreakoutRoom2x) = {
def updateUserJoined(meetingId: String, usersInRoom: Vector[String], breakoutRoom: BreakoutRoom2x) = {
DatabaseConnection.db.run(
TableQuery[BreakoutRoomUserDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.userId inSet usersInRoom)
.filter(_.breakoutRoomId === breakoutRoom.id)
.filter(_.joinedAt.isEmpty)
@ -76,9 +81,9 @@ object BreakoutRoomUserDAO {
}
}
def updateUserEjected(userId: String, breakoutRoomId: String) = {
def updateUserEjected(meetingId: String, userId: String, breakoutRoomId: String) = {
DatabaseConnection.db.run(DBIO.seq(
BreakoutRoomUserDAO.prepareDelete(userId, breakoutRoomId),
BreakoutRoomUserDAO.prepareDelete(meetingId, userId, breakoutRoomId),
).transactionally)
.onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) deleted on breakoutRoom_user table!")
@ -90,7 +95,7 @@ object BreakoutRoomUserDAO {
for {
(redirectToHtml5JoinURL, redirectJoinURL) <- BreakoutHdlrHelpers.getRedirectUrls(liveMeeting, userId, room.externalId, room.sequence.toString)
} yield {
DatabaseConnection.db.run(BreakoutRoomUserDAO.prepareInsert(room.id, userId, redirectToHtml5JoinURL))
DatabaseConnection.db.run(BreakoutRoomUserDAO.prepareInsert(room.id, liveMeeting.props.meetingProp.intId, userId, redirectToHtml5JoinURL))
.onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on breakoutRoom_user table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting breakoutRoom_user: $e")
@ -98,10 +103,11 @@ object BreakoutRoomUserDAO {
}
}
// def updateUserJoined(userId: String, breakoutRoomId: String) = {
// def updateUserJoined(meetingId: String, userId: String, breakoutRoomId: String) = {
// DatabaseConnection.db.run(
// TableQuery[BreakoutRoomUserDbTableDef]
// .filter(_.breakoutRoomId === breakoutRoomId)
// .filter(_.meetingId === meetingId)
// .filter(_.userId === userId)
// .map(u => u.joinedAt)
// .update(Some(new java.sql.Timestamp(System.currentTimeMillis())))

View File

@ -9,7 +9,7 @@ case class CaptionDbModel(
meetingId: String,
captionType: String,
userId: String,
lang: String,
locale: String,
captionText: String,
createdAt: java.sql.Timestamp
)
@ -19,10 +19,10 @@ class CaptionTableDef(tag: Tag) extends Table[CaptionDbModel](tag, None, "captio
val meetingId = column[String]("meetingId")
val captionType = column[String]("captionType")
val userId = column[String]("userId")
val lang = column[String]("lang")
val locale = column[String]("locale")
val captionText = column[String]("captionText")
val createdAt = column[java.sql.Timestamp]("createdAt")
def * = (captionId, meetingId, captionType, userId, lang, captionText, createdAt) <> (CaptionDbModel.tupled, CaptionDbModel.unapply)
def * = (captionId, meetingId, captionType, userId, locale, captionText, createdAt) <> (CaptionDbModel.tupled, CaptionDbModel.unapply)
}
object CaptionTypes {
@ -32,7 +32,7 @@ object CaptionTypes {
object CaptionDAO {
def insertOrUpdateAudioCaption(captionId: String, meetingId: String, userId: String, transcript: String, lang: String) = {
def insertOrUpdateAudioCaption(captionId: String, meetingId: String, userId: String, transcript: String, locale: String) = {
DatabaseConnection.db.run(
TableQuery[CaptionTableDef].insertOrUpdate(
CaptionDbModel(
@ -40,7 +40,7 @@ object CaptionDAO {
meetingId = meetingId,
captionType = CaptionTypes.AUDIO_TRANSCRIPTION,
userId = userId,
lang = lang,
locale = locale,
captionText = transcript,
createdAt = new java.sql.Timestamp(System.currentTimeMillis())
)
@ -68,7 +68,7 @@ object CaptionDAO {
SELECT "captionId", "captionText", "createdAt"
FROM caption
WHERE "meetingId" = ${meetingId}
AND lang = ${locale}
AND locale = ${locale}
AND "captionType" = ${CaptionTypes.TYPED}
order by "createdAt" desc
limit 2
@ -80,13 +80,13 @@ object CaptionDAO {
LIMIT 1
)
RETURNING *)
INSERT INTO caption ("captionId", "meetingId", "captionType", "userId", "lang", "captionText", "createdAt")
INSERT INTO caption ("captionId", "meetingId", "captionType", "userId", "locale", "captionText", "createdAt")
SELECT md5(random()::text || clock_timestamp()::text), ${meetingId}, 'TYPED', ${userId}, ${locale}, ${line}, current_timestamp
WHERE NOT EXISTS (SELECT * FROM upsert)
AND ${line} NOT IN (SELECT "captionText"
FROM caption
WHERE "meetingId" = ${meetingId}
AND lang = ${locale}
AND locale = ${locale}
AND "captionType" = ${CaptionTypes.TYPED}
order by "createdAt" desc
limit 2

View File

@ -0,0 +1,41 @@
package org.bigbluebutton.core.db
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class CaptionLocaleDbModel(
meetingId: String,
locale: String,
captionType: String,
ownerUserId: String,
updatedAt: java.sql.Timestamp
)
class CaptionLocaleTableDef(tag: Tag) extends Table[CaptionLocaleDbModel](tag, None, "caption_locale") {
val meetingId = column[String]("meetingId", O.PrimaryKey)
val locale = column[String]("locale", O.PrimaryKey)
val captionType = column[String]("captionType", O.PrimaryKey)
val ownerUserId = column[String]("ownerUserId")
val updatedAt = column[java.sql.Timestamp]("updatedAt")
def * = (meetingId, locale, captionType, ownerUserId, updatedAt) <> (CaptionLocaleDbModel.tupled, CaptionLocaleDbModel.unapply)
}
object CaptionLocaleDAO {
def insertOrUpdateCaptionLocale(meetingId: String, locale: String, captionType: String, ownerUserId: String) = {
DatabaseConnection.db.run(
TableQuery[CaptionLocaleTableDef].insertOrUpdate(
CaptionLocaleDbModel(
meetingId = meetingId,
locale = locale,
captionType = captionType,
ownerUserId = ownerUserId,
updatedAt = new java.sql.Timestamp(System.currentTimeMillis())
)
)
).onComplete {
case Success(_) => DatabaseConnection.logger.debug(s"Upserted caption with ID $meetingId-$locale on CaptionLocale table")
case Failure(e) => DatabaseConnection.logger.debug(s"Error upserting caption on CaptionLocale: $e")
}
}
}

View File

@ -7,6 +7,14 @@ import org.bigbluebutton.core.apps.groupchats.GroupChatApp
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class MeetingSystemColumnsDbModel(
loginUrl: Option[String],
logoutUrl: Option[String],
customLogoUrl: Option[String],
bannerText: Option[String],
bannerColor: Option[String],
)
case class MeetingDbModel(
meetingId: String,
extId: String,
@ -19,12 +27,11 @@ case class MeetingDbModel(
presentationUploadExternalDescription: String,
presentationUploadExternalUrl: String,
learningDashboardAccessToken: String,
logoutUrl: String,
customLogoUrl: Option[String],
bannerText: Option[String],
bannerColor: Option[String],
systemColumns: MeetingSystemColumnsDbModel,
createdTime: Long,
durationInSeconds: Int,
endWhenNoModerator: Boolean,
endWhenNoModeratorDelayInMinutes: Int,
endedAt: Option[java.sql.Timestamp],
endedReasonCode: Option[String],
endedBy: Option[String],
@ -43,12 +50,11 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
presentationUploadExternalDescription,
presentationUploadExternalUrl,
learningDashboardAccessToken,
logoutUrl,
customLogoUrl,
bannerText,
bannerColor,
systemColumns,
createdTime,
durationInSeconds,
endWhenNoModerator,
endWhenNoModeratorDelayInMinutes,
endedAt,
endedReasonCode,
endedBy
@ -64,12 +70,16 @@ 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 loginUrl = column[Option[String]]("loginUrl")
val logoutUrl = column[Option[String]]("logoutUrl")
val customLogoUrl = column[Option[String]]("customLogoUrl")
val bannerText = column[Option[String]]("bannerText")
val bannerColor = column[Option[String]]("bannerColor")
val systemColumns = (loginUrl, logoutUrl, customLogoUrl, bannerText, bannerColor) <> (MeetingSystemColumnsDbModel.tupled, MeetingSystemColumnsDbModel.unapply)
val createdTime = column[Long]("createdTime")
val durationInSeconds = column[Int]("durationInSeconds")
val endWhenNoModerator = column[Boolean]("endWhenNoModerator")
val endWhenNoModeratorDelayInMinutes = column[Int]("endWhenNoModeratorDelayInMinutes")
val endedAt = column[Option[java.sql.Timestamp]]("endedAt")
val endedReasonCode = column[Option[String]]("endedReasonCode")
val endedBy = column[Option[String]]("endedBy")
@ -91,7 +101,15 @@ object MeetingDAO {
presentationUploadExternalDescription = meetingProps.meetingProp.presentationUploadExternalDescription,
presentationUploadExternalUrl = meetingProps.meetingProp.presentationUploadExternalUrl,
learningDashboardAccessToken = meetingProps.password.learningDashboardAccessToken,
logoutUrl = meetingProps.systemProps.logoutUrl,
systemColumns = MeetingSystemColumnsDbModel(
loginUrl = meetingProps.systemProps.loginUrl match {
case "" => None
case loginUrl => Some(loginUrl)
},
logoutUrl = meetingProps.systemProps.logoutUrl match {
case "" => None
case logoutUrl => Some(logoutUrl)
},
customLogoUrl = meetingProps.systemProps.customLogoURL match {
case "" => None
case logoUrl => Some(logoUrl)
@ -104,8 +122,11 @@ object MeetingDAO {
case "" => None
case bannerColor => Some(bannerColor)
},
),
createdTime = meetingProps.durationProps.createdTime,
durationInSeconds = meetingProps.durationProps.duration * 60,
endWhenNoModerator = meetingProps.durationProps.endWhenNoModerator,
endWhenNoModeratorDelayInMinutes = meetingProps.durationProps.endWhenNoModeratorDelayInMinutes,
endedAt = None,
endedReasonCode = None,
endedBy = None
@ -123,7 +144,7 @@ object MeetingDAO {
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)
TimerDAO.insert(meetingProps.meetingProp.intId, clientSettings)
LayoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp.meetingLayout)
MeetingClientSettingsDAO.insert(meetingProps.meetingProp.intId, JsonUtils.mapToJson(clientSettings))
}
@ -182,4 +203,27 @@ object MeetingDAO {
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating endedAt=now() Meeting: $e")
}
}
def setAllMeetingsEnded(endedReasonCode: String, endedBy: String) = {
DatabaseConnection.db.run(
TableQuery[MeetingDbTableDef]
.filter(_.endedAt.isEmpty)
.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 all-meetings endedAt=now() on Meeting table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating all-meetings endedAt=now() on Meeting table: $e")
}
}
}

View File

@ -0,0 +1,76 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.common2.msgs.{BbbCommonEnvCoreMsg, NotifyAllInMeetingEvtMsg, NotifyRoleInMeetingEvtMsg, NotifyUserInMeetingEvtMsg}
import PostgresProfile.api._
import spray.json.JsValue
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global
case class NotificationDbModel(
// notificationId: String,
meetingId: String,
notificationType: String,
icon: String,
messageId: String,
messageDescription: String,
messageValues: JsValue,
role: Option[String],
userMeetingId: Option[String],
userId: Option[String],
createdAt: java.sql.Timestamp,
)
class NotificationDbTableDef(tag: Tag) extends Table[NotificationDbModel](tag, None, "notification") {
val meetingId = column[String]("meetingId")
val notificationType = column[String]("notificationType")
val icon = column[String]("icon")
val messageId = column[String]("messageId")
val messageDescription = column[String]("messageDescription")
val messageValues = column[JsValue]("messageValues")
val role = column[Option[String]]("role")
val userMeetingId = column[Option[String]]("userMeetingId")
val userId = column[Option[String]]("userId")
val createdAt = column[java.sql.Timestamp]("createdAt")
override def * = (meetingId, notificationType, icon, messageId, messageDescription, messageValues, role, userMeetingId, userId, createdAt) <> (NotificationDbModel.tupled, NotificationDbModel.unapply)
}
object NotificationDAO {
def insert(notification: BbbCommonEnvCoreMsg) = {
val (meetingId, notificationType, icon, messageId, messageDescription, messageValues, role, userId) = notification.core match {
case event: NotifyAllInMeetingEvtMsg =>
(event.body.meetingId, event.body.notificationType, event.body.icon, event.body.messageId, event.body.messageDescription, event.body.messageValues, None, None)
case event: NotifyRoleInMeetingEvtMsg =>
(event.body.meetingId, event.body.notificationType, event.body.icon, event.body.messageId, event.body.messageDescription, event.body.messageValues, Some(event.body.role), None)
case event: NotifyUserInMeetingEvtMsg =>
(event.body.meetingId, event.body.notificationType, event.body.icon, event.body.messageId, event.body.messageDescription, event.body.messageValues, None, Some(event.body.userId))
case _ =>
("","", "", "", "", Vector(""), None, None)
}
if (notificationType != "") {
DatabaseConnection.db.run(
TableQuery[NotificationDbTableDef].forceInsert(
NotificationDbModel(
meetingId,
notificationType,
icon,
messageId,
messageDescription,
JsonUtils.vectorToJson(messageValues),
role,
userMeetingId = userId match {
case Some(u) => Some(meetingId)
case _ => None
},
userId,
createdAt = new java.sql.Timestamp(System.currentTimeMillis())
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted/updated on Notification table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting/updating Notification: $e")
}
}
}
}

View File

@ -13,11 +13,12 @@ object Permission {
val allowedRoles = List("MODERATOR","VIEWER","PRESENTER")
}
case class PluginDataChannelMessageDbModel(
case class PluginDataChannelEntryDbModel(
meetingId: String,
pluginName: String,
dataChannel: String,
// messageId: Option[String] = None,
channelName: String,
subChannelName: String,
// entryId: Option[String] = None,
payloadJson: JsValue,
fromUserId: String,
toRoles: Option[List[String]],
@ -26,28 +27,30 @@ case class PluginDataChannelMessageDbModel(
deletedAt: Option[java.sql.Timestamp],
)
class PluginDataChannelMessageDbTableDef(tag: Tag) extends Table[PluginDataChannelMessageDbModel](tag, None, "pluginDataChannelMessage") {
class PluginDataChannelEntryDbTableDef(tag: Tag) extends Table[PluginDataChannelEntryDbModel](tag, None, "pluginDataChannelEntry") {
val meetingId = column[String]("meetingId", O.PrimaryKey)
val pluginName = column[String]("pluginName", O.PrimaryKey)
val dataChannel = column[String]("dataChannel", O.PrimaryKey)
// val messageId = column[Option[String]]("messageId", O.PrimaryKey) //// The messageId is generated by the database
val channelName = column[String]("channelName", O.PrimaryKey)
val subChannelName = column[String]("subChannelName")
// val entryId = column[Option[String]]("messageId", O.PrimaryKey) //// The messageId is generated by the database
val payloadJson = column[JsValue]("payloadJson")
val fromUserId = column[String]("fromUserId")
val toRoles = column[Option[List[String]]]("toRoles")
val toUserIds = column[Option[List[String]]]("toUserIds")
val createdAt = column[java.sql.Timestamp]("createdAt")
val deletedAt = column[Option[java.sql.Timestamp]]("deletedAt")
override def * = (meetingId, pluginName, dataChannel, payloadJson, fromUserId, toRoles, toUserIds, createdAt, deletedAt) <> (PluginDataChannelMessageDbModel.tupled, PluginDataChannelMessageDbModel.unapply)
override def * = (meetingId, pluginName, channelName, subChannelName, payloadJson, fromUserId, toRoles, toUserIds, createdAt, deletedAt) <> (PluginDataChannelEntryDbModel.tupled, PluginDataChannelEntryDbModel.unapply)
}
object PluginDataChannelMessageDAO {
def insert(meetingId: String, pluginName: String, dataChannel: String, senderUserId: String, payloadJson: String, toRoles: List[String], toUserIds: List[String]) = {
object PluginDataChannelEntryDAO {
def insert(meetingId: String, pluginName: String, channelName: String, subChannelName: String, senderUserId: String, payloadJson: String, toRoles: List[String], toUserIds: List[String]) = {
DatabaseConnection.db.run(
TableQuery[PluginDataChannelMessageDbTableDef].forceInsert(
PluginDataChannelMessageDbModel(
TableQuery[PluginDataChannelEntryDbTableDef].forceInsert(
PluginDataChannelEntryDbModel(
meetingId = meetingId,
pluginName = pluginName,
dataChannel = dataChannel,
channelName = channelName,
subChannelName = subChannelName,
payloadJson = JsonUtils.stringToJson(payloadJson),
fromUserId = senderUserId,
toRoles = toRoles.map(_.toUpperCase).filter(Permission.allowedRoles.contains) match {
@ -60,54 +63,62 @@ object PluginDataChannelMessageDAO {
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on PluginDataChannelMessage table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PluginDataChannelMessage: $e")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on PluginDataChannelEntry table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PluginDataChannelEntry: $e")
}
}
def reset(meetingId: String, pluginName: String, dataChannel: String) = {
def reset(meetingId: String, pluginName: String,
channelName: String, subChannelName: String) = {
DatabaseConnection.db.run(
TableQuery[PluginDataChannelMessageDbTableDef]
TableQuery[PluginDataChannelEntryDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.pluginName === pluginName)
.filter(_.dataChannel === dataChannel)
.filter(_.channelName === channelName)
.filter(_.subChannelName === subChannelName)
.filter(_.deletedAt.isEmpty)
.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")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated deleted=now() on pluginDataChannelEntry table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating deleted=now() pluginDataChannelEntry: $e")
}
}
def getMessageSender(meetingId: String, pluginName: String, dataChannel: String, messageId: String): String = {
def getMessageSender(meetingId: String, pluginName: String, channelName: String,
subChannelName: String, entryId: String): String = {
val query = sql"""SELECT "fromUserId"
FROM "pluginDataChannelMessage"
FROM "pluginDataChannelEntry"
WHERE "deletedAt" is null
AND "meetingId" = ${meetingId}
AND "pluginName" = ${pluginName}
AND "dataChannel" = ${dataChannel}
AND "messageId" = ${messageId}""".as[String].headOption
AND "channelName" = ${channelName}
AND "subChannelName" = ${subChannelName}
AND "entryId" = ${entryId}""".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)
logger.debug("Message {} not found in database (maybe it was deleted).", entryId)
""
}
}
}
def delete(meetingId: String, pluginName: String, dataChannel: String, messageId: String) = {
def delete(meetingId: String, pluginName: String,
channelName: String, subChannelName: String, entryId: String) = {
DatabaseConnection.db.run(
sqlu"""UPDATE "pluginDataChannelMessage" SET
sqlu"""UPDATE "pluginDataChannelEntry" SET
"deletedAt" = current_timestamp
WHERE "meetingId" = ${meetingId}
WHERE "deletedAt" is null
AND "meetingId" = ${meetingId}
AND "pluginName" = ${pluginName}
AND "dataChannel" = ${dataChannel}
AND "messageId" = ${messageId}"""
AND "channelName" = ${channelName}
AND "subChannelName" = ${subChannelName}
AND "entryId" = ${entryId}"""
).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")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated deleted=now() on pluginDataChannelEntry table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating deleted=now() pluginDataChannelEntry: $e")
}
}

View File

@ -10,23 +10,26 @@ import scala.util.{ Failure, Success }
case class PollResponseDbModel(
pollId: String,
optionId: Option[Int],
meetingId: Option[String],
userId: Option[String]
)
class PollResponseDbTableDef(tag: Tag) extends Table[PollResponseDbModel](tag, None, "poll_response") {
val pollId = column[String]("pollId")
val optionId = column[Option[Int]]("optionId")
val meetingId = column[Option[String]]("meetingId")
val userId = column[Option[String]]("userId")
val * = (pollId, optionId, userId) <> (PollResponseDbModel.tupled, PollResponseDbModel.unapply)
val * = (pollId, optionId, meetingId, userId) <> (PollResponseDbModel.tupled, PollResponseDbModel.unapply)
}
object PollResponseDAO {
def insert(poll: Poll, userId: String, seqOptionIds: Seq[Int]) = {
def insert(poll: Poll, meetingId: String, userId: String, seqOptionIds: Seq[Int]) = {
//Clear previous responses of the user and add all
DatabaseConnection.db.run(
TableQuery[PollResponseDbTableDef]
.filter(_.pollId === poll.id)
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.delete
).onComplete {
@ -39,6 +42,9 @@ object PollResponseDAO {
PollResponseDbModel(
pollId = poll.id,
optionId = Some(optionId),
meetingId = {
if (poll.isSecret) None else Some(meetingId)
},
userId = {
if (poll.isSecret) None else Some(userId)
}
@ -57,6 +63,7 @@ object PollResponseDAO {
PollResponseDbModel(
pollId = poll.id,
optionId = None,
meetingId = Some(meetingId),
userId = Some(userId)
)
)

View File

@ -3,6 +3,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.DefaultJsonProtocol.{ StringJsonFormat, vectorFormat }
import spray.json.{ JsArray, JsBoolean, JsNumber, JsObject, JsString, JsValue, JsonWriter, _ }
import scala.util.{ Failure, Success, Try }
@ -55,6 +56,10 @@ object JsonUtils {
genericMap.toJson
}
def vectorToJson(genericVector: Vector[String]) = {
genericVector.toJson
}
def stringToJson(jsonString: String): JsValue = {
Try(jsonString.parseJson) match {
case Success(jsValue) => jsValue

View File

@ -1,13 +1,14 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.common2.msgs.AnnotationVO
import PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
case class PresAnnotationDbModel(
annotationId: String,
pageId: String,
meetingId: String,
userId: String,
annotationInfo: String,
lastHistorySequence: Int,
@ -17,17 +18,18 @@ case class PresAnnotationDbModel(
class PresAnnotationDbTableDef(tag: Tag) extends Table[PresAnnotationDbModel](tag, None, "pres_annotation") {
val annotationId = column[String]("annotationId", O.PrimaryKey)
val pageId = column[String]("pageId")
val meetingId = column[String]("meetingId")
val userId = column[String]("userId")
val annotationInfo = column[String]("annotationInfo")
val lastHistorySequence = column[Int]("lastHistorySequence")
val lastUpdatedAt = column[java.sql.Timestamp]("lastUpdatedAt")
// def whiteboard = foreignKey("whiteboard_fk", whiteboardId, Whiteboards)(_.whiteboardId, onDelete = ForeignKeyAction.Cascade)
def * = (annotationId, pageId, userId, annotationInfo, lastHistorySequence, lastUpdatedAt) <> (PresAnnotationDbModel.tupled, PresAnnotationDbModel.unapply)
def * = (annotationId, pageId, meetingId, userId, annotationInfo, lastHistorySequence, lastUpdatedAt) <> (PresAnnotationDbModel.tupled, PresAnnotationDbModel.unapply)
}
object PresAnnotationDAO {
def insertOrUpdate(annotation: AnnotationVO, annotationDiff: AnnotationVO) = {
PresAnnotationHistoryDAO.insert(annotationDiff).onComplete {
def insertOrUpdate(meetingId: String, annotation: AnnotationVO, annotationDiff: AnnotationVO) = {
PresAnnotationHistoryDAO.insert(meetingId, annotationDiff).onComplete {
case Success(sequence) => {
DatabaseConnection.logger.debug(s"Sequence generated to PresAnnotationHistory record: $sequence")
DatabaseConnection.db.run(
@ -35,6 +37,7 @@ object PresAnnotationDAO {
PresAnnotationDbModel(
annotationId = annotation.id,
pageId = annotation.wbId,
meetingId = meetingId,
userId = annotation.userId,
annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo).compactPrint,
lastHistorySequence = sequence.getOrElse(0),
@ -42,8 +45,8 @@ object PresAnnotationDAO {
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on PresAnnotation table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PresAnnotation: $e")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted or updated on PresAnnotation table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting or updating PresAnnotation: $e")
}
}
@ -51,15 +54,44 @@ object PresAnnotationDAO {
}
}
def delete(wbId: String, userId: String, annotationId: String) = {
def prepareInsertOrUpdate(meetingId: String, annotation: AnnotationVO) = {
TableQuery[PresAnnotationDbTableDef].insertOrUpdate(
PresAnnotationDbModel(
annotationId = annotation.id,
pageId = annotation.wbId,
meetingId = meetingId,
userId = annotation.userId,
annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo).compactPrint,
lastHistorySequence = 0,
lastUpdatedAt = new java.sql.Timestamp(System.currentTimeMillis())
)
)
}
PresAnnotationHistoryDAO.delete(wbId, userId, annotationId).onComplete {
def insertOrUpdateMap(meetingId: String, annotations: Array[AnnotationVO]) = {
DatabaseConnection.db.run(
DBIO.sequence(
annotations.map { annotation =>
prepareInsertOrUpdate(meetingId, annotation)
}.toVector
).transactionally
)
.onComplete {
case Success(rowsAffected) =>
DatabaseConnection.logger.debug(s"${rowsAffected.sum} row(s) inserted or updated on PresAnnotation table!")
case Failure(e) =>
DatabaseConnection.logger.debug(s"Error inserting or updating PresAnnotation: $e")
}
}
def delete(wbId: String, meetingId: String, userId: String, annotationId: String) = {
PresAnnotationHistoryDAO.delete(wbId, meetingId, userId, annotationId).onComplete {
case Success(sequence) => {
DatabaseConnection.db.run(
TableQuery[PresAnnotationDbTableDef]
.filter(_.annotationId === annotationId)
.map(a => (a.annotationInfo, a.lastHistorySequence, a.lastUpdatedAt))
.update("", sequence.getOrElse(0), new java.sql.Timestamp(System.currentTimeMillis()))
.map(a => (a.annotationInfo, a.lastHistorySequence, a.meetingId, a.userId, a.lastUpdatedAt))
.update("", sequence.getOrElse(0), meetingId, userId, new java.sql.Timestamp(System.currentTimeMillis()))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated annotationInfo=null on PresAnnotation table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating annotationInfo=null PresAnnotation: $e")
@ -69,4 +101,16 @@ object PresAnnotationDAO {
}
}
def delete(meetingId: String, userId: String, annotationIds: Array[String]) = {
DatabaseConnection.db.run(
TableQuery[PresAnnotationDbTableDef]
.filter(_.annotationId inSet annotationIds)
.map(a => (a.annotationInfo, a.lastHistorySequence, a.meetingId, a.userId, a.lastUpdatedAt))
.update("", 0, meetingId, userId, new java.sql.Timestamp(System.currentTimeMillis()))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated annotationInfo=null on PresAnnotation table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating annotationInfo=null PresAnnotation: $e")
}
}
}

View File

@ -7,6 +7,7 @@ case class PresAnnotationHistoryDbModel(
sequence: Option[Int] = None,
annotationId: String,
pageId: String,
meetingId: String,
userId: String,
annotationInfo: String
// lastUpdatedAt: java.sql.Timestamp = new java.sql.Timestamp(System.currentTimeMillis())
@ -16,16 +17,17 @@ class PresAnnotationHistoryDbTableDef(tag: Tag) extends Table[PresAnnotationHist
val sequence = column[Option[Int]]("sequence", O.PrimaryKey, O.AutoInc)
val annotationId = column[String]("annotationId")
val pageId = column[String]("pageId")
val meetingId = column[String]("meetingId")
val userId = column[String]("userId")
val annotationInfo = column[String]("annotationInfo")
// val lastUpdatedAt = column[java.sql.Timestamp]("lastUpdatedAt")
// def whiteboard = foreignKey("whiteboard_fk", whiteboardId, Whiteboards)(_.whiteboardId, onDelete = ForeignKeyAction.Cascade)
def * = (sequence, annotationId, pageId, userId, annotationInfo) <> (PresAnnotationHistoryDbModel.tupled, PresAnnotationHistoryDbModel.unapply)
def * = (sequence, annotationId, pageId, meetingId, userId, annotationInfo) <> (PresAnnotationHistoryDbModel.tupled, PresAnnotationHistoryDbModel.unapply)
}
object PresAnnotationHistoryDAO {
def insert(annotationDiff: AnnotationVO) = {
def insert(meetingId: String, annotationDiff: AnnotationVO) = {
DatabaseConnection.db.run(
TableQuery[PresAnnotationHistoryDbTableDef].returning(
TableQuery[PresAnnotationHistoryDbTableDef].map(_.sequence)
@ -33,13 +35,14 @@ object PresAnnotationHistoryDAO {
None,
annotationId = annotationDiff.id,
pageId = annotationDiff.wbId,
meetingId = meetingId,
userId = annotationDiff.userId,
annotationInfo = JsonUtils.mapToJson(annotationDiff.annotationInfo).compactPrint
)
)
}
def delete(wbId: String, userId: String, annotationId: String) = {
def delete(wbId: String, meetingId: String, userId: String, annotationId: String) = {
DatabaseConnection.db.run(
TableQuery[PresAnnotationHistoryDbTableDef].returning(
TableQuery[PresAnnotationHistoryDbTableDef].map(_.sequence)
@ -47,6 +50,7 @@ object PresAnnotationHistoryDAO {
None,
annotationId = annotationId,
pageId = wbId,
meetingId = meetingId,
userId = userId,
annotationInfo = ""
)

View File

@ -8,6 +8,7 @@ import scala.util.{ Failure, Success }
case class PresPageCursorDbModel(
pageId: String,
meetingId: String,
userId: String,
xPercent: Double,
yPercent: Double,
@ -16,11 +17,11 @@ case class PresPageCursorDbModel(
class PresPageCursorDbTableDef(tag: Tag) extends Table[PresPageCursorDbModel](tag, None, "pres_page_cursor") {
override def * = (
pageId, userId, xPercent, yPercent, lastUpdatedAt
pageId, meetingId, userId, xPercent, yPercent, lastUpdatedAt
) <> (PresPageCursorDbModel.tupled, PresPageCursorDbModel.unapply)
def pk = primaryKey("pres_page_cursor_pkey", (pageId, userId))
val pageId = column[String]("pageId")
val userId = column[String]("userId")
val pageId = column[String]("pageId", O.PrimaryKey)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val xPercent = column[Double]("xPercent")
val yPercent = column[Double]("yPercent")
val lastUpdatedAt = column[java.sql.Timestamp]("lastUpdatedAt")
@ -28,11 +29,12 @@ class PresPageCursorDbTableDef(tag: Tag) extends Table[PresPageCursorDbModel](ta
object PresPageCursorDAO {
def insertOrUpdate(pageId: String, userId: String, xPercent: Double, yPercent: Double) = {
def insertOrUpdate(pageId: String, meetingId: String, userId: String, xPercent: Double, yPercent: Double) = {
DatabaseConnection.db.run(
TableQuery[PresPageCursorDbTableDef].insertOrUpdate(
PresPageCursorDbModel(
pageId = pageId,
meetingId = meetingId,
userId = userId,
xPercent = xPercent,
yPercent = yPercent,

View File

@ -8,33 +8,35 @@ import scala.util.{Failure, Success}
case class PresPageWritersDbModel(
pageId: String,
meetingId: String,
userId: String,
changedModeOn: Long,
)
class PresPageWritersDbTableDef(tag: Tag) extends Table[PresPageWritersDbModel](tag, None, "pres_page_writers") {
override def * = (
pageId, userId, changedModeOn) <> (PresPageWritersDbModel.tupled, PresPageWritersDbModel.unapply)
def pk = primaryKey("pres_page_writers_pkey", (pageId, userId))
val pageId = column[String]("pageId")
val userId = column[String]("userId")
pageId, meetingId, userId, changedModeOn) <> (PresPageWritersDbModel.tupled, PresPageWritersDbModel.unapply)
val pageId = column[String]("pageId", O.PrimaryKey)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val changedModeOn = column[Long]("changedModeOn")
}
object PresPageWritersDAO {
def updateMultiuser(whiteboard: Whiteboard) = {
def updateMultiuser(meetingId: String, whiteboard: Whiteboard) = {
val deleteQuery = TableQuery[PresPageWritersDbTableDef]
.filter(_.pageId === whiteboard.id)
if(whiteboard.multiUser.length > 0) {
deleteQuery.filter(_.meetingId === meetingId)
deleteQuery.filterNot(_.userId inSet whiteboard.multiUser)
}
DatabaseConnection.db.run(deleteQuery.delete).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Users deleted from Whiteboard ${whiteboard.id}")
case Failure(e) => DatabaseConnection.logger.error(s"Error deleting users from whiteboard: $e")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Users deleted from pres_page_writers ${whiteboard.id}")
case Failure(e) => DatabaseConnection.logger.error(s"Error deleting users from pres_page_writers: $e")
}
for {
@ -44,6 +46,7 @@ object PresPageWritersDAO {
TableQuery[PresPageWritersDbTableDef].insertOrUpdate(
PresPageWritersDbModel(
pageId = whiteboard.id,
meetingId = meetingId,
userId = userId,
changedModeOn = whiteboard.changedModeOn
)

View File

@ -35,10 +35,11 @@ object SharedNotesSessionDAO {
}
}
def delete(intId: String, sessionId: String) = {
def delete(meetingId: String, userId: String, sessionId: String) = {
DatabaseConnection.db.run(
TableQuery[SharedNotesSessionDbTableDef]
.filter(_.userId === intId)
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.filter(_.sessionId === sessionId)
.delete
).onComplete {

View File

@ -1,5 +1,6 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.ClientSettings.{getConfigPropertyValueByPathAsBooleanOrElse, getConfigPropertyValueByPathAsIntOrElse}
import org.bigbluebutton.core.apps.TimerModel
import org.bigbluebutton.core.apps.TimerModel.{getAccumulated, getEndedAt, getIsActive, getRunning, getStartedAt, getStopwatch, getTime, getTrack}
import slick.jdbc.PostgresProfile.api._
@ -33,7 +34,12 @@ class TimerDbTableDef(tag: Tag) extends Table[TimerDbModel](tag, None, "timer")
}
object TimerDAO {
def insert(meetingId: String) = {
def insert(meetingId: String, clientSettings: Map[String, Object]) = {
val timerEnabled = getConfigPropertyValueByPathAsBooleanOrElse(clientSettings, "public.timer.enabled", alternativeValue = true)
if(timerEnabled) {
val timerDefaultTimeInMinutes = getConfigPropertyValueByPathAsIntOrElse(clientSettings, "public.timer.time", 5)
val timerDefaultTimeInMilli = timerDefaultTimeInMinutes * 60000
DatabaseConnection.db.run(
TableQuery[TimerDbTableDef].insertOrUpdate(
TimerDbModel(
@ -41,7 +47,7 @@ object TimerDAO {
stopwatch = true,
running = false,
active = false,
time = 300000,
time = timerDefaultTimeInMilli,
accumulated = 0,
startedOn = 0,
endedOn = 0,
@ -53,6 +59,7 @@ object TimerDAO {
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting Timer: $e")
}
}
}
def update(meetingId: String, timerModel: TimerModel) = {
DatabaseConnection.db.run(

View File

@ -8,6 +8,7 @@ import scala.util.{Failure, Success }
case class UserBreakoutRoomDbModel(
breakoutRoomId: String,
meetingId: String,
userId: String,
isDefaultName: Boolean,
sequence: Int,
@ -17,7 +18,8 @@ case class UserBreakoutRoomDbModel(
class UserBreakoutRoomDbTableDef(tag: Tag) extends Table[UserBreakoutRoomDbModel](tag, None, "user_breakoutRoom") {
override def * = (
breakoutRoomId, userId, isDefaultName, sequence, shortName, currentlyInRoom) <> (UserBreakoutRoomDbModel.tupled, UserBreakoutRoomDbModel.unapply)
breakoutRoomId, meetingId, userId, isDefaultName, sequence, shortName, currentlyInRoom) <> (UserBreakoutRoomDbModel.tupled, UserBreakoutRoomDbModel.unapply)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val breakoutRoomId = column[String]("breakoutRoomId")
val isDefaultName = column[Boolean]("isDefaultName")
@ -28,11 +30,11 @@ class UserBreakoutRoomDbTableDef(tag: Tag) extends Table[UserBreakoutRoomDbModel
object UserBreakoutRoomDAO {
def updateLastBreakoutRoom(userId: String, breakoutRoom: BreakoutRoom2x) = {
def updateLastBreakoutRoom(meetingId: String, userId: String, breakoutRoom: BreakoutRoom2x) = {
DatabaseConnection.db.run(
TableQuery[UserBreakoutRoomDbTableDef].insertOrUpdate(
UserBreakoutRoomDbModel(
meetingId = meetingId,
userId = userId,
breakoutRoomId = breakoutRoom.id,
isDefaultName = breakoutRoom.isDefaultName,
@ -49,10 +51,11 @@ object UserBreakoutRoomDAO {
}
}
def updateLastBreakoutRoom(usersInRoom: Vector[String], breakoutRoom: BreakoutRoom2x) = {
def updateLastBreakoutRoom(meetingId:String, usersInRoom: Vector[String], breakoutRoom: BreakoutRoom2x) = {
DatabaseConnection.db.run(
TableQuery[UserBreakoutRoomDbTableDef]
.filter(_.meetingId === meetingId)
.filterNot(_.userId inSet usersInRoom)
.filter(_.breakoutRoomId === breakoutRoom.id)
.map(u_bk => u_bk.currentlyInRoom)
@ -68,6 +71,7 @@ object UserBreakoutRoomDAO {
} yield {
TableQuery[UserBreakoutRoomDbTableDef].insertOrUpdate(
UserBreakoutRoomDbModel(
meetingId = meetingId,
userId = userId,
breakoutRoomId = breakoutRoom.id,
isDefaultName = breakoutRoom.isDefaultName,

View File

@ -8,24 +8,27 @@ import scala.util.{Failure, Success}
case class UserCameraDbModel(
streamId: String,
meetingId: String,
userId: String,
)
class UserCameraDbTableDef(tag: Tag) extends Table[UserCameraDbModel](tag, None, "user_camera") {
override def * = (
streamId, userId) <> (UserCameraDbModel.tupled, UserCameraDbModel.unapply)
streamId, meetingId, userId) <> (UserCameraDbModel.tupled, UserCameraDbModel.unapply)
val streamId = column[String]("streamId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val userId = column[String]("userId")
}
object UserCameraDAO {
def insert(webcam: WebcamStream) = {
def insert(meetingId: String, webcam: WebcamStream) = {
DatabaseConnection.db.run(
TableQuery[UserCameraDbTableDef].forceInsert(
UserCameraDbModel(
streamId = webcam.streamId,
userId = webcam.userId
meetingId = meetingId,
userId = webcam.userId,
)
)
).onComplete {

View File

@ -14,7 +14,7 @@ case class UserClientSettingsDbModel(
class UserClientSettingsDbTableDef(tag: Tag) extends Table[UserClientSettingsDbModel](tag, "user_clientSettings") {
val userId = column[String]("userId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userClientSettingsJson = column[JsValue]("userClientSettingsJson")
override def * : ProvenShape[UserClientSettingsDbModel] = (userId, meetingId, userClientSettingsJson) <> (UserClientSettingsDbModel.tupled, UserClientSettingsDbModel.unapply)

View File

@ -8,19 +8,21 @@ case class UserConnectionStatusDbModel(
userId: String,
meetingId: String,
connectionAliveAt: Option[java.sql.Timestamp],
userClientResponseAt: Option[java.sql.Timestamp],
networkRttInMs: Option[Double]
networkRttInMs: Option[Double],
status: String,
statusUpdatedAt: Option[java.sql.Timestamp]
)
class UserConnectionStatusDbTableDef(tag: Tag) extends Table[UserConnectionStatusDbModel](tag, None, "user_connectionStatus") {
override def * = (
userId, meetingId, connectionAliveAt, userClientResponseAt, networkRttInMs
userId, meetingId, connectionAliveAt, networkRttInMs, status, statusUpdatedAt
) <> (UserConnectionStatusDbModel.tupled, UserConnectionStatusDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val meetingId = column[String]("meetingId", O.PrimaryKey)
val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt")
val userClientResponseAt = column[Option[java.sql.Timestamp]]("userClientResponseAt")
val networkRttInMs = column[Option[Double]]("networkRttInMs")
val status = column[String]("status")
val statusUpdatedAt = column[Option[java.sql.Timestamp]]("statusUpdatedAt")
}
object UserConnectionStatusDAO {
@ -32,8 +34,9 @@ object UserConnectionStatusDAO {
userId = userId,
meetingId = meetingId,
connectionAliveAt = None,
userClientResponseAt = None,
networkRttInMs = None
networkRttInMs = None,
status = "normal",
statusUpdatedAt = None
)
)
).onComplete {
@ -42,28 +45,24 @@ object UserConnectionStatusDAO {
}
}
def updateUserAlive(userId: String) = {
def updateUserAlive(meetingId: String, userId: String, rtt: Option[Double], status: String) = {
DatabaseConnection.db.run(
TableQuery[UserConnectionStatusDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(t => (t.connectionAliveAt))
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
.map(t => (t.connectionAliveAt, t.networkRttInMs, t.status, t.statusUpdatedAt))
.update(
(
Some(new java.sql.Timestamp(System.currentTimeMillis())),
rtt,
status,
Some(new java.sql.Timestamp(System.currentTimeMillis())),
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated connectionAliveAt on UserConnectionStatus table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating connectionAliveAt on UserConnectionStatus: $e")
}
}
def updateUserRtt(userId: String, networkRttInMs: Double) = {
DatabaseConnection.db.run(
TableQuery[UserConnectionStatusDbTableDef]
.filter(_.userId === userId)
.map(t => (t.networkRttInMs, t.userClientResponseAt))
.update((Some(networkRttInMs), Some(new java.sql.Timestamp(System.currentTimeMillis()))))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated networkRttInMs on UserConnectionStatus table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating networkRttInMs on UserConnectionStatus: $e")
}
}
}

View File

@ -7,27 +7,30 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class UserCustomParameterDbModel(
meetingId: String,
userId: String,
parameter: String,
value: String
)
class UserCustomParameterDbTableDef(tag: Tag) extends Table[UserCustomParameterDbModel](tag, "user_customParameter") {
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val parameter = column[String]("parameter", O.PrimaryKey)
val value = column[String]("value")
override def * : ProvenShape[UserCustomParameterDbModel] = (userId, parameter, value) <> (UserCustomParameterDbModel.tupled, UserCustomParameterDbModel.unapply)
override def * : ProvenShape[UserCustomParameterDbModel] = (meetingId, userId, parameter, value) <> (UserCustomParameterDbModel.tupled, UserCustomParameterDbModel.unapply)
}
object UserCustomParameterDAO {
def insert(userId: String, customParameters: Map[String, String]) = {
def insert(meetingId: String, userId: String, customParameters: Map[String, String]) = {
DatabaseConnection.db.run(DBIO.sequence(
for {
parameter <- customParameters
} yield {
TableQuery[UserCustomParameterDbTableDef].insertOrUpdate(
UserCustomParameterDbModel(
meetingId = meetingId,
userId = userId,
parameter = parameter._1,
value = parameter._2

View File

@ -6,9 +6,9 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
case class UserDbModel(
meetingId: String,
userId: String,
extId: String,
meetingId: String,
name: String,
role: String,
avatar: String = "",
@ -32,10 +32,11 @@ case class UserDbModel(
class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
override def * = (
userId,extId,meetingId,name,role,avatar,color, sessionToken, authToken, authed,joined,joinErrorCode, joinErrorMessage, banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard, enforceLayout) <> (UserDbModel.tupled, UserDbModel.unapply)
meetingId,userId,extId,name,role,avatar,color, sessionToken, authToken, authed,joined,joinErrorCode,
joinErrorMessage, banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard, enforceLayout) <> (UserDbModel.tupled, UserDbModel.unapply)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val extId = column[String]("extId")
val meetingId = column[String]("meetingId")
val name = column[String]("name")
val role = column[String]("role")
val avatar = column[String]("avatar")
@ -89,7 +90,7 @@ object UserDAO {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in User table!")
UserConnectionStatusDAO.insert(meetingId, regUser.id)
UserCustomParameterDAO.insert(regUser.id, regUser.customParameters)
UserCustomParameterDAO.insert(meetingId, regUser.id, regUser.customParameters)
UserClientSettingsDAO.insert(regUser.id, meetingId)
ChatUserDAO.insertUserPublicChat(meetingId, regUser.id)
}
@ -100,6 +101,7 @@ object UserDAO {
def update(regUser: RegisteredUser) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.meetingId === regUser.meetingId)
.filter(_.userId === regUser.id)
.map(u => (u.guest, u.guestStatus, u.role, u.authed, u.joined, u.banned, u.loggedOut))
.update((regUser.guest, regUser.guestStatus, regUser.role, regUser.authed, regUser.joined, regUser.banned, regUser.loggedOut))
@ -113,18 +115,20 @@ object UserDAO {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.meetingId === voiceUserState.meetingId)
.filter(_.userId === voiceUserState.intId)
.map(u => (u.guest, u.guestStatus, u.authed, u.joined))
.update((false, "ALLOW", true, true))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user voice table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user voice: $e")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user table: $e")
}
}
def updateJoinError(userId: String, joinErrorCode: String, joinErrorMessage: String) = {
def updateJoinError(meetingId: String, userId: String, joinErrorCode: String, joinErrorMessage: String) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(u => (u.joined, u.joinErrorCode, u.joinErrorMessage))
.update((false, Some(joinErrorCode), Some(joinErrorMessage)))
@ -134,11 +138,11 @@ object UserDAO {
}
}
def softDelete(intId: String) = {
def softDelete(meetingId: String, userId: String) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === intId)
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(u => (u.loggedOut))
.update((true))
).onComplete {
@ -159,6 +163,62 @@ object UserDAO {
}
}
def transferUserToBreakoutRoomAsAudioOnly(userId: String, meetingIdFrom: String, meetingIdTo: String) = {
//Create a copy of the user using the same userId, but with the meetingId of the breakoutRoom
//The user will be flagged by `transferredFromParentMeeting=true`
DatabaseConnection.db.run(
sqlu"""
WITH upsert AS (
UPDATE "user"
SET "loggedOut"=false
where "userId" = ${userId}
and "meetingId" = ${meetingIdTo}
RETURNING *)
insert into "user"("meetingId","userId","extId","name","role","guest","authed","guestStatus","locked",
"color","loggedOut","expired","ejected","joined","registeredOn","transferredFromParentMeeting","clientType")
select
${meetingIdTo} as "meetingId",
"userId",
"extId",
"name",
"role",
true as "guest",
true as "authed",
'ALLOW' as "guestStatus",
false as "locked",
"color",
"loggedOut",
"expired",
"ejected",
"joined",
"registeredOn",
true as "transferredFromParentMeeting",
'dial-in-user' as "clientType"
from "user"
where "userId" = ${userId}
and "meetingId" = ${meetingIdFrom}
and NOT EXISTS (SELECT * FROM upsert)
"""
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in user (transferredFromParentMeeting) table")
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting user (transferredFromParentMeeting): $e")
}
//Set user as loggedOut in the old meeting (if it is from transferred origin)
DatabaseConnection.db.run(
sqlu"""update "user"
set "loggedOut" = true
where "userId" = ${userId}
and "meetingId" = ${meetingIdFrom}
and "transferredFromParentMeeting" is true
"""
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated in user (transferredFromParentMeeting) table")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating user (transferredFromParentMeeting): $e")
}
}
def permanentlyDeleteAllFromMeeting(meetingId: String) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]

View File

@ -7,6 +7,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class UserReactionDbModel(
meetingId: String,
userId: String,
reactionEmoji: String,
durationInSeconds: Int,
@ -14,19 +15,21 @@ case class UserReactionDbModel(
)
class UserReactionDbTableDef(tag: Tag) extends Table[UserReactionDbModel](tag, "user_reaction") {
val meetingId = column[String]("meetingId")
val userId = column[String]("userId")
val reactionEmoji = column[String]("reactionEmoji")
val durationInSeconds = column[Int]("durationInSeconds")
val createdAt = column[java.sql.Timestamp]("createdAt")
override def * : ProvenShape[UserReactionDbModel] = (userId, reactionEmoji, durationInSeconds, createdAt) <> (UserReactionDbModel.tupled, UserReactionDbModel.unapply)
override def * : ProvenShape[UserReactionDbModel] = (meetingId, userId, reactionEmoji, durationInSeconds, createdAt) <> (UserReactionDbModel.tupled, UserReactionDbModel.unapply)
}
object UserReactionDAO {
def insert(userId: String, reactionEmoji: String, durationInSeconds: Int) = {
def insert(meetingId: String, userId: String, reactionEmoji: String, durationInSeconds: Int) = {
DatabaseConnection.db.run(
TableQuery[UserReactionDbTableDef].forceInsert(
UserReactionDbModel(
meetingId = meetingId,
userId = userId,
reactionEmoji = reactionEmoji,
durationInSeconds = durationInSeconds,

View File

@ -7,6 +7,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
case class UserStateDbModel(
meetingId: String,
userId: String,
emoji: String = "none",
away: Boolean = false,
@ -26,11 +27,16 @@ case class UserStateDbModel(
pinned: Boolean = false,
locked: Boolean = false,
speechLocale: String,
inactivityWarningDisplay: Boolean = false,
inactivityWarningTimeoutSecs: Option[Long],
)
class UserStateDbTableDef(tag: Tag) extends Table[UserStateDbModel](tag, None, "user") {
override def * = (
userId,emoji,away,raiseHand,guestStatus,guestStatusSetByModerator,guestLobbyMessage,mobile,clientType,disconnected,expired,ejected,ejectReason,ejectReasonCode,ejectedByModerator,presenter,pinned,locked,speechLocale) <> (UserStateDbModel.tupled, UserStateDbModel.unapply)
meetingId, userId,emoji,away,raiseHand,guestStatus,guestStatusSetByModerator,guestLobbyMessage,mobile,clientType,disconnected,
expired,ejected,ejectReason,ejectReasonCode,ejectedByModerator,presenter,pinned,locked,speechLocale,
inactivityWarningDisplay, inactivityWarningTimeoutSecs) <> (UserStateDbModel.tupled, UserStateDbModel.unapply)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val emoji = column[String]("emoji")
val away = column[Boolean]("away")
@ -50,12 +56,15 @@ class UserStateDbTableDef(tag: Tag) extends Table[UserStateDbModel](tag, None, "
val pinned = column[Boolean]("pinned")
val locked = column[Boolean]("locked")
val speechLocale = column[String]("speechLocale")
val inactivityWarningDisplay = column[Boolean]("inactivityWarningDisplay")
val inactivityWarningTimeoutSecs = column[Option[Long]]("inactivityWarningTimeoutSecs")
}
object UserStateDAO {
def update(userState: UserState) = {
DatabaseConnection.db.run(
TableQuery[UserStateDbTableDef]
.filter(_.meetingId === userState.meetingId)
.filter(_.userId === userState.intId)
.map(u => (u.presenter, u.pinned, u.locked, u.speechLocale, u.emoji, u.away, u.raiseHand, u.mobile, u.clientType, u.disconnected))
.update((userState.presenter, userState.pin, userState.locked, userState.speechLocale, userState.emoji, userState.away, userState.raiseHand, userState.mobile, userState.clientType, userState.userLeftFlag.left))
@ -65,9 +74,10 @@ object UserStateDAO {
}
}
def updateEjected(userId: String, ejectReason: String, ejectReasonCode: String, ejectedByModerator: String) = {
def updateEjected(meetingId: String, userId: String, ejectReason: String, ejectReasonCode: String, ejectedByModerator: String) = {
DatabaseConnection.db.run(
TableQuery[UserStateDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(u => (u.ejected, u.ejectReason, u.ejectReasonCode, u.ejectedByModerator))
.update((true, Some(ejectReason), Some(ejectReasonCode), Some(ejectedByModerator)))
@ -77,10 +87,11 @@ object UserStateDAO {
}
}
def updateExpired(intId: String, expired: Boolean) = {
def updateExpired(meetingId: String, userId: String, expired: Boolean) = {
DatabaseConnection.db.run(
TableQuery[UserStateDbTableDef]
.filter(_.userId === intId)
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(u => (u.expired))
.update((expired))
).onComplete {
@ -89,10 +100,11 @@ object UserStateDAO {
}
}
def updateGuestStatus(intId: String, guestStatus: String, guestStatusSetByModerator: String) = {
def updateGuestStatus(meetingId: String, userId: String, guestStatus: String, guestStatusSetByModerator: String) = {
DatabaseConnection.db.run(
TableQuery[UserStateDbTableDef]
.filter(_.userId === intId)
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(u => (u.guestStatus, u.guestStatusSetByModerator))
.update((guestStatus,
guestStatusSetByModerator match {
@ -107,10 +119,11 @@ object UserStateDAO {
}
}
def updateGuestLobbyMessage(intId: String, guestLobbyMessage: String) = {
def updateGuestLobbyMessage(meetingId: String, userId: String, guestLobbyMessage: String) = {
DatabaseConnection.db.run(
TableQuery[UserStateDbTableDef]
.filter(_.userId === intId)
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(u => u.guestLobbyMessage)
.update(Some(guestLobbyMessage))
).onComplete {
@ -119,4 +132,22 @@ object UserStateDAO {
}
}
def updateInactivityWarning(meetingId: String, userId: String, inactivityWarningDisplay: Boolean, inactivityWarningTimeoutSecs: Long) = {
DatabaseConnection.db.run(
TableQuery[UserStateDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(u => (u.inactivityWarningDisplay, u.inactivityWarningTimeoutSecs))
.update((inactivityWarningDisplay,
inactivityWarningTimeoutSecs match {
case 0 => None
case timeout: Long => Some(timeout)
case _ => None
}))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated inactivityWarningDisplay on user table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating inactivityWarningDisplay user: $e")
}
}
}

View File

@ -0,0 +1,46 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.models.{ VoiceUserState }
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class UserTranscriptionErrorDbModel(
meetingId: String,
userId: String,
errorCode: String,
errorMessage: String,
lastUpdatedAt: java.sql.Timestamp = new java.sql.Timestamp(System.currentTimeMillis())
)
class UserTranscriptionErrorDbTableDef(tag: Tag) extends Table[UserTranscriptionErrorDbModel](tag, None, "user_transcriptionError") {
override def * = (
meetingId, userId, errorCode, errorMessage, lastUpdatedAt
) <> (UserTranscriptionErrorDbModel.tupled, UserTranscriptionErrorDbModel.unapply)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val errorCode = column[String]("errorCode")
val errorMessage = column[String]("errorMessage")
val lastUpdatedAt = column[java.sql.Timestamp]("lastUpdatedAt")
}
object UserTranscriptionErrorDAO {
def insert(userId: String, meetingId: String, errorCode: String, errorMessage: String) = {
DatabaseConnection.db.run(
TableQuery[UserTranscriptionErrorDbTableDef].insertOrUpdate(
UserTranscriptionErrorDbModel(
meetingId = meetingId,
userId = userId,
errorCode = errorCode,
errorMessage = errorMessage,
lastUpdatedAt = new java.sql.Timestamp(System.currentTimeMillis()),
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on user_transcriptionError table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting user_transcriptionError: $e")
}
}
}

View File

@ -7,6 +7,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success }
case class UserVoiceConfStateDbModel(
meetingId: String,
userId: String,
voiceConf: String,
voiceConfCallSession: String,
@ -16,8 +17,9 @@ case class UserVoiceConfStateDbModel(
class UserVoiceConfStateDbTableDef(tag: Tag) extends Table[UserVoiceConfStateDbModel](tag, None, "user_voice") {
override def * = (
userId, voiceConf, voiceConfCallSession, voiceConfClientSession, voiceConfCallState
meetingId, userId, voiceConf, voiceConfCallSession, voiceConfClientSession, voiceConfCallState
) <> (UserVoiceConfStateDbModel.tupled, UserVoiceConfStateDbModel.unapply)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val voiceConf = column[String]("voiceConf")
val voiceConfCallSession = column[String]("voiceConfCallSession")
@ -26,10 +28,11 @@ class UserVoiceConfStateDbTableDef(tag: Tag) extends Table[UserVoiceConfStateDbM
}
object UserVoiceConfStateDAO {
def insertOrUpdate(userId: String, voiceConf: String, voiceConfCallSession: String, clientSession: String, callState: String) = {
def insertOrUpdate(meetingId: String, userId: String, voiceConf: String, voiceConfCallSession: String, clientSession: String, callState: String) = {
DatabaseConnection.db.run(
TableQuery[UserVoiceConfStateDbTableDef].insertOrUpdate(
UserVoiceConfStateDbModel(
meetingId = meetingId,
userId = userId,
voiceConf = voiceConf,
voiceConfCallSession = voiceConfCallSession,
@ -38,9 +41,7 @@ object UserVoiceConfStateDAO {
)
)
).onComplete {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on user_voice table!")
}
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on user_voice table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting voice: $e")
}
}

View File

@ -7,6 +7,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success }
case class UserVoiceDbModel(
meetingId: String,
userId: String,
voiceUserId: String,
callerName: String,
@ -25,9 +26,10 @@ case class UserVoiceDbModel(
class UserVoiceDbTableDef(tag: Tag) extends Table[UserVoiceDbModel](tag, None, "user_voice") {
override def * = (
userId, voiceUserId, callerName, callerNum, callingWith, joined, listenOnly,
meetingId, userId, voiceUserId, callerName, callerNum, callingWith, joined, listenOnly,
muted, spoke, talking, floor, lastFloorTime, startTime, endTime
) <> (UserVoiceDbModel.tupled, UserVoiceDbModel.unapply)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val voiceUserId = column[String]("voiceUserId")
val callerName = column[String]("callerName")
@ -54,6 +56,7 @@ object UserVoiceDAO {
DatabaseConnection.db.run(
TableQuery[UserVoiceDbTableDef].insertOrUpdate(
UserVoiceDbModel(
meetingId = voiceUserState.meetingId,
userId = voiceUserState.intId,
voiceUserId = voiceUserState.voiceUserId,
callerName = voiceUserState.callerName,
@ -99,13 +102,15 @@ object UserVoiceDAO {
"spoke" = true,
"endTime" = null,
"startTime" = (case when "talking" is false then $now else "startTime" end)
WHERE "userId" = ${voiceUserState.intId}"""
WHERE "meetingId" = ${voiceUserState.meetingId}
AND "userId" = ${voiceUserState.intId}"""
} else {
sqlu"""UPDATE user_voice SET
"talking" = false,
"startTime" = null,
"endTime" = (case when "talking" is true then $now else "endTime" end)
WHERE "userId" = ${voiceUserState.intId}"""
WHERE "meetingId" = ${voiceUserState.meetingId}
AND "userId" = ${voiceUserState.intId}"""
}
DatabaseConnection.db.run(updateSql).onComplete {
@ -114,10 +119,11 @@ object UserVoiceDAO {
}
}
def delete(intId: String) = {
def delete(meetingId: String, userId: String) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === intId)
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(u => (u.loggedOut))
.update((true))
).onComplete {
@ -126,7 +132,7 @@ object UserVoiceDAO {
}
}
def deleteUserVoice(userId: String) = {
def deleteUserVoice(meetingId: String,userId: String) = {
//Meteor sets this props instead of removing
// muted: false
// talking: false
@ -136,6 +142,7 @@ object UserVoiceDAO {
DatabaseConnection.db.run(
TableQuery[UserVoiceDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(u => (u.muted, u.talking, u.listenOnly, u.joined, u.spoke, u.startTime, u.endTime))
.update((false, false, false, false, false, None, None))

View File

@ -1,60 +0,0 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.apps.whiteboard.Whiteboard
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
case class UserWhiteboardDbModel(
whiteboardId: String,
userId: String,
changedModeOn: Long,
)
class UserWhiteboardDbTableDef(tag: Tag) extends Table[UserWhiteboardDbModel](tag, None, "user_whiteboard") {
override def * = (
whiteboardId, userId, changedModeOn) <> (UserWhiteboardDbModel.tupled, UserWhiteboardDbModel.unapply)
def pk = primaryKey("user_whiteboard_pkey", (whiteboardId, userId))
val whiteboardId = column[String]("whiteboardId")
val userId = column[String]("userId")
val changedModeOn = column[Long]("changedModeOn")
}
object UserWhiteboardDAO {
def updateMultiuser(whiteboard: Whiteboard) = {
val deleteQuery = TableQuery[UserWhiteboardDbTableDef]
.filter(_.whiteboardId === whiteboard.id)
if(whiteboard.multiUser.length > 0) {
deleteQuery.filterNot(_.userId inSet whiteboard.multiUser)
}
DatabaseConnection.db.run(deleteQuery.delete).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Users deleted from Whiteboard ${whiteboard.id}")
case Failure(e) => DatabaseConnection.logger.error(s"Error deleting users from whiteboard: $e")
}
for {
userId <- whiteboard.multiUser
} yield {
DatabaseConnection.db.run(
TableQuery[UserWhiteboardDbTableDef].insertOrUpdate(
UserWhiteboardDbModel(
whiteboardId = whiteboard.id,
userId = userId,
changedModeOn = whiteboard.changedModeOn
)
)
).onComplete {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on user_whiteboard table!")
}
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting user_whiteboard: $e")
}
}
}
}

View File

@ -42,4 +42,5 @@ object MeetingEndReason {
val BREAKOUT_ENDED_BY_MOD = "BREAKOUT_ENDED_BY_MOD"
val ENDED_DUE_TO_NO_AUTHED_USER = "ENDED_DUE_TO_NO_AUTHED_USER"
val ENDED_DUE_TO_NO_MODERATOR = "ENDED_DUE_TO_NO_MODERATOR"
val ENDED_DUE_TO_SERVICE_INTERRUPTION = "ENDED_DUE_TO_SERVICE_INTERRUPTION"
}

Some files were not shown because too many files have changed in this diff Show More