Merge pull request #19104 from gustavotrott/graphql-user-join-action

graphql: New User Flow for Joining Meetings via GraphQL
This commit is contained in:
Gustavo Trott 2023-11-15 09:16:02 -03:00 committed by GitHub
commit 6dc3dbb1fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 317 additions and 192 deletions

View File

@ -2,15 +2,14 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
import org.bigbluebutton.core.db.{ UserStateDAO } import org.bigbluebutton.core.db.{ UserDAO, UserStateDAO }
import org.bigbluebutton.core.domain.MeetingState2x import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ RegisteredUser, RegisteredUsers, Users2x, VoiceUsers } import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter } import org.bigbluebutton.core.running._
import org.bigbluebutton.core2.message.senders.MsgBuilder import org.bigbluebutton.core2.message.senders._
trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers { trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
this: MeetingActor => this: MeetingActor =>
val liveMeeting: LiveMeeting val liveMeeting: LiveMeeting
val outGW: OutMsgRouter val outGW: OutMsgRouter
@ -18,64 +17,136 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
log.info("Received user joined meeting. user {} meetingId={}", msg.body.userId, msg.header.meetingId) log.info("Received user joined meeting. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match { Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match {
case Some(reconnectingUser) => case Some(user) => handleUserReconnecting(user, msg, state)
if (reconnectingUser.userLeftFlag.left) { case None => handleUserJoining(msg, state)
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
}
state
case None =>
// Check if maxParticipants has been reached
// User are able to reenter if he already joined previously with the same extId
val userHasJoinedAlready = RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers) match {
case Some(regUser: RegisteredUser) => RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers)
case None => false
}
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 &&
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
userHasJoinedAlready == false
if (!hasReachedMaxParticipants) {
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
if (liveMeeting.props.meetingProp.isBreakout) {
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
}
// Warn previous users that someone connected with same Id
for {
regUser <- RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId,
liveMeeting.registeredUsers)
} yield {
RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers)
.filter(u => u.id != regUser.id)
.foreach { previousUser =>
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
previousUser.id,
liveMeeting.props.meetingProp.intId,
"info",
"promote",
"app.mobileAppModal.userConnectedWithSameId",
"Notification to warn that user connect again from other browser/device",
Vector(regUser.name)
)
outGW.send(notifyUserEvent)
}
}
// fresh user joined (not due to reconnection). Clear (pop) the cached voice user
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, msg.body.userId)
UserStateDAO.updateExpired(msg.body.userId, false)
newState
} else {
log.info("Ignoring user {} attempt to join, once the meeting {} has reached max participants: {}", msg.body.userId, msg.header.meetingId, liveMeeting.props.usersProp.maxUsers)
state
}
} }
} }
}
private def handleUserJoining(msg: UserJoinMeetingReqMsg, state: MeetingState2x): MeetingState2x = {
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers)
log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]")
regUser.fold {
handleFailedUserJoin(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN)
state
} { user =>
val validationResult = for {
_ <- checkIfUserGuestStatusIsAllowed(user)
_ <- checkIfUserIsBanned(user)
_ <- checkIfUserLoggedOut(user)
_ <- validateMaxParticipants(user)
} yield user
validationResult.fold(
reason => handleFailedUserJoin(msg, reason._1, reason._2),
validUser => handleSuccessfulUserJoin(msg, validUser)
)
}
}
private def handleSuccessfulUserJoin(msg: UserJoinMeetingReqMsg, regUser: RegisteredUser) = {
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
updateParentMeetingWithNewListOfUsers()
notifyPreviousUsersWithSameExtId(regUser)
clearCachedVoiceUser(regUser)
clearExpiredUserState(regUser)
invalidateUserGraphqlConnection(regUser)
newState
}
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)
state
}
private def handleUserReconnecting(user: UserState, msg: UserJoinMeetingReqMsg, state: MeetingState2x): MeetingState2x = {
if (user.userLeftFlag.left) {
resetUserLeftFlag(msg)
}
state
}
private def resetUserLeftFlag(msg: UserJoinMeetingReqMsg) = {
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
}
private def validateMaxParticipants(regUser: RegisteredUser): Either[(String, String), Unit] = {
val userHasJoinedAlready = RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers)
val maxParticipants = liveMeeting.props.usersProp.maxUsers - 1
if (maxParticipants > 0 && //0 = no limit
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= maxParticipants &&
!userHasJoinedAlready) {
Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS))
} else {
Right(())
}
}
private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.guestStatus != GuestStatus.ALLOW) {
Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED))
} else {
Right(())
}
}
private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.banned) {
Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING))
} else {
Right(())
}
}
private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.loggedOut) {
Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT))
} else {
Right(())
}
}
private def updateParentMeetingWithNewListOfUsers() = {
if (liveMeeting.props.meetingProp.isBreakout) {
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
}
}
private def notifyPreviousUsersWithSameExtId(regUser: RegisteredUser) = {
RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers)
.filter(_.id != regUser.id)
.foreach { previousUser =>
sendUserConnectedNotification(previousUser, regUser, liveMeeting)
}
}
private def sendUserConnectedNotification(previousUser: RegisteredUser, newUser: RegisteredUser, liveMeeting: LiveMeeting) = {
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
previousUser.id,
liveMeeting.props.meetingProp.intId,
"info",
"promote",
"app.mobileAppModal.userConnectedWithSameId",
"Notification to warn that user connect again from other browser/device",
Vector(newUser.name)
)
outGW.send(notifyUserEvent)
}
private def clearCachedVoiceUser(regUser: RegisteredUser) =
// fresh user joined (not due to reconnection). Clear (pop) the cached voice user
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, regUser.id)
private def clearExpiredUserState(regUser: RegisteredUser) =
UserStateDAO.updateExpired(regUser.id, false)
private def invalidateUserGraphqlConnection(regUser: RegisteredUser) =
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "user_joined", outGW)
}

View File

@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.InternalEventBus import org.bigbluebutton.core.bus.InternalEventBus
import org.bigbluebutton.core.db.UserDAO
import org.bigbluebutton.core.domain.MeetingState2x import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models._ import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter } import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter }
@ -15,103 +16,79 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
val eventBus: InternalEventBus val eventBus: InternalEventBus
def handleValidateAuthTokenReqMsg(msg: ValidateAuthTokenReqMsg, state: MeetingState2x): MeetingState2x = { def handleValidateAuthTokenReqMsg(msg: ValidateAuthTokenReqMsg, state: MeetingState2x): MeetingState2x = {
log.debug("RECEIVED ValidateAuthTokenReqMsg msg {}", msg) log.debug(s"Received ValidateAuthTokenReqMsg msg $msg")
var failReason = "Invalid auth token." val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers)
var failReasonCode = EjectReasonCode.VALIDATE_TOKEN log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]")
log.info("Number of registered users [{}]", RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)) regUser.fold {
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, sendFailedValidateAuthTokenRespMsg(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN)
liveMeeting.registeredUsers) } { user =>
regUser match { val validationResult = for {
case Some(u) => _ <- checkIfUserGuestStatusIsAllowed(user)
// Check if maxParticipants has been reached _ <- checkIfUserIsBanned(user)
// User are able to reenter if he already joined previously with the same extId _ <- checkIfUserLoggedOut(user)
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 && _ <- validateMaxParticipants(user)
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers && } yield user
RegisteredUsers.checkUserExtIdHasJoined(u.externId, liveMeeting.registeredUsers) == false
// Check if banned user is rejoining. validationResult.fold(
// Fail validation if ejected user is rejoining. reason => sendFailedValidateAuthTokenRespMsg(msg, reason._1, reason._2),
// ralam april 21, 2020 validUser => sendSuccessfulValidateAuthTokenRespMsg(validUser)
if (u.guestStatus == GuestStatus.ALLOW && !u.banned && !u.loggedOut && !hasReachedMaxParticipants) { )
userValidated(u, state) }
} else {
if (u.banned) {
failReason = "Banned user rejoining"
failReasonCode = EjectReasonCode.BANNED_USER_REJOINING
} else if (u.loggedOut) {
failReason = "User had logged out"
failReasonCode = EjectReasonCode.USER_LOGGED_OUT
} else if (hasReachedMaxParticipants) {
failReason = "The maximum number of participants allowed for this meeting has been reached."
failReasonCode = EjectReasonCode.MAX_PARTICIPANTS
}
validateTokenFailed(
outGW,
meetingId = liveMeeting.props.meetingProp.intId,
userId = msg.header.userId,
authToken = msg.body.authToken,
valid = false,
waitForApproval = false,
failReason,
failReasonCode,
state
)
}
case None => state
validateTokenFailed( }
outGW,
meetingId = liveMeeting.props.meetingProp.intId,
userId = msg.header.userId,
authToken = msg.body.authToken,
valid = false,
waitForApproval = false,
failReason,
failReasonCode,
state
)
private def validateMaxParticipants(user: RegisteredUser): Either[(String, String), Unit] = {
if (liveMeeting.props.usersProp.maxUsers > 0 &&
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
RegisteredUsers.checkUserExtIdHasJoined(user.externId, liveMeeting.registeredUsers) == false) {
Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS))
} else {
Right(())
} }
} }
def validateTokenFailed( private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = {
outGW: OutMsgRouter, if (user.guestStatus != GuestStatus.ALLOW) {
meetingId: String, Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED))
userId: String, } else {
authToken: String, Right(())
valid: Boolean, }
waitForApproval: Boolean,
reason: String,
reasonCode: String,
state: MeetingState2x
): MeetingState2x = {
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, 0,
0, reasonCode, reason)
outGW.send(event)
// send a system message to force disconnection
// Comment out as meteor will disconnect the client. Requested by Tiago (ralam apr 28, 2020)
//Sender.sendDisconnectClientSysMsg(meetingId, userId, SystemUser.ID, reasonCode, outGW)
state
} }
def sendValidateAuthTokenRespMsg(meetingId: String, userId: String, authToken: String, private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = {
valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long, if (user.banned) {
reasonCode: String = EjectReasonCode.NOT_EJECT, reason: String = "User not ejected"): Unit = { Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING))
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, registeredOn, } else {
authTokenValidatedOn, reasonCode, reason) Right(())
}
}
private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.loggedOut) {
Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT))
} else {
Right(())
}
}
private def sendFailedValidateAuthTokenRespMsg(msg: ValidateAuthTokenReqMsg, failReason: String, failReasonCode: String) = {
UserDAO.updateJoinError(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)
outGW.send(event) outGW.send(event)
} }
def userValidated(user: RegisteredUser, state: MeetingState2x): MeetingState2x = { def sendSuccessfulValidateAuthTokenRespMsg(user: RegisteredUser) = {
val meetingId = liveMeeting.props.meetingProp.intId val meetingId = liveMeeting.props.meetingProp.intId
val updatedUser = RegisteredUsers.updateUserLastAuthTokenValidated(liveMeeting.registeredUsers, user) val updatedUser = RegisteredUsers.updateUserLastAuthTokenValidated(liveMeeting.registeredUsers, user)
sendValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, valid = true, waitForApproval = false, updatedUser.registeredOn, updatedUser.lastAuthTokenValidatedOn) val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, true, false, updatedUser.registeredOn,
state updatedUser.lastAuthTokenValidatedOn, EjectReasonCode.NOT_EJECT, "User not ejected")
outGW.send(event)
} }
def sendAllUsersInMeeting(requesterId: String): Unit = { def sendAllUsersInMeeting(requesterId: String): Unit = {

View File

@ -77,12 +77,12 @@ object MeetingDAO {
).onComplete { ).onComplete {
case Success(rowsAffected) => { case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Meeting table!") DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Meeting table!")
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
MeetingUsersPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp) MeetingUsersPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp)
MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps) MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps)
MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp) MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp)
MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp) MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp)
MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp) MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp)
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp) MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp)
MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups) MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups)
MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps) MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps)

View File

@ -19,7 +19,7 @@ class UserConnectionStatusDbTableDef(tag: Tag) extends Table[UserConnectionStatu
val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt") val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt")
} }
object UserConnectionStatusdDAO { object UserConnectionStatusDAO {
def insert(meetingId: String, userId: String) = { def insert(meetingId: String, userId: String) = {
DatabaseConnection.db.run( DatabaseConnection.db.run(

View File

@ -6,29 +6,31 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
case class UserDbModel( case class UserDbModel(
userId: String, userId: String,
extId: String, extId: String,
meetingId: String, meetingId: String,
name: String, name: String,
role: String, role: String,
avatar: String = "", avatar: String = "",
color: String = "", color: String = "",
sessionToken: String = "", sessionToken: String = "",
authed: Boolean = false, authed: Boolean = false,
joined: Boolean = false, joined: Boolean = false,
banned: Boolean = false, joinErrorMessage: Option[String],
loggedOut: Boolean = false, joinErrorCode: Option[String],
guest: Boolean, banned: Boolean = false,
guestStatus: String, loggedOut: Boolean = false,
registeredOn: Long, guest: Boolean,
excludeFromDashboard: Boolean, guestStatus: String,
registeredOn: Long,
excludeFromDashboard: Boolean,
) )
class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") { class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
override def * = ( override def * = (
userId,extId,meetingId,name,role,avatar,color, sessionToken, authed,joined,banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard) <> (UserDbModel.tupled, UserDbModel.unapply) userId,extId,meetingId,name,role,avatar,color, sessionToken, authed,joined,joinErrorCode, joinErrorMessage, banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard) <> (UserDbModel.tupled, UserDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey) val userId = column[String]("userId", O.PrimaryKey)
val extId = column[String]("extId") val extId = column[String]("extId")
val meetingId = column[String]("meetingId") val meetingId = column[String]("meetingId")
@ -39,6 +41,8 @@ class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
val sessionToken = column[String]("sessionToken") val sessionToken = column[String]("sessionToken")
val authed = column[Boolean]("authed") val authed = column[Boolean]("authed")
val joined = column[Boolean]("joined") val joined = column[Boolean]("joined")
val joinErrorCode = column[Option[String]]("joinErrorCode")
val joinErrorMessage = column[Option[String]]("joinErrorMessage")
val banned = column[Boolean]("banned") val banned = column[Boolean]("banned")
val loggedOut = column[Boolean]("loggedOut") val loggedOut = column[Boolean]("loggedOut")
val guest = column[Boolean]("guest") val guest = column[Boolean]("guest")
@ -62,6 +66,8 @@ object UserDAO {
sessionToken = regUser.sessionToken, sessionToken = regUser.sessionToken,
authed = regUser.authed, authed = regUser.authed,
joined = regUser.joined, joined = regUser.joined,
joinErrorCode = None,
joinErrorMessage = None,
banned = regUser.banned, banned = regUser.banned,
loggedOut = regUser.loggedOut, loggedOut = regUser.loggedOut,
guest = regUser.guest, guest = regUser.guest,
@ -73,10 +79,10 @@ object UserDAO {
).onComplete { ).onComplete {
case Success(rowsAffected) => { case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in User table!") DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in User table!")
ChatUserDAO.insertUserPublicChat(meetingId, regUser.id) UserConnectionStatusDAO.insert(meetingId, regUser.id)
UserConnectionStatusdDAO.insert(meetingId, regUser.id)
UserCustomParameterDAO.insert(regUser.id, regUser.customParameters) UserCustomParameterDAO.insert(regUser.id, regUser.customParameters)
UserClientSettingsDAO.insert(regUser.id, meetingId) UserClientSettingsDAO.insert(regUser.id, meetingId)
ChatUserDAO.insertUserPublicChat(meetingId, regUser.id)
} }
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting user: $e") case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting user: $e")
} }
@ -94,6 +100,17 @@ object UserDAO {
} }
} }
def updateJoinError(userId: String, joinErrorCode: String, joinErrorMessage: String) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === userId)
.map(u => (u.joined, u.joinErrorCode, u.joinErrorMessage))
.update((false, Some(joinErrorCode), Some(joinErrorMessage)))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user (Joined) table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user (Joined): $e")
}
}
def delete(intId: String) = { def delete(intId: String) = {

View File

@ -30,6 +30,7 @@ function App() {
const [sessionToken, setSessionToken] = useState(null); const [sessionToken, setSessionToken] = useState(null);
const [userId, setUserId] = useState(null); const [userId, setUserId] = useState(null);
const [userName, setUserName] = useState(null); const [userName, setUserName] = useState(null);
const [userAuthToken, setUserAuthToken] = useState(null);
const [graphqlClient, setGraphqlClient] = useState(null); const [graphqlClient, setGraphqlClient] = useState(null);
const [enterApiResponse, setEnterApiResponse] = useState(''); const [enterApiResponse, setEnterApiResponse] = useState('');
@ -46,6 +47,7 @@ function App() {
if(json?.response?.internalUserID) { if(json?.response?.internalUserID) {
setUserId(json.response.internalUserID); setUserId(json.response.internalUserID);
setUserName(json.response.fullname); setUserName(json.response.fullname);
setUserAuthToken(json.response.authToken);
} }
}); });
} }
@ -110,7 +112,7 @@ function App() {
<br /> <br />
<PluginDataChannel userId={userId} /> <PluginDataChannel userId={userId} />
<br /> <br />
<MyInfo /> <MyInfo userAuthToken={userAuthToken} />
<br /> <br />
<PresPresentationUploadToken /> <PresPresentationUploadToken />
<br /> <br />

View File

@ -1,7 +1,7 @@
import {gql, useMutation, useQuery, useSubscription} from '@apollo/client'; import {gql, useMutation, useSubscription} from '@apollo/client';
import React from "react"; import React from "react";
export default function MyInfo() { export default function MyInfo({userAuthToken}) {
//where is not necessary once user can update only its own status //where is not necessary once user can update only its own status
//Hasura accepts "now()" as value to timestamp fields //Hasura accepts "now()" as value to timestamp fields
@ -20,17 +20,32 @@ export default function MyInfo() {
updateUserClientEchoTestRunningAtMeAsNow(); updateUserClientEchoTestRunningAtMeAsNow();
}; };
const [dispatchUserJoin] = useMutation(gql`
mutation UserJoin($authToken: String!, $clientType: String!) {
userJoin(
authToken: $authToken,
clientType: $clientType,
)
}
`);
const handleDispatchUserJoin = (authToken) => {
dispatchUserJoin({
variables: {
authToken: authToken,
clientType: 'HTML5',
},
});
};
const { loading, error, data } = useSubscription( const { loading, error, data } = useSubscription(
gql`subscription { gql`subscription {
user_current { user_current {
userId userId
name name
meeting { joined
name joinErrorCode
} joinErrorMessage
echoTestRunningAt
isRunningEchoTest
} }
}` }`
); );
@ -45,23 +60,23 @@ export default function MyInfo() {
{/*<th>Id</th>*/} {/*<th>Id</th>*/}
<th>userId</th> <th>userId</th>
<th>name</th> <th>name</th>
<th>Meeting</th> <th>joined</th>
<th>echoTestRunningAt</th> <th>joinErrorCode</th>
<th>isRunningEchoTest</th> <th>joinErrorMessage</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{data.user_current.map((curr) => { {data.user_current.map((curr) => {
console.log('meeting', curr); console.log('user_current', curr);
return ( return (
<tr key={curr.userId}> <tr key={curr.userId}>
<td>{curr.userId}</td> <td>{curr.userId}</td>
<td>{curr.name}</td> <td>{curr.name}</td>
<td>{curr.meeting.name}</td> <td>{curr.joined ? 'Yes' : 'No'}
<td>{curr.echoTestRunningAt} {curr.joined ? '' : <button onClick={() => handleDispatchUserJoin(userAuthToken)}>Join Now!</button>}
<button onClick={() => handleUpdateUserEchoTestRunningAt()}>Set running now!</button>
</td> </td>
<td>{curr.isRunningEchoTest ? 'Yes' : 'No'}</td> <td>{curr.joinErrorCode}</td>
<td>{curr.joinErrorMessage}</td>
</tr> </tr>
); );
})} })}

View File

@ -230,6 +230,8 @@ CREATE TABLE "user" (
"sessionToken" varchar(16), "sessionToken" varchar(16),
"authed" bool, "authed" bool,
"joined" bool, "joined" bool,
"joinErrorCode" varchar(50),
"joinErrorMessage" varchar(400),
"banned" bool, "banned" bool,
"loggedOut" bool, -- when user clicked Leave meeting button "loggedOut" bool, -- when user clicked Leave meeting button
"guest" bool, --used for dialIn "guest" bool, --used for dialIn
@ -392,6 +394,8 @@ AS SELECT "user"."userId",
"user"."role", "user"."role",
"user"."authed", "user"."authed",
"user"."joined", "user"."joined",
"user"."joinErrorCode",
"user"."joinErrorMessage",
"user"."disconnected", "user"."disconnected",
"user"."expired", "user"."expired",
"user"."ejected", "user"."ejected",

View File

@ -23,3 +23,10 @@ type Mutation {
): Boolean ): Boolean
} }
type Mutation {
userJoin(
authToken: String!
clientType: String!
): Boolean
}

View File

@ -17,6 +17,13 @@ actions:
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}' handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
permissions: permissions:
- role: bbb_client - role: bbb_client
- name: userJoin
definition:
kind: synchronous
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
permissions:
- role: pre_join_bbb_client
- role: bbb_client
custom_types: custom_types:
enums: [] enums: []
input_objects: [] input_objects: []

View File

@ -149,6 +149,8 @@ select_permissions:
- isDialIn - isDialIn
- isModerator - isModerator
- isRunningEchoTest - isRunningEchoTest
- joinErrorCode
- joinErrorMessage
- joined - joined
- locked - locked
- loggedOut - loggedOut
@ -158,16 +160,39 @@ select_permissions:
- pinned - pinned
- presenter - presenter
- raiseHand - raiseHand
- registeredAt
- registeredOn - registeredOn
- role - role
- speechLocale - speechLocale
- userId - userId
filter: filter:
_and: userId:
- meetingId: _eq: X-Hasura-UserId
_eq: X-Hasura-MeetingId - role: pre_join_bbb_client
- userId: permission:
_eq: X-Hasura-UserId columns:
- authed
- banned
- color
- disconnected
- ejectReason
- ejectReasonCode
- ejected
- expired
- extId
- guest
- joinErrorCode
- joinErrorMessage
- joined
- loggedOut
- name
- registeredAt
- registeredOn
- userId
filter:
userId:
_eq: X-Hasura-UserId
comment: ""
update_permissions: update_permissions:
- role: bbb_client - role: bbb_client
permission: permission:

View File

@ -81,12 +81,12 @@ class ConnectionController {
def builder = new JsonBuilder() def builder = new JsonBuilder()
builder { builder {
"response" "authorized" "response" "authorized"
"X-Hasura-Role" "bbb_client" "X-Hasura-Role" u ? "bbb_client" : "pre_join_bbb_client"
"X-Hasura-Locked" u.locked ? "true" : "false" "X-Hasura-Locked" u && u.locked ? "true" : "false"
"X-Hasura-LockedInMeeting" u.locked ? userSession.meetingID : "" "X-Hasura-LockedInMeeting" u && u.locked ? userSession.meetingID : ""
"X-Hasura-LockedUserId" u.locked ? userSession.internalUserId : "" "X-Hasura-LockedUserId" u && u.locked ? userSession.internalUserId : ""
"X-Hasura-ModeratorInMeeting" u.isModerator() ? userSession.meetingID : "" "X-Hasura-ModeratorInMeeting" u && u.isModerator() ? userSession.meetingID : ""
"X-Hasura-PresenterInMeeting" u.isPresenter() ? userSession.meetingID : "" "X-Hasura-PresenterInMeeting" u && u.isPresenter() ? userSession.meetingID : ""
"X-Hasura-UserId" userSession.internalUserId "X-Hasura-UserId" userSession.internalUserId
"X-Hasura-MeetingId" userSession.meetingID "X-Hasura-MeetingId" userSession.meetingID
} }