diff --git a/WateringScaleCache.ts b/WateringScaleCache.ts new file mode 100644 index 0000000..5452505 --- /dev/null +++ b/WateringScaleCache.ts @@ -0,0 +1,69 @@ +import * as NodeCache from "node-cache"; +import { GeoCoordinates, PWS } from "./types"; +import { AdjustmentOptions } from "./routes/adjustmentMethods/AdjustmentMethod"; +import * as moment from "moment-timezone"; +import * as geoTZ from "geo-tz"; +import { Moment } from "moment-timezone/moment-timezone"; + +export default class WateringScaleCache { + private readonly cache: NodeCache = new NodeCache(); + + /** + * Stores the results of a watering scale calculation. The scale will be cached until the end of the day in the local + * timezone of the specified coordinates. If a scale has already been cached for the specified calculation parameters, + * this method will have no effect. + * @param adjustmentMethodId The ID of the AdjustmentMethod used to calculate this watering scale. This value should + * have the appropriate bits set for any restrictions that were used. + * @param coordinates The coordinates the watering scale was calculated for. + * @param pws The PWS used to calculate the watering scale, or undefined if one was not used. + * @param adjustmentOptions Any user-specified adjustment options that were used when calculating the watering scale. + * @param wateringScale The results of the watering scale calculation. + */ + public storeWateringScale( + adjustmentMethodId: number, + coordinates: GeoCoordinates, + pws: PWS, + adjustmentOptions: AdjustmentOptions, + wateringScale: CachedScale + ): void { + // The end of the day in the controller's timezone. + const expirationDate: Moment = moment().tz( geoTZ( coordinates[ 0 ], coordinates[ 1 ] )[ 0 ] ).endOf( "day" ); + const ttl: number = ( expirationDate.unix() - moment().unix() ); + const key = this.makeKey( adjustmentMethodId, coordinates, pws, adjustmentOptions ); + this.cache.set( key, wateringScale, ttl ); + } + + /** + * Retrieves a cached scale that was previously calculated with the given parameters. + * @param adjustmentMethodId The ID of the AdjustmentMethod used to calculate this watering scale. This value should + * have the appropriate bits set for any restrictions that were used. + * @param coordinates The coordinates the watering scale was calculated for. + * @param pws The PWS used to calculate the watering scale, or undefined if one was not used. + * @param adjustmentOptions Any user-specified adjustment options that were used when calculating the watering scale. + * @return The cached result of the watering scale calculation, or undefined if no values were cached. + */ + public getWateringScale( + adjustmentMethodId: number, + coordinates: GeoCoordinates, + pws: PWS, + adjustmentOptions: AdjustmentOptions + ): CachedScale | undefined { + const key = this.makeKey( adjustmentMethodId, coordinates, pws, adjustmentOptions ); + return this.cache.get( key ); + } + + private makeKey( + adjustmentMethodId: number, + coordinates: GeoCoordinates, + pws: PWS, + adjustmentOptions: AdjustmentOptions + ): string { + return `${ adjustmentMethodId }#${ coordinates.join( "," ) }#${ pws ? pws.id : "" }#${ JSON.stringify( adjustmentOptions ) }` + } +} + +export interface CachedScale { + scale: number; + rawData: object; + rainDelay: number; +} diff --git a/package-lock.json b/package-lock.json index 322f555..042b6ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "os-weather-service", - "version": "1.0.2", + "version": "1.0.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -127,6 +127,15 @@ "integrity": "sha512-Fvm24+u85lGmV4hT5G++aht2C5I4Z4dYlWZIh62FAfFO/TfzXtPpoLI6I7AuBWkIFqZCnhFOoTT7RjjaIL5Fjg==", "dev": true }, + "@types/node-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/node-cache/-/node-cache-4.1.3.tgz", + "integrity": "sha512-3hsqnv3H1zkOhjygJaJUYmgz5+FcPO3vejBX7cE9/cnuINOJYrzkfOnUCvpwGe9kMZANIHJA7J5pOdeyv52OEw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", @@ -571,6 +580,11 @@ "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", "dev": true }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -1126,8 +1140,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -1148,14 +1161,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1170,20 +1181,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -1300,8 +1308,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -1313,7 +1320,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1328,7 +1334,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1336,14 +1341,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1362,7 +1365,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -1443,8 +1445,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -1456,7 +1457,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -1542,8 +1542,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -1579,7 +1578,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1599,7 +1597,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1643,14 +1640,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -2091,8 +2086,7 @@ "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lowercase-keys": { "version": "1.0.1", @@ -2465,6 +2459,15 @@ } } }, + "node-cache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-4.2.0.tgz", + "integrity": "sha512-obRu6/f7S024ysheAjoYFEEBqqDWv4LOMNJEuO8vMeEw2AT4z+NCzO4hlc2lhI4vATzbCQv6kke9FVdx0RbCOw==", + "requires": { + "clone": "2.x", + "lodash": "4.x" + } + }, "node-watch": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.6.2.tgz", diff --git a/package.json b/package.json index 0ce5cd2..fa2f69f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "geo-tz": "^5.0.4", "mockdate": "^2.0.2", "moment-timezone": "^0.5.25", + "node-cache": "^4.2.0", "suncalc": "^1.8.0" }, "devDependencies": { @@ -30,6 +31,7 @@ "@types/mocha": "^5.2.6", "@types/moment-timezone": "^0.5.12", "@types/node": "^10.14.6", + "@types/node-cache": "^4.1.3", "@types/suncalc": "^1.8.0", "chai": "^4.2.0", "mocha": "^5.2.0", diff --git a/routes/adjustmentMethods/AdjustmentMethod.ts b/routes/adjustmentMethods/AdjustmentMethod.ts index 3416a2a..514dabe 100644 --- a/routes/adjustmentMethods/AdjustmentMethod.ts +++ b/routes/adjustmentMethods/AdjustmentMethod.ts @@ -56,4 +56,9 @@ export interface AdjustmentMethodResponse { wateringData: BaseWateringData; } -export interface AdjustmentOptions {} +export interface AdjustmentOptions { + /** The ID of the PWS to use, prefixed with "pws:". */ + pws?: string; + /** The API key to use to access PWS data. */ + key?: string; +} diff --git a/routes/weather.spec.ts b/routes/weather.spec.ts index c2bdf0d..961e7ba 100644 --- a/routes/weather.spec.ts +++ b/routes/weather.spec.ts @@ -4,6 +4,10 @@ import * as MockExpressRequest from 'mock-express-request'; import * as MockExpressResponse from 'mock-express-response'; import * as MockDate from 'mockdate'; +// The tests don't use OWM, but the WeatherProvider API key must be set to prevent an error from being thrown on startup. +process.env.WEATHER_PROVIDER = "OWM"; +process.env.OWM_API_KEY = "NO_KEY"; + import { getWateringData } from './weather'; import { GeoCoordinates, WeatherData, ZimmermanWateringData } from "../types"; import { WeatherProvider } from "./weatherProviders/WeatherProvider"; diff --git a/routes/weather.ts b/routes/weather.ts index d993876..e744186 100644 --- a/routes/weather.ts +++ b/routes/weather.ts @@ -8,6 +8,7 @@ import * as geoTZ from "geo-tz"; import { BaseWateringData, GeoCoordinates, PWS, TimeData, WeatherData } from "../types"; import { WeatherProvider } from "./weatherProviders/WeatherProvider"; import { AdjustmentMethod, AdjustmentMethodResponse, AdjustmentOptions } from "./adjustmentMethods/AdjustmentMethod"; +import WateringScaleCache, { CachedScale } from "../WateringScaleCache"; import ManualAdjustmentMethod from "./adjustmentMethods/ManualAdjustmentMethod"; import ZimmermanAdjustmentMethod from "./adjustmentMethods/ZimmermanAdjustmentMethod"; import RainDelayAdjustmentMethod from "./adjustmentMethods/RainDelayAdjustmentMethod"; @@ -32,6 +33,8 @@ const ADJUSTMENT_METHOD: { [ key: number ] : AdjustmentMethod } = { 3: EToAdjustmentMethod }; +const cache = new WateringScaleCache(); + /** * Resolves a location description to geographic coordinates. * @param location A partial zip/city/country or a coordinate pair. @@ -184,7 +187,6 @@ export const getWateringData = async function( req: express.Request, res: expres location: string | GeoCoordinates = getParameter(req.query.loc), outputFormat: string = getParameter(req.query.format), remoteAddress: string = getParameter(req.headers[ "x-forwarded-for" ]) || req.connection.remoteAddress, - pwsString: string = getParameter( req.query.pws ), adjustmentOptions: AdjustmentOptions; // X-Forwarded-For header may contain more than one IP address and therefore @@ -224,67 +226,108 @@ export const getWateringData = async function( req: express.Request, res: expres // Parse the PWS information. let pws: PWS | undefined = undefined; - if ( pwsString ) { - try { - pws = parsePWS( pwsString ); - } catch ( err ) { - res.send( `Error: ${ err }` ); + if ( adjustmentOptions.pws ) { + if ( !adjustmentOptions.key ) { + res.send("Error: An API key must be provided when using a PWS."); return; } + + const idMatch = adjustmentOptions.pws.match( /^pws:([a-zA-Z\d]+)$/ ); + const pwsId = idMatch ? idMatch[ 1 ] : undefined; + const keyMatch = adjustmentOptions.key.match( /^[a-f\d]{32}$/ ); + const apiKey = keyMatch ? keyMatch[ 0 ] : undefined; + + // Make sure that the PWS ID and API key look valid. + if ( !pwsId ) { + res.send("Error: PWS ID does not appear to be valid."); + return; + } + if ( !apiKey ) { + res.send("Error: PWS API key does not appear to be valid."); + return; + } + + pws = { id: pwsId, apiKey: apiKey }; } const weatherProvider = pws ? PWS_WEATHER_PROVIDER : WEATHER_PROVIDER; - let adjustmentMethodResponse: AdjustmentMethodResponse; - try { - adjustmentMethodResponse = await adjustmentMethod.calculateWateringScale( - adjustmentOptions, coordinates, weatherProvider, pws - ); - } catch ( err ) { - if ( typeof err != "string" ) { - /* If an error occurs under expected circumstances (e.g. required optional fields from a weather API are - missing), an AdjustmentOption must throw a string. If a non-string error is caught, it is likely an Error - thrown by the JS engine due to unexpected circumstances. The user should not be shown the error message - since it may contain sensitive information. */ - res.send( "Error: an unexpected error occurred." ); - console.error( `An unexpected error occurred for ${ req.url }: `, err ); - } else { - res.send( "Error: " + err ); - } - - return; - } - - let scale = adjustmentMethodResponse.scale; - - if ( checkRestrictions ) { - let wateringData: BaseWateringData = adjustmentMethodResponse.wateringData; - // Fetch the watering data if the AdjustmentMethod didn't fetch it and restrictions are being checked. - if ( checkRestrictions && !wateringData ) { - try { - wateringData = await weatherProvider.getWateringData( coordinates ); - } catch ( err ) { - res.send( "Error: " + err ); - return; - } - } - - // Check for any user-set restrictions and change the scale to 0 if the criteria is met - if ( checkWeatherRestriction( req.params[ 0 ], wateringData ) ) { - scale = 0; - } - } const data = { - scale: scale, - rd: adjustmentMethodResponse.rainDelay, + scale: undefined, + rd: undefined, tz: getTimezone( timeData.timezone, undefined ), sunrise: timeData.sunrise, sunset: timeData.sunset, eip: ipToInt( remoteAddress ), - rawData: adjustmentMethodResponse.rawData, - error: adjustmentMethodResponse.errorMessage + rawData: undefined, + error: undefined }; + let cachedScale: CachedScale; + if ( weatherProvider.shouldCacheWateringScale() ) { + cachedScale = cache.getWateringScale( req.params[ 0 ], coordinates, pws, adjustmentOptions ); + } + + if ( cachedScale ) { + // Use the cached data if it exists. + data.scale = cachedScale.scale; + data.rawData = cachedScale.rawData; + data.rd = cachedScale.rainDelay; + } else { + // Calculate the watering scale if it wasn't found in the cache. + let adjustmentMethodResponse: AdjustmentMethodResponse; + try { + adjustmentMethodResponse = await adjustmentMethod.calculateWateringScale( + adjustmentOptions, coordinates, weatherProvider, pws + ); + } catch ( err ) { + if ( typeof err != "string" ) { + /* If an error occurs under expected circumstances (e.g. required optional fields from a weather API are + missing), an AdjustmentOption must throw a string. If a non-string error is caught, it is likely an Error + thrown by the JS engine due to unexpected circumstances. The user should not be shown the error message + since it may contain sensitive information. */ + res.send( "Error: an unexpected error occurred." ); + console.error( `An unexpected error occurred for ${ req.url }: `, err ); + } else { + res.send( "Error: " + err ); + } + + return; + } + + data.scale = adjustmentMethodResponse.scale; + data.error = adjustmentMethodResponse.errorMessage; + data.rd = adjustmentMethodResponse.rainDelay; + data.rawData = adjustmentMethodResponse.rawData; + + if ( checkRestrictions ) { + let wateringData: BaseWateringData = adjustmentMethodResponse.wateringData; + // Fetch the watering data if the AdjustmentMethod didn't fetch it and restrictions are being checked. + if ( checkRestrictions && !wateringData ) { + try { + wateringData = await weatherProvider.getWateringData( coordinates ); + } catch ( err ) { + res.send( "Error: " + err ); + return; + } + } + + // Check for any user-set restrictions and change the scale to 0 if the criteria is met + if ( checkWeatherRestriction( req.params[ 0 ], wateringData ) ) { + data.scale = 0; + } + } + + // Cache the watering scale if caching is enabled and no error occurred. + if ( weatherProvider.shouldCacheWateringScale() && !data.error ) { + cache.storeWateringScale( req.params[ 0 ], coordinates, pws, adjustmentOptions, { + scale: data.scale, + rawData: data.rawData, + rainDelay: data.rd + } ); + } + } + // Return the response to the client in the requested format if ( outputFormat === "json" ) { res.json( data ); @@ -452,21 +495,3 @@ export function getParameter( parameter: string | string[] ): string { // Return an empty string if the parameter is undefined. return parameter || ""; } - -/** - * Creates a PWS object from a string. - * @param pwsString Information about the PWS in the format "pws:API_KEY@PWS_ID". - * @return The PWS specified by the string. - * @throws Throws an error message if the string is in an invalid format and cannot be parsed. - */ -function parsePWS( pwsString: string): PWS { - const match = pwsString.match( /^pws:([a-f\d]{32})@([a-zA-Z\d]+)$/ ); - if ( !match ) { - throw "Invalid PWS format."; - } - - return { - apiKey: match[ 1 ], - id: match[ 2 ] - }; -} diff --git a/routes/weatherProviders/DarkSky.ts b/routes/weatherProviders/DarkSky.ts index 7cfb637..933aee2 100644 --- a/routes/weatherProviders/DarkSky.ts +++ b/routes/weatherProviders/DarkSky.ts @@ -7,38 +7,36 @@ import { approximateSolarRadiation, CloudCoverInfo, EToData } from "../adjustmen export default class DarkSkyWeatherProvider extends WeatherProvider { + private readonly API_KEY: string; + + public constructor() { + super(); + this.API_KEY = process.env.DARKSKY_API_KEY; + if (!this.API_KEY) { + throw "DARKSKY_API_KEY environment variable is not defined."; + } + } + public async getWateringData( coordinates: GeoCoordinates ): Promise< ZimmermanWateringData > { // The Unix timestamp of 24 hours ago. const yesterdayTimestamp: number = moment().subtract( 1, "day" ).unix(); - const todayTimestamp: number = moment().unix(); - const DARKSKY_API_KEY = process.env.DARKSKY_API_KEY, - yesterdayUrl = `https://api.darksky.net/forecast/${ DARKSKY_API_KEY }/${ coordinates[ 0 ] },${ coordinates[ 1 ] },${ yesterdayTimestamp }?exclude=currently,minutely,daily,alerts,flags`, - todayUrl = `https://api.darksky.net/forecast/${ DARKSKY_API_KEY }/${ coordinates[ 0 ] },${ coordinates[ 1 ] },${ todayTimestamp }?exclude=currently,minutely,daily,alerts,flags`; + const yesterdayUrl = `https://api.darksky.net/forecast/${ this.API_KEY }/${ coordinates[ 0 ] },${ coordinates[ 1 ] },${ yesterdayTimestamp }?exclude=currently,minutely,daily,alerts,flags`; - let yesterdayData, todayData; + let yesterdayData; try { yesterdayData = await httpJSONRequest( yesterdayUrl ); - todayData = await httpJSONRequest( todayUrl ); } catch ( err ) { console.error( "Error retrieving weather information from Dark Sky:", err ); throw "An error occurred while retrieving weather information from Dark Sky." } - if ( !todayData.hourly || !todayData.hourly.data || !yesterdayData.hourly || !yesterdayData.hourly.data ) { + if ( !yesterdayData.hourly || !yesterdayData.hourly.data ) { throw "Necessary field(s) were missing from weather information returned by Dark Sky."; } - /* The number of hourly forecasts to use from today's data. This will only include elements that contain historic - data (not forecast data). */ - // Find the first element that contains forecast data. - const todayElements = Math.min( 24, todayData.hourly.data.findIndex( ( data ) => data.time > todayTimestamp - 60 * 60 ) ); - - /* Take as much data as possible from the first elements of today's data and take the remaining required data from - the remaining data from the last elements of yesterday's data. */ const samples = [ - ...yesterdayData.hourly.data.slice( todayElements - 24 ), - ...todayData.hourly.data.slice( 0, todayElements ) + ...yesterdayData.hourly.data ]; // Fail if not enough data is available. @@ -70,8 +68,7 @@ export default class DarkSkyWeatherProvider extends WeatherProvider { } public async getWeatherData( coordinates: GeoCoordinates ): Promise< WeatherData > { - const DARKSKY_API_KEY = process.env.DARKSKY_API_KEY, - forecastUrl = `https://api.darksky.net/forecast/${ DARKSKY_API_KEY }/${ coordinates[ 0 ] },${ coordinates[ 1 ] }?exclude=minutely,alerts,flags`; + const forecastUrl = `https://api.darksky.net/forecast/${ this.API_KEY }/${ coordinates[ 0 ] },${ coordinates[ 1 ] }?exclude=minutely,alerts,flags`; let forecast; try { @@ -163,4 +160,8 @@ export default class DarkSkyWeatherProvider extends WeatherProvider { precip: ( historicData.daily.data[ 0 ].precipIntensity || 0 ) * 24 }; } + + public shouldCacheWateringScale(): boolean { + return true; + } } diff --git a/routes/weatherProviders/OWM.ts b/routes/weatherProviders/OWM.ts index f09e23e..cf109c6 100644 --- a/routes/weatherProviders/OWM.ts +++ b/routes/weatherProviders/OWM.ts @@ -6,9 +6,18 @@ import * as moment from "moment"; export default class OWMWeatherProvider extends WeatherProvider { + private readonly API_KEY: string; + + public constructor() { + super(); + this.API_KEY = process.env.OWM_API_KEY; + if (!this.API_KEY) { + throw "OWM_API_KEY environment variable is not defined."; + } + } + public async getWateringData( coordinates: GeoCoordinates ): Promise< ZimmermanWateringData > { - const OWM_API_KEY = process.env.OWM_API_KEY, - forecastUrl = "http://api.openweathermap.org/data/2.5/forecast?appid=" + OWM_API_KEY + "&units=imperial&lat=" + coordinates[ 0 ] + "&lon=" + coordinates[ 1 ]; + const forecastUrl = `http://api.openweathermap.org/data/2.5/forecast?appid=${ this.API_KEY }&units=imperial&lat=${ coordinates[ 0 ] }&lon=${ coordinates[ 1 ] }`; // Perform the HTTP request to retrieve the weather data let forecast; @@ -45,9 +54,8 @@ export default class OWMWeatherProvider extends WeatherProvider { } public async getWeatherData( coordinates: GeoCoordinates ): Promise< WeatherData > { - const OWM_API_KEY = process.env.OWM_API_KEY, - currentUrl = "http://api.openweathermap.org/data/2.5/weather?appid=" + OWM_API_KEY + "&units=imperial&lat=" + coordinates[ 0 ] + "&lon=" + coordinates[ 1 ], - forecastDailyUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?appid=" + OWM_API_KEY + "&units=imperial&lat=" + coordinates[ 0 ] + "&lon=" + coordinates[ 1 ]; + const currentUrl = `http://api.openweathermap.org/data/2.5/weather?appid=${ this.API_KEY }&units=imperial&lat=${ coordinates[ 0 ] }&lon=${ coordinates[ 1 ] }`, + forecastDailyUrl = `http://api.openweathermap.org/data/2.5/forecast/daily?appid=${ this.API_KEY }&units=imperial&lat=${ coordinates[ 0 ] }&lon=${ coordinates[ 1 ] }`; let current, forecast; try { diff --git a/routes/weatherProviders/WeatherProvider.ts b/routes/weatherProviders/WeatherProvider.ts index 6956a8e..e837d6a 100644 --- a/routes/weatherProviders/WeatherProvider.ts +++ b/routes/weatherProviders/WeatherProvider.ts @@ -36,4 +36,13 @@ export class WeatherProvider { getEToData( coordinates: GeoCoordinates ): Promise< EToData > { throw "Selected WeatherProvider does not support getEToData"; }; + + /** + * Returns a boolean indicating if watering scales calculated using data from this WeatherProvider should be cached + * until the end of the day in timezone the data was for. + * @return a boolean indicating if watering scales calculated using data from this WeatherProvider should be cached. + */ + shouldCacheWateringScale(): boolean { + return false; + } }