Files
OwnCord/Server/db/queries/sqlite/voice.sql
T
J3vbandClaude 36be31db43 fix: 11 defects from bughunt sweep across server, db and client (#1382)
* fix(ws): 1 defect(s) (OC-0001)

* fix(db): 1 defect(s) (OC-0002)

sanitizeFTSQuery filtered only characters, so FTS5's bareword boolean
keywords (AND, OR, NOT) reached MATCH as operators; a query in an
invalid operator position raised "fts5: syntax error" instead of
returning results. Drop those bareword tokens after sanitizing.

* fix(dm): 1 defect(s) (OC-0004)

* fix(ws): 1 defect(s) (OC-0005)

* fix(voice): 1 defect(s) (OC-0006)

Count the shared voice_max_video budget in streams rather than rows: a
single user publishing both camera and screenshare consumed one slot while
producing two live streams, letting a channel over-admit up to 2N streams
against an N-stream cap.

* fix(client): 2 defect(s) (OC-0007, OC-0009)

OC-0007: mark the active channel loading before invalidating its message
window on a full-ready resync, so MessageList shows the spinner instead
of the empty-channel state for the duration of the refetch.

OC-0009: fan USER_UPDATE renames out to voiceStore.voiceUsers, which
keeps its own frozen username copy, so the voice roster no longer shows
a stale name for the rest of the call.

* fix(admin): 1 defect(s) (OC-0010)

* fix(identity): 1 defect(s) (OC-0011)

* fix(ws): 1 defect(s) (OC-0003)

The public half of an invisible user's presence (PresenceOthersEvent, and
BroadcastPresence's own mapped payload) went out via broadcastExcludeLow on
the low-priority queue - the ephemeral, unsequenced, drop-on-overflow
transport built for typing indicators - while every other source of the same
user's presence shares the normal-priority queue. That split one user's
presence across two per-client FIFOs with different durability and different
drain order (writePump drains normal strictly before low), so a frame could
land out of order against a later connect/disconnect presence frame, or be
silently dropped with no replay recovery.

Adds Hub.BroadcastToAllExcept, which routes through the same h.broadcast
channel and seqMu-serialized deliverBroadcast as BroadcastToAll, carrying an
excludeUserID that deliverBroadcast applies via pubsub.Publish(TopicGlobal,
msg, excludeUserID).

* fix(ws): 1 defect(s) (OC-0008)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-16 17:06:11 +02:00

123 lines
4.9 KiB
SQL

-- server_muted / server_deafened are deliberately absent from both upserts'
-- reset lists: a moderator-imposed mute must survive a channel switch, which
-- reaches the ON CONFLICT branch. It is scoped to the voice session:
-- leaving voice deletes the row, so a rejoin starts clean.
-- name: JoinVoiceChannel :exec
INSERT INTO voice_states (user_id, channel_id, muted, deafened, speaking, camera, screenshare, joined_at)
VALUES (?, ?, 0, 0, 0, 0, 0, ?)
ON CONFLICT(user_id) DO UPDATE SET
channel_id = excluded.channel_id,
muted = 0,
deafened = 0,
speaking = 0,
camera = 0,
screenshare = 0,
joined_at = excluded.joined_at;
-- name: JoinVoiceChannelIfCapacity :execresult
INSERT INTO voice_states (user_id, channel_id, muted, deafened, speaking, camera, screenshare, joined_at)
SELECT ?, ?, 0, 0, 0, 0, 0, ?
WHERE (SELECT COUNT(*) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) < ?
ON CONFLICT(user_id) DO UPDATE SET
channel_id = excluded.channel_id,
muted = 0,
deafened = 0,
speaking = 0,
camera = 0,
screenshare = 0,
joined_at = excluded.joined_at;
-- name: LeaveVoiceChannel :exec
DELETE FROM voice_states WHERE user_id = ?;
-- name: LeaveVoiceChannelIfMatch :execresult
DELETE FROM voice_states WHERE user_id = ? AND channel_id = ? AND joined_at = ?;
-- name: GetUserVoiceState :one
SELECT vs.user_id, vs.channel_id, u.username,
vs.muted, vs.deafened, vs.speaking,
vs.camera, vs.screenshare,
vs.server_muted, vs.server_deafened, vs.joined_at
FROM voice_states vs
JOIN users u ON u.id = vs.user_id
WHERE vs.user_id = ?;
-- name: GetChannelVoiceStates :many
SELECT vs.user_id, vs.channel_id, u.username,
vs.muted, vs.deafened, vs.speaking,
vs.camera, vs.screenshare,
vs.server_muted, vs.server_deafened, vs.joined_at
FROM voice_states vs
JOIN users u ON u.id = vs.user_id
WHERE vs.channel_id = ?
ORDER BY vs.joined_at ASC;
-- name: GetAllVoiceStates :many
SELECT vs.user_id, vs.channel_id, u.username,
vs.muted, vs.deafened, vs.speaking,
vs.camera, vs.screenshare,
vs.server_muted, vs.server_deafened, vs.joined_at
FROM voice_states vs
JOIN users u ON u.id = vs.user_id
ORDER BY vs.channel_id, vs.joined_at ASC;
-- name: UpdateVoiceMute :exec
UPDATE voice_states SET muted = ? WHERE user_id = ?;
-- name: UpdateVoiceDeafen :exec
UPDATE voice_states SET deafened = ? WHERE user_id = ?;
-- name: UpdateVoiceCamera :exec
UPDATE voice_states SET camera = ? WHERE user_id = ?;
-- name: UpdateVoiceScreenshare :exec
UPDATE voice_states SET screenshare = ? WHERE user_id = ?;
-- Scoped to channel_id as well as user_id: the moderator's authorization is
-- checked against a channel snapshot several round trips before this write
-- lands, so an unscoped `WHERE user_id = ?` would follow the target onto
-- whatever channel their row points at by then -- including a DM call the
-- moderator was never authorized against (OC-0005). :execresult so the
-- caller can tell a real no-op (target moved) from a normal apply.
-- name: ApplyVoiceServerMute :execresult
UPDATE voice_states SET server_muted = 1, muted = 1 WHERE user_id = ? AND channel_id = ?;
-- name: ClearVoiceServerMute :execresult
UPDATE voice_states SET server_muted = 0 WHERE user_id = ? AND channel_id = ?;
-- name: ApplyVoiceServerDeafen :execresult
UPDATE voice_states SET server_deafened = 1, deafened = 1 WHERE user_id = ? AND channel_id = ?;
-- name: ClearVoiceServerDeafen :execresult
UPDATE voice_states SET server_deafened = 0 WHERE user_id = ? AND channel_id = ?;
-- Camera and screenshare share one voice_max_video budget, counted in
-- STREAMS, not rows: a channel capped at N simultaneous video streams must
-- not let a camera publish ignore screenshare occupants (or vice versa,
-- OC-0023), and a single user with both flags set must consume two of the N
-- slots, not one (OC-0006) -- so both gates sum `vs2.camera + vs2.screenshare`
-- across the channel's rows rather than counting rows where either is set.
-- The enabling user's own bit is still 0 at gate time, so no self-exclusion
-- term is needed.
-- name: EnableCameraIfUnderLimit :execresult
UPDATE voice_states SET camera = 1
WHERE voice_states.user_id = ? AND voice_states.channel_id = ?
AND (SELECT COALESCE(SUM(vs2.camera), 0) + COALESCE(SUM(vs2.screenshare), 0) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) < sqlc.arg(max_video);
-- name: EnableScreenshareIfUnderLimit :execresult
UPDATE voice_states SET screenshare = 1
WHERE voice_states.user_id = ? AND voice_states.channel_id = ?
AND (SELECT COALESCE(SUM(vs2.camera), 0) + COALESCE(SUM(vs2.screenshare), 0) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) < sqlc.arg(max_video);
-- name: ClearVoiceState :exec
DELETE FROM voice_states WHERE user_id = ?;
-- name: ClearAllVoiceStates :exec
DELETE FROM voice_states;
-- name: CountActiveCameras :one
SELECT COUNT(*) FROM voice_states WHERE channel_id = ? AND camera = 1;