samples-js/functions/weatherMonitor.js (95 lines of code) (raw):

const { output } = require("@azure/functions"); const df = require("durable-functions"); const { DateTime } = require("luxon"); const axios = require("axios").default; const clearWeatherConditions = ["clear sky", "few clouds", "scattered clouds", "broken clouds"]; const getIsClearActivity = "getIsClear"; const sendGoodWeatherAlertActivity = "sendGoodWeatherAlert"; df.app.orchestration("weatherMonitor", function* (context) { // get input const input = context.df.getInput(); context.log(`Received monitor request. Location: ${input.location}. Phone: ${input.phone}`); verifyRequest(input); // set expiry time to 6 hours from now const endTime = DateTime.fromJSDate(context.df.currentUtcDateTime).plus({ hours: 6 }); const locationString = `${input.location.city}${ input.location.state ? `, ${input.location.state}` : "" }, ${input.location.country}`; context.log(`Instantiating monitor for ${locationString}. Expires: ${endTime.toString()}.`); // until the expiry time while (DateTime.fromJSDate(context.df.currentUtcDateTime) < endTime) { // Check the weather context.log( `Checking current weather conditions for ${locationString} at ${context.df.currentUtcDateTime}.` ); const isClear = yield context.df.callActivity(getIsClearActivity, input.location); if (isClear) { // It's not raining! Or snowing. Or misting. Tell our user to take advantage of it. context.log(`Detected clear weather for ${locationString}. Notifying ${input.phone}.`); yield context.df.callActivity(sendGoodWeatherAlertActivity, input.phone); break; } else { // Wait for the next checkpoint const nextCheckpoint = DateTime.fromJSDate(context.df.currentUtcDateTime).plus({ seconds: 30, }); context.log(`Next check for ${locationString} at ${nextCheckpoint.toString()}`); yield context.df.createTimer(nextCheckpoint.toJSDate()); } } context.log("Monitor expiring."); }); function verifyRequest(request) { if (!request) { throw new Error("An input object is required."); } if (!request.location) { throw new Error("A location input is required."); } if (!request.location.city) { throw new Error("A city is required on the location input."); } if (!request.location.country) { throw new Error("A country code is required on location input."); } if ( (request.location.country === "USA" || request.location.country === "US") && !request.location.state ) { throw new Error("A state code is required on location input for US locations."); } if (!request.phone) { throw new Error("A phone number input is required."); } } df.app.activity(getIsClearActivity, { handler: async function (location, context) { try { // get current conditions from OpenWeatherMap API const currentConditions = await getCurrentConditions(location); // compare against known clear conditions return clearWeatherConditions.includes(currentConditions.description); } catch (err) { context.log(`${getIsClearActivity} encountered an error: ${err}`); throw err; } }, }); async function getCurrentConditions(location) { try { // get current conditions from OpenWeatherMap API const url = location.state ? `https://api.openweathermap.org/data/2.5/weather?q=${location.city},${location.state},${location.country}&appid=${process.env.OpenWeatherMapApiKey}` : `https://api.openweathermap.org/data/2.5/weather?q=${location.city},${location.country}&appid=${process.env.OpenWeatherMapApiKey}`; const response = await axios.get(url); return response.data.weather[0]; } catch (err) { throw err; } } // configure twilio output const twilioOutput = output.generic({ type: "twilioSms", from: "%TwilioPhoneNumber%", accountSidSetting: "TwilioAccountSid", authTokenSetting: "TwilioAuthToken", }); df.app.activity(sendGoodWeatherAlertActivity, { extraOutputs: [twilioOutput], // register twilio output handler: function (phoneNumber, context) { // send message to phone number context.extraOutputs.set(twilioOutput, { body: "The weather's clear outside! Go talk a walk!", to: phoneNumber, }); }, });