const axios = require('axios'); const config = require('../config'); const merakiAxios = axios.create({ baseURL: config.meraki.baseUrl, headers: { 'X-Cisco-Meraki-API-Key': config.meraki.apiKey, 'Content-Type': 'application/json' } }); // In-memory cache let cachedNetworks = []; let lastCacheTime = 0; const CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours async function refreshMerakiNetworksCache() { const start = Date.now(); console.log('🔄 Refreshing Meraki networks cache...'); try { const url = `/organizations/${config.meraki.orgId}/networks?perPage=5000`; const res = await merakiAxios.get(url); cachedNetworks = res.data || []; lastCacheTime = Date.now(); console.log(`✅ Cached ${cachedNetworks.length} Meraki networks (${Date.now() - start}ms)`); } catch (err) { console.error('❌ Failed to refresh Meraki networks cache:', err.message); } } async function getMerakiNetworks(forceRefresh = false) { const now = Date.now(); if (forceRefresh || cachedNetworks.length === 0 || (now - lastCacheTime > CACHE_TTL_MS)) { await refreshMerakiNetworksCache(); } return cachedNetworks; } async function findMerakiNetwork(storeNum) { if (!storeNum) return null; const raw = String(storeNum).trim(); const match = raw.match(/\d+/); if (!match) return null; let searchTerm = match[0].padStart(5, '0').slice(-5); const networks = await getMerakiNetworks(); console.log(`🔍 Searching for store "${raw}" → 5-digit term "${searchTerm}"`); 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; } } } if (bestMatch) { console.log(`✅ Best Meraki match: ${bestMatch.name} (ID: ${bestMatch.id})`); return bestMatch; } console.log(`❌ No Meraki network found for store ${storeNum}`); return null; } async function getNetworkClients(networkId) { if (!networkId) return []; try { const res = await merakiAxios.get(`/networks/${networkId}/clients?timespan=7200`); // last 2 hours return res.data || []; } catch (err) { console.error(`❌ Failed to get clients for network ${networkId}:`, err.message); return []; } } async function getAllMerakiDevices(networkId) { if (!networkId) return []; try { const res = await merakiAxios.get(`/networks/${networkId}/devices`); return res.data || []; } catch (err) { console.error(`❌ Failed to get devices for network ${networkId}:`, err.message); return []; } } async function getMerakiDeviceAvailabilities(networkId) { if (!networkId || !config.meraki.orgId) return []; try { const res = await merakiAxios.get( `/organizations/${config.meraki.orgId}/devices/availabilities`, { params: { networkIds: [networkId] } } ); return res.data || []; } catch (err) { console.error(`❌ Device availabilities failed:`, err.message); return []; } } async function getMerakiClients(networkId) { if (!networkId) return []; try { // 7 days = much better chance of catching registers & printers const timespanSeconds = 7 * 24 * 60 * 60; // 604800 const res = await merakiAxios.get( `/networks/${networkId}/clients?perPage=1000×pan=${timespanSeconds}` ); const clients = res.data || []; console.log(`✅ Fetched ${clients.length} Meraki clients (7-day timespan) [getMerakiClients]`); return clients; } catch (err) { console.error(`❌ Failed to fetch Meraki clients:`, err.message); return []; } } module.exports = { findMerakiNetwork, getNetworkClients, getMerakiClients, getAllMerakiDevices, getMerakiDeviceAvailabilities, refreshMerakiNetworksCache };