@gotcos/glasses-server 6.21.31 → 6.21.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +16 -5
- package/CHANGELOG.md +40 -0
- package/README.md +22 -0
- package/package.json +1 -1
- package/server/lib/adaptive-playback-audio.ts +384 -0
- package/server/lib/cos-operations-meetings.ts +230 -12
- package/server/lib/meeting-audio-archive.ts +41 -8
- package/server/lib/meeting-store.ts +8 -1
- package/server/routes/health.ts +12 -1
- package/server/routes/meeting.ts +121 -17
- package/server/routes/meetings.ts +91 -4
package/server/routes/meeting.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { durableAtomicWriteFileSync } from '../lib/atomic-fs.js'
|
|
|
11
11
|
import { appendCorrection, appliedCorrections, pendingCorrections } from '../lib/meeting-corrections.js'
|
|
12
12
|
import { isSampleFromSession, untraceableSampleCount } from '../lib/training-audio-provenance.js'
|
|
13
13
|
import { sendAudioFile } from '../lib/send-audio.js'
|
|
14
|
+
import { adaptivePlaybackAudio } from '../lib/adaptive-playback-audio.js'
|
|
14
15
|
import { chunkDiagnostics } from '../lib/chunk-embedding-diagnostics.js'
|
|
15
16
|
import { errMsg } from '../lib/utils.js'
|
|
16
17
|
import { confirmedLabels } from '../lib/meeting-corrections.js'
|
|
@@ -100,6 +101,7 @@ import {
|
|
|
100
101
|
getSessionStartTime,
|
|
101
102
|
getSessionTranscript,
|
|
102
103
|
getMeetingSessionStatus,
|
|
104
|
+
getTranscriptionSessionLiveness,
|
|
103
105
|
hasSessionAudio,
|
|
104
106
|
moveSessionAudioToPending,
|
|
105
107
|
type IndexedTranscriptChunk,
|
|
@@ -110,6 +112,7 @@ import {
|
|
|
110
112
|
import { getServerInstanceId } from '../lib/server-instance-id.js'
|
|
111
113
|
import {
|
|
112
114
|
cosOperationsMeetingsConfigured,
|
|
115
|
+
findDirectLibraryMeetingBySessionId,
|
|
113
116
|
findCosOperationsMeetingBySessionId,
|
|
114
117
|
resolveCosOperationsDir,
|
|
115
118
|
} from '../lib/cos-operations-meetings.js'
|
|
@@ -154,6 +157,10 @@ function cosOpsPipelineConfigured(): boolean {
|
|
|
154
157
|
return Boolean(process.env.COS_SCRIPTS_DIR?.trim())
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
function requestedRecordMatches(requested: unknown, expected: string): boolean {
|
|
161
|
+
return requested == null || requested === '' || requested === expected
|
|
162
|
+
}
|
|
163
|
+
|
|
157
164
|
interface MeetingSessionSource {
|
|
158
165
|
getTranscript(sessionId: string): string | null
|
|
159
166
|
getStartTime(sessionId: string): number | null
|
|
@@ -710,15 +717,16 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
710
717
|
const operations = cosOperationsMeetingsConfigured()
|
|
711
718
|
? findCosOperationsMeetingBySessionId(sessionId)
|
|
712
719
|
: null
|
|
713
|
-
const
|
|
714
|
-
|
|
720
|
+
const direct = operations ? null : findDirectLibraryMeetingBySessionId(sessionId)
|
|
721
|
+
const saved = operations || direct ? null : store.findBySessionId(sessionId)
|
|
722
|
+
if (!operations && !direct && !saved) {
|
|
715
723
|
res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
|
|
716
724
|
return
|
|
717
725
|
}
|
|
718
|
-
const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
|
|
719
|
-
const title = operations?.title ?? saved!.title
|
|
720
|
-
const domain = operations?.domain ?? saved!.domain
|
|
721
|
-
const filename = operations?.filename ?? saved!.filename
|
|
726
|
+
const sidecarPath = operations?.sidecarPath ?? direct?.sidecarPath ?? saved!.sidecarPath
|
|
727
|
+
const title = operations?.title ?? direct?.title ?? saved!.title
|
|
728
|
+
const domain = operations?.domain ?? direct?.domain ?? saved!.domain
|
|
729
|
+
const filename = operations?.filename ?? direct?.filename ?? saved!.filename
|
|
722
730
|
|
|
723
731
|
let chunks: unknown
|
|
724
732
|
try {
|
|
@@ -768,7 +776,11 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
768
776
|
title,
|
|
769
777
|
domain,
|
|
770
778
|
filename,
|
|
771
|
-
source: operations ? 'cos_operations' : 'standalone_recordings',
|
|
779
|
+
source: operations ? 'cos_operations' : direct ? 'direct_library' : 'standalone_recordings',
|
|
780
|
+
recordId: operations
|
|
781
|
+
? `ops:${operations.domain}:${operations.month}:${operations.filename}`
|
|
782
|
+
: direct?.recordId ?? `standalone:${sessionId}`,
|
|
783
|
+
mutable: direct == null,
|
|
772
784
|
...(saved ? { durationMin: saved.durationMin } : {}),
|
|
773
785
|
...review,
|
|
774
786
|
})
|
|
@@ -810,14 +822,15 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
810
822
|
const operations = cosOperationsMeetingsConfigured()
|
|
811
823
|
? findCosOperationsMeetingBySessionId(sessionId)
|
|
812
824
|
: null
|
|
813
|
-
const
|
|
814
|
-
|
|
825
|
+
const direct = operations ? null : findDirectLibraryMeetingBySessionId(sessionId)
|
|
826
|
+
const saved = operations || direct ? null : store.findBySessionId(sessionId)
|
|
827
|
+
if (!operations && !direct && !saved) {
|
|
815
828
|
res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
|
|
816
829
|
return
|
|
817
830
|
}
|
|
818
|
-
const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
|
|
819
|
-
const title = operations?.title ?? saved!.title
|
|
820
|
-
const mdPath = operations?.meetingPath ?? sidecarPath.replace(/\.g2-chunks\.json$/, '.md')
|
|
831
|
+
const sidecarPath = operations?.sidecarPath ?? direct?.sidecarPath ?? saved!.sidecarPath
|
|
832
|
+
const title = operations?.title ?? direct?.title ?? saved!.title
|
|
833
|
+
const mdPath = operations?.meetingPath ?? direct?.meetingPath ?? sidecarPath.replace(/\.g2-chunks\.json$/, '.md')
|
|
821
834
|
|
|
822
835
|
let sidecar: Record<string, unknown>
|
|
823
836
|
try {
|
|
@@ -907,7 +920,7 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
907
920
|
// Which business this is. The sibling /speakers route has always carried
|
|
908
921
|
// this and this one dropped it, so a personal 1:1 about someone's
|
|
909
922
|
// compensation was byte-identical to a marketing sync.
|
|
910
|
-
domain: operations?.domain ?? saved?.domain ?? '',
|
|
923
|
+
domain: operations?.domain ?? direct?.domain ?? saved?.domain ?? '',
|
|
911
924
|
// Transcript actually captured, whether or not a write-up exists yet. 140
|
|
912
925
|
// of 399 real sidecars have no .md, and the fallback text claimed there was
|
|
913
926
|
// no transcript while holding one.
|
|
@@ -941,6 +954,11 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
941
954
|
// no write-up yet.
|
|
942
955
|
capturedChars: clip.capturedChars,
|
|
943
956
|
domain: clip.domain,
|
|
957
|
+
source: operations ? 'cos_operations' : direct ? 'direct_library' : 'standalone_recordings',
|
|
958
|
+
recordId: operations
|
|
959
|
+
? `ops:${operations.domain}:${operations.month}:${operations.filename}`
|
|
960
|
+
: direct?.recordId ?? `standalone:${sessionId}`,
|
|
961
|
+
mutable: direct == null,
|
|
944
962
|
// So the panel can warn above the write-up, not just the clipboard.
|
|
945
963
|
removedNames: clip.removed,
|
|
946
964
|
coverage,
|
|
@@ -988,11 +1006,32 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
988
1006
|
const operations = cosOperationsMeetingsConfigured()
|
|
989
1007
|
? findCosOperationsMeetingBySessionId(sessionId)
|
|
990
1008
|
: null
|
|
1009
|
+
const direct = operations ? null : findDirectLibraryMeetingBySessionId(sessionId)
|
|
1010
|
+
if (direct) {
|
|
1011
|
+
if (!requestedRecordMatches(req.body?.recordId, direct.recordId || '')) {
|
|
1012
|
+
res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
|
|
1013
|
+
return
|
|
1014
|
+
}
|
|
1015
|
+
res.status(409).json({
|
|
1016
|
+
error: 'This meeting comes from a read-only library',
|
|
1017
|
+
reason: 'direct_library_read_only',
|
|
1018
|
+
recordId: direct.recordId,
|
|
1019
|
+
mutable: false,
|
|
1020
|
+
})
|
|
1021
|
+
return
|
|
1022
|
+
}
|
|
991
1023
|
const saved = operations ? null : store.findBySessionId(sessionId)
|
|
992
1024
|
if (!operations && !saved) {
|
|
993
1025
|
res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
|
|
994
1026
|
return
|
|
995
1027
|
}
|
|
1028
|
+
const selectedRecordId = operations
|
|
1029
|
+
? `ops:${operations.domain}:${operations.month}:${operations.filename}`
|
|
1030
|
+
: `standalone:${sessionId}`
|
|
1031
|
+
if (!requestedRecordMatches(req.body?.recordId, selectedRecordId)) {
|
|
1032
|
+
res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
|
|
1033
|
+
return
|
|
1034
|
+
}
|
|
996
1035
|
const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
|
|
997
1036
|
const meetingPath = operations?.meetingPath ?? saved!.filepath
|
|
998
1037
|
const title = operations?.title ?? saved!.title
|
|
@@ -1162,11 +1201,32 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
1162
1201
|
const operations = cosOperationsMeetingsConfigured()
|
|
1163
1202
|
? findCosOperationsMeetingBySessionId(sessionId)
|
|
1164
1203
|
: null
|
|
1204
|
+
const direct = operations ? null : findDirectLibraryMeetingBySessionId(sessionId)
|
|
1205
|
+
if (direct) {
|
|
1206
|
+
if (!requestedRecordMatches(req.body?.recordId, direct.recordId || '')) {
|
|
1207
|
+
res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
|
|
1208
|
+
return
|
|
1209
|
+
}
|
|
1210
|
+
res.status(409).json({
|
|
1211
|
+
error: 'This meeting comes from a read-only library',
|
|
1212
|
+
reason: 'direct_library_read_only',
|
|
1213
|
+
recordId: direct.recordId,
|
|
1214
|
+
mutable: false,
|
|
1215
|
+
})
|
|
1216
|
+
return
|
|
1217
|
+
}
|
|
1165
1218
|
const saved = operations ? null : store.findBySessionId(sessionId)
|
|
1166
1219
|
if (!operations && !saved) {
|
|
1167
1220
|
res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
|
|
1168
1221
|
return
|
|
1169
1222
|
}
|
|
1223
|
+
const selectedRecordId = operations
|
|
1224
|
+
? `ops:${operations.domain}:${operations.month}:${operations.filename}`
|
|
1225
|
+
: `standalone:${sessionId}`
|
|
1226
|
+
if (!requestedRecordMatches(req.body?.recordId, selectedRecordId)) {
|
|
1227
|
+
res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
|
|
1228
|
+
return
|
|
1229
|
+
}
|
|
1170
1230
|
const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
|
|
1171
1231
|
|
|
1172
1232
|
// Refuse to confirm a label the meeting does not actually carry. Otherwise
|
|
@@ -1240,11 +1300,32 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
1240
1300
|
const operations = cosOperationsMeetingsConfigured()
|
|
1241
1301
|
? findCosOperationsMeetingBySessionId(sessionId)
|
|
1242
1302
|
: null
|
|
1303
|
+
const direct = operations ? null : findDirectLibraryMeetingBySessionId(sessionId)
|
|
1304
|
+
if (direct) {
|
|
1305
|
+
if (!requestedRecordMatches(req.body?.recordId, direct.recordId || '')) {
|
|
1306
|
+
res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
|
|
1307
|
+
return
|
|
1308
|
+
}
|
|
1309
|
+
res.status(409).json({
|
|
1310
|
+
error: 'This meeting comes from a read-only library',
|
|
1311
|
+
reason: 'direct_library_read_only',
|
|
1312
|
+
recordId: direct.recordId,
|
|
1313
|
+
mutable: false,
|
|
1314
|
+
})
|
|
1315
|
+
return
|
|
1316
|
+
}
|
|
1243
1317
|
const saved = operations ? null : store.findBySessionId(sessionId)
|
|
1244
1318
|
if (!operations && !saved) {
|
|
1245
1319
|
res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
|
|
1246
1320
|
return
|
|
1247
1321
|
}
|
|
1322
|
+
const selectedRecordId = operations
|
|
1323
|
+
? `ops:${operations.domain}:${operations.month}:${operations.filename}`
|
|
1324
|
+
: `standalone:${sessionId}`
|
|
1325
|
+
if (!requestedRecordMatches(req.body?.recordId, selectedRecordId)) {
|
|
1326
|
+
res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
|
|
1327
|
+
return
|
|
1328
|
+
}
|
|
1248
1329
|
const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
|
|
1249
1330
|
const meetingPath = operations?.meetingPath ?? saved!.filepath
|
|
1250
1331
|
const title = operations?.title ?? saved!.title
|
|
@@ -1423,7 +1504,7 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
1423
1504
|
// voice. Retention is 7 days (see meeting-audio-archive), so this answers with
|
|
1424
1505
|
// 404 + a reason once the window has passed rather than pretending the audio
|
|
1425
1506
|
// was never there.
|
|
1426
|
-
router.get('/meeting/:sessionId/audio/:chunkIndex', (req, res) => {
|
|
1507
|
+
router.get('/meeting/:sessionId/audio/:chunkIndex', async (req, res) => {
|
|
1427
1508
|
res.set('Cache-Control', 'private, no-store')
|
|
1428
1509
|
const sessionId = String(req.params.sessionId ?? '')
|
|
1429
1510
|
if (!/^[A-Za-z0-9:_-]{3,96}$/.test(sessionId)) {
|
|
@@ -1440,8 +1521,8 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
1440
1521
|
// fallback there is nothing to play on any meeting predating 6.21.18 — while
|
|
1441
1522
|
// 72 hours of unidentified-voice audio is sitting right there, and an
|
|
1442
1523
|
// unidentified voice is exactly what a reviewer needs to hear.
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1524
|
+
const archivedPath = meetingAudioChunkPath(sessionId, chunkIndex)
|
|
1525
|
+
const path = archivedPath ?? extAudioChunkPath(sessionId, chunkIndex)
|
|
1445
1526
|
if (!path) {
|
|
1446
1527
|
const retained = [...new Set([
|
|
1447
1528
|
...listMeetingAudioChunks(sessionId),
|
|
@@ -1460,7 +1541,30 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
1460
1541
|
})
|
|
1461
1542
|
return
|
|
1462
1543
|
}
|
|
1463
|
-
|
|
1544
|
+
// Canary scope is intentionally narrow: only the week-retained immutable
|
|
1545
|
+
// archive gets a derived playback copy. Legacy ext-audio remains byte-for-
|
|
1546
|
+
// byte raw. `?raw=1` is the authenticated A/B and emergency per-request
|
|
1547
|
+
// escape hatch; the COS Control toggle is the machine-wide rollback.
|
|
1548
|
+
const liveRecording = getTranscriptionSessionLiveness().live > 0
|
|
1549
|
+
const playback = archivedPath && req.query.raw !== '1' && !liveRecording
|
|
1550
|
+
? await adaptivePlaybackAudio(archivedPath, {
|
|
1551
|
+
// Admission is not enough: if a meeting starts while FFmpeg is
|
|
1552
|
+
// working, the replay job is preempted and this request hears raw.
|
|
1553
|
+
shouldAbort: () => getTranscriptionSessionLiveness().live > 0,
|
|
1554
|
+
})
|
|
1555
|
+
: { path, mode: 'raw' as const, profile: null }
|
|
1556
|
+
res.set('X-COS-Audio-Playback', playback.mode)
|
|
1557
|
+
const bypassReason = 'reason' in playback ? playback.reason : undefined
|
|
1558
|
+
const bypass = liveRecording
|
|
1559
|
+
? 'live_recording'
|
|
1560
|
+
: bypassReason === 'live_recording'
|
|
1561
|
+
? 'live_recording'
|
|
1562
|
+
: bypassReason === 'cleanup_busy'
|
|
1563
|
+
? 'cleanup_busy'
|
|
1564
|
+
: null
|
|
1565
|
+
if (bypass) res.set('X-COS-Audio-Bypass', bypass)
|
|
1566
|
+
if (playback.profile) res.set('X-COS-Audio-Profile', playback.profile)
|
|
1567
|
+
sendAudioFile(res, playback.path)
|
|
1464
1568
|
})
|
|
1465
1569
|
|
|
1466
1570
|
/**
|
|
@@ -4,10 +4,44 @@ import { Router } from 'express'
|
|
|
4
4
|
import { getMeetingStore, MeetingStore, MeetingStoreError } from '../lib/meeting-store.js'
|
|
5
5
|
import {
|
|
6
6
|
cosOperationsMeetingsConfigured,
|
|
7
|
+
getDirectLibraryMeetingDetail,
|
|
7
8
|
getCosOperationsMeetingDetail,
|
|
9
|
+
listDirectLibraryMeetings,
|
|
8
10
|
listCosOperationsMeetings,
|
|
9
|
-
|
|
11
|
+
resolveMeetingLibrary,
|
|
10
12
|
} from '../lib/cos-operations-meetings.js'
|
|
13
|
+
import type { MeetingMeta } from '../lib/meeting-store.js'
|
|
14
|
+
|
|
15
|
+
function withStandaloneIdentity(meeting: MeetingMeta): MeetingMeta {
|
|
16
|
+
return {
|
|
17
|
+
...meeting,
|
|
18
|
+
librarySource: 'standalone_recordings',
|
|
19
|
+
recordId: `standalone:${meeting.sessionId || `${meeting.domain}:${meeting.month}:${meeting.filename}`}`,
|
|
20
|
+
mutable: true,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function mergeMeetingSources(groups: MeetingMeta[][], limit: number): MeetingMeta[] {
|
|
25
|
+
const seenSessions = new Set<string>()
|
|
26
|
+
const seenExact = new Set<string>()
|
|
27
|
+
const merged: MeetingMeta[] = []
|
|
28
|
+
for (const group of groups) {
|
|
29
|
+
for (const meeting of group) {
|
|
30
|
+
if (meeting.sessionId) {
|
|
31
|
+
if (seenSessions.has(meeting.sessionId)) continue
|
|
32
|
+
seenSessions.add(meeting.sessionId)
|
|
33
|
+
} else {
|
|
34
|
+
const exact = `${meeting.librarySource || ''}:${meeting.domain}:${meeting.month}:${meeting.filename}`
|
|
35
|
+
if (seenExact.has(exact)) continue
|
|
36
|
+
seenExact.add(exact)
|
|
37
|
+
}
|
|
38
|
+
merged.push(meeting)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
merged.sort((a, b) => `${b.date}T${b.time || '00:00'}`.localeCompare(`${a.date}T${a.time || '00:00'}`)
|
|
42
|
+
|| b.filename.localeCompare(a.filename))
|
|
43
|
+
return merged.slice(0, Math.min(Math.max(limit, 1), 50))
|
|
44
|
+
}
|
|
11
45
|
|
|
12
46
|
export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): Router {
|
|
13
47
|
const router = Router()
|
|
@@ -20,17 +54,54 @@ export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): R
|
|
|
20
54
|
const domain = typeof req.query.domain === 'string' ? req.query.domain : 'all'
|
|
21
55
|
res.set('Cache-Control', 'private, no-store')
|
|
22
56
|
|
|
23
|
-
|
|
57
|
+
const library = resolveMeetingLibrary()
|
|
58
|
+
if (library.layout === 'invalid_explicit_root') {
|
|
59
|
+
res.status(409).json({
|
|
60
|
+
error: 'Configured meetings library is unavailable or malformed',
|
|
61
|
+
reason: 'invalid_explicit_root',
|
|
62
|
+
layout: library.layout,
|
|
63
|
+
warnings: library.warnings,
|
|
64
|
+
})
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (library.layout === 'direct') {
|
|
69
|
+
const operations = cosOperationsMeetingsConfigured()
|
|
70
|
+
? listCosOperationsMeetings({ limit: 50, domain })
|
|
71
|
+
: []
|
|
72
|
+
const direct = domain === 'all' || domain === 'library'
|
|
73
|
+
? listDirectLibraryMeetings({ limit: 50 })
|
|
74
|
+
: []
|
|
75
|
+
const standalone = store.list({ limit: 50, domain }).map(withStandaloneIdentity)
|
|
76
|
+
const meetings = mergeMeetingSources([operations, direct, standalone], limit)
|
|
77
|
+
res.json({
|
|
78
|
+
meetings,
|
|
79
|
+
source: operations.length > 0 ? 'mixed_library' : 'direct_library',
|
|
80
|
+
layout: 'direct',
|
|
81
|
+
root: library.root,
|
|
82
|
+
rootFingerprint: library.rootFingerprint,
|
|
83
|
+
meetingCount: meetings.length,
|
|
84
|
+
warnings: library.warnings,
|
|
85
|
+
})
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (library.layout === 'multi_domain') {
|
|
24
90
|
const meetings = listCosOperationsMeetings({ limit, domain })
|
|
25
91
|
res.json({
|
|
26
92
|
meetings,
|
|
27
93
|
source: 'cos_operations',
|
|
28
|
-
|
|
94
|
+
layout: 'multi_domain',
|
|
95
|
+
root: library.root,
|
|
96
|
+
rootFingerprint: library.rootFingerprint,
|
|
97
|
+
meetingCount: meetings.length,
|
|
98
|
+
warnings: library.warnings,
|
|
29
99
|
})
|
|
30
100
|
return
|
|
31
101
|
}
|
|
32
102
|
|
|
33
|
-
|
|
103
|
+
const meetings = store.list({ limit, domain }).map(withStandaloneIdentity)
|
|
104
|
+
res.json({ meetings, source: 'standalone_recordings', layout: 'standalone', meetingCount: meetings.length })
|
|
34
105
|
} catch (error) {
|
|
35
106
|
sendMeetingStoreError(res, error)
|
|
36
107
|
}
|
|
@@ -49,6 +120,14 @@ export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): R
|
|
|
49
120
|
}
|
|
50
121
|
res.set('Cache-Control', 'private, no-store')
|
|
51
122
|
|
|
123
|
+
if (domain === 'library') {
|
|
124
|
+
const detail = getDirectLibraryMeetingDetail(month, filename)
|
|
125
|
+
if (detail) {
|
|
126
|
+
res.json(detail)
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
52
131
|
if (cosOperationsMeetingsConfigured()) {
|
|
53
132
|
const detail = getCosOperationsMeetingDetail(domain, month, filename)
|
|
54
133
|
if (detail) {
|
|
@@ -71,6 +150,14 @@ export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): R
|
|
|
71
150
|
try {
|
|
72
151
|
res.set('Cache-Control', 'private, no-store')
|
|
73
152
|
|
|
153
|
+
if (req.params.domain === 'library') {
|
|
154
|
+
const detail = getDirectLibraryMeetingDetail(req.params.month, req.params.filename)
|
|
155
|
+
if (detail) {
|
|
156
|
+
res.json(detail)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
74
161
|
if (cosOperationsMeetingsConfigured()) {
|
|
75
162
|
const detail = getCosOperationsMeetingDetail(
|
|
76
163
|
req.params.domain,
|