|
|
@@ -11,6 +11,62 @@ const corsHeaders = {
|
|
|
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
|
}
|
|
|
|
|
|
+const STORAGE_BUCKET = 'call-recordings'
|
|
|
+
|
|
|
+/**
|
|
|
+ * Download recording from VAPI and upload to Supabase Storage
|
|
|
+ */
|
|
|
+async function downloadAndStoreRecording(
|
|
|
+ supabase: ReturnType<typeof createClient>,
|
|
|
+ recordingUrl: string,
|
|
|
+ storeId: string,
|
|
|
+ callId: string
|
|
|
+): Promise<string | null> {
|
|
|
+ try {
|
|
|
+ console.log('Downloading recording from:', recordingUrl)
|
|
|
+
|
|
|
+ // Download the recording
|
|
|
+ const response = await fetch(recordingUrl)
|
|
|
+ if (!response.ok) {
|
|
|
+ console.error('Failed to download recording:', response.status, response.statusText)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ const audioBlob = await response.blob()
|
|
|
+
|
|
|
+ // Extract filename from URL or generate one
|
|
|
+ const urlParts = recordingUrl.split('/')
|
|
|
+ const originalFilename = urlParts[urlParts.length - 1] || `${callId}.wav`
|
|
|
+ const storagePath = `${storeId}/recordings/${originalFilename}`
|
|
|
+
|
|
|
+ console.log('Uploading recording to storage:', storagePath)
|
|
|
+
|
|
|
+ // Upload to Supabase Storage
|
|
|
+ const { data: uploadData, error: uploadError } = await supabase.storage
|
|
|
+ .from(STORAGE_BUCKET)
|
|
|
+ .upload(storagePath, audioBlob, {
|
|
|
+ contentType: 'audio/wav',
|
|
|
+ upsert: true
|
|
|
+ })
|
|
|
+
|
|
|
+ if (uploadError) {
|
|
|
+ console.error('Failed to upload recording:', uploadError)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get public URL
|
|
|
+ const { data: publicUrlData } = supabase.storage
|
|
|
+ .from(STORAGE_BUCKET)
|
|
|
+ .getPublicUrl(storagePath)
|
|
|
+
|
|
|
+ console.log('Recording stored successfully:', publicUrlData.publicUrl)
|
|
|
+ return publicUrlData.publicUrl
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error processing recording:', error)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
serve(async (req) => {
|
|
|
// Handle CORS preflight
|
|
|
if (req.method === 'OPTIONS') {
|
|
|
@@ -45,6 +101,7 @@ serve(async (req) => {
|
|
|
// Extract and validate store_id from query parameters
|
|
|
const url = new URL(req.url)
|
|
|
const storeId = url.searchParams.get('store_id')
|
|
|
+ const caller = url.searchParams.get('caller') // Optional caller number
|
|
|
|
|
|
if (!storeId) {
|
|
|
return new Response(
|
|
|
@@ -98,11 +155,34 @@ serve(async (req) => {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
- // Prepare simplified call log data
|
|
|
+ const message = payload.message
|
|
|
+ const callId = crypto.randomUUID()
|
|
|
+
|
|
|
+ // Download and store recording if available
|
|
|
+ let recordingUrl: string | null = null
|
|
|
+ if (message.stereoRecordingUrl) {
|
|
|
+ recordingUrl = await downloadAndStoreRecording(
|
|
|
+ supabase,
|
|
|
+ message.stereoRecordingUrl,
|
|
|
+ storeId,
|
|
|
+ callId
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // Prepare call log data with extracted fields
|
|
|
const callLogData = {
|
|
|
- id: crypto.randomUUID(),
|
|
|
+ id: callId,
|
|
|
store_id: storeId,
|
|
|
payload: payload,
|
|
|
+ // Extracted fields
|
|
|
+ started_at: message.startedAt || null,
|
|
|
+ ended_at: message.endedAt || null,
|
|
|
+ transcript: message.transcript || null,
|
|
|
+ recording_url: recordingUrl,
|
|
|
+ duration: message.durationSeconds || null,
|
|
|
+ costs: message.costs || null,
|
|
|
+ cost_total: message.call?.cost || message.cost || null,
|
|
|
+ caller: caller || null,
|
|
|
}
|
|
|
|
|
|
console.log('Inserting call log for store:', storeId)
|
|
|
@@ -129,6 +209,7 @@ serve(async (req) => {
|
|
|
JSON.stringify({
|
|
|
status: 'success',
|
|
|
call_log_id: data.id,
|
|
|
+ recording_stored: !!recordingUrl,
|
|
|
message: 'Call log stored successfully'
|
|
|
}),
|
|
|
{ status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|