[dotenv@17.2.3] injecting env (112) from .env -- tip: 📡 add observability to secrets: https://dotenvx.com/ops
[dotenv@17.2.3] injecting env (0) from .env -- tip: 🔐 encrypt with Dotenvx: https://dotenvx.com
[protected-image] IMAGE_ROOTS = [ 'C:\\Bordales Projects\\giftwrap - Copy\\protected\\images' ]
[protected-image] IMAGE_ROOT = /home/bordales/public_html/v1/protected/images
[protected-image] SKU fallback enabled
[Server] ✅ Real-Time Monitor: Initialized
[protected-image] Cache initialized with 9638 files
[Schema] PRICE_HAS_COST_MARKUP = true
✅ Database synced – tables are ready
[Server] Running on port 3012 in development mode
[Server] Health check: http://localhost:3012/healthz
[Server] Ready check: http://localhost:3012/readyz
[Server] ✅ WhatsApp Service: Loaded
[Server] ✅ AI Assistant: Loaded
[Server] ✅ Gate System: Loaded
[TOP-DEBUG] Request: GET /api/v3/products?page=1&limit=5&sort=newest&region=ZA&nocache=1
{"t":"2026-02-20T16:44:33.968Z","event":"tenancy.host","rid":null,"raw":"127.0.0.1","norm":"127.0.0.1"}
{"t":"2026-02-20T16:44:33.981Z","event":"tenants.lookup.exact.ok","rid":null,"ms":11.8838,"sql":"SELECT id, name, domain, base_currency, timezone, recaptcha_secret, recaptcha_site_key, theme, email, phone, address, company_name FROM tenants WHERE LOWER(TRIM(TRAILING '-' FROM domain)) = ? LIMIT 1","params":["127.0.0.1"],"rows":0}
{"t":"2026-02-20T16:44:33.993Z","event":"tenants.lookup.apex.ok","rid":null,"ms":10.4364,"sql":"SELECT id, name, domain, base_currency, timezone, recaptcha_secret, recaptcha_site_key, theme, email, phone, address, company_name FROM tenants WHERE LOWER(TRIM(TRAILING '-' FROM domain)) = ? LIMIT 1","params":["0.1"],"rows":0}
{"t":"2026-02-20T16:44:33.994Z","event":"tenancy.apex","rid":null,"apex":"0.1","found":false}
{"t":"2026-02-20T16:44:33.994Z","event":"tenancy.fallback","rid":null,"reason":"no_match","tried":"127.0.0.1"}
{"t":"2026-02-20T16:44:34.006Z","event":"tenants.lookup.default.ok","rid":null,"ms":11.117,"sql":"SELECT id, name, domain, base_currency, timezone, recaptcha_secret, recaptcha_site_key, theme, email, phone, address, company_name FROM tenants WHERE id=1 LIMIT 1","params":[],"rows":1}
{"t":"2026-02-20T16:44:34.007Z","event":"tenancy.ok","rid":null,"ms":39,"hostRaw":"127.0.0.1:3012","hostNorm":"127.0.0.1","matched":true,"tenant":{"id":1,"domain":"mobi.giftwrap.co.za"},"hdrs":{"host":"127.0.0.1:3012","xfh":null,"xoh":null}}
[GATE DEBUG] Path: /v3/products Original URL: /api/v3/products?page=1&limit=5&sort=newest&region=ZA&nocache=1
[GATE DEBUG] Session ID: likSh4oUfoHHjlcfLScNvcfbIkTnL-mB
[GATE DEBUG] Session: Session {
  cookie: {
    path: '/',
    _expires: null,
    originalMaxAge: null,
    httpOnly: true,
    sameSite: 'lax',
    secure: false
  }
}
[GATE DEBUG] Authenticated: undefined
[V3-DEBUG] Request: GET /api/v3/products?page=1&limit=5&sort=newest&region=ZA&nocache=1 -> path inside router: /products
[ProductService DEBUG] SQL: 
            SELECT
                p.*,
                CAST(
                  CASE
                    WHEN pr.stock_qty IS NOT NULL THEN pr.stock_qty
                    WHEN p.stock_qty IS NULL THEN COALESCE(JSON_UNQUOTE(JSON_EXTRACT(p.attributes_json, '$.stock_qty')), '0')
                    ELSE p.stock_qty
                  END AS SIGNED
                ) as stock_qty,
                CAST(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(p.attributes_json, '$.lead_time')), '') AS CHAR) as lead_time,
                (
                    SELECT pi.path
                    FROM catalog_product_images pi
                    WHERE pi.product_id = p.id
                    ORDER BY pi.is_primary DESC, pi.position ASC
                    LIMIT 1
                ) as primary_image_path,
                COALESCE(pr.price_ex_vat, (
                    SELECT pp.price_ex_vat
                    FROM catalog_product_prices pp
                    WHERE pp.product_id = p.id AND pp.is_active = 1 AND pp.min_qty = 1
                    ORDER BY pp.effective_from DESC LIMIT 1
                ), p.selling_price, p.base_price, 0) as price_ex_vat,
                COALESCE(regions.currency_code, 'ZAR') as currency_code,
                CASE WHEN pr.price_ex_vat IS NOT NULL THEN 'region' ELSE 'default' END as price_source,
                COALESCE(pr.price_inc_vat, (
                    SELECT COALESCE(pp.price_ex_vat, 0) * 1.15
                    FROM catalog_product_prices pp
                    WHERE pp.product_id = p.id AND pp.is_active = 1 AND pp.min_qty = 1
                    ORDER BY pp.effective_from DESC LIMIT 1
                )) as price_inc_vat,
                (
                    SELECT GROUP_CONCAT(DISTINCT pc.category_id)
                    FROM catalog_product_categories pc
                    WHERE pc.product_id = p.id
                ) as category_ids,
                (
                    SELECT GROUP_CONCAT(DISTINCT t.tag)
                    FROM catalog_product_tags t
                    WHERE t.product_id = p.id
                ) as tags_string,
                (
                    SELECT c.slug
                    FROM catalog_product_categories pc_slug
                    JOIN catalog_categories c ON c.id = pc_slug.category_id
                    WHERE pc_slug.product_id = p.id
                    LIMIT 1
                ) as category_slug,
                (
                    SELECT JSON_ARRAYAGG(JSON_OBJECT('id', bm.id, 'name', bm.name, 'slug', bm.slug))
                    FROM catalog_product_branding_methods pm
                    JOIN catalog_branding_methods bm ON bm.id = pm.method_id
                    WHERE pm.product_id = p.id
                ) as branding_methods_json,
                COALESCE(seasonality.score, 0) as seasonal_score,
                COALESCE(relevance.score, 0) as popularity_score
            FROM catalog_products p
            LEFT JOIN product_region pr ON p.id = pr.product_id AND pr.region_code = ?
            LEFT JOIN regions ON regions.code = pr.region_code
            LEFT JOIN product_seasonality seasonality ON p.id = seasonality.product_id AND seasonality.region_code = ? AND seasonality.season = ?
            LEFT JOIN product_relevance relevance ON p.id = relevance.product_id AND relevance.region_code = ?
    
            WHERE p.tenant_id = ?
              AND COALESCE(p.is_active, 1) = 1
              AND (p.status IS NULL OR p.status = '' OR p.status IN ('active','published','live','enabled'))
     AND COALESCE(pr.price_ex_vat, (
        SELECT COALESCE(pp.price_ex_vat, 0)
        FROM catalog_product_prices pp
        WHERE pp.product_id = p.id AND pp.is_active = 1 AND pp.min_qty = 1
        ORDER BY pp.effective_from DESC LIMIT 1
    ), p.selling_price, p.base_price, 0) > 0  AND (COALESCE(pr.price_ex_vat, (SELECT pp.price_ex_vat FROM catalog_product_prices pp WHERE pp.product_id = p.id AND pp.is_active = 1 AND pp.min_qty = 1 ORDER BY pp.effective_from DESC LIMIT 1), p.selling_price, p.base_price, 0) > 0)  AND (p.image_url IS NOT NULL OR EXISTS (SELECT 1 FROM catalog_product_images pi WHERE pi.product_id = p.id) OR (p.attributes_json IS NOT NULL AND (JSON_EXTRACT(p.attributes_json, '$.images') IS NOT NULL OR JSON_EXTRACT(p.attributes_json, '$.image_url') IS NOT NULL))) 
[ProductService DEBUG] allParams: [ 'ZA', 'ZA', '', 'ZA', 1 ]
