174 | 'tagName': 'div',
175 | 'classNames': [(isUser ? 'from-user' : 'from-watson'), 'latest', ((messageArray.length === 0) ? 'top' : 'sub')],
176 | 'children': [{
177 | //
178 | 'tagName': 'div',
179 | 'classNames': ['message-inner'],
180 | 'children': [{
181 | //
{messageText}
182 | 'tagName': 'p',
183 | 'text': currentText
184 | }]
185 | }]
186 | }]
187 | };
188 | messageArray.push(Common.buildDomElement(messageJson));
189 | }
190 | });
191 |
192 | return messageArray;
193 | }
194 |
195 | // Scroll to the bottom of the chat window (to the most recent messages)
196 | // Note: this method will bring the most recent user message into view,
197 | // even if the most recent message is from Watson.
198 | // This is done so that the "context" of the conversation is maintained in the view,
199 | // even if the Watson message is long.
200 | function scrollToChatBottom() {
201 | var scrollingChat = document.querySelector('#scrollingChat');
202 |
203 | // Scroll to the latest message sent by the user
204 | var scrollEl = scrollingChat.querySelector(settings.selectors.fromUser +
205 | settings.selectors.latest);
206 | if (scrollEl) {
207 | scrollingChat.scrollTop = scrollEl.offsetTop;
208 | }
209 | }
210 |
211 | // Handles the submission of input
212 | function inputKeyDown(event, inputBox) {
213 | // Submit on enter key, dis-allowing blank messages
214 | if (event.keyCode === 13 && inputBox.value) {
215 | // Retrieve the context from the previous server response
216 | var context;
217 | var latestResponse = Api.getResponsePayload();
218 | if (latestResponse) {
219 | context = latestResponse.context;
220 | }
221 |
222 | // Send the user message
223 | Api.sendRequest(inputBox.value, context);
224 |
225 | // Clear input box for further messages
226 | inputBox.value = '';
227 | Common.fireEvent(inputBox, 'input');
228 | }
229 | }
230 | }());
--------------------------------------------------------------------------------
/main_application/public/js/global.js:
--------------------------------------------------------------------------------
1 | /* global ConversationPanel: true */
2 | /* eslint no-unused-vars: "off" */
3 |
4 | // Other JS files required to be loaded first: apis.js, conversation.js
5 | (function() {
6 | // Initialize all modules
7 | ConversationPanel.init();
8 | })();
9 |
10 | // nasty globals for state
11 | var tripRetrieved = false;
12 | var alternatesRetrieved = false;
13 | var problemState = {};
14 |
15 | // this function handles responses from the Watson conversation service
16 | // and optionally uses intents and e
17 | function handleResponseMessage(incomingMsg) {
18 | var response = incomingMsg;
19 | for (var i = 0; i < response.intents.length; i++) {
20 | var intent = response.intents[i];
21 | var handled = false;
22 | switch (intent.intent) {
23 | case "when":
24 | if (intent.confidence > 0.5) {
25 | response = handleWhen(response);
26 | handled = true;
27 | } else {
28 | // not enough confidence to guess "when"..ask for more
29 | response.output.text = "Can you rephrase what you are looking for?";
30 | }
31 | break;
32 | case "alternate":
33 | if (intent.confidence > 0.5) {
34 | response = handleAlternate(response);
35 | handled = true;
36 | break;
37 | } else {
38 | // not enough confidence to guess "when"..ask for more
39 | response.output.text = "Can you rephrase what you are looking for?";
40 | }
41 | break;
42 | case "problem":
43 | if (intent.confidence > 0.5) {
44 | response = handleProblem(intent, response);
45 | handled = true;
46 | break;
47 | } else {
48 | // not enough confidence to guess "when"..ask for more
49 | response.output.text = "Can you rephrase what you are looking for?";
50 | }
51 | break;
52 | }
53 | if (handled) {
54 | break;
55 | }
56 | }
57 | return response;
58 | }
59 |
60 | function handleWhen(response) {
61 | if (tripRetrieved === true) {
62 | $('#flight-alternates').css("display", "none");
63 | $('#flight-results').css("display", "block");
64 | } else {
65 | parseTripsForFlights(false, false);
66 | tripRetrieved = true;
67 | }
68 | response.output.text = "I've loaded your next trip details for you.";
69 | return response;
70 | }
71 |
72 | function handleAlternate(response) {
73 | if (!tripRetrieved) {
74 | parseTripsForFlights(false, true);
75 | tripRetrieved = true;
76 | }
77 | $('#flight-results').css("display", "none");
78 | if (alternatesRetrieved) {
79 | $('#flight-alternates').css("display", "block");
80 | } else {
81 | $('#flight-alternates').trigger('instantiate');
82 | alternatesRetrieved = true;
83 | }
84 | response.output.text = "I've loaded alternate flights between your origin and destination for you.";
85 | return response;
86 | }
87 |
88 | function handleProblem(intent, response) {
89 | // first check if the conversation service has a response (to clarify information):
90 | if (response.output.text.length > 0) {
91 | problemState = response;
92 | return response;
93 | }
94 | var airport = "";
95 | if (response.entities.length > 0) {
96 | if (response.entities[0].entity === "Airport") {
97 | // user has responded with an airport:
98 | airport = response.entities[0].value; // this will be the official entity name
99 | }
100 | }
101 | if (airport !== "") {
102 | var childDiv = $("#flight-alternates").children("div");
103 | response.output.text = "Let me see if I can help you get from " + airport + " to your destination via an alternate flight.";
104 | // BIG FIXME: Due to time constraints, for our DockerCon demo with the
105 | // conversation service, we hardcoded Austin and the date to show a
106 | // lookup specific to a story we were telling about Lin's canceled flight
107 | // FIX: actually parse the known trip JSON to find where the traveler is
108 | // going and use the date of "now" or that original flight date/time to
109 | // do the alternate search.
110 | outputAlternateFlights(childDiv[0], airportCode(airport), "AUS", "2017-04-18T13:00:00-05:00", 12, 15);
111 | } else {
112 | response.output.text = "I see you are having flight problems.";
113 | }
114 | return response;
115 | }
116 |
117 | function airportCode(cityEntity) {
118 | switch (cityEntity) {
119 | case "Dallas":
120 | return "DFW";
121 | case "Austin":
122 | return "AUS";
123 | case "Chicago":
124 | return "ORD";
125 | case "Raleigh":
126 | return "RDU";
127 | case "Charlotte":
128 | return "CLT";
129 | case "Charlottesville":
130 | return "CHO";
131 | default:
132 | return "Unknown";
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/main_application/public/js/ie/backgroundsize.min.htc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/main_application/public/js/ie/html5shiv.js:
--------------------------------------------------------------------------------
1 | /*
2 | HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
3 | */
4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x";
6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML=" ";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b1){for(o=0;o 0) {
80 |
81 | var $header_header = $header.find('header');
82 |
83 | $window
84 | .on('resize.overflow_fsh', function() {
85 |
86 | if (skel.breakpoint('mobile').active)
87 | $header.css('padding', '');
88 | else {
89 |
90 | var p = Math.max(192, ($window.height() - $header_header.outerHeight()) / 2);
91 | $header.css('padding', p + 'px 0 ' + p + 'px 0');
92 |
93 | }
94 |
95 | })
96 | .trigger('resize.overflow_fsh');
97 |
98 | $window.load(function() {
99 | $window.trigger('resize.overflow_fsh');
100 | });
101 |
102 | }
103 |
104 | }
105 |
106 | // Parallax background.
107 |
108 | // Disable parallax on IE (smooth scrolling is jerky), and on mobile platforms (= better performance).
109 | if (skel.vars.browser == 'ie'
110 | || skel.vars.mobile)
111 | settings.parallax = false;
112 |
113 | if (settings.parallax) {
114 |
115 | var $dummy = $(), $bg;
116 |
117 | $window
118 | .on('scroll.overflow_parallax', function() {
119 |
120 | // Adjust background position.
121 | $bg.css('background-position', 'center ' + (-1 * (parseInt($window.scrollTop()) / settings.parallaxFactor)) + 'px');
122 |
123 | })
124 | .on('resize.overflow_parallax', function() {
125 |
126 | // If we're in a situation where we need to temporarily disable parallax, do so.
127 | if (!skel.breakpoint('wide').active
128 | || skel.breakpoint('narrow').active) {
129 |
130 | $body.css('background-position', '');
131 | $bg = $dummy;
132 |
133 | }
134 |
135 | // Otherwise, continue as normal.
136 | else
137 | $bg = $body;
138 |
139 | // Trigger scroll handler.
140 | $window.triggerHandler('scroll.overflow_parallax');
141 |
142 | })
143 | .trigger('resize.overflow_parallax');
144 | }
145 | });
146 |
147 | })(jQuery);
148 |
--------------------------------------------------------------------------------
/main_application/public/js/skel.min.js:
--------------------------------------------------------------------------------
1 | /* skel.js v3.0.1 | (c) skel.io | MIT licensed */
2 | var skel=function(){"use strict";var t={breakpointIds:null,events:{},isInit:!1,obj:{attachments:{},breakpoints:{},head:null,states:{}},sd:"/",state:null,stateHandlers:{},stateId:"",vars:{},DOMReady:null,indexOf:null,isArray:null,iterate:null,matchesMedia:null,extend:function(e,n){t.iterate(n,function(i){t.isArray(n[i])?(t.isArray(e[i])||(e[i]=[]),t.extend(e[i],n[i])):"object"==typeof n[i]?("object"!=typeof e[i]&&(e[i]={}),t.extend(e[i],n[i])):e[i]=n[i]})},newStyle:function(t){var e=document.createElement("style");return e.type="text/css",e.innerHTML=t,e},_canUse:null,canUse:function(e){t._canUse||(t._canUse=document.createElement("div"));var n=t._canUse.style,i=e.charAt(0).toUpperCase()+e.slice(1);return e in n||"Moz"+i in n||"Webkit"+i in n||"O"+i in n||"ms"+i in n},on:function(e,n){var i=e.split(/[\s]+/);return t.iterate(i,function(e){var a=i[e];if(t.isInit){if("init"==a)return void n();if("change"==a)n();else{var r=a.charAt(0);if("+"==r||"!"==r){var o=a.substring(1);if(o in t.obj.breakpoints)if("+"==r&&t.obj.breakpoints[o].active)n();else if("!"==r&&!t.obj.breakpoints[o].active)return void n()}}}t.events[a]||(t.events[a]=[]),t.events[a].push(n)}),t},trigger:function(e){return t.events[e]&&0!=t.events[e].length?(t.iterate(t.events[e],function(n){t.events[e][n]()}),t):void 0},breakpoint:function(e){return t.obj.breakpoints[e]},breakpoints:function(e){function n(t,e){this.name=this.id=t,this.media=e,this.active=!1,this.wasActive=!1}return n.prototype.matches=function(){return t.matchesMedia(this.media)},n.prototype.sync=function(){this.wasActive=this.active,this.active=this.matches()},t.iterate(e,function(i){t.obj.breakpoints[i]=new n(i,e[i])}),window.setTimeout(function(){t.poll()},0),t},addStateHandler:function(e,n){t.stateHandlers[e]=n},callStateHandler:function(e){var n=t.stateHandlers[e]();t.iterate(n,function(e){t.state.attachments.push(n[e])})},changeState:function(e){t.iterate(t.obj.breakpoints,function(e){t.obj.breakpoints[e].sync()}),t.vars.lastStateId=t.stateId,t.stateId=e,t.breakpointIds=t.stateId===t.sd?[]:t.stateId.substring(1).split(t.sd),t.obj.states[t.stateId]?t.state=t.obj.states[t.stateId]:(t.obj.states[t.stateId]={attachments:[]},t.state=t.obj.states[t.stateId],t.iterate(t.stateHandlers,t.callStateHandler)),t.detachAll(t.state.attachments),t.attachAll(t.state.attachments),t.vars.stateId=t.stateId,t.vars.state=t.state,t.trigger("change"),t.iterate(t.obj.breakpoints,function(e){t.obj.breakpoints[e].active?t.obj.breakpoints[e].wasActive||t.trigger("+"+e):t.obj.breakpoints[e].wasActive&&t.trigger("-"+e)})},generateStateConfig:function(e,n){var i={};return t.extend(i,e),t.iterate(t.breakpointIds,function(e){t.extend(i,n[t.breakpointIds[e]])}),i},getStateId:function(){var e="";return t.iterate(t.obj.breakpoints,function(n){var i=t.obj.breakpoints[n];i.matches()&&(e+=t.sd+i.id)}),e},poll:function(){var e="";e=t.getStateId(),""===e&&(e=t.sd),e!==t.stateId&&t.changeState(e)},_attach:null,attach:function(e){var n=t.obj.head,i=e.element;return i.parentNode&&i.parentNode.tagName?!1:(t._attach||(t._attach=n.firstChild),n.insertBefore(i,t._attach.nextSibling),e.permanent&&(t._attach=i),!0)},attachAll:function(e){var n=[];t.iterate(e,function(t){n[e[t].priority]||(n[e[t].priority]=[]),n[e[t].priority].push(e[t])}),n.reverse(),t.iterate(n,function(e){t.iterate(n[e],function(i){t.attach(n[e][i])})})},detach:function(t){var e=t.element;return t.permanent||!e.parentNode||e.parentNode&&!e.parentNode.tagName?!1:(e.parentNode.removeChild(e),!0)},detachAll:function(e){var n={};t.iterate(e,function(t){n[e[t].id]=!0}),t.iterate(t.obj.attachments,function(e){e in n||t.detach(t.obj.attachments[e])})},attachment:function(e){return e in t.obj.attachments?t.obj.attachments[e]:null},newAttachment:function(e,n,i,a){return t.obj.attachments[e]={id:e,element:n,priority:i,permanent:a}},init:function(){t.initMethods(),t.initVars(),t.initEvents(),t.obj.head=document.getElementsByTagName("head")[0],t.isInit=!0,t.trigger("init")},initEvents:function(){t.on("resize",function(){t.poll()}),t.on("orientationChange",function(){t.poll()}),t.DOMReady(function(){t.trigger("ready")}),window.onload&&t.on("load",window.onload),window.onload=function(){t.trigger("load")},window.onresize&&t.on("resize",window.onresize),window.onresize=function(){t.trigger("resize")},window.onorientationchange&&t.on("orientationChange",window.onorientationchange),window.onorientationchange=function(){t.trigger("orientationChange")}},initMethods:function(){document.addEventListener?!function(e,n){t.DOMReady=n()}("domready",function(){function t(t){for(r=1;t=n.shift();)t()}var e,n=[],i=document,a="DOMContentLoaded",r=/^loaded|^c/.test(i.readyState);return i.addEventListener(a,e=function(){i.removeEventListener(a,e),t()}),function(t){r?t():n.push(t)}}):!function(e,n){t.DOMReady=n()}("domready",function(t){function e(t){for(h=1;t=i.shift();)t()}var n,i=[],a=!1,r=document,o=r.documentElement,s=o.doScroll,c="DOMContentLoaded",d="addEventListener",u="onreadystatechange",l="readyState",f=s?/^loaded|^c/:/^loaded|c/,h=f.test(r[l]);return r[d]&&r[d](c,n=function(){r.removeEventListener(c,n,a),e()},a),s&&r.attachEvent(u,n=function(){/^c/.test(r[l])&&(r.detachEvent(u,n),e())}),t=s?function(e){self!=top?h?e():i.push(e):function(){try{o.doScroll("left")}catch(n){return setTimeout(function(){t(e)},50)}e()}()}:function(t){h?t():i.push(t)}}),Array.prototype.indexOf?t.indexOf=function(t,e){return t.indexOf(e)}:t.indexOf=function(t,e){if("string"==typeof t)return t.indexOf(e);var n,i,a=e?e:0;if(!this)throw new TypeError;if(i=this.length,0===i||a>=i)return-1;for(0>a&&(a=i-Math.abs(a)),n=a;i>n;n++)if(this[n]===t)return n;return-1},Array.isArray?t.isArray=function(t){return Array.isArray(t)}:t.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)},Object.keys?t.iterate=function(t,e){if(!t)return[];var n,i=Object.keys(t);for(n=0;i[n]&&e(i[n],t[i[n]])!==!1;n++);}:t.iterate=function(t,e){if(!t)return[];var n;for(n in t)if(Object.prototype.hasOwnProperty.call(t,n)&&e(n,t[n])===!1)break},window.matchMedia?t.matchesMedia=function(t){return""==t?!0:window.matchMedia(t).matches}:window.styleMedia||window.media?t.matchesMedia=function(t){if(""==t)return!0;var e=window.styleMedia||window.media;return e.matchMedium(t||"all")}:window.getComputedStyle?t.matchesMedia=function(t){if(""==t)return!0;var e=document.createElement("style"),n=document.getElementsByTagName("script")[0],i=null;e.type="text/css",e.id="matchmediajs-test",n.parentNode.insertBefore(e,n),i="getComputedStyle"in window&&window.getComputedStyle(e,null)||e.currentStyle;var a="@media "+t+"{ #matchmediajs-test { width: 1px; } }";return e.styleSheet?e.styleSheet.cssText=a:e.textContent=a,"1px"===i.width}:t.matchesMedia=function(t){if(""==t)return!0;var e,n,i,a,r={"min-width":null,"max-width":null},o=!1;for(i=t.split(/\s+and\s+/),e=0;er["max-width"]||null!==r["min-height"]&&cr["max-height"]?!1:!0},navigator.userAgent.match(/MSIE ([0-9]+)/)&&RegExp.$1<9&&(t.newStyle=function(t){var e=document.createElement("span");return e.innerHTML=' ",e})},initVars:function(){var e,n,i,a=navigator.userAgent;e="other",n=0,i=[["firefox",/Firefox\/([0-9\.]+)/],["bb",/BlackBerry.+Version\/([0-9\.]+)/],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/],["opera",/OPR\/([0-9\.]+)/],["opera",/Opera\/([0-9\.]+)/],["edge",/Edge\/([0-9\.]+)/],["safari",/Version\/([0-9\.]+).+Safari/],["chrome",/Chrome\/([0-9\.]+)/],["ie",/MSIE ([0-9]+)/],["ie",/Trident\/.+rv:([0-9]+)/]],t.iterate(i,function(t,i){return a.match(i[1])?(e=i[0],n=parseFloat(RegExp.$1),!1):void 0}),t.vars.browser=e,t.vars.browserVersion=n,e="other",n=0,i=[["ios",/([0-9_]+) like Mac OS X/,function(t){return t.replace("_",".").replace("_","")}],["ios",/CPU like Mac OS X/,function(t){return 0}],["wp",/Windows Phone ([0-9\.]+)/,null],["android",/Android ([0-9\.]+)/,null],["mac",/Macintosh.+Mac OS X ([0-9_]+)/,function(t){return t.replace("_",".").replace("_","")}],["windows",/Windows NT ([0-9\.]+)/,null],["bb",/BlackBerry.+Version\/([0-9\.]+)/,null],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/,null]],t.iterate(i,function(t,i){return a.match(i[1])?(e=i[0],n=parseFloat(i[2]?i[2](RegExp.$1):RegExp.$1),!1):void 0}),t.vars.os=e,t.vars.osVersion=n,t.vars.IEVersion="ie"==t.vars.browser?t.vars.browserVersion:99,t.vars.touch="wp"==t.vars.os?navigator.msMaxTouchPoints>0:!!("ontouchstart"in window),t.vars.mobile="wp"==t.vars.os||"android"==t.vars.os||"ios"==t.vars.os||"bb"==t.vars.os}};return t.init(),t}();!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.skel=e()}(this,function(){return skel});
3 |
--------------------------------------------------------------------------------
/main_application/restcall.js:
--------------------------------------------------------------------------------
1 | // restcall.js - module for handling REST API calls
2 |
3 | // METHOD == GET
4 | exports.get = function(options, secure, callbackFn) {
5 |
6 | var httpcall = require(secure ? 'https' : 'http');
7 |
8 | var req = httpcall.request(options, function(res) {
9 | res.setEncoding('utf-8');
10 | var responseString = '';
11 |
12 | res.on('data', function(data) {
13 | responseString += data;
14 | });
15 |
16 | res.on('end', function() {
17 | var responseObject = JSON.parse(responseString);
18 | callbackFn(responseObject);
19 | });
20 | });
21 |
22 | req.write(""); //method == GET, no data
23 | req.end();
24 |
25 | };
26 |
27 | // METHOD == POST
28 | exports.post = function(options, secure, postdata, callbackFn) {
29 |
30 | var httpcall = require(secure ? 'https' : 'http');
31 |
32 | var req = httpcall.request(options, function(res) {
33 | res.setEncoding('utf-8');
34 | var responseString = '';
35 |
36 | res.on('data', function(data) {
37 | responseString += data;
38 | });
39 |
40 | res.on('end', function() {
41 | var responseObject = JSON.parse(responseString);
42 | callbackFn(responseObject);
43 | });
44 | });
45 |
46 | req.setHeader("Content-type", "application/json");
47 | req.write(JSON.stringify(postdata)); //method == POST, write data
48 | req.end();
49 |
50 | };
--------------------------------------------------------------------------------
/main_application/routes/index.js:
--------------------------------------------------------------------------------
1 |
2 | // GET initial page
3 | exports.index = function(req, res){
4 | res.render('index');
5 | };
6 |
--------------------------------------------------------------------------------
/main_application/routes/tripdata.js:
--------------------------------------------------------------------------------
1 | var tripit = require('../tripit.js');
2 |
3 | // GET flight data from TripIt (or cached in Cloudant)
4 | exports.getFlights = function(req, res) {
5 | if (req.session.oauth_access_token === "") {
6 | res.redirect("/authorize");
7 | return;
8 | }
9 | var user = req.session.user;
10 | var authToken = req.session.oauth_access_token;
11 | var authTokenSecret = req.session.oauth_access_token_secret;
12 | tripit.getTrips(user, authToken, authTokenSecret).then(function(data) {
13 | console.log("Returning TripIt trip/flight data for user " + user);
14 | // if we have a development setting to force viewing flights, set
15 | // a variable in the response data stream to mark that
16 | if (process.env.FORCE_FLIGHT_VIEW === "true") {
17 | data.forceFlights = 1;
18 | }
19 | res.send(data);
20 | }).catch(function(err) {
21 | console.log("Error retriving trip data: " + err);
22 | res.send(err);
23 | });
24 | };
--------------------------------------------------------------------------------
/main_application/tripit.js:
--------------------------------------------------------------------------------
1 | /* Retrieve trip information */
2 |
3 | var TripItApiClient = require("tripit-node"),
4 | Cloudant = require('cloudant'),
5 | fs = require('fs');
6 |
7 | var tripItKey = "";
8 | var tripItSecret = "";
9 |
10 | if (process.env.DEPLOY === "swarm" || process.env.DEPLOY === "kubernetes") {
11 | tripItKey = global.tripit_api_key;
12 | tripItSecret = global.tripit_api_secret;
13 | } else {
14 | tripItKey = process.env.TRIPIT_API_KEY;
15 | tripItSecret = process.env.TRIPIT_API_SECRET;
16 | }
17 | var client = new TripItApiClient(tripItKey, tripItSecret);
18 |
19 | // cloudant credentials URL
20 | var cURL = "";
21 | if (process.env.DEVMODE === "true") {
22 | if (process.env.DEPLOY === "swarm") {
23 | cURL = global.cloudant_url;
24 | } else {
25 | cURL = process.env.CLOUDANT_URL;
26 | }
27 | } else if (process.env.DEPLOY === "kubernetes") {
28 | console.log("kubernetes deploy mode is detected")
29 | var binding = JSON.parse(fs.readFileSync('/opt/service-bind/binding', 'utf8'));
30 | cURL = binding.url
31 | } else {
32 | var vcap_services = JSON.parse(process.env.VCAP_SERVICES);
33 | cURL = vcap_services.cloudantNoSQLDB[0].credentials.url;
34 | }
35 |
36 | var cloudant = Cloudant({ url: cURL, plugin: 'promises' });
37 |
38 | var requestTokenSecrets = {};
39 |
40 | module.exports = {
41 | authorize: function(callBackURL, isMobile, res) {
42 | var baseURL = "https://www.tripit.com";
43 | if (isMobile === "true") {
44 | baseURL = "https://m.tripit.com";
45 | }
46 | client.getRequestToken().then(function(results) {
47 | var token = results[0],
48 | secret = results[1];
49 | requestTokenSecrets[token] = secret;
50 | res.redirect(baseURL + "/oauth/authorize?oauth_token=" + token + "&oauth_callback=" + callBackURL);
51 | }, function(error) {
52 | res.send(error);
53 | });
54 | },
55 | getAccessTokens: function(req) {
56 | var token = req.query.oauth_token,
57 | secret = requestTokenSecrets[token],
58 | verifier = req.query.oauth_verifier;
59 | return client.getAccessToken(token, secret, verifier);
60 | },
61 | getProfileData: function(accessToken, accessTokenSecret) {
62 | return client.requestResource("/get/profile", "GET", accessToken, accessTokenSecret);
63 | },
64 | getTrips: function(user, accessToken, accessTokenSecret) {
65 | var tripList = {};
66 | return getUserTrips(user).then(function(data) {
67 | tripList = data;
68 | }).catch(function(err) {
69 | console.log("[getUserTrips] Cloudant lookup error/empty: " + err);
70 | }).then(function() {
71 | if (isEmpty(tripList)) {
72 | return client.requestResource("/list/trip/traveler/true/exclude_types/weather/include_objects/true", "GET", accessToken, accessTokenSecret);
73 | } else {
74 | // only request and update since the last query of TripIt and then merge
75 | // updates into our cache
76 | var modifiedParam = "modified_since/" + tripList.timestamp;
77 | return client.requestResource("/list/trip/traveler/true/exclude_types/weather/" + modifiedParam + "/include_objects/true", "GET", accessToken, accessTokenSecret);
78 | }
79 | }).then(function(data) {
80 | // process the trip API result data
81 | console.log("Acquired trip data from TripIt for " + user);
82 | return processTripData(user, tripList, JSON.parse(data[0]));
83 | }).catch(function(err) {
84 | console.log("Error retrieving trip data from TripIt: " + err);
85 | });
86 | }
87 | };
88 |
89 | function getUserTrips(user) {
90 | // query cloudant to see if we have cached any trips for this username
91 | var tripDB = cloudant.db.use("trips");
92 | return tripDB.get(user);
93 | }
94 |
95 | function putUserTrips(tripData) {
96 | var tripDB = cloudant.db.use("trips");
97 | tripDB.insert(tripData, function(err, data) {
98 | if (err) {
99 | console.log("Error on trip DB insert: " + err);
100 | }
101 | });
102 | }
103 |
104 | // This function is rather ugly. First, one of its jobs is to reduce
105 | // the amount of trip information to just the outer "trip" object metadata
106 | // and flight segments associated with that. Because the TripIt API does
107 | // not join these two objects (Trips and AirObjects), we are doing that
108 | // association/join in our "filtered" JSON representation. Secondly, this function
109 | // handles partial information updates ("modified_since//" in TripIt
110 | // API terms), and then has to re-assemble the filtered/combined JSON representation,
111 | // while ignoring updates if there are no AirObject updates in the partial update.
112 | function processTripData(user, tripJSON, newData) {
113 | if (isEmpty(tripJSON)) {
114 | tripJSON.timestamp = newData.timestamp;
115 | // this is all new data; nothing in the cache, so filter our required information
116 | // and cache it
117 | var trips = [];
118 | var airobjs = [];
119 | // walk the JSON looking for "Trip" and "AirObject" types
120 | for (var key in newData) {
121 | if (key === "Trip") {
122 | var trip = newData.Trip;
123 | if (trip instanceof Array) {
124 | trips = trip;
125 | } else {
126 | trips.push(newData.Trip);
127 | }
128 | } else if (key === "AirObject") {
129 | var airobj = newData.AirObject;
130 | if (airobj instanceof Array) {
131 | airobjs = airobj;
132 | } else {
133 | airobjs.push(newData.AirObject);
134 | }
135 | }
136 | }
137 | // assemble our variant of the JSON for cacheing/use
138 |
139 | var tripMap = new Map();
140 | for (var i = 0; i < trips.length; i++) {
141 | tripMap.set(trips[i].id, trips[i]);
142 | }
143 | for (var j = 0; j < airobjs.length; j++) {
144 | //find trip ID for this air object/segments and
145 | //add to trip information
146 | var relatedTrip = tripMap.get(airobjs[j].trip_id);
147 | if (relatedTrip === undefined) {
148 | console.log("Can't find trip for air segment!");
149 | } else {
150 | airArray = relatedTrip.air_segments;
151 | if (airArray === undefined) {
152 | //haven't added any airsegments yet
153 | airArray = [airobjs[j]];
154 | relatedTrip.air_segments = airArray;
155 | } else {
156 | relatedTrip.air_segments.push(airobjs[j]);
157 | }
158 | }
159 | }
160 | tripJSON.Trips = trips;
161 | tripJSON._id = user;
162 | putUserTrips(tripJSON);
163 | return tripJSON;
164 | }
165 | // first, clear any old trips from the cache (e.g. trips now in the past)
166 | tripJSON = removePastTrips(Date.now(), tripJSON);
167 | // set a new cache timestamp
168 | tripJSON.timestamp = newData.timestamp;
169 | // we have cached data; see if any trips are updated in the newData object from
170 | // the API call and update the cache
171 | var airUpdate = false;
172 | var changedTrips = [];
173 | var changedAirobjs = [];
174 | // walk the JSON looking for "Trip" and "AirObject" types
175 | for (var nkey in newData) {
176 | if (nkey === "Trip") {
177 | var dtrip = newData.Trip;
178 | if (dtrip instanceof Array) {
179 | changedTrips = dtrip;
180 | } else {
181 | changedTrips.push(dtrip);
182 | }
183 | } else if (nkey === "AirObject") {
184 | airUpdate = true;
185 | var dairobj = newData.AirObject;
186 | if (dairobj instanceof Array) {
187 | changedAirobjs = dairobj;
188 | } else {
189 | changedAirobjs.push(dairobj);
190 | }
191 | }
192 | }
193 | if (!airUpdate) {
194 | // no update necessary as no air segment changes happened
195 | // still write the cache as we have an updated "since"
196 | // timestamp that will limit getting back any "ignored"
197 | // updates next time we call the TripIt API.
198 | putUserTrips(tripJSON);
199 | return tripJSON;
200 | }
201 | // only if there are flight changes will we process
202 | // this update as our app only deals with flights
203 | var updateTripMap = new Map();
204 | for (var i = 0; i < changedTrips.length; i++) {
205 | updateTripMap.set(changedTrips[i].id, changedTrips[i]);
206 | }
207 | var cachedTrips = tripJSON.Trips;
208 | for (var k = 0; k < cachedTrips.length; k++) {
209 | if (updateTripMap.has(cachedTrips[k].id)) {
210 | // if we got an update on this trip, update it
211 | cachedTrips[k] = updateTripMap.get(cachedTrips[k].id);
212 | updateTripMap.delete(cachedTrips[k].id);
213 | }
214 | }
215 | // any elements left in the updated trip map are new
216 | // trips we have never cached:
217 | for (var newTrip of updateTripMap.values()) {
218 | cachedTrips.push(newTrip);
219 | }
220 | // now handle air segment adds/updates
221 | var newTripMap = new Map();
222 | for (var m = 0; m < cachedTrips.length; m++) {
223 | newTripMap.set(cachedTrips[m].id, cachedTrips[m]);
224 | }
225 | for (var n = 0; n < changedAirobjs.length; n++) {
226 | //find trip ID for this air object/segments and
227 | //add/update trip information
228 | var rTrip = newTripMap.get(changedAirobjs[n].trip_id);
229 | if (rTrip === undefined) {
230 | console.log("Can't find trip for air segment!");
231 | } else {
232 | airArray = rTrip.air_segments;
233 | if (airArray === undefined) {
234 | //haven't added any airsegments yet
235 | airArray = [changedAirobjs[n]];
236 | rTrip.air_segments = airArray;
237 | } else {
238 | var found = false;
239 | for (var p = 0; p < rTrip.air_segments.length; p++) {
240 | if (rTrip.air_segments[p].id == changedAirobjs[n].id) {
241 | found = true;
242 | rTrip.air_segments[p] = changedAirobjs[n];
243 | }
244 | }
245 | if (!found) {
246 | // new segment to add to the trip
247 | rTrip.air_segments.push(changedAirobjs[n]);
248 | }
249 | }
250 | }
251 | }
252 | tripJSON.Trips = cachedTrips;
253 | putUserTrips(tripJSON);
254 | return tripJSON;
255 | }
256 |
257 | function removePastTrips(epochMS, tripJSON) {
258 | var updatedTrips = [];
259 | var cachedTrips = tripJSON.Trips;
260 | for (var k = 0; k < cachedTrips.length; k++) {
261 | var endTrip = Date.parse(cachedTrips[k].end_date);
262 | if (endTrip > epochMS) {
263 | updatedTrips.push(cachedTrips[k]);
264 | }
265 | }
266 | tripJSON.Trips = updatedTrips;
267 | return tripJSON;
268 | }
269 |
270 | function isEmpty(obj) {
271 | if (obj === undefined) {
272 | return true;
273 | }
274 | return Object.keys(obj).length === 0;
275 | }
276 |
--------------------------------------------------------------------------------
/main_application/views/conversation.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | FlightAssist | Watson Powered Conversation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/main_application/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | FlightAssist
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
28 |
29 |
30 |
31 |
32 | This is FlightAssist
33 |
34 | A demo application designed to help compare application deployment methods between
35 | containers, traditional PaaS, and serverless/Function-as-a-service computing. The
36 | application uses TripIt profile data to find upcoming flights, helping you determine
37 | if there are flight or weather issues, and providing potential alternative routes to
38 | your same destination. Follow the link below to authorize this application to your
39 | TripIt profile.
40 |
41 |
44 |
45 |
46 |
47 |
48 |
49 |
56 |
57 |
58 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/main_application/views/trips.ejs:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | FlightAssist | Trip Information
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | <% if (typeof no_data !== "undefined") { %>
21 |
22 |
23 |
24 |
25 | An error
26 | has occurred!
27 |
28 |
Message: <%= message %>
29 |
30 |
31 | <% } else { %>
32 |
33 |
34 |
35 | Profile: <%= name %> | <%= company %> | <%= home %>
36 |
37 |
38 | >
39 |
40 |
41 |
42 |
43 |
44 | >
45 |
46 |
47 |
48 |
49 |
50 | <% } %>
51 |
52 |
53 |
54 |
61 |
62 |
63 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/main_application/weather.js:
--------------------------------------------------------------------------------
1 | // retrieve and cache weather data
2 |
3 | var Cloudant = require('cloudant'),
4 | fs = require('fs');
5 |
6 | var restcall = require('./restcall.js');
7 | var url = require('url');
8 |
9 | // cloudant & weather co. credentials URL
10 | var cURL = "";
11 | var weatherURL = "";
12 | if (process.env.DEVMODE === "true") {
13 | if (process.env.DEPLOY === "swarm") {
14 | cURL = global.cloudant_url;
15 | weatherURL = global.weather_url;
16 | } else {
17 | cURL = process.env.CLOUDANT_URL;
18 | weatherURL = process.env.WEATHER_URL;
19 | }
20 | } else if (process.env.DEPLOY === "kubernetes") {
21 | console.log("kubernetes deploy mode is detected");
22 | var binding = JSON.parse(fs.readFileSync('/opt/service-bind/binding', 'utf8'));
23 | cURL = binding.url;
24 | if (process.env.USE_WEATHER_SERVERLESS == "true"){
25 | var weatherbinding = JSON.parse(fs.readFileSync('/opt/service-bind2/binding', 'utf8'));
26 | weatherURL = weatherbinding.url;
27 | }
28 | } else {
29 | var vcap_services = JSON.parse(process.env.VCAP_SERVICES);
30 | cURL = vcap_services.cloudantNoSQLDB[0].credentials.url;
31 | weatherURL = vcap_services.weatherinsights[0].credentials.url;
32 | }
33 |
34 | var cloudant = Cloudant({ url: cURL, plugin: 'promises' });
35 |
36 | module.exports = {
37 | // requires input in the query string:
38 | // - lat = location latitude
39 | // - lon = location longitude
40 | // - locID = airport code representing this location (for cache key)
41 | getThreeDayForecast: function(req, resp) {
42 | // retrieve forecast from either cache; or
43 | // if cache is "expired", re-query from weather co. API
44 |
45 | // Look up cache..
46 | getCachedData(req.query.locID).then(function(data) {
47 | var now = Date.now();
48 | if ((now - data.cachetime) > 10 * 60 * 1000) {
49 | // data older than 10 minutes; don't use cache
50 | console.log("Expiring cached weather data for " + req.query.locID);
51 | data.expired = true;
52 | }
53 | return data;
54 | }).catch(function(err) {
55 | console.log("[getCachedWeatherData] Cloudant lookup error/empty: " + err);
56 | }).then(function(data) {
57 | if (!isEmpty(data) && !data.expired) {
58 | // use cached weather data
59 | console.log("using cached weather data for " + req.query.locID);
60 | resp.send(data);
61 | return;
62 | }
63 | if (process.env.USE_WEATHER_SERVICE !== "true") {
64 | if (process.env.USE_WEATHER_SERVERLESS !== "true") {
65 | // our default mode: as a "monolith" deployment; simply use our external
66 | // API query to retrieve weather company data
67 | return handleViaWeatherAPI(req, resp, data);
68 | } else {
69 | // use an OpenWhisk action to retrieve the weather forecast details
70 | return handleViaWeatherWhiskAction(req, resp, data);
71 | }
72 | } else {
73 | // external weather microservice deployment mode; call
74 | // our microservice using service name ("weather-service")
75 | // or if dev mode, simply look on localhost at the expected port
76 | return handleViaWeatherMicroservice(req, resp, data);
77 | }
78 | });
79 | }
80 | };
81 |
82 | // handle a request for weather data via direct call to Weather Co. data API
83 | function handleViaWeatherAPI(req, resp, data) {
84 | var host = "";
85 | var endpoint = "/api/weather/v1/geocode/" + req.query.lat + "/" + req.query.lon + "/forecast/daily/3day.json";
86 | var wURLObj = url.parse(weatherURL);
87 | host = wURLObj.host;
88 | var authStr = wURLObj.auth;
89 |
90 | var options = {
91 | host: host,
92 | path: endpoint,
93 | method: "GET",
94 | auth: authStr,
95 | rejectUnauthorized: false
96 | };
97 |
98 | //send the request to the Weather API
99 | restcall.get(options, true, function(newData) {
100 | // cache this data in cloudant with the current epoch ms
101 | var currentEpochms = Date.now();
102 | newData.cachetime = currentEpochms;
103 | if (!isEmpty(data)) {
104 | //set the rev ID so cache update works
105 | newData._rev = data._rev;
106 | }
107 | newData._id = req.query.locID;
108 | cacheWeatherData(newData);
109 | // send data as response:
110 | console.log("sending JSON weather response for " + req.query.locID);
111 | resp.send(newData);
112 | });
113 | }
114 |
115 | function handleViaWeatherMicroservice(req, resp, data) {
116 | var microserviceURL = process.env.MICROSERVICE_URL;
117 | console.log("using external weather microservice: " + process.env.USE_WEATHER_SERVICE);
118 | // overwrite host, endpoint to point to our weather microservice
119 | if (process.env.DEVMODE === "true" && process.env.DEPLOY !== "swarm") {
120 | if(process.env.DEPLOY === "compose"){
121 | host = "weather-service";
122 | } else{
123 | host = "localhost";
124 | }
125 | } else if(process.env.DEPLOY === "cloudfoundry") {
126 | host = microserviceURL;
127 | } else{
128 | host = "weather-service";
129 | }
130 | var endpoint = "/weather/" + req.query.lat + "/" + req.query.lon;
131 |
132 | var options = {
133 | host: host,
134 | port: 5000,
135 | path: endpoint,
136 | method: "GET",
137 | rejectUnauthorized: false
138 | };
139 |
140 | if (process.env.DEPLOY === "cloudfoundry"){
141 | options.port = null;
142 | }
143 | //send the request to the Weather API
144 | restcall.get(options, false, function(newData) {
145 | // cache this data in cloudant with the current epoch ms
146 | var currentEpochms = Date.now();
147 | newData.cachetime = currentEpochms;
148 | if (!isEmpty(data)) {
149 | //set the rev ID so cache update works
150 | newData._rev = data._rev;
151 | }
152 | newData._id = req.query.locID;
153 | cacheWeatherData(newData);
154 | // send data as response:
155 | console.log("sending JSON weather response for " + req.query.locID);
156 | resp.send(newData);
157 | });
158 | }
159 |
160 | // handle a request for weather data via calling an OpenWhisk action
161 | function handleViaWeatherWhiskAction(req, resp, data) {
162 | console.log("use OpenWhisk action for weather service: " + process.env.USE_WEATHER_SERVERLESS);
163 |
164 | var host = "openwhisk.ng.bluemix.net";
165 | var endpoint = "/api/v1/namespaces/whisk.system/actions/weather/forecast?blocking=true";
166 | var options = {
167 | host: host,
168 | path: endpoint,
169 | method: "POST",
170 | auth: process.env.OPENWHISK_AUTH,
171 | rejectUnauthorized: false
172 | };
173 |
174 | // we need our weather API credentials from the weather URL
175 | var wURLObj = url.parse(weatherURL);
176 | var weatherAuth = wURLObj.auth.split(":");
177 | var postdata = {
178 | "username": weatherAuth[0],
179 | "password": weatherAuth[1],
180 | "latitude": req.query.lat,
181 | "longitude": req.query.lon,
182 | };
183 |
184 | //direct call the OpenWhisk action via HTTP
185 | restcall.post(options, true, postdata, function(newData) {
186 | // OpenWhisk HTTP response has the JSON data in "{ response: { result: { ..."
187 | console.log(JSON.stringify(newData));
188 | var forecastData = newData.response.result;
189 | // cache this data in cloudant with the current epoch ms
190 | var currentEpochms = Date.now();
191 | forecastData.cachetime = currentEpochms;
192 | if (!isEmpty(data)) {
193 | //set the rev ID so cache update works
194 | forecastData._rev = data._rev;
195 | }
196 | forecastData._id = req.query.locID;
197 | cacheWeatherData(forecastData);
198 | // send data as response:
199 | console.log("sending JSON weather response for " + req.query.locID);
200 | resp.send(forecastData);
201 | });
202 | }
203 |
204 | function getCachedData(location) {
205 | // query cloudant to see if we have cached any weather for this location
206 | var weatherDB = cloudant.db.use("weather");
207 | return weatherDB.get(location);
208 | }
209 |
210 | function cacheWeatherData(weatherData) {
211 | var weatherDB = cloudant.db.use("weather");
212 | weatherDB.insert(weatherData, function(err, data) {
213 | if (err) {
214 | console.log("Error on weather DB insert: " + err);
215 | }
216 | });
217 | }
218 |
219 | function isEmpty(obj) {
220 | if (obj === undefined) {
221 | return true;
222 | }
223 | return Object.keys(obj).length === 0;
224 | }
--------------------------------------------------------------------------------
/ratelimit.yaml:
--------------------------------------------------------------------------------
1 | rules:
2 | - aspects:
3 | - kind: quotas
4 | params:
5 | quotas:
6 | - descriptorName: RequestCount
7 | maxAmount: 50
8 | expiration: 10s
9 |
--------------------------------------------------------------------------------
/scripts/bx_login.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ -z $CF_ORG ]; then
4 | CF_ORG="$BLUEMIX_ORG"
5 | fi
6 | if [ -z $CF_SPACE ]; then
7 | CF_SPACE="$BLUEMIX_SPACE"
8 | fi
9 |
10 |
11 | if ([ -z "$BLUEMIX_USER" ] || [ -z "$BLUEMIX_PASSWORD" ] || [ -z "$BLUEMIX_ACCOUNT" ]) && ([ -z "$API_KEY"]); then
12 | echo "Define all required environment variables and re-run the stage."
13 | exit 1
14 | fi
15 |
16 | echo "bx login -a $CF_TARGET_URL"
17 |
18 | if [ -z "$API_KEY"]; then
19 | bx login -a "$CF_TARGET_URL" -u "$BLUEMIX_USER" -p "$BLUEMIX_PASSWORD" -c "$BLUEMIX_ACCOUNT" -o "$CF_ORG" -s "$CF_SPACE"
20 | else
21 | bx login -a "$CF_TARGET_URL" --apikey "$API_KEY" -o "$CF_ORG" -s "$CF_SPACE"
22 | fi
23 |
24 | if [ $? -ne 0 ]; then
25 | echo "Failed to authenticate to Bluemix"
26 | exit 1
27 | fi
28 |
29 | echo "bx cs init"
30 | bx cs init
31 | if [ $? -ne 0 ]; then
32 | echo "Failed to initialize to Bluemix Container Service"
33 | exit 1
34 | fi
35 |
--------------------------------------------------------------------------------
/scripts/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "Create FlightAssist"
4 | IP_ADDR=$(bx cs workers $CLUSTER_NAME | grep Ready | awk '{ print $2 }')
5 | if [ -z $IP_ADDR ]; then
6 | echo "$CLUSTER_NAME not created or workers not ready"
7 | exit 1
8 | fi
9 |
10 | echo -e "Configuring vars"
11 | exp=$(bx cs cluster-config $CLUSTER_NAME | grep export)
12 | if [ $? -ne 0 ]; then
13 | echo "Cluster $CLUSTER_NAME not created or not ready."
14 | exit 1
15 | fi
16 | eval "$exp"
17 |
18 | echo -e "Deleting previous version of FlightAssist if it exists"
19 | kubectl delete --ignore-not-found=true svc,deployment flightassist-service
20 | kubectl delete --ignore-not-found=true svc,deployment weather-service
21 | kubectl delete --ignore-not-found=true -f secret.yaml
22 |
23 | bx cs cluster-service-bind $CLUSTER_NAME default mycloudant
24 | bx cs cluster-service-bind $CLUSTER_NAME default myweatherinsights
25 |
26 | sed -i s#""#$FLIGHTSTATS_APP_ID# secret.yaml
27 | sed -i s#""#$FLIGHTSTATS_APP_KEY# secret.yaml
28 | sed -i s#""#$TRIPIT_API_KEY# secret.yaml
29 | sed -i s#""#$TRIPIT_API_SECRET# secret.yaml
30 |
31 | kubectl create -f secret.yaml
32 |
33 | if [ -z $OPENWHISK_AUTH ]; then
34 | sed -i s#""#$IP_ADDR:30080# flightassist.yaml
35 | sed -i s#"registry.ng.bluemix.net//flightassist"#docker.io/tomcli/flightassist# flightassist.yaml
36 | sed -i s#"registry.ng.bluemix.net//weather-service"#docker.io/tomcli/weather-service# flightassist.yaml
37 |
38 | kubectl create -f flightassist.yaml
39 | else
40 | sed -i s#""#$IP_ADDR:30080# flightassist_serverless.yaml
41 | sed -i s#"registry.ng.bluemix.net//flightassist"#docker.io/tomcli/flightassist# flightassist_serverless.yaml
42 | sed -i s#""#$OPENWHISK_AUTH# flightassist_serverless.yaml
43 |
44 | kubectl create -f flightassist_serverless.yaml
45 | fi
46 |
47 | echo "" && echo "View your FlightAssist website at http://$IP_ADDR:30080"
48 |
49 |
--------------------------------------------------------------------------------
/scripts/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | function install_bluemix_cli() {
4 | #statements
5 | echo "Installing Bluemix cli"
6 | curl -L "https://cli.run.pivotal.io/stable?release=linux64-binary&source=github" | tar -zx
7 | sudo mv cf /usr/local/bin
8 | sudo curl -o /usr/share/bash-completion/completions/cf https://raw.githubusercontent.com/cloudfoundry/cli/master/ci/installers/completion/cf
9 | cf --version
10 | curl -L public.dhe.ibm.com/cloud/bluemix/cli/bluemix-cli/Bluemix_CLI_0.5.1_amd64.tar.gz > Bluemix_CLI.tar.gz
11 | tar -xvf Bluemix_CLI.tar.gz
12 | sudo ./Bluemix_CLI/install_bluemix_cli
13 | }
14 |
15 | function bluemix_auth() {
16 | echo "Authenticating with Bluemix"
17 | echo "1" | bx login -a https://api.ng.bluemix.net -u $BLUEMIX_USER -p $BLUEMIX_PASS
18 | echo "1" | cf login -a https://api.ng.bluemix.net -u $BLUEMIX_USER -p $BLUEMIX_PASS
19 | curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
20 | bx plugin install container-service -r Bluemix
21 | echo "Installing kubectl"
22 | chmod +x ./kubectl
23 | sudo mv ./kubectl /usr/local/bin/kubectl
24 | }
25 |
26 | function cluster_setup() {
27 | #change cluster-travis to cluster name
28 | bx cs workers $CLUSTER
29 | $(bx cs cluster-config $CLUSTER | grep export)
30 |
31 | echo "Deleting old deployment if it exists..."
32 | kubectl delete --ignore-not-found=true svc,deployment flightassist-service
33 | kubectl delete --ignore-not-found=true svc,deployment weather-service
34 | kubectl delete --ignore-not-found=true -f secret.yaml
35 | }
36 |
37 | function application_setup() {
38 | #creating services
39 |
40 | # Trial accounts that are expired cannot use any Bluemix service other than kubernetes cluster.
41 | # bx service create cloudantNoSQLDB Lite mycloudant
42 | # bx service create weatherinsights Free-v2 myweatherinsights
43 |
44 | # bx cs cluster-service-bind $CLUSTER_NAME default mycloudant
45 | # bx cs cluster-service-bind $CLUSTER_NAME default myweatherinsights
46 |
47 | #set dummy cred
48 | sed -i s#""#"1930fe0e"# secret.yaml
49 | sed -i s#""#"2153768d0be39fcb226ee76d28499ded"# secret.yaml
50 | sed -i s#""#"f88710e02094db0b0b2994c0806a53a3f76250d5"# secret.yaml
51 | sed -i s#""#"bca43acbf2d095030f0ea924139acf7e4989ade2"# secret.yaml
52 | kubectl create -f secret.yaml
53 |
54 | echo "Create FlightAssist"
55 | IP_ADDR=$(bx cs workers $CLUSTER | grep Ready | awk '{ print $2 }')
56 |
57 | sed -i s#""#$IP_ADDR:30080# flightassist.yaml
58 | sed -i s#"registry.ng.bluemix.net//flightassist"#docker.io/tomcli/flightassist# flightassist.yaml
59 | sed -i s#"registry.ng.bluemix.net//weather-service"#docker.io/tomcli/weather-service# flightassist.yaml
60 |
61 | kubectl create -f flightassist.yaml
62 |
63 | }
64 |
65 |
66 |
67 | install_bluemix_cli
68 | bluemix_auth
69 | cluster_setup
70 | application_setup
71 | cluster_setup
72 |
--------------------------------------------------------------------------------
/scripts/install_bx.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "Download Bluemix CLI"
4 | wget --quiet --output-document=/tmp/Bluemix_CLI_amd64.tar.gz http://public.dhe.ibm.com/cloud/bluemix/cli/bluemix-cli/latest/Bluemix_CLI_amd64.tar.gz
5 | tar -xf /tmp/Bluemix_CLI_amd64.tar.gz --directory=/tmp
6 |
7 | # Create bx alias
8 | echo "#!/bin/bash" >/tmp/Bluemix_CLI/bin/bx
9 | echo "/tmp/Bluemix_CLI/bin/bluemix \"\$@\" " >>/tmp/Bluemix_CLI/bin/bx
10 | chmod +x /tmp/Bluemix_CLI/bin/*
11 |
12 | export PATH="/tmp/Bluemix_CLI/bin:$PATH"
13 |
14 | # Install Bluemix CS plugin
15 | echo "Install the Bluemix container-service plugin"
16 | bx plugin install container-service -r Bluemix
17 |
18 | echo "Install kubectl"
19 | wget --quiet --output-document=/tmp/Bluemix_CLI/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
20 | chmod +x /tmp/Bluemix_CLI/bin/kubectl
21 |
22 | if [ -n "$DEBUG" ]; then
23 | bx --version
24 | bx plugin list
25 | fi
26 |
27 |
--------------------------------------------------------------------------------
/secret.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: mysecret
5 | type: Opaque
6 | data:
7 | flightstats-app-id:
8 | flightstats-app-key:
9 | tripit-api-key:
10 | tripit-api-secret:
11 |
--------------------------------------------------------------------------------