|
@@ -0,0 +1,412 @@
|
|
|
|
|
+import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
|
|
|
+import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
|
|
|
|
+import { wrapHandler } from '../_shared/error-handler.ts'
|
|
|
|
|
+import { getCorsHeaders, handleCorsPreflightRequest } from '../_shared/cors.ts'
|
|
|
|
|
+import { createScraperClient } from '../_shared/scraper-client.ts'
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Auto-register a user account during ShopRenter OAuth flow.
|
|
|
|
|
+ * This creates a new user account using the shop's config_email,
|
|
|
|
|
+ * logs them in automatically, and completes the store connection.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Input: { installation_id: string, phone_number_id?: string }
|
|
|
|
|
+ * Output: { success: boolean, session: object, user: object, store: object }
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+// Generate a secure random password
|
|
|
|
|
+function generateRandomPassword(length: number = 32): string {
|
|
|
|
|
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
|
|
|
|
|
+ const randomBytes = new Uint8Array(length)
|
|
|
|
|
+ crypto.getRandomValues(randomBytes)
|
|
|
|
|
+ return Array.from(randomBytes, byte => chars[byte % chars.length]).join('')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Trigger initial data sync for the store
|
|
|
|
|
+async function triggerInitialSync(
|
|
|
|
|
+ storeId: string,
|
|
|
|
|
+ supabaseUrl: string,
|
|
|
|
|
+ supabaseServiceKey: string
|
|
|
|
|
+): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ console.log(`[AutoRegister] Triggering initial sync for store ${storeId}`)
|
|
|
|
|
+
|
|
|
|
|
+ const response = await fetch(`${supabaseUrl}/functions/v1/trigger-sync`, {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Authorization': `Bearer ${supabaseServiceKey}`,
|
|
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({ store_id: storeId })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ const result = await response.json()
|
|
|
|
|
+ console.log(`[AutoRegister] Initial sync triggered successfully for store ${storeId}:`, result)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const error = await response.json()
|
|
|
|
|
+ console.error(`[AutoRegister] Failed to trigger initial sync for store ${storeId}:`, error)
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`[AutoRegister] Error triggering initial sync for store ${storeId}:`, error)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Register store with scraper service
|
|
|
|
|
+async function registerStoreWithScraper(
|
|
|
|
|
+ storeId: string,
|
|
|
|
|
+ storeUrl: string,
|
|
|
|
|
+ supabase: any
|
|
|
|
|
+): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ console.log(`[AutoRegister] Registering store ${storeId} with scraper service`)
|
|
|
|
|
+
|
|
|
|
|
+ const scraperClient = await createScraperClient()
|
|
|
|
|
+ const job = await scraperClient.registerShop(storeUrl, storeId)
|
|
|
|
|
+ console.log(`[AutoRegister] Scraper registration job created: ${job.id}`)
|
|
|
|
|
+
|
|
|
|
|
+ const webhookUrl = `${Deno.env.get('SUPABASE_URL')}/functions/v1/scraper-webhook`
|
|
|
|
|
+ try {
|
|
|
|
|
+ await scraperClient.setWebhook(storeId, webhookUrl)
|
|
|
|
|
+ console.log(`[AutoRegister] Scraper webhook configured for store ${storeId}`)
|
|
|
|
|
+ } catch (webhookError) {
|
|
|
|
|
+ console.warn(`[AutoRegister] Failed to configure scraper webhook:`, webhookError)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await scraperClient.setScheduling(storeId, true)
|
|
|
|
|
+ console.log(`[AutoRegister] Scraper scheduling enabled for store ${storeId}`)
|
|
|
|
|
+ } catch (scheduleError) {
|
|
|
|
|
+ console.warn(`[AutoRegister] Failed to enable scraper scheduling:`, scheduleError)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const { error: updateError } = await supabase
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .update({ scraper_registered: true })
|
|
|
|
|
+ .eq('id', storeId)
|
|
|
|
|
+
|
|
|
|
|
+ if (updateError) {
|
|
|
|
|
+ console.error(`[AutoRegister] Failed to update scraper registration status:`, updateError)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log(`[AutoRegister] Store ${storeId} successfully registered with scraper`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`[AutoRegister] Failed to register store with scraper:`, error)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+serve(wrapHandler('auto-register-shoprenter', async (req) => {
|
|
|
|
|
+ const origin = req.headers.get('Origin') || undefined
|
|
|
|
|
+ const corsHeaders = getCorsHeaders(origin)
|
|
|
|
|
+
|
|
|
|
|
+ if (req.method === 'OPTIONS') {
|
|
|
|
|
+ return handleCorsPreflightRequest(origin)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (req.method !== 'POST') {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ success: false, error: 'Method not allowed' }),
|
|
|
|
|
+ { status: 405, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { installation_id, phone_number_id } = await req.json()
|
|
|
|
|
+
|
|
|
|
|
+ if (!installation_id) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ success: false, error: 'Missing installation_id' }),
|
|
|
|
|
+ { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const supabaseUrl = Deno.env.get('SUPABASE_URL')!
|
|
|
|
|
+ const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
|
|
|
+ const supabase = createClient(supabaseUrl, supabaseServiceKey)
|
|
|
|
|
+
|
|
|
|
|
+ // Fetch the pending installation
|
|
|
|
|
+ const { data: pendingInstall, error: fetchError } = await supabase
|
|
|
|
|
+ .from('pending_shoprenter_installs')
|
|
|
|
|
+ .select('*')
|
|
|
|
|
+ .eq('installation_id', installation_id)
|
|
|
|
|
+ .single()
|
|
|
|
|
+
|
|
|
|
|
+ if (fetchError || !pendingInstall) {
|
|
|
|
|
+ console.error('[AutoRegister] Installation not found:', fetchError)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ success: false, error: 'Installation not found or expired' }),
|
|
|
|
|
+ { status: 404, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if installation has expired
|
|
|
|
|
+ if (new Date(pendingInstall.expires_at) < new Date()) {
|
|
|
|
|
+ console.error('[AutoRegister] Installation has expired')
|
|
|
|
|
+ await supabase
|
|
|
|
|
+ .from('pending_shoprenter_installs')
|
|
|
|
|
+ .delete()
|
|
|
|
|
+ .eq('id', pendingInstall.id)
|
|
|
|
|
+
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ success: false, error: 'Installation has expired. Please try again.' }),
|
|
|
|
|
+ { status: 410, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if we have shop_email
|
|
|
|
|
+ if (!pendingInstall.shop_email) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ success: false, error: 'No shop email available for auto-registration. Please use manual registration.' }),
|
|
|
|
|
+ { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const email = pendingInstall.shop_email
|
|
|
|
|
+ const ownerName = pendingInstall.shop_owner_name || pendingInstall.shopname
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`[AutoRegister] Processing auto-registration for ${email} (shop: ${pendingInstall.shopname})`)
|
|
|
|
|
+
|
|
|
|
|
+ // Check if email already exists in profiles
|
|
|
|
|
+ const { data: existingProfile } = await supabase
|
|
|
|
|
+ .from('profiles')
|
|
|
|
|
+ .select('id')
|
|
|
|
|
+ .eq('email', email)
|
|
|
|
|
+ .maybeSingle()
|
|
|
|
|
+
|
|
|
|
|
+ if (existingProfile) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({
|
|
|
|
|
+ success: false,
|
|
|
|
|
+ error: 'Email already registered. Please login instead.',
|
|
|
|
|
+ email_exists: true
|
|
|
|
|
+ }),
|
|
|
|
|
+ { status: 409, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Generate a secure random password (user won't know it, needs password reset for future logins)
|
|
|
|
|
+ const password = generateRandomPassword(32)
|
|
|
|
|
+
|
|
|
|
|
+ // Create user using Supabase Auth Admin API
|
|
|
|
|
+ console.log(`[AutoRegister] Creating user account for ${email}`)
|
|
|
|
|
+ const { data: authData, error: createUserError } = await supabase.auth.admin.createUser({
|
|
|
|
|
+ email: email,
|
|
|
|
|
+ password: password,
|
|
|
|
|
+ email_confirm: true, // Skip email verification - trust ShopRenter's email
|
|
|
|
|
+ user_metadata: {
|
|
|
|
|
+ full_name: ownerName,
|
|
|
|
|
+ company_name: ownerName,
|
|
|
|
|
+ user_name: ownerName.toLowerCase().replace(/\s+/g, '_'),
|
|
|
|
|
+ auto_registered: true,
|
|
|
|
|
+ auto_registered_from: 'shoprenter',
|
|
|
|
|
+ shopname: pendingInstall.shopname
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (createUserError || !authData.user) {
|
|
|
|
|
+ console.error('[AutoRegister] Failed to create user:', createUserError)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ success: false, error: 'Failed to create user account', details: createUserError?.message }),
|
|
|
|
|
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const newUser = authData.user
|
|
|
|
|
+ console.log(`[AutoRegister] User created: ${newUser.id}`)
|
|
|
|
|
+
|
|
|
|
|
+ // Create profile record
|
|
|
|
|
+ const { error: profileError } = await supabase
|
|
|
|
|
+ .from('profiles')
|
|
|
|
|
+ .upsert({
|
|
|
|
|
+ id: newUser.id,
|
|
|
|
|
+ email: email,
|
|
|
|
|
+ full_name: ownerName,
|
|
|
|
|
+ company_name: ownerName,
|
|
|
|
|
+ username: ownerName.toLowerCase().replace(/\s+/g, '_'),
|
|
|
|
|
+ is_verified: true,
|
|
|
|
|
+ created_at: new Date().toISOString(),
|
|
|
|
|
+ updated_at: new Date().toISOString()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (profileError) {
|
|
|
|
|
+ console.error('[AutoRegister] Failed to create profile:', profileError)
|
|
|
|
|
+ // Continue anyway - profile might be auto-created by trigger
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Sign in the new user to get a session
|
|
|
|
|
+ console.log(`[AutoRegister] Signing in user ${email}`)
|
|
|
|
|
+ const { data: signInData, error: signInError } = await supabase.auth.signInWithPassword({
|
|
|
|
|
+ email: email,
|
|
|
|
|
+ password: password
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (signInError || !signInData.session) {
|
|
|
|
|
+ console.error('[AutoRegister] Failed to sign in new user:', signInError)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ success: false, error: 'Account created but failed to sign in. Please use password reset.', user_created: true }),
|
|
|
|
|
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const session = signInData.session
|
|
|
|
|
+ console.log(`[AutoRegister] User signed in successfully`)
|
|
|
|
|
+
|
|
|
|
|
+ // Now create the store (same logic as complete-shoprenter-install)
|
|
|
|
|
+ const shoprenterClientId = Deno.env.get('SHOPRENTER_APP_CLIENT_ID') || Deno.env.get('SHOPRENTER_CLIENT_ID')
|
|
|
|
|
+ const shoprenterClientSecret = Deno.env.get('SHOPRENTER_APP_CLIENT_SECRET') || Deno.env.get('SHOPRENTER_CLIENT_SECRET')
|
|
|
|
|
+
|
|
|
|
|
+ const finalPhoneNumberId = phone_number_id || pendingInstall.phone_number_id
|
|
|
|
|
+
|
|
|
|
|
+ // Check if store already exists for this user
|
|
|
|
|
+ const { data: existingStore } = await supabase
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .select('id')
|
|
|
|
|
+ .eq('user_id', newUser.id)
|
|
|
|
|
+ .eq('platform_name', 'shoprenter')
|
|
|
|
|
+ .eq('store_name', pendingInstall.shopname)
|
|
|
|
|
+ .single()
|
|
|
|
|
+
|
|
|
|
|
+ let storeId: string
|
|
|
|
|
+
|
|
|
|
|
+ if (existingStore) {
|
|
|
|
|
+ // Update existing store
|
|
|
|
|
+ const { error: updateError } = await supabase
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .update({
|
|
|
|
|
+ access_token: pendingInstall.access_token,
|
|
|
|
|
+ refresh_token: pendingInstall.refresh_token,
|
|
|
|
|
+ token_expires_at: new Date(Date.now() + (pendingInstall.expires_in || 3600) * 1000).toISOString(),
|
|
|
|
|
+ scopes: pendingInstall.scopes,
|
|
|
|
|
+ is_active: true,
|
|
|
|
|
+ updated_at: new Date().toISOString(),
|
|
|
|
|
+ phone_number_id: finalPhoneNumberId || existingStore.phone_number_id
|
|
|
|
|
+ })
|
|
|
|
|
+ .eq('id', existingStore.id)
|
|
|
|
|
+
|
|
|
|
|
+ if (updateError) {
|
|
|
|
|
+ console.error('[AutoRegister] Error updating existing store:', updateError)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ storeId = existingStore.id
|
|
|
|
|
+ console.log(`[AutoRegister] Updated existing store ${storeId}`)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Create new store
|
|
|
|
|
+ const { data: newStore, error: createError } = await supabase
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .insert({
|
|
|
|
|
+ user_id: newUser.id,
|
|
|
|
|
+ platform_name: 'shoprenter',
|
|
|
|
|
+ store_name: pendingInstall.shopname,
|
|
|
|
|
+ store_url: `https://${pendingInstall.shopname}.myshoprenter.hu`,
|
|
|
|
|
+ access_token: pendingInstall.access_token,
|
|
|
|
|
+ refresh_token: pendingInstall.refresh_token,
|
|
|
|
|
+ token_expires_at: new Date(Date.now() + (pendingInstall.expires_in || 3600) * 1000).toISOString(),
|
|
|
|
|
+ scopes: pendingInstall.scopes,
|
|
|
|
|
+ is_active: true,
|
|
|
|
|
+ phone_number_id: finalPhoneNumberId,
|
|
|
|
|
+ alt_data: {
|
|
|
|
|
+ client_id: shoprenterClientId,
|
|
|
|
|
+ client_secret: shoprenterClientSecret,
|
|
|
|
|
+ connectedAt: new Date().toISOString(),
|
|
|
|
|
+ autoRegistered: true
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .select()
|
|
|
|
|
+ .single()
|
|
|
|
|
+
|
|
|
|
|
+ if (createError || !newStore) {
|
|
|
|
|
+ console.error('[AutoRegister] Error creating store:', createError)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ success: false, error: 'Failed to create store', session: session }),
|
|
|
|
|
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ storeId = newStore.id
|
|
|
|
|
+ console.log(`[AutoRegister] Created new store ${storeId}`)
|
|
|
|
|
+
|
|
|
|
|
+ // Create default sync configuration
|
|
|
|
|
+ const { error: syncConfigError } = await supabase
|
|
|
|
|
+ .from('store_sync_config')
|
|
|
|
|
+ .insert({
|
|
|
|
|
+ store_id: storeId,
|
|
|
|
|
+ enabled: true,
|
|
|
|
|
+ sync_frequency: 'hourly',
|
|
|
|
|
+ products_access_policy: 'sync',
|
|
|
|
|
+ customers_access_policy: 'api_only',
|
|
|
|
|
+ orders_access_policy: 'api_only'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (syncConfigError) {
|
|
|
|
|
+ console.error('[AutoRegister] Error creating sync config:', syncConfigError)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Update phone number assignment if provided
|
|
|
|
|
+ if (finalPhoneNumberId) {
|
|
|
|
|
+ const { error: phoneError } = await supabase
|
|
|
|
|
+ .from('phone_numbers')
|
|
|
|
|
+ .update({
|
|
|
|
|
+ assigned_to_store_id: storeId,
|
|
|
|
|
+ assigned_to_user_id: newUser.id,
|
|
|
|
|
+ assigned_at: new Date().toISOString(),
|
|
|
|
|
+ is_available: false
|
|
|
|
|
+ })
|
|
|
|
|
+ .eq('id', finalPhoneNumberId)
|
|
|
|
|
+
|
|
|
|
|
+ if (phoneError) {
|
|
|
|
|
+ console.error('[AutoRegister] Error updating phone number:', phoneError)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Clean up pending installation
|
|
|
|
|
+ await supabase
|
|
|
|
|
+ .from('pending_shoprenter_installs')
|
|
|
|
|
+ .delete()
|
|
|
|
|
+ .eq('id', pendingInstall.id)
|
|
|
|
|
+
|
|
|
|
|
+ // Clean up any related oauth_states
|
|
|
|
|
+ await supabase
|
|
|
|
|
+ .from('oauth_states')
|
|
|
|
|
+ .delete()
|
|
|
|
|
+ .eq('platform', 'shoprenter')
|
|
|
|
|
+ .eq('shopname', pendingInstall.shopname)
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`[AutoRegister] Successfully completed auto-registration for ${email}`)
|
|
|
|
|
+
|
|
|
|
|
+ // Register store with scraper service in background
|
|
|
|
|
+ registerStoreWithScraper(storeId, `https://${pendingInstall.shopname}.myshoprenter.hu`, supabase)
|
|
|
|
|
+ .then(() => console.log(`[AutoRegister] Scraper registration completed for store ${storeId}`))
|
|
|
|
|
+ .catch(err => console.error(`[AutoRegister] Scraper registration failed:`, err))
|
|
|
|
|
+
|
|
|
|
|
+ // Trigger initial data sync in background
|
|
|
|
|
+ triggerInitialSync(storeId, supabaseUrl, supabaseServiceKey)
|
|
|
|
|
+ .then(() => console.log(`[AutoRegister] Initial sync completed for store ${storeId}`))
|
|
|
|
|
+ .catch(err => console.error(`[AutoRegister] Initial sync failed:`, err))
|
|
|
|
|
+
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ session: session,
|
|
|
|
|
+ user: {
|
|
|
|
|
+ id: newUser.id,
|
|
|
|
|
+ email: newUser.email,
|
|
|
|
|
+ full_name: ownerName
|
|
|
|
|
+ },
|
|
|
|
|
+ store: {
|
|
|
|
|
+ id: storeId,
|
|
|
|
|
+ shopname: pendingInstall.shopname
|
|
|
|
|
+ },
|
|
|
|
|
+ message: 'Account created and store connected successfully. For future logins, please use password reset.'
|
|
|
|
|
+ }),
|
|
|
|
|
+ { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('[AutoRegister] Error:', error)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ success: false, error: 'Internal server error' }),
|
|
|
|
|
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}))
|