feat(db): complete async call-site conversion — Phase 3 done, validated on SQLite
The full sync→async conversion is complete and green on the SQLite backend. Every DB call across the app now awaits the async adapter, so the identical code runs on Postgres at cutover. Converted (this commit finishes Phase 3): - session.js: currentUser/apiKeyFromReq async → 63 route awaits + WS + static. - routes.js: all ~250 R.* awaited; DTO helpers (namesFor, avatarsFor, buildMsgDTO, buildPollDTO, reactionsForMessage, postSystemMessage, pushGroupUpdate, issueRefreshToken, provisionFromBizgaze) made async; every `.map(x=>buildDTO(x))` restructured to `await Promise.all(...map(async...))` preserving order; `.filter` predicates that hit the DB moved to an `asyncFilter` helper; chained `R.x.y(...).length/.map/.filter` wrapped as `(await R.x.y(...)).method`; stream upload handlers (recording/transcript/attachment) made async. - calls.js / signaling.js: all call/meeting fns async; leaveMeeting AWAITS persistCallHistory + finalizeTranscript BEFORE endCallByRoom (ordering matters — fire-and-forget would race the map teardown); WS handle()/cleanup() async with .catch guards. - static.js: authAttachment(Raw) async (the .some carrier check became a loop), handleGet async; server.js dispatch catches handler rejections → 500 not a hang. - media.js backfill, push.js, reminders.js, webhooks.js await their repo calls. Validation on DB_BACKEND=sqlite: db-smoke 22/22; legacy e2e 80 checks pass with zero FAILs (throws only at a PRE-EXISTING WS lobby-drift assertion, unrelated). Every server file `node --check` clean. Still on the branch — master untouched. Next: Phase 5 (pg backend + ~7 dialect queries + data migration + Docker Postgres + cutover), then merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+13
-13
@@ -11,7 +11,7 @@ const now = () => Date.now();
|
||||
const pairKey = (a, b) => [a, b].sort().join('|');
|
||||
|
||||
// Resolve a room's meeting context (group / scheduled meeting / title) for labelling recordings.
|
||||
function meetingContext(room) {
|
||||
async function meetingContext(room) {
|
||||
const ctx = { groupId: null, meetingId: null, title: 'Meeting' };
|
||||
try {
|
||||
const sched = await R.scheduledMeetings.byCode(room);
|
||||
@@ -26,12 +26,12 @@ function meetingContext(room) {
|
||||
// Save the FULL shared conversation transcript as a PRIVATE copy for each subscriber. onlyUserId
|
||||
// finalizes just that subscriber (on their leave / opt-out); omit to flush all remaining (room end).
|
||||
// Must run BEFORE endCallByRoom (which clears the room→meeting maps meetingContext relies on).
|
||||
function finalizeTranscript(room, onlyUserId) {
|
||||
async function finalizeTranscript(room, onlyUserId) {
|
||||
const subs = transcriptSubs.get(room); if (!subs || !subs.size) { if (!onlyUserId) { transcriptBuffers.delete(room); transcriptSubs.delete(room); } return; }
|
||||
const buf = transcriptBuffers.get(room) || [];
|
||||
const ids = onlyUserId ? (subs.has(onlyUserId) ? [onlyUserId] : []) : [...subs];
|
||||
if (ids.length && buf.length) {
|
||||
const ctx = meetingContext(room);
|
||||
const ctx = await meetingContext(room);
|
||||
const lines = buf.map((s) => { const ts = new Date(s.t); const hh = String(ts.getHours()).padStart(2, '0'), mm = String(ts.getMinutes()).padStart(2, '0'); return '[' + hh + ':' + mm + '] ' + s.speaker + ': ' + s.text; });
|
||||
const body = ctx.title + ' — transcript\n' + new Date(buf[0].t).toLocaleString() + '\n\n' + lines.join('\n') + '\n';
|
||||
for (const uid of ids) {
|
||||
@@ -49,17 +49,17 @@ function finalizeTranscript(room, onlyUserId) {
|
||||
|
||||
function fmtDur(ms) { const s = Math.max(0, Math.round(ms / 1000)); const m = Math.floor(s / 60); return m ? (m + 'm ' + (s % 60) + 's') : (s + 's'); }
|
||||
|
||||
function broadcast(group, evt) { try { for (const mid of await R.conversations.members(group)) CHAT.pushToUser(mid, evt); } catch (_) {} }
|
||||
async function broadcast(group, evt) { try { for (const mid of await R.conversations.members(group)) CHAT.pushToUser(mid, evt); } catch (_) {} }
|
||||
|
||||
// Post a centered activity line into the group (system sender → no ping on clients).
|
||||
function postSystem(group, teamId, text) {
|
||||
async function postSystem(group, teamId, text) {
|
||||
const id = A.id();
|
||||
await R.messages.send({ id, teamId, senderId: '__system__', recipientId: '', body: text, conversationId: group });
|
||||
const m = await R.messages.byId(id);
|
||||
broadcast(group, { type: 'chat-message', message: { id: m.id, from: '__system__', conversation_id: group, body: m.body, created_at: m.created_at, system: true } });
|
||||
}
|
||||
|
||||
function startGroupCall(group, teamId, user) {
|
||||
async function startGroupCall(group, teamId, user) {
|
||||
const existing = groupCalls.get(group);
|
||||
if (existing) return { room: existing.room, active: true, already: true };
|
||||
let room; do { room = A.numericCode(6); } while (meetingRooms.has(room));
|
||||
@@ -68,27 +68,27 @@ function startGroupCall(group, teamId, user) {
|
||||
// Log the call as a meeting so it appears under Past meetings (history) with the group name.
|
||||
try { const hid = A.id(); await R.scheduledMeetings.create({ id: hid, teamId, groupId: group, roomCode: room, title: 'Group call', description: null, scheduledAt: now(), createdBy: user.id }); call.historyId = hid; call.teamId = teamId; } catch (_) {}
|
||||
groupCalls.set(group, call); roomToGroupCall.set(room, group); roomHost.set(room, user.id); // creator = host
|
||||
postSystem(group, teamId, '📞 ' + call.startedByName + ' started a group call');
|
||||
postSystem(group, teamId, '📞 ' + call.startedByName + ' started a group call').catch(() => {});
|
||||
let gName = 'Group'; try { const g = await R.conversations.byId(group); if (g) gName = g.name || 'Group'; } catch (_) {}
|
||||
broadcast(group, { type: 'group-call', group, active: true, room, by: user.id, startedByName: call.startedByName, groupName: gName });
|
||||
return { room, active: true };
|
||||
}
|
||||
|
||||
// Called from signaling when a mesh room empties — ends the group call if this room was one.
|
||||
function endGroupCallByRoom(room) {
|
||||
async function endGroupCallByRoom(room) {
|
||||
const group = roomToGroupCall.get(room);
|
||||
if (!group) return;
|
||||
const call = groupCalls.get(group);
|
||||
roomToGroupCall.delete(room); groupCalls.delete(group); roomHost.delete(room);
|
||||
if (call) {
|
||||
let teamId = call.teamId; try { const g = await R.conversations.byId(group); if (g) { teamId = g.team_id; postSystem(group, g.team_id, '📞 Group call ended · ' + fmtDur(now() - call.startedAt)); } } catch (_) {}
|
||||
let teamId = call.teamId; try { const g = await R.conversations.byId(group); if (g) { teamId = g.team_id; postSystem(group, g.team_id, '📞 Group call ended · ' + fmtDur(now() - call.startedAt)).catch(() => {}); } } catch (_) {}
|
||||
if (call.historyId && teamId) { try { await R.scheduledMeetings.end(call.historyId, teamId); } catch (_) {} } // mark the history row past
|
||||
broadcast(group, { type: 'group-call', group, active: false, room });
|
||||
}
|
||||
}
|
||||
|
||||
// 1:1 (DM) call. Notifies both parties (state + a chat line) so the callee sees "Join".
|
||||
function startDmCall(me, otherId, teamId) {
|
||||
async function startDmCall(me, otherId, teamId) {
|
||||
const key = pairKey(me.id, otherId);
|
||||
const existing = dmCalls.get(key);
|
||||
if (existing) return { room: existing.room, active: true, already: true };
|
||||
@@ -118,7 +118,7 @@ function startDmCall(me, otherId, teamId) {
|
||||
return { room, active: true };
|
||||
}
|
||||
|
||||
function endDmCallByRoom(room, silent) {
|
||||
async function endDmCallByRoom(room, silent) {
|
||||
const key = roomToDmCall.get(room); if (!key) return;
|
||||
const call = dmCalls.get(key);
|
||||
roomToDmCall.delete(room); dmCalls.delete(key); roomHost.delete(room);
|
||||
@@ -143,10 +143,10 @@ function markDmAnswered(room, userId) {
|
||||
if (userId && userId !== call.startedBy && !call.answered) { call.answered = true; call.answeredAt = now(); if (call.ringTimer) { try { clearTimeout(call.ringTimer); } catch (_) {} call.ringTimer = null; } }
|
||||
}
|
||||
// Called from signaling when any mesh room empties.
|
||||
function endCallByRoom(room) { endGroupCallByRoom(room); endDmCallByRoom(room); }
|
||||
async function endCallByRoom(room) { await endGroupCallByRoom(room); await endDmCallByRoom(room); }
|
||||
|
||||
// Callee declines a 1:1 call: post "Call declined" into the DM, drop the waiting caller, end it.
|
||||
function declineDmCall(room, byUser) {
|
||||
async function declineDmCall(room, byUser) {
|
||||
const key = roomToDmCall.get(room); if (!key) return { ok: false };
|
||||
const call = dmCalls.get(key); if (!call) return { ok: false };
|
||||
// #8: if this user has ALREADY accepted on another device (they're in the room), this is just the
|
||||
|
||||
Reference in New Issue
Block a user