/** * GET /api/telephony/recordings?source=lead|user|partner&recordId=[&numbers=a,b] * Call recordings (FreePBX) for a contact, matched by its phone numbers, one row * per recording file, newest first. Staff only (system user + non-limited role). * Never throws to the client: { status, available, numbers, rows }. */ import { gateFromEvent } from '../../../utils/chatAccess' import { findRecordings, statRecordings, hasTranscript, pbxAvailable, digitsOf } from '../../../utils/telephony/recordings' const contactNumbers = async (event: any, source: string, recordId: number): Promise => { const token = String(await getTokenHelper(event) || '') const nums: string[] = [] if (source === 'lead' || source === 'user') { const u: any = await fetchHelper(event, `models/ad_user/${recordId}`, 'GET', token, null) nums.push(u?.Phone, u?.Phone2, u?.phone, u?.phone2) } else if (source === 'partner') { const users: any = await fetchHelper(event, `models/ad_user?$filter=C_BPartner_ID eq ${recordId}&$top=50`, 'GET', token, null) for (const u of users?.records || []) nums.push(u?.Phone, u?.Phone2) const locs: any = await fetchHelper(event, `models/c_bpartner_location?$filter=C_BPartner_ID eq ${recordId}&$top=50`, 'GET', token, null) for (const l of locs?.records || []) nums.push(l?.Phone, l?.Phone2) } return nums.map((n) => String(n || '').trim()).filter((n) => digitsOf(n).length >= 6) } export default defineEventHandler(async (event) => { try { await gateFromEvent(event) } catch (err: any) { return { status: err?.statusCode || 403, available: false, numbers: [], rows: [] } } const q = getQuery(event) const source = String(q.source || 'lead') const recordId = Number(q.recordId) || 0 const extra = String(q.numbers || '').split(',').map((s) => s.trim()).filter((n) => digitsOf(n).length >= 6) try { let numbers: string[] = [...extra] if (recordId && ['lead', 'user', 'partner'].includes(source)) { try { numbers.push(...await contactNumbers(event, source, recordId)) } catch (err: any) { try { const t = await refreshTokenHelper(event); const u: any = await fetchHelper(event, `models/ad_user/${recordId}`, 'GET', t, null); numbers.push(u?.Phone, u?.Phone2) } catch { /* numbers stay as given */ } } } numbers = [...new Set(numbers.map((n) => String(n || '').trim()).filter((n) => digitsOf(n).length >= 6))] if (!numbers.length) return { status: 200, available: true, numbers, rows: [] } const { available, rows } = await findRecordings(numbers) if (!available) return { status: 200, available: false, numbers, rows: [], message: 'CDR not configured' } if (!pbxAvailable()) return { status: 200, available: false, numbers, rows: [], message: 'Recordings service not configured' } const stat = await statRecordings(rows.map((r) => r.file)) const out = rows .filter((r) => stat[r.file] && Number(stat[r.file]!.size) > 1000) // 44-byte files = never-answered legs .map((r) => ({ ...r, size: Number(stat[r.file]!.size), hasTranscript: hasTranscript(r.file) })) return { status: 200, available: true, numbers, rows: out } } catch (err: any) { console.warn('[recordings] list failed:', err?.message || err) return { status: 502, available: false, numbers: [], rows: [], message: err?.message || 'Recordings unavailable' } } })