feat(stats): voice-time and message leaderboards per space
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
Voice stays get their own table rather than joining the audit log: that table records points in time, a call is an interval, and pairing join/leave point events would leave every query guessing at joins whose leave never arrived. Sessions are opened and closed inside joinRoom/leaveCurrentRoom rather than at the seven call sites that reach them, so no path can be missed, and destroyRoom closes them too — it bypasses leaveCurrentRoom and would otherwise leak open rows. A restart leaves sessions open with an unknowable end time. They are closed at startedAt, discarding that time rather than inventing it: crediting the gap would hand someone hours they never spent, and the numbers are the point. Mirrors the existing users.status sweep on boot. Only closed sessions count, so a figure does not move on every refresh. Bars scale to the leader, not the total — with five people every share of a total looks identical. Statistics are readable by any member, since they are the group's own numbers; the audit log, which names who did what, stays on MANAGE_SPACE.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import type { WebSocket } from 'ws';
|
||||
import { verifyJwt } from '../utils/auth.js';
|
||||
import { openVoiceSession, closeVoiceSession } from '../utils/voiceSessions.js';
|
||||
import { getDb, schema } from '../db/index.js';
|
||||
import { eq, and, or, inArray, isNull, desc, sql } from 'drizzle-orm';
|
||||
import { handleClientEvent } from './events.js';
|
||||
@@ -714,6 +715,16 @@ class ConnectionManager {
|
||||
|
||||
room.participants.add(userId);
|
||||
this.userToRoom.set(userId, roomId);
|
||||
|
||||
// Recorded here rather than at the seven call sites that lead into voice:
|
||||
// every path — join, move, DM call, reconnect — funnels through this
|
||||
// method, so hooking it cannot miss one.
|
||||
openVoiceSession({
|
||||
spaceId: room.roomType === 'space' ? (room.metadata as SpaceRoomMeta).spaceId : null,
|
||||
channelId: roomId,
|
||||
userId,
|
||||
});
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
@@ -746,6 +757,8 @@ class ConnectionManager {
|
||||
const room = this.leaveRoom(roomId, userId);
|
||||
if (!room) return null;
|
||||
|
||||
closeVoiceSession(userId);
|
||||
|
||||
return { roomId, room };
|
||||
}
|
||||
|
||||
@@ -757,6 +770,9 @@ class ConnectionManager {
|
||||
const displaced: string[] = [];
|
||||
for (const userId of room.participants) {
|
||||
this.userToRoom.delete(userId);
|
||||
// Destroying a room bypasses leaveCurrentRoom, so these sessions would
|
||||
// otherwise stay open until the next restart swept them away.
|
||||
closeVoiceSession(userId);
|
||||
displaced.push(userId);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user