mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-11-14 18:58:49 -05:00
feat(identity): add audioGainBoost user flag
Adds an audio_gain_boost_enabled status column (plus _at/_by audit fields) to user_status, exposed as user.audioGainBoost and copied onto sockets as socket.data.hasAudioGainBoost. The flag marks VIPs allowed to raise their personal horn/TTS/mic gain ceiling past the global admin gain settings. Grant/revoke goes through verificationService so socket flags refresh and an event is published. Resolution is restricted to verified users, so an unverified visitor sharing a nickname can never be matched. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,9 +27,11 @@ const {
|
||||
setVerified,
|
||||
setDeterrence,
|
||||
setMuted,
|
||||
setAudioGainBoost,
|
||||
listVerifiedUsers,
|
||||
listDeterredUsers,
|
||||
listMutedUsers,
|
||||
listAudioGainBoostUsers,
|
||||
resolveUserBySelector,
|
||||
userToLegacyIdentityEntry,
|
||||
} = require('../identityService');
|
||||
@@ -113,10 +115,17 @@ function refreshSocketIdentityFlags(socket) {
|
||||
operational chat/audio tools while preserving the flag for normal roles.
|
||||
*/
|
||||
socket.data.isMuted = isAdminRole(role) ? false : mutedByUser;
|
||||
/*
|
||||
Admins already control the global gain settings, so they carry the raised
|
||||
audio ceiling implicitly for the same reason they carry verification: a
|
||||
stored per-user grant should never be what gates an operator's own tools.
|
||||
*/
|
||||
socket.data.hasAudioGainBoost = verifiedByRole || Boolean(user.audioGainBoost?.enabled);
|
||||
return {
|
||||
isVerified: socket.data.isVerified,
|
||||
isDeterred: socket.data.isDeterred,
|
||||
isMuted: socket.data.isMuted,
|
||||
hasAudioGainBoost: socket.data.hasAudioGainBoost,
|
||||
matchedRecordId: user.id,
|
||||
reason: socket.data.isVerified ? 'matched' : 'no_match',
|
||||
userId: user.id,
|
||||
@@ -565,6 +574,53 @@ function setUserMute(selector, enabled, actor = null) {
|
||||
return userToLegacyIdentityEntry(user);
|
||||
}
|
||||
|
||||
/*
|
||||
Audio gain boost is a VIP-only grant. resolveUserBySelector's `includeVerified`
|
||||
flag reads as an inclusion toggle but actually relaxes the verified filter, so
|
||||
passing `false` is what restricts the nickname lookup to verified users. That
|
||||
keeps an unverified visitor from being matched by a shared nickname and handed
|
||||
a raised gain ceiling they are not eligible to use.
|
||||
*/
|
||||
function setUserAudioGainBoost(selector, enabled, actor = null) {
|
||||
const resolved = resolveUserBySelector(selector, { includeVerified: false, includeDeterred: true });
|
||||
if (resolved.error || !resolved.user) {
|
||||
throw new Error(resolved.error === 'ambiguous_nickname'
|
||||
? 'Nickname matches multiple VIPs.'
|
||||
: 'VIP not found. Only verified users can be granted an audio gain boost.');
|
||||
}
|
||||
if (!resolved.user.verified?.enabled) {
|
||||
throw new Error('Only verified VIPs can be granted an audio gain boost.');
|
||||
}
|
||||
|
||||
const user = setAudioGainBoost(resolved.user.id, {
|
||||
enabled,
|
||||
actor: actor ? String(actor) : null,
|
||||
at: Date.now(),
|
||||
});
|
||||
refreshSocketsForUser(user.id);
|
||||
publishEvent({
|
||||
source: 'moderation',
|
||||
type: enabled ? 'audio.gainBoostGranted' : 'audio.gainBoostRevoked',
|
||||
payload: {
|
||||
userId: user.id,
|
||||
cookieUserId: user.cookieUserIds[0] || null,
|
||||
nickname: user.nickname,
|
||||
actor: actor ? String(actor) : null,
|
||||
ts: Date.now(),
|
||||
},
|
||||
});
|
||||
emitChange(enabled ? 'audio_gain_boost_grant' : 'audio_gain_boost_revoke', { userId: user.id });
|
||||
return userToLegacyIdentityEntry(user);
|
||||
}
|
||||
|
||||
function grantAudioGainBoost(selector, actor = null) {
|
||||
return setUserAudioGainBoost(selector, true, actor);
|
||||
}
|
||||
|
||||
function revokeAudioGainBoost(selector, actor = null) {
|
||||
return setUserAudioGainBoost(selector, false, actor);
|
||||
}
|
||||
|
||||
function muteUser(selector, actor = null) {
|
||||
return setUserMute(selector, true, actor);
|
||||
}
|
||||
@@ -696,9 +752,13 @@ module.exports = {
|
||||
undeterUser,
|
||||
muteUser,
|
||||
unmuteUser,
|
||||
listAudioGainBoostUsers,
|
||||
grantAudioGainBoost,
|
||||
revokeAudioGainBoost,
|
||||
isVerified: (socket) => Boolean(socket?.data?.isVerified),
|
||||
isDeterred: (socket) => Boolean(socket?.data?.isDeterred),
|
||||
isMuted: (socket) => Boolean(socket?.data?.isMuted),
|
||||
hasAudioGainBoost: (socket) => Boolean(socket?.data?.hasAudioGainBoost),
|
||||
reevaluateSocketVerification,
|
||||
reevaluateSocketDeterrence,
|
||||
verificationEvents,
|
||||
|
||||
Reference in New Issue
Block a user