|
@@ -0,0 +1,502 @@
|
|
|
|
|
+import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
|
|
|
+import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
|
|
|
|
+import {
|
|
|
|
|
+ fetchProducts,
|
|
|
|
|
+ fetchOrders,
|
|
|
|
|
+ fetchCustomers,
|
|
|
|
|
+ WooCommerceProduct,
|
|
|
|
|
+ WooCommerceOrder,
|
|
|
|
|
+ WooCommerceCustomer
|
|
|
|
|
+} from '../_shared/woocommerce-client.ts'
|
|
|
|
|
+
|
|
|
|
|
+const corsHeaders = {
|
|
|
|
|
+ 'Access-Control-Allow-Origin': '*',
|
|
|
|
|
+ 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Rate limiter class to prevent API throttling
|
|
|
|
|
+class RateLimiter {
|
|
|
|
|
+ private queue: Array<() => Promise<any>> = []
|
|
|
|
|
+ private running = 0
|
|
|
|
|
+ private maxConcurrent = 5
|
|
|
|
|
+ private delayMs = 200 // 5 requests per second
|
|
|
|
|
+
|
|
|
|
|
+ async add<T>(fn: () => Promise<T>): Promise<T> {
|
|
|
|
|
+ while (this.running >= this.maxConcurrent) {
|
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, this.delayMs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.running++
|
|
|
|
|
+ try {
|
|
|
|
|
+ const result = await fn()
|
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, this.delayMs))
|
|
|
|
|
+ return result
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.running--
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Retry logic for API calls
|
|
|
|
|
+async function fetchWithRetry<T>(
|
|
|
|
|
+ fn: () => Promise<T>,
|
|
|
|
|
+ maxRetries = 3,
|
|
|
|
|
+ retryDelay = 1000
|
|
|
|
|
+): Promise<T> {
|
|
|
|
|
+ for (let i = 0; i < maxRetries; i++) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return await fn()
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ const isLastAttempt = i === maxRetries - 1
|
|
|
|
|
+
|
|
|
|
|
+ // Check if error is rate limiting
|
|
|
|
|
+ if (error.message?.includes('429') || error.message?.includes('Rate limit')) {
|
|
|
|
|
+ if (!isLastAttempt) {
|
|
|
|
|
+ const delay = retryDelay * Math.pow(2, i) // Exponential backoff
|
|
|
|
|
+ console.log(`[WooCommerce] Rate limited, retrying in ${delay}ms...`)
|
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, delay))
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // For other errors or last attempt, throw
|
|
|
|
|
+ if (isLastAttempt) {
|
|
|
|
|
+ throw error
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Retry other errors with shorter delay
|
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, retryDelay))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ throw new Error('Max retries exceeded')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Sync products from WooCommerce
|
|
|
|
|
+async function syncProducts(
|
|
|
|
|
+ storeId: string,
|
|
|
|
|
+ supabaseAdmin: any,
|
|
|
|
|
+ rateLimiter: RateLimiter
|
|
|
|
|
+): Promise<{ synced: number; errors: number }> {
|
|
|
|
|
+ console.log('[WooCommerce] Syncing products...')
|
|
|
|
|
+ let synced = 0
|
|
|
|
|
+ let errors = 0
|
|
|
|
|
+ let page = 1
|
|
|
|
|
+ const perPage = 25
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ while (true) {
|
|
|
|
|
+ const products = await rateLimiter.add(() =>
|
|
|
|
|
+ fetchWithRetry(() => fetchProducts(storeId, page, perPage))
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (!products || products.length === 0) {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Map and upsert products
|
|
|
|
|
+ const productsToCache = products.map((product: WooCommerceProduct) => ({
|
|
|
|
|
+ store_id: storeId,
|
|
|
|
|
+ wc_product_id: product.id.toString(),
|
|
|
|
|
+ name: product.name,
|
|
|
|
|
+ sku: product.sku || null,
|
|
|
|
|
+ price: parseFloat(product.price) || 0,
|
|
|
|
|
+ currency: 'USD', // Default, should be from store settings
|
|
|
|
|
+ description: product.description || null,
|
|
|
|
|
+ short_description: product.short_description || null,
|
|
|
|
|
+ stock_quantity: product.stock_quantity,
|
|
|
|
|
+ stock_status: product.stock_status,
|
|
|
|
|
+ type: product.type || 'simple',
|
|
|
|
|
+ categories: product.categories || [],
|
|
|
|
|
+ images: product.images || [],
|
|
|
|
|
+ raw_data: product,
|
|
|
|
|
+ last_synced_at: new Date().toISOString()
|
|
|
|
|
+ }))
|
|
|
|
|
+
|
|
|
|
|
+ const { error: upsertError } = await supabaseAdmin
|
|
|
|
|
+ .from('woocommerce_products_cache')
|
|
|
|
|
+ .upsert(productsToCache, {
|
|
|
|
|
+ onConflict: 'store_id,wc_product_id'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (upsertError) {
|
|
|
|
|
+ console.error('[WooCommerce] Error caching products:', upsertError)
|
|
|
|
|
+ errors += productsToCache.length
|
|
|
|
|
+ } else {
|
|
|
|
|
+ synced += productsToCache.length
|
|
|
|
|
+ console.log(`[WooCommerce] Cached ${productsToCache.length} products (page ${page})`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if there are more pages
|
|
|
|
|
+ if (products.length < perPage) {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ page++
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`[WooCommerce] Products sync complete: ${synced} synced, ${errors} errors`)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('[WooCommerce] Product sync error:', error)
|
|
|
|
|
+ errors++
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return { synced, errors }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Sync orders from WooCommerce
|
|
|
|
|
+async function syncOrders(
|
|
|
|
|
+ storeId: string,
|
|
|
|
|
+ supabaseAdmin: any,
|
|
|
|
|
+ rateLimiter: RateLimiter
|
|
|
|
|
+): Promise<{ synced: number; errors: number }> {
|
|
|
|
|
+ console.log('[WooCommerce] Syncing orders...')
|
|
|
|
|
+ let synced = 0
|
|
|
|
|
+ let errors = 0
|
|
|
|
|
+ let page = 1
|
|
|
|
|
+ const perPage = 25
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ while (true) {
|
|
|
|
|
+ const orders = await rateLimiter.add(() =>
|
|
|
|
|
+ fetchWithRetry(() => fetchOrders(storeId, page, perPage))
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (!orders || orders.length === 0) {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Map and upsert orders
|
|
|
|
|
+ const ordersToCache = orders.map((order: WooCommerceOrder) => ({
|
|
|
|
|
+ store_id: storeId,
|
|
|
|
|
+ wc_order_id: order.id.toString(),
|
|
|
|
|
+ order_number: order.number || order.id.toString(),
|
|
|
|
|
+ status: order.status,
|
|
|
|
|
+ total: parseFloat(order.total) || 0,
|
|
|
|
|
+ currency: order.currency || 'USD',
|
|
|
|
|
+ customer_name: `${order.billing?.first_name || ''} ${order.billing?.last_name || ''}`.trim(),
|
|
|
|
|
+ customer_email: order.billing?.email || null,
|
|
|
|
|
+ line_items: order.line_items || [],
|
|
|
|
|
+ billing_address: order.billing || null,
|
|
|
|
|
+ shipping_address: order.shipping || null,
|
|
|
|
|
+ created_at: order.date_created || new Date().toISOString(),
|
|
|
|
|
+ raw_data: order,
|
|
|
|
|
+ last_synced_at: new Date().toISOString()
|
|
|
|
|
+ }))
|
|
|
|
|
+
|
|
|
|
|
+ const { error: upsertError } = await supabaseAdmin
|
|
|
|
|
+ .from('woocommerce_orders_cache')
|
|
|
|
|
+ .upsert(ordersToCache, {
|
|
|
|
|
+ onConflict: 'store_id,wc_order_id'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (upsertError) {
|
|
|
|
|
+ console.error('[WooCommerce] Error caching orders:', upsertError)
|
|
|
|
|
+ errors += ordersToCache.length
|
|
|
|
|
+ } else {
|
|
|
|
|
+ synced += ordersToCache.length
|
|
|
|
|
+ console.log(`[WooCommerce] Cached ${ordersToCache.length} orders (page ${page})`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if there are more pages
|
|
|
|
|
+ if (orders.length < perPage) {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ page++
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`[WooCommerce] Orders sync complete: ${synced} synced, ${errors} errors`)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('[WooCommerce] Order sync error:', error)
|
|
|
|
|
+ errors++
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return { synced, errors }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Sync customers from WooCommerce
|
|
|
|
|
+async function syncCustomers(
|
|
|
|
|
+ storeId: string,
|
|
|
|
|
+ supabaseAdmin: any,
|
|
|
|
|
+ rateLimiter: RateLimiter
|
|
|
|
|
+): Promise<{ synced: number; errors: number }> {
|
|
|
|
|
+ console.log('[WooCommerce] Syncing customers...')
|
|
|
|
|
+ let synced = 0
|
|
|
|
|
+ let errors = 0
|
|
|
|
|
+ let page = 1
|
|
|
|
|
+ const perPage = 25
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ while (true) {
|
|
|
|
|
+ const customers = await rateLimiter.add(() =>
|
|
|
|
|
+ fetchWithRetry(() => fetchCustomers(storeId, page, perPage))
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (!customers || customers.length === 0) {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Map and upsert customers
|
|
|
|
|
+ const customersToCache = customers.map((customer: WooCommerceCustomer) => ({
|
|
|
|
|
+ store_id: storeId,
|
|
|
|
|
+ wc_customer_id: customer.id.toString(),
|
|
|
|
|
+ email: customer.email || null,
|
|
|
|
|
+ first_name: customer.first_name || null,
|
|
|
|
|
+ last_name: customer.last_name || null,
|
|
|
|
|
+ username: customer.username || null,
|
|
|
|
|
+ billing_address: customer.billing || null,
|
|
|
|
|
+ shipping_address: customer.shipping || null,
|
|
|
|
|
+ orders_count: customer.orders_count || 0,
|
|
|
|
|
+ total_spent: parseFloat(customer.total_spent || '0'),
|
|
|
|
|
+ raw_data: customer,
|
|
|
|
|
+ last_synced_at: new Date().toISOString()
|
|
|
|
|
+ }))
|
|
|
|
|
+
|
|
|
|
|
+ const { error: upsertError } = await supabaseAdmin
|
|
|
|
|
+ .from('woocommerce_customers_cache')
|
|
|
|
|
+ .upsert(customersToCache, {
|
|
|
|
|
+ onConflict: 'store_id,wc_customer_id'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (upsertError) {
|
|
|
|
|
+ console.error('[WooCommerce] Error caching customers:', upsertError)
|
|
|
|
|
+ errors += customersToCache.length
|
|
|
|
|
+ } else {
|
|
|
|
|
+ synced += customersToCache.length
|
|
|
|
|
+ console.log(`[WooCommerce] Cached ${customersToCache.length} customers (page ${page})`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if there are more pages
|
|
|
|
|
+ if (customers.length < perPage) {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ page++
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`[WooCommerce] Customers sync complete: ${synced} synced, ${errors} errors`)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('[WooCommerce] Customer sync error:', error)
|
|
|
|
|
+ errors++
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return { synced, errors }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+serve(async (req) => {
|
|
|
|
|
+ if (req.method === 'OPTIONS') {
|
|
|
|
|
+ return new Response('ok', { headers: corsHeaders })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const supabaseUrl = Deno.env.get('SUPABASE_URL')!
|
|
|
|
|
+ const supabaseKey = Deno.env.get('SUPABASE_ANON_KEY')!
|
|
|
|
|
+ const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
|
|
|
+
|
|
|
|
|
+ const supabase = createClient(supabaseUrl, supabaseKey)
|
|
|
|
|
+ const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey)
|
|
|
|
|
+
|
|
|
|
|
+ // GET request - Get sync status
|
|
|
|
|
+ if (req.method === 'GET') {
|
|
|
|
|
+ // Get user from authorization header
|
|
|
|
|
+ const authHeader = req.headers.get('authorization')
|
|
|
|
|
+ if (!authHeader) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'No authorization header' }),
|
|
|
|
|
+ { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const token = authHeader.replace('Bearer ', '')
|
|
|
|
|
+ const { data: { user }, error: userError } = await supabase.auth.getUser(token)
|
|
|
|
|
+
|
|
|
|
|
+ if (userError || !user) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Invalid token' }),
|
|
|
|
|
+ { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get storeId from query params
|
|
|
|
|
+ const url = new URL(req.url)
|
|
|
|
|
+ const storeId = url.searchParams.get('store_id')
|
|
|
|
|
+
|
|
|
|
|
+ if (!storeId) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'store_id parameter is required' }),
|
|
|
|
|
+ { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Verify store belongs to user
|
|
|
|
|
+ const { data: store, error: storeError } = await supabase
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .select('id')
|
|
|
|
|
+ .eq('id', storeId)
|
|
|
|
|
+ .eq('user_id', user.id)
|
|
|
|
|
+ .eq('platform_name', 'woocommerce')
|
|
|
|
|
+ .single()
|
|
|
|
|
+
|
|
|
|
|
+ if (storeError || !store) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Store not found or access denied' }),
|
|
|
|
|
+ { status: 404, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get sync status using helper function
|
|
|
|
|
+ const { data: statusData, error: statusError } = await supabaseAdmin
|
|
|
|
|
+ .rpc('get_woocommerce_sync_status', { p_store_id: storeId })
|
|
|
|
|
+
|
|
|
|
|
+ if (statusError) {
|
|
|
|
|
+ console.error('[WooCommerce] Error fetching sync status:', statusError)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Failed to fetch sync status' }),
|
|
|
|
|
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const status = statusData && statusData.length > 0 ? statusData[0] : {
|
|
|
|
|
+ last_sync_at: null,
|
|
|
|
|
+ sync_status: 'idle',
|
|
|
|
|
+ products_count: 0,
|
|
|
|
|
+ orders_count: 0,
|
|
|
|
|
+ customers_count: 0
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify(status),
|
|
|
|
|
+ { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // POST request - Trigger sync
|
|
|
|
|
+ if (req.method === 'POST') {
|
|
|
|
|
+ // Get user from authorization header
|
|
|
|
|
+ const authHeader = req.headers.get('authorization')
|
|
|
|
|
+ if (!authHeader) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'No authorization header' }),
|
|
|
|
|
+ { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const token = authHeader.replace('Bearer ', '')
|
|
|
|
|
+ const { data: { user }, error: userError } = await supabase.auth.getUser(token)
|
|
|
|
|
+
|
|
|
|
|
+ if (userError || !user) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Invalid token' }),
|
|
|
|
|
+ { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Parse request body
|
|
|
|
|
+ const body = await req.json()
|
|
|
|
|
+ const { store_id, sync_type } = body
|
|
|
|
|
+
|
|
|
|
|
+ if (!store_id) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'store_id is required' }),
|
|
|
|
|
+ { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!sync_type || !['products', 'orders', 'customers', 'all'].includes(sync_type)) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'sync_type must be one of: products, orders, customers, all' }),
|
|
|
|
|
+ { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Verify store belongs to user
|
|
|
|
|
+ const { data: store, error: storeError } = await supabase
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .select('id, store_name, store_url')
|
|
|
|
|
+ .eq('id', store_id)
|
|
|
|
|
+ .eq('user_id', user.id)
|
|
|
|
|
+ .eq('platform_name', 'woocommerce')
|
|
|
|
|
+ .single()
|
|
|
|
|
+
|
|
|
|
|
+ if (storeError || !store) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Store not found or access denied' }),
|
|
|
|
|
+ { status: 404, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`[WooCommerce] Starting ${sync_type} sync for store ${store_id}`)
|
|
|
|
|
+
|
|
|
|
|
+ const syncStats = {
|
|
|
|
|
+ products: { synced: 0, errors: 0 },
|
|
|
|
|
+ orders: { synced: 0, errors: 0 },
|
|
|
|
|
+ customers: { synced: 0, errors: 0 }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const rateLimiter = new RateLimiter()
|
|
|
|
|
+
|
|
|
|
|
+ // Sync based on type
|
|
|
|
|
+ if (sync_type === 'products' || sync_type === 'all') {
|
|
|
|
|
+ syncStats.products = await syncProducts(store_id, supabaseAdmin, rateLimiter)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (sync_type === 'orders' || sync_type === 'all') {
|
|
|
|
|
+ syncStats.orders = await syncOrders(store_id, supabaseAdmin, rateLimiter)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (sync_type === 'customers' || sync_type === 'all') {
|
|
|
|
|
+ syncStats.customers = await syncCustomers(store_id, supabaseAdmin, rateLimiter)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Update store alt_data with sync info
|
|
|
|
|
+ const { data: currentStore } = await supabaseAdmin
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .select('alt_data')
|
|
|
|
|
+ .eq('id', store_id)
|
|
|
|
|
+ .single()
|
|
|
|
|
+
|
|
|
|
|
+ const altData = currentStore?.alt_data || {}
|
|
|
|
|
+
|
|
|
|
|
+ await supabaseAdmin
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .update({
|
|
|
|
|
+ alt_data: {
|
|
|
|
|
+ ...altData,
|
|
|
|
|
+ lastSync: new Date().toISOString(),
|
|
|
|
|
+ syncStatus: 'completed',
|
|
|
|
|
+ productsCount: syncStats.products.synced,
|
|
|
|
|
+ ordersCount: syncStats.orders.synced,
|
|
|
|
|
+ customersCount: syncStats.customers.synced,
|
|
|
|
|
+ lastSyncStats: syncStats
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .eq('id', store_id)
|
|
|
|
|
+
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ message: `${sync_type} sync completed`,
|
|
|
|
|
+ stats: syncStats,
|
|
|
|
|
+ timestamp: new Date().toISOString()
|
|
|
|
|
+ }),
|
|
|
|
|
+ { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Method not allowed' }),
|
|
|
|
|
+ { status: 405, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('[WooCommerce] Sync endpoint error:', error)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({
|
|
|
|
|
+ error: 'Sync failed',
|
|
|
|
|
+ details: error instanceof Error ? error.message : 'Unknown error'
|
|
|
|
|
+ }),
|
|
|
|
|
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+})
|