|
|
@@ -555,29 +555,60 @@ async function syncProducts(
|
|
|
|
|
|
const currency = store?.alt_data?.currency || 'USD'
|
|
|
|
|
|
- // Map and upsert products to SQL cache
|
|
|
- const productsToCache = products.map((product: ShopifyProduct) => {
|
|
|
- const primaryVariant = product.variants?.[0]
|
|
|
+ // Map and upsert products to SQL cache with exclusion status
|
|
|
+ const productsToCache = await Promise.all(
|
|
|
+ products.map(async (product: ShopifyProduct) => {
|
|
|
+ const primaryVariant = product.variants?.[0]
|
|
|
+
|
|
|
+ // Build categories array from product_type and vendor
|
|
|
+ const categories: any[] = []
|
|
|
+ if (product.product_type) {
|
|
|
+ categories.push({
|
|
|
+ id: product.product_type.toLowerCase().replace(/\s+/g, '-'),
|
|
|
+ name: product.product_type
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
- // Build categories array from product_type and vendor
|
|
|
- const categories: any[] = []
|
|
|
- if (product.product_type) {
|
|
|
- categories.push({
|
|
|
- id: product.product_type.toLowerCase().replace(/\s+/g, '-'),
|
|
|
- name: product.product_type
|
|
|
- })
|
|
|
- }
|
|
|
+ // Check if product is excluded by category using database function
|
|
|
+ const { data: categoryExcluded, error: categoryExclusionError } = await supabaseAdmin
|
|
|
+ .rpc('is_product_excluded_by_category', {
|
|
|
+ p_store_id: storeId,
|
|
|
+ p_categories: categories,
|
|
|
+ p_platform: 'shopify'
|
|
|
+ })
|
|
|
|
|
|
- return {
|
|
|
- store_id: storeId,
|
|
|
- product_id: product.id.toString(),
|
|
|
- name: product.title, // Map title to name for consistency
|
|
|
- sku: primaryVariant?.sku || null,
|
|
|
- categories: categories,
|
|
|
- excluded: false, // Will be set if in exclusion list
|
|
|
- last_synced_at: new Date().toISOString()
|
|
|
- }
|
|
|
- })
|
|
|
+ if (categoryExclusionError) {
|
|
|
+ console.error(`[Shopify] Error checking category exclusion for product ${product.id}:`, categoryExclusionError)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if product is individually excluded from cache
|
|
|
+ const { data: existingProduct, error: existingError } = await supabaseAdmin
|
|
|
+ .from('shopify_products_cache')
|
|
|
+ .select('excluded')
|
|
|
+ .eq('store_id', storeId)
|
|
|
+ .eq('product_id', product.id.toString())
|
|
|
+ .single()
|
|
|
+
|
|
|
+ if (existingError && existingError.code !== 'PGRST116') {
|
|
|
+ console.error(`[Shopify] Error checking existing product exclusion for ${product.id}:`, existingError)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Product is excluded if:
|
|
|
+ // 1. Excluded by category, OR
|
|
|
+ // 2. Individually excluded (from existing cache record)
|
|
|
+ const isExcluded = categoryExcluded || (existingProduct?.excluded === true)
|
|
|
+
|
|
|
+ return {
|
|
|
+ store_id: storeId,
|
|
|
+ product_id: product.id.toString(),
|
|
|
+ name: product.title, // Map title to name for consistency
|
|
|
+ sku: primaryVariant?.sku || null,
|
|
|
+ categories: categories,
|
|
|
+ excluded: isExcluded,
|
|
|
+ last_synced_at: new Date().toISOString()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ )
|
|
|
|
|
|
// Batch upsert in chunks of 100 to avoid payload size limits
|
|
|
const chunkSize = 100
|
|
|
@@ -601,10 +632,42 @@ async function syncProducts(
|
|
|
|
|
|
console.log(`[Shopify] Products sync complete: ${synced} synced, ${errors} errors`)
|
|
|
|
|
|
- // Sync to Qdrant if enabled
|
|
|
+ // Handle Qdrant sync with exclusion logic
|
|
|
let qdrantResult
|
|
|
if (qdrantEnabled) {
|
|
|
- qdrantResult = await syncProductsToQdrant(storeId, storeName, products, supabaseAdmin)
|
|
|
+ // Get excluded product IDs for Qdrant cleanup
|
|
|
+ const excludedProductIds = productsToCache
|
|
|
+ .filter(p => p.excluded)
|
|
|
+ .map(p => p.product_id)
|
|
|
+
|
|
|
+ // Filter products for Qdrant sync (only non-excluded)
|
|
|
+ const productsForQdrant = products.filter(product => {
|
|
|
+ const productCacheItem = productsToCache.find(p => p.product_id === product.id.toString())
|
|
|
+ return productCacheItem && !productCacheItem.excluded
|
|
|
+ })
|
|
|
+
|
|
|
+ // Delete excluded products from Qdrant
|
|
|
+ if (excludedProductIds.length > 0) {
|
|
|
+ console.log(`[Shopify] Deleting ${excludedProductIds.length} excluded products from Qdrant...`)
|
|
|
+ try {
|
|
|
+ const { deletePointsByFilter, getCollectionName } = await import('../_shared/qdrant-client.ts')
|
|
|
+ const collectionName = getCollectionName(storeName, 'products')
|
|
|
+ await deletePointsByFilter(
|
|
|
+ collectionName,
|
|
|
+ {
|
|
|
+ key: 'product_id',
|
|
|
+ match: { any: excludedProductIds }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ console.log(`[Shopify] Deleted excluded products from Qdrant successfully`)
|
|
|
+ } catch (qdrantDeleteError) {
|
|
|
+ console.error('[Shopify] Error deleting excluded products from Qdrant:', qdrantDeleteError)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Sync remaining (non-excluded) products to Qdrant
|
|
|
+ qdrantResult = await syncProductsToQdrant(storeId, storeName, productsForQdrant, supabaseAdmin)
|
|
|
+ console.log(`[Shopify] Qdrant sync complete: ${qdrantResult.synced} synced, ${qdrantResult.errors} errors, ${excludedProductIds.length} excluded`)
|
|
|
}
|
|
|
|
|
|
return { synced, errors, qdrant: qdrantResult }
|