Merge remote-tracking branch 'upstream/v3.0.x-release' into fix-volume-bar-overlap

This commit is contained in:
André 2024-05-03 10:29:00 -03:00
commit 4c0df76b23
645 changed files with 12885 additions and 14816 deletions

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

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]()
@ -83,7 +83,7 @@ class WhiteboardModel extends SystemConfiguration {
}
}
PresAnnotationDAO.insertOrUpdateMap(newAnnotationsMap)
PresAnnotationDAO.insertOrUpdateMap(meetingId, annotationsAdded)
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(newWb)
@ -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)
PresAnnotationDAO.delete(annotationsIdsRemoved)
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

@ -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

@ -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

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

@ -21,25 +21,19 @@ 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,
"info",
"rooms",
"app.toast.breakoutRoomEnded",
"Message when the breakout room is ended",
Vector()
)
outGW.send(notifyEvent)
NotificationDAO.insert(notifyEvent)
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
meetingId,
"info",
"rooms",
"app.toast.breakoutRoomEnded",
"Message when the breakout room is ended",
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) {
val filename = liveMeeting.props.breakoutProps.captureNotesFilename
val captureNotesEvent = BigBlueButtonEvent(msg.parentId, CaptureSharedNotesReqInternalMsg(msg.breakoutId, filename))
eventBus.publish(captureNotesEvent)
for {
group <- Pads.getGroup(liveMeeting.pads, "notes")
} yield {
val filename = liveMeeting.props.breakoutProps.captureNotesFilename
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

@ -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,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

@ -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

@ -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

@ -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

@ -59,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)

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

@ -26,7 +26,7 @@ trait UserConnectionAliveReqMsgHdlr extends RightsManagementTrait {
val status = getLevelFromRtt(msg.body.networkRttInMs)
UserConnectionStatusDAO.updateUserAlive(user.intId, rtt, status)
UserConnectionStatusDAO.updateUserAlive(user.meetingId, user.intId, rtt, status)
}
}

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

@ -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
}
@ -145,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 {

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

@ -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,

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

@ -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

@ -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

@ -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,10 +27,7 @@ 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,
@ -45,10 +50,7 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
presentationUploadExternalDescription,
presentationUploadExternalUrl,
learningDashboardAccessToken,
logoutUrl,
customLogoUrl,
bannerText,
bannerColor,
systemColumns,
createdTime,
durationInSeconds,
endWhenNoModerator,
@ -68,10 +70,12 @@ 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")
@ -97,19 +101,28 @@ object MeetingDAO {
presentationUploadExternalDescription = meetingProps.meetingProp.presentationUploadExternalDescription,
presentationUploadExternalUrl = meetingProps.meetingProp.presentationUploadExternalUrl,
learningDashboardAccessToken = meetingProps.password.learningDashboardAccessToken,
logoutUrl = meetingProps.systemProps.logoutUrl,
customLogoUrl = meetingProps.systemProps.customLogoURL match {
case "" => None
case logoUrl => Some(logoUrl)
},
bannerText = meetingProps.systemProps.bannerText match {
case "" => None
case bannerText => Some(bannerText)
},
bannerColor = meetingProps.systemProps.bannerColor match {
case "" => None
case bannerColor => Some(bannerColor)
},
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)
},
bannerText = meetingProps.systemProps.bannerText match {
case "" => None
case bannerText => Some(bannerText)
},
bannerColor = meetingProps.systemProps.bannerColor match {
case "" => None
case bannerColor => Some(bannerColor)
},
),
createdTime = meetingProps.durationProps.createdTime,
durationInSeconds = meetingProps.durationProps.duration * 60,
endWhenNoModerator = meetingProps.durationProps.endWhenNoModerator,
@ -131,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))
}

View File

@ -15,6 +15,7 @@ case class NotificationDbModel(
messageDescription: String,
messageValues: JsValue,
role: Option[String],
userMeetingId: Option[String],
userId: Option[String],
createdAt: java.sql.Timestamp,
)
@ -27,9 +28,10 @@ class NotificationDbTableDef(tag: Tag) extends Table[NotificationDbModel](tag, N
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, userId, createdAt) <> (NotificationDbModel.tupled, NotificationDbModel.unapply)
override def * = (meetingId, notificationType, icon, messageId, messageDescription, messageValues, role, userMeetingId, userId, createdAt) <> (NotificationDbModel.tupled, NotificationDbModel.unapply)
}
object NotificationDAO {
@ -57,6 +59,10 @@ object NotificationDAO {
messageDescription,
JsonUtils.vectorToJson(messageValues),
role,
userMeetingId = userId match {
case Some(u) => Some(meetingId)
case _ => None
},
userId,
createdAt = new java.sql.Timestamp(System.currentTimeMillis())
)

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,56 +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 "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

@ -8,25 +8,28 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class PollResponseDbModel(
pollId: String,
optionId: Option[Int],
userId: Option[String]
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

@ -8,6 +8,7 @@ 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),
@ -51,11 +54,12 @@ object PresAnnotationDAO {
}
}
def prepareInsertOrUpdate(annotation: AnnotationVO) = {
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,
@ -64,12 +68,12 @@ object PresAnnotationDAO {
)
}
def insertOrUpdateMap(annotations: Map[String, AnnotationVO]) = {
def insertOrUpdateMap(meetingId: String, annotations: Array[AnnotationVO]) = {
DatabaseConnection.db.run(
DBIO.sequence(
annotations.map { annotation =>
prepareInsertOrUpdate(annotation._2)
}
prepareInsertOrUpdate(meetingId, annotation)
}.toVector
).transactionally
)
.onComplete {
@ -80,14 +84,14 @@ object PresAnnotationDAO {
}
}
def delete(wbId: String, userId: String, annotationId: String) = {
PresAnnotationHistoryDAO.delete(wbId, userId, annotationId).onComplete {
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")
@ -97,12 +101,12 @@ object PresAnnotationDAO {
}
}
def delete(annotationIds: Array[String]) = {
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.lastUpdatedAt))
.update("", 0, new java.sql.Timestamp(System.currentTimeMillis()))
.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

@ -7,34 +7,36 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
case class PresPageWritersDbModel(
pageId: String,
userId: String,
changedModeOn: Long,
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,25 +34,31 @@ class TimerDbTableDef(tag: Tag) extends Table[TimerDbModel](tag, None, "timer")
}
object TimerDAO {
def insert(meetingId: String) = {
DatabaseConnection.db.run(
TableQuery[TimerDbTableDef].insertOrUpdate(
TimerDbModel(
meetingId = meetingId,
stopwatch = true,
running = false,
active = false,
time = 300000,
accumulated = 0,
startedOn = 0,
endedOn = 0,
songTrack = "noTrack",
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(
meetingId = meetingId,
stopwatch = true,
running = false,
active = false,
time = timerDefaultTimeInMilli,
accumulated = 0,
startedOn = 0,
endedOn = 0,
songTrack = "noTrack",
)
)
)
).onComplete {
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on Timer table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting Timer: $e")
}
}
}
def update(meetingId: String, timerModel: TimerModel) = {

View File

@ -7,17 +7,19 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success }
case class UserBreakoutRoomDbModel(
breakoutRoomId: String,
userId: String,
isDefaultName: Boolean,
sequence: Int,
shortName: String,
currentlyInRoom: Boolean,
breakoutRoomId: String,
meetingId: String,
userId: String,
isDefaultName: Boolean,
sequence: Int,
shortName: String,
currentlyInRoom: Boolean,
)
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

@ -7,25 +7,28 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
case class UserCameraDbModel(
streamId: String,
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

@ -18,7 +18,7 @@ class UserConnectionStatusDbTableDef(tag: Tag) extends Table[UserConnectionStatu
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 networkRttInMs = column[Option[Double]]("networkRttInMs")
val status = column[String]("status")
@ -45,9 +45,10 @@ object UserConnectionStatusDAO {
}
}
def updateUserAlive(userId: String, rtt: Option[Double], status: 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, t.networkRttInMs, t.status, t.statusUpdatedAt))
.update(

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,6 +115,7 @@ 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))
@ -122,9 +125,10 @@ object UserDAO {
}
}
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,34 +7,36 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
case class UserStateDbModel(
userId: String,
emoji: String = "none",
away: Boolean = false,
raiseHand: Boolean = false,
guestStatus: String,
guestStatusSetByModerator: Option[String],
guestLobbyMessage: Option[String],
mobile: Boolean,
clientType: String,
disconnected: Boolean = false,
expired: Boolean = false,
ejected: Boolean = false,
ejectReason: Option[String],
ejectReasonCode: Option[String],
ejectedByModerator: Option[String],
presenter: Boolean = false,
pinned: Boolean = false,
locked: Boolean = false,
speechLocale: String,
inactivityWarningDisplay: Boolean = false,
meetingId: String,
userId: String,
emoji: String = "none",
away: Boolean = false,
raiseHand: Boolean = false,
guestStatus: String,
guestStatusSetByModerator: Option[String],
guestLobbyMessage: Option[String],
mobile: Boolean,
clientType: String,
disconnected: Boolean = false,
expired: Boolean = false,
ejected: Boolean = false,
ejectReason: Option[String],
ejectReasonCode: Option[String],
ejectedByModerator: Option[String],
presenter: Boolean = false,
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,
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")
@ -62,6 +64,7 @@ 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))
@ -71,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)))
@ -83,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 {
@ -95,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 {
@ -113,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 {
@ -125,10 +132,11 @@ object UserStateDAO {
}
}
def updateInactivityWarning(intId: String, inactivityWarningDisplay: Boolean, inactivityWarningTimeoutSecs: Long) = {
def updateInactivityWarning(meetingId: String, userId: String, inactivityWarningDisplay: Boolean, inactivityWarningTimeoutSecs: Long) = {
DatabaseConnection.db.run(
TableQuery[UserStateDbTableDef]
.filter(_.userId === intId)
.filter(_.meetingId === meetingId)
.filter(_.userId === userId)
.map(u => (u.inactivityWarningDisplay, u.inactivityWarningTimeoutSecs))
.update((inactivityWarningDisplay,
inactivityWarningTimeoutSecs match {

View File

@ -7,8 +7,8 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class UserTranscriptionErrorDbModel(
userId: String,
meetingId: String,
userId: String,
errorCode: String,
errorMessage: String,
lastUpdatedAt: java.sql.Timestamp = new java.sql.Timestamp(System.currentTimeMillis())
@ -16,10 +16,10 @@ case class UserTranscriptionErrorDbModel(
class UserTranscriptionErrorDbTableDef(tag: Tag) extends Table[UserTranscriptionErrorDbModel](tag, None, "user_transcriptionError") {
override def * = (
userId, meetingId, errorCode, errorMessage, lastUpdatedAt
meetingId, userId, errorCode, errorMessage, lastUpdatedAt
) <> (UserTranscriptionErrorDbModel.tupled, UserTranscriptionErrorDbModel.unapply)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val errorCode = column[String]("errorCode")
val errorMessage = column[String]("errorMessage")
val lastUpdatedAt = column[java.sql.Timestamp]("lastUpdatedAt")
@ -30,17 +30,15 @@ object UserTranscriptionErrorDAO {
DatabaseConnection.db.run(
TableQuery[UserTranscriptionErrorDbTableDef].insertOrUpdate(
UserTranscriptionErrorDbModel(
userId = userId,
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 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

@ -45,6 +45,10 @@ object Pads {
def setGroupId(pads: Pads, externalId: String, groupId: String): Unit = pads.setGroupId(externalId, groupId)
def setPadId(pads: Pads, externalId: String, padId: String): Unit = pads.setGroupPadId(externalId, padId)
def setRev(pads: Pads, externalId: String, rev: Int): Unit = pads.setGroupRev(externalId, rev)
def getGroupById(pads: Pads, groupId: String): Option[PadGroup] = pads.getGroupById(groupId)
}
@ -54,13 +58,31 @@ class Pads {
def addGroup(externalId: String, model: String, name: String, userId: String): Unit = groups += externalId -> new PadGroup(externalId, model, name, userId)
def setGroupId(externalId: String, groupId: String): Unit = {
groups.get(externalId) match {
case Some(group) => groups += externalId -> new PadGroup(externalId, group.model, group.name, group.userId, groupId)
case _ =>
for {
group <- groups.get(externalId)
} yield {
groups += externalId -> group.copy(groupId = groupId)
}
}
def setGroupPadId(externalId: String, padId: String): Unit = {
for {
group <- groups.get(externalId)
} yield {
groups += externalId -> group.copy(padId = padId)
}
}
def setGroupRev(externalId: String, rev: Int): Unit = {
for {
group <- groups.get(externalId)
} yield {
groups += externalId -> group.copy(rev = rev)
}
}
def getGroupById(groupId: String): Option[PadGroup] = groups.values.find(_.groupId == groupId)
}
class PadGroup(val externalId: String, val model: String, val name: String, val userId: String, val groupId: String = "")
case class PadGroup(val externalId: String, val model: String, val name: String, val userId: String, val groupId: String = "", padId: String = "", rev: Int = 0)

View File

@ -114,7 +114,7 @@ object Polls {
shape = pollResultToWhiteboardShape(result)
annot <- send(result, shape)
} yield {
lm.wbModel.addAnnotations(annot.wbId, requesterId, Array[AnnotationVO](annot), false, false)
lm.wbModel.addAnnotations(annot.wbId, lm.props.meetingProp.intId, requesterId, Array[AnnotationVO](annot), isPresenter = false, isModerator = false)
showPollResult(pollId, lm.polls)
(result, annot)
}
@ -150,7 +150,7 @@ object Polls {
simplePoll <- getSimplePollResult(pollId, lm.polls)
pvo <- handleRespondToPoll(simplePoll, requesterId, pollId, questionId, answerIds, lm)
} yield {
PollResponseDAO.insert(poll, requesterId, answerIds)
PollResponseDAO.insert(poll, lm.props.meetingProp.intId, requesterId, answerIds)
(pollId, pvo)
}

View File

@ -5,13 +5,14 @@ import org.bigbluebutton.core.db.{UserBreakoutRoomDAO, UserDAO, UserDbModel}
import org.bigbluebutton.core.domain.BreakoutRoom2x
object RegisteredUsers {
def create(userId: String, extId: String, name: String, roles: String,
def create(meetingId: String, userId: String, extId: String, name: String, roles: String,
authToken: String, sessionToken: String, avatar: String, color: String, guest: Boolean, authenticated: Boolean,
guestStatus: String, excludeFromDashboard: Boolean, enforceLayout: String,
customParameters: Map[String, String], loggedOut: Boolean): RegisteredUser = {
new RegisteredUser(
userId,
extId,
meetingId,
name,
roles,
authToken,
@ -202,6 +203,7 @@ class RegisteredUsers {
case class RegisteredUser(
id: String,
externId: String,
meetingId: String,
name: String,
role: String,
authToken: String,

View File

@ -49,7 +49,7 @@ object Users2x {
val newUser = u.copy(userLeftFlag = UserLeftFlag(false, 0))
users.save(newUser)
UserStateDAO.update(newUser)
UserStateDAO.updateExpired(u.intId, false)
UserStateDAO.updateExpired(u.meetingId, u.intId, false)
newUser
}
}
@ -101,7 +101,7 @@ object Users2x {
def resetLastInactivityInspect(users: Users2x, u: UserState): UserState = {
val newUserState = modify(u)(_.lastInactivityInspect).setTo(0)
users.save(newUserState)
UserStateDAO.updateInactivityWarning(u.intId, inactivityWarningDisplay = false, 0)
UserStateDAO.updateInactivityWarning(u.meetingId, u.intId, inactivityWarningDisplay = false, 0)
newUserState
}
@ -207,7 +207,7 @@ object Users2x {
.modify(_.reactionChangedOn).setTo(System.currentTimeMillis())
users.save(newUser)
UserReactionDAO.insert(intId, reactionEmoji, durationInSeconds)
UserReactionDAO.insert(u.meetingId, u.intId, reactionEmoji, durationInSeconds)
newUser
}
}
@ -409,6 +409,7 @@ case class UserLeftFlag(left: Boolean, leftOn: Long)
case class UserState(
intId: String,
extId: String,
meetingId: String,
name: String,
role: String,
guest: Boolean,

View File

@ -38,8 +38,8 @@ object VoiceUsers {
UserVoiceDAO.insert(user)
}
def removeWithIntId(users: VoiceUsers, intId: String): Option[VoiceUserState] = {
UserVoiceDAO.deleteUserVoice(intId)
def removeWithIntId(users: VoiceUsers, meetingId: String, intId: String): Option[VoiceUserState] = {
UserVoiceDAO.deleteUserVoice(meetingId, intId)
users.remove(intId)
}
@ -197,6 +197,7 @@ case class VoiceUserVO2x(
case class VoiceUserState(
intId: String,
voiceUserId: String,
meetingId: String,
callingWith: String,
callerName: String,
callerNum: String,

View File

@ -15,10 +15,10 @@ object Webcams {
def findAll(webcams: Webcams): Vector[WebcamStream] = webcams.toVector
def addWebcamStream(webcams: Webcams, webcam: WebcamStream): Option[WebcamStream] = {
def addWebcamStream(meetingId: String, webcams: Webcams, webcam: WebcamStream): Option[WebcamStream] = {
findWithStreamId(webcams, webcam.streamId) match {
case None => {
UserCameraDAO.insert(webcam)
UserCameraDAO.insert(meetingId, webcam)
Some(webcams.save(webcam))
}
case _ => None

View File

@ -3,9 +3,10 @@ package org.bigbluebutton.core.pubsub.senders
import org.apache.pekko.actor.{ Actor, ActorLogging, Props }
import org.bigbluebutton.SystemConfiguration
import com.fasterxml.jackson.databind.JsonNode
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.common2.msgs.{ PluginDataChannelDeleteEntryMsgBody, _ }
import org.bigbluebutton.core.bus._
import org.bigbluebutton.core2.ReceivedMessageRouter
import scala.reflect.runtime.universe._
import org.bigbluebutton.common2.bus.ReceivedJsonMessage
import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
@ -179,8 +180,6 @@ class ReceivedJsonMsgHandlerActor(
routePadMsg[PadPatchSysMsg](envelope, jsonNode)
case PadUpdatePubMsg.NAME =>
routeGenericMsg[PadUpdatePubMsg](envelope, jsonNode)
case PadCapturePubMsg.NAME =>
routePadMsg[PadCapturePubMsg](envelope, jsonNode)
case PadPinnedReqMsg.NAME =>
routeGenericMsg[PadPinnedReqMsg](envelope, jsonNode)
@ -420,11 +419,11 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[CreateGroupChatReqMsg](envelope, jsonNode)
//Plugin
case PluginDataChannelDispatchMessageMsg.NAME =>
routeGenericMsg[PluginDataChannelDispatchMessageMsg](envelope, jsonNode)
case PluginDataChannelPushEntryMsg.NAME =>
routeGenericMsg[PluginDataChannelPushEntryMsg](envelope, jsonNode)
case PluginDataChannelDeleteMessageMsg.NAME =>
routeGenericMsg[PluginDataChannelDeleteMessageMsg](envelope, jsonNode)
case PluginDataChannelDeleteEntryMsg.NAME =>
routeGenericMsg[PluginDataChannelDeleteEntryMsg](envelope, jsonNode)
case PluginDataChannelResetMsg.NAME =>
routeGenericMsg[PluginDataChannelResetMsg](envelope, jsonNode)
@ -470,7 +469,7 @@ class ReceivedJsonMsgHandlerActor(
route[CheckGraphqlMiddlewareAlivePongSysMsg](meetingManagerChannel, envelope, jsonNode)
case _ =>
log.error("Cannot route envelope name " + envelope.name)
log.debug("Cannot route envelope name " + envelope.name)
// do nothing
}
}

View File

@ -57,6 +57,7 @@ trait HandlerHelpers extends SystemConfiguration {
UserState(
intId = regUser.id,
extId = regUser.externId,
meetingId = regUser.meetingId,
name = regUser.name,
role = regUser.role,
guest = regUser.guest,
@ -237,7 +238,7 @@ trait HandlerHelpers extends SystemConfiguration {
} yield {
model.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(liveMeeting.props.meetingProp.intId, room.id, reason)))
UserBreakoutRoomDAO.updateLastBreakoutRoom(Vector(), room)
UserBreakoutRoomDAO.updateLastBreakoutRoom(liveMeeting.props.meetingProp.intId, Vector(), room)
}
}

View File

@ -15,7 +15,7 @@ import org.bigbluebutton.core.apps._
import org.bigbluebutton.core.apps.caption.CaptionApp2x
import org.bigbluebutton.core.apps.chat.ChatApp2x
import org.bigbluebutton.core.apps.externalvideo.ExternalVideoApp2x
import org.bigbluebutton.core.apps.pads.PadsApp2x
import org.bigbluebutton.core.apps.pads.{ PadsApp2x, PadslHdlrHelpers }
import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x
import org.bigbluebutton.core.apps.audiocaptions.AudioCaptionsApp2x
import org.bigbluebutton.core.apps.timer.TimerApp2x
@ -28,12 +28,13 @@ import org.bigbluebutton.core.models.{ Users2x, VoiceUsers, _ }
import org.bigbluebutton.core2.{ MeetingStatus2x, Permissions }
import org.bigbluebutton.core2.message.handlers._
import org.bigbluebutton.core2.message.handlers.meeting._
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.common2.msgs.{ PluginDataChannelDeleteEntryMsgBody, _ }
import org.bigbluebutton.core.apps.breakout._
import org.bigbluebutton.core.apps.polls._
import org.bigbluebutton.core.apps.voice._
import org.apache.pekko.actor.Props
import org.apache.pekko.actor.OneForOneStrategy
import org.bigbluebutton.ClientSettings.{ getConfigPropertyValueByPathAsBooleanOrElse, getConfigPropertyValueByPathAsStringOrElse }
import org.bigbluebutton.common2.msgs
import scala.concurrent.duration._
@ -41,7 +42,7 @@ import org.bigbluebutton.core.apps.layout.LayoutApp2x
import org.bigbluebutton.core.apps.meeting.{ SyncGetMeetingInfoRespMsgHdlr, ValidateConnAuthTokenSysMsgHdlr }
import org.bigbluebutton.core.apps.plugin.PluginHdlrs
import org.bigbluebutton.core.apps.users.ChangeLockSettingsInMeetingCmdMsgHdlr
import org.bigbluebutton.core.db.{ NotificationDAO, UserStateDAO }
import org.bigbluebutton.core.db.{ MeetingDAO, NotificationDAO, UserStateDAO }
import org.bigbluebutton.core.models.VoiceUsers.{ findAllFreeswitchCallers, findAllListenOnlyVoiceUsers }
import org.bigbluebutton.core.models.Webcams.findAll
import org.bigbluebutton.core2.MeetingStatus2x.hasAuthedUserJoined
@ -177,6 +178,9 @@ class MeetingActor(
val msgEvent = MsgBuilder.buildMeetingCreatedEvtMsg(liveMeeting.props.meetingProp.intId, liveMeeting.props)
outGW.send(msgEvent)
//Insert meeting into the database
MeetingDAO.insert(liveMeeting.props, liveMeeting.clientSettings)
// Create a default public group chat
state = groupChatApp.handleCreateDefaultPublicGroupChat(state, liveMeeting, msgBus)
@ -204,6 +208,8 @@ class MeetingActor(
initLockSettings(liveMeeting, liveMeeting.props.lockSettingsProps)
initSharedNotes(liveMeeting)
/** *****************************************************************/
// Helper to create fake users for testing (ralam jan 5, 2018)
//object FakeTestData extends FakeTestData
@ -294,7 +300,6 @@ class MeetingActor(
case msg: SendBreakoutTimeRemainingInternalMsg =>
handleSendBreakoutTimeRemainingInternalMsg(msg)
case msg: CapturePresentationReqInternalMsg => presentationPodsApp.handle(msg, state, liveMeeting, msgBus)
case msg: CaptureSharedNotesReqInternalMsg => presentationPodsApp.handle(msg, liveMeeting, msgBus)
case msg: SendRecordingTimerInternalMsg =>
state = usersApp.handleSendRecordingTimerInternalMsg(msg, state)
@ -319,6 +324,27 @@ class MeetingActor(
MeetingStatus2x.setPermissions(liveMeeting.status, settings)
}
private def initSharedNotes(liveMeeting: LiveMeeting): Unit = {
val sharedNotesEnabledInClientSettings = getConfigPropertyValueByPathAsBooleanOrElse(
liveMeeting.clientSettings,
"public.notes.enabled",
alternativeValue = true
)
if (sharedNotesEnabledInClientSettings && !liveMeeting.props.meetingProp.disabledFeatures.contains("sharedNotes")) {
val sharedNotesPadId = getConfigPropertyValueByPathAsStringOrElse(
liveMeeting.clientSettings,
"public.notes.id",
alternativeValue = ""
)
if (!Pads.hasGroup(liveMeeting.pads, sharedNotesPadId)) {
Pads.addGroup(liveMeeting.pads, sharedNotesPadId, sharedNotesPadId, sharedNotesPadId, "SYSTEM")
PadslHdlrHelpers.broadcastPadCreateGroupCmdMsg(outGW, liveMeeting.props.meetingProp.intId, sharedNotesPadId, sharedNotesPadId)
}
}
}
private def updateVoiceUserLastActivity(userId: String) {
for {
vu <- VoiceUsers.findWithVoiceUserId(liveMeeting.voiceUsers, userId)
@ -530,7 +556,6 @@ class MeetingActor(
case m: MakePresentationDownloadReqMsg => presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: NewPresFileAvailableMsg => presentationPodsApp.handle(m, liveMeeting, msgBus)
case m: PresAnnStatusMsg => presentationPodsApp.handle(m, liveMeeting, msgBus)
case m: PadCapturePubMsg => presentationPodsApp.handle(m, liveMeeting, msgBus)
// Presentation Pods
case m: CreateNewPresentationPodPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
@ -603,8 +628,8 @@ class MeetingActor(
updateUserLastActivity(m.body.msg.sender.id)
// Plugin
case m: PluginDataChannelDispatchMessageMsg => pluginHdlrs.handle(m, state, liveMeeting)
case m: PluginDataChannelDeleteMessageMsg => pluginHdlrs.handle(m, state, liveMeeting)
case m: PluginDataChannelPushEntryMsg => pluginHdlrs.handle(m, state, liveMeeting)
case m: PluginDataChannelDeleteEntryMsg => pluginHdlrs.handle(m, state, liveMeeting)
case m: PluginDataChannelResetMsg => pluginHdlrs.handle(m, state, liveMeeting)
// Webcams
@ -934,7 +959,7 @@ class MeetingActor(
Polls.handleStopPollReqMsg(state, u.intId, liveMeeting)
}
UserStateDAO.updateExpired(u.intId, true)
UserStateDAO.updateExpired(u.meetingId, u.intId, true)
}
}
@ -985,7 +1010,7 @@ class MeetingActor(
val secsToDisconnect = TimeUnit.MILLISECONDS.toSeconds(expiryTracker.userActivitySignResponseDelayInMs);
Sender.sendUserInactivityInspectMsg(liveMeeting.props.meetingProp.intId, u.intId, secsToDisconnect, outGW)
UserStateDAO.updateInactivityWarning(u.intId, inactivityWarningDisplay = true, secsToDisconnect)
UserStateDAO.updateInactivityWarning(u.meetingId, u.intId, inactivityWarningDisplay = true, secsToDisconnect)
updateUserLastInactivityInspect(u.intId)
}
}

View File

@ -2,11 +2,13 @@ package org.bigbluebutton.core.running
import org.apache.pekko.actor.ActorContext
import org.bigbluebutton.ClientSettings
import org.bigbluebutton.ClientSettings.{getConfigPropertyValueByPathAsBooleanOrElse, getConfigPropertyValueByPathAsStringOrElse}
import org.bigbluebutton.common2.domain.DefaultProps
import org.bigbluebutton.core.apps._
import org.bigbluebutton.core.bus._
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.OutMessageGateway
import org.bigbluebutton.core.apps.pads.PadslHdlrHelpers
import org.bigbluebutton.core2.MeetingStatus2x
object RunningMeeting {

View File

@ -206,7 +206,6 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
case m: PadUpdatedEvtMsg => logMessage(msg)
case m: PadUpdatePubMsg => logMessage(msg)
case m: PadUpdateCmdMsg => logMessage(msg)
case m: PadCapturePubMsg => logMessage(msg)
case _ => // ignore message
}

View File

@ -10,7 +10,6 @@ object RunningMeetings {
def add(meetings: RunningMeetings, meeting: RunningMeeting): RunningMeeting = {
meetings.save(meeting)
MeetingDAO.insert(meeting.props, meeting.clientSettings)
meeting
}

View File

@ -21,7 +21,7 @@ trait SetPrivateGuestLobbyMessageCmdMsgHdlr extends RightsManagementTrait {
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
} else {
GuestsWaiting.setPrivateGuestLobbyMessage(liveMeeting.guestsWaiting, msg.body.guestId, msg.body.message)
UserStateDAO.updateGuestLobbyMessage(msg.body.guestId, msg.body.message)
UserStateDAO.updateGuestLobbyMessage(msg.header.meetingId, msg.body.guestId, msg.body.message)
val event = MsgBuilder.buildPrivateGuestLobbyMsgChangedEvtMsg(
liveMeeting.props.meetingProp.intId,
msg.header.userId,

View File

@ -29,16 +29,18 @@ trait FakeTestData {
val guestWait2 = GuestWaiting(guest2.intId, guest2.name, guest2.role, guest2.guest, "", "#ff6242", guest2.authed, System.currentTimeMillis())
GuestsWaiting.add(liveMeeting.guestsWaiting, guestWait2)
val vu1 = FakeUserGenerator.createFakeVoiceOnlyUser(CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
val meetingId = liveMeeting.props.meetingProp.intId
val vu1 = FakeUserGenerator.createFakeVoiceOnlyUser(meetingId, CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
VoiceUsers.add(liveMeeting.voiceUsers, vu1)
val vu2 = FakeUserGenerator.createFakeVoiceOnlyUser(CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
val vu2 = FakeUserGenerator.createFakeVoiceOnlyUser(meetingId, CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
VoiceUsers.add(liveMeeting.voiceUsers, vu2)
val vu3 = FakeUserGenerator.createFakeVoiceOnlyUser(CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
val vu3 = FakeUserGenerator.createFakeVoiceOnlyUser(meetingId, CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
VoiceUsers.add(liveMeeting.voiceUsers, vu3)
val vu4 = FakeUserGenerator.createFakeVoiceOnlyUser(CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
val vu4 = FakeUserGenerator.createFakeVoiceOnlyUser(meetingId, CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
VoiceUsers.add(liveMeeting.voiceUsers, vu4)
val vu5 = FakeUserGenerator.createFakeVoiceOnlyUser(CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
val vu5 = FakeUserGenerator.createFakeVoiceOnlyUser(meetingId, CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
VoiceUsers.add(liveMeeting.voiceUsers, vu5)
for (i <- 1 to 50) {
@ -60,13 +62,14 @@ trait FakeTestData {
val others = rusers.filterNot(u => u.intId == ruser1.id)
val subscribers = others.map { o => o.intId }
val wstream1 = FakeUserGenerator.createFakeWebcamStreamFor(ruser1.id, subscribers.toSet)
Webcams.addWebcamStream(liveMeeting.webcams, wstream1)
Webcams.addWebcamStream(liveMeeting.props.meetingProp.intId, liveMeeting.webcams, wstream1)
createFakeUser(liveMeeting, ruser1)
}
def createFakeUser(liveMeeting: LiveMeeting, regUser: RegisteredUser): UserState = {
UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, pin = false,
UserState(intId = regUser.id, extId = regUser.externId, meetingId = regUser.meetingId,
name = regUser.name, role = regUser.role, pin = false,
mobile = false, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, locked = false, presenter = false,
avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown", userLeftFlag = UserLeftFlag(false, 0))

View File

@ -55,7 +55,7 @@ object FakeUserGenerator {
RandomStringGenerator.randomAlphanumericString(10) + ".png"
val color = "#ff6242"
val ru = RegisteredUsers.create(userId = id, extId, name, role,
val ru = RegisteredUsers.create(meetingId, userId = id, extId, name, role,
authToken, sessionToken, avatarURL, color, guest, authed, guestStatus = GuestStatus.ALLOW, false, "", Map(), false)
RegisteredUsers.add(users, ru, meetingId)
ru
@ -65,22 +65,50 @@ object FakeUserGenerator {
listenOnly: Boolean, floor: Boolean = false): VoiceUserState = {
val voiceUserId = RandomStringGenerator.randomAlphanumericString(8)
val lastFloorTime = System.currentTimeMillis().toString();
VoiceUserState(intId = user.id, voiceUserId = voiceUserId, callingWith, callerName = user.name,
callerNum = user.name, "#ff6242", muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime,
VoiceUserState(
intId = user.id,
voiceUserId = voiceUserId,
meetingId = user.meetingId,
callingWith,
callerName = user.name,
callerNum = user.name,
"#ff6242",
muted,
talking,
listenOnly,
"freeswitch",
System.currentTimeMillis(),
floor,
lastFloorTime,
false,
"9b3f4504-275d-4315-9922-21174262d88c")
"9b3f4504-275d-4315-9922-21174262d88c"
)
}
def createFakeVoiceOnlyUser(callingWith: String, muted: Boolean, talking: Boolean,
def createFakeVoiceOnlyUser(meetingId: String, callingWith: String, muted: Boolean, talking: Boolean,
listenOnly: Boolean, floor: Boolean = false): VoiceUserState = {
val voiceUserId = RandomStringGenerator.randomAlphanumericString(8)
val intId = "v_" + RandomStringGenerator.randomAlphanumericString(16)
val name = getRandomElement(firstNames, random) + " " + getRandomElement(lastNames, random)
val lastFloorTime = System.currentTimeMillis().toString();
VoiceUserState(intId, voiceUserId = voiceUserId, callingWith, callerName = name,
callerNum = name, "#ff6242", muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime,
false,
"9b3f4504-275d-4315-9922-21174262d88c")
VoiceUserState(
intId,
voiceUserId = voiceUserId,
meetingId = "",
callingWith,
callerName = name,
callerNum = name,
"#ff6242",
muted,
talking,
listenOnly,
"freeswitch",
System.currentTimeMillis(),
floor,
lastFloorTime,
hold = false,
"9b3f4504-275d-4315-9922-21174262d88c"
)
}
def createFakeWebcamStreamFor(userId: String, subscribers: Set[String]): WebcamStream = {

View File

@ -5,7 +5,7 @@ import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.util.RandomStringGenerator
object TestDataGen {
def createRegisteredUser(users: RegisteredUsers, name: String, role: String,
def createRegisteredUser(meetingId: String, users: RegisteredUsers, name: String, role: String,
guest: Boolean, authed: Boolean, waitForApproval: Boolean): RegisteredUser = {
val id = "w_" + RandomStringGenerator.randomAlphanumericString(16)
val extId = RandomStringGenerator.randomAlphanumericString(16)
@ -15,7 +15,7 @@ object TestDataGen {
RandomStringGenerator.randomAlphanumericString(10) + ".png"
val color = "#ff6242"
val ru = RegisteredUsers.create(userId = id, extId, name, role,
val ru = RegisteredUsers.create(meetingId, userId = id, extId, name, role,
authToken, sessionToken, avatarURL, color, guest, authed, GuestStatus.ALLOW, false, "", Map(), false)
RegisteredUsers.add(users, ru, meetingId = "test")
@ -25,19 +25,45 @@ object TestDataGen {
def createVoiceUserForUser(user: RegisteredUser, callingWith: String, muted: Boolean, talking: Boolean,
listenOnly: Boolean): VoiceUserState = {
val voiceUserId = RandomStringGenerator.randomAlphanumericString(8)
VoiceUserState(intId = user.id, voiceUserId = voiceUserId, callingWith, callerName = user.name,
callerNum = user.name, "#ff6242", muted, talking, listenOnly,
false,
VoiceUserState(
intId = user.id,
voiceUserId = voiceUserId,
meetingId = user.meetingId,
callingWith,
callerName = user.name,
callerNum = user.name,
color = "#ff6242",
muted,
talking,
listenOnly,
calledInto = "freeswitch",
lastStatusUpdateOn = System.currentTimeMillis(),
floor = false,
lastFloorTime = System.currentTimeMillis().toString(),
hold = false,
"9b3f4504-275d-4315-9922-21174262d88c")
}
def createFakeVoiceOnlyUser(callingWith: String, muted: Boolean, talking: Boolean,
def createFakeVoiceOnlyUser(meetingId: String, callingWith: String, muted: Boolean, talking: Boolean,
listenOnly: Boolean, name: String): VoiceUserState = {
val voiceUserId = RandomStringGenerator.randomAlphanumericString(8)
val intId = "v_" + RandomStringGenerator.randomAlphanumericString(16)
VoiceUserState(intId, voiceUserId = voiceUserId, callingWith, callerName = name,
callerNum = name, "#ff6242", muted, talking, listenOnly
false,
VoiceUserState(
intId,
voiceUserId = voiceUserId,
meetingId,
callingWith,
callerName = name,
callerNum = name,
color = "#ff6242",
muted,
talking,
listenOnly,
calledInto = "freeswitch",
lastStatusUpdateOn = System.currentTimeMillis(),
floor = false,
lastFloorTime = System.currentTimeMillis().toString(),
hold = false,
"9b3f4504-275d-4315-9922-21174262d88c")
}
@ -47,8 +73,8 @@ object TestDataGen {
}
def createUserFor(liveMeeting: LiveMeeting, regUser: RegisteredUser, presenter: Boolean): UserState = {
val u = UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role,
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
val u = UserState(intId = regUser.id, extId = regUser.externId, meetingId = regUser.meetingId, name = regUser.name,
role = regUser.role, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, pin = false, mobile = false,
locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242",
clientType = "unknown", userLeftFlag = UserLeftFlag(false, 0))

View File

@ -69,6 +69,7 @@ case class LockSettingsProps(
case class SystemProps(
html5InstanceId: Int,
loginUrl: String,
logoutUrl: String,
customLogoURL: String,
bannerText: String,

View File

@ -5,22 +5,24 @@ package org.bigbluebutton.common2.msgs
/**
* Sent from graphql-actions to bbb-akka
*/
object PluginDataChannelDispatchMessageMsg { val NAME = "PluginDataChannelDispatchMessageMsg" }
case class PluginDataChannelDispatchMessageMsg(header: BbbClientMsgHeader, body: PluginDataChannelDispatchMessageMsgBody) extends StandardMsg
case class PluginDataChannelDispatchMessageMsgBody(
object PluginDataChannelPushEntryMsg { val NAME = "PluginDataChannelPushEntryMsg" }
case class PluginDataChannelPushEntryMsg(header: BbbClientMsgHeader, body: PluginDataChannelPushEntryMsgBody) extends StandardMsg
case class PluginDataChannelPushEntryMsgBody(
pluginName: String,
dataChannel: String,
channelName: String,
subChannelName: String,
payloadJson: String,
toRoles: List[String],
toUserIds: List[String],
)
object PluginDataChannelDeleteMessageMsg { val NAME = "PluginDataChannelDeleteMessageMsg" }
case class PluginDataChannelDeleteMessageMsg(header: BbbClientMsgHeader, body: PluginDataChannelDeleteMessageMsgBody) extends StandardMsg
case class PluginDataChannelDeleteMessageMsgBody(
object PluginDataChannelDeleteEntryMsg { val NAME = "PluginDataChannelDeleteEntryMsg" }
case class PluginDataChannelDeleteEntryMsg(header: BbbClientMsgHeader, body: PluginDataChannelDeleteEntryMsgBody) extends StandardMsg
case class PluginDataChannelDeleteEntryMsgBody(
pluginName: String,
dataChannel: String,
messageId: String
subChannelName: String,
channelName: String,
entryId: String
)
@ -28,5 +30,6 @@ object PluginDataChannelResetMsg { val NAME = "PluginDataChannelResetMsg" }
case class PluginDataChannelResetMsg(header: BbbClientMsgHeader, body: PluginDataChannelResetMsgBody) extends StandardMsg
case class PluginDataChannelResetMsgBody(
pluginName: String,
dataChannel: String
subChannelName: String,
channelName: String
)

View File

@ -37,6 +37,7 @@ public class ApiParams {
public static final String IS_BREAKOUT = "isBreakout";
public static final String LOGO = "logo";
public static final String LOGOUT_TIMER = "logoutTimer";
public static final String LOGIN_URL = "loginURL";
public static final String LOGOUT_URL = "logoutURL";
public static final String MAX_PARTICIPANTS = "maxParticipants";
public static final String MEETING_ID = "meetingID";

View File

@ -452,7 +452,7 @@ public class MeetingService implements MessageListener {
m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(),
m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(),
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(), m.getLogoutUrl(), m.getCustomLogoURL(),
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(), m.getLoginUrl(), m.getLogoutUrl(), m.getCustomLogoURL(),
m.getBannerText(), m.getBannerColor(), m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(),
m.getOverrideClientSettings());

View File

@ -454,6 +454,7 @@ public class ParamsProcessorUtil {
// Get all the other relevant parameters and generate defaults if none
// has been provided.
String dialNumber = processDialNumber(params.get(ApiParams.DIAL_NUMBER));
String loginUrl = params.get(ApiParams.LOGIN_URL);
String logoutUrl = processLogoutUrl(params.get(ApiParams.LOGOUT_URL));
boolean record = processRecordMeeting(params.get(ApiParams.RECORD));
int maxUsers = processMaxUser(params.get(ApiParams.MAX_PARTICIPANTS));
@ -742,7 +743,9 @@ public class ParamsProcessorUtil {
internalMeetingId, createTime).withName(meetingName)
.withMaxUsers(maxUsers).withModeratorPass(modPass)
.withViewerPass(viewerPass).withRecording(record)
.withDuration(meetingDuration).withLogoutUrl(logoutUrl)
.withDuration(meetingDuration)
.withLoginUrl(loginUrl)
.withLogoutUrl(logoutUrl)
.withLogoutTimer(logoutTimer)
.withBannerText(bannerText).withBannerColor(bannerColor)
.withTelVoice(telVoice).withWebVoice(webVoice)

View File

@ -60,6 +60,7 @@ public class Meeting {
private String welcomeMsgTemplate;
private String welcomeMsg;
private String modOnlyMessage = "";
private String loginUrl;
private String logoutUrl;
private int logoutTimer = 0;
private int maxUsers;
@ -143,6 +144,7 @@ public class Meeting {
maxUsers = builder.maxUsers;
bannerColor = builder.bannerColor;
bannerText = builder.bannerText;
loginUrl = builder.loginUrl;
logoutUrl = builder.logoutUrl;
logoutTimer = builder.logoutTimer;
defaultAvatarURL = builder.defaultAvatarURL;
@ -560,6 +562,10 @@ public class Meeting {
}
public String getLoginUrl() {
return loginUrl;
}
public String getLogoutUrl() {
return logoutUrl;
}
@ -899,6 +905,7 @@ public class Meeting {
private String telVoice;
private String welcomeMsgTemplate;
private String welcomeMsg;
private String loginUrl;
private String logoutUrl;
private String bannerColor;
private String bannerText;
@ -1057,9 +1064,14 @@ public class Meeting {
return this;
}
public Builder withLoginUrl(String l) {
loginUrl = l;
return this;
}
public Builder withLogoutUrl(String l) {
logoutUrl = l;
return this;
logoutUrl = l;
return this;
}
public Builder withLogoutTimer(int l) {

View File

@ -42,6 +42,7 @@ public interface IBbbWebApiGWApp {
BreakoutRoomsParams breakoutParams,
LockSettingsParams lockSettingsParams,
Integer html5InstanceId,
String loginUrl,
String logoutUrl,
String customLogoURL,
String bannerText,

View File

@ -149,6 +149,7 @@ class BbbWebApiGWApp(
breakoutParams: BreakoutRoomsParams,
lockSettingsParams: LockSettingsParams,
html5InstanceId: java.lang.Integer,
loginUrl: String,
logoutUrl: String,
customLogoURL: String,
bannerText: String,
@ -235,6 +236,10 @@ class BbbWebApiGWApp(
val systemProps = SystemProps(
html5InstanceId,
loginUrl match {
case url: String => url
case _ => ""
},
logoutUrl,
customLogoURL,
bannerText match {

View File

@ -0,0 +1,24 @@
import { RedisMessage } from '../types';
export default function buildRedisMessage(sessionVariables: Record<string, unknown>, input: Record<string, unknown>): RedisMessage {
const eventName = `UpdateCaptionOwnerPubMsg`;
const routing = {
meetingId: sessionVariables['x-hasura-meetingid'] as String,
userId: sessionVariables['x-hasura-userid'] as String
};
const header = {
name: eventName,
meetingId: routing.meetingId,
userId: routing.userId
};
const body = {
name: '',
locale: input.locale,
ownerId: input.ownerUserId,
};
return { eventName, routing, header, body };
}

View File

@ -1,5 +1,4 @@
import { RedisMessage } from '../types';
import { ValidationError } from '../types/ValidationError';
export default function buildRedisMessage(sessionVariables: Record<string, unknown>, input: Record<string, unknown>): RedisMessage {
const eventName = `PluginDataChannelDeleteMessageMsg`;
@ -17,8 +16,9 @@ export default function buildRedisMessage(sessionVariables: Record<string, unkno
const body = {
pluginName: input.pluginName,
dataChannel: input.dataChannel,
messageId: input.messageId
channelName: input.channelName,
subChannelName: input.subChannelName,
entryId: input.entryId
};
return { eventName, routing, header, body };

View File

@ -2,7 +2,7 @@ import { RedisMessage } from '../types';
import { ValidationError } from '../types/ValidationError';
export default function buildRedisMessage(sessionVariables: Record<string, unknown>, input: Record<string, unknown>): RedisMessage {
const eventName = `PluginDataChannelDispatchMessageMsg`;
const eventName = `PluginDataChannelPushEntryMsg`;
const routing = {
meetingId: sessionVariables['x-hasura-meetingid'] as String,
@ -23,7 +23,8 @@ export default function buildRedisMessage(sessionVariables: Record<string, unkno
const body = {
pluginName: input.pluginName,
dataChannel: input.dataChannel,
channelName: input.channelName,
subChannelName: input.subChannelName,
payloadJson: input.payloadJson,
toRoles: input.toRoles,
toUserIds: input.toUserIds

View File

@ -1,5 +1,4 @@
import { RedisMessage } from '../types';
import { ValidationError } from '../types/ValidationError';
export default function buildRedisMessage(sessionVariables: Record<string, unknown>, input: Record<string, unknown>): RedisMessage {
const eventName = `PluginDataChannelResetMsg`;
@ -17,7 +16,8 @@ export default function buildRedisMessage(sessionVariables: Record<string, unkno
const body = {
pluginName: input.pluginName,
dataChannel: input.dataChannel
channelName: input.channelName,
subChannelName: input.subChannelName
};
return { eventName, routing, header, body };

View File

@ -20,6 +20,7 @@ create table "meeting" (
"presentationUploadExternalUrl" varchar(500),
"learningDashboardAccessToken" varchar(100),
"html5InstanceId" varchar(100),
"loginUrl" varchar(500),
"logoutUrl" varchar(500),
"customLogoUrl" varchar(500),
"bannerText" text,
@ -248,9 +249,9 @@ create view "v_meeting_group" as select * from meeting_group;
-- ========== User tables
CREATE TABLE "user" (
"userId" varchar(50) NOT NULL PRIMARY KEY,
"meetingId" varchar(100) references "meeting"("meetingId") ON DELETE CASCADE,
"userId" varchar(50) NOT NULL,
"extId" varchar(50),
"meetingId" varchar(100) references "meeting"("meetingId") ON DELETE CASCADE,
"name" varchar(255),
"role" varchar(20),
"avatar" varchar(500),
@ -275,10 +276,11 @@ CREATE TABLE "user" (
"awayTime" timestamp with time zone,
"emoji" varchar,
"emojiTime" timestamp with time zone,
"guestStatusSetByModerator" varchar(50) references "user"("userId") ON DELETE SET NULL,
"guestStatusSetByModerator" varchar(50),
"guestLobbyMessage" text,
"mobile" bool,
"clientType" varchar(50),
"transferredFromParentMeeting" bool default false, --when a user join in breakoutRoom only in audio
"disconnected" bool default false, -- this is the old leftFlag (that was renamed), set when the user just closed the client
"expired" bool default false, -- when it is been some time the user is disconnected
"ejected" bool,
@ -292,7 +294,9 @@ CREATE TABLE "user" (
"inactivityWarningDisplay" bool default FALSE,
"inactivityWarningTimeoutSecs" numeric,
"hasDrawPermissionOnCurrentPage" bool default FALSE,
"echoTestRunningAt" timestamp with time zone
"echoTestRunningAt" timestamp with time zone,
CONSTRAINT "user_pkey" PRIMARY KEY ("meetingId","userId"),
FOREIGN KEY ("meetingId", "guestStatusSetByModerator") REFERENCES "user"("meetingId","userId") ON DELETE SET NULL
);
CREATE INDEX "idx_user_meetingId" ON "user"("meetingId");
CREATE INDEX "idx_user_extId" ON "user"("meetingId", "extId");
@ -478,9 +482,10 @@ where u."guestStatus" = 'WAIT';
--it is necessary because v_user has some conditions like "lockSettings-hideUserList"
--but viewers still needs to query this users as foreign key of chat, cameras, etc
CREATE OR REPLACE VIEW "v_user_ref"
AS SELECT "user"."userId",
"user"."extId",
AS SELECT
"user"."meetingId",
"user"."userId",
"user"."extId",
"user"."name",
"user"."nameSortable",
"user"."avatar",
@ -512,16 +517,17 @@ AS SELECT "user"."userId",
FROM "user";
create table "user_customParameter"(
"userId" varchar(50) REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"parameter" varchar(255),
"value" varchar(255),
CONSTRAINT "user_customParameter_pkey" PRIMARY KEY ("userId","parameter")
CONSTRAINT "user_customParameter_pkey" PRIMARY KEY ("meetingId", "userId","parameter"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
CREATE VIEW "v_user_customParameter" AS
SELECT u."meetingId", "user_customParameter".*
FROM "user_customParameter"
JOIN "user" u ON u."userId" = "user_customParameter"."userId";
SELECT *
FROM "user_customParameter";
CREATE VIEW "v_user_welcomeMsgs" AS
SELECT
@ -534,7 +540,8 @@ join meeting_welcome w USING("meetingId");
CREATE TABLE "user_voice" (
"userId" varchar(50) PRIMARY KEY NOT NULL REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"voiceUserId" varchar(100),
"callerName" varchar(100),
"callerNum" varchar(100),
@ -551,7 +558,9 @@ CREATE TABLE "user_voice" (
"voiceConfClientSession" varchar(10),
"voiceConfCallState" varchar(30),
"endTime" bigint,
"startTime" bigint
"startTime" bigint,
CONSTRAINT "user_voice_pkey" PRIMARY KEY ("meetingId","userId"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
--CREATE INDEX "idx_user_voice_userId" ON "user_voice"("userId");
-- + 6000 means it will hide after 6 seconds
@ -564,19 +573,26 @@ GENERATED ALWAYS AS (to_timestamp("startTime"::double precision / 1000)) STORED;
ALTER TABLE "user_voice" ADD COLUMN "endedAt" timestamp with time zone
GENERATED ALWAYS AS (to_timestamp("endTime"::double precision / 1000)) STORED;
CREATE INDEX "idx_user_voice_userId_talking" ON "user_voice"("userId","talking");
CREATE INDEX "idx_user_voice_userId_hideTalkingIndicatorAt" ON "user_voice"("userId","hideTalkingIndicatorAt");
CREATE INDEX "idx_user_voice_userId_talking" ON "user_voice"("meetingId", "userId","talking");
CREATE INDEX "idx_user_voice_userId_hideTalkingIndicatorAt" ON "user_voice"("meetingId", "userId","hideTalkingIndicatorAt");
CREATE OR REPLACE VIEW "v_user_voice" AS
SELECT
u."meetingId",
"user_voice" .*,
greatest(coalesce(user_voice."startTime", 0), coalesce(user_voice."endTime", 0)) AS "lastSpeakChangedAt",
user_talking."userId" IS NOT NULL "showTalkingIndicator"
FROM "user" u
JOIN "user_voice" ON "user_voice"."userId" = u."userId"
LEFT JOIN "user_voice" user_talking ON (user_talking."userId" = u."userId" and user_talking."talking" IS TRUE)
OR (user_talking."userId" = u."userId" and user_talking."hideTalkingIndicatorAt" > now())
FROM "user_voice"
LEFT JOIN "user_voice" user_talking ON (
user_talking."meetingId" = user_voice."meetingId" and
user_talking."userId" = user_voice."userId" and
user_talking."talking" IS TRUE
)
OR
(
user_talking."meetingId" = user_voice."meetingId" and
user_talking."userId" = user_voice."userId" and
user_talking."hideTalkingIndicatorAt" > now()
)
WHERE "user_voice"."joined" is true;
@ -588,7 +604,7 @@ CREATE OR REPLACE FUNCTION "update_user_voiceUpdatedAt_func"() RETURNS TRIGGER A
BEGIN
UPDATE "user"
SET "voiceUpdatedAt" = current_timestamp
WHERE "userId" = NEW."userId";
WHERE "meetingId" = NEW."meetingId" AND "userId" = NEW."userId";
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
@ -630,51 +646,58 @@ SELECT
greatest(coalesce(user_voice."startTime", 0), coalesce(user_voice."endTime", 0)) AS "lastSpeakChangedAt",
user_talking."userId" IS NOT NULL "showTalkingIndicator"
FROM "user" u
LEFT JOIN "user_voice" ON "user_voice"."userId" = u."userId"
LEFT JOIN "user_voice" user_talking ON (user_talking."userId" = u."userId" and user_talking."talking" IS TRUE)
OR (user_talking."userId" = u."userId" and user_talking."hideTalkingIndicatorAt" > now());
LEFT JOIN "user_voice" ON "user_voice"."meetingId" = u."meetingId" AND "user_voice"."userId" = u."userId"
LEFT JOIN "user_voice" user_talking ON (
user_talking."meetingId" = u."meetingId" and
user_talking."userId" = u."userId" and
user_talking."talking" IS TRUE
)
OR (
user_talking."meetingId" = u."meetingId" and
user_talking."userId" = u."userId" and
user_talking."hideTalkingIndicatorAt" > now()
);
---TEMPORARY MINIMONGO ADAPTER END
CREATE TABLE "user_camera" (
"streamId" varchar(100) PRIMARY KEY,
"userId" varchar(50) NOT NULL REFERENCES "user"("userId") ON DELETE CASCADE
"meetingId" varchar(100),
"userId" varchar(50),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
CREATE INDEX "idx_user_camera_userId" ON "user_camera"("userId");
CREATE INDEX "idx_user_camera_userId" ON "user_camera"("meetingId", "userId");
CREATE OR REPLACE VIEW "v_user_camera" AS
SELECT
u."meetingId",
"user_camera" .*
FROM "user_camera"
JOIN "user" u ON u."userId" = user_camera."userId";
SELECT * FROM "user_camera";
CREATE TABLE "user_breakoutRoom" (
"userId" varchar(50) PRIMARY KEY REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"breakoutRoomId" varchar(100),
"isDefaultName" boolean,
"sequence" int,
"shortName" varchar(100),
"currentlyInRoom" boolean
"currentlyInRoom" boolean,
CONSTRAINT "user_breakoutRoom_pkey" PRIMARY KEY ("meetingId","userId"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
--CREATE INDEX "idx_user_breakoutRoom_userId" ON "user_breakoutRoom"("userId");
--CREATE INDEX "idx_user_breakoutRoom_userId" ON "user_breakoutRoom"("meetingId", "userId");
CREATE OR REPLACE VIEW "v_user_breakoutRoom" AS
SELECT
u."meetingId",
"user_breakoutRoom" .*
FROM "user_breakoutRoom"
JOIN "user" u ON u."userId" = "user_breakoutRoom"."userId";
SELECT * FROM "user_breakoutRoom";
CREATE TABLE "user_connectionStatus" (
"userId" varchar(50) PRIMARY KEY REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100) REFERENCES "meeting"("meetingId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"connectionAliveAtMaxIntervalMs" numeric,
"connectionAliveAt" timestamp with time zone,
"networkRttInMs" numeric,
"status" varchar(25),
"statusUpdatedAt" timestamp with time zone
"statusUpdatedAt" timestamp with time zone,
CONSTRAINT "user_connectionStatus_voice_pkey" PRIMARY KEY ("meetingId","userId"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
create index "idx_user_connectionStatus_meetingId" on "user_connectionStatus"("meetingId");
@ -718,7 +741,8 @@ EXECUTE FUNCTION "update_connectionAliveAtMaxIntervalMs"();
--);
CREATE TABLE "user_connectionStatusMetrics" (
"userId" varchar(50) REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"status" varchar(25),
"occurrencesCount" integer,
"firstOccurrenceAt" timestamp with time zone,
@ -726,10 +750,11 @@ CREATE TABLE "user_connectionStatusMetrics" (
"lowestNetworkRttInMs" numeric,
"highestNetworkRttInMs" numeric,
"lastNetworkRttInMs" numeric,
CONSTRAINT "user_connectionStatusMetrics_pkey" PRIMARY KEY ("userId","status")
CONSTRAINT "user_connectionStatusMetrics_pkey" PRIMARY KEY ("meetingId","userId","status"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
create index "idx_user_connectionStatusMetrics_userId" on "user_connectionStatusMetrics"("userId");
create index "idx_user_connectionStatusMetrics_userId" on "user_connectionStatusMetrics"("meetingId","userId");
--This function populate rtt, status and the table user_connectionStatusMetrics
CREATE OR REPLACE FUNCTION "update_user_connectionStatus_trigger_func"() RETURNS TRIGGER AS $$
@ -745,10 +770,10 @@ BEGIN
"lowestNetworkRttInMs" = LEAST("user_connectionStatusMetrics"."lowestNetworkRttInMs",NEW."networkRttInMs"),
"lastNetworkRttInMs" = NEW."networkRttInMs",
"lastOccurrenceAt" = current_timestamp
WHERE "userId"=NEW."userId" AND "status"= NEW."status" RETURNING *)
INSERT INTO "user_connectionStatusMetrics"("userId","status","occurrencesCount", "firstOccurrenceAt",
WHERE "meetingId"=NEW."meetingId" AND "userId"=NEW."userId" AND "status"= NEW."status" RETURNING *)
INSERT INTO "user_connectionStatusMetrics"("meetingId","userId","status","occurrencesCount", "firstOccurrenceAt",
"highestNetworkRttInMs", "lowestNetworkRttInMs", "lastNetworkRttInMs")
SELECT NEW."userId", NEW."status", 1, current_timestamp,
SELECT NEW."meetingId", NEW."userId", NEW."status", 1, current_timestamp,
NEW."networkRttInMs", NEW."networkRttInMs", NEW."networkRttInMs"
WHERE NOT EXISTS (SELECT * FROM upsert);
@ -768,11 +793,11 @@ CASE WHEN max(cs."connectionAliveAt") < current_timestamp - INTERVAL '1 millisec
(array_agg(csm."status" ORDER BY csm."lastOccurrenceAt" DESC))[1] as "lastUnstableStatus",
max(csm."lastOccurrenceAt") AS "lastUnstableStatusAt"
FROM "user" u
JOIN "user_connectionStatus" cs ON cs."userId" = u."userId"
LEFT JOIN "user_connectionStatusMetrics" csm ON csm."userId" = u."userId" AND csm."status" != 'normal'
JOIN "user_connectionStatus" cs ON cs."meetingId" = u."meetingId" and cs."userId" = u."userId"
LEFT JOIN "user_connectionStatusMetrics" csm ON csm."meetingId" = u."meetingId" AND csm."userId" = u."userId" AND csm."status" != 'normal'
GROUP BY u."meetingId", u."userId";
CREATE INDEX "idx_user_connectionStatusMetrics_UnstableReport" ON "user_connectionStatusMetrics" ("userId") WHERE "status" != 'normal';
CREATE INDEX "idx_user_connectionStatusMetrics_UnstableReport" ON "user_connectionStatusMetrics" ("meetingId", "userId") WHERE "status" != 'normal';
CREATE TABLE "user_graphqlConnection" (
@ -806,23 +831,27 @@ CREATE INDEX "idx_user_graphqlConnectionSessionToken" ON "user_graphqlConnection
--LEFT JOIN "user_connectionStatus" uc ON uc."userId" = u."userId";
CREATE TABLE "user_clientSettings"(
"userId" varchar(50) PRIMARY KEY REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100) references "meeting"("meetingId") ON DELETE CASCADE,
"userClientSettingsJson" jsonb
"meetingId" varchar(100),
"userId" varchar(50),
"userClientSettingsJson" jsonb,
CONSTRAINT "user_clientSettings_pkey" PRIMARY KEY ("meetingId","userId"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
CREATE INDEX "idx_user_clientSettings_meetingId" ON "user_clientSettings"("meetingId");
CREATE INDEX "idx_user_clientSettings_userId" ON "user_clientSettings"("userId");
CREATE INDEX "idx_user_clientSettings_userId" ON "user_clientSettings"("meetingId", "userId");
create view "v_user_clientSettings" as select * from "user_clientSettings";
CREATE TABLE "user_reaction" (
"userId" varchar(50) REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"reactionEmoji" varchar(25),
"durationInSeconds" integer not null,
"createdAt" timestamp with time zone not null,
"expiresAt" timestamp with time zone
"expiresAt" timestamp with time zone,
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
--Set expiresAt on isert or update user_reaction
@ -841,29 +870,29 @@ EXECUTE FUNCTION "update_user_reaction_trigger_func"();
--ALTER TABLE "user_reaction" ADD COLUMN "expiresAt" timestamp with time zone GENERATED ALWAYS AS ("createdAt" + '1 seconds'::INTERVAL * "durationInSeconds") STORED;
CREATE INDEX "idx_user_reaction_userId_createdAt" ON "user_reaction"("userId", "expiresAt");
CREATE INDEX "idx_user_reaction_userId_createdAt" ON "user_reaction"("meetingId", "userId", "expiresAt");
CREATE VIEW v_user_reaction AS
SELECT u."meetingId", ur."userId", ur."reactionEmoji", ur."createdAt", ur."expiresAt"
FROM "user" u
JOIN "user_reaction" ur ON u."userId" = ur."userId" AND "expiresAt" > current_timestamp;
SELECT ur."meetingId", ur."userId", ur."reactionEmoji", ur."createdAt", ur."expiresAt"
FROM "user_reaction" ur;
CREATE VIEW v_user_reaction_current AS
SELECT u."meetingId", ur."userId", (array_agg(ur."reactionEmoji" ORDER BY ur."expiresAt" DESC))[1] as "reactionEmoji"
FROM "user" u
JOIN "user_reaction" ur ON u."userId" = ur."userId" AND "expiresAt" > current_timestamp
GROUP BY u."meetingId", ur."userId";
SELECT ur."meetingId", ur."userId", (array_agg(ur."reactionEmoji" ORDER BY ur."expiresAt" DESC))[1] as "reactionEmoji"
FROM "user_reaction" ur
GROUP BY ur."meetingId", ur."userId";
CREATE TABLE "user_transcriptionError"(
"userId" varchar(50) PRIMARY KEY REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100) references "meeting"("meetingId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"errorCode" varchar(255),
"errorMessage" text,
"lastUpdatedAt" timestamp with time zone DEFAULT now()
"lastUpdatedAt" timestamp with time zone DEFAULT now(),
CONSTRAINT "user_transcriptionError_pkey" PRIMARY KEY ("meetingId","userId"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
CREATE INDEX "idx_user_transcriptionError_meetingId" ON "user_transcriptionError"("meetingId");
CREATE INDEX "idx_user_transcriptionError_userId" ON "user_transcriptionError"("userId");
CREATE INDEX "idx_user_transcriptionError_userId" ON "user_transcriptionError"("meetingId", "userId");
create view "v_user_transcriptionError" as select * from "user_transcriptionError";
@ -873,7 +902,7 @@ create view "v_user_transcriptionError" as select * from "user_transcriptionErro
create view "v_meeting" as
select "meeting".*, "user_ended"."name" as "endedByUserName"
from "meeting"
left join "user" "user_ended" on "user_ended"."userId" = "meeting"."endedBy"
left join "user" "user_ended" on "user_ended"."meetingId" = "meeting"."meetingId" and "user_ended"."userId" = "meeting"."endedBy"
;
create view "v_meeting_learningDashboard" as
@ -1004,8 +1033,12 @@ LEFT JOIN "chat_user" cu ON cu."meetingId" = "user"."meetingId" AND cu."userId"
--now it will always add chat_user for public chat onUserJoin
--JOIN "chat" ON "user"."meetingId" = chat."meetingId" AND (cu."chatId" = chat."chatId" OR chat."chatId" = 'MAIN-PUBLIC-GROUP-CHAT')
JOIN "chat" ON "user"."meetingId" = chat."meetingId" AND cu."chatId" = chat."chatId"
LEFT JOIN "chat_user" chat_with ON chat_with."meetingId" = chat."meetingId" AND chat_with."chatId" = chat."chatId" AND chat."chatId" != 'MAIN-PUBLIC-GROUP-CHAT' AND chat_with."userId" != cu."userId"
LEFT JOIN chat_message cm ON cm."meetingId" = chat."meetingId" AND cm."chatId" = chat."chatId"
LEFT JOIN "chat_user" chat_with ON chat_with."meetingId" = chat."meetingId" AND
chat_with."chatId" = chat."chatId" AND
chat."chatId" != 'MAIN-PUBLIC-GROUP-CHAT' AND
chat_with."userId" != cu."userId"
LEFT JOIN chat_message cm ON cm."meetingId" = chat."meetingId" AND
cm."chatId" = chat."chatId"
WHERE cu."visible" is true
GROUP BY "user"."userId", chat."meetingId", chat."chatId", cu."visible", cu."lastSeenAt", chat_with."userId";
@ -1015,14 +1048,23 @@ FROM chat_message cm
WHERE cm."chatId" = 'MAIN-PUBLIC-GROUP-CHAT';
CREATE OR REPLACE VIEW "v_chat_message_private" AS
SELECT cu."userId",
cm.*
SELECT cu."meetingId",
cu."userId",
cm."messageId",
cm."chatId",
cm."correlationId",
cm."chatEmphasizedText",
cm."message",
cm."messageType",
cm."messageMetadata",
cm."senderId",
cm."senderName",
cm."senderRole",
cm."createdAt"
FROM chat_message cm
JOIN chat_user cu ON cu."meetingId" = cm."meetingId" AND cu."chatId" = cm."chatId"
WHERE cm."chatId" != 'MAIN-PUBLIC-GROUP-CHAT';
--============ Presentation / Annotation
@ -1170,6 +1212,7 @@ and pres_presentation."current" IS TRUE;
CREATE TABLE "pres_annotation" (
"annotationId" varchar(100) PRIMARY KEY,
"pageId" varchar(100) REFERENCES "pres_page"("pageId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"annotationInfo" TEXT,
"lastHistorySequence" integer,
@ -1182,6 +1225,7 @@ CREATE TABLE "pres_annotation_history" (
"sequence" serial PRIMARY KEY,
"annotationId" varchar(100),
"pageId" varchar(100) REFERENCES "pres_page"("pageId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"annotationInfo" TEXT
-- "lastUpdatedAt" timestamp with time zone DEFAULT now()
@ -1189,7 +1233,7 @@ CREATE TABLE "pres_annotation_history" (
CREATE INDEX "idx_pres_annotation_history_pageId" ON "pres_annotation"("pageId");
CREATE VIEW "v_pres_annotation_curr" AS
SELECT p."meetingId", pp."presentationId", pa.*
SELECT p."meetingId", pp."presentationId", pa."annotationId", pa."pageId", pa."userId", pa."annotationInfo", pa."lastHistorySequence", pa."lastUpdatedAt"
FROM pres_presentation p
JOIN pres_page pp ON pp."presentationId" = p."presentationId"
JOIN pres_annotation pa ON pa."pageId" = pp."pageId"
@ -1197,7 +1241,7 @@ WHERE p."current" IS true
AND pp."current" IS true;
CREATE VIEW "v_pres_annotation_history_curr" AS
SELECT p."meetingId", pp."presentationId", pah.*
SELECT p."meetingId", pp."presentationId", pah."pageId", pah."userId", pah."annotationId", pah."annotationInfo", pah."sequence"
FROM pres_presentation p
JOIN pres_page pp ON pp."presentationId" = p."presentationId"
JOIN pres_annotation_history pah ON pah."pageId" = pp."pageId"
@ -1206,20 +1250,20 @@ AND pp."current" IS true;
CREATE TABLE "pres_page_writers" (
"pageId" varchar(100) REFERENCES "pres_page"("pageId") ON DELETE CASCADE,
"userId" varchar(50) REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"changedModeOn" bigint,
CONSTRAINT "pres_page_writers_pkey" PRIMARY KEY ("pageId","userId")
CONSTRAINT "pres_page_writers_pkey" PRIMARY KEY ("pageId","meetingId","userId"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
create index "idx_pres_page_writers_userID" on "pres_page_writers"("userId");
create index "idx_pres_page_writers_userID" on "pres_page_writers"("meetingId", "userId");
CREATE OR REPLACE VIEW "v_pres_page_writers" AS
SELECT
u."meetingId",
"pres_presentation"."presentationId",
"pres_page_writers" .*,
CASE WHEN pres_presentation."current" IS true AND pres_page."current" IS true THEN true ELSE false END AS "isCurrentPage"
FROM "pres_page_writers"
JOIN "user" u ON u."userId" = "pres_page_writers"."userId"
JOIN "pres_page" ON "pres_page"."pageId" = "pres_page_writers"."pageId"
JOIN "pres_presentation" ON "pres_presentation"."presentationId" = "pres_page"."presentationId" ;
@ -1251,7 +1295,8 @@ BEGIN
CASE WHEN presenter THEN TRUE
WHEN EXISTS (
SELECT 1 FROM "v_pres_page_writers" v
WHERE v."userId" = "user"."userId"
WHERE v."meetingId" = "user"."meetingId"
AND v."userId" = "user"."userId"
AND v."isCurrentPage" IS TRUE
) THEN TRUE
ELSE FALSE
@ -1266,7 +1311,7 @@ $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION update_user_presenter_trigger_func() RETURNS TRIGGER AS $$
BEGIN
IF OLD."presenter" <> NEW."presenter" THEN
PERFORM "update_user_hasDrawPermissionOnCurrentPage"(NEW."userId", NULL);
PERFORM "update_user_hasDrawPermissionOnCurrentPage"(NEW."userId", NEW."meetingId");
END IF;
RETURN NEW;
END;
@ -1309,9 +1354,9 @@ CREATE OR REPLACE FUNCTION ins_upd_del_pres_page_writers_trigger_func()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' or TG_OP = 'INSERT' THEN
PERFORM "update_user_hasDrawPermissionOnCurrentPage"(NEW."userId", NULL);
PERFORM "update_user_hasDrawPermissionOnCurrentPage"(NEW."userId", NEW."meetingId");
ELSIF TG_OP = 'DELETE' THEN
PERFORM "update_user_hasDrawPermissionOnCurrentPage"(OLD."userId", NULL);
PERFORM "update_user_hasDrawPermissionOnCurrentPage"(OLD."userId", NEW."meetingId");
END IF;
RETURN NEW;
END;
@ -1326,18 +1371,20 @@ FOR EACH ROW EXECUTE FUNCTION ins_upd_del_pres_page_writers_trigger_func();
CREATE TABLE "pres_page_cursor" (
"pageId" varchar(100) REFERENCES "pres_page"("pageId") ON DELETE CASCADE,
"userId" varchar(50) REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"xPercent" numeric,
"yPercent" numeric,
"lastUpdatedAt" timestamp with time zone DEFAULT now(),
CONSTRAINT "pres_page_cursor_pkey" PRIMARY KEY ("pageId","userId")
CONSTRAINT "pres_page_cursor_pkey" PRIMARY KEY ("pageId","meetingId","userId"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
create index "idx_pres_page_cursor_pageId" on "pres_page_cursor"("pageId");
create index "idx_pres_page_cursor_userID" on "pres_page_cursor"("userId");
create index "idx_pres_page_cursor_userID" on "pres_page_cursor"("meetingId","userId");
create index "idx_pres_page_cursor_lastUpdatedAt" on "pres_page_cursor"("pageId","lastUpdatedAt");
CREATE VIEW "v_pres_page_cursor" AS
SELECT pres_presentation."meetingId", pres_page."presentationId", c.*,
SELECT pres_page."presentationId", c.*,
CASE WHEN pres_presentation."current" IS true AND pres_page."current" IS true THEN true ELSE false END AS "isCurrentPage"
FROM pres_page_cursor c
JOIN pres_page ON pres_page."pageId" = c."pageId"
@ -1349,8 +1396,8 @@ JOIN pres_presentation ON pres_presentation."presentationId" = pres_page."presen
CREATE TABLE "poll" (
"pollId" varchar(100) PRIMARY KEY,
"meetingId" varchar(100) REFERENCES "meeting"("meetingId") ON DELETE CASCADE,
"ownerId" varchar(100) REFERENCES "user"("userId"),
"meetingId" varchar(100),
"ownerId" varchar(100),
"questionText" TEXT,
"type" varchar(30),
"secret" boolean,
@ -1358,9 +1405,11 @@ CREATE TABLE "poll" (
"ended" boolean,
"published" boolean,
"publishedAt" timestamp with time zone,
"createdAt" timestamp with time zone not null default current_timestamp
"createdAt" timestamp with time zone not null default current_timestamp,
FOREIGN KEY ("meetingId", "ownerId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
CREATE INDEX "idx_poll_meetingId" ON "poll"("meetingId");
CREATE INDEX "idx_poll_ownerId" ON "poll"("meetingId","ownerId");
CREATE INDEX "idx_poll_meetingId_active" ON "poll"("meetingId") where ended is false;
CREATE INDEX "idx_poll_meetingId_published" ON "poll"("meetingId") where published is true;
@ -1375,12 +1424,14 @@ CREATE INDEX "idx_poll_option_pollId" ON "poll_option"("pollId");
CREATE TABLE "poll_response" (
"pollId" varchar(100),
"optionId" integer,
"userId" varchar(100) REFERENCES "user"("userId") ON DELETE CASCADE,
FOREIGN KEY ("pollId", "optionId") REFERENCES "poll_option"("pollId", "optionId") ON DELETE CASCADE
"meetingId" varchar(100),
"userId" varchar(100),
FOREIGN KEY ("pollId", "optionId") REFERENCES "poll_option"("pollId", "optionId") ON DELETE CASCADE,
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
CREATE INDEX "idx_poll_response_pollId" ON "poll_response"("pollId");
CREATE INDEX "idx_poll_response_userId" ON "poll_response"("userId");
CREATE INDEX "idx_poll_response_pollId_userId" ON "poll_response"("pollId", "userId");
CREATE INDEX "idx_poll_response_userId" ON "poll_response"("meetingId", "userId");
CREATE INDEX "idx_poll_response_pollId_userId" ON "poll_response"("pollId", "meetingId", "userId");
CREATE OR REPLACE VIEW "v_poll_response" AS
SELECT
@ -1388,6 +1439,7 @@ poll."meetingId",
poll."pollId",
poll."type",
poll."questionText",
poll."meetingId" AS "pollOwnerMeetingId",
poll."ownerId" AS "pollOwnerId",
poll.published,
o."optionId",
@ -1402,12 +1454,13 @@ ORDER BY poll."pollId";
CREATE VIEW "v_poll_user" AS
SELECT
poll."meetingId",
poll."meetingId" AS "pollOwnerMeetingId",
poll."ownerId" AS "pollOwnerId",
u."meetingId",
u."userId",
poll."pollId",
poll."type",
poll."questionText",
poll."ownerId" AS "pollOwnerId",
u."userId",
array_remove(array_agg(o."optionId"), NULL) AS "optionIds",
array_remove(array_agg(o."optionDesc"), NULL) AS "optionDescIds",
CASE WHEN count(o."optionId") > 0 THEN TRUE ELSE FALSE end responded
@ -1415,7 +1468,7 @@ FROM poll
JOIN v_user u ON u."meetingId" = poll."meetingId" AND "isDialIn" IS FALSE AND presenter IS FALSE
LEFT JOIN poll_response r ON r."pollId" = poll."pollId" AND r."userId" = u."userId"
LEFT JOIN poll_option o ON o."pollId" = r."pollId" AND o."optionId" = r."optionId"
GROUP BY poll."pollId", u."userId", u.name ;
GROUP BY poll."pollId", u."meetingId", u."userId";
CREATE VIEW "v_poll" AS SELECT * FROM "poll";
@ -1426,11 +1479,13 @@ JOIN poll using("pollId")
WHERE poll."type" != 'R-';
create view "v_poll_user_current" as
select "user"."userId", "poll"."pollId", case when count(pr.*) > 0 then true else false end as responded
select "user"."meetingId", "user"."userId", "poll"."pollId", case when count(pr.*) > 0 then true else false end as responded
from "user"
join "poll" on "poll"."meetingId" = "user"."meetingId"
left join "poll_response" pr on pr."userId" = "user"."userId" and pr."pollId" = "poll"."pollId"
group by "user"."userId", "poll"."pollId";
left join "poll_response" pr on pr."meetingId" = "user"."meetingId" and
pr."userId" = "user"."userId" and
pr."pollId" = "poll"."pollId"
group by "user"."meetingId", "user"."userId", "poll"."pollId";
--------------------------------
----External video
@ -1538,12 +1593,14 @@ CREATE INDEX "idx_breakoutRoom_parentMeetingId" ON "breakoutRoom"("parentMeeting
CREATE TABLE "breakoutRoom_user" (
"breakoutRoomId" varchar(100) NOT NULL REFERENCES "breakoutRoom"("breakoutRoomId") ON DELETE CASCADE,
"userId" varchar(50) NOT NULL REFERENCES "user"("userId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"joinURL" text,
"assignedAt" timestamp with time zone,
"joinedAt" timestamp with time zone,
"inviteDismissedAt" timestamp with time zone,
CONSTRAINT "breakoutRoom_user_pkey" PRIMARY KEY ("breakoutRoomId", "userId")
CONSTRAINT "breakoutRoom_user_pkey" PRIMARY KEY ("breakoutRoomId", "meetingId", "userId"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
CREATE OR REPLACE VIEW "v_breakoutRoom" AS
@ -1564,20 +1621,20 @@ SELECT *,
AND ("isModerator" is false OR "sendInvitationToModerators")
THEN TRUE ELSE FALSE END "showInvitation"
from (
SELECT u."userId", b."parentMeetingId", b."breakoutRoomId", b."freeJoin", b."sequence", b."name", b."isDefaultName",
SELECT u."meetingId" as "userMeetingId", u."userId", b."parentMeetingId", b."breakoutRoomId", b."freeJoin", b."sequence", b."name", b."isDefaultName",
b."shortName", b."startedAt", b."endedAt", b."durationInSeconds", b."sendInvitationToModerators",
bu."assignedAt", bu."joinURL", bu."inviteDismissedAt", u."role" = 'MODERATOR' as "isModerator",
--CASE WHEN b."durationInSeconds" = 0 THEN NULL ELSE b."startedAt" + b."durationInSeconds" * '1 second'::INTERVAL END AS "willEndAt",
ub."isOnline" AS "currentRoomIsOnline",
ub."registeredAt" AS "currentRoomRegisteredAt",
ub."joined" AS "currentRoomJoined",
rank() OVER (partition BY u."userId" order by "assignedAt" desc nulls last) as "currentRoomPriority",
max(bu."joinedAt") OVER (partition BY u."userId") AS "lastRoomJoinedAt",
max(bu."breakoutRoomId") OVER (partition BY u."userId" ORDER BY bu."joinedAt") AS "lastRoomJoinedId",
sum(CASE WHEN ub."isOnline" THEN 1 ELSE 0 END) OVER (partition BY u."userId") > 0 as "lastRoomIsOnline"
rank() OVER (partition BY u."meetingId", u."userId" order by "assignedAt" desc nulls last) as "currentRoomPriority",
max(bu."joinedAt") OVER (partition BY u."meetingId", u."userId") AS "lastRoomJoinedAt",
max(bu."breakoutRoomId") OVER (partition BY u."meetingId", u."userId" ORDER BY bu."joinedAt") AS "lastRoomJoinedId",
sum(CASE WHEN ub."isOnline" THEN 1 ELSE 0 END) OVER (partition BY u."meetingId", u."userId") > 0 as "lastRoomIsOnline"
FROM "user" u
JOIN "breakoutRoom" b ON b."parentMeetingId" = u."meetingId"
LEFT JOIN "breakoutRoom_user" bu ON bu."userId" = u."userId" AND bu."breakoutRoomId" = b."breakoutRoomId"
LEFT JOIN "breakoutRoom_user" bu ON bu."meetingId" = u."meetingId" AND bu."userId" = u."userId" AND bu."breakoutRoomId" = b."breakoutRoomId"
LEFT JOIN "meeting" mb ON mb."extId" = b."externalId"
LEFT JOIN "v_user" ub ON ub."meetingId" = mb."meetingId" and ub."extId" = u."extId" || '-' || b."sequence"
WHERE (bu."assignedAt" IS NOT NULL
@ -1587,16 +1644,32 @@ from (
) a;
CREATE OR REPLACE VIEW "v_breakoutRoom_assignedUser" AS
SELECT "parentMeetingId", "breakoutRoomId", "userId"
SELECT "parentMeetingId", "breakoutRoomId", "userMeetingId", "userId"
FROM "v_breakoutRoom"
WHERE "assignedAt" IS NOT NULL;
--TODO improve performance (and handle two users with same extId)
CREATE OR REPLACE VIEW "v_breakoutRoom_participant" AS
SELECT DISTINCT "parentMeetingId", "breakoutRoomId", "userId"
CREATE OR REPLACE VIEW "v_breakoutRoom_participant" as
SELECT DISTINCT
"parentMeetingId",
"breakoutRoomId",
"userMeetingId",
"userId",
false as "isAudioOnly"
FROM "v_breakoutRoom"
WHERE "currentRoomIsOnline" IS TRUE;
--SELECT DISTINCT br."parentMeetingId", br."breakoutRoomId", "user"."userId"
WHERE "currentRoomIsOnline" IS TRUE
union --include users that joined only with audio
select parent_user."meetingId" as "parentMeetingId",
bk_user."meetingId" as "breakoutRoomId",
parent_user."meetingId" as "userMeetingId",
parent_user."userId",
true as "isAudioOnly"
from "user" bk_user
join "user" parent_user on parent_user."userId" = bk_user."userId" and parent_user."transferredFromParentMeeting" is false
where bk_user."transferredFromParentMeeting" is true
and bk_user."loggedOut" is false;
--SELECT DISTINCT br."parentMeetingId", br."breakoutRoomId", "user"."meetingId", "user"."userId"
--FROM v_user "user"
--JOIN "meeting" m using("meetingId")
--JOIN "v_meeting_breakoutPolicies" vmbp using("meetingId")
@ -1610,7 +1683,8 @@ where bu."breakoutRoomId" in (
select b."breakoutRoomId"
from "user" u
join "breakoutRoom" b on b."parentMeetingId" = u."meetingId" and b."endedAt" is null
where u."userId" = bu."userId"
where u."meetingId" = bu."meetingId"
and u."userId" = bu."userId"
);
------------------------------------
@ -1630,13 +1704,14 @@ create table "sharedNotes_rev" (
"meetingId" varchar(100) references "meeting"("meetingId") ON DELETE CASCADE,
"sharedNotesExtId" varchar(25),
"rev" integer,
"userId" varchar(50) references "user"("userId") ON DELETE SET NULL,
"userId" varchar(50),
"changeset" text,
"start" integer,
"end" integer,
"diff" TEXT,
"createdAt" timestamp with time zone,
constraint "pk_sharedNotes_rev" primary key ("meetingId", "sharedNotesExtId", "rev")
constraint "pk_sharedNotes_rev" primary key ("meetingId", "sharedNotesExtId", "rev"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE SET NULL
);
--create view "v_sharedNotes_rev" as select * from "sharedNotes_rev";
@ -1648,11 +1723,12 @@ where "diff" is not null;
create table "sharedNotes_session" (
"meetingId" varchar(100) references "meeting"("meetingId") ON DELETE CASCADE,
"sharedNotesExtId" varchar(25),
"userId" varchar(50) references "user"("userId") ON DELETE CASCADE,
"userId" varchar(50),
"sessionId" varchar(50),
constraint "pk_sharedNotes_session" primary key ("meetingId", "sharedNotesExtId", "userId")
constraint "pk_sharedNotes_session" primary key ("meetingId", "sharedNotesExtId", "userId"),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
create index "sharedNotes_session_userId" on "sharedNotes_session"("userId");
create index "sharedNotes_session_userId" on "sharedNotes_session"("meetingId", "userId");
create view "v_sharedNotes" as
SELECT sn.*, max(snr.rev) "lastRev"
@ -1673,32 +1749,62 @@ SELECT
FLOOR(EXTRACT(EPOCH FROM current_timestamp) * 1000)::bigint AS "currentTimeMillis";
------------------------------------
----audioCaption
----audioCaption or typedCaption
CREATE TABLE "caption_locale" (
"meetingId" varchar(100) NOT NULL REFERENCES "meeting"("meetingId") ON DELETE CASCADE,
"locale" varchar(15) NOT NULL,
"captionType" varchar(100) NOT NULL, --Audio Transcription or Typed Caption
"ownerUserId" varchar(50),
"createdAt" timestamp with time zone default current_timestamp,
"updatedAt" timestamp with time zone,
CONSTRAINT "caption_locale_pk" primary key ("meetingId","locale","captionType"),
FOREIGN KEY ("meetingId", "ownerUserId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
CREATE TABLE "caption" (
"captionId" varchar(100) NOT NULL PRIMARY KEY,
"meetingId" varchar(100) NOT NULL REFERENCES "meeting"("meetingId") ON DELETE CASCADE,
"captionType" varchar(100) NOT NULL, --Audio Transcription or Typed Caption
"userId" varchar(50) REFERENCES "user"("userId") ON DELETE CASCADE,
"lang" varchar(15),
"userId" varchar(50),
"locale" varchar(15),
"captionText" text,
"createdAt" timestamp with time zone
"createdAt" timestamp with time zone,
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
create index idx_caption on caption("meetingId","lang","createdAt");
create index idx_caption_captionType on caption("meetingId","lang","captionType","createdAt");
CREATE OR REPLACE FUNCTION "update_caption_locale_owner_func"() RETURNS TRIGGER AS $$
BEGIN
WITH upsert AS (
UPDATE "caption_locale" SET
"ownerUserId" = NEW."userId",
"updatedAt" = current_timestamp
WHERE "meetingId"=NEW."meetingId" AND "locale"=NEW."locale" AND "captionType"= NEW."captionType"
RETURNING *)
INSERT INTO "caption_locale"("meetingId","locale","captionType","ownerUserId")
SELECT NEW."meetingId", NEW."locale", NEW."captionType", NEW."userId"
WHERE NOT EXISTS (SELECT * FROM upsert);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "insert_caption_trigger" BEFORE INSERT ON "caption" FOR EACH ROW
EXECUTE FUNCTION "update_caption_locale_owner_func"();
create index idx_caption on caption("meetingId","locale","createdAt");
create index idx_caption_captionType on caption("meetingId","locale","captionType","createdAt");
CREATE OR REPLACE VIEW "v_caption" AS
SELECT *
FROM "caption"
WHERE "createdAt" > current_timestamp - INTERVAL '5 seconds';
CREATE OR REPLACE VIEW "v_caption_typed_activeLocales" AS
select distinct "meetingId", "lang", "userId"
from "caption"
where "captionType" = 'TYPED';
CREATE OR REPLACE VIEW "v_caption_activeLocales" AS
select distinct "meetingId", "locale", "ownerUserId", "captionType"
from "caption_locale";
create index "idx_caption_typed_activeLocales" on caption("meetingId","lang","userId") where "captionType" = 'TYPED';
create index "idx_caption_typed_activeLocales" on caption("meetingId","locale","userId") where "captionType" = 'TYPED';
------------------------------------
----
@ -1730,8 +1836,10 @@ CREATE TABLE "notification" (
"messageDescription" varchar(100),
"messageValues" jsonb,
"role" varchar(100), --MODERATOR, PRESENTER, VIEWER
"userId" varchar(50) references "user"("userId") ON DELETE CASCADE,
"createdAt" timestamp with time zone DEFAULT current_timestamp
"userMeetingId" varchar(100),
"userId" varchar(50),
"createdAt" timestamp with time zone DEFAULT current_timestamp,
FOREIGN KEY ("userMeetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
create or replace VIEW "v_notification" AS
@ -1763,27 +1871,29 @@ create index idx_notification on notification("meetingId","userId","role","creat
---Plugins Data Channel
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE "pluginDataChannelMessage" (
CREATE TABLE "pluginDataChannelEntry" (
"meetingId" varchar(100) references "meeting"("meetingId") ON DELETE CASCADE,
"pluginName" varchar(255),
"dataChannel" varchar(255),
"messageId" varchar(50) DEFAULT uuid_generate_v4(),
"channelName" varchar(255),
"entryId" varchar(50) DEFAULT uuid_generate_v4(),
"subChannelName" varchar(255),
"payloadJson" jsonb,
"fromUserId" varchar(50) REFERENCES "user"("userId") ON DELETE CASCADE,
"fromUserId" varchar(50),
"toRoles" varchar[], --MODERATOR, VIEWER, PRESENTER
"toUserIds" varchar[],
"createdAt" timestamp with time zone DEFAULT current_timestamp,
"deletedAt" timestamp with time zone,
CONSTRAINT "pluginDataChannel_pkey" PRIMARY KEY ("meetingId","pluginName","dataChannel","messageId")
CONSTRAINT "pluginDataChannel_pkey" PRIMARY KEY ("meetingId","pluginName","channelName","entryId", "subChannelName"),
FOREIGN KEY ("meetingId", "fromUserId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
);
create index "idx_pluginDataChannelMessage_dataChannel" on "pluginDataChannelMessage"("meetingId", "pluginName", "dataChannel", "toRoles", "toUserIds", "createdAt") where "deletedAt" is null;
create index "idx_pluginDataChannelMessage_roles" on "pluginDataChannelMessage"("meetingId", "toRoles", "toUserIds", "createdAt") where "deletedAt" is null;
create index "idx_pluginDataChannelEntry_channelName" on "pluginDataChannelEntry"("meetingId", "pluginName", "channelName", "toRoles", "toUserIds", "subChannelName", "createdAt") where "deletedAt" is null;
create index "idx_pluginDataChannelEntry_roles" on "pluginDataChannelEntry"("meetingId", "toRoles", "toUserIds", "createdAt") where "deletedAt" is null;
CREATE OR REPLACE VIEW "v_pluginDataChannelMessage" AS
SELECT u."meetingId", u."userId", m."pluginName", m."dataChannel", m."messageId", m."payloadJson", m."fromUserId", m."toRoles", m."createdAt"
CREATE OR REPLACE VIEW "v_pluginDataChannelEntry" AS
SELECT u."meetingId", u."userId", m."pluginName", m."channelName", m."subChannelName", m."entryId", m."payloadJson", m."fromUserId", m."toRoles", m."createdAt"
FROM "user" u
JOIN "pluginDataChannelMessage" m ON m."meetingId" = u."meetingId"
JOIN "pluginDataChannelEntry" m ON m."meetingId" = u."meetingId"
AND ((m."toRoles" IS NULL AND m."toUserIds" IS NULL)
OR u."userId" = ANY(m."toUserIds")
OR u."role" = ANY(m."toRoles")

View File

@ -201,17 +201,19 @@ type Mutation {
}
type Mutation {
pluginDataChannelDeleteMessage(
pluginDataChannelDeleteEntry(
pluginName: String!
dataChannel: String!
messageId: String!
channelName: String!
subChannelName: String!
entryId: String!
): Boolean
}
type Mutation {
pluginDataChannelDispatchMessage(
pluginDataChannelPushEntry(
pluginName: String!
dataChannel: String!
subChannelName: String!
channelName: String!
payloadJson: String!
toRoles: [String]!
toUserIds: [String]!
@ -221,7 +223,8 @@ type Mutation {
type Mutation {
pluginDataChannelReset(
pluginName: String!
dataChannel: String!
channelName: String!
subChannelName: String!
): Boolean
}
@ -559,3 +562,11 @@ input GuestUserApprovalStatus {
status: String!
}
type Mutation {
captionSetOwner(
locale: String!
ownerUserId: String!
): Boolean
}

View File

@ -179,13 +179,13 @@ actions:
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
permissions:
- role: bbb_client
- name: pluginDataChannelDeleteMessage
- name: pluginDataChannelDeleteEntry
definition:
kind: synchronous
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
permissions:
- role: bbb_client
- name: pluginDataChannelDispatchMessage
- name: pluginDataChannelPushEntry
definition:
kind: synchronous
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
@ -493,6 +493,12 @@ actions:
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
permissions:
- role: bbb_client
- name: captionSetOwner
definition:
kind: synchronous
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
permissions:
- role: bbb_client
custom_types:
enums: []
input_objects:

View File

@ -51,6 +51,9 @@ select_permissions:
- showInvitation
- startedAt
filter:
userId:
_eq: X-Hasura-UserId
_and:
- userMeetingId:
_eq: X-Hasura-MeetingId
- userId:
_eq: X-Hasura-UserId
allow_aggregations: true

View File

@ -12,6 +12,7 @@ object_relationships:
manual_configuration:
column_mapping:
userId: userId
userMeetingId: meetingId
insertion_order: null
remote_table:
name: v_user_ref
@ -25,8 +26,11 @@ select_permissions:
_or:
- parentMeetingId:
_eq: X-Hasura-ModeratorInMeeting
- userId:
_eq: X-Hasura-UserId
- _and:
- userMeetingId:
_eq: X-Hasura-MeetingId
- userId:
_eq: X-Hasura-UserId
allow_aggregations: true
query_root_fields: []
subscription_root_fields: []

View File

@ -12,6 +12,7 @@ object_relationships:
manual_configuration:
column_mapping:
userId: userId
userMeetingId: meetingId
insertion_order: null
remote_table:
name: v_user_ref
@ -20,6 +21,7 @@ select_permissions:
- role: bbb_client
permission:
columns:
- isAudioOnly
- userId
filter:
parentMeetingId:

View File

@ -16,8 +16,11 @@ select_permissions:
- joinURL
- joinedAt
filter:
userId:
_eq: X-Hasura-UserId
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- userId:
_eq: X-Hasura-UserId
comment: ""
update_permissions:
- role: bbb_client
@ -25,7 +28,10 @@ update_permissions:
columns:
- inviteDismissedAt
filter:
userId:
_eq: X-Hasura-UserId
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- userId:
_eq: X-Hasura-UserId
check: null
comment: ""

View File

@ -11,6 +11,7 @@ object_relationships:
using:
manual_configuration:
column_mapping:
meetingId: meetingId
userId: userId
insertion_order: null
remote_table:
@ -25,7 +26,7 @@ select_permissions:
- captionId
- userId
- captionType
- lang
- locale
filter:
meetingId:
_eq: X-Hasura-MeetingId

View File

@ -1,17 +1,18 @@
table:
name: v_caption_typed_activeLocales
name: v_caption_activeLocales
schema: public
configuration:
column_config: {}
custom_column_names: {}
custom_name: caption_typed_activeLocales
custom_name: caption_activeLocales
custom_root_fields: {}
object_relationships:
- name: userOwner
using:
manual_configuration:
column_mapping:
userId: userId
meetingId: meetingId
ownerUserId: userId
insertion_order: null
remote_table:
name: v_user_ref
@ -20,7 +21,8 @@ select_permissions:
- role: bbb_client
permission:
columns:
- lang
- locale
- captionType
filter:
meetingId:
_eq: X-Hasura-MeetingId

View File

@ -11,6 +11,7 @@ object_relationships:
using:
manual_configuration:
column_mapping:
meetingId: meetingId
participantId: userId
insertion_order: null
remote_table:

View File

@ -11,6 +11,7 @@ object_relationships:
using:
manual_configuration:
column_mapping:
meetingId: meetingId
senderId: userId
insertion_order: null
remote_table:

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