|
|
@@ -1,5 +1,9 @@
|
|
|
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
|
|
+import {
|
|
|
+ requireInternalApiKey,
|
|
|
+ createInternalApiKeyErrorResponse,
|
|
|
+} from '../_shared/internal-api-key-auth.ts'
|
|
|
|
|
|
const corsHeaders = {
|
|
|
'Access-Control-Allow-Origin': '*',
|
|
|
@@ -78,6 +82,65 @@ serve(async (req) => {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+ // Create Supabase admin client for authentication
|
|
|
+ const supabaseUrl = Deno.env.get('SUPABASE_URL')!
|
|
|
+ const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
|
+ const supabase = createClient(supabaseUrl, supabaseKey)
|
|
|
+
|
|
|
+ // Validate internal API key
|
|
|
+ const authResult = await requireInternalApiKey(req, supabase)
|
|
|
+ if (!authResult.valid) {
|
|
|
+ const errorResponse = createInternalApiKeyErrorResponse(authResult)
|
|
|
+ // Add CORS headers to error response
|
|
|
+ return new Response(errorResponse.body, {
|
|
|
+ status: errorResponse.status,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract and validate webshop_id from query parameters
|
|
|
+ const url = new URL(req.url)
|
|
|
+ const webshopId = url.searchParams.get('webshop_id')
|
|
|
+
|
|
|
+ if (!webshopId) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'Missing required query parameter: webshop_id' }),
|
|
|
+ { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validate UUID format
|
|
|
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
|
+ if (!uuidRegex.test(webshopId)) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'Invalid webshop_id format. Must be a valid UUID.' }),
|
|
|
+ { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify webshop exists and is active
|
|
|
+ const { data: store, error: storeError } = await supabase
|
|
|
+ .from('stores')
|
|
|
+ .select('id, store_name, is_active')
|
|
|
+ .eq('id', webshopId)
|
|
|
+ .single()
|
|
|
+
|
|
|
+ if (storeError || !store) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'Webshop not found', webshop_id: webshopId }),
|
|
|
+ { status: 404, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!store.is_active) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'Webshop is inactive', webshop_id: webshopId }),
|
|
|
+ { status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`Processing VAPI webhook for webshop: ${store.store_name} (${webshopId})`)
|
|
|
+
|
|
|
// Parse the webhook payload
|
|
|
const payload: VAPIWebhookPayload = await req.json()
|
|
|
console.log('Received VAPI webhook:', JSON.stringify(payload, null, 2))
|
|
|
@@ -94,11 +157,6 @@ serve(async (req) => {
|
|
|
const { message } = payload
|
|
|
const { timestamp, analysis, artifact, call } = message
|
|
|
|
|
|
- // Create Supabase client with service role key (bypasses RLS for webhook)
|
|
|
- const supabaseUrl = Deno.env.get('SUPABASE_URL')!
|
|
|
- const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
|
- const supabase = createClient(supabaseUrl, supabaseKey)
|
|
|
-
|
|
|
// Extract transcript from messages
|
|
|
let transcript = artifact?.transcript || ''
|
|
|
if (!transcript && artifact?.messages) {
|
|
|
@@ -133,6 +191,9 @@ serve(async (req) => {
|
|
|
// Primary key - generate UUID for the call log
|
|
|
id: crypto.randomUUID(),
|
|
|
|
|
|
+ // Associate with webshop
|
|
|
+ store_id: webshopId,
|
|
|
+
|
|
|
// VAPI-specific fields
|
|
|
vapi_call_id: call?.id || crypto.randomUUID(),
|
|
|
vapi_timestamp: timestamp,
|