diff --git a/src/integrations/schema.js b/src/integrations/schema.js index 4b9079a..2c5958e 100644 --- a/src/integrations/schema.js +++ b/src/integrations/schema.js @@ -60,7 +60,13 @@ export const INTEGRATIONS = { met_office: { name: 'Met Office DataHub', configFields: [], - secretFields: ['api_key'], + // Met Office DataHub sells the "Global Spot" and "Forecast" site-specific + // products as separate subscriptions, each with its own API key — api_key is + // Spot (used for the daily endpoint, confirmed live via hvac); forecast_api_key + // is optional and, if set, is what weather's hourly/three-hourly endpoints try + // first (falling back to api_key, then to Open-Meteo, per tier — see weather's + // lib/weather-fetch.js). Leave it blank if you only have a Spot subscription. + secretFields: ['api_key', 'forecast_api_key'], }, } diff --git a/src/routes/integrations.js b/src/routes/integrations.js index 76ac858..94251d9 100644 --- a/src/routes/integrations.js +++ b/src/routes/integrations.js @@ -171,12 +171,26 @@ export async function integrationRoutes(app) { const { rows: [loc] } = await pool.query(`SELECT config FROM integrations WHERE slug = 'hotel_location'`) const lat = loc?.config?.latitude || '51.5072' const lon = loc?.config?.longitude || '-0.1276' - const res = await fetch( + // Two separate subscriptions, two separate keys — test each against the + // endpoint weather actually uses it for (Spot -> daily, Forecast -> + // hourly). forecast_api_key is optional, only tested if set. + const errors = [] + const dailyRes = await fetch( `https://data.hub.api.metoffice.gov.uk/sitespecific/v0/point/daily?latitude=${lat}&longitude=${lon}`, { headers: { apikey: creds.api_key }, signal: AbortSignal.timeout(8000) } ) - if (res.status === 401 || res.status === 403) throw new Error('Invalid Met Office API key') - if (!res.ok) throw new Error(`Met Office DataHub returned ${res.status}`) + if (dailyRes.status === 401 || dailyRes.status === 403) errors.push('Spot key: invalid or not subscribed to the daily endpoint') + else if (!dailyRes.ok) errors.push(`Spot key: daily endpoint returned ${dailyRes.status}`) + + if (creds.forecast_api_key) { + const hourlyRes = await fetch( + `https://data.hub.api.metoffice.gov.uk/sitespecific/v0/point/hourly?latitude=${lat}&longitude=${lon}`, + { headers: { apikey: creds.forecast_api_key }, signal: AbortSignal.timeout(8000) } + ) + if (hourlyRes.status === 401 || hourlyRes.status === 403) errors.push('Forecast key: invalid or not subscribed to the hourly endpoint') + else if (!hourlyRes.ok) errors.push(`Forecast key: hourly endpoint returned ${hourlyRes.status}`) + } + if (errors.length) throw new Error(errors.join(' | ')) } else if (req.params.slug === 'sambapos') { const errors = []