// src/integrations/meraki/networks.js import { fetchAllPages } from './client.js'; import { logger } from '../../utils/logger.js'; // In-memory cache let cachedNetworks = []; let lastCacheTime = 0; const CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours /** * Refresh the full list of networks (called by cron or on cache miss) */ export async function refreshMerakiNetworksCache() { const start = Date.now(); logger('meraki:networks', 'Refreshing cache', 'debug'); try { const url = `/organizations/${process.env.MERAKI_ORG_ID}/networks?perPage=5000`; cachedNetworks = await fetchAllPages(url); lastCacheTime = Date.now(); logger('meraki:networks', `Cached ${cachedNetworks.length} networks (${Date.now() - start} ms)`, 'debug'); } catch (err) { logger('meraki:networks', `Cache refresh failed: ${err.message}`, 'warn'); // Keep old cache if it exists } } /** * Get cached networks (auto-refresh if stale or empty) */ export async function getMerakiNetworks(forceRefresh = false) { const now = Date.now(); if (forceRefresh || !cachedNetworks.length || (now - lastCacheTime > CACHE_TTL_MS)) { await refreshMerakiNetworksCache(); } return cachedNetworks; } /** * Find Meraki network by store number — returns the FULL network object (contains .url, .name, .id, etc.) * @param {string|number} storeNum * @returns {object|null} full network object or null */ export async function findMerakiNetwork(storeNum) { // ← renamed for clarity if (!storeNum) return null; const raw = String(storeNum).trim(); let searchTerm = raw.match(/\d+/)[0]; searchTerm = searchTerm.padStart(5, '0').slice(-5); const networks = await getMerakiNetworks(); logger('meraki:find', `Searching store "${raw}" → using 5-digit term "${searchTerm}"`, 'debug'); let bestMatch = null; let bestScore = -1; for (const net of networks) { const name = (net.name || '').toLowerCase(); const term = searchTerm.toLowerCase(); if (name.includes(term)) { const score = (name.includes(` ${term}`) || name.includes(`-${term}`) || name.includes(term)) ? 100 : 50; if (score > bestScore) { bestScore = score; bestMatch = net; // ← full object } } } if (bestMatch) { logger('meraki:find', `✅ Best match: ${bestMatch.name} (ID: ${bestMatch.id})`, 'debug'); logger('meraki:networks', `Best match for store ${storeNum}: ${bestMatch?.name || 'none'}`, 'debug'); return bestMatch; // ← return full network } logger('meraki:find', `❌ No match found for 5-digit term "${searchTerm}"`, 'warn'); return null; }