|
@@ -1090,6 +1090,58 @@ serve(async (req) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Helper functions for category format transformation
|
|
|
|
|
+ // =========================================================================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Extract numeric category ID from base64-encoded category ID
|
|
|
|
|
+ * Example: "Y2F0ZWdvcnktY2F0ZWdvcnlfaWQ9MjA2" -> "206"
|
|
|
|
|
+ */
|
|
|
|
|
+ function extractNumericCategoryId(base64CategoryId: string): string | null {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const decoded = atob(base64CategoryId)
|
|
|
|
|
+ // Format: "category-category_id=206"
|
|
|
|
|
+ const match = decoded.match(/category_id=(\d+)/)
|
|
|
|
|
+ return match ? match[1] : null
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Failed to decode category ID:', base64CategoryId, error)
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Transform old category format (base64 strings) to new format (objects with id and name)
|
|
|
|
|
+ * If already in new format, return as-is
|
|
|
|
|
+ */
|
|
|
|
|
+ function transformCategoriesToNewFormat(categories: any[]): Array<{ id: string; name: string }> {
|
|
|
|
|
+ if (!categories || categories.length === 0) return []
|
|
|
|
|
+
|
|
|
|
|
+ return categories.map(cat => {
|
|
|
|
|
+ // If already in new format (has id and name properties)
|
|
|
|
|
+ if (cat && typeof cat === 'object' && (cat.id || cat.category_id) && cat.name) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: cat.id || cat.category_id,
|
|
|
|
|
+ name: cat.name || cat.category_name || 'Unknown Category'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // If it's old format (base64 string)
|
|
|
|
|
+ if (typeof cat === 'string') {
|
|
|
|
|
+ const numericId = extractNumericCategoryId(cat)
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: numericId || cat,
|
|
|
|
|
+ name: 'Legacy Category ' + (numericId || 'Unknown') // Temporary name until sync updates it
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Fallback for other formats
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: cat?.id || cat?.category_id || cat || 'unknown',
|
|
|
|
|
+ name: cat?.name || cat?.category_name || 'Unknown Category'
|
|
|
|
|
+ }
|
|
|
|
|
+ }).filter(cat => cat.id) // Remove invalid categories
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// =========================================================================
|
|
// =========================================================================
|
|
|
// NEW ENDPOINT: GET /api/store-data/categories - Get categories for a store
|
|
// NEW ENDPOINT: GET /api/store-data/categories - Get categories for a store
|
|
|
// =========================================================================
|
|
// =========================================================================
|
|
@@ -1119,25 +1171,62 @@ serve(async (req) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // Use the helper function to get categories
|
|
|
|
|
- const { data: categories, error: categoriesError } = await supabase
|
|
|
|
|
- .rpc('get_store_categories', {
|
|
|
|
|
- p_store_id: storeId,
|
|
|
|
|
- p_platform: store.platform_name
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ // Get all products for this store to extract categories
|
|
|
|
|
+ const platform = store.platform_name
|
|
|
|
|
+ const tableName = platform === 'woocommerce'
|
|
|
|
|
+ ? 'woocommerce_products_cache'
|
|
|
|
|
+ : platform === 'shopify'
|
|
|
|
|
+ ? 'shopify_products_cache'
|
|
|
|
|
+ : 'shoprenter_products_cache'
|
|
|
|
|
+
|
|
|
|
|
+ const { data: products, error: productsError } = await supabase
|
|
|
|
|
+ .from(tableName)
|
|
|
|
|
+ .select('categories')
|
|
|
|
|
+ .eq('store_id', storeId)
|
|
|
|
|
|
|
|
- if (categoriesError) {
|
|
|
|
|
- console.error('Error fetching categories:', categoriesError)
|
|
|
|
|
|
|
+ if (productsError) {
|
|
|
|
|
+ console.error('Error fetching products for categories:', productsError)
|
|
|
return new Response(
|
|
return new Response(
|
|
|
- JSON.stringify({ error: 'Failed to fetch categories' }),
|
|
|
|
|
|
|
+ JSON.stringify({ error: 'Failed to fetch products' }),
|
|
|
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Extract and count categories
|
|
|
|
|
+ const categoryMap = new Map<string, { name: string; count: number }>()
|
|
|
|
|
+
|
|
|
|
|
+ for (const product of products || []) {
|
|
|
|
|
+ const transformedCategories = transformCategoriesToNewFormat(product.categories || [])
|
|
|
|
|
+ for (const cat of transformedCategories) {
|
|
|
|
|
+ if (categoryMap.has(cat.id)) {
|
|
|
|
|
+ categoryMap.get(cat.id)!.count++
|
|
|
|
|
+ } else {
|
|
|
|
|
+ categoryMap.set(cat.id, { name: cat.name, count: 1 })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get excluded categories
|
|
|
|
|
+ const { data: excludedCategories } = await supabase
|
|
|
|
|
+ .from('excluded_categories')
|
|
|
|
|
+ .select('category_id')
|
|
|
|
|
+ .eq('store_id', storeId)
|
|
|
|
|
+ .eq('platform', platform)
|
|
|
|
|
+
|
|
|
|
|
+ const excludedCategoryIds = new Set(excludedCategories?.map(ec => ec.category_id) || [])
|
|
|
|
|
+
|
|
|
|
|
+ // Build response format
|
|
|
|
|
+ const categories = Array.from(categoryMap.entries()).map(([categoryId, { name, count }]) => ({
|
|
|
|
|
+ category_id: categoryId,
|
|
|
|
|
+ category_name: name,
|
|
|
|
|
+ product_count: count,
|
|
|
|
|
+ is_excluded: excludedCategoryIds.has(categoryId)
|
|
|
|
|
+ }))
|
|
|
|
|
+
|
|
|
return new Response(
|
|
return new Response(
|
|
|
JSON.stringify({
|
|
JSON.stringify({
|
|
|
success: true,
|
|
success: true,
|
|
|
- categories: categories || []
|
|
|
|
|
|
|
+ categories: categories.sort((a, b) => a.category_name.localeCompare(b.category_name))
|
|
|
}),
|
|
}),
|
|
|
{ status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
{ status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
|
)
|
|
)
|
|
@@ -1269,11 +1358,11 @@ serve(async (req) => {
|
|
|
query = query.or(`name.ilike.%${search}%,sku.ilike.%${search}%`)
|
|
query = query.or(`name.ilike.%${search}%,sku.ilike.%${search}%`)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Apply category filter
|
|
|
|
|
- if (categoryFilter) {
|
|
|
|
|
- // Filter products that have this category
|
|
|
|
|
- query = query.contains('categories', [{ id: categoryFilter }])
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Apply category filter - disabled temporarily because of format mismatch
|
|
|
|
|
+ // Will be re-enabled after sync function deployment updates data format
|
|
|
|
|
+ // if (categoryFilter) {
|
|
|
|
|
+ // query = query.contains('categories', [{ id: categoryFilter }])
|
|
|
|
|
+ // }
|
|
|
|
|
|
|
|
const { data: products, error: productsError } = await query
|
|
const { data: products, error: productsError } = await query
|
|
|
|
|
|
|
@@ -1318,7 +1407,10 @@ serve(async (req) => {
|
|
|
|
|
|
|
|
// Transform to response format
|
|
// Transform to response format
|
|
|
let results = products.map(p => {
|
|
let results = products.map(p => {
|
|
|
- const excludedByCategory = isExcludedByCategory(p.categories || [])
|
|
|
|
|
|
|
+ // Transform categories to consistent format
|
|
|
|
|
+ const transformedCategories = transformCategoriesToNewFormat(p.categories || [])
|
|
|
|
|
+
|
|
|
|
|
+ const excludedByCategory = isExcludedByCategory(transformedCategories)
|
|
|
const excludedByIndividual = p.excluded === true
|
|
const excludedByIndividual = p.excluded === true
|
|
|
const effectivelyExcluded = excludedByIndividual || excludedByCategory
|
|
const effectivelyExcluded = excludedByIndividual || excludedByCategory
|
|
|
|
|
|
|
@@ -1327,7 +1419,7 @@ serve(async (req) => {
|
|
|
inner_id: p.inner_id, // For ShopRenter
|
|
inner_id: p.inner_id, // For ShopRenter
|
|
|
name: p.name || '',
|
|
name: p.name || '',
|
|
|
sku: p.sku || '',
|
|
sku: p.sku || '',
|
|
|
- categories: p.categories || [],
|
|
|
|
|
|
|
+ categories: transformedCategories, // Now consistently formatted
|
|
|
enabled_in_context: !effectivelyExcluded, // Inverted logic: enabled = NOT excluded
|
|
enabled_in_context: !effectivelyExcluded, // Inverted logic: enabled = NOT excluded
|
|
|
excluded_by_individual: excludedByIndividual,
|
|
excluded_by_individual: excludedByIndividual,
|
|
|
excluded_by_category: excludedByCategory,
|
|
excluded_by_category: excludedByCategory,
|
|
@@ -1335,6 +1427,13 @@ serve(async (req) => {
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ // Apply category filter after transformation
|
|
|
|
|
+ if (categoryFilter && categoryFilter !== 'all') {
|
|
|
|
|
+ results = results.filter(r =>
|
|
|
|
|
+ r.categories.some(cat => cat.id === categoryFilter)
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Apply enabled filter if specified
|
|
// Apply enabled filter if specified
|
|
|
if (enabledFilter !== null) {
|
|
if (enabledFilter !== null) {
|
|
|
const filterEnabled = enabledFilter === 'true'
|
|
const filterEnabled = enabledFilter === 'true'
|