Today's post isn't necessarily too interesting code-wise, but it touches upon some greater, more broad, serverless topics that I'd like to bring up. A few weeks ago I discovered an interesting GitHub repository: https://github.com/gadael/icsdb.
This repository contains iCal files (think plain text calendar data) for non-working days for all 50 US states and various European countries. This could be useful in a number of ways, if, of course, you can parse the iCal data. I thought it might be interesting to build a simple service that would take a URL pointing to iCal data and return the information in JSON form.
There are multiple different ways of parsing iCal, but I felt ical.js by Mozilla was good enough. The ical.js library is pretty complex, letting you work with iCal data like a component, calling different methods to find data, but it can also just return a simple parsed version of the text. Here is the code I came up with:
const rp = require('request-promise');
const ical = require('ical.js');
function flattenEvent(e) {
let event = {};
for(let i=0;i<e[1].length;i++) {
let prop = e[1][i];
event[prop[0]] = prop[3];
//console.log('e',prop);
}
return event;
}
exports.main = (args) => {
return new Promise((resolve, reject) => {
rp(args.url).then((txt) => {
try {
let parsed = ical.parse(txt);
let events = parsed[2];
let result = [];
events.forEach(e => result.push(flattenEvent(e)));
resolve({events:result});
} catch(e) {
console.log(e);
reject(e.message);
}
})
.catch((e) => {
reject(e);
});
});
}
Basically - suck down the remote URL and parse using the library. As I said, there is a nice "object based" API that the library provides, but I found I could work with the initial data a bit easier. You can see where I just grab the third item in the array to get the actual events. I then "flatten" the data using flattenEvent
. If your curious, the data is in a form called jCal, which has a specification: https://tools.ietf.org/html/draft-ietf-jcardcal-jcal-10. My flattenEvent
function makes some assumptions that may not necessarily always work out well, but so far it's done ok.
I tested it by asking for Louisiana holidays:
wsk action invoke ical/get --param url https://raw.githubusercontent.com/gadael/icsdb/master/build/en-US/us-louisiana-nonworkingdays.ics -r
And here are the top five results.
{
"class": "PUBLIC",
"created": "2014-01-09T00:47:56Z",
"description": "",
"dtend": "1970-01-02",
"dtstamp": "2017-06-23T13:53:48Z",
"dtstart": "1970-01-01",
"last-modified": "2017-06-23T13:53:48Z",
"rrule": {
"freq": "YEARLY"
},
"sequence": 0,
"status": "CONFIRMED",
"summary": "New Year's Day",
"transp": "TRANSPARENT",
"uid": "b901ca08-d924-43c3-9166-1d215c9453d6"
},
{
"class": "PUBLIC",
"created": "2014-01-09T00:47:56Z",
"description": "",
"dtend": "1983-01-02",
"dtstamp": "2017-06-23T13:53:48Z",
"dtstart": "1983-01-01",
"last-modified": "2017-06-23T13:53:48Z",
"rrule": {
"byday": "3MO",
"freq": "YEARLY"
},
"sequence": 0,
"status": "CONFIRMED",
"summary": "Marthin Luther King day/Robert E. Lee day",
"transp": "TRANSPARENT",
"uid": "0ae8128a-e360-492c-b2bd-52ed0d6d06fd"
},
{
"categories": "-New Mexico",
"class": "PUBLIC",
"created": "2014-01-09T00:47:56Z",
"description": "",
"dtend": "1970-02-01",
"dtstamp": "2017-06-23T13:53:48Z",
"dtstart": "1970-02-01",
"last-modified": "2017-06-23T13:53:48Z",
"rrule": {
"byday": "3MO",
"freq": "YEARLY"
},
"sequence": 0,
"status": "CONFIRMED",
"summary": "Presidents Day",
"transp": "TRANSPARENT",
"uid": "17425d41-9ed3-4088-adad-4693d1bd44c9"
},
{
"categories": "Louisiana",
"class": "PUBLIC",
"created": "2014-01-09T00:47:56Z",
"description": "",
"dtend": "1970-04-02",
"dtstamp": "2017-06-23T13:53:48Z",
"dtstart": "1970-04-01",
"last-modified": "2017-06-23T13:53:48Z",
"rdate": "1970-02-10",
"sequence": 0,
"status": "CONFIRMED",
"summary": "Mardi gras",
"transp": "TRANSPARENT",
"uid": "7ac45e93-a684-4061-a0c7-948a89e358b0"
},
{
"categories": "Connecticut",
"class": "PUBLIC",
"created": "2014-01-09T00:47:56Z",
"description": "",
"dtend": "1970-04-09",
"dtstamp": "2017-06-23T13:53:48Z",
"dtstart": "1970-04-08",
"last-modified": "2017-06-23T13:53:48Z",
"rdate": "1970-03-26",
"sequence": 0,
"status": "CONFIRMED",
"summary": "Good Friday",
"transp": "TRANSPARENT",
"uid": "3c46243f-00f8-418f-94cf-4eda72ae7cb2"
}
So... that's some data. Cool. What would I do next? When I first started thinking about this data, my first thought is that it could be a useful API for web apps. I'm a web developer so everything looks like a web-related source for me. Of course, why would I need serverless for that? I could run the iCal library in the browser - the only issue I'd run into is CORS - maybe. And I could always copy the iCal files to the same server as the app. But that's really limited thinking.
By creating this as a serverless action, I've opened it up to any event source, web or not. How about an example of something completely non-web based?
Imagine a support service where every day, my process gets a list of employees responsible for IT work in case of emergencies. I could imagine a process by which after getting a list of employees, I could check each one's home state against a list of holidays for that state and determine if that person is off today. It's the exact same code, but I'm simply using it in a different context.
And this is something important to keep in mind here. Serverless isn't just about building NodeJS apps simpler. Instead I'm creating a resource that can be used in multiple situations, both direct (web app making a request) and indirect (a process run on a timed schedule).
This is something I've talked about before, but I feel like it's something that's possibly not quite as evident.
p.s. You can find the source code for this demo here: https://github.com/cfjedimaster/Serverless-Examples/blob/master/ical/get.js