Initial implementation of Postgres data and Hasura

This commit is contained in:
Gustavo Trott 2023-03-08 12:23:45 -03:00
parent aa517df377
commit 42711ac5ae
57 changed files with 1002 additions and 47 deletions

View File

@ -28,6 +28,10 @@ object Dependencies {
// BigBlueButton // BigBlueButton
val bbbCommons = "0.0.22-SNAPSHOT" val bbbCommons = "0.0.22-SNAPSHOT"
// Database
val slick = "3.4.1"
val postgresql = "42.5.0"
// Test // Test
val scalaTest = "3.2.11" val scalaTest = "3.2.11"
val mockito = "2.23.0" val mockito = "2.23.0"
@ -55,6 +59,10 @@ object Dependencies {
val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang
val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.13" % Versions.bbbCommons val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.13" % Versions.bbbCommons
val slick = "com.typesafe.slick" %% "slick" % Versions.slick
val slickHikaricp = "com.typesafe.slick" %% "slick-hikaricp" % Versions.slick
val postgresql = "org.postgresql" % "postgresql" % Versions.postgresql
} }
object Test { object Test {
@ -87,5 +95,8 @@ object Dependencies {
Compile.apacheLang, Compile.apacheLang,
Compile.akkaHttp, Compile.akkaHttp,
Compile.akkaHttpSprayJson, Compile.akkaHttpSprayJson,
Compile.bbbCommons) ++ testing Compile.bbbCommons,
Compile.slick,
Compile.slickHikaricp,
Compile.postgresql) ++ testing
} }

View File

@ -0,0 +1,42 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
trait ChangeUserMobileFlagReqMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleChangeUserMobileFlagReqMsg(msg: ChangeUserMobileFlagReqMsg): Unit = {
log.info("handleChangeUserMobileFlagReqMsg: mobile={} userId={}", msg.body.mobile, msg.body.userId)
def broadcastUserMobileChanged(user: UserState, mobile: Boolean): Unit = {
val routingChange = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, user.intId
)
val envelopeChange = BbbCoreEnvelope(UserMobileFlagChangedEvtMsg.NAME, routingChange)
val headerChange = BbbClientMsgHeader(UserMobileFlagChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId,
user.intId)
val bodyChange = UserMobileFlagChangedEvtMsgBody(user.intId, mobile)
val eventChange = UserMobileFlagChangedEvtMsg(headerChange, bodyChange)
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
outGW.send(msgEventChange)
}
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
} yield {
if (user.mobile != msg.body.mobile) {
val userMobile = Users2x.setMobile(liveMeeting.users2x, user)
broadcastUserMobileChanged(userMobile, msg.body.mobile)
}
}
}
}

View File

@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models._ import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender } import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
import scala.util.Random
trait RegisterUserReqMsgHdlr { trait RegisterUserReqMsgHdlr {
this: UsersApp => this: UsersApp =>
@ -54,13 +55,16 @@ trait RegisterUserReqMsgHdlr {
val guestStatus = msg.body.guestStatus val guestStatus = msg.body.guestStatus
val colorOptions = List("#7b1fa2", "#6a1b9a", "#4a148c", "#5e35b1", "#512da8", "#4527a0", "#311b92",
"#3949ab", "#303f9f", "#283593", "#1a237e", "#1976d2", "#1565c0", "#0d47a1", "#0277bd", "#01579b")
val userColor = colorOptions(Random.nextInt(colorOptions.length))
val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId, val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId,
msg.body.name, msg.body.role, msg.body.authToken, msg.body.name, msg.body.role, msg.body.authToken,
msg.body.avatarURL, msg.body.guest, msg.body.authed, guestStatus, msg.body.excludeFromDashboard, false) msg.body.avatarURL, userColor, msg.body.guest, msg.body.authed, guestStatus, msg.body.excludeFromDashboard, false)
checkUserConcurrentAccesses(regUser) checkUserConcurrentAccesses(regUser)
RegisteredUsers.add(liveMeeting.registeredUsers, regUser, liveMeeting.props.meetingProp.intId)
RegisteredUsers.add(liveMeeting.registeredUsers, regUser)
log.info("Register user success. meetingId=" + liveMeeting.props.meetingProp.intId log.info("Register user success. meetingId=" + liveMeeting.props.meetingProp.intId
+ " userId=" + msg.body.extUserId + " user=" + regUser) + " userId=" + msg.body.extUserId + " user=" + regUser)

View File

@ -158,6 +158,7 @@ class UsersApp(
with SelectRandomViewerReqMsgHdlr with SelectRandomViewerReqMsgHdlr
with AssignPresenterReqMsgHdlr with AssignPresenterReqMsgHdlr
with ChangeUserPinStateReqMsgHdlr with ChangeUserPinStateReqMsgHdlr
with ChangeUserMobileFlagReqMsgHdlr
with EjectUserFromMeetingCmdMsgHdlr with EjectUserFromMeetingCmdMsgHdlr
with EjectUserFromMeetingSysMsgHdlr with EjectUserFromMeetingSysMsgHdlr
with MuteUserCmdMsgHdlr { with MuteUserCmdMsgHdlr {

View File

@ -8,6 +8,8 @@ import org.bigbluebutton.core.models._
import org.bigbluebutton.core.apps.users.UsersApp import org.bigbluebutton.core.apps.users.UsersApp
import org.bigbluebutton.core2.MeetingStatus2x import org.bigbluebutton.core2.MeetingStatus2x
import scala.util.Random
trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration { trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
this: MeetingActor => this: MeetingActor =>
@ -19,6 +21,10 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
val guestPolicy = GuestsWaiting.getGuestPolicy(liveMeeting.guestsWaiting) val guestPolicy = GuestsWaiting.getGuestPolicy(liveMeeting.guestsWaiting)
val isDialInUser = msg.body.intId.startsWith(IntIdPrefixType.DIAL_IN) val isDialInUser = msg.body.intId.startsWith(IntIdPrefixType.DIAL_IN)
val colorOptions = List("#7b1fa2", "#6a1b9a", "#4a148c", "#5e35b1", "#512da8", "#4527a0", "#311b92",
"#3949ab", "#303f9f", "#283593", "#1a237e", "#1976d2", "#1565c0", "#0d47a1", "#0277bd", "#01579b")
val userColor = colorOptions(Random.nextInt(colorOptions.length))
def notifyModeratorsOfGuestWaiting(guest: GuestWaiting, users: Users2x, meetingId: String): Unit = { def notifyModeratorsOfGuestWaiting(guest: GuestWaiting, users: Users2x, meetingId: String): Unit = {
val moderators = Users2x.findAll(users).filter(p => p.role == Roles.MODERATOR_ROLE) val moderators = Users2x.findAll(users).filter(p => p.role == Roles.MODERATOR_ROLE)
moderators foreach { mod => moderators foreach { mod =>
@ -32,9 +38,9 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
def registerUserInRegisteredUsers() = { def registerUserInRegisteredUsers() = {
val regUser = RegisteredUsers.create(msg.body.intId, msg.body.voiceUserId, val regUser = RegisteredUsers.create(msg.body.intId, msg.body.voiceUserId,
msg.body.callerIdName, Roles.VIEWER_ROLE, "", msg.body.callerIdName, Roles.VIEWER_ROLE, "", "", userColor,
"", true, true, GuestStatus.WAIT, true, false) true, true, GuestStatus.WAIT, true, false)
RegisteredUsers.add(liveMeeting.registeredUsers, regUser) RegisteredUsers.add(liveMeeting.registeredUsers, regUser, liveMeeting.props.meetingProp.intId)
} }
def registerUserInUsers2x() = { def registerUserInUsers2x() = {
@ -48,9 +54,11 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
guestStatus = GuestStatus.WAIT, guestStatus = GuestStatus.WAIT,
emoji = "none", emoji = "none",
pin = false, pin = false,
mobile = false,
presenter = false, presenter = false,
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin, locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
avatar = "", avatar = "",
color = userColor,
clientType = "", clientType = "",
pickExempted = false, pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0) userLeftFlag = UserLeftFlag(false, 0)

View File

@ -0,0 +1,9 @@
package org.bigbluebutton.core.db
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
object DatabaseConnection {
val db = Database.forConfig("postgres")
// implicit val session: Session = db.createSession()
}

View File

@ -0,0 +1,52 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.models.{RegisteredUser, UserState, VoiceUserState, WebcamStream}
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success, Try}
case class UserCameraDbModel(
streamId: String,
userId: String,
)
class UserCameraDbTableDef(tag: Tag) extends Table[UserCameraDbModel](tag, None, "user_camera") {
override def * = (
streamId, userId) <> (UserCameraDbModel.tupled, UserCameraDbModel.unapply)
val streamId = column[String]("streamId", O.PrimaryKey)
val userId = column[String]("userId")
}
object UserCameraDAO {
// val usersTable = TableQuery[UserTableDef]
def insert(webcam: WebcamStream) = {
DatabaseConnection.db.run(
TableQuery[UserCameraDbTableDef].forceInsert(
UserCameraDbModel(
streamId = webcam.streamId,
userId = webcam.userId
)
)
).onComplete {
case Success(rowsAffected) => {
println(s"$rowsAffected row(s) inserted!")
}
case Failure(e) => println(s"Error inserting webcam: $e")
}
}
def delete(streamId: String) = {
DatabaseConnection.db.run(
TableQuery[UserCameraDbTableDef]
.filter(_.streamId === streamId)
.delete
).onComplete {
case Success(rowsAffected) => println(s"Webcam ${streamId} deleted")
case Failure(e) => println(s"Error deleting webcam: $e")
}
}
}

View File

@ -0,0 +1,211 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.models.{RegisteredUser, UserState, VoiceUserState}
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success, Try}
case class UserDbModel(
userId: String,
extId: String,
meetingId: String,
name: String,
avatar: String = "",
color: String = "",
emoji: String = "",
guest: Boolean,
guestStatus: String = "",
mobile: Boolean,
// excludeFromDashboard: Boolean,
role: String,
authed: Boolean = false,
joined: Boolean = false,
leftFlag: Boolean = false,
ejected: Boolean = false,
ejectReason: String = "",
banned: Boolean = false,
loggedOut: Boolean = false,
registeredOn: Long,
presenter: Boolean = false,
pinned: Boolean = false,
locked: Boolean = false,
// registeredOn: Option[Long]
// registeredOn: Option[LocalDate]
)
class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
override def * = (
userId, extId, meetingId, name, avatar, color, emoji, guest, guestStatus, mobile, role, authed, joined,
leftFlag, ejected, ejectReason, banned, loggedOut, registeredOn, presenter, pinned, locked) <> (UserDbModel.tupled, UserDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey)
val extId = column[String]("extId")
val meetingId = column[String]("meetingId")
val name = column[String]("name")
val avatar = column[String]("avatar")
val color = column[String]("color")
val emoji = column[String]("emoji")
val guest = column[Boolean]("guest")
val guestStatus = column[String]("guestStatus")
val mobile = column[Boolean]("mobile")
// val excludeFromDashboard = column[Boolean]("excludeFromDashboard")
val role = column[String]("role")
val authed = column[Boolean]("authed")
val joined = column[Boolean]("joined")
val leftFlag = column[Boolean]("leftFlag")
val ejected = column[Boolean]("ejected")
val ejectReason = column[String]("ejectReason")
val banned = column[Boolean]("banned")
val loggedOut = column[Boolean]("loggedOut")
val registeredOn = column[Long]("registeredOn")
val presenter = column[Boolean]("presenter")
val pinned = column[Boolean]("pinned")
val locked = column[Boolean]("locked")
// val registeredOn: Rep[Option[Long]] = column[Option[Long]]("registeredOn")
// val registeredOn: Rep[Option[LocalDate]] = column[Option[LocalDate]]("registeredOn")
}
object UserDAO {
// val usersTable = TableQuery[UserTableDef]
def insert(meetingId: String, regUser: RegisteredUser) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef].forceInsert(
UserDbModel(
userId = regUser.id,
extId = regUser.externId,
meetingId = meetingId,
name = regUser.name,
avatar = regUser.avatarURL,
color = regUser.color,
guest = regUser.guest,
guestStatus = regUser.guestStatus,
mobile = false,
// excludeFromDashboard = regUser.excludeFromDashboard,
role = regUser.role,
authed = regUser.authed,
registeredOn = regUser.registeredOn
)
)
).onComplete {
case Success(rowsAffected) => {
println(s"$rowsAffected row(s) inserted!")
}
case Failure(e) => println(s"Error inserting user: $e")
}
}
def update(regUser: RegisteredUser) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.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))
).onComplete {
case Success(rowsAffected) => println(s"$rowsAffected row(s) updated")
case Failure(e) => println(s"Error updating user: $e")
}
}
def update(userState: UserState) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === userState.intId)
.map(u => (u.presenter, u.pinned, u.locked, u.emoji, u.mobile, u.leftFlag))
.update((userState.presenter, userState.pin, userState.locked, userState.emoji, userState.mobile, userState.userLeftFlag.left))
// "guest" bool NULL
// "guestStatus" varchar (255)
// "role" varchar (255) NULL
// "ejected" bool null
// "eject_reason" varchar (255)
// "talking" bool NULL
// "emoji" varchar
// ,
).onComplete {
case Success(rowsAffected) => println(s"$rowsAffected row(s) updated")
case Failure(e) => println(s"Error updating user: $e")
}
}
// def deleteVoiceUser(userIntId: String) = {
//
//
// val a : String = ""
// val b : Boolean = null
//
// DatabaseConnection.db.run(
// TableQuery[UserDbTableDef]
// .filter(_.intId === userIntId)
// .map(u => (u.voiceUserId, u.talking, u.muted, u.listenOnly))
// .update((a, b, b, b))
// ).onComplete {
// case Success(rowsAffected) => println(s"$rowsAffected row(s) updated")
// case Failure(e) => println(s"Error updating user: $e")
// }
// }
//
// //TODO talking
// def updateTalking(userIntId: String, talking: Boolean) = {
// DatabaseConnection.db.run(
// TableQuery[UserDbTableDef]
// .filter(_.intId === userIntId)
// .map(u => (u.talking))
// .update((talking))
// ).onComplete {
// case Success(rowsAffected) => println(s"$rowsAffected row(s) updated")
// case Failure(e) => println(s"Error updating user: $e")
// }
// }
def delete(regUser: RegisteredUser) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === regUser.id)
.delete
).onComplete {
case Success(rowsAffected) => println(s"User ${regUser.id} deleted")
case Failure(e) => println(s"Error deleting user: $e")
}
}
def insert(user: UserDbModel) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef].forceInsert(user)
).onComplete {
case Success(rowsAffected) => println(s"$rowsAffected row(s) updated")
case Failure(e) => println(s"Error updating user: $e")
}
}
def updateUserJoined(intId: String, joined: Boolean) = {
// TableQuery[UserDbTableDef].update()
// val query = TableQuery[UserDbTableDef].filter(_.intId === user.intId).map(_.joined).update(true)
// DatabaseConnection.db.run(query).onComplete {
// case Success(rowsAffected) => println(s"$rowsAffected row(s) updated")
// case Failure(e) => println(s"Error updating user: $e")
// }
// val updateOperation: DBIO[Int] = all
// .filter(_.registrationHash === hash)
// .map(u => (u.field1, u.field2, u.field3))
// .update((newValue1, newValue2, newValue3))
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === intId)
.map(_.joined)
.update(joined)
).map { updatedRows =>
{
if (updatedRows >= 0) {
println(s"$updatedRows row(s) updated")
} else {
println(s"$updatedRows row(s) updated")
}
}
}
}
}

View File

@ -0,0 +1,167 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.models.{RegisteredUser, UserState, VoiceUserState, WebcamStream}
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success, Try}
case class UserMicrophoneDbModel(
voiceUserId: String,
userId: String,
// meetingId: String,
callerName: String,
callerNum: String,
callingWith: String,
joined: Boolean,
listenOnly: Boolean,
muted: Boolean,
spoke: Boolean,
talking: Boolean,
floor: Boolean,
lastFloorTime: String,
voiceConf: String,
color: String,
startTime: Option[Long],
endTime: Option[Long],
)
class UserMicrophoneDbTableDef(tag: Tag) extends Table[UserMicrophoneDbModel](tag, None, "user_microphone") {
override def * = (
voiceUserId, userId, callerName, callerNum, callingWith, joined, listenOnly,
muted, spoke, talking, floor, lastFloorTime, voiceConf, color, startTime, endTime
) <> (UserMicrophoneDbModel.tupled, UserMicrophoneDbModel.unapply)
val voiceUserId = column[String]("voiceUserId", O.PrimaryKey)
val userId = column[String]("userId")
val callerName = column[String]("callerName")
val callerNum = column[String]("callerNum")
val callingWith = column[String]("callingWith")
val joined = column[Boolean]("joined")
val listenOnly = column[Boolean]("listenOnly")
val muted = column[Boolean]("muted")
val spoke = column[Boolean]("spoke")
val talking = column[Boolean]("talking")
val floor = column[Boolean]("floor")
val lastFloorTime = column[String]("lastFloorTime")
val voiceConf = column[String]("voiceConf")
val color = column[String]("color")
val startTime = column[Option[Long]]("startTime")
val endTime = column[Option[Long]]("endTime")
}
object UserMicrophoneDAO {
// val usersTable = TableQuery[UserTableDef]
def insert(voiceUserState: VoiceUserState) = {
DatabaseConnection.db.run(
TableQuery[UserMicrophoneDbTableDef].forceInsert(
UserMicrophoneDbModel(
voiceUserId = voiceUserState.voiceUserId,
userId = voiceUserState.intId,
callerName = voiceUserState.callerName,
callerNum = voiceUserState.callerNum,
callingWith = voiceUserState.callingWith,
joined = true,
listenOnly = voiceUserState.listenOnly,
muted = voiceUserState.muted,
spoke = false,
talking = voiceUserState.talking,
floor = voiceUserState.floor,
lastFloorTime = voiceUserState.lastFloorTime,
voiceConf = "",
color = "",
startTime = None,
endTime = None
)
)
).onComplete {
case Success(rowsAffected) => {
println(s"$rowsAffected row(s) inserted!")
}
case Failure(e) => println(s"Error inserting voice: $e")
}
}
def update(voiceUserState: VoiceUserState) = {
DatabaseConnection.db.run(
TableQuery[UserMicrophoneDbTableDef]
.filter(_.voiceUserId === voiceUserState.voiceUserId)
.map(u => (u.listenOnly, u.muted, u.floor, u.lastFloorTime))
.update((voiceUserState.listenOnly, voiceUserState.muted, voiceUserState.floor, voiceUserState.lastFloorTime))
).onComplete {
case Success(rowsAffected) => println(s"$rowsAffected row(s) updated")
case Failure(e) => println(s"Error updating user: $e")
}
}
// updateFloor
def updateTalking(voiceUserState: VoiceUserState) = {
// DatabaseConnection.db.run(sql"SELECT * FROM $users".as[(Int, Double, String)]).onComplete {
val now = System.currentTimeMillis()
val updateSql = if(voiceUserState.talking) {
sqlu"""UPDATE user_microphone SET
"talking" = true,
"spoke" = true,
"endTime" = null,
"startTime" = (case when "talking" is false then $now else "startTime" end)
WHERE "voiceUserId" = ${voiceUserState.voiceUserId}"""
} else {
sqlu"""UPDATE user_microphone SET
"talking" = false,
"startTime" = null,
"endTime" = (case when "talking" is true then $now else "endTime" end)
WHERE "voiceUserId" = ${voiceUserState.voiceUserId}"""
}
DatabaseConnection.db.run(updateSql).onComplete {
case Success(rowsAffected) => println(s"$rowsAffected row(s) updated with talking: ${voiceUserState.talking}")
case Failure(e) => println(s"Error updating voice talking: $e")
}
}
def updateFloor(voiceUserState: VoiceUserState) = {
DatabaseConnection.db.run(
TableQuery[UserMicrophoneDbTableDef]
.filter(_.voiceUserId === voiceUserState.voiceUserId)
.map(u => (u.listenOnly, u.muted))
.update((voiceUserState.listenOnly, voiceUserState.muted))
).onComplete {
case Success(rowsAffected) => println(s"$rowsAffected row(s) updated")
case Failure(e) => println(s"Error updating user: $e")
}
}
// updateTalking
//talking
//spoke
//startTime
//endTime
def delete(voiceUserId: String) = {
DatabaseConnection.db.run(
TableQuery[UserMicrophoneDbTableDef]
.filter(_.voiceUserId === voiceUserId)
.delete
).onComplete {
case Success(rowsAffected) => println(s"Voice ${voiceUserId} deleted")
case Failure(e) => println(s"Error deleting voice: $e")
}
}
def deleteUser(userId: String) = {
DatabaseConnection.db.run(
TableQuery[UserMicrophoneDbTableDef]
.filter(_.userId === userId)
.delete
).onComplete {
case Success(rowsAffected) => println(s"Voice of user ${userId} deleted")
case Failure(e) => println(s"Error deleting voice: $e")
}
}
}

View File

@ -1,11 +1,12 @@
package org.bigbluebutton.core.models package org.bigbluebutton.core.models
import com.softwaremill.quicklens._ import com.softwaremill.quicklens._
import org.bigbluebutton.core.db.{UserDAO, UserDbModel}
import org.bigbluebutton.core.domain.BreakoutRoom2x import org.bigbluebutton.core.domain.BreakoutRoom2x
object RegisteredUsers { object RegisteredUsers {
def create(userId: String, extId: String, name: String, roles: String, def create(userId: String, extId: String, name: String, roles: String,
token: String, avatar: String, guest: Boolean, authenticated: Boolean, token: String, avatar: String, color: String, guest: Boolean, authenticated: Boolean,
guestStatus: String, excludeFromDashboard: Boolean, loggedOut: Boolean): RegisteredUser = { guestStatus: String, excludeFromDashboard: Boolean, loggedOut: Boolean): RegisteredUser = {
new RegisteredUser( new RegisteredUser(
userId, userId,
@ -14,6 +15,7 @@ object RegisteredUsers {
roles, roles,
token, token,
avatar, avatar,
color,
guest, guest,
authenticated, authenticated,
guestStatus, guestStatus,
@ -76,7 +78,7 @@ object RegisteredUsers {
regUsers.toVector.size regUsers.toVector.size
} }
def add(users: RegisteredUsers, user: RegisteredUser): Vector[RegisteredUser] = { def add(users: RegisteredUsers, user: RegisteredUser, meetingId: String): Vector[RegisteredUser] = {
findWithExternUserId(user.externId, users) match { findWithExternUserId(user.externId, users) match {
case Some(u) => case Some(u) =>
@ -85,18 +87,20 @@ object RegisteredUsers {
// will fail and can't join. // will fail and can't join.
// ralam april 21, 2020 // ralam april 21, 2020
val bannedUser = user.copy(banned = true) val bannedUser = user.copy(banned = true)
UserDAO.insert(meetingId, bannedUser)
users.save(bannedUser) users.save(bannedUser)
} else { } else {
// If user hasn't been ejected, we allow user to join // If user hasn't been ejected, we allow user to join
// as the user might be joining using 2 browsers for // as the user might be joining using 2 browsers for
// better management of meeting. // better management of meeting.
// ralam april 21, 2020 // ralam april 21, 2020
UserDAO.insert(meetingId, user)
users.save(user) users.save(user)
} }
case None => case None =>
UserDAO.insert(meetingId, user)
users.save(user) users.save(user)
} }
} }
private def banOrEjectUser(ejectedUser: RegisteredUser, users: RegisteredUsers, ban: Boolean): RegisteredUser = { private def banOrEjectUser(ejectedUser: RegisteredUser, users: RegisteredUsers, ban: Boolean): RegisteredUser = {
@ -110,9 +114,11 @@ object RegisteredUsers {
// ralam april 21, 2020 // ralam april 21, 2020
val u = ejectedUser.modify(_.banned).setTo(true) val u = ejectedUser.modify(_.banned).setTo(true)
users.save(u) users.save(u)
UserDAO.update(u)
u u
} else { } else {
users.delete(ejectedUser.id) users.delete(ejectedUser.id)
UserDAO.delete(ejectedUser)
ejectedUser ejectedUser
} }
} }
@ -128,6 +134,7 @@ object RegisteredUsers {
guestStatus: String): RegisteredUser = { guestStatus: String): RegisteredUser = {
val u = user.modify(_.guestStatus).setTo(guestStatus) val u = user.modify(_.guestStatus).setTo(guestStatus)
users.save(u) users.save(u)
UserDAO.update(u)
u u
} }
@ -135,6 +142,7 @@ object RegisteredUsers {
role: String): RegisteredUser = { role: String): RegisteredUser = {
val u = user.modify(_.role).setTo(role) val u = user.modify(_.role).setTo(role)
users.save(u) users.save(u)
UserDAO.update(u)
u u
} }
@ -148,6 +156,7 @@ object RegisteredUsers {
def updateUserJoin(users: RegisteredUsers, user: RegisteredUser): RegisteredUser = { def updateUserJoin(users: RegisteredUsers, user: RegisteredUser): RegisteredUser = {
val u = user.copy(joined = true) val u = user.copy(joined = true)
users.save(u) users.save(u)
UserDAO.update(u)
u u
} }
@ -160,6 +169,7 @@ object RegisteredUsers {
def setUserLoggedOutFlag(users: RegisteredUsers, user: RegisteredUser): RegisteredUser = { def setUserLoggedOutFlag(users: RegisteredUsers, user: RegisteredUser): RegisteredUser = {
val u = user.copy(loggedOut = true) val u = user.copy(loggedOut = true)
users.save(u) users.save(u)
UserDAO.update(u)
u u
} }
@ -191,6 +201,7 @@ case class RegisteredUser(
role: String, role: String,
authToken: String, authToken: String,
avatarURL: String, avatarURL: String,
color: String,
guest: Boolean, guest: Boolean,
authed: Boolean, authed: Boolean,
guestStatus: String, guestStatus: String,

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.core.models package org.bigbluebutton.core.models
import com.softwaremill.quicklens._ import com.softwaremill.quicklens._
import org.bigbluebutton.core.db.UserDAO
import org.bigbluebutton.core.util.TimeUtil import org.bigbluebutton.core.util.TimeUtil
import org.bigbluebutton.core2.message.senders.MsgBuilder import org.bigbluebutton.core2.message.senders.MsgBuilder
@ -34,6 +35,7 @@ object Users2x {
} yield { } yield {
val newUser = u.copy(userLeftFlag = UserLeftFlag(true, System.currentTimeMillis())) val newUser = u.copy(userLeftFlag = UserLeftFlag(true, System.currentTimeMillis()))
users.save(newUser) users.save(newUser)
UserDAO.update(newUser)
newUser newUser
} }
} }
@ -107,6 +109,13 @@ object Users2x {
newUserState newUserState
} }
def setMobile(users: Users2x, u: UserState): UserState = {
val newUserState = modify(u)(_.mobile).setTo(true)
users.save(newUserState)
UserDAO.update((newUserState))
newUserState
}
def ejectFromMeeting(users: Users2x, intId: String): Option[UserState] = { def ejectFromMeeting(users: Users2x, intId: String): Option[UserState] = {
for { for {
_ <- users.remove(intId) _ <- users.remove(intId)
@ -122,6 +131,7 @@ object Users2x {
} yield { } yield {
val newUser = u.modify(_.presenter).setTo(true) val newUser = u.modify(_.presenter).setTo(true)
users.save(newUser) users.save(newUser)
UserDAO.update(newUser)
newUser newUser
} }
} }
@ -132,6 +142,7 @@ object Users2x {
} yield { } yield {
val newUser = u.modify(_.presenter).setTo(false) val newUser = u.modify(_.presenter).setTo(false)
users.save(newUser) users.save(newUser)
UserDAO.update(newUser)
newUser newUser
} }
} }
@ -163,6 +174,7 @@ object Users2x {
} yield { } yield {
val newUser = u.modify(_.pin).setTo(pin) val newUser = u.modify(_.pin).setTo(pin)
users.save(newUser) users.save(newUser)
UserDAO.update(newUser)
newUser newUser
} }
} }
@ -173,6 +185,7 @@ object Users2x {
} yield { } yield {
val newUser = u.modify(_.emoji).setTo(emoji) val newUser = u.modify(_.emoji).setTo(emoji)
users.save(newUser) users.save(newUser)
UserDAO.update(newUser)
newUser newUser
} }
} }
@ -183,6 +196,7 @@ object Users2x {
} yield { } yield {
val newUser = u.modify(_.locked).setTo(locked) val newUser = u.modify(_.locked).setTo(locked)
users.save(newUser) users.save(newUser)
UserDAO.update(newUser)
newUser newUser
} }
} }
@ -354,12 +368,14 @@ case class UserState(
role: String, role: String,
guest: Boolean, guest: Boolean,
pin: Boolean, pin: Boolean,
mobile: Boolean,
authed: Boolean, authed: Boolean,
guestStatus: String, guestStatus: String,
emoji: String, emoji: String,
locked: Boolean, locked: Boolean,
presenter: Boolean, presenter: Boolean,
avatar: String, avatar: String,
color: String,
roleChangedOn: Long = System.currentTimeMillis(), roleChangedOn: Long = System.currentTimeMillis(),
lastActivityTime: Long = System.currentTimeMillis(), lastActivityTime: Long = System.currentTimeMillis(),
lastInactivityInspect: Long = 0, lastInactivityInspect: Long = 0,

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.core.models package org.bigbluebutton.core.models
import com.softwaremill.quicklens._ import com.softwaremill.quicklens._
import org.bigbluebutton.core.db.{ UserDAO, UserMicrophoneDAO }
object VoiceUsers { object VoiceUsers {
def findWithVoiceUserId(users: VoiceUsers, voiceUserId: String): Option[VoiceUserState] = { def findWithVoiceUserId(users: VoiceUsers, voiceUserId: String): Option[VoiceUserState] = {
@ -30,9 +31,11 @@ object VoiceUsers {
def add(users: VoiceUsers, user: VoiceUserState): Unit = { def add(users: VoiceUsers, user: VoiceUserState): Unit = {
users.save(user) users.save(user)
UserMicrophoneDAO.insert(user)
} }
def removeWithIntId(users: VoiceUsers, intId: String): Option[VoiceUserState] = { def removeWithIntId(users: VoiceUsers, intId: String): Option[VoiceUserState] = {
UserMicrophoneDAO.deleteUser(intId)
users.remove(intId) users.remove(intId)
} }
@ -52,6 +55,7 @@ object VoiceUsers {
.modify(_.talking).setTo(false) .modify(_.talking).setTo(false)
.modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis()) .modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis())
users.save(vu) users.save(vu)
UserMicrophoneDAO.update(vu)
vu vu
} }
} }
@ -64,6 +68,8 @@ object VoiceUsers {
.modify(_.talking).setTo(talking) .modify(_.talking).setTo(talking)
.modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis()) .modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis())
users.save(vu) users.save(vu)
UserMicrophoneDAO.updateTalking(vu)
vu vu
} }
} }
@ -76,6 +82,7 @@ object VoiceUsers {
.modify(_.lastFloorTime).setTo(timestamp) .modify(_.lastFloorTime).setTo(timestamp)
.modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis()) .modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis())
users.save(vu) users.save(vu)
UserMicrophoneDAO.update(vu)
vu vu
} }
} }
@ -87,6 +94,7 @@ object VoiceUsers {
val vu = u.modify(_.floor).setTo(floor) val vu = u.modify(_.floor).setTo(floor)
.modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis()) .modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis())
users.save(vu) users.save(vu)
UserMicrophoneDAO.update(vu)
vu vu
} }
} }

View File

@ -1,5 +1,7 @@
package org.bigbluebutton.core.models package org.bigbluebutton.core.models
import org.bigbluebutton.core.db.UserCameraDAO
import collection.immutable.HashMap import collection.immutable.HashMap
object Webcams { object Webcams {
@ -15,14 +17,20 @@ object Webcams {
def addWebcamStream(webcams: Webcams, webcam: WebcamStream): Option[WebcamStream] = { def addWebcamStream(webcams: Webcams, webcam: WebcamStream): Option[WebcamStream] = {
findWithStreamId(webcams, webcam.streamId) match { findWithStreamId(webcams, webcam.streamId) match {
case None => Some(webcams.save(webcam)) case None => {
UserCameraDAO.insert(webcam)
Some(webcams.save(webcam))
}
case _ => None case _ => None
} }
} }
def removeWebcamStream(webcams: Webcams, streamId: String): Option[WebcamStream] = { def removeWebcamStream(webcams: Webcams, streamId: String): Option[WebcamStream] = {
for { for {
webcam <- webcams.remove(streamId) webcam <- {
UserCameraDAO.delete(streamId)
webcams.remove(streamId)
}
} yield webcam } yield webcam
} }

View File

@ -109,6 +109,8 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[UserActivitySignCmdMsg](envelope, jsonNode) routeGenericMsg[UserActivitySignCmdMsg](envelope, jsonNode)
case ChangeUserPinStateReqMsg.NAME => case ChangeUserPinStateReqMsg.NAME =>
routeGenericMsg[ChangeUserPinStateReqMsg](envelope, jsonNode) routeGenericMsg[ChangeUserPinStateReqMsg](envelope, jsonNode)
case ChangeUserMobileFlagReqMsg.NAME =>
routeGenericMsg[ChangeUserMobileFlagReqMsg](envelope, jsonNode)
case SelectRandomViewerReqMsg.NAME => case SelectRandomViewerReqMsg.NAME =>
routeGenericMsg[SelectRandomViewerReqMsg](envelope, jsonNode) routeGenericMsg[SelectRandomViewerReqMsg](envelope, jsonNode)

View File

@ -63,9 +63,11 @@ trait HandlerHelpers extends SystemConfiguration {
guestStatus = regUser.guestStatus, guestStatus = regUser.guestStatus,
emoji = "none", emoji = "none",
pin = false, pin = false,
mobile = false,
presenter = false, presenter = false,
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin, locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
avatar = regUser.avatarURL, avatar = regUser.avatarURL,
color = regUser.color,
clientType = clientType, clientType = clientType,
pickExempted = false, pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0) userLeftFlag = UserLeftFlag(false, 0)

View File

@ -388,6 +388,7 @@ class MeetingActor(
case m: ChangeUserEmojiCmdMsg => handleChangeUserEmojiCmdMsg(m) case m: ChangeUserEmojiCmdMsg => handleChangeUserEmojiCmdMsg(m)
case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m) case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m)
case m: ChangeUserPinStateReqMsg => usersApp.handleChangeUserPinStateReqMsg(m) case m: ChangeUserPinStateReqMsg => usersApp.handleChangeUserPinStateReqMsg(m)
case m: ChangeUserMobileFlagReqMsg => usersApp.handleChangeUserMobileFlagReqMsg(m)
// Client requested to eject user // Client requested to eject user
case m: EjectUserFromMeetingCmdMsg => case m: EjectUserFromMeetingCmdMsg =>

View File

@ -77,6 +77,7 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
case m: UserDisconnectedFromGlobalAudioMsg => logMessage(msg) case m: UserDisconnectedFromGlobalAudioMsg => logMessage(msg)
case m: AssignPresenterReqMsg => logMessage(msg) case m: AssignPresenterReqMsg => logMessage(msg)
case m: ChangeUserPinStateReqMsg => logMessage(msg) case m: ChangeUserPinStateReqMsg => logMessage(msg)
case m: ChangeUserMobileFlagReqMsg => logMessage(msg)
case m: ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg => logMessage(msg) case m: ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg => logMessage(msg)
case m: ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsg => logMessage(msg) case m: ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsg => logMessage(msg)
case m: ScreenshareRtmpBroadcastStartedEvtMsg => logMessage(msg) case m: ScreenshareRtmpBroadcastStartedEvtMsg => logMessage(msg)

View File

@ -13,7 +13,7 @@ object UserJoinedMeetingEvtMsgBuilder {
guestStatus = userState.guestStatus, guestStatus = userState.guestStatus,
emoji = userState.emoji, emoji = userState.emoji,
pin = userState.pin, pin = userState.pin,
presenter = userState.presenter, locked = userState.locked, avatar = userState.avatar, presenter = userState.presenter, locked = userState.locked, avatar = userState.avatar, color = userState.color,
clientType = userState.clientType) clientType = userState.clientType)
val event = UserJoinedMeetingEvtMsg(meetingId, userState.intId, body) val event = UserJoinedMeetingEvtMsg(meetingId, userState.intId, body)

View File

@ -51,7 +51,7 @@ trait FakeTestData {
def createUserVoiceAndCam(liveMeeting: LiveMeeting, role: String, guest: Boolean, authed: Boolean, callingWith: String, def createUserVoiceAndCam(liveMeeting: LiveMeeting, role: String, guest: Boolean, authed: Boolean, callingWith: String,
muted: Boolean, talking: Boolean, listenOnly: Boolean): UserState = { muted: Boolean, talking: Boolean, listenOnly: Boolean): UserState = {
val ruser1 = FakeUserGenerator.createFakeRegisteredUser(liveMeeting.registeredUsers, Roles.MODERATOR_ROLE, true, false) val ruser1 = FakeUserGenerator.createFakeRegisteredUser(liveMeeting.registeredUsers, Roles.MODERATOR_ROLE, true, false, liveMeeting.props.meetingProp.intId)
val vuser1 = FakeUserGenerator.createFakeVoiceUser(ruser1, "webrtc", muted = false, talking = true, listenOnly = false) val vuser1 = FakeUserGenerator.createFakeVoiceUser(ruser1, "webrtc", muted = false, talking = true, listenOnly = false)
VoiceUsers.add(liveMeeting.voiceUsers, vuser1) VoiceUsers.add(liveMeeting.voiceUsers, vuser1)
@ -67,8 +67,8 @@ trait FakeTestData {
def createFakeUser(liveMeeting: LiveMeeting, regUser: RegisteredUser): UserState = { 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, name = regUser.name, role = regUser.role, pin = false,
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus, mobile = false, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, clientType = "unknown", emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown",
pickExempted = false, userLeftFlag = UserLeftFlag(false, 0)) pickExempted = false, userLeftFlag = UserLeftFlag(false, 0))
} }

View File

@ -45,17 +45,18 @@ object FakeUserGenerator {
private def getRandomElement(list: Seq[String], random: Random): String = list(random.nextInt(list.length)) private def getRandomElement(list: Seq[String], random: Random): String = list(random.nextInt(list.length))
def createFakeRegisteredUser(users: RegisteredUsers, role: String, guest: Boolean, authed: Boolean): RegisteredUser = { def createFakeRegisteredUser(users: RegisteredUsers, role: String, guest: Boolean, authed: Boolean, meetingId: String): RegisteredUser = {
val name = getRandomElement(firstNames, random) + " " + getRandomElement(lastNames, random) val name = getRandomElement(firstNames, random) + " " + getRandomElement(lastNames, random)
val id = "w_" + RandomStringGenerator.randomAlphanumericString(16) val id = "w_" + RandomStringGenerator.randomAlphanumericString(16)
val extId = RandomStringGenerator.randomAlphanumericString(16) val extId = RandomStringGenerator.randomAlphanumericString(16)
val authToken = RandomStringGenerator.randomAlphanumericString(16) val authToken = RandomStringGenerator.randomAlphanumericString(16)
val avatarURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" + val avatarURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" +
RandomStringGenerator.randomAlphanumericString(10) + ".png" RandomStringGenerator.randomAlphanumericString(10) + ".png"
val color = "#ff6242"
val ru = RegisteredUsers.create(userId = id, extId, name, role, val ru = RegisteredUsers.create(userId = id, extId, name, role,
authToken, avatarURL, guest, authed, guestStatus = GuestStatus.ALLOW, false, false) authToken, avatarURL, color, guest, authed, guestStatus = GuestStatus.ALLOW, false, false)
RegisteredUsers.add(users, ru) RegisteredUsers.add(users, ru, meetingId)
ru ru
} }

View File

@ -1,8 +1,8 @@
[Unit] [Unit]
Description=BigBlueButton Apps (Akka) Description=BigBlueButton Apps (Akka)
Requires=network.target Requires=network.target
Wants=redis-server.service bbb-fsesl-akka.service Wants=redis-server.service bbb-fsesl-akka.service postgresql.service
After=redis-server.service bbb-fsesl-akka.service After=redis-server.service bbb-fsesl-akka.service postgresql.service
PartOf=bigbluebutton.target PartOf=bigbluebutton.target
[Service] [Service]

View File

@ -16,7 +16,7 @@ object TestDataGen {
val ru = RegisteredUsers.create(userId = id, extId, name, role, val ru = RegisteredUsers.create(userId = id, extId, name, role,
authToken, avatarURL, guest, authed, GuestStatus.ALLOW, false) authToken, avatarURL, guest, authed, GuestStatus.ALLOW, false)
RegisteredUsers.add(users, ru) RegisteredUsers.add(users, ru, meetingId = "test")
ru ru
} }
@ -43,8 +43,8 @@ object TestDataGen {
def createUserFor(liveMeeting: LiveMeeting, regUser: RegisteredUser, presenter: Boolean): UserState = { def createUserFor(liveMeeting: LiveMeeting, regUser: RegisteredUser, presenter: Boolean): UserState = {
val u = UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, val u = UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role,
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, clientType = "unknown", emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242",
userLeftFlag = UserLeftFlag(false, 0)) clientType = "unknown", userLeftFlag = UserLeftFlag(false, 0))
Users2x.add(liveMeeting.users2x, u) Users2x.add(liveMeeting.users2x, u)
u u
} }

View File

@ -35,6 +35,21 @@ redis {
keyExpiry=1209600 keyExpiry=1209600
} }
postgres {
dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
properties = {
serverName = "localhost"
portNumber = "5432"
databaseName = "bigbluebutton"
user = "postgres"
password = "bigbluebutton"
}
numThreads = 1
maxConnections = 1
}
expire { expire {
# time in seconds # time in seconds
lastUserLeft = 60 lastUserLeft = 60

View File

@ -88,11 +88,22 @@ case class UserJoinedMeetingEvtMsg(
header: BbbClientMsgHeader, header: BbbClientMsgHeader,
body: UserJoinedMeetingEvtMsgBody body: UserJoinedMeetingEvtMsgBody
) extends BbbCoreMsg ) extends BbbCoreMsg
case class UserJoinedMeetingEvtMsgBody(intId: String, extId: String, name: String, role: String, case class UserJoinedMeetingEvtMsgBody(
guest: Boolean, authed: Boolean, guestStatus: String, intId: String,
extId: String,
name: String,
role: String,
guest: Boolean,
authed: Boolean,
guestStatus: String,
emoji: String, emoji: String,
pin: Boolean, pin: Boolean,
presenter: Boolean, locked: Boolean, avatar: String, clientType: String) presenter: Boolean,
locked: Boolean,
avatar: String,
color: String,
clientType: String
)
/** /**
* Sent by client to get all users in a meeting. * Sent by client to get all users in a meeting.
@ -189,6 +200,20 @@ object UserEmojiChangedEvtMsg { val NAME = "UserEmojiChangedEvtMsg" }
case class UserEmojiChangedEvtMsg(header: BbbClientMsgHeader, body: UserEmojiChangedEvtMsgBody) extends BbbCoreMsg case class UserEmojiChangedEvtMsg(header: BbbClientMsgHeader, body: UserEmojiChangedEvtMsgBody) extends BbbCoreMsg
case class UserEmojiChangedEvtMsgBody(userId: String, emoji: String) case class UserEmojiChangedEvtMsgBody(userId: String, emoji: String)
/**
* Sent from client about a user mobile flag.
*/
object ChangeUserMobileFlagReqMsg { val NAME = "ChangeUserMobileFlagReqMsg" }
case class ChangeUserMobileFlagReqMsg(header: BbbClientMsgHeader, body: ChangeUserMobileFlagReqMsgBody) extends StandardMsg
case class ChangeUserMobileFlagReqMsgBody(userId: String, mobile: Boolean)
/**
* Sent to all clients about a user mobile flag.
*/
object UserMobileFlagChangedEvtMsg { val NAME = "UserMobileFlagChangedEvtMsg" }
case class UserMobileFlagChangedEvtMsg(header: BbbClientMsgHeader, body: UserMobileFlagChangedEvtMsgBody) extends BbbCoreMsg
case class UserMobileFlagChangedEvtMsgBody(userId: String, mobile: Boolean)
object AssignPresenterReqMsg { val NAME = "AssignPresenterReqMsg" } object AssignPresenterReqMsg { val NAME = "AssignPresenterReqMsg" }
case class AssignPresenterReqMsg(header: BbbClientMsgHeader, body: AssignPresenterReqMsgBody) extends StandardMsg case class AssignPresenterReqMsg(header: BbbClientMsgHeader, body: AssignPresenterReqMsgBody) extends StandardMsg
case class AssignPresenterReqMsgBody(requesterId: String, newPresenterId: String, newPresenterName: String, assignedBy: String) case class AssignPresenterReqMsgBody(requesterId: String, newPresenterId: String, newPresenterName: String, assignedBy: String)

View File

@ -0,0 +1,25 @@
[Unit]
Description=BigBlueButton GraphQL Server
Requires=network.target
Wants=postgresql.service
After=postgresql.service
PartOf=bigbluebutton.target
[Service]
Type=simple
User=bigbluebutton
Group=bigbluebutton
WorkingDirectory=/usr/share/bbb-web
EnvironmentFile=/etc/default/bbb-graphql-server
ExecStart=/usr/local/bin/hasura-graphql-engine serve
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=60
SuccessExitStatus=143
TimeoutStopSec=5
PermissionsStartOnly=true
LimitNOFILE=1024
[Install]
WantedBy=multi-user.target bigbluebutton.target

View File

@ -0,0 +1,74 @@
drop table if exists "user";
CREATE TABLE public."user" (
"userId" varchar(255) NOT NULL,
"extId" varchar(255) NULL,
"meetingId" varchar(255) NULL,
"name" varchar(255) NULL,
"avatar" varchar(255) NULL,
"color" varchar(7) null,
"emoji" varchar,
"guest" bool NULL,
"guestStatus" varchar(255),
"mobile" bool null,
-- "excludeFromDashboard" bool NULL,
"role" varchar(255) NULL,
"authed" bool NULL,
"joined" bool NULL,
"leftFlag" bool null,
"ejected" bool null,
"ejectReason" varchar(255),
"banned" bool NULL,
"loggedOut" bool NULL,
"registeredOn" int8 NULL,
"presenter" bool null,
"pinned" bool NULL,
"locked" bool null,
CONSTRAINT user_pkey PRIMARY KEY ("userId")
);
create index "user_meetingId" on "user"("meetingId");
drop view if exists "v_user_microphone";
drop table if exists "user_microphone";
create table "user_microphone" (
"voiceUserId" varchar(255) primary key,
"userId" varchar(255) references "user"("userId"),
"callerName" varchar(255),
"callerNum" varchar(255),
"callingWith" varchar(255),
"joined" boolean null,
"listenOnly" boolean null,
"muted" boolean null,
"spoke" boolean null,
"talking" boolean null,
"floor" boolean null,
"lastFloorTime" varchar(25),
"voiceConf" varchar(255),
"color" varchar(7),
"endTime" bigint null,
"startTime" bigint null
);
create index "user_microphone_userId" on "user_microphone"("userId");
create or replace view "v_user_microphone" as
select u."meetingId", "user_microphone".*
from "user_microphone"
join "user" u on u."userId" = "user_microphone"."userId";
drop view if exists "v_user_camera";
drop table if exists "user_camera";
create table "user_camera" (
"streamId" varchar(255) primary key,
"userId" varchar(255) NOT NULL references "user"("userId")
);
create index "user_camera_userId" on "user_camera"("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";

View File

@ -0,0 +1,6 @@
version: 3
endpoint: http://localhost:8080
metadata_directory: metadata
actions:
kind: synchronous
handler_webhook_baseurl: http://localhost:3000

View File

@ -0,0 +1,5 @@
HASURA_GRAPHQL_DATABASE_URL=postgres://postgres:bigbluebutton@localhost:5432/hasura_app
#HASURA_GRAPHQL_METADATA_DATABASE_URL=postgres://postgres:bigbluebutton@localhost:5432/hasura_app
#HASURA_GRAPHQL_NO_OF_RETRIES
HASURA_GRAPHQL_ENABLE_CONSOLE=true
HASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_REFETCH_INTERVAL=500

View File

@ -0,0 +1,69 @@
#!/bin/bash
if [ "$EUID" -ne 0 ]; then
echo "Please run this script as root ( or with sudo )" ;
exit 1;
fi;
cd "$(dirname "$0")"
# sudo apt -y install postgresql postgresql-contrib postgresql-client postgresql-client-common
# sudo -u postgres psql -c "SELECT version();"
# sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'bigbluebutton';"
# psql -U postgres -d myDataBase -a -f myInsertFile
# Install Postgresql
sudo apt update
sudo apt install postgresql postgresql-contrib -y
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'bigbluebutton'"
sudo -u postgres psql -c "create database bigbluebutton"
sudo -u postgres psql -U postgres -d bigbluebutton -a -f bbb_schema.sql
sudo -u postgres psql -c "create database hasura_app"
echo "Postgresql installed!"
# sudo vi /etc/postgresql/12/main/postgresql.conf
# listen_addresses = '*'
# sudo vi /etc/postgresql/12/main/pg_hba.conf
# host all all 0.0.0.0/0 md5
# host all all ::/0 md5
# sudo docker run --name hasura --rm --net=host -itd -e HASURA_GRAPHQL_DATABASE_URL=postgres://postgres:bigbluebutton@bbb26.bbbvm.imdt.com.br:5432/hasura_app -e HASURA_GRAPHQL_ENABLE_CONSOLE=true -e HASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_REFETCH_INTERVAL=100 hasura/graphql-engine:v2.20.0
# Install Hasura graphql
wget https://graphql-engine-cdn.hasura.io/server/latest/linux-amd64 -O /usr/local/bin/hasura-graphql-engine
chmod +x /usr/local/bin/hasura-graphql-engine
apt-get install -y gnupg2 curl apt-transport-https ca-certificates libkrb5-3 libpq5 libnuma1 unixodbc-dev libmariadb-dev-compat mariadb-client-10.3
cp ./hasura-config.env /etc/default/bbb-graphql-server
cp ./bbb-graphql-server.service /lib/systemd/system/bbb-graphql-server.service
sudo systemctl enable bbb-graphql-server
sudo systemctl start bbb-graphql-server
# Install Hasura CLI
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
# Apply BBB metadata in Hasura
/usr/local/bin/hasura metadata apply
echo ""
echo ""
echo "Bbb-graphql-server Installed!"
echo "http://bbb26.bbbvm.imdt.com.br:8080/console"
# /usr/local/bin/hasura-graphql-engine serve
# hasura init bbb-graphql --endpoint http://localhost:8080
# deb http://apt.postgresql.org/pub/repos/apt focal-pgdg main" > /etc/apt/sources.list.d/pgdg.list && curl -s https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && apt-get -y update && apt-get install -y postgresql-client-14 && find /usr/bin -name 'pg*' -not -path '/usr/bin/pg_dump' -delete

View File

@ -0,0 +1,6 @@
actions: []
custom_types:
enums: []
input_objects: []
objects: []
scalars: []

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,66 @@
table:
name: user
schema: public
object_relationships:
- name: camera
using:
manual_configuration:
column_mapping:
userId: userId
insertion_order: null
remote_table:
name: v_user_camera
schema: public
array_relationships:
- name: microphones
using:
manual_configuration:
column_mapping:
userId: userId
insertion_order: null
remote_table:
name: v_user_microphone
schema: public
- name: user_cameras
using:
foreign_key_constraint_on:
column: userId
table:
name: user_camera
schema: public
- name: user_microphones
using:
foreign_key_constraint_on:
column: userId
table:
name: user_microphone
schema: public
select_permissions:
- role: bbb_client
permission:
columns:
- authed
- avatar
- banned
- color
- ejectReason
- ejected
- emoji
- extId
- guest
- guestStatus
- joined
- leftFlag
- locked
- loggedOut
- meetingId
- mobile
- name
- pinned
- presenter
- role
- userId
filter:
meetingId:
_eq: X-Hasura-MeetingId
allow_aggregations: true

View File

@ -0,0 +1,3 @@
table:
name: user_camera
schema: public

View File

@ -0,0 +1,3 @@
table:
name: user_microphone
schema: public

View File

@ -0,0 +1,13 @@
table:
name: v_user_camera
schema: public
select_permissions:
- role: bbb_client
permission:
columns:
- meetingId
- streamId
- userId
filter:
meetingId:
_eq: X-Hasura-MeetingId

View File

@ -0,0 +1,27 @@
table:
name: v_user_microphone
schema: public
select_permissions:
- role: bbb_client
permission:
columns:
- meetingId
- voiceUserId
- userId
- callerName
- callerNum
- callingWith
- joined
- listenOnly
- muted
- spoke
- talking
- floor
- lastFloorTime
- voiceConf
- color
- endTime
- startTime
filter:
meetingId:
_eq: X-Hasura-MeetingId

View File

@ -0,0 +1,5 @@
- "!include public_user.yaml"
- "!include public_user_camera.yaml"
- "!include public_user_microphone.yaml"
- "!include public_v_user_camera.yaml"
- "!include public_v_user_microphone.yaml"

View File

@ -0,0 +1,28 @@
- name: default
kind: postgres
configuration:
connection_info:
database_url:
from_env: HASURA_GRAPHQL_DATABASE_URL
isolation_level: read-committed
pool_settings:
connection_lifetime: 600
idle_timeout: 180
max_connections: 50
retries: 1
use_prepared_statements: true
tables: "!include default/tables/tables.yaml"
- name: BigBlueButton
kind: postgres
configuration:
connection_info:
database_url:
connection_parameters:
database: bigbluebutton
host: localhost
password: bigbluebutton
port: 5432
username: postgres
isolation_level: read-committed
use_prepared_statements: false
tables: "!include BigBlueButton/tables/tables.yaml"

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
disabled_for_roles: []

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
version: 3

View File

@ -4,6 +4,7 @@ import Logger from '/imports/startup/server/logger';
import GuestUsers from '/imports/api/guest-users/'; import GuestUsers from '/imports/api/guest-users/';
import updatePositionInWaitingQueue from '../methods/updatePositionInWaitingQueue'; import updatePositionInWaitingQueue from '../methods/updatePositionInWaitingQueue';
//TODO receive color from akka-apps
const COLOR_LIST = [ const COLOR_LIST = [
'#7b1fa2', '#6a1b9a', '#4a148c', '#5e35b1', '#512da8', '#4527a0', '#7b1fa2', '#6a1b9a', '#4a148c', '#5e35b1', '#512da8', '#4527a0',
'#311b92', '#3949ab', '#303f9f', '#283593', '#1a237e', '#1976d2', '#1565c0', '#311b92', '#3949ab', '#303f9f', '#283593', '#1a237e', '#1976d2', '#1565c0',

View File

@ -8,6 +8,7 @@ import handleEmojiStatus from './handlers/emojiStatus';
import handleChangeRole from './handlers/changeRole'; import handleChangeRole from './handlers/changeRole';
import handleUserPinChanged from './handlers/userPinChanged'; import handleUserPinChanged from './handlers/userPinChanged';
import handleUserInactivityInspect from './handlers/userInactivityInspect'; import handleUserInactivityInspect from './handlers/userInactivityInspect';
import handleChangeMobileFlag from "/imports/api/users/server/handlers/changeMobileFlag";
RedisPubSub.on('PresenterAssignedEvtMsg', handlePresenterAssigned); RedisPubSub.on('PresenterAssignedEvtMsg', handlePresenterAssigned);
RedisPubSub.on('UserJoinedMeetingEvtMsg', handleUserJoined); RedisPubSub.on('UserJoinedMeetingEvtMsg', handleUserJoined);
@ -15,6 +16,7 @@ RedisPubSub.on('UserLeftMeetingEvtMsg', handleRemoveUser);
RedisPubSub.on('ValidateAuthTokenRespMsg', handleValidateAuthToken); RedisPubSub.on('ValidateAuthTokenRespMsg', handleValidateAuthToken);
RedisPubSub.on('UserEmojiChangedEvtMsg', handleEmojiStatus); RedisPubSub.on('UserEmojiChangedEvtMsg', handleEmojiStatus);
RedisPubSub.on('UserRoleChangedEvtMsg', handleChangeRole); RedisPubSub.on('UserRoleChangedEvtMsg', handleChangeRole);
RedisPubSub.on('UserMobileFlagChangedEvtMsg', handleChangeMobileFlag);
RedisPubSub.on('UserLeftFlagUpdatedEvtMsg', handleUserLeftFlagUpdated); RedisPubSub.on('UserLeftFlagUpdatedEvtMsg', handleUserLeftFlagUpdated);
RedisPubSub.on('UserPinStateChangedEvtMsg', handleUserPinChanged); RedisPubSub.on('UserPinStateChangedEvtMsg', handleUserPinChanged);
RedisPubSub.on('UserInactivityInspectMsg', handleUserInactivityInspect); RedisPubSub.on('UserInactivityInspectMsg', handleUserInactivityInspect);

View File

@ -0,0 +1,13 @@
import { check } from 'meteor/check';
import setMobile from '/imports/api/users/server/modifiers/setMobile';
export default function handleChangeMobileFlag(payload, meetingId) {
check(payload.body, Object);
check(meetingId, String);
const { userId, mobile } = payload.body;
if(mobile) {
setMobile(meetingId, userId);
}
}

View File

@ -2,17 +2,27 @@ import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger'; import Logger from '/imports/startup/server/logger';
import setMobile from '../modifiers/setMobile'; import setMobile from '../modifiers/setMobile';
import { extractCredentials } from '/imports/api/common/server/helpers'; import { extractCredentials } from '/imports/api/common/server/helpers';
import RedisPubSub from "/imports/startup/server/redis";
export default function setMobileUser() { export default function setMobileUser() {
try { try {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'ChangeUserMobileFlagReqMsg';
const { meetingId, requesterUserId } = extractCredentials(this.userId); const { meetingId, requesterUserId } = extractCredentials(this.userId);
check(meetingId, String); check(meetingId, String);
check(requesterUserId, String); check(requesterUserId, String);
const payload = {
userId: requesterUserId,
mobile: true,
};
Logger.verbose(`Mobile user ${requesterUserId} from meeting ${meetingId}`); Logger.verbose(`Mobile user ${requesterUserId} from meeting ${meetingId}`);
setMobile(meetingId, requesterUserId); RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
} catch (err) { } catch (err) {
Logger.error(`Exception while invoking method setMobileUser ${err.stack}`); Logger.error(`Exception while invoking method setMobileUser ${err.stack}`);
} }

View File

@ -10,12 +10,6 @@ import { lowercaseTrim } from '/imports/utils/string-utils';
import addVoiceUser from '/imports/api/voice-users/server/modifiers/addVoiceUser'; import addVoiceUser from '/imports/api/voice-users/server/modifiers/addVoiceUser';
const COLOR_LIST = [
'#7b1fa2', '#6a1b9a', '#4a148c', '#5e35b1', '#512da8', '#4527a0',
'#311b92', '#3949ab', '#303f9f', '#283593', '#1a237e', '#1976d2', '#1565c0',
'#0d47a1', '#0277bd', '#01579b',
];
export default function addUser(meetingId, userData) { export default function addUser(meetingId, userData) {
const user = userData; const user = userData;
@ -34,6 +28,7 @@ export default function addUser(meetingId, userData) {
presenter: Boolean, presenter: Boolean,
locked: Boolean, locked: Boolean,
avatar: String, avatar: String,
color: String,
pin: Boolean, pin: Boolean,
clientType: String, clientType: String,
}); });
@ -46,14 +41,9 @@ export default function addUser(meetingId, userData) {
}; };
const Meeting = Meetings.findOne({ meetingId }); const Meeting = Meetings.findOne({ meetingId });
/* While the akka-apps dont generate a color we just pick one
from a list based on the userId */
const color = COLOR_LIST[stringHash(user.intId) % COLOR_LIST.length];
const userInfos = { const userInfos = {
meetingId, meetingId,
sortName: lowercaseTrim(user.name), sortName: lowercaseTrim(user.name),
color,
speechLocale: '', speechLocale: '',
mobile: false, mobile: false,
breakoutProps: { breakoutProps: {