|
@@ -467,6 +467,450 @@ serve(async (req) => {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // GET /api/stores/:id/knowledge-data - List knowledge data items
|
|
|
|
|
+ if (path.match(/^stores\/[^\/]+\/knowledge-data$/) && req.method === 'GET') {
|
|
|
|
|
+ const storeId = path.split('/')[1]
|
|
|
|
|
+
|
|
|
|
|
+ // Verify store ownership
|
|
|
|
|
+ const { data: store, error: storeError } = await supabase
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .select('id, platform_name')
|
|
|
|
|
+ .eq('id', storeId)
|
|
|
|
|
+ .eq('user_id', user.id)
|
|
|
|
|
+ .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 query parameters
|
|
|
|
|
+ const dataType = url.searchParams.get('data_type')
|
|
|
|
|
+ const search = url.searchParams.get('search')
|
|
|
|
|
+ const page = parseInt(url.searchParams.get('page') || '1')
|
|
|
|
|
+ const limit = Math.min(parseInt(url.searchParams.get('limit') || '50'), 100)
|
|
|
|
|
+ const offset = (page - 1) * limit
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Build queries based on platform and data type
|
|
|
|
|
+ const results: any[] = []
|
|
|
|
|
+ const platform = store.platform_name
|
|
|
|
|
+
|
|
|
|
|
+ // Helper to get table name and ID column
|
|
|
|
|
+ const getTableInfo = (type: string) => {
|
|
|
|
|
+ if (platform === 'woocommerce') {
|
|
|
|
|
+ return { table: `woocommerce_${type}s_cache`, idCol: `wc_${type}_id` }
|
|
|
|
|
+ } else if (platform === 'shopify') {
|
|
|
|
|
+ return { table: `shopify_${type}s_cache`, idCol: `shopify_${type}_id` }
|
|
|
|
|
+ } else if (platform === 'shoprenter') {
|
|
|
|
|
+ return { table: `shoprenter_${type}s_cache`, idCol: `shoprenter_${type}_id` }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Determine which data types to query
|
|
|
|
|
+ const typesToQuery = dataType ? [dataType] : ['product', 'order', 'customer']
|
|
|
|
|
+
|
|
|
|
|
+ for (const type of typesToQuery) {
|
|
|
|
|
+ const tableInfo = getTableInfo(type)
|
|
|
|
|
+ if (!tableInfo) continue
|
|
|
|
|
+
|
|
|
|
|
+ // First, fetch all cache items
|
|
|
|
|
+ let cacheQuery = supabase
|
|
|
|
|
+ .from(tableInfo.table)
|
|
|
|
|
+ .select('*')
|
|
|
|
|
+ .eq('store_id', storeId)
|
|
|
|
|
+ .order('created_at', { ascending: false })
|
|
|
|
|
+
|
|
|
|
|
+ // Apply search filter
|
|
|
|
|
+ if (search) {
|
|
|
|
|
+ if (type === 'product') {
|
|
|
|
|
+ if (platform === 'shoprenter') {
|
|
|
|
|
+ // For ShopRenter, search in product_data jsonb
|
|
|
|
|
+ cacheQuery = cacheQuery.or(`product_data->>name.ilike.%${search}%,product_data->>sku.ilike.%${search}%`)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ cacheQuery = cacheQuery.or(`name.ilike.%${search}%,sku.ilike.%${search}%`)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (type === 'order') {
|
|
|
|
|
+ cacheQuery = cacheQuery.or(`order_number.ilike.%${search}%,customer_name.ilike.%${search}%,customer_email.ilike.%${search}%`)
|
|
|
|
|
+ } else if (type === 'customer') {
|
|
|
|
|
+ cacheQuery = cacheQuery.or(`email.ilike.%${search}%,first_name.ilike.%${search}%,last_name.ilike.%${search}%,phone.ilike.%${search}%`)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const { data: cacheItems, error: cacheError } = await cacheQuery
|
|
|
|
|
+
|
|
|
|
|
+ if (cacheError) {
|
|
|
|
|
+ console.error(`Error fetching ${type}s from cache:`, cacheError)
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!cacheItems || cacheItems.length === 0) continue
|
|
|
|
|
+
|
|
|
|
|
+ // Get all exclusions for this store and type
|
|
|
|
|
+ const { data: exclusions } = await supabase
|
|
|
|
|
+ .from('store_data_exclusions')
|
|
|
|
|
+ .select('data_id, is_enabled, metadata')
|
|
|
|
|
+ .eq('store_id', storeId)
|
|
|
|
|
+ .eq('data_type', type)
|
|
|
|
|
+
|
|
|
|
|
+ // Create a map of exclusions for quick lookup
|
|
|
|
|
+ const exclusionMap = new Map()
|
|
|
|
|
+ if (exclusions) {
|
|
|
|
|
+ exclusions.forEach(e => {
|
|
|
|
|
+ exclusionMap.set(e.data_id, { is_enabled: e.is_enabled, metadata: e.metadata })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Transform cache items to result format
|
|
|
|
|
+ for (const item of cacheItems) {
|
|
|
|
|
+ const itemId = item[tableInfo.idCol]
|
|
|
|
|
+ const exclusionData = exclusionMap.get(itemId)
|
|
|
|
|
+
|
|
|
|
|
+ let resultItem: any = {
|
|
|
|
|
+ data_type: type,
|
|
|
|
|
+ data_id: itemId,
|
|
|
|
|
+ is_enabled: exclusionData ? exclusionData.is_enabled : true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Add type-specific fields
|
|
|
|
|
+ if (type === 'product') {
|
|
|
|
|
+ if (platform === 'shoprenter') {
|
|
|
|
|
+ resultItem.name = item.product_data?.name
|
|
|
|
|
+ resultItem.sku = item.product_data?.sku
|
|
|
|
|
+ resultItem.price = item.product_data?.price
|
|
|
|
|
+ } else {
|
|
|
|
|
+ resultItem.name = item.name
|
|
|
|
|
+ resultItem.sku = item.sku
|
|
|
|
|
+ resultItem.price = item.price
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (type === 'order') {
|
|
|
|
|
+ resultItem.order_number = item.order_number
|
|
|
|
|
+ resultItem.customer_name = item.customer_name
|
|
|
|
|
+ resultItem.customer_email = item.customer_email
|
|
|
|
|
+ resultItem.total = item.total
|
|
|
|
|
+ } else if (type === 'customer') {
|
|
|
|
|
+ resultItem.email = item.email
|
|
|
|
|
+ resultItem.first_name = item.first_name
|
|
|
|
|
+ resultItem.last_name = item.last_name
|
|
|
|
|
+ resultItem.phone = item.phone || ''
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ results.push(resultItem)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Apply pagination to combined results
|
|
|
|
|
+ const totalCount = results.length
|
|
|
|
|
+ const paginatedResults = results.slice(offset, offset + limit)
|
|
|
|
|
+
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ data: paginatedResults,
|
|
|
|
|
+ pagination: {
|
|
|
|
|
+ page,
|
|
|
|
|
+ limit,
|
|
|
|
|
+ total: totalCount,
|
|
|
|
|
+ totalPages: Math.ceil(totalCount / limit)
|
|
|
|
|
+ }
|
|
|
|
|
+ }),
|
|
|
|
|
+ { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error fetching knowledge data:', error)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Failed to fetch knowledge data' }),
|
|
|
|
|
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // PUT /api/stores/:id/knowledge-data/:dataType/:dataId - Toggle item state
|
|
|
|
|
+ if (path.match(/^stores\/[^\/]+\/knowledge-data\/[^\/]+\/[^\/]+$/) && req.method === 'PUT') {
|
|
|
|
|
+ const pathParts = path.split('/')
|
|
|
|
|
+ const storeId = pathParts[1]
|
|
|
|
|
+ const dataType = pathParts[3]
|
|
|
|
|
+ const dataId = pathParts[4]
|
|
|
|
|
+
|
|
|
|
|
+ // Validate data_type
|
|
|
|
|
+ if (!['product', 'order', 'customer'].includes(dataType)) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Invalid data_type. Must be product, order, or customer' }),
|
|
|
|
|
+ { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Verify store ownership
|
|
|
|
|
+ const { data: store, error: storeError } = await supabase
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .select('id, platform_name')
|
|
|
|
|
+ .eq('id', storeId)
|
|
|
|
|
+ .eq('user_id', user.id)
|
|
|
|
|
+ .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 request body
|
|
|
|
|
+ const body = await req.json()
|
|
|
|
|
+ const isEnabled = body.is_enabled !== undefined ? body.is_enabled : true
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Extract metadata from cache tables
|
|
|
|
|
+ const platform = store.platform_name
|
|
|
|
|
+ const tableName = platform === 'woocommerce'
|
|
|
|
|
+ ? `woocommerce_${dataType}s_cache`
|
|
|
|
|
+ : platform === 'shopify'
|
|
|
|
|
+ ? `shopify_${dataType}s_cache`
|
|
|
|
|
+ : `shoprenter_${dataType}s_cache`
|
|
|
|
|
+
|
|
|
|
|
+ const idColumn = platform === 'woocommerce'
|
|
|
|
|
+ ? `wc_${dataType}_id`
|
|
|
|
|
+ : platform === 'shopify'
|
|
|
|
|
+ ? `shopify_${dataType}_id`
|
|
|
|
|
+ : `shoprenter_${dataType}_id`
|
|
|
|
|
+
|
|
|
|
|
+ // Fetch item from cache to extract metadata
|
|
|
|
|
+ let metadata = {}
|
|
|
|
|
+ const { data: cacheItem } = await supabase
|
|
|
|
|
+ .from(tableName)
|
|
|
|
|
+ .select('*')
|
|
|
|
|
+ .eq('store_id', storeId)
|
|
|
|
|
+ .eq(idColumn, dataId)
|
|
|
|
|
+ .single()
|
|
|
|
|
+
|
|
|
|
|
+ if (cacheItem) {
|
|
|
|
|
+ if (dataType === 'product') {
|
|
|
|
|
+ if (platform === 'shoprenter') {
|
|
|
|
|
+ metadata = {
|
|
|
|
|
+ name: cacheItem.product_data?.name,
|
|
|
|
|
+ sku: cacheItem.product_data?.sku,
|
|
|
|
|
+ price: cacheItem.product_data?.price
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ metadata = {
|
|
|
|
|
+ name: cacheItem.name,
|
|
|
|
|
+ sku: cacheItem.sku,
|
|
|
|
|
+ price: cacheItem.price
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (dataType === 'order') {
|
|
|
|
|
+ metadata = {
|
|
|
|
|
+ order_number: cacheItem.order_number,
|
|
|
|
|
+ customer_name: cacheItem.customer_name,
|
|
|
|
|
+ customer_email: cacheItem.customer_email,
|
|
|
|
|
+ total: cacheItem.total
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (dataType === 'customer') {
|
|
|
|
|
+ metadata = {
|
|
|
|
|
+ email: cacheItem.email,
|
|
|
|
|
+ first_name: cacheItem.first_name,
|
|
|
|
|
+ last_name: cacheItem.last_name,
|
|
|
|
|
+ phone: cacheItem.phone
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Upsert into store_data_exclusions
|
|
|
|
|
+ const { data: exclusion, error: upsertError } = await supabase
|
|
|
|
|
+ .from('store_data_exclusions')
|
|
|
|
|
+ .upsert({
|
|
|
|
|
+ store_id: storeId,
|
|
|
|
|
+ data_type: dataType,
|
|
|
|
|
+ data_id: dataId,
|
|
|
|
|
+ is_enabled: isEnabled,
|
|
|
|
|
+ metadata,
|
|
|
|
|
+ updated_at: new Date().toISOString()
|
|
|
|
|
+ }, {
|
|
|
|
|
+ onConflict: 'store_id,data_type,data_id'
|
|
|
|
|
+ })
|
|
|
|
|
+ .select()
|
|
|
|
|
+ .single()
|
|
|
|
|
+
|
|
|
|
|
+ if (upsertError) {
|
|
|
|
|
+ console.error('Error upserting exclusion:', upsertError)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Failed to update item state' }),
|
|
|
|
|
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ message: `Item ${isEnabled ? 'enabled' : 'disabled'} successfully`,
|
|
|
|
|
+ data: exclusion
|
|
|
|
|
+ }),
|
|
|
|
|
+ { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error updating item state:', error)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Failed to update item state' }),
|
|
|
|
|
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // POST /api/stores/:id/knowledge-data/batch - Bulk enable/disable items
|
|
|
|
|
+ if (path.match(/^stores\/[^\/]+\/knowledge-data\/batch$/) && req.method === 'POST') {
|
|
|
|
|
+ const storeId = path.split('/')[1]
|
|
|
|
|
+
|
|
|
|
|
+ // Verify store ownership
|
|
|
|
|
+ const { data: store, error: storeError } = await supabase
|
|
|
|
|
+ .from('stores')
|
|
|
|
|
+ .select('id, platform_name')
|
|
|
|
|
+ .eq('id', storeId)
|
|
|
|
|
+ .eq('user_id', user.id)
|
|
|
|
|
+ .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 request body
|
|
|
|
|
+ const body = await req.json()
|
|
|
|
|
+ const { items, data_type, is_enabled } = body
|
|
|
|
|
+
|
|
|
|
|
+ if (!items || !Array.isArray(items) || items.length === 0) {
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'items array is required and must not be empty' }),
|
|
|
|
|
+ { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const platform = store.platform_name
|
|
|
|
|
+ const results = []
|
|
|
|
|
+
|
|
|
|
|
+ for (const item of items) {
|
|
|
|
|
+ const itemDataType = item.data_type || data_type
|
|
|
|
|
+ const itemDataId = item.data_id
|
|
|
|
|
+ const itemIsEnabled = item.is_enabled !== undefined ? item.is_enabled : is_enabled
|
|
|
|
|
+
|
|
|
|
|
+ if (!itemDataType || !itemDataId) {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Validate data_type
|
|
|
|
|
+ if (!['product', 'order', 'customer'].includes(itemDataType)) {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Extract metadata from cache tables
|
|
|
|
|
+ const tableName = platform === 'woocommerce'
|
|
|
|
|
+ ? `woocommerce_${itemDataType}s_cache`
|
|
|
|
|
+ : platform === 'shopify'
|
|
|
|
|
+ ? `shopify_${itemDataType}s_cache`
|
|
|
|
|
+ : `shoprenter_${itemDataType}s_cache`
|
|
|
|
|
+
|
|
|
|
|
+ const idColumn = platform === 'woocommerce'
|
|
|
|
|
+ ? `wc_${itemDataType}_id`
|
|
|
|
|
+ : platform === 'shopify'
|
|
|
|
|
+ ? `shopify_${itemDataType}_id`
|
|
|
|
|
+ : `shoprenter_${itemDataType}_id`
|
|
|
|
|
+
|
|
|
|
|
+ // Fetch item from cache to extract metadata
|
|
|
|
|
+ let metadata = {}
|
|
|
|
|
+ const { data: cacheItem } = await supabase
|
|
|
|
|
+ .from(tableName)
|
|
|
|
|
+ .select('*')
|
|
|
|
|
+ .eq('store_id', storeId)
|
|
|
|
|
+ .eq(idColumn, itemDataId)
|
|
|
|
|
+ .single()
|
|
|
|
|
+
|
|
|
|
|
+ if (cacheItem) {
|
|
|
|
|
+ if (itemDataType === 'product') {
|
|
|
|
|
+ if (platform === 'shoprenter') {
|
|
|
|
|
+ metadata = {
|
|
|
|
|
+ name: cacheItem.product_data?.name,
|
|
|
|
|
+ sku: cacheItem.product_data?.sku,
|
|
|
|
|
+ price: cacheItem.product_data?.price
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ metadata = {
|
|
|
|
|
+ name: cacheItem.name,
|
|
|
|
|
+ sku: cacheItem.sku,
|
|
|
|
|
+ price: cacheItem.price
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (itemDataType === 'order') {
|
|
|
|
|
+ metadata = {
|
|
|
|
|
+ order_number: cacheItem.order_number,
|
|
|
|
|
+ customer_name: cacheItem.customer_name,
|
|
|
|
|
+ customer_email: cacheItem.customer_email,
|
|
|
|
|
+ total: cacheItem.total
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (itemDataType === 'customer') {
|
|
|
|
|
+ metadata = {
|
|
|
|
|
+ email: cacheItem.email,
|
|
|
|
|
+ first_name: cacheItem.first_name,
|
|
|
|
|
+ last_name: cacheItem.last_name,
|
|
|
|
|
+ phone: cacheItem.phone
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Upsert into store_data_exclusions
|
|
|
|
|
+ const { error: upsertError } = await supabase
|
|
|
|
|
+ .from('store_data_exclusions')
|
|
|
|
|
+ .upsert({
|
|
|
|
|
+ store_id: storeId,
|
|
|
|
|
+ data_type: itemDataType,
|
|
|
|
|
+ data_id: itemDataId,
|
|
|
|
|
+ is_enabled: itemIsEnabled,
|
|
|
|
|
+ metadata,
|
|
|
|
|
+ updated_at: new Date().toISOString()
|
|
|
|
|
+ }, {
|
|
|
|
|
+ onConflict: 'store_id,data_type,data_id'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (!upsertError) {
|
|
|
|
|
+ results.push({
|
|
|
|
|
+ data_type: itemDataType,
|
|
|
|
|
+ data_id: itemDataId,
|
|
|
|
|
+ success: true
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ results.push({
|
|
|
|
|
+ data_type: itemDataType,
|
|
|
|
|
+ data_id: itemDataId,
|
|
|
|
|
+ success: false,
|
|
|
|
|
+ error: upsertError.message
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const successCount = results.filter(r => r.success).length
|
|
|
|
|
+ const failCount = results.filter(r => !r.success).length
|
|
|
|
|
+
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ message: `Batch operation completed: ${successCount} succeeded, ${failCount} failed`,
|
|
|
|
|
+ results
|
|
|
|
|
+ }),
|
|
|
|
|
+ { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error in batch operation:', error)
|
|
|
|
|
+ return new Response(
|
|
|
|
|
+ JSON.stringify({ error: 'Failed to complete batch operation' }),
|
|
|
|
|
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// GET /api/dashboard/stats - Get dashboard statistics
|
|
// GET /api/dashboard/stats - Get dashboard statistics
|
|
|
if (path === 'dashboard/stats' && req.method === 'GET') {
|
|
if (path === 'dashboard/stats' && req.method === 'GET') {
|
|
|
// TODO: Implement real dashboard stats calculation
|
|
// TODO: Implement real dashboard stats calculation
|