Просмотр исходного кода

fix: restrict store-data endpoints to products only for GDPR compliance #52

- Orders and customers cache tables removed for GDPR compliance
- Only products are cached and can be managed via store-data endpoints
- Updated all store-data endpoints to return 501 for orders/customers
- Added clear error messages directing to shop-data-api for real-time access
- Products endpoint continues to work from cache as before
Claude 5 месяцев назад
Родитель
Сommit
9602bbb42d
1 измененных файлов с 144 добавлено и 119 удалено
  1. 144 119
      supabase/functions/api/index.ts

+ 144 - 119
supabase/functions/api/index.ts

@@ -1023,48 +1023,61 @@ serve(wrapHandler('api', async (req) => {
         const singularType = dataType === 'products' ? 'product' : dataType === 'orders' ? 'order' : 'customer'
         const platform = store.platform_name
 
-        // Get table info based on platform
-        const tableName = platform === 'woocommerce'
-          ? `woocommerce_${dataType}_cache`
-          : platform === 'shopify'
-          ? `shopify_${dataType}_cache`
-          : `shoprenter_${dataType}_cache`
+        // Orders and customers are fetched in real-time (GDPR compliance)
+        // Products are fetched from cache
+        let cacheItems: any[] = []
+        let idColumn = ''
 
-        const idColumn = platform === 'woocommerce'
-          ? `wc_${singularType}_id`
-          : platform === 'shopify'
-          ? `shopify_${singularType}_id`
-          : `shoprenter_${singularType}_id`
+        if (dataType === 'products') {
+          // Fetch products from cache
+          const tableName = platform === 'woocommerce'
+            ? 'woocommerce_products_cache'
+            : platform === 'shopify'
+            ? 'shopify_products_cache'
+            : 'shoprenter_products_cache'
 
-        // Build cache query
-        let cacheQuery = supabase
-          .from(tableName)
-          .select('*')
-          .eq('store_id', storeId)
-          .order('created_at', { ascending: false })
+          idColumn = platform === 'woocommerce'
+            ? 'wc_product_id'
+            : platform === 'shopify'
+            ? 'shopify_product_id'
+            : 'shoprenter_product_id'
 
-        // Apply search filter
-        if (search) {
-          if (dataType === 'products') {
+          // Build cache query
+          let cacheQuery = supabase
+            .from(tableName)
+            .select('*')
+            .eq('store_id', storeId)
+            .order('created_at', { ascending: false })
+
+          // Apply search filter
+          if (search) {
             if (platform === 'shoprenter') {
               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 (dataType === 'orders') {
-            cacheQuery = cacheQuery.or(`order_number.ilike.%${search}%,customer_name.ilike.%${search}%,customer_email.ilike.%${search}%`)
-          } else if (dataType === 'customers') {
-            cacheQuery = cacheQuery.or(`email.ilike.%${search}%,first_name.ilike.%${search}%,last_name.ilike.%${search}%,phone.ilike.%${search}%`)
           }
-        }
 
-        const { data: cacheItems, error: cacheError } = await cacheQuery
+          const { data, error: cacheError } = await cacheQuery
+
+          if (cacheError) {
+            console.error(`Error fetching products from cache:`, cacheError)
+            return new Response(
+              JSON.stringify({ error: 'Failed to fetch products' }),
+              { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+            )
+          }
 
-        if (cacheError) {
-          console.error(`Error fetching ${dataType} from cache:`, cacheError)
+          cacheItems = data || []
+        } else {
+          // For orders and customers, return message that real-time access is not available via this endpoint
           return new Response(
-            JSON.stringify({ error: `Failed to fetch ${dataType}` }),
-            { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+            JSON.stringify({
+              error: `${dataType} data is no longer cached for GDPR compliance`,
+              hint: `Access ${dataType} in real-time via the shop-data-api Edge Function or through your platform's admin panel`,
+              success: false
+            }),
+            { status: 501, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
           )
         }
 
@@ -1107,29 +1120,17 @@ serve(wrapHandler('api', async (req) => {
             enabled_in_context: isEnabled
           }
 
-          // Add type-specific fields
-          if (dataType === 'products') {
-            if (platform === 'shoprenter') {
-              resultItem.name = item.product_data?.name || ''
-              resultItem.sku = item.product_data?.sku || ''
-              resultItem.price = item.product_data?.price || '0'
-              resultItem.currency = item.product_data?.currency || 'HUF'
-            } else {
-              resultItem.name = item.name || item.title || ''
-              resultItem.sku = item.sku || ''
-              resultItem.price = item.price || '0'
-              resultItem.currency = item.currency || 'USD'
-            }
-          } else if (dataType === 'orders') {
-            resultItem.order_number = item.order_number || item.name || ''
-            resultItem.customer_name = item.customer_name || ''
-            resultItem.customer_email = item.customer_email || item.email || ''
-            resultItem.total = item.total || item.total_price || '0'
+          // Add product-specific fields
+          if (platform === 'shoprenter') {
+            resultItem.name = item.product_data?.name || ''
+            resultItem.sku = item.product_data?.sku || ''
+            resultItem.price = item.product_data?.price || '0'
+            resultItem.currency = item.product_data?.currency || 'HUF'
+          } else {
+            resultItem.name = item.name || item.title || ''
+            resultItem.sku = item.sku || ''
+            resultItem.price = item.price || '0'
             resultItem.currency = item.currency || 'USD'
-          } else if (dataType === 'customers') {
-            resultItem.name = `${item.first_name || ''} ${item.last_name || ''}`.trim()
-            resultItem.email = item.email || ''
-            resultItem.orders_count = item.orders_count || 0
           }
 
           results.push(resultItem)
@@ -1169,13 +1170,24 @@ serve(wrapHandler('api', async (req) => {
       }
     }
 
-    // PUT /api/store-data/:type/:id/toggle - Toggle individual item
+    // PUT /api/store-data/:type/:id/toggle - Toggle individual item (products only)
     if (path.match(/^store-data\/(products|orders|customers)\/[^\/]+\/toggle$/) && req.method === 'PUT') {
       const pathParts = path.split('/')
       const dataType = pathParts[1] // products, orders, or customers
       const itemId = pathParts[2]
       const { store_id, enabled } = await req.json()
 
+      // Only allow toggle for products (orders/customers not cached due to GDPR)
+      if (dataType !== 'products') {
+        return new Response(
+          JSON.stringify({
+            error: `Cannot toggle ${dataType} - only products can be toggled`,
+            hint: 'Orders and customers are not cached for GDPR compliance'
+          }),
+          { status: 501, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+        )
+      }
+
       if (!store_id) {
         return new Response(
           JSON.stringify({ error: 'store_id is required' }),
@@ -1199,21 +1211,20 @@ serve(wrapHandler('api', async (req) => {
       }
 
       try {
-        const singularType = dataType === 'products' ? 'product' : dataType === 'orders' ? 'order' : 'customer'
         const platform = store.platform_name
 
-        // Get metadata from cache
+        // Get metadata from product cache
         const tableName = platform === 'woocommerce'
-          ? `woocommerce_${dataType}_cache`
+          ? 'woocommerce_products_cache'
           : platform === 'shopify'
-          ? `shopify_${dataType}_cache`
-          : `shoprenter_${dataType}_cache`
+          ? 'shopify_products_cache'
+          : 'shoprenter_products_cache'
 
         const idColumn = platform === 'woocommerce'
-          ? `wc_${singularType}_id`
+          ? 'wc_product_id'
           : platform === 'shopify'
-          ? `shopify_${singularType}_id`
-          : `shoprenter_${singularType}_id`
+          ? 'shopify_product_id'
+          : 'shoprenter_product_id'
 
         const { data: cacheItem } = await supabase
           .from(tableName)
@@ -1224,31 +1235,17 @@ serve(wrapHandler('api', async (req) => {
 
         let metadata = {}
         if (cacheItem) {
-          if (singularType === '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 || cacheItem.title,
-                sku: cacheItem.sku,
-                price: cacheItem.price
-              }
-            }
-          } else if (singularType === 'order') {
+          if (platform === 'shoprenter') {
             metadata = {
-              order_number: cacheItem.order_number || cacheItem.name,
-              customer_name: cacheItem.customer_name,
-              total: cacheItem.total || cacheItem.total_price
+              name: cacheItem.product_data?.name,
+              sku: cacheItem.product_data?.sku,
+              price: cacheItem.product_data?.price
             }
-          } else if (singularType === 'customer') {
+          } else {
             metadata = {
-              email: cacheItem.email,
-              first_name: cacheItem.first_name,
-              last_name: cacheItem.last_name
+              name: cacheItem.name || cacheItem.title,
+              sku: cacheItem.sku,
+              price: cacheItem.price
             }
           }
         }
@@ -1258,7 +1255,7 @@ serve(wrapHandler('api', async (req) => {
           .from('store_data_exclusions')
           .upsert({
             store_id: store_id,
-            data_type: singularType,
+            data_type: 'product',
             data_id: itemId,
             is_enabled: enabled,
             metadata,
@@ -1278,24 +1275,35 @@ serve(wrapHandler('api', async (req) => {
         return new Response(
           JSON.stringify({
             success: true,
-            message: `Item ${enabled ? 'enabled' : 'disabled'} successfully`
+            message: `Product ${enabled ? 'enabled' : 'disabled'} successfully`
           }),
           { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         )
       } catch (error) {
-        console.error('Error toggling item:', error)
+        console.error('Error toggling product:', error)
         return new Response(
-          JSON.stringify({ error: 'Failed to update item' }),
+          JSON.stringify({ error: 'Failed to update product' }),
           { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         )
       }
     }
 
-    // PUT /api/store-data/:type/bulk-toggle - Bulk toggle items
+    // PUT /api/store-data/:type/bulk-toggle - Bulk toggle items (products only)
     if (path.match(/^store-data\/(products|orders|customers)\/bulk-toggle$/) && req.method === 'PUT') {
       const dataType = path.split('/')[1]
       const { store_id, item_ids, enabled } = await req.json()
 
+      // Only allow bulk toggle for products
+      if (dataType !== 'products') {
+        return new Response(
+          JSON.stringify({
+            error: `Cannot bulk toggle ${dataType} - only products can be toggled`,
+            hint: 'Orders and customers are not cached for GDPR compliance'
+          }),
+          { status: 501, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+        )
+      }
+
       if (!store_id || !item_ids || !Array.isArray(item_ids)) {
         return new Response(
           JSON.stringify({ error: 'store_id and item_ids array are required' }),
@@ -1319,14 +1327,12 @@ serve(wrapHandler('api', async (req) => {
       }
 
       try {
-        const singularType = dataType === 'products' ? 'product' : dataType === 'orders' ? 'order' : 'customer'
-
         for (const itemId of item_ids) {
           await supabase
             .from('store_data_exclusions')
             .upsert({
               store_id: store_id,
-              data_type: singularType,
+              data_type: 'product',
               data_id: itemId,
               is_enabled: enabled,
               metadata: {},
@@ -1339,24 +1345,35 @@ serve(wrapHandler('api', async (req) => {
         return new Response(
           JSON.stringify({
             success: true,
-            message: `${item_ids.length} items ${enabled ? 'enabled' : 'disabled'} successfully`
+            message: `${item_ids.length} products ${enabled ? 'enabled' : 'disabled'} successfully`
           }),
           { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         )
       } catch (error) {
-        console.error('Error bulk toggling items:', error)
+        console.error('Error bulk toggling products:', error)
         return new Response(
-          JSON.stringify({ error: 'Failed to update items' }),
+          JSON.stringify({ error: 'Failed to update products' }),
           { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         )
       }
     }
 
-    // PUT /api/store-data/:type/enable-all - Enable all items
+    // PUT /api/store-data/:type/enable-all - Enable all items (products only)
     if (path.match(/^store-data\/(products|orders|customers)\/enable-all$/) && req.method === 'PUT') {
       const dataType = path.split('/')[1]
       const { store_id } = await req.json()
 
+      // Only allow enable-all for products
+      if (dataType !== 'products') {
+        return new Response(
+          JSON.stringify({
+            error: `Cannot enable all ${dataType} - only products can be managed`,
+            hint: 'Orders and customers are not cached for GDPR compliance'
+          }),
+          { status: 501, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+        )
+      }
+
       if (!store_id) {
         return new Response(
           JSON.stringify({ error: 'store_id is required' }),
@@ -1380,17 +1397,15 @@ serve(wrapHandler('api', async (req) => {
       }
 
       try {
-        const singularType = dataType === 'products' ? 'product' : dataType === 'orders' ? 'order' : 'customer'
-
-        // Update all exclusions for this store and type to enabled
+        // Update all exclusions for this store to enabled
         const { error: updateError } = await supabase
           .from('store_data_exclusions')
           .update({ is_enabled: true, updated_at: new Date().toISOString() })
           .eq('store_id', store_id)
-          .eq('data_type', singularType)
+          .eq('data_type', 'product')
 
         if (updateError) {
-          console.error('Error enabling all items:', updateError)
+          console.error('Error enabling all products:', updateError)
           return new Response(
             JSON.stringify({ error: 'Failed to enable all items' }),
             { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
@@ -1400,24 +1415,35 @@ serve(wrapHandler('api', async (req) => {
         return new Response(
           JSON.stringify({
             success: true,
-            message: `All ${dataType} enabled successfully`
+            message: 'All products enabled successfully'
           }),
           { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         )
       } catch (error) {
-        console.error('Error enabling all items:', error)
+        console.error('Error enabling all products:', error)
         return new Response(
-          JSON.stringify({ error: 'Failed to enable all items' }),
+          JSON.stringify({ error: 'Failed to enable all products' }),
           { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         )
       }
     }
 
-    // PUT /api/store-data/:type/disable-all - Disable all items
+    // PUT /api/store-data/:type/disable-all - Disable all items (products only)
     if (path.match(/^store-data\/(products|orders|customers)\/disable-all$/) && req.method === 'PUT') {
       const dataType = path.split('/')[1]
       const { store_id } = await req.json()
 
+      // Only allow disable-all for products
+      if (dataType !== 'products') {
+        return new Response(
+          JSON.stringify({
+            error: `Cannot disable all ${dataType} - only products can be managed`,
+            hint: 'Orders and customers are not cached for GDPR compliance'
+          }),
+          { status: 501, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+        )
+      }
+
       if (!store_id) {
         return new Response(
           JSON.stringify({ error: 'store_id is required' }),
@@ -1441,21 +1467,20 @@ serve(wrapHandler('api', async (req) => {
       }
 
       try {
-        const singularType = dataType === 'products' ? 'product' : dataType === 'orders' ? 'order' : 'customer'
         const platform = store.platform_name
 
-        // Get all items from cache to create exclusions
+        // Get all products from cache to create exclusions
         const tableName = platform === 'woocommerce'
-          ? `woocommerce_${dataType}_cache`
+          ? 'woocommerce_products_cache'
           : platform === 'shopify'
-          ? `shopify_${dataType}_cache`
-          : `shoprenter_${dataType}_cache`
+          ? 'shopify_products_cache'
+          : 'shoprenter_products_cache'
 
         const idColumn = platform === 'woocommerce'
-          ? `wc_${singularType}_id`
+          ? 'wc_product_id'
           : platform === 'shopify'
-          ? `shopify_${singularType}_id`
-          : `shoprenter_${singularType}_id`
+          ? 'shopify_product_id'
+          : 'shoprenter_product_id'
 
         const { data: cacheItems } = await supabase
           .from(tableName)
@@ -1463,10 +1488,10 @@ serve(wrapHandler('api', async (req) => {
           .eq('store_id', store_id)
 
         if (cacheItems && cacheItems.length > 0) {
-          // Create or update exclusions for all items
+          // Create or update exclusions for all products
           const exclusions = cacheItems.map(item => ({
             store_id: store_id,
-            data_type: singularType,
+            data_type: 'product',
             data_id: item[idColumn],
             is_enabled: false,
             metadata: {},
@@ -1480,9 +1505,9 @@ serve(wrapHandler('api', async (req) => {
             })
 
           if (upsertError) {
-            console.error('Error disabling all items:', upsertError)
+            console.error('Error disabling all products:', upsertError)
             return new Response(
-              JSON.stringify({ error: 'Failed to disable all items' }),
+              JSON.stringify({ error: 'Failed to disable all products' }),
               { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
             )
           }
@@ -1491,14 +1516,14 @@ serve(wrapHandler('api', async (req) => {
         return new Response(
           JSON.stringify({
             success: true,
-            message: `All ${dataType} disabled successfully`
+            message: 'All products disabled successfully'
           }),
           { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         )
       } catch (error) {
-        console.error('Error disabling all items:', error)
+        console.error('Error disabling all products:', error)
         return new Response(
-          JSON.stringify({ error: 'Failed to disable all items' }),
+          JSON.stringify({ error: 'Failed to disable all products' }),
           { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         )
       }