feat(events): Add user custom data to events.xml (#20566)

* Send custom user data to akka apps

* Add user custom data to registered user

* Add user custom data to user join event

* Store user custom data in Redis

* Rename userCustomData to customParameters

* Rename xml tag to userdata
This commit is contained in:
Paul Trudel 2024-06-26 11:39:39 -04:00 committed by GitHub
parent aeaf206f33
commit b697667364
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 80 additions and 34 deletions

View File

@ -63,6 +63,14 @@ trait RegisterUserReqMsgHdlr {
RegisteredUsers.add(liveMeeting.registeredUsers, regUser) RegisteredUsers.add(liveMeeting.registeredUsers, regUser)
val userCustomData: Map[String, String] = msg.body.userCustomData.map {
case (k, v) => k -> v.toString
}
if (userCustomData.nonEmpty) {
RegisteredUsers.updateUserCustomData(liveMeeting.registeredUsers, regUser, userCustomData)
}
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

@ -164,6 +164,12 @@ object RegisteredUsers {
u u
} }
def updateUserCustomData(users: RegisteredUsers, user: RegisteredUser, userCustomData: Map[String, String]): RegisteredUser = {
val u = user.modify(_.userCustomData).setTo(userCustomData)
users.save(u)
u
}
} }
class RegisteredUsers { class RegisteredUsers {
@ -202,6 +208,7 @@ case class RegisteredUser(
joined: Boolean, joined: Boolean,
banned: Boolean, banned: Boolean,
loggedOut: Boolean, loggedOut: Boolean,
lastBreakoutRoom: BreakoutRoom2x = null lastBreakoutRoom: BreakoutRoom2x = null,
userCustomData: Map[String, String] = Map.empty
) )

View File

@ -407,20 +407,21 @@ case class UserState(
guestStatus: String, guestStatus: String,
emoji: String, emoji: String,
reactionEmoji: String, reactionEmoji: String,
reactionChangedOn: Long = 0, reactionChangedOn: Long = 0,
raiseHand: Boolean, raiseHand: Boolean,
away: Boolean, away: Boolean,
locked: Boolean, locked: Boolean,
presenter: Boolean, presenter: Boolean,
avatar: String, avatar: String,
color: 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,
clientType: String, clientType: String,
pickExempted: Boolean, pickExempted: Boolean,
userLeftFlag: UserLeftFlag, userLeftFlag: UserLeftFlag,
speechLocale: String = "" speechLocale: String = "",
userCustomData: Map[String, String] = Map.empty
) )
case class UserIdAndName(id: String, name: String) case class UserIdAndName(id: String, name: String)

View File

@ -19,8 +19,11 @@
package org.bigbluebutton.core.record.events package org.bigbluebutton.core.record.events
import spray.json._
class ParticipantJoinRecordEvent extends AbstractParticipantRecordEvent { class ParticipantJoinRecordEvent extends AbstractParticipantRecordEvent {
import ParticipantJoinRecordEvent._ import ParticipantJoinRecordEvent._
import ParticipantJoinRecordEventJsonProtocol._
setEvent("ParticipantJoinEvent") setEvent("ParticipantJoinEvent")
@ -43,6 +46,10 @@ class ParticipantJoinRecordEvent extends AbstractParticipantRecordEvent {
def setRole(role: String) { def setRole(role: String) {
eventMap.put(ROLE, role) eventMap.put(ROLE, role)
} }
def setUserdata(userCustomData: Map[String, String]): Unit = {
eventMap.put(USER_DATA, userCustomData.toJson.compactPrint)
}
} }
object ParticipantJoinRecordEvent { object ParticipantJoinRecordEvent {
@ -50,4 +57,9 @@ object ParticipantJoinRecordEvent {
protected final val EXT_USER_ID = "externalUserId" protected final val EXT_USER_ID = "externalUserId"
protected final val NAME = "name" protected final val NAME = "name"
protected final val ROLE = "role" protected final val ROLE = "role"
protected final val USER_DATA = "userdata"
}
object ParticipantJoinRecordEventJsonProtocol extends DefaultJsonProtocol {
} }

View File

@ -73,7 +73,8 @@ trait HandlerHelpers extends SystemConfiguration {
color = regUser.color, color = regUser.color,
clientType = clientType, clientType = clientType,
pickExempted = false, pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0) userLeftFlag = UserLeftFlag(false, 0),
userCustomData = regUser.userCustomData
) )
} }

View File

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

View File

@ -357,6 +357,7 @@ class RedisRecorderActor(
ev.setExternalUserId(msg.body.extId) ev.setExternalUserId(msg.body.extId)
ev.setName(msg.body.name) ev.setName(msg.body.name)
ev.setRole(msg.body.role) ev.setRole(msg.body.role)
ev.setUserdata(msg.body.userCustomData)
record(msg.header.meetingId, ev.toMap.asJava) record(msg.header.meetingId, ev.toMap.asJava)
} }

View File

@ -7,7 +7,8 @@ case class RegisterUserReqMsg(
) extends BbbCoreMsg ) extends BbbCoreMsg
case class RegisterUserReqMsgBody(meetingId: String, intUserId: String, name: String, role: String, case class RegisterUserReqMsgBody(meetingId: String, intUserId: String, name: String, role: String,
extUserId: String, authToken: String, avatarURL: String, extUserId: String, authToken: String, avatarURL: String,
guest: Boolean, authed: Boolean, guestStatus: String, excludeFromDashboard: Boolean) guest: Boolean, authed: Boolean, guestStatus: String, excludeFromDashboard: Boolean,
userCustomData: Map[String, AnyRef])
object UserRegisteredRespMsg { val NAME = "UserRegisteredRespMsg" } object UserRegisteredRespMsg { val NAME = "UserRegisteredRespMsg" }
case class UserRegisteredRespMsg( case class UserRegisteredRespMsg(
@ -89,23 +90,24 @@ case class UserJoinedMeetingEvtMsg(
body: UserJoinedMeetingEvtMsgBody body: UserJoinedMeetingEvtMsgBody
) extends BbbCoreMsg ) extends BbbCoreMsg
case class UserJoinedMeetingEvtMsgBody( case class UserJoinedMeetingEvtMsgBody(
intId: String, intId: String,
extId: String, extId: String,
name: String, name: String,
role: String, role: String,
guest: Boolean, guest: Boolean,
authed: Boolean, authed: Boolean,
guestStatus: String, guestStatus: String,
emoji: String, emoji: String,
reactionEmoji: String, reactionEmoji: String,
raiseHand: Boolean, raiseHand: Boolean,
away: Boolean, away: Boolean,
pin: Boolean, pin: Boolean,
presenter: Boolean, presenter: Boolean,
locked: Boolean, locked: Boolean,
avatar: String, avatar: String,
color: String, color: String,
clientType: String clientType: String,
userCustomData: Map[String, String]
) )
/** /**

View File

@ -426,10 +426,19 @@ public class MeetingService implements MessageListener {
} }
private void processRegisterUser(RegisterUser message) { private void processRegisterUser(RegisterUser message) {
Map<String, Object> userCustomData = null;
Meeting meeting = meetings.get(message.meetingID);
if (meeting != null) {
userCustomData = meeting.getUserCustomData(message.externUserID);
}
if (userCustomData == null) {
userCustomData = new HashMap<>();
}
gw.registerUser(message.meetingID, gw.registerUser(message.meetingID,
message.internalUserId, message.fullname, message.role, message.internalUserId, message.fullname, message.role,
message.externUserID, message.authToken, message.avatarURL, message.guest, message.externUserID, message.authToken, message.avatarURL, message.guest,
message.authed, message.guestStatus, message.excludeFromDashboard); message.authed, message.guestStatus, message.excludeFromDashboard, userCustomData);
} }
public Meeting getMeeting(String meetingId) { public Meeting getMeeting(String meetingId) {

View File

@ -51,7 +51,7 @@ public interface IBbbWebApiGWApp {
void registerUser(String meetingID, String internalUserId, String fullname, String role, void registerUser(String meetingID, String internalUserId, String fullname, String role,
String externUserID, String authToken, String avatarURL, String externUserID, String authToken, String avatarURL,
Boolean guest, Boolean authed, String guestStatus, Boolean excludeFromDashboard); Boolean guest, Boolean authed, String guestStatus, Boolean excludeFromDashboard, Map<String, Object> userCustomData);
void guestWaitingLeft(String meetingID, String internalUserId); void guestWaitingLeft(String meetingID, String internalUserId);
void destroyMeeting(DestroyMeetingMessage msg); void destroyMeeting(DestroyMeetingMessage msg);

View File

@ -263,16 +263,18 @@ class BbbWebApiGWApp(
def registerUser(meetingId: String, intUserId: String, name: String, def registerUser(meetingId: String, intUserId: String, name: String,
role: String, extUserId: String, authToken: String, avatarURL: String, role: String, extUserId: String, authToken: String, avatarURL: String,
guest: java.lang.Boolean, authed: java.lang.Boolean, guest: java.lang.Boolean, authed: java.lang.Boolean,
guestStatus: String, excludeFromDashboard: java.lang.Boolean): Unit = { guestStatus: String, excludeFromDashboard: java.lang.Boolean, userCustomData: java.util.Map[String, AnyRef]): Unit = {
// meetingManagerActorRef ! new RegisterUser(meetingId = meetingId, intUserId = intUserId, name = name, // meetingManagerActorRef ! new RegisterUser(meetingId = meetingId, intUserId = intUserId, name = name,
// role = role, extUserId = extUserId, authToken = authToken, avatarURL = avatarURL, // role = role, extUserId = extUserId, authToken = authToken, avatarURL = avatarURL,
// guest = guest, authed = authed) // guest = guest, authed = authed)
val ucd = userCustomData.asScala.toMap
val regUser = new RegisterUser(meetingId = meetingId, intUserId = intUserId, name = name, val regUser = new RegisterUser(meetingId = meetingId, intUserId = intUserId, name = name,
role = role, extUserId = extUserId, authToken = authToken, avatarURL = avatarURL, role = role, extUserId = extUserId, authToken = authToken, avatarURL = avatarURL,
guest = guest.booleanValue(), authed = authed.booleanValue(), guestStatus = guestStatus, guest = guest.booleanValue(), authed = authed.booleanValue(), guestStatus = guestStatus,
excludeFromDashboard = excludeFromDashboard) excludeFromDashboard = excludeFromDashboard, ucd)
val event = MsgBuilder.buildRegisterUserRequestToAkkaApps(regUser) val event = MsgBuilder.buildRegisterUserRequestToAkkaApps(regUser)
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event)) msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event))

View File

@ -41,7 +41,7 @@ object MsgBuilder {
val body = RegisterUserReqMsgBody(meetingId = msg.meetingId, intUserId = msg.intUserId, val body = RegisterUserReqMsgBody(meetingId = msg.meetingId, intUserId = msg.intUserId,
name = msg.name, role = msg.role, extUserId = msg.extUserId, authToken = msg.authToken, name = msg.name, role = msg.role, extUserId = msg.extUserId, authToken = msg.authToken,
avatarURL = msg.avatarURL, guest = msg.guest, authed = msg.authed, guestStatus = msg.guestStatus, avatarURL = msg.avatarURL, guest = msg.guest, authed = msg.authed, guestStatus = msg.guestStatus,
excludeFromDashboard = msg.excludeFromDashboard) excludeFromDashboard = msg.excludeFromDashboard, msg.userCustomData)
val req = RegisterUserReqMsg(header, body) val req = RegisterUserReqMsg(header, body)
BbbCommonEnvCoreMsg(envelope, req) BbbCommonEnvCoreMsg(envelope, req)
} }

View File

@ -17,7 +17,7 @@ case class CreateBreakoutRoomMsg(meetingId: String, parentMeetingId: String,
case class AddUserSession(token: String, session: UserSession) case class AddUserSession(token: String, session: UserSession)
case class RegisterUser(meetingId: String, intUserId: String, name: String, role: String, case class RegisterUser(meetingId: String, intUserId: String, name: String, role: String,
extUserId: String, authToken: String, avatarURL: String, extUserId: String, authToken: String, avatarURL: String,
guest: Boolean, authed: Boolean, guestStatus: String, excludeFromDashboard: Boolean) guest: Boolean, authed: Boolean, guestStatus: String, excludeFromDashboard: Boolean, userCustomData: Map[String, AnyRef])
case class CreateMeetingMsg(defaultProps: DefaultProps) case class CreateMeetingMsg(defaultProps: DefaultProps)

View File

@ -29,7 +29,7 @@ trait ToAkkaAppsSendersTrait extends SystemConfiguration {
val body = RegisterUserReqMsgBody(meetingId = msg.meetingId, intUserId = msg.intUserId, val body = RegisterUserReqMsgBody(meetingId = msg.meetingId, intUserId = msg.intUserId,
name = msg.name, role = msg.role, extUserId = msg.extUserId, authToken = msg.authToken, name = msg.name, role = msg.role, extUserId = msg.extUserId, authToken = msg.authToken,
avatarURL = msg.avatarURL, guest = msg.guest, authed = msg.authed, guestStatus = msg.guestStatus, avatarURL = msg.avatarURL, guest = msg.guest, authed = msg.authed, guestStatus = msg.guestStatus,
excludeFromDashboard = msg.excludeFromDashboard) excludeFromDashboard = msg.excludeFromDashboard, msg.userCustomData)
val req = RegisterUserReqMsg(header, body) val req = RegisterUserReqMsg(header, body)
val message = BbbCommonEnvCoreMsg(envelope, req) val message = BbbCommonEnvCoreMsg(envelope, req)
sendToBus(message) sendToBus(message)

View File

@ -33,6 +33,7 @@ export default async function addUser(meetingId, userData) {
color: String, color: String,
pin: Boolean, pin: Boolean,
clientType: String, clientType: String,
userCustomData: Match.Optional(Match.Any),
}); });
const userId = user.intId; const userId = user.intId;
@ -43,6 +44,8 @@ export default async function addUser(meetingId, userData) {
}; };
const Meeting = await Meetings.findOneAsync({ meetingId }); const Meeting = await Meetings.findOneAsync({ meetingId });
const { userCustomData, ...restOfUser } = user;
const userInfos = { const userInfos = {
meetingId, meetingId,
sortName: lowercaseTrim(user.name), sortName: lowercaseTrim(user.name),
@ -57,7 +60,7 @@ export default async function addUser(meetingId, userData) {
responseDelay: 0, responseDelay: 0,
loggedOut: false, loggedOut: false,
left: false, left: false,
...flat(user), ...flat(restOfUser),
}; };
const modifier = { const modifier = {