From b7fe455c935569a05d15baa9cf8a16299a553ccd Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 03:13:10 -0500 Subject: [PATCH 01/27] chore --- README.md | 7 +------ index.html | 10 ++++++++++ server.js | 20 ++++++++++++++++---- 3 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 index.html diff --git a/README.md b/README.md index 1e2c8d4..8bdf250 100644 --- a/README.md +++ b/README.md @@ -1,6 +1 @@ -##Getting started -* `npm i` - -##Todo - -create a basic web server with expressthat can handle a GET request for whatever resource you'd like and whatever url you want. Navigate to that url in the browser and see if yuor resource is there. The solution is on the `step-1-fix` branch +##Solution to step-1 diff --git a/index.html b/index.html new file mode 100644 index 0000000..1667638 --- /dev/null +++ b/index.html @@ -0,0 +1,10 @@ + + + + + hello + + +

hey there

+ + diff --git a/server.js b/server.js index 80f064e..62e12ab 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,16 @@ -// TODO: create a basic server with express -// that can handle a GET request for what ever resource you'd like -// whatever url you want, and then navigate to that url in the browser and -// see if yuor resource is there. +var express = require('express'); +var app = express(); + +app.get('/', function(req, res){ + res.sendFile(__dirname + '/index.html', function(err) { + if (err) { + res.status(500).send(err); + } + }) +}); + + +var port = 3000; +app.listen(port, function(){ + console.log('listening on http://localhost:', port); +}); From de8a7df4c03ea00420ff062b15f78a8659869207 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 03:18:20 -0500 Subject: [PATCH 02/27] chore --- server.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server.js b/server.js index 62e12ab..65828b1 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,11 @@ var express = require('express'); var app = express(); +var jsonData = {count: 12, message: 'hey'}; + app.get('/', function(req, res){ + // res.sendFile takes an absolute path to a file and + // sets the mime type based n the file extname res.sendFile(__dirname + '/index.html', function(err) { if (err) { res.status(500).send(err); @@ -9,6 +13,10 @@ app.get('/', function(req, res){ }) }); +app.get('/data', function(req, res) { + res.json(jsonData); +}); + var port = 3000; app.listen(port, function(){ From 1db5de0a76b0bb12d38137094c7349eb71e5de36 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 04:36:20 -0500 Subject: [PATCH 03/27] chore --- client/app.js | 543 ++++++++++++++++++++++++++++++++++++++++++++++ client/index.html | 48 ++++ client/style.css | 542 +++++++++++++++++++++++++++++++++++++++++++++ index.html | 10 - server.js | 24 -- server/server.js | 49 +++++ 6 files changed, 1182 insertions(+), 34 deletions(-) create mode 100644 client/app.js create mode 100644 client/index.html create mode 100644 client/style.css delete mode 100644 index.html delete mode 100644 server.js create mode 100644 server/server.js diff --git a/client/app.js b/client/app.js new file mode 100644 index 0000000..1351400 --- /dev/null +++ b/client/app.js @@ -0,0 +1,543 @@ +/** + * @license + * lodash 3.10.1 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE + * Build: `lodash modern -o ./lodash.js` + */ +;(function(){function n(n,t){if(n!==t){var r=null===n,e=n===w,u=n===n,o=null===t,i=t===w,f=t===t;if(n>t&&!o||!u||r&&!i&&f||e&&f)return 1;if(n=n&&9<=n&&13>=n||32==n||160==n||5760==n||6158==n||8192<=n&&(8202>=n||8232==n||8233==n||8239==n||8287==n||12288==n||65279==n); +}function v(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r=F&&gu&&lu?new Dn(t):null,c=t.length;a&&(i=Mn,f=false,t=a);n:for(;++oi(t,a,0)&&u.push(a);return u}function at(n,t){var r=true;return Su(n,function(n,e,u){return r=!!t(n,e,u)}),r}function ct(n,t,r,e){var u=e,o=u;return Su(n,function(n,i,f){i=+t(n,i,f),(r(i,u)||i===e&&i===o)&&(u=i, +o=n)}),o}function lt(n,t){var r=[];return Su(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function st(n,t,r,e){var u;return r(n,function(n,r,o){return t(n,r,o)?(u=e?r:n,false):void 0}),u}function pt(n,t,r,e){e||(e=[]);for(var u=-1,o=n.length;++ut&&(t=-t>u?0:u+t),r=r===w||r>u?u:+r||0,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Be(u);++e=c)break n;o=e[o],u*="asc"===o||true===o?1:-1;break n}u=t.b-r.b}return u})}function $t(n,t){ +var r=0;return Su(n,function(n,e,u){r+=+t(n,e,u)||0}),r}function St(n,t){var e=-1,u=xr(),o=n.length,i=u===r,f=i&&o>=F,a=f&&gu&&lu?new Dn(void 0):null,c=[];a?(u=Mn,i=false):(f=false,a=t?[]:c);n:for(;++eu(a,s,0)&&((t||f)&&a.push(s),c.push(l))}return c}function Ft(n,t){for(var r=-1,e=t.length,u=Be(e);++r>>1,i=n[o];(r?i<=t:iu?w:o,u=1);++e=F)return t.plant(e).value();for(var u=0,n=r?o[u].apply(this,n):e;++uarguments.length;return typeof e=="function"&&o===w&&Oo(r)?n(r,e,u,i):Ot(r,wr(e,o,4),u,i,t)}}function sr(n,t,r,e,u,o,i,f,a,c){function l(){for(var m=arguments.length,b=m,j=Be(m);b--;)j[b]=arguments[b];if(e&&(j=Mt(j,e,u)),o&&(j=qt(j,o,i)),_||y){var b=l.placeholder,k=v(j,b),m=m-k.length;if(mt?0:t)):[]}function Pr(n,t,r){var e=n?n.length:0;return e?((r?Ur(n,t,r):null==t)&&(t=1),t=e-(+t||0),Et(n,0,0>t?0:t)):[]}function Kr(n){return n?n[0]:w}function Vr(n,t,e){var u=n?n.length:0;if(!u)return-1;if(typeof e=="number")e=0>e?bu(u+e,0):e;else if(e)return e=Lt(n,t), +er?bu(u+r,0):r||0,typeof n=="string"||!Oo(n)&&be(n)?r<=u&&-1t?0:+t||0,e);++r=n&&(t=w),r}}function ae(n,t,r){function e(t,r){r&&iu(r),a=p=h=w,t&&(_=ho(),c=n.apply(s,f),p||a||(f=s=w))}function u(){var n=t-(ho()-l);0>=n||n>t?e(h,a):p=su(u,n)}function o(){e(g,p); +}function i(){if(f=arguments,l=ho(),s=this,h=g&&(p||!y),false===v)var r=y&&!p;else{a||y||(_=l);var e=v-(l-_),i=0>=e||e>v;i?(a&&(a=iu(a)),_=l,c=n.apply(s,f)):a||(a=su(o,e))}return i&&p?p=iu(p):p||t===v||(p=su(u,t)),r&&(i=true,c=n.apply(s,f)),!i||p||a||(f=s=w),c}var f,a,c,l,s,p,h,_=0,v=false,g=true;if(typeof n!="function")throw new Ge(L);if(t=0>t?0:+t||0,true===r)var y=true,g=false;else ge(r)&&(y=!!r.leading,v="maxWait"in r&&bu(+r.maxWait||0,t),g="trailing"in r?!!r.trailing:g);return i.cancel=function(){p&&iu(p),a&&iu(a), +_=0,a=p=h=w},i}function ce(n,t){function r(){var e=arguments,u=t?t.apply(this,e):e[0],o=r.cache;return o.has(u)?o.get(u):(e=n.apply(this,e),r.cache=o.set(u,e),e)}if(typeof n!="function"||t&&typeof t!="function")throw new Ge(L);return r.cache=new ce.Cache,r}function le(n,t){if(typeof n!="function")throw new Ge(L);return t=bu(t===w?n.length-1:+t||0,0),function(){for(var r=arguments,e=-1,u=bu(r.length-t,0),o=Be(u);++et}function pe(n){return h(n)&&Er(n)&&nu.call(n,"callee")&&!cu.call(n,"callee")}function he(n,t,r,e){return e=(r=typeof r=="function"?Bt(r,e,3):w)?r(n,t):w,e===w?dt(n,t,r):!!e}function _e(n){return h(n)&&typeof n.message=="string"&&ru.call(n)==P}function ve(n){return ge(n)&&ru.call(n)==K}function ge(n){var t=typeof n;return!!n&&("object"==t||"function"==t)}function ye(n){ +return null==n?false:ve(n)?uu.test(Qe.call(n)):h(n)&&Rn.test(n)}function de(n){return typeof n=="number"||h(n)&&ru.call(n)==V}function me(n){var t;if(!h(n)||ru.call(n)!=Z||pe(n)||!(nu.call(n,"constructor")||(t=n.constructor,typeof t!="function"||t instanceof t)))return false;var r;return ht(n,function(n,t){r=t}),r===w||nu.call(n,r)}function we(n){return ge(n)&&ru.call(n)==Y}function be(n){return typeof n=="string"||h(n)&&ru.call(n)==G}function xe(n){return h(n)&&Sr(n.length)&&!!Sn[ru.call(n)]}function Ae(n,t){ +return nt||!n||!mu(t))return r;do t%2&&(r+=n),t=yu(t/2),n+=n;while(t);return r}function We(n,t,r){var e=n;return(n=u(n))?(r?Ur(e,t,r):null==t)?n.slice(g(n),y(n)+1):(t+="",n.slice(o(n,t),i(n,t)+1)):n}function $e(n,t,r){return r&&Ur(n,t,r)&&(t=w),n=u(n),n.match(t||Wn)||[]}function Se(n,t,r){return r&&Ur(n,t,r)&&(t=w),h(n)?Ne(n):ut(n,t)}function Fe(n){ +return n}function Ne(n){return bt(ot(n,true))}function Te(n,t,r){if(null==r){var e=ge(t),u=e?zo(t):w;((u=u&&u.length?gt(t,u):w)?u.length:e)||(u=false,r=t,t=n,n=this)}u||(u=gt(t,zo(t)));var o=true,e=-1,i=ve(n),f=u.length;false===r?o=false:ge(r)&&"chain"in r&&(o=r.chain);for(;++e=$)return r}else n=0;return Lu(r,e)}}(),Mu=le(function(n,t){ +return h(n)&&Er(n)?ft(n,pt(t,false,true)):[]}),qu=tr(),Pu=tr(true),Ku=le(function(n){for(var t=n.length,e=t,u=Be(l),o=xr(),i=o===r,f=[];e--;){var a=n[e]=Er(a=n[e])?a:[];u[e]=i&&120<=a.length&&gu&&lu?new Dn(e&&a):null}var i=n[0],c=-1,l=i?i.length:0,s=u[0];n:for(;++c(s?Mn(s,a):o(f,a,0))){for(e=t;--e;){var p=u[e];if(0>(p?Mn(p,a):o(n[e],a,0)))continue n}s&&s.push(a),f.push(a)}return f}),Vu=le(function(t,r){r=pt(r);var e=rt(t,r);return It(t,r.sort(n)),e}),Zu=vr(),Yu=vr(true),Gu=le(function(n){return St(pt(n,false,true)); +}),Ju=le(function(n,t){return Er(n)?ft(n,t):[]}),Xu=le(Jr),Hu=le(function(n){var t=n.length,r=2--n?t.apply(this,arguments):void 0}},Nn.ary=function(n,t,r){return r&&Ur(n,t,r)&&(t=w),t=n&&null==t?n.length:bu(+t||0,0),gr(n,E,w,w,w,w,t)},Nn.assign=Co,Nn.at=no,Nn.before=fe,Nn.bind=_o,Nn.bindAll=vo,Nn.bindKey=go,Nn.callback=Se,Nn.chain=Qr,Nn.chunk=function(n,t,r){t=(r?Ur(n,t,r):null==t)?1:bu(yu(t)||1,1),r=0;for(var e=n?n.length:0,u=-1,o=Be(vu(e/t));rr&&(r=-r>u?0:u+r),e=e===w||e>u?u:+e||0,0>e&&(e+=u),u=r>e?0:e>>>0,r>>>=0;rt?0:t)):[]},Nn.takeRight=function(n,t,r){var e=n?n.length:0;return e?((r?Ur(n,t,r):null==t)&&(t=1),t=e-(+t||0),Et(n,0>t?0:t)):[]},Nn.takeRightWhile=function(n,t,r){ +return n&&n.length?Nt(n,wr(t,r,3),false,true):[]},Nn.takeWhile=function(n,t,r){return n&&n.length?Nt(n,wr(t,r,3)):[]},Nn.tap=function(n,t,r){return t.call(r,n),n},Nn.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new Ge(L);return false===r?e=false:ge(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),ae(n,t,{leading:e,maxWait:+t,trailing:u})},Nn.thru=ne,Nn.times=function(n,t,r){if(n=yu(n),1>n||!mu(n))return[];var e=-1,u=Be(xu(n,4294967295));for(t=Bt(t,r,1);++ee?u[e]=t(e):t(e); +return u},Nn.toArray=je,Nn.toPlainObject=ke,Nn.transform=function(n,t,r,e){var u=Oo(n)||xe(n);return t=wr(t,e,4),null==r&&(u||ge(n)?(e=n.constructor,r=u?Oo(n)?new e:[]:$u(ve(e)?e.prototype:w)):r={}),(u?Pn:_t)(n,function(n,e,u){return t(r,n,e,u)}),r},Nn.union=Gu,Nn.uniq=Gr,Nn.unzip=Jr,Nn.unzipWith=Xr,Nn.values=Ee,Nn.valuesIn=function(n){return Ft(n,Re(n))},Nn.where=function(n,t){return re(n,bt(t))},Nn.without=Ju,Nn.wrap=function(n,t){return t=null==t?Fe:t,gr(t,R,w,[n],[])},Nn.xor=function(){for(var n=-1,t=arguments.length;++nr?0:+r||0,e),r-=t.length,0<=r&&n.indexOf(t,r)==r},Nn.escape=function(n){return(n=u(n))&&hn.test(n)?n.replace(sn,c):n},Nn.escapeRegExp=function(n){return(n=u(n))&&bn.test(n)?n.replace(wn,l):n||"(?:)"},Nn.every=te,Nn.find=ro,Nn.findIndex=qu,Nn.findKey=$o,Nn.findLast=eo, +Nn.findLastIndex=Pu,Nn.findLastKey=So,Nn.findWhere=function(n,t){return ro(n,bt(t))},Nn.first=Kr,Nn.floor=ni,Nn.get=function(n,t,r){return n=null==n?w:yt(n,Dr(t),t+""),n===w?r:n},Nn.gt=se,Nn.gte=function(n,t){return n>=t},Nn.has=function(n,t){if(null==n)return false;var r=nu.call(n,t);if(!r&&!Wr(t)){if(t=Dr(t),n=1==t.length?n:yt(n,Et(t,0,-1)),null==n)return false;t=Zr(t),r=nu.call(n,t)}return r||Sr(n.length)&&Cr(t,n.length)&&(Oo(n)||pe(n))},Nn.identity=Fe,Nn.includes=ee,Nn.indexOf=Vr,Nn.inRange=function(n,t,r){ +return t=+t||0,r===w?(r=t,t=0):r=+r||0,n>=xu(t,r)&&nr?bu(e+r,0):xu(r||0,e-1))+1;else if(r)return u=Lt(n,t,true)-1,n=n[u],(t===t?t===n:n!==n)?u:-1; +if(t!==t)return p(n,u,true);for(;u--;)if(n[u]===t)return u;return-1},Nn.lt=Ae,Nn.lte=function(n,t){return n<=t},Nn.max=ti,Nn.min=ri,Nn.noConflict=function(){return Zn._=eu,this},Nn.noop=Le,Nn.now=ho,Nn.pad=function(n,t,r){n=u(n),t=+t;var e=n.length;return er?0:+r||0,n.length),n.lastIndexOf(t,r)==r},Nn.sum=function(n,t,r){if(r&&Ur(n,t,r)&&(t=w),t=wr(t,r,3),1==t.length){n=Oo(n)?n:zr(n),r=n.length;for(var e=0;r--;)e+=+t(n[r])||0;n=e}else n=$t(n,t);return n},Nn.template=function(n,t,r){var e=Nn.templateSettings;r&&Ur(n,t,r)&&(t=r=w),n=u(n),t=nt(tt({},r||t),e,Qn),r=nt(tt({},t.imports),e.imports,Qn); +var o,i,f=zo(r),a=Ft(r,f),c=0;r=t.interpolate||Cn;var l="__p+='";r=Ze((t.escape||Cn).source+"|"+r.source+"|"+(r===gn?jn:Cn).source+"|"+(t.evaluate||Cn).source+"|$","g");var p="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";if(n.replace(r,function(t,r,e,u,f,a){return e||(e=u),l+=n.slice(c,a).replace(Un,s),r&&(o=true,l+="'+__e("+r+")+'"),f&&(i=true,l+="';"+f+";\n__p+='"),e&&(l+="'+((__t=("+e+"))==null?'':__t)+'"),c=a+t.length,t}),l+="';",(t=t.variable)||(l="with(obj){"+l+"}"),l=(i?l.replace(fn,""):l).replace(an,"$1").replace(cn,"$1;"), +l="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(o?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}",t=Jo(function(){return qe(f,p+"return "+l).apply(w,a)}),t.source=l,_e(t))throw t;return t},Nn.trim=We,Nn.trimLeft=function(n,t,r){var e=n;return(n=u(n))?n.slice((r?Ur(e,t,r):null==t)?g(n):o(n,t+"")):n},Nn.trimRight=function(n,t,r){var e=n;return(n=u(n))?(r?Ur(e,t,r):null==t)?n.slice(0,y(n)+1):n.slice(0,i(n,t+"")+1):n; +},Nn.trunc=function(n,t,r){r&&Ur(n,t,r)&&(t=w);var e=U;if(r=W,null!=t)if(ge(t)){var o="separator"in t?t.separator:o,e="length"in t?+t.length||0:e;r="omission"in t?u(t.omission):r}else e=+t||0;if(n=u(n),e>=n.length)return n;if(e-=r.length,1>e)return r;if(t=n.slice(0,e),null==o)return t+r;if(we(o)){if(n.slice(e).search(o)){var i,f=n.slice(0,e);for(o.global||(o=Ze(o.source,(kn.exec(o)||"")+"g")),o.lastIndex=0;n=o.exec(f);)i=n.index;t=t.slice(0,null==i?e:i)}}else n.indexOf(o,e)!=e&&(o=t.lastIndexOf(o), +-1u.__dir__?"Right":"")}),u},zn.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),Pn(["filter","map","takeWhile"],function(n,t){ +var r=t+1,e=r!=T;zn.prototype[n]=function(n,t){var u=this.clone();return u.__iteratees__.push({iteratee:wr(n,t,1),type:r}),u.__filtered__=u.__filtered__||e,u}}),Pn(["first","last"],function(n,t){var r="take"+(t?"Right":"");zn.prototype[n]=function(){return this[r](1).value()[0]}}),Pn(["initial","rest"],function(n,t){var r="drop"+(t?"":"Right");zn.prototype[n]=function(){return this.__filtered__?new zn(this):this[r](1)}}),Pn(["pluck","where"],function(n,t){var r=t?"filter":"map",e=t?bt:ze;zn.prototype[n]=function(n){ +return this[r](e(n))}}),zn.prototype.compact=function(){return this.filter(Fe)},zn.prototype.reject=function(n,t){return n=wr(n,t,1),this.filter(function(t){return!n(t)})},zn.prototype.slice=function(n,t){n=null==n?0:+n||0;var r=this;return r.__filtered__&&(0t)?new zn(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==w&&(t=+t||0,r=0>t?r.dropRight(-t):r.take(t-n)),r)},zn.prototype.takeRightWhile=function(n,t){return this.reverse().takeWhile(n,t).reverse()},zn.prototype.toArray=function(){return this.take(Ru); +},_t(zn.prototype,function(n,t){var r=/^(?:filter|map|reject)|While$/.test(t),e=/^(?:first|last)$/.test(t),u=Nn[e?"take"+("last"==t?"Right":""):t];u&&(Nn.prototype[t]=function(){function t(n){return e&&i?u(n,1)[0]:u.apply(w,Jn([n],o))}var o=e?[1]:arguments,i=this.__chain__,f=this.__wrapped__,a=!!this.__actions__.length,c=f instanceof zn,l=o[0],s=c||Oo(f);return s&&r&&typeof l=="function"&&1!=l.length&&(c=s=false),l={func:ne,args:[t],thisArg:w},a=c&&!a,e&&!i?a?(f=f.clone(),f.__actions__.push(l),n.call(f)):u.call(w,this.value())[0]:!e&&s?(f=a?f:new zn(this), +f=n.apply(f,o),f.__actions__.push(l),new Ln(f,i)):this.thru(t)})}),Pn("join pop push replace shift sort splice split unshift".split(" "),function(n){var t=(/^(?:replace|split)$/.test(n)?He:Je)[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:join|pop|replace|shift)$/.test(n);Nn.prototype[n]=function(){var n=arguments;return e&&!this.__chain__?t.apply(this.value(),n):this[r](function(r){return t.apply(r,n)})}}),_t(zn.prototype,function(n,t){var r=Nn[t];if(r){var e=r.name+"";(Wu[e]||(Wu[e]=[])).push({ +name:t,func:r})}}),Wu[sr(w,A).name]=[{name:"wrapper",func:w}],zn.prototype.clone=function(){var n=new zn(this.__wrapped__);return n.__actions__=qn(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=qn(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=qn(this.__views__),n},zn.prototype.reverse=function(){if(this.__filtered__){var n=new zn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},zn.prototype.value=function(){ +var n,t=this.__wrapped__.value(),r=this.__dir__,e=Oo(t),u=0>r,o=e?t.length:0;n=o;for(var i=this.__views__,f=0,a=-1,c=i.length;++a"'`]/g,pn=RegExp(ln.source),hn=RegExp(sn.source),_n=/<%-([\s\S]+?)%>/g,vn=/<%([\s\S]+?)%>/g,gn=/<%=([\s\S]+?)%>/g,yn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,dn=/^\w*$/,mn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,wn=/^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,bn=RegExp(wn.source),xn=/[\u0300-\u036f\ufe20-\ufe23]/g,An=/\\(\\)?/g,jn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,kn=/\w*$/,In=/^0[xX]/,Rn=/^\[object .+?Constructor\]$/,On=/^\d+$/,En=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,Cn=/($^)/,Un=/['\n\r\u2028\u2029\\]/g,Wn=RegExp("[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?=[A-Z\\xc0-\\xd6\\xd8-\\xde][a-z\\xdf-\\xf6\\xf8-\\xff]+)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+|[A-Z\\xc0-\\xd6\\xd8-\\xde]+|[0-9]+","g"),$n="Array ArrayBuffer Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Math Number Object RegExp Set String _ clearTimeout isFinite parseFloat parseInt setTimeout TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap".split(" "),Sn={}; +Sn[X]=Sn[H]=Sn[Q]=Sn[nn]=Sn[tn]=Sn[rn]=Sn[en]=Sn[un]=Sn[on]=true,Sn[B]=Sn[D]=Sn[J]=Sn[M]=Sn[q]=Sn[P]=Sn[K]=Sn["[object Map]"]=Sn[V]=Sn[Z]=Sn[Y]=Sn["[object Set]"]=Sn[G]=Sn["[object WeakMap]"]=false;var Fn={};Fn[B]=Fn[D]=Fn[J]=Fn[M]=Fn[q]=Fn[X]=Fn[H]=Fn[Q]=Fn[nn]=Fn[tn]=Fn[V]=Fn[Z]=Fn[Y]=Fn[G]=Fn[rn]=Fn[en]=Fn[un]=Fn[on]=true,Fn[P]=Fn[K]=Fn["[object Map]"]=Fn["[object Set]"]=Fn["[object WeakMap]"]=false;var Nn={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a", +"\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y", +"\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss"},Tn={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},Ln={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},zn={"function":true,object:true},Bn={0:"x30",1:"x31",2:"x32",3:"x33",4:"x34",5:"x35",6:"x36",7:"x37",8:"x38",9:"x39",A:"x41",B:"x42",C:"x43",D:"x44",E:"x45",F:"x46",a:"x61",b:"x62",c:"x63",d:"x64",e:"x65",f:"x66",n:"x6e",r:"x72",t:"x74",u:"x75",v:"x76",x:"x78"},Dn={"\\":"\\", +"'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Mn=zn[typeof exports]&&exports&&!exports.nodeType&&exports,qn=zn[typeof module]&&module&&!module.nodeType&&module,Pn=zn[typeof self]&&self&&self.Object&&self,Kn=zn[typeof window]&&window&&window.Object&&window,Vn=qn&&qn.exports===Mn&&Mn,Zn=Mn&&qn&&typeof global=="object"&&global&&global.Object&&global||Kn!==(this&&this.window)&&Kn||Pn||this,Yn=m();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Zn._=Yn, define(function(){ +return Yn})):Mn&&qn?Vn?(qn.exports=Yn)._=Yn:Mn._=Yn:Zn._=Yn}).call(this); +(function() { + 'use strict'; + + if (self.fetch) { + return + } + + function normalizeName(name) { + if (typeof name !== 'string') { + name = String(name) + } + if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { + throw new TypeError('Invalid character in header field name') + } + return name.toLowerCase() + } + + function normalizeValue(value) { + if (typeof value !== 'string') { + value = String(value) + } + return value + } + + function Headers(headers) { + this.map = {} + + if (headers instanceof Headers) { + headers.forEach(function(value, name) { + this.append(name, value) + }, this) + + } else if (headers) { + Object.getOwnPropertyNames(headers).forEach(function(name) { + this.append(name, headers[name]) + }, this) + } + } + + Headers.prototype.append = function(name, value) { + name = normalizeName(name) + value = normalizeValue(value) + var list = this.map[name] + if (!list) { + list = [] + this.map[name] = list + } + list.push(value) + } + + Headers.prototype['delete'] = function(name) { + delete this.map[normalizeName(name)] + } + + Headers.prototype.get = function(name) { + var values = this.map[normalizeName(name)] + return values ? values[0] : null + } + + Headers.prototype.getAll = function(name) { + return this.map[normalizeName(name)] || [] + } + + Headers.prototype.has = function(name) { + return this.map.hasOwnProperty(normalizeName(name)) + } + + Headers.prototype.set = function(name, value) { + this.map[normalizeName(name)] = [normalizeValue(value)] + } + + Headers.prototype.forEach = function(callback, thisArg) { + Object.getOwnPropertyNames(this.map).forEach(function(name) { + this.map[name].forEach(function(value) { + callback.call(thisArg, value, name, this) + }, this) + }, this) + } + + function consumed(body) { + if (body.bodyUsed) { + return Promise.reject(new TypeError('Already read')) + } + body.bodyUsed = true + } + + function fileReaderReady(reader) { + return new Promise(function(resolve, reject) { + reader.onload = function() { + resolve(reader.result) + } + reader.onerror = function() { + reject(reader.error) + } + }) + } + + function readBlobAsArrayBuffer(blob) { + var reader = new FileReader() + reader.readAsArrayBuffer(blob) + return fileReaderReady(reader) + } + + function readBlobAsText(blob) { + var reader = new FileReader() + reader.readAsText(blob) + return fileReaderReady(reader) + } + + var support = { + blob: 'FileReader' in self && 'Blob' in self && (function() { + try { + new Blob(); + return true + } catch(e) { + return false + } + })(), + formData: 'FormData' in self + } + + function Body() { + this.bodyUsed = false + + + this._initBody = function(body) { + this._bodyInit = body + if (typeof body === 'string') { + this._bodyText = body + } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { + this._bodyBlob = body + } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { + this._bodyFormData = body + } else if (!body) { + this._bodyText = '' + } else { + throw new Error('unsupported BodyInit type') + } + } + + if (support.blob) { + this.blob = function() { + var rejected = consumed(this) + if (rejected) { + return rejected + } + + if (this._bodyBlob) { + return Promise.resolve(this._bodyBlob) + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as blob') + } else { + return Promise.resolve(new Blob([this._bodyText])) + } + } + + this.arrayBuffer = function() { + return this.blob().then(readBlobAsArrayBuffer) + } + + this.text = function() { + var rejected = consumed(this) + if (rejected) { + return rejected + } + + if (this._bodyBlob) { + return readBlobAsText(this._bodyBlob) + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as text') + } else { + return Promise.resolve(this._bodyText) + } + } + } else { + this.text = function() { + var rejected = consumed(this) + return rejected ? rejected : Promise.resolve(this._bodyText) + } + } + + if (support.formData) { + this.formData = function() { + return this.text().then(decode) + } + } + + this.json = function() { + return this.text().then(JSON.parse) + } + + return this + } + + // HTTP methods whose capitalization should be normalized + var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + + function normalizeMethod(method) { + var upcased = method.toUpperCase() + return (methods.indexOf(upcased) > -1) ? upcased : method + } + + function Request(url, options) { + options = options || {} + this.url = url + + this.credentials = options.credentials || 'omit' + this.headers = new Headers(options.headers) + this.method = normalizeMethod(options.method || 'GET') + this.mode = options.mode || null + this.referrer = null + + if ((this.method === 'GET' || this.method === 'HEAD') && options.body) { + throw new TypeError('Body not allowed for GET or HEAD requests') + } + this._initBody(options.body) + } + + function decode(body) { + var form = new FormData() + body.trim().split('&').forEach(function(bytes) { + if (bytes) { + var split = bytes.split('=') + var name = split.shift().replace(/\+/g, ' ') + var value = split.join('=').replace(/\+/g, ' ') + form.append(decodeURIComponent(name), decodeURIComponent(value)) + } + }) + return form + } + + function headers(xhr) { + var head = new Headers() + var pairs = xhr.getAllResponseHeaders().trim().split('\n') + pairs.forEach(function(header) { + var split = header.trim().split(':') + var key = split.shift().trim() + var value = split.join(':').trim() + head.append(key, value) + }) + return head + } + + Body.call(Request.prototype) + + function Response(bodyInit, options) { + if (!options) { + options = {} + } + + this._initBody(bodyInit) + this.type = 'default' + this.url = null + this.status = options.status + this.ok = this.status >= 200 && this.status < 300 + this.statusText = options.statusText + this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) + this.url = options.url || '' + } + + Body.call(Response.prototype) + + self.Headers = Headers; + self.Request = Request; + self.Response = Response; + + self.fetch = function(input, init) { + // TODO: Request constructor should accept input, init + var request + if (Request.prototype.isPrototypeOf(input) && !init) { + request = input + } else { + request = new Request(input, init) + } + + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest() + + function responseURL() { + if ('responseURL' in xhr) { + return xhr.responseURL + } + + // Avoid security warnings on getResponseHeader when not allowed by CORS + if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { + return xhr.getResponseHeader('X-Request-URL') + } + + return; + } + + xhr.onload = function() { + var status = (xhr.status === 1223) ? 204 : xhr.status + if (status < 100 || status > 599) { + reject(new TypeError('Network request failed')) + return + } + var options = { + status: status, + statusText: xhr.statusText, + headers: headers(xhr), + url: responseURL() + } + var body = 'response' in xhr ? xhr.response : xhr.responseText; + resolve(new Response(body, options)) + } + + xhr.onerror = function() { + reject(new TypeError('Network request failed')) + } + + xhr.open(request.method, request.url, true) + + if (request.credentials === 'include') { + xhr.withCredentials = true + } + + if ('responseType' in xhr && support.blob) { + xhr.responseType = 'blob' + } + + request.headers.forEach(function(value, name) { + xhr.setRequestHeader(name, value) + }) + + xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) + }) + } + self.fetch.polyfill = true +})(); + + + + + + + + + + + + + + + + + + +function ready(fn) { + if (document.readyState != 'loading'){ + fn(); + } else { + document.addEventListener('DOMContentLoaded', fn); + } +} + +var getValues = function(){ + var name = document.querySelector('input[name=lion-name]').value; + var pride = document.querySelector('input[name=lion-pride]').value; + var age = document.querySelector('input[type=number').value; + var gender = document.querySelector('select'); + gender = gender.options[gender.selectedIndex].value; + + + document.querySelector('input[name=lion-name]').value = ''; + document.querySelector('input[name=lion-pride]').value = ''; + document.querySelector('input[type=number').value = ''; + + + return { + name: name, + pride: pride, + age: age, + gender: gender + }; +}; + + +var lionTemplate = '

<%= name %>'+ + '

<%= pride %>

'+ + 'age: <%= age %> '+ + '<%= gender %>'; + +var lions = []; + +var makeTemplate = function(data) { + var li = document.createElement('li'); + var lionList = document.querySelector('.lion-list'); + var compiled = _.template(lionTemplate); + var lionHtml = compiled(data); + li.innerHTML = lionHtml; + lionList.insertBefore(li, lionList.firstChild); +} + +var updateLionList = function(){ + var lionData = lions[lions.length-1]; + makeTemplate(lionData); +} + +var makeLionList = function() { + lions.forEach(function(lion) { + makeTemplate(lion); + }); +}; + +var getAllLions = function() { + fetch('/lions') + .then(function(resp) { + console.log(resp); + return resp.json(); + }) + .then(function(data) { + lions = lions.concat(data); + makeLionList(); + }); +} + +ready(function() { + getAllLions(); + var form = document.querySelector('form'); + + form.addEventListener('submit', function(e) { + e.preventDefault(); + + var values = getValues(); + console.log(values); + fetch('/lions', { + method: 'post', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + + body: JSON.stringify(values) + }) + .then(function(resp) { + return resp.json(); + }) + .then(function(createdLion){ + lions.push(createdLion); + updateLionList(); + }) + return false; + }) +}); diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..e9987a7 --- /dev/null +++ b/client/index.html @@ -0,0 +1,48 @@ + + + + Lions + + + + +
+

Lions

+ +
+
+ + + + +
+ +
+ + + + +
+ + + + + +
+ +
+
    +
    +
    + + + diff --git a/client/style.css b/client/style.css new file mode 100644 index 0000000..08c888e --- /dev/null +++ b/client/style.css @@ -0,0 +1,542 @@ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS and IE text size adjust after device orientation change, + * without disabling user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability of focused elements when they are also in an + * active/hover state. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + box-sizing: content-box; /* 2 */ +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} + + + +/**************************************************************/ +body, html { + height: 100%; +} + +main { + width: 80vw; + margin: 0 auto; +} +.textbox { + max-width: 200px; + margin-bottom: 40px; + position: relative; + width: 100%; +} +.textbox input { + font-size: 18px; + padding: 10px 10px 10px 5px; + display: block; + width: 100%; + background: transparent; + border: none; + border-bottom: 1px solid #bf0000; +} +.textbox input:focus { + outline: none; +} +.textbox input:focus ~ .highlight { + animation: inputHighlight 0.3s ease; +} +.textbox input:focus ~ .bar:before, +.textbox input:focus ~ .bar:after { + width: 50%; +} +.textbox input:focus ~ label, +.textbox input:valid ~ label { + top: -20px; + font-size: 14px; + color: #ff1a1a; +} +.textbox label { + color: #4d0000; + font-size: 18px; + font-weight: normal; + position: absolute; + pointer-events: none; + left: 5px; + top: 10px; + transition: all 0.2s ease; +} +.textbox .bar { + position: relative; + display: block; +} +.textbox .bar:before, +.textbox .bar:after { + content: ''; + height: 2px; + width: 0; + bottom: 1px; + position: absolute; + background: #f00; + transition: ease all 0.2s; +} +.textbox .bar:before { + left: 50%; +} +.textbox .bar:after { + right: 50%; +} +.textbox .highlight { + position: absolute; + height: 60%; + width: 100px; + top: 25%; + left: 0; + pointer-events: none; + opacity: 0.5; +} +@-moz-keyframes inputHighlight { + from { + background: #f00; + } + to { + background: transparent; + width: 0; + } +} +@-webkit-keyframes inputHighlight { + from { + background: #f00; + } + to { + background: transparent; + width: 0; + } +} +@-o-keyframes inputHighlight { + from { + background: #f00; + } + to { + background: transparent; + width: 0; + } +} +@keyframes inputHighlight { + from { + background: #f00; + } + to { + background: transparent; + width: 0; + } +} diff --git a/index.html b/index.html deleted file mode 100644 index 1667638..0000000 --- a/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - hello - - -

    hey there

    - - diff --git a/server.js b/server.js deleted file mode 100644 index 65828b1..0000000 --- a/server.js +++ /dev/null @@ -1,24 +0,0 @@ -var express = require('express'); -var app = express(); - -var jsonData = {count: 12, message: 'hey'}; - -app.get('/', function(req, res){ - // res.sendFile takes an absolute path to a file and - // sets the mime type based n the file extname - res.sendFile(__dirname + '/index.html', function(err) { - if (err) { - res.status(500).send(err); - } - }) -}); - -app.get('/data', function(req, res) { - res.json(jsonData); -}); - - -var port = 3000; -app.listen(port, function(){ - console.log('listening on http://localhost:', port); -}); diff --git a/server/server.js b/server/server.js new file mode 100644 index 0000000..1f15e38 --- /dev/null +++ b/server/server.js @@ -0,0 +1,49 @@ +var express = require('express'); +var bodyParser = require('body-parser'); +var app = express(); +var _ = require('lodash'); + +app.use(express.static('client')); +app.use(bodyParser.urlencoded({extended: true})); +app.use(bodyParser.json()); + +var lions = []; +var id = 0; + +app.get('/lions', function(req, res){ + res.json(lions); +}); + +app.get('/lions/:id', function(req, res){ + var lion = _.find(lions, {id: req.params.id}); + res.json(lion || {}); +}); + +app.post('/lions', function(req, res) { + var lion = req.body; + id++; + lion.id = id + ''; + + lions.push(lion); + + res.json(lion); +}); + + +app.put('/lions/:id', function(req, res) { + var update = req.body; + if (update.id) { + delete update.id + } + + var lion = _.findIndex(lions, {id: req.params.id}); + if (!lions[lion]) { + res.send(); + } else { + var updatedLion = _.assign(lions[lion], update); + res.json(updatedLion); + } +}); + +app.listen(3000); +console.log('on port 3000'); From 28698e2519d63855c4c6108cd4ede9396c5dbd6e Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 04:52:00 -0500 Subject: [PATCH 04/27] chore --- server/server.js | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/server/server.js b/server/server.js index 1f15e38..9a17611 100644 --- a/server/server.js +++ b/server/server.js @@ -2,27 +2,52 @@ var express = require('express'); var bodyParser = require('body-parser'); var app = express(); var _ = require('lodash'); +var morgan = require('morgan'); +var lions = []; +var id = 0; + +var updateId = function(req, res, next) { + if (!req.body.id) { + id++; + req.body.id = id + ''; + } + next(); +}; + +app.use(morgan('dev')) app.use(express.static('client')); app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.json()); -var lions = []; -var id = 0; +app.use(function(err, req, res, next) { + if (err) { + res.status(500).send(error); + } +}); + +app.param('id', function(req, res, next, id) { + var todo = _.find(todos, {id: id}); + + if (todo) { + req.todo = todo; + next(); + } else { + res.send(); + } +}); app.get('/lions', function(req, res){ res.json(lions); }); app.get('/lions/:id', function(req, res){ - var lion = _.find(lions, {id: req.params.id}); + var lion = req.todo; res.json(lion || {}); }); -app.post('/lions', function(req, res) { +app.post('/lions', updateId, function(req, res) { var lion = req.body; - id++; - lion.id = id + ''; lions.push(lion); From fb9a2a4ff84fededf661d5c82eb99339bd171d46 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 05:09:32 -0500 Subject: [PATCH 05/27] chore --- server/server.js | 58 +++++------------------------------------------- 1 file changed, 6 insertions(+), 52 deletions(-) diff --git a/server/server.js b/server/server.js index 9a17611..82316f8 100644 --- a/server/server.js +++ b/server/server.js @@ -4,21 +4,17 @@ var app = express(); var _ = require('lodash'); var morgan = require('morgan'); -var lions = []; -var id = 0; - -var updateId = function(req, res, next) { - if (!req.body.id) { - id++; - req.body.id = id + ''; - } - next(); -}; +var lionRouter = require('./lions'); +var tigerRouter = require('./tigers'); app.use(morgan('dev')) app.use(express.static('client')); app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.json()); +// this is called mounting. when ever a req comes in for +// '/lion' we want to use this router +app.use('/lions', lionRouter); +app.use('/tigers', tigerRouter); app.use(function(err, req, res, next) { if (err) { @@ -26,49 +22,7 @@ app.use(function(err, req, res, next) { } }); -app.param('id', function(req, res, next, id) { - var todo = _.find(todos, {id: id}); - - if (todo) { - req.todo = todo; - next(); - } else { - res.send(); - } -}); - -app.get('/lions', function(req, res){ - res.json(lions); -}); - -app.get('/lions/:id', function(req, res){ - var lion = req.todo; - res.json(lion || {}); -}); - -app.post('/lions', updateId, function(req, res) { - var lion = req.body; - - lions.push(lion); - - res.json(lion); -}); - -app.put('/lions/:id', function(req, res) { - var update = req.body; - if (update.id) { - delete update.id - } - - var lion = _.findIndex(lions, {id: req.params.id}); - if (!lions[lion]) { - res.send(); - } else { - var updatedLion = _.assign(lions[lion], update); - res.json(updatedLion); - } -}); app.listen(3000); console.log('on port 3000'); From f482a9319e5b8387d2c588cdbdd9fdb70a824182 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 05:09:42 -0500 Subject: [PATCH 06/27] chore --- server/lions.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ server/tigers.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 server/lions.js create mode 100644 server/tigers.js diff --git a/server/lions.js b/server/lions.js new file mode 100644 index 0000000..1b85c09 --- /dev/null +++ b/server/lions.js @@ -0,0 +1,58 @@ +var lionRouter = require('express').Router(); + +var lions = []; +var id = 0; + +var updateId = function(req, res, next) { + if (!req.body.id) { + id++; + req.body.id = id + ''; + } + next(); +}; + +lionRouter.param('id', function(req, res, next, id) { + var todo = _.find(todos, {id: id}); + + if (todo) { + req.todo = todo; + next(); + } else { + res.send(); + } +}); + +lionRouter.get('/', function(req, res){ + res.json(lions); +}); + +lionRouter.get('/:id', function(req, res){ + var lion = req.todo; + res.json(lion || {}); +}); + +lionRouter.post('/', updateId, function(req, res) { + var lion = req.body; + + lions.push(lion); + + res.json(lion); +}); + + +lionRouter.put('/:id', function(req, res) { + var update = req.body; + if (update.id) { + delete update.id + } + + var lion = _.findIndex(lions, {id: req.params.id}); + if (!lions[lion]) { + res.send(); + } else { + var updatedLion = _.assign(lions[lion], update); + res.json(updatedLion); + } +}); + +module.exports = lionRouter; diff --git a/server/tigers.js b/server/tigers.js new file mode 100644 index 0000000..a0e4578 --- /dev/null +++ b/server/tigers.js @@ -0,0 +1,58 @@ +var tigerRouter = require('express').Router(); + +var tigers = []; +var id = 0; + +var updateId = function(req, res, next) { + if (!req.body.id) { + id++; + req.body.id = id + ''; + } + next(); +}; + +tigerRouter.param('id', function(req, res, next, id) { + var todo = _.find(todos, {id: id}); + + if (todo) { + req.todo = todo; + next(); + } else { + res.send(); + } +}); + +tigerRouter.get('/', function(req, res){ + res.json(tigers); +}); + +tigerRouter.get('/:id', function(req, res){ + var tiger = req.todo; + res.json(tiger || {}); +}); + +tigerRouter.post('/', updateId, function(req, res) { + var tiger = req.body; + + tigers.push(tiger); + + res.json(tiger); +}); + + +tigerRouter.put('/:id', function(req, res) { + var update = req.body; + if (update.id) { + delete update.id + } + + var tiger = _.findIndex(tigers, {id: req.params.id}); + if (!tigers[tiger]) { + res.send(); + } else { + var updatedtiger = _.assign(tigers[tiger], update); + res.json(updatedtiger); + } +}); + +module.exports = tigerRouter; From 5850443cebd35eb5513f6a87f26c9605d68d8524 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 05:43:08 -0500 Subject: [PATCH 07/27] chore --- index.js | 4 ++++ package.json | 4 ++++ server/lions.js | 15 ++++++++---- server/server.js | 5 ++-- server/spec.js | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 index.js create mode 100644 server/spec.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..68890cc --- /dev/null +++ b/index.js @@ -0,0 +1,4 @@ +var app = require('./server/server'); + +app.listen(3000); +console.log('on port 3000'); diff --git a/package.json b/package.json index ea99b7f..26c6562 100644 --- a/package.json +++ b/package.json @@ -13,5 +13,9 @@ "express": "^4.13.1", "lodash": "^3.10.0", "morgan": "^1.6.1" + }, + "devDependencies": { + "chai": "^3.2.0", + "supertest": "^1.0.1" } } diff --git a/server/lions.js b/server/lions.js index 1b85c09..15c4785 100644 --- a/server/lions.js +++ b/server/lions.js @@ -1,3 +1,4 @@ +var _ = require('lodash'); var lionRouter = require('express').Router(); var lions = []; @@ -12,10 +13,10 @@ var updateId = function(req, res, next) { }; lionRouter.param('id', function(req, res, next, id) { - var todo = _.find(todos, {id: id}); + var lion = _.find(lions, {id: id}) - if (todo) { - req.todo = todo; + if (lion) { + req.lion = lion; next(); } else { res.send(); @@ -27,7 +28,7 @@ lionRouter.get('/', function(req, res){ }); lionRouter.get('/:id', function(req, res){ - var lion = req.todo; + var lion = req.lion; res.json(lion || {}); }); @@ -39,6 +40,12 @@ lionRouter.post('/', updateId, function(req, res) { res.json(lion); }); +lionRouter.delete('/:id', function(req, res) { + var lion = _.findIndex(lions, {id: req.params.id}); + lions.splice(lion, 1); + + res.json(req.lion); +}); lionRouter.put('/:id', function(req, res) { var update = req.body; diff --git a/server/server.js b/server/server.js index 82316f8..fb1bfcb 100644 --- a/server/server.js +++ b/server/server.js @@ -18,11 +18,10 @@ app.use('/tigers', tigerRouter); app.use(function(err, req, res, next) { if (err) { - res.status(500).send(error); + res.status(500).send(err); } }); -app.listen(3000); -console.log('on port 3000'); +module.exports = app; diff --git a/server/spec.js b/server/spec.js new file mode 100644 index 0000000..762b9a7 --- /dev/null +++ b/server/spec.js @@ -0,0 +1,59 @@ +var app = require('./server'); +var request = require('supertest'); +var expect = require('chai').expect; + +describe('lions', function(){ + + it('should get all lions', function(done) { + request(app) + .get('/lions') + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + .end(function(err, resp) { + expect(resp.body).to.be.an('array'); + done(); + }) + }); + + it('should create a lion', function(done) { + request(app) + .post('/lions') + .send({ + name: 'Mufasa', + age: 100, + pride: 'Evil lions', + gender:'male' + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(201) + .end(function(err, resp) { + expect(resp.body).to.be.an('object'); + done(); + }) + }); + + it('should delete a lion', function(done) { + request(app) + .post('/lions') + .send({ + name: 'test lion', + age: 100, + pride: 'test lion', + gender:'female' + }) + .set('Accept', 'application/json') + .end(function(err, resp) { + var lion = resp.body; + request(app) + .delete('/lions/' + lion.id) + .end(function(err, resp) { + expect(resp.body).to.eql(lion); + done(); + }); + }) + }); +}); + + From b593bfc828eb82a0c4e6c016619352622b965d80 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 05:47:05 -0500 Subject: [PATCH 08/27] chore --- server/spec.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/server/spec.js b/server/spec.js index 762b9a7..7a066e9 100644 --- a/server/spec.js +++ b/server/spec.js @@ -2,7 +2,7 @@ var app = require('./server'); var request = require('supertest'); var expect = require('chai').expect; -describe('lions', function(){ +describe('[LIONS]', function(){ it('should get all lions', function(done) { request(app) @@ -54,6 +54,30 @@ describe('lions', function(){ }); }) }); + + it('should update a lion', function(done) { + request(app) + .post('/lions') + .send({ + name: 'test lion', + age: 100, + pride: 'test lion', + gender:'female' + }) + .set('Accept', 'application/json') + .end(function(err, resp) { + var lion = resp.body; + request(app) + .put('/lions/' + lion.id) + .send({ + name: 'new name' + }) + .end(function(err, resp) { + expect(resp.body.name).to.equal('new name'); + done(); + }); + }) + }); }); From 15a975e72d193cf438b7cf1df9d079abcd911f6f Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 05:51:23 -0500 Subject: [PATCH 09/27] chore --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bdf250..bf5d246 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ -##Solution to step-1 +##Testing our app + +We're going to use mocha and chai along with supertest to runt inegration test against our api. **Be sure to gloably install mocha**. `npm i -g mocha`. + +To run the test, run `mocha server/spec.js` or `npm test`; From 0a70576c22b16847a39bbedeb0801fcff3a652b3 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 19:05:22 -0500 Subject: [PATCH 10/27] chore --- server/tigers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/tigers.js b/server/tigers.js index a0e4578..08829b3 100644 --- a/server/tigers.js +++ b/server/tigers.js @@ -12,10 +12,10 @@ var updateId = function(req, res, next) { }; tigerRouter.param('id', function(req, res, next, id) { - var todo = _.find(todos, {id: id}); + var tiger = _.find(tigers, {id: id}); - if (todo) { - req.todo = todo; + if (tiger) { + req.tiger = tiger; next(); } else { res.send(); @@ -27,7 +27,7 @@ tigerRouter.get('/', function(req, res){ }); tigerRouter.get('/:id', function(req, res){ - var tiger = req.todo; + var tiger = req.tiger; res.json(tiger || {}); }); From c8976af4040c99d1337b9d4d095c0f5abdd6543a Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 20:37:12 -0500 Subject: [PATCH 11/27] chore --- README.md | 5 - client/app.js | 543 -------------------------- client/index.html | 48 --- client/style.css | 542 ------------------------- index.js | 15 +- package.json | 4 +- server/api/api.js | 9 + server/api/category/categoryRoutes.js | 12 + server/api/post/postRoutes.js | 12 + server/api/user/userRoutes.js | 12 + server/config/config.js | 29 ++ server/config/development.js | 4 + server/config/production.js | 4 + server/config/testing.js | 4 + server/lions.js | 65 --- server/middleware/appMiddlware.js | 9 + server/server.js | 28 +- server/spec.js | 83 ---- server/tigers.js | 58 --- server/util/logger.js | 40 ++ 20 files changed, 158 insertions(+), 1368 deletions(-) delete mode 100644 client/app.js delete mode 100644 client/index.html delete mode 100644 client/style.css create mode 100644 server/api/api.js create mode 100644 server/api/category/categoryRoutes.js create mode 100644 server/api/post/postRoutes.js create mode 100644 server/api/user/userRoutes.js create mode 100644 server/config/config.js create mode 100644 server/config/development.js create mode 100644 server/config/production.js create mode 100644 server/config/testing.js delete mode 100644 server/lions.js create mode 100644 server/middleware/appMiddlware.js delete mode 100644 server/spec.js delete mode 100644 server/tigers.js create mode 100644 server/util/logger.js diff --git a/README.md b/README.md index bf5d246..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,5 +0,0 @@ -##Testing our app - -We're going to use mocha and chai along with supertest to runt inegration test against our api. **Be sure to gloably install mocha**. `npm i -g mocha`. - -To run the test, run `mocha server/spec.js` or `npm test`; diff --git a/client/app.js b/client/app.js deleted file mode 100644 index 1351400..0000000 --- a/client/app.js +++ /dev/null @@ -1,543 +0,0 @@ -/** - * @license - * lodash 3.10.1 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE - * Build: `lodash modern -o ./lodash.js` - */ -;(function(){function n(n,t){if(n!==t){var r=null===n,e=n===w,u=n===n,o=null===t,i=t===w,f=t===t;if(n>t&&!o||!u||r&&!i&&f||e&&f)return 1;if(n=n&&9<=n&&13>=n||32==n||160==n||5760==n||6158==n||8192<=n&&(8202>=n||8232==n||8233==n||8239==n||8287==n||12288==n||65279==n); -}function v(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r=F&&gu&&lu?new Dn(t):null,c=t.length;a&&(i=Mn,f=false,t=a);n:for(;++oi(t,a,0)&&u.push(a);return u}function at(n,t){var r=true;return Su(n,function(n,e,u){return r=!!t(n,e,u)}),r}function ct(n,t,r,e){var u=e,o=u;return Su(n,function(n,i,f){i=+t(n,i,f),(r(i,u)||i===e&&i===o)&&(u=i, -o=n)}),o}function lt(n,t){var r=[];return Su(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function st(n,t,r,e){var u;return r(n,function(n,r,o){return t(n,r,o)?(u=e?r:n,false):void 0}),u}function pt(n,t,r,e){e||(e=[]);for(var u=-1,o=n.length;++ut&&(t=-t>u?0:u+t),r=r===w||r>u?u:+r||0,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Be(u);++e=c)break n;o=e[o],u*="asc"===o||true===o?1:-1;break n}u=t.b-r.b}return u})}function $t(n,t){ -var r=0;return Su(n,function(n,e,u){r+=+t(n,e,u)||0}),r}function St(n,t){var e=-1,u=xr(),o=n.length,i=u===r,f=i&&o>=F,a=f&&gu&&lu?new Dn(void 0):null,c=[];a?(u=Mn,i=false):(f=false,a=t?[]:c);n:for(;++eu(a,s,0)&&((t||f)&&a.push(s),c.push(l))}return c}function Ft(n,t){for(var r=-1,e=t.length,u=Be(e);++r>>1,i=n[o];(r?i<=t:iu?w:o,u=1);++e=F)return t.plant(e).value();for(var u=0,n=r?o[u].apply(this,n):e;++uarguments.length;return typeof e=="function"&&o===w&&Oo(r)?n(r,e,u,i):Ot(r,wr(e,o,4),u,i,t)}}function sr(n,t,r,e,u,o,i,f,a,c){function l(){for(var m=arguments.length,b=m,j=Be(m);b--;)j[b]=arguments[b];if(e&&(j=Mt(j,e,u)),o&&(j=qt(j,o,i)),_||y){var b=l.placeholder,k=v(j,b),m=m-k.length;if(mt?0:t)):[]}function Pr(n,t,r){var e=n?n.length:0;return e?((r?Ur(n,t,r):null==t)&&(t=1),t=e-(+t||0),Et(n,0,0>t?0:t)):[]}function Kr(n){return n?n[0]:w}function Vr(n,t,e){var u=n?n.length:0;if(!u)return-1;if(typeof e=="number")e=0>e?bu(u+e,0):e;else if(e)return e=Lt(n,t), -er?bu(u+r,0):r||0,typeof n=="string"||!Oo(n)&&be(n)?r<=u&&-1t?0:+t||0,e);++r=n&&(t=w),r}}function ae(n,t,r){function e(t,r){r&&iu(r),a=p=h=w,t&&(_=ho(),c=n.apply(s,f),p||a||(f=s=w))}function u(){var n=t-(ho()-l);0>=n||n>t?e(h,a):p=su(u,n)}function o(){e(g,p); -}function i(){if(f=arguments,l=ho(),s=this,h=g&&(p||!y),false===v)var r=y&&!p;else{a||y||(_=l);var e=v-(l-_),i=0>=e||e>v;i?(a&&(a=iu(a)),_=l,c=n.apply(s,f)):a||(a=su(o,e))}return i&&p?p=iu(p):p||t===v||(p=su(u,t)),r&&(i=true,c=n.apply(s,f)),!i||p||a||(f=s=w),c}var f,a,c,l,s,p,h,_=0,v=false,g=true;if(typeof n!="function")throw new Ge(L);if(t=0>t?0:+t||0,true===r)var y=true,g=false;else ge(r)&&(y=!!r.leading,v="maxWait"in r&&bu(+r.maxWait||0,t),g="trailing"in r?!!r.trailing:g);return i.cancel=function(){p&&iu(p),a&&iu(a), -_=0,a=p=h=w},i}function ce(n,t){function r(){var e=arguments,u=t?t.apply(this,e):e[0],o=r.cache;return o.has(u)?o.get(u):(e=n.apply(this,e),r.cache=o.set(u,e),e)}if(typeof n!="function"||t&&typeof t!="function")throw new Ge(L);return r.cache=new ce.Cache,r}function le(n,t){if(typeof n!="function")throw new Ge(L);return t=bu(t===w?n.length-1:+t||0,0),function(){for(var r=arguments,e=-1,u=bu(r.length-t,0),o=Be(u);++et}function pe(n){return h(n)&&Er(n)&&nu.call(n,"callee")&&!cu.call(n,"callee")}function he(n,t,r,e){return e=(r=typeof r=="function"?Bt(r,e,3):w)?r(n,t):w,e===w?dt(n,t,r):!!e}function _e(n){return h(n)&&typeof n.message=="string"&&ru.call(n)==P}function ve(n){return ge(n)&&ru.call(n)==K}function ge(n){var t=typeof n;return!!n&&("object"==t||"function"==t)}function ye(n){ -return null==n?false:ve(n)?uu.test(Qe.call(n)):h(n)&&Rn.test(n)}function de(n){return typeof n=="number"||h(n)&&ru.call(n)==V}function me(n){var t;if(!h(n)||ru.call(n)!=Z||pe(n)||!(nu.call(n,"constructor")||(t=n.constructor,typeof t!="function"||t instanceof t)))return false;var r;return ht(n,function(n,t){r=t}),r===w||nu.call(n,r)}function we(n){return ge(n)&&ru.call(n)==Y}function be(n){return typeof n=="string"||h(n)&&ru.call(n)==G}function xe(n){return h(n)&&Sr(n.length)&&!!Sn[ru.call(n)]}function Ae(n,t){ -return nt||!n||!mu(t))return r;do t%2&&(r+=n),t=yu(t/2),n+=n;while(t);return r}function We(n,t,r){var e=n;return(n=u(n))?(r?Ur(e,t,r):null==t)?n.slice(g(n),y(n)+1):(t+="",n.slice(o(n,t),i(n,t)+1)):n}function $e(n,t,r){return r&&Ur(n,t,r)&&(t=w),n=u(n),n.match(t||Wn)||[]}function Se(n,t,r){return r&&Ur(n,t,r)&&(t=w),h(n)?Ne(n):ut(n,t)}function Fe(n){ -return n}function Ne(n){return bt(ot(n,true))}function Te(n,t,r){if(null==r){var e=ge(t),u=e?zo(t):w;((u=u&&u.length?gt(t,u):w)?u.length:e)||(u=false,r=t,t=n,n=this)}u||(u=gt(t,zo(t)));var o=true,e=-1,i=ve(n),f=u.length;false===r?o=false:ge(r)&&"chain"in r&&(o=r.chain);for(;++e=$)return r}else n=0;return Lu(r,e)}}(),Mu=le(function(n,t){ -return h(n)&&Er(n)?ft(n,pt(t,false,true)):[]}),qu=tr(),Pu=tr(true),Ku=le(function(n){for(var t=n.length,e=t,u=Be(l),o=xr(),i=o===r,f=[];e--;){var a=n[e]=Er(a=n[e])?a:[];u[e]=i&&120<=a.length&&gu&&lu?new Dn(e&&a):null}var i=n[0],c=-1,l=i?i.length:0,s=u[0];n:for(;++c(s?Mn(s,a):o(f,a,0))){for(e=t;--e;){var p=u[e];if(0>(p?Mn(p,a):o(n[e],a,0)))continue n}s&&s.push(a),f.push(a)}return f}),Vu=le(function(t,r){r=pt(r);var e=rt(t,r);return It(t,r.sort(n)),e}),Zu=vr(),Yu=vr(true),Gu=le(function(n){return St(pt(n,false,true)); -}),Ju=le(function(n,t){return Er(n)?ft(n,t):[]}),Xu=le(Jr),Hu=le(function(n){var t=n.length,r=2--n?t.apply(this,arguments):void 0}},Nn.ary=function(n,t,r){return r&&Ur(n,t,r)&&(t=w),t=n&&null==t?n.length:bu(+t||0,0),gr(n,E,w,w,w,w,t)},Nn.assign=Co,Nn.at=no,Nn.before=fe,Nn.bind=_o,Nn.bindAll=vo,Nn.bindKey=go,Nn.callback=Se,Nn.chain=Qr,Nn.chunk=function(n,t,r){t=(r?Ur(n,t,r):null==t)?1:bu(yu(t)||1,1),r=0;for(var e=n?n.length:0,u=-1,o=Be(vu(e/t));rr&&(r=-r>u?0:u+r),e=e===w||e>u?u:+e||0,0>e&&(e+=u),u=r>e?0:e>>>0,r>>>=0;rt?0:t)):[]},Nn.takeRight=function(n,t,r){var e=n?n.length:0;return e?((r?Ur(n,t,r):null==t)&&(t=1),t=e-(+t||0),Et(n,0>t?0:t)):[]},Nn.takeRightWhile=function(n,t,r){ -return n&&n.length?Nt(n,wr(t,r,3),false,true):[]},Nn.takeWhile=function(n,t,r){return n&&n.length?Nt(n,wr(t,r,3)):[]},Nn.tap=function(n,t,r){return t.call(r,n),n},Nn.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new Ge(L);return false===r?e=false:ge(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),ae(n,t,{leading:e,maxWait:+t,trailing:u})},Nn.thru=ne,Nn.times=function(n,t,r){if(n=yu(n),1>n||!mu(n))return[];var e=-1,u=Be(xu(n,4294967295));for(t=Bt(t,r,1);++ee?u[e]=t(e):t(e); -return u},Nn.toArray=je,Nn.toPlainObject=ke,Nn.transform=function(n,t,r,e){var u=Oo(n)||xe(n);return t=wr(t,e,4),null==r&&(u||ge(n)?(e=n.constructor,r=u?Oo(n)?new e:[]:$u(ve(e)?e.prototype:w)):r={}),(u?Pn:_t)(n,function(n,e,u){return t(r,n,e,u)}),r},Nn.union=Gu,Nn.uniq=Gr,Nn.unzip=Jr,Nn.unzipWith=Xr,Nn.values=Ee,Nn.valuesIn=function(n){return Ft(n,Re(n))},Nn.where=function(n,t){return re(n,bt(t))},Nn.without=Ju,Nn.wrap=function(n,t){return t=null==t?Fe:t,gr(t,R,w,[n],[])},Nn.xor=function(){for(var n=-1,t=arguments.length;++nr?0:+r||0,e),r-=t.length,0<=r&&n.indexOf(t,r)==r},Nn.escape=function(n){return(n=u(n))&&hn.test(n)?n.replace(sn,c):n},Nn.escapeRegExp=function(n){return(n=u(n))&&bn.test(n)?n.replace(wn,l):n||"(?:)"},Nn.every=te,Nn.find=ro,Nn.findIndex=qu,Nn.findKey=$o,Nn.findLast=eo, -Nn.findLastIndex=Pu,Nn.findLastKey=So,Nn.findWhere=function(n,t){return ro(n,bt(t))},Nn.first=Kr,Nn.floor=ni,Nn.get=function(n,t,r){return n=null==n?w:yt(n,Dr(t),t+""),n===w?r:n},Nn.gt=se,Nn.gte=function(n,t){return n>=t},Nn.has=function(n,t){if(null==n)return false;var r=nu.call(n,t);if(!r&&!Wr(t)){if(t=Dr(t),n=1==t.length?n:yt(n,Et(t,0,-1)),null==n)return false;t=Zr(t),r=nu.call(n,t)}return r||Sr(n.length)&&Cr(t,n.length)&&(Oo(n)||pe(n))},Nn.identity=Fe,Nn.includes=ee,Nn.indexOf=Vr,Nn.inRange=function(n,t,r){ -return t=+t||0,r===w?(r=t,t=0):r=+r||0,n>=xu(t,r)&&nr?bu(e+r,0):xu(r||0,e-1))+1;else if(r)return u=Lt(n,t,true)-1,n=n[u],(t===t?t===n:n!==n)?u:-1; -if(t!==t)return p(n,u,true);for(;u--;)if(n[u]===t)return u;return-1},Nn.lt=Ae,Nn.lte=function(n,t){return n<=t},Nn.max=ti,Nn.min=ri,Nn.noConflict=function(){return Zn._=eu,this},Nn.noop=Le,Nn.now=ho,Nn.pad=function(n,t,r){n=u(n),t=+t;var e=n.length;return er?0:+r||0,n.length),n.lastIndexOf(t,r)==r},Nn.sum=function(n,t,r){if(r&&Ur(n,t,r)&&(t=w),t=wr(t,r,3),1==t.length){n=Oo(n)?n:zr(n),r=n.length;for(var e=0;r--;)e+=+t(n[r])||0;n=e}else n=$t(n,t);return n},Nn.template=function(n,t,r){var e=Nn.templateSettings;r&&Ur(n,t,r)&&(t=r=w),n=u(n),t=nt(tt({},r||t),e,Qn),r=nt(tt({},t.imports),e.imports,Qn); -var o,i,f=zo(r),a=Ft(r,f),c=0;r=t.interpolate||Cn;var l="__p+='";r=Ze((t.escape||Cn).source+"|"+r.source+"|"+(r===gn?jn:Cn).source+"|"+(t.evaluate||Cn).source+"|$","g");var p="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";if(n.replace(r,function(t,r,e,u,f,a){return e||(e=u),l+=n.slice(c,a).replace(Un,s),r&&(o=true,l+="'+__e("+r+")+'"),f&&(i=true,l+="';"+f+";\n__p+='"),e&&(l+="'+((__t=("+e+"))==null?'':__t)+'"),c=a+t.length,t}),l+="';",(t=t.variable)||(l="with(obj){"+l+"}"),l=(i?l.replace(fn,""):l).replace(an,"$1").replace(cn,"$1;"), -l="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(o?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}",t=Jo(function(){return qe(f,p+"return "+l).apply(w,a)}),t.source=l,_e(t))throw t;return t},Nn.trim=We,Nn.trimLeft=function(n,t,r){var e=n;return(n=u(n))?n.slice((r?Ur(e,t,r):null==t)?g(n):o(n,t+"")):n},Nn.trimRight=function(n,t,r){var e=n;return(n=u(n))?(r?Ur(e,t,r):null==t)?n.slice(0,y(n)+1):n.slice(0,i(n,t+"")+1):n; -},Nn.trunc=function(n,t,r){r&&Ur(n,t,r)&&(t=w);var e=U;if(r=W,null!=t)if(ge(t)){var o="separator"in t?t.separator:o,e="length"in t?+t.length||0:e;r="omission"in t?u(t.omission):r}else e=+t||0;if(n=u(n),e>=n.length)return n;if(e-=r.length,1>e)return r;if(t=n.slice(0,e),null==o)return t+r;if(we(o)){if(n.slice(e).search(o)){var i,f=n.slice(0,e);for(o.global||(o=Ze(o.source,(kn.exec(o)||"")+"g")),o.lastIndex=0;n=o.exec(f);)i=n.index;t=t.slice(0,null==i?e:i)}}else n.indexOf(o,e)!=e&&(o=t.lastIndexOf(o), --1u.__dir__?"Right":"")}),u},zn.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),Pn(["filter","map","takeWhile"],function(n,t){ -var r=t+1,e=r!=T;zn.prototype[n]=function(n,t){var u=this.clone();return u.__iteratees__.push({iteratee:wr(n,t,1),type:r}),u.__filtered__=u.__filtered__||e,u}}),Pn(["first","last"],function(n,t){var r="take"+(t?"Right":"");zn.prototype[n]=function(){return this[r](1).value()[0]}}),Pn(["initial","rest"],function(n,t){var r="drop"+(t?"":"Right");zn.prototype[n]=function(){return this.__filtered__?new zn(this):this[r](1)}}),Pn(["pluck","where"],function(n,t){var r=t?"filter":"map",e=t?bt:ze;zn.prototype[n]=function(n){ -return this[r](e(n))}}),zn.prototype.compact=function(){return this.filter(Fe)},zn.prototype.reject=function(n,t){return n=wr(n,t,1),this.filter(function(t){return!n(t)})},zn.prototype.slice=function(n,t){n=null==n?0:+n||0;var r=this;return r.__filtered__&&(0t)?new zn(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==w&&(t=+t||0,r=0>t?r.dropRight(-t):r.take(t-n)),r)},zn.prototype.takeRightWhile=function(n,t){return this.reverse().takeWhile(n,t).reverse()},zn.prototype.toArray=function(){return this.take(Ru); -},_t(zn.prototype,function(n,t){var r=/^(?:filter|map|reject)|While$/.test(t),e=/^(?:first|last)$/.test(t),u=Nn[e?"take"+("last"==t?"Right":""):t];u&&(Nn.prototype[t]=function(){function t(n){return e&&i?u(n,1)[0]:u.apply(w,Jn([n],o))}var o=e?[1]:arguments,i=this.__chain__,f=this.__wrapped__,a=!!this.__actions__.length,c=f instanceof zn,l=o[0],s=c||Oo(f);return s&&r&&typeof l=="function"&&1!=l.length&&(c=s=false),l={func:ne,args:[t],thisArg:w},a=c&&!a,e&&!i?a?(f=f.clone(),f.__actions__.push(l),n.call(f)):u.call(w,this.value())[0]:!e&&s?(f=a?f:new zn(this), -f=n.apply(f,o),f.__actions__.push(l),new Ln(f,i)):this.thru(t)})}),Pn("join pop push replace shift sort splice split unshift".split(" "),function(n){var t=(/^(?:replace|split)$/.test(n)?He:Je)[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:join|pop|replace|shift)$/.test(n);Nn.prototype[n]=function(){var n=arguments;return e&&!this.__chain__?t.apply(this.value(),n):this[r](function(r){return t.apply(r,n)})}}),_t(zn.prototype,function(n,t){var r=Nn[t];if(r){var e=r.name+"";(Wu[e]||(Wu[e]=[])).push({ -name:t,func:r})}}),Wu[sr(w,A).name]=[{name:"wrapper",func:w}],zn.prototype.clone=function(){var n=new zn(this.__wrapped__);return n.__actions__=qn(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=qn(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=qn(this.__views__),n},zn.prototype.reverse=function(){if(this.__filtered__){var n=new zn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},zn.prototype.value=function(){ -var n,t=this.__wrapped__.value(),r=this.__dir__,e=Oo(t),u=0>r,o=e?t.length:0;n=o;for(var i=this.__views__,f=0,a=-1,c=i.length;++a"'`]/g,pn=RegExp(ln.source),hn=RegExp(sn.source),_n=/<%-([\s\S]+?)%>/g,vn=/<%([\s\S]+?)%>/g,gn=/<%=([\s\S]+?)%>/g,yn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,dn=/^\w*$/,mn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,wn=/^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,bn=RegExp(wn.source),xn=/[\u0300-\u036f\ufe20-\ufe23]/g,An=/\\(\\)?/g,jn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,kn=/\w*$/,In=/^0[xX]/,Rn=/^\[object .+?Constructor\]$/,On=/^\d+$/,En=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,Cn=/($^)/,Un=/['\n\r\u2028\u2029\\]/g,Wn=RegExp("[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?=[A-Z\\xc0-\\xd6\\xd8-\\xde][a-z\\xdf-\\xf6\\xf8-\\xff]+)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+|[A-Z\\xc0-\\xd6\\xd8-\\xde]+|[0-9]+","g"),$n="Array ArrayBuffer Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Math Number Object RegExp Set String _ clearTimeout isFinite parseFloat parseInt setTimeout TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap".split(" "),Sn={}; -Sn[X]=Sn[H]=Sn[Q]=Sn[nn]=Sn[tn]=Sn[rn]=Sn[en]=Sn[un]=Sn[on]=true,Sn[B]=Sn[D]=Sn[J]=Sn[M]=Sn[q]=Sn[P]=Sn[K]=Sn["[object Map]"]=Sn[V]=Sn[Z]=Sn[Y]=Sn["[object Set]"]=Sn[G]=Sn["[object WeakMap]"]=false;var Fn={};Fn[B]=Fn[D]=Fn[J]=Fn[M]=Fn[q]=Fn[X]=Fn[H]=Fn[Q]=Fn[nn]=Fn[tn]=Fn[V]=Fn[Z]=Fn[Y]=Fn[G]=Fn[rn]=Fn[en]=Fn[un]=Fn[on]=true,Fn[P]=Fn[K]=Fn["[object Map]"]=Fn["[object Set]"]=Fn["[object WeakMap]"]=false;var Nn={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a", -"\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y", -"\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss"},Tn={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},Ln={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},zn={"function":true,object:true},Bn={0:"x30",1:"x31",2:"x32",3:"x33",4:"x34",5:"x35",6:"x36",7:"x37",8:"x38",9:"x39",A:"x41",B:"x42",C:"x43",D:"x44",E:"x45",F:"x46",a:"x61",b:"x62",c:"x63",d:"x64",e:"x65",f:"x66",n:"x6e",r:"x72",t:"x74",u:"x75",v:"x76",x:"x78"},Dn={"\\":"\\", -"'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Mn=zn[typeof exports]&&exports&&!exports.nodeType&&exports,qn=zn[typeof module]&&module&&!module.nodeType&&module,Pn=zn[typeof self]&&self&&self.Object&&self,Kn=zn[typeof window]&&window&&window.Object&&window,Vn=qn&&qn.exports===Mn&&Mn,Zn=Mn&&qn&&typeof global=="object"&&global&&global.Object&&global||Kn!==(this&&this.window)&&Kn||Pn||this,Yn=m();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Zn._=Yn, define(function(){ -return Yn})):Mn&&qn?Vn?(qn.exports=Yn)._=Yn:Mn._=Yn:Zn._=Yn}).call(this); -(function() { - 'use strict'; - - if (self.fetch) { - return - } - - function normalizeName(name) { - if (typeof name !== 'string') { - name = String(name) - } - if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { - throw new TypeError('Invalid character in header field name') - } - return name.toLowerCase() - } - - function normalizeValue(value) { - if (typeof value !== 'string') { - value = String(value) - } - return value - } - - function Headers(headers) { - this.map = {} - - if (headers instanceof Headers) { - headers.forEach(function(value, name) { - this.append(name, value) - }, this) - - } else if (headers) { - Object.getOwnPropertyNames(headers).forEach(function(name) { - this.append(name, headers[name]) - }, this) - } - } - - Headers.prototype.append = function(name, value) { - name = normalizeName(name) - value = normalizeValue(value) - var list = this.map[name] - if (!list) { - list = [] - this.map[name] = list - } - list.push(value) - } - - Headers.prototype['delete'] = function(name) { - delete this.map[normalizeName(name)] - } - - Headers.prototype.get = function(name) { - var values = this.map[normalizeName(name)] - return values ? values[0] : null - } - - Headers.prototype.getAll = function(name) { - return this.map[normalizeName(name)] || [] - } - - Headers.prototype.has = function(name) { - return this.map.hasOwnProperty(normalizeName(name)) - } - - Headers.prototype.set = function(name, value) { - this.map[normalizeName(name)] = [normalizeValue(value)] - } - - Headers.prototype.forEach = function(callback, thisArg) { - Object.getOwnPropertyNames(this.map).forEach(function(name) { - this.map[name].forEach(function(value) { - callback.call(thisArg, value, name, this) - }, this) - }, this) - } - - function consumed(body) { - if (body.bodyUsed) { - return Promise.reject(new TypeError('Already read')) - } - body.bodyUsed = true - } - - function fileReaderReady(reader) { - return new Promise(function(resolve, reject) { - reader.onload = function() { - resolve(reader.result) - } - reader.onerror = function() { - reject(reader.error) - } - }) - } - - function readBlobAsArrayBuffer(blob) { - var reader = new FileReader() - reader.readAsArrayBuffer(blob) - return fileReaderReady(reader) - } - - function readBlobAsText(blob) { - var reader = new FileReader() - reader.readAsText(blob) - return fileReaderReady(reader) - } - - var support = { - blob: 'FileReader' in self && 'Blob' in self && (function() { - try { - new Blob(); - return true - } catch(e) { - return false - } - })(), - formData: 'FormData' in self - } - - function Body() { - this.bodyUsed = false - - - this._initBody = function(body) { - this._bodyInit = body - if (typeof body === 'string') { - this._bodyText = body - } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body - } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body - } else if (!body) { - this._bodyText = '' - } else { - throw new Error('unsupported BodyInit type') - } - } - - if (support.blob) { - this.blob = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as blob') - } else { - return Promise.resolve(new Blob([this._bodyText])) - } - } - - this.arrayBuffer = function() { - return this.blob().then(readBlobAsArrayBuffer) - } - - this.text = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return readBlobAsText(this._bodyBlob) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as text') - } else { - return Promise.resolve(this._bodyText) - } - } - } else { - this.text = function() { - var rejected = consumed(this) - return rejected ? rejected : Promise.resolve(this._bodyText) - } - } - - if (support.formData) { - this.formData = function() { - return this.text().then(decode) - } - } - - this.json = function() { - return this.text().then(JSON.parse) - } - - return this - } - - // HTTP methods whose capitalization should be normalized - var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] - - function normalizeMethod(method) { - var upcased = method.toUpperCase() - return (methods.indexOf(upcased) > -1) ? upcased : method - } - - function Request(url, options) { - options = options || {} - this.url = url - - this.credentials = options.credentials || 'omit' - this.headers = new Headers(options.headers) - this.method = normalizeMethod(options.method || 'GET') - this.mode = options.mode || null - this.referrer = null - - if ((this.method === 'GET' || this.method === 'HEAD') && options.body) { - throw new TypeError('Body not allowed for GET or HEAD requests') - } - this._initBody(options.body) - } - - function decode(body) { - var form = new FormData() - body.trim().split('&').forEach(function(bytes) { - if (bytes) { - var split = bytes.split('=') - var name = split.shift().replace(/\+/g, ' ') - var value = split.join('=').replace(/\+/g, ' ') - form.append(decodeURIComponent(name), decodeURIComponent(value)) - } - }) - return form - } - - function headers(xhr) { - var head = new Headers() - var pairs = xhr.getAllResponseHeaders().trim().split('\n') - pairs.forEach(function(header) { - var split = header.trim().split(':') - var key = split.shift().trim() - var value = split.join(':').trim() - head.append(key, value) - }) - return head - } - - Body.call(Request.prototype) - - function Response(bodyInit, options) { - if (!options) { - options = {} - } - - this._initBody(bodyInit) - this.type = 'default' - this.url = null - this.status = options.status - this.ok = this.status >= 200 && this.status < 300 - this.statusText = options.statusText - this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) - this.url = options.url || '' - } - - Body.call(Response.prototype) - - self.Headers = Headers; - self.Request = Request; - self.Response = Response; - - self.fetch = function(input, init) { - // TODO: Request constructor should accept input, init - var request - if (Request.prototype.isPrototypeOf(input) && !init) { - request = input - } else { - request = new Request(input, init) - } - - return new Promise(function(resolve, reject) { - var xhr = new XMLHttpRequest() - - function responseURL() { - if ('responseURL' in xhr) { - return xhr.responseURL - } - - // Avoid security warnings on getResponseHeader when not allowed by CORS - if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { - return xhr.getResponseHeader('X-Request-URL') - } - - return; - } - - xhr.onload = function() { - var status = (xhr.status === 1223) ? 204 : xhr.status - if (status < 100 || status > 599) { - reject(new TypeError('Network request failed')) - return - } - var options = { - status: status, - statusText: xhr.statusText, - headers: headers(xhr), - url: responseURL() - } - var body = 'response' in xhr ? xhr.response : xhr.responseText; - resolve(new Response(body, options)) - } - - xhr.onerror = function() { - reject(new TypeError('Network request failed')) - } - - xhr.open(request.method, request.url, true) - - if (request.credentials === 'include') { - xhr.withCredentials = true - } - - if ('responseType' in xhr && support.blob) { - xhr.responseType = 'blob' - } - - request.headers.forEach(function(value, name) { - xhr.setRequestHeader(name, value) - }) - - xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) - }) - } - self.fetch.polyfill = true -})(); - - - - - - - - - - - - - - - - - - -function ready(fn) { - if (document.readyState != 'loading'){ - fn(); - } else { - document.addEventListener('DOMContentLoaded', fn); - } -} - -var getValues = function(){ - var name = document.querySelector('input[name=lion-name]').value; - var pride = document.querySelector('input[name=lion-pride]').value; - var age = document.querySelector('input[type=number').value; - var gender = document.querySelector('select'); - gender = gender.options[gender.selectedIndex].value; - - - document.querySelector('input[name=lion-name]').value = ''; - document.querySelector('input[name=lion-pride]').value = ''; - document.querySelector('input[type=number').value = ''; - - - return { - name: name, - pride: pride, - age: age, - gender: gender - }; -}; - - -var lionTemplate = '

    <%= name %>'+ - '

    <%= pride %>

    '+ - 'age: <%= age %> '+ - '<%= gender %>'; - -var lions = []; - -var makeTemplate = function(data) { - var li = document.createElement('li'); - var lionList = document.querySelector('.lion-list'); - var compiled = _.template(lionTemplate); - var lionHtml = compiled(data); - li.innerHTML = lionHtml; - lionList.insertBefore(li, lionList.firstChild); -} - -var updateLionList = function(){ - var lionData = lions[lions.length-1]; - makeTemplate(lionData); -} - -var makeLionList = function() { - lions.forEach(function(lion) { - makeTemplate(lion); - }); -}; - -var getAllLions = function() { - fetch('/lions') - .then(function(resp) { - console.log(resp); - return resp.json(); - }) - .then(function(data) { - lions = lions.concat(data); - makeLionList(); - }); -} - -ready(function() { - getAllLions(); - var form = document.querySelector('form'); - - form.addEventListener('submit', function(e) { - e.preventDefault(); - - var values = getValues(); - console.log(values); - fetch('/lions', { - method: 'post', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - - body: JSON.stringify(values) - }) - .then(function(resp) { - return resp.json(); - }) - .then(function(createdLion){ - lions.push(createdLion); - updateLionList(); - }) - return false; - }) -}); diff --git a/client/index.html b/client/index.html deleted file mode 100644 index e9987a7..0000000 --- a/client/index.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - Lions - - - - -
    -

    Lions

    - -
    -
    - - - - -
    - -
    - - - - -
    - - - - - -
    - -
    -
      -
      -
      - - - diff --git a/client/style.css b/client/style.css deleted file mode 100644 index 08c888e..0000000 --- a/client/style.css +++ /dev/null @@ -1,542 +0,0 @@ -/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ - -/** - * 1. Set default font family to sans-serif. - * 2. Prevent iOS and IE text size adjust after device orientation change, - * without disabling user zoom. - */ - -html { - font-family: sans-serif; /* 1 */ - -ms-text-size-adjust: 100%; /* 2 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/** - * Remove default margin. - */ - -body { - margin: 0; -} - -/* HTML5 display definitions - ========================================================================== */ - -/** - * Correct `block` display not defined for any HTML5 element in IE 8/9. - * Correct `block` display not defined for `details` or `summary` in IE 10/11 - * and Firefox. - * Correct `block` display not defined for `main` in IE 11. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} - -/** - * 1. Correct `inline-block` display not defined in IE 8/9. - * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. - */ - -audio, -canvas, -progress, -video { - display: inline-block; /* 1 */ - vertical-align: baseline; /* 2 */ -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address `[hidden]` styling not present in IE 8/9/10. - * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. - */ - -[hidden], -template { - display: none; -} - -/* Links - ========================================================================== */ - -/** - * Remove the gray background color from active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * Improve readability of focused elements when they are also in an - * active/hover state. - */ - -a:active, -a:hover { - outline: 0; -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Address styling not present in IE 8/9/10/11, Safari, and Chrome. - */ - -abbr[title] { - border-bottom: 1px dotted; -} - -/** - * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. - */ - -b, -strong { - font-weight: bold; -} - -/** - * Address styling not present in Safari and Chrome. - */ - -dfn { - font-style: italic; -} - -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari, and Chrome. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/** - * Address styling not present in IE 8/9. - */ - -mark { - background: #ff0; - color: #000; -} - -/** - * Address inconsistent and variable font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove border when inside `a` element in IE 8/9/10. - */ - -img { - border: 0; -} - -/** - * Correct overflow not hidden in IE 9/10/11. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* Grouping content - ========================================================================== */ - -/** - * Address margin not present in IE 8/9 and Safari. - */ - -figure { - margin: 1em 40px; -} - -/** - * Address differences between Firefox and other browsers. - */ - -hr { - box-sizing: content-box; - height: 0; -} - -/** - * Contain overflow in all browsers. - */ - -pre { - overflow: auto; -} - -/** - * Address odd `em`-unit font size rendering in all browsers. - */ - -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} - -/* Forms - ========================================================================== */ - -/** - * Known limitation: by default, Chrome and Safari on OS X allow very limited - * styling of `select`, unless a `border` property is set. - */ - -/** - * 1. Correct color not being inherited. - * Known issue: affects color of disabled elements. - * 2. Correct font properties not being inherited. - * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. - */ - -button, -input, -optgroup, -select, -textarea { - color: inherit; /* 1 */ - font: inherit; /* 2 */ - margin: 0; /* 3 */ -} - -/** - * Address `overflow` set to `hidden` in IE 8/9/10/11. - */ - -button { - overflow: visible; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. - * Correct `select` style inheritance in Firefox. - */ - -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ -} - -/** - * Re-set default cursor for disabled elements. - */ - -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * Remove inner padding and border in Firefox 4+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -input { - line-height: normal; -} - -/** - * It's recommended that you don't attempt to style these elements. - * Firefox's implementation doesn't respect box-sizing, padding, or width. - * - * 1. Address box sizing set to `content-box` in IE 8/9/10. - * 2. Remove excess padding in IE 8/9/10. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Fix the cursor style for Chrome's increment/decrement buttons. For certain - * `font-size` values of the `input`, it causes the cursor style of the - * decrement button to change from `default` to `text`. - */ - -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. - */ - -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ - box-sizing: content-box; /* 2 */ -} - -/** - * Remove inner padding and search cancel button in Safari and Chrome on OS X. - * Safari (but not Chrome) clips the cancel button when the search input has - * padding (and `textfield` appearance). - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Define consistent border, margin, and padding. - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct `color` not being inherited in IE 8/9/10/11. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ - -legend { - border: 0; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Remove default vertical scrollbar in IE 8/9/10/11. - */ - -textarea { - overflow: auto; -} - -/** - * Don't inherit the `font-weight` (applied by a rule above). - * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. - */ - -optgroup { - font-weight: bold; -} - -/* Tables - ========================================================================== */ - -/** - * Remove most spacing between table cells. - */ - -table { - border-collapse: collapse; - border-spacing: 0; -} - -td, -th { - padding: 0; -} - - - -/**************************************************************/ -body, html { - height: 100%; -} - -main { - width: 80vw; - margin: 0 auto; -} -.textbox { - max-width: 200px; - margin-bottom: 40px; - position: relative; - width: 100%; -} -.textbox input { - font-size: 18px; - padding: 10px 10px 10px 5px; - display: block; - width: 100%; - background: transparent; - border: none; - border-bottom: 1px solid #bf0000; -} -.textbox input:focus { - outline: none; -} -.textbox input:focus ~ .highlight { - animation: inputHighlight 0.3s ease; -} -.textbox input:focus ~ .bar:before, -.textbox input:focus ~ .bar:after { - width: 50%; -} -.textbox input:focus ~ label, -.textbox input:valid ~ label { - top: -20px; - font-size: 14px; - color: #ff1a1a; -} -.textbox label { - color: #4d0000; - font-size: 18px; - font-weight: normal; - position: absolute; - pointer-events: none; - left: 5px; - top: 10px; - transition: all 0.2s ease; -} -.textbox .bar { - position: relative; - display: block; -} -.textbox .bar:before, -.textbox .bar:after { - content: ''; - height: 2px; - width: 0; - bottom: 1px; - position: absolute; - background: #f00; - transition: ease all 0.2s; -} -.textbox .bar:before { - left: 50%; -} -.textbox .bar:after { - right: 50%; -} -.textbox .highlight { - position: absolute; - height: 60%; - width: 100px; - top: 25%; - left: 0; - pointer-events: none; - opacity: 0.5; -} -@-moz-keyframes inputHighlight { - from { - background: #f00; - } - to { - background: transparent; - width: 0; - } -} -@-webkit-keyframes inputHighlight { - from { - background: #f00; - } - to { - background: transparent; - width: 0; - } -} -@-o-keyframes inputHighlight { - from { - background: #f00; - } - to { - background: transparent; - width: 0; - } -} -@keyframes inputHighlight { - from { - background: #f00; - } - to { - background: transparent; - width: 0; - } -} diff --git a/index.js b/index.js index 68890cc..d164a97 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,15 @@ +// intro point for our server. +// PRO-TIP: if you have an index.js file +// on the root of a folder in node +// you can just require that folder and node will +// automatically require the index.js on the root + +// setup config first before anything by requiring it +var config = require('./server/config/config'); var app = require('./server/server'); +var logger = require('./server/util/logger'); + +app.listen(config.port); +logger.log('listening on http://localhost:' + config.port); + -app.listen(3000); -console.log('on port 3000'); diff --git a/package.json b/package.json index 26c6562..c91f153 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,14 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start": "nodemon index.js", + "test": "" }, "author": "", "license": "ISC", "dependencies": { "body-parser": "^1.13.2", + "colors": "^1.1.2", "express": "^4.13.1", "lodash": "^3.10.0", "morgan": "^1.6.1" diff --git a/server/api/api.js b/server/api/api.js new file mode 100644 index 0000000..d438c24 --- /dev/null +++ b/server/api/api.js @@ -0,0 +1,9 @@ +var router = require('express').Router(); + +// api router will mount other routers +// for all our resources +router.use('/users', require('./user/userRoutes')); +router.use('/categories', require('./category/categoryRoutes')); +router.use('/posts', require('./post/postRoutes')); + +module.exports = router; diff --git a/server/api/category/categoryRoutes.js b/server/api/category/categoryRoutes.js new file mode 100644 index 0000000..865443a --- /dev/null +++ b/server/api/category/categoryRoutes.js @@ -0,0 +1,12 @@ +var router = require('express').Router(); +var logger = require('../../util/logger'); + +// setup boilerplate route jsut to satisfy a request +// for building +router.route('/') + .get(function(req, res){ + logger.log('Hey from user!!'); + res.send({ok: true}); + }); + +module.exports = router; diff --git a/server/api/post/postRoutes.js b/server/api/post/postRoutes.js new file mode 100644 index 0000000..3671e47 --- /dev/null +++ b/server/api/post/postRoutes.js @@ -0,0 +1,12 @@ +var router = require('express').Router(); +var logger = require('../../util/logger'); + +// setup boilerplate route jsut to satisfy a request +// for building +router.route('/') + .get(function(req, res){ + logger.log('Hey from post!!'); + res.send({ok: true}); + }); + +module.exports = router; diff --git a/server/api/user/userRoutes.js b/server/api/user/userRoutes.js new file mode 100644 index 0000000..865443a --- /dev/null +++ b/server/api/user/userRoutes.js @@ -0,0 +1,12 @@ +var router = require('express').Router(); +var logger = require('../../util/logger'); + +// setup boilerplate route jsut to satisfy a request +// for building +router.route('/') + .get(function(req, res){ + logger.log('Hey from user!!'); + res.send({ok: true}); + }); + +module.exports = router; diff --git a/server/config/config.js b/server/config/config.js new file mode 100644 index 0000000..2a1adf9 --- /dev/null +++ b/server/config/config.js @@ -0,0 +1,29 @@ +var _ = require('lodash'); + +var config = { + dev: 'development', + test: 'testing', + prod: 'production', + port: process.env.PORT || 3000 +}; + +process.env.NODE_ENV = process.env.NODE_ENV || config.dev; +config.env = process.env.NODE_ENV; + +var envConfig; +// require could error out if +// the file don't exist so lets try this statement +// and fallback to an empty object if it does error out +try { + envConfig = require('./' + config.env); + // just making sure the require actually + // got something back :) + envConfig = envConfig || {}; +} catch(e) { + envConfig = {}; +} + +// merge the two config files together +// the envConfig file will overwrite properties +// on the config object +module.exports = _.merge(config, envConfig); diff --git a/server/config/development.js b/server/config/development.js new file mode 100644 index 0000000..b882c8d --- /dev/null +++ b/server/config/development.js @@ -0,0 +1,4 @@ +module.exports = { + // enabled logging for development + logging: true +}; diff --git a/server/config/production.js b/server/config/production.js new file mode 100644 index 0000000..eab9e02 --- /dev/null +++ b/server/config/production.js @@ -0,0 +1,4 @@ +module.exports = { + // disbable logging for production + logging: false +}; diff --git a/server/config/testing.js b/server/config/testing.js new file mode 100644 index 0000000..72b4323 --- /dev/null +++ b/server/config/testing.js @@ -0,0 +1,4 @@ +module.exports = { + // disbable logging for testing + logging: false +}; diff --git a/server/lions.js b/server/lions.js deleted file mode 100644 index 15c4785..0000000 --- a/server/lions.js +++ /dev/null @@ -1,65 +0,0 @@ -var _ = require('lodash'); -var lionRouter = require('express').Router(); - -var lions = []; -var id = 0; - -var updateId = function(req, res, next) { - if (!req.body.id) { - id++; - req.body.id = id + ''; - } - next(); -}; - -lionRouter.param('id', function(req, res, next, id) { - var lion = _.find(lions, {id: id}) - - if (lion) { - req.lion = lion; - next(); - } else { - res.send(); - } -}); - -lionRouter.get('/', function(req, res){ - res.json(lions); -}); - -lionRouter.get('/:id', function(req, res){ - var lion = req.lion; - res.json(lion || {}); -}); - -lionRouter.post('/', updateId, function(req, res) { - var lion = req.body; - - lions.push(lion); - - res.json(lion); -}); - -lionRouter.delete('/:id', function(req, res) { - var lion = _.findIndex(lions, {id: req.params.id}); - lions.splice(lion, 1); - - res.json(req.lion); -}); - -lionRouter.put('/:id', function(req, res) { - var update = req.body; - if (update.id) { - delete update.id - } - - var lion = _.findIndex(lions, {id: req.params.id}); - if (!lions[lion]) { - res.send(); - } else { - var updatedLion = _.assign(lions[lion], update); - res.json(updatedLion); - } -}); - -module.exports = lionRouter; diff --git a/server/middleware/appMiddlware.js b/server/middleware/appMiddlware.js new file mode 100644 index 0000000..380a5d6 --- /dev/null +++ b/server/middleware/appMiddlware.js @@ -0,0 +1,9 @@ +var morgan = require('morgan'); +var bodyParser = require('body-parser'); +// setup global middleware here + +module.exports = function(app) { + app.use(morgan('dev')); + app.use(bodyParser.urlencoded({ extended: true })); + app.use(bodyParser.json()); +}; diff --git a/server/server.js b/server/server.js index fb1bfcb..2925a3f 100644 --- a/server/server.js +++ b/server/server.js @@ -1,27 +1,13 @@ var express = require('express'); -var bodyParser = require('body-parser'); var app = express(); -var _ = require('lodash'); -var morgan = require('morgan'); - -var lionRouter = require('./lions'); -var tigerRouter = require('./tigers'); - -app.use(morgan('dev')) -app.use(express.static('client')); -app.use(bodyParser.urlencoded({extended: true})); -app.use(bodyParser.json()); -// this is called mounting. when ever a req comes in for -// '/lion' we want to use this router -app.use('/lions', lionRouter); -app.use('/tigers', tigerRouter); - -app.use(function(err, req, res, next) { - if (err) { - res.status(500).send(err); - } -}); +var api = require('./api/api'); +// setup the app middlware +require('./middleware/appMiddlware')(app); +// setup the api +app.use('/api/', api); +// set up global error handling +// export the app for testing module.exports = app; diff --git a/server/spec.js b/server/spec.js deleted file mode 100644 index 7a066e9..0000000 --- a/server/spec.js +++ /dev/null @@ -1,83 +0,0 @@ -var app = require('./server'); -var request = require('supertest'); -var expect = require('chai').expect; - -describe('[LIONS]', function(){ - - it('should get all lions', function(done) { - request(app) - .get('/lions') - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200) - .end(function(err, resp) { - expect(resp.body).to.be.an('array'); - done(); - }) - }); - - it('should create a lion', function(done) { - request(app) - .post('/lions') - .send({ - name: 'Mufasa', - age: 100, - pride: 'Evil lions', - gender:'male' - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(201) - .end(function(err, resp) { - expect(resp.body).to.be.an('object'); - done(); - }) - }); - - it('should delete a lion', function(done) { - request(app) - .post('/lions') - .send({ - name: 'test lion', - age: 100, - pride: 'test lion', - gender:'female' - }) - .set('Accept', 'application/json') - .end(function(err, resp) { - var lion = resp.body; - request(app) - .delete('/lions/' + lion.id) - .end(function(err, resp) { - expect(resp.body).to.eql(lion); - done(); - }); - }) - }); - - it('should update a lion', function(done) { - request(app) - .post('/lions') - .send({ - name: 'test lion', - age: 100, - pride: 'test lion', - gender:'female' - }) - .set('Accept', 'application/json') - .end(function(err, resp) { - var lion = resp.body; - request(app) - .put('/lions/' + lion.id) - .send({ - name: 'new name' - }) - .end(function(err, resp) { - expect(resp.body.name).to.equal('new name'); - done(); - }); - }) - }); -}); - - diff --git a/server/tigers.js b/server/tigers.js deleted file mode 100644 index 08829b3..0000000 --- a/server/tigers.js +++ /dev/null @@ -1,58 +0,0 @@ -var tigerRouter = require('express').Router(); - -var tigers = []; -var id = 0; - -var updateId = function(req, res, next) { - if (!req.body.id) { - id++; - req.body.id = id + ''; - } - next(); -}; - -tigerRouter.param('id', function(req, res, next, id) { - var tiger = _.find(tigers, {id: id}); - - if (tiger) { - req.tiger = tiger; - next(); - } else { - res.send(); - } -}); - -tigerRouter.get('/', function(req, res){ - res.json(tigers); -}); - -tigerRouter.get('/:id', function(req, res){ - var tiger = req.tiger; - res.json(tiger || {}); -}); - -tigerRouter.post('/', updateId, function(req, res) { - var tiger = req.body; - - tigers.push(tiger); - - res.json(tiger); -}); - - -tigerRouter.put('/:id', function(req, res) { - var update = req.body; - if (update.id) { - delete update.id - } - - var tiger = _.findIndex(tigers, {id: req.params.id}); - if (!tigers[tiger]) { - res.send(); - } else { - var updatedtiger = _.assign(tigers[tiger], update); - res.json(updatedtiger); - } -}); - -module.exports = tigerRouter; diff --git a/server/util/logger.js b/server/util/logger.js new file mode 100644 index 0000000..3a311df --- /dev/null +++ b/server/util/logger.js @@ -0,0 +1,40 @@ +// no var needed here, colors will attached colors +// to String.prototype +require('colors'); +var _ = require('lodash'); + +var config = require('../config/config'); + +// create a noop (no operation) function for when loggin is disabled +var noop = function(){}; +// check if loggin is enabled in the config +// if it is, then use console.log +// if not then noop +var consoleLog = config.logging ? console.log.bind(console) : noop; + +var logger = { + log: function() { + // arguments is an array like object with all the passed + // in arguments to this function + var args = _.toArray(arguments) + .map(function(arg) { + if(typeof arg === 'object') { + // turn the object to a string so we + // can log all the properties and color it + var string = JSON.stringify(arg); + return string.magenta; + } else { + // coerce to string to color + arg+=''; + return arg.magenta; + } + }); + + // call either console.log or noop here + // with the console object as the context + // and the new colored args :) + consoleLog.apply(console, args); + } +}; + +module.exports = logger; From 9449deac036554efdf9d8e072933a9b927a381bc Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 20:53:40 -0500 Subject: [PATCH 12/27] chore --- server/util/logger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/util/logger.js b/server/util/logger.js index 3a311df..a741055 100644 --- a/server/util/logger.js +++ b/server/util/logger.js @@ -21,7 +21,7 @@ var logger = { if(typeof arg === 'object') { // turn the object to a string so we // can log all the properties and color it - var string = JSON.stringify(arg); + var string = JSON.stringify(arg, 2); return string.magenta; } else { // coerce to string to color From ef7f2ec310f128f73047d378c526a903c37da5ce Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 21:49:33 -0500 Subject: [PATCH 13/27] chore --- package.json | 1 + server/dbTester.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 server/dbTester.js diff --git a/package.json b/package.json index c91f153..4f31f28 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "colors": "^1.1.2", "express": "^4.13.1", "lodash": "^3.10.0", + "mongoose": "^4.0.8", "morgan": "^1.6.1" }, "devDependencies": { diff --git a/server/dbTester.js b/server/dbTester.js new file mode 100644 index 0000000..589951c --- /dev/null +++ b/server/dbTester.js @@ -0,0 +1,21 @@ +var mongoose = require('mongoose'); +mongoose.connect('mongodb://127.0.0.1/sip'); +// connect to a database so the below will work. +// what's happening is that we're querying yor mongo db +// on the todos collection and +// logging the results. The todos collection +// will be created for you so no need to make that. +// So in the mongo shell in the terminal, +// or in here if you're feeling ambitions, +// you need to add some documents +// to the todos collection. +// run node server/db.Tester to execute this file +// MAKE SURE MONGOD is RUNNING + +var TodoSchema = new mongoose.Schema({}); + +var Todo = mongoose.model('todo', TodoSchema); + +Todo.find({}, function(err, todos) { + console.log(err, todos); +}); From c63842f3b4cd30de6eeda186b67d14103e7085f6 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 21:51:12 -0500 Subject: [PATCH 14/27] chore --- package.json | 1 - server/dbTester.js | 21 --------------------- 2 files changed, 22 deletions(-) delete mode 100644 server/dbTester.js diff --git a/package.json b/package.json index 4f31f28..c91f153 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "colors": "^1.1.2", "express": "^4.13.1", "lodash": "^3.10.0", - "mongoose": "^4.0.8", "morgan": "^1.6.1" }, "devDependencies": { diff --git a/server/dbTester.js b/server/dbTester.js deleted file mode 100644 index 589951c..0000000 --- a/server/dbTester.js +++ /dev/null @@ -1,21 +0,0 @@ -var mongoose = require('mongoose'); -mongoose.connect('mongodb://127.0.0.1/sip'); -// connect to a database so the below will work. -// what's happening is that we're querying yor mongo db -// on the todos collection and -// logging the results. The todos collection -// will be created for you so no need to make that. -// So in the mongo shell in the terminal, -// or in here if you're feeling ambitions, -// you need to add some documents -// to the todos collection. -// run node server/db.Tester to execute this file -// MAKE SURE MONGOD is RUNNING - -var TodoSchema = new mongoose.Schema({}); - -var Todo = mongoose.model('todo', TodoSchema); - -Todo.find({}, function(err, todos) { - console.log(err, todos); -}); From 01a993d5000439a0c2b3f8c12ab5f381c9aec0d5 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 21:52:17 -0500 Subject: [PATCH 15/27] chore --- dbTester.js | 21 +++++++++++++++++++++ package.json | 1 + 2 files changed, 22 insertions(+) create mode 100644 dbTester.js diff --git a/dbTester.js b/dbTester.js new file mode 100644 index 0000000..589951c --- /dev/null +++ b/dbTester.js @@ -0,0 +1,21 @@ +var mongoose = require('mongoose'); +mongoose.connect('mongodb://127.0.0.1/sip'); +// connect to a database so the below will work. +// what's happening is that we're querying yor mongo db +// on the todos collection and +// logging the results. The todos collection +// will be created for you so no need to make that. +// So in the mongo shell in the terminal, +// or in here if you're feeling ambitions, +// you need to add some documents +// to the todos collection. +// run node server/db.Tester to execute this file +// MAKE SURE MONGOD is RUNNING + +var TodoSchema = new mongoose.Schema({}); + +var Todo = mongoose.model('todo', TodoSchema); + +Todo.find({}, function(err, todos) { + console.log(err, todos); +}); diff --git a/package.json b/package.json index c91f153..4f31f28 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "colors": "^1.1.2", "express": "^4.13.1", "lodash": "^3.10.0", + "mongoose": "^4.0.8", "morgan": "^1.6.1" }, "devDependencies": { From b179f09bc0a202e12ed7073a44b557f33708973a Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 21:57:28 -0500 Subject: [PATCH 16/27] chore --- dbTester.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbTester.js b/dbTester.js index 589951c..37f66e2 100644 --- a/dbTester.js +++ b/dbTester.js @@ -1,5 +1,5 @@ var mongoose = require('mongoose'); -mongoose.connect('mongodb://127.0.0.1/sip'); + // connect to a database so the below will work. // what's happening is that we're querying yor mongo db // on the todos collection and From 8f9dced04f437c50b2464137f1c3ca78feabe5cf Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 22:09:01 -0500 Subject: [PATCH 17/27] chore --- dbTester.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/dbTester.js b/dbTester.js index 37f66e2..88e5930 100644 --- a/dbTester.js +++ b/dbTester.js @@ -1,21 +1,24 @@ var mongoose = require('mongoose'); // connect to a database so the below will work. -// what's happening is that we're querying yor mongo db -// on the todos collection and -// logging the results. The todos collection -// will be created for you so no need to make that. +// what's happening is that we're createing a new schema +// and making a todos collection and a Todo model. +// We then create new Todo. // So in the mongo shell in the terminal, -// or in here if you're feeling ambitions, -// you need to add some documents -// to the todos collection. +// connect to your database and uery it there +// and see if you see the todo below log in the terminal // run node server/db.Tester to execute this file // MAKE SURE MONGOD is RUNNING -var TodoSchema = new mongoose.Schema({}); +var TodoSchema = new mongoose.Schema({ + name: String, + completed: Boolean +}); var Todo = mongoose.model('todo', TodoSchema); - -Todo.find({}, function(err, todos) { - console.log(err, todos); +Todo.create({ + name: 'clean up your room!!!', + completed: false +}).then(function(err, todo) { + console.log(err, todo); }); From 55fe70142b0ab815a00bd6d84b2593429b839a05 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Wed, 22 Jul 2015 22:33:39 -0500 Subject: [PATCH 18/27] chore --- dbTester.js | 24 ------------------------ server/api/category/categoryModel.js | 12 ++++++++++++ server/api/post/postModel.js | 21 +++++++++++++++++++++ server/api/user/userModel.js | 12 ++++++++++++ server/config/development.js | 5 ++++- server/config/testing.js | 5 ++++- server/server.js | 3 +++ 7 files changed, 56 insertions(+), 26 deletions(-) delete mode 100644 dbTester.js create mode 100644 server/api/category/categoryModel.js create mode 100644 server/api/post/postModel.js create mode 100644 server/api/user/userModel.js diff --git a/dbTester.js b/dbTester.js deleted file mode 100644 index 88e5930..0000000 --- a/dbTester.js +++ /dev/null @@ -1,24 +0,0 @@ -var mongoose = require('mongoose'); - -// connect to a database so the below will work. -// what's happening is that we're createing a new schema -// and making a todos collection and a Todo model. -// We then create new Todo. -// So in the mongo shell in the terminal, -// connect to your database and uery it there -// and see if you see the todo below log in the terminal -// run node server/db.Tester to execute this file -// MAKE SURE MONGOD is RUNNING - -var TodoSchema = new mongoose.Schema({ - name: String, - completed: Boolean -}); - -var Todo = mongoose.model('todo', TodoSchema); -Todo.create({ - name: 'clean up your room!!!', - completed: false -}).then(function(err, todo) { - console.log(err, todo); -}); diff --git a/server/api/category/categoryModel.js b/server/api/category/categoryModel.js new file mode 100644 index 0000000..f974543 --- /dev/null +++ b/server/api/category/categoryModel.js @@ -0,0 +1,12 @@ +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; + +var CategorySchema = new Schema({ + name: { + type: String, + required: true, + unique: true + } +}); + +module.exports = mongoose.model('category', CategorySchema); diff --git a/server/api/post/postModel.js b/server/api/post/postModel.js new file mode 100644 index 0000000..196a0f8 --- /dev/null +++ b/server/api/post/postModel.js @@ -0,0 +1,21 @@ +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; + +var PostSchema = new Schema({ + title: { + type: String, + required: true, + unique: true + }, + + text: { + type: String, + required: true + }, + // array of ids from the users + author: [{type: Schema.Types.ObjectId, ref: 'user'}], + + categories: [type: Schema.Types.ObjectId, ref: 'category'] +}); + +module.exports = mongoose.model('post', PostSchema); diff --git a/server/api/user/userModel.js b/server/api/user/userModel.js new file mode 100644 index 0000000..a46b880 --- /dev/null +++ b/server/api/user/userModel.js @@ -0,0 +1,12 @@ +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; + +var UserSchema = new Schema({ + username: { + type: String, + unique: true, + required: true + } +}); + +module.exports = mongoose.model('user', UserSchema); diff --git a/server/config/development.js b/server/config/development.js index b882c8d..1495217 100644 --- a/server/config/development.js +++ b/server/config/development.js @@ -1,4 +1,7 @@ module.exports = { // enabled logging for development - logging: true + logging: true, + db: { + url: 'mongodb://localhost/nodeblog' + } }; diff --git a/server/config/testing.js b/server/config/testing.js index 72b4323..d9ff459 100644 --- a/server/config/testing.js +++ b/server/config/testing.js @@ -1,4 +1,7 @@ module.exports = { // disbable logging for testing - logging: false + logging: false, + db: { + url: 'mongodb://localhost/nodeblog-test' + } }; diff --git a/server/server.js b/server/server.js index 2925a3f..8eda1f7 100644 --- a/server/server.js +++ b/server/server.js @@ -1,6 +1,9 @@ var express = require('express'); var app = express(); var api = require('./api/api'); +var config = require('./config/config'); +// db.url is different depending on NODE_ENV +require('mongoose').connect(config.db.url); // setup the app middlware require('./middleware/appMiddlware')(app); From 9375e0e3a613aaa4944da39059a26f938091dd8a Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Thu, 23 Jul 2015 06:51:11 -0500 Subject: [PATCH 19/27] chore --- server/api/category/categoryController.js | 67 ++++++++++++++++++++++ server/api/post/postController.js | 69 +++++++++++++++++++++++ server/api/user/userController.js | 67 ++++++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 server/api/category/categoryController.js create mode 100644 server/api/post/postController.js create mode 100644 server/api/user/userController.js diff --git a/server/api/category/categoryController.js b/server/api/category/categoryController.js new file mode 100644 index 0000000..fe6fcbb --- /dev/null +++ b/server/api/category/categoryController.js @@ -0,0 +1,67 @@ +var Category = require('./categoryModel'); +var _ = require('lodash'); + +exports.params = function(req, res, next, id) { + Category.findById(id) + .then(function(category) { + if (!category) { + next(new Error('No category with that id')); + } else { + req.category = category; + next(); + } + }, function(err) { + next(err); + }); +}; + +exports.get = function(req, res, next) { + Category.find({}) + .then(function(categories){ + res.json(categories); + }, function(err){ + next(err); + }); +}; + +exports.getOne = function(req, res, next) { + var category = req.category; + res.json(category); +}; + +exports.put = function(req, res, next) { + var category = req.category; + + var update = req.body; + + _.merge(category, update); + + category.save(function(err, saved) { + if (err) { + next(err); + } else { + res.json(saved); + } + }) +}; + +exports.post = function(req, res, next) { + var newcategory = req.body; + + Category.create(newcategory) + .then(function(category) { + res.json(category); + }, function(err) { + next(err); + }); +}; + +exports.delete = function(req, res, next) { + req.category.remove(function(err, removed) { + if (err) { + next(err); + } else { + res.json(removed); + } + }); +}; diff --git a/server/api/post/postController.js b/server/api/post/postController.js new file mode 100644 index 0000000..9d95e26 --- /dev/null +++ b/server/api/post/postController.js @@ -0,0 +1,69 @@ +var Post = require('./postModel'); +var _ = require('lodash'); + +exports.params = function(req, res, next, id) { + Post.findById(id) + .then(function(post) { + if (!post) { + next(new Error('No post with that id')); + } else { + req.post = post; + next(); + } + }, function(err) { + next(err); + }); +}; + +exports.get = function(req, res, next) { + Post.find({}) + .populate('author categories') + .exec() + .then(function(posts){ + res.json(posts); + }, function(err){ + next(err); + }); +}; + +exports.getOne = function(req, res, next) { + var post = req.post; + res.json(post); +}; + +exports.put = function(req, res, next) { + var post = req.post; + + var update = req.body; + + _.merge(post, update); + + post.save(function(err, saved) { + if (err) { + next(err); + } else { + res.json(saved); + } + }) +}; + +exports.post = function(req, res, next) { + var newpost = req.body; + + Post.create(newpost) + .then(function(post) { + res.json(post); + }, function(err) { + next(err); + }); +}; + +exports.delete = function(req, res, next) { + req.post.remove(function(err, removed) { + if (err) { + next(err); + } else { + res.json(removed); + } + }); +}; diff --git a/server/api/user/userController.js b/server/api/user/userController.js new file mode 100644 index 0000000..8a7a2fc --- /dev/null +++ b/server/api/user/userController.js @@ -0,0 +1,67 @@ +var User = require('./userModel'); +var _ = require('lodash'); + +exports.params = function(req, res, next, id) { + User.findById(id) + .then(function(user) { + if (!user) { + next(new Error('No user with that id')); + } else { + req.user = user; + next(); + } + }, function(err) { + next(err); + }); +}; + +exports.get = function(req, res, next) { + User.find({}) + .then(function(users){ + res.json(users); + }, function(err){ + next(err); + }); +}; + +exports.getOne = function(req, res, next) { + var user = req.user; + res.json(user); +}; + +exports.put = function(req, res, next) { + var user = req.user; + + var update = req.body; + + _.merge(user, update); + + user.save(function(err, saved) { + if (err) { + next(err); + } else { + res.json(saved); + } + }) +}; + +exports.post = function(req, res, next) { + var newUser = req.body; + + User.create(newUser) + .then(function(user) { + res.json(user); + }, function(err) { + next(err); + }); +}; + +exports.delete = function(req, res, next) { + req.user.remove(function(err, removed) { + if (err) { + next(err); + } else { + res.json(removed); + } + }); +}; From 858f34a1cd89357593a148859445107a3debbbfa Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Thu, 23 Jul 2015 07:13:27 -0500 Subject: [PATCH 20/27] chore --- server/api/category/categoryRoutes.js | 15 ++++++++++----- server/api/post/postController.js | 3 ++- server/api/post/postModel.js | 2 +- server/api/post/postRoutes.js | 15 ++++++++++----- server/api/user/userRoutes.js | 14 +++++++++----- server/util/logger.js | 4 ++++ 6 files changed, 36 insertions(+), 17 deletions(-) diff --git a/server/api/category/categoryRoutes.js b/server/api/category/categoryRoutes.js index 865443a..1433792 100644 --- a/server/api/category/categoryRoutes.js +++ b/server/api/category/categoryRoutes.js @@ -1,12 +1,17 @@ var router = require('express').Router(); var logger = require('../../util/logger'); - +var controller = require('./categoryController'); // setup boilerplate route jsut to satisfy a request // for building +router.param('id', controller.params); + router.route('/') - .get(function(req, res){ - logger.log('Hey from user!!'); - res.send({ok: true}); - }); + .get(controller.get) + .post(controller.post) + +router.route('/:id') + .get(controller.getOne) + .put(controller.put) + .delete(controller.delete) module.exports = router; diff --git a/server/api/post/postController.js b/server/api/post/postController.js index 9d95e26..9ff78d6 100644 --- a/server/api/post/postController.js +++ b/server/api/post/postController.js @@ -1,5 +1,6 @@ var Post = require('./postModel'); var _ = require('lodash'); +var logger = require('../../util/logger'); exports.params = function(req, res, next, id) { Post.findById(id) @@ -49,11 +50,11 @@ exports.put = function(req, res, next) { exports.post = function(req, res, next) { var newpost = req.body; - Post.create(newpost) .then(function(post) { res.json(post); }, function(err) { + logger.error(err); next(err); }); }; diff --git a/server/api/post/postModel.js b/server/api/post/postModel.js index 196a0f8..b2827eb 100644 --- a/server/api/post/postModel.js +++ b/server/api/post/postModel.js @@ -15,7 +15,7 @@ var PostSchema = new Schema({ // array of ids from the users author: [{type: Schema.Types.ObjectId, ref: 'user'}], - categories: [type: Schema.Types.ObjectId, ref: 'category'] + categories: [{type: Schema.Types.ObjectId, ref: 'category'}] }); module.exports = mongoose.model('post', PostSchema); diff --git a/server/api/post/postRoutes.js b/server/api/post/postRoutes.js index 3671e47..a9d67f5 100644 --- a/server/api/post/postRoutes.js +++ b/server/api/post/postRoutes.js @@ -1,12 +1,17 @@ var router = require('express').Router(); var logger = require('../../util/logger'); - +var controller = require('./postController'); // setup boilerplate route jsut to satisfy a request // for building +router.param('id', controller.params); + router.route('/') - .get(function(req, res){ - logger.log('Hey from post!!'); - res.send({ok: true}); - }); + .get(controller.get) + .post(controller.post) + +router.route('/:id') + .get(controller.getOne) + .put(controller.put) + .delete(controller.delete) module.exports = router; diff --git a/server/api/user/userRoutes.js b/server/api/user/userRoutes.js index 865443a..1399680 100644 --- a/server/api/user/userRoutes.js +++ b/server/api/user/userRoutes.js @@ -1,12 +1,16 @@ var router = require('express').Router(); var logger = require('../../util/logger'); - +var controller = require('./userController'); // setup boilerplate route jsut to satisfy a request // for building +router.param('id', controller.params); + router.route('/') - .get(function(req, res){ - logger.log('Hey from user!!'); - res.send({ok: true}); - }); + .get(controller.get) + .post(controller.post) +router.route('/:id') + .get(controller.getOne) + .put(controller.put) + .delete(controller.delete) module.exports = router; diff --git a/server/util/logger.js b/server/util/logger.js index a741055..2a858e0 100644 --- a/server/util/logger.js +++ b/server/util/logger.js @@ -34,6 +34,10 @@ var logger = { // with the console object as the context // and the new colored args :) consoleLog.apply(console, args); + }, + + error: function() { + consoleLog.apply(console, _.toArray(arguments)); } }; From c008d13b0d07384c6ed227d18b2521d2025c3598 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Thu, 23 Jul 2015 20:25:00 -0500 Subject: [PATCH 21/27] chore --- server/api/post/postController.js | 2 ++ server/api/post/postModel.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/api/post/postController.js b/server/api/post/postController.js index 9ff78d6..2ac1755 100644 --- a/server/api/post/postController.js +++ b/server/api/post/postController.js @@ -4,6 +4,8 @@ var logger = require('../../util/logger'); exports.params = function(req, res, next, id) { Post.findById(id) + .populate('author') + .exec() .then(function(post) { if (!post) { next(new Error('No post with that id')); diff --git a/server/api/post/postModel.js b/server/api/post/postModel.js index b2827eb..d0ae4ef 100644 --- a/server/api/post/postModel.js +++ b/server/api/post/postModel.js @@ -12,8 +12,8 @@ var PostSchema = new Schema({ type: String, required: true }, - // array of ids from the users - author: [{type: Schema.Types.ObjectId, ref: 'user'}], + + author: {type: Schema.Types.ObjectId, ref: 'user'}, categories: [{type: Schema.Types.ObjectId, ref: 'category'}] }); From 49133f64161e2a8fb3d4d20941fd3de45ebacc90 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Thu, 23 Jul 2015 21:47:06 -0500 Subject: [PATCH 22/27] chore --- server/api/user/userModel.js | 4 +- server/config/development.js | 1 + server/server.js | 3 ++ server/util/logger.js | 2 +- server/util/seed.js | 99 ++++++++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 server/util/seed.js diff --git a/server/api/user/userModel.js b/server/api/user/userModel.js index a46b880..5e0cf7b 100644 --- a/server/api/user/userModel.js +++ b/server/api/user/userModel.js @@ -4,8 +4,8 @@ var Schema = mongoose.Schema; var UserSchema = new Schema({ username: { type: String, - unique: true, - required: true + required: true, + unique: true } }); diff --git a/server/config/development.js b/server/config/development.js index 1495217..82f6eee 100644 --- a/server/config/development.js +++ b/server/config/development.js @@ -1,6 +1,7 @@ module.exports = { // enabled logging for development logging: true, + seed: true, db: { url: 'mongodb://localhost/nodeblog' } diff --git a/server/server.js b/server/server.js index 8eda1f7..77440a8 100644 --- a/server/server.js +++ b/server/server.js @@ -5,6 +5,9 @@ var config = require('./config/config'); // db.url is different depending on NODE_ENV require('mongoose').connect(config.db.url); +if (config.seed) { + require('./util/seed'); +} // setup the app middlware require('./middleware/appMiddlware')(app); diff --git a/server/util/logger.js b/server/util/logger.js index 2a858e0..f059746 100644 --- a/server/util/logger.js +++ b/server/util/logger.js @@ -21,7 +21,7 @@ var logger = { if(typeof arg === 'object') { // turn the object to a string so we // can log all the properties and color it - var string = JSON.stringify(arg, 2); + var string = JSON.stringify(arg, null, 2); return string.magenta; } else { // coerce to string to color diff --git a/server/util/seed.js b/server/util/seed.js new file mode 100644 index 0000000..8c16375 --- /dev/null +++ b/server/util/seed.js @@ -0,0 +1,99 @@ +var User = require('../api/user/userModel'); +var Post = require('../api/post/postModel'); +var Category = require('../api/category/categoryModel'); +var _ = require('lodash'); +var logger = require('./logger'); + +logger.log('Seeding the Database'); + +var users = [ + {username: 'Jimmylo'}, + {username: 'Xoko'}, + {username: 'katamon'} +]; + +var categories = [ + {name: 'intros'}, + {name: 'angular'}, + {name: 'UI/UX'} +]; + +var posts = [ + {title: 'Learn angular 2 today', text: 'Angular to is so dope'}, + {title: '10 reasons you should love IE7', text: 'IE7 is so amazing'}, + {title: 'Why we switched to Go', text: 'go is dope'} +]; + +var createDoc = function(model, doc) { + return new Promise(function(resolve, reject) { + new model(doc).save(function(err, saved) { + return err ? reject(err) : resolve(saved); + }); + }); +}; + +var cleanDB = function() { + logger.log('... cleaning the DB'); + var cleanPromises = [User, Category, Post] + .map(function(model) { + return model.remove().exec(); + }); + return Promise.all(cleanPromises); +} + +var createUsers = function(data) { + + var promises = users.map(function(user) { + return createDoc(User, user); + }); + + return Promise.all(promises) + .then(function(users) { + return _.merge({users: users}, data || {}); + }); +}; + +var createCategories = function(data) { + var promises = categories.map(function(category) { + return createDoc(Category, category); + }); + + return Promise.all(promises) + .then(function(categories) { + return _.merge({categories: categories}, data || {}); + }); +}; + +var createPosts = function(data) { + var addCategory = function(post, category) { + post.categories.push(category); + + return new Promise(function(resolve, reject) { + post.save(function(err, saved) { + return err ? reject(err) : resolve(saved) + }); + }); + }; + + var newPosts = posts.map(function(post, i) { + post.author = data.users[i]._id; + return createDoc(Post, post); + }); + + return Promise.all(newPosts) + .then(function(savedPosts) { + return Promise.all(savedPosts.map(function(post, i){ + return addCategory(post, data.categories[i]) + })); + }) + .then(function() { + return 'Seeded DB with 3 Posts, 3 Users, 3 Categories'; + }); +}; + +cleanDB() + .then(createUsers) + .then(createCategories) + .then(createPosts) + .then(logger.log.bind(logger)) + .catch(logger.log.bind(logger)); From 98e174b1351fa1a7c0aff2f8af9c95b4bd1a23ee Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Thu, 23 Jul 2015 23:51:24 -0500 Subject: [PATCH 23/27] chore --- package.json | 3 ++ server/api/user/userController.js | 16 +++--- server/api/user/userModel.js | 33 +++++++++++- server/auth/auth.js | 83 +++++++++++++++++++++++++++++++ server/auth/controller.js | 10 ++++ server/auth/routes.js | 7 +++ server/config/config.js | 7 ++- server/server.js | 10 +++- server/util/seed.js | 6 +-- 9 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 server/auth/auth.js create mode 100644 server/auth/controller.js create mode 100644 server/auth/routes.js diff --git a/package.json b/package.json index 4f31f28..77daafd 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,12 @@ "author": "", "license": "ISC", "dependencies": { + "bcrypt": "^0.8.3", "body-parser": "^1.13.2", "colors": "^1.1.2", "express": "^4.13.1", + "express-jwt": "^3.0.1", + "jsonwebtoken": "^5.0.4", "lodash": "^3.10.0", "mongoose": "^4.0.8", "morgan": "^1.6.1" diff --git a/server/api/user/userController.js b/server/api/user/userController.js index 8a7a2fc..a3df1df 100644 --- a/server/api/user/userController.js +++ b/server/api/user/userController.js @@ -1,5 +1,6 @@ var User = require('./userModel'); var _ = require('lodash'); +var signToken = require('../../auth/auth').signToken; exports.params = function(req, res, next, id) { User.findById(id) @@ -46,14 +47,15 @@ exports.put = function(req, res, next) { }; exports.post = function(req, res, next) { - var newUser = req.body; + var newUser = new User(req.body); + console.log(newUser.encryptPassword) + newUser.hashedPassword = newUser.encryptPassword(newUser.password); + newUser.save(function(err, user) { + if(err) {next(err);} - User.create(newUser) - .then(function(user) { - res.json(user); - }, function(err) { - next(err); - }); + var token = signToken(user._id); + res.json({token: token}); + }); }; exports.delete = function(req, res, next) { diff --git a/server/api/user/userModel.js b/server/api/user/userModel.js index 5e0cf7b..032d1b5 100644 --- a/server/api/user/userModel.js +++ b/server/api/user/userModel.js @@ -1,12 +1,43 @@ var mongoose = require('mongoose'); var Schema = mongoose.Schema; - +var bcrypt = require('bcrypt'); var UserSchema = new Schema({ username: { type: String, required: true, unique: true + }, + + // dont store the password as plain text + password: { + type: String, + required: true } }); +UserSchema.pre('save', function(next) { + if (!this.isModified('password')) return next(); + this.password = this.encryptPassword(this.password); + next(); +}) + + +UserSchema.methods = { + // check the passwords on signin + authenticate: function(plainTextPword) { + return bcrypt.compareSync(plainTextPword, this.password); + }, + // hash the passwords + encryptPassword: function(plainTextPword) { + if (!plainTextPword) { + return '' + } else { + var salt = bcrypt.genSaltSync(10); + return bcrypt.hashSync(plainTextPword, salt); + } + } +}; + + + module.exports = mongoose.model('user', UserSchema); diff --git a/server/auth/auth.js b/server/auth/auth.js new file mode 100644 index 0000000..ff916fe --- /dev/null +++ b/server/auth/auth.js @@ -0,0 +1,83 @@ +var jwt = require('jsonwebtoken'); +var expressJwt = require('express-jwt'); +var config = require('../config/config'); +var checkToken = expressJwt({ secret: config.secrets.jwt }); +var User = require('../api/user/userModel'); + +exports.decodeToken = function() { + return function(req, res, next) { + // make it optional to place token on query string + // if it is, place it on the headers where it should be + // so checkToken can see it. See follow the 'Bearer 034930493' format + // so checkToken can see it and decode it + if (req.query && req.query.hasOwnPropery('access_token')) { + req.headers.authorization = 'Bearer ' + req.query.access_token; + } + + // this will call next if token is valid + // and send error if its not. It will attached + // the decoded token to req.user + checkToken(req, res, next); + }; +}; + +exports.getFreshUser = function() { + return function(req, res, next) { + User.findById(req.user._id) + .then(function(user) { + if (!user) { + // if no user is found it was not + // it was a valid JWT but didn't decode + // to a real user in our DB. Either the user was deleted + // since the client got the JWT, or + // it was a JWT from some other source + res.status(401).send('Unauthorized'); + } else { + // update req.user with fresh user from + // stale token data + req.user = user; + next(); + } + }, function(err) { + next(err); + }); + } +}; + +exports.verifyUser = function() { + return function(req, res, next) { + var username = req.body.username; + var password = req.body.password; + + // if no username or password then send + if (!username || !password) { + res.status(400).send('You need a username and password'); + return; + } + + User.findOne({username: username}) + .then(function(user) { + if (!user) { + res.status(401).send('No user with the given username'); + } else { + if (!user.authenticate(password)) { + res.status(401).send('Wrong password'); + } else { + req.user = user; + next(); + } + } + }, function(err) { + next(err); + }); + }; +}; + +// util method to sign tokens on signup +exports.signToken = function(id) { + return jwt.sign( + {_id: id}, + config.secrets.jwt, + {expiresInMinutes: config.expireTime} + ); +}; diff --git a/server/auth/controller.js b/server/auth/controller.js new file mode 100644 index 0000000..38f1fe3 --- /dev/null +++ b/server/auth/controller.js @@ -0,0 +1,10 @@ +var User = require('../api/user/userModel'); +var signToken = require('./auth').signToken; + +exports.signin = function(req, res, next) { + // req.user will be there from the middleware + // verify user. Then we can just create a token + // and send it back for the client to consume + var token = signToken(req.user._id); + res.json({token: token}); +}; diff --git a/server/auth/routes.js b/server/auth/routes.js new file mode 100644 index 0000000..1e9e87f --- /dev/null +++ b/server/auth/routes.js @@ -0,0 +1,7 @@ +var router = require('express').Router(); +var verifyUser = require('./auth').verifyUser; +var controller = require('./controller'); + +router.post('/signin', verifyUser(), controller.signin); + +module.exports = router; diff --git a/server/config/config.js b/server/config/config.js index 2a1adf9..11bf35e 100644 --- a/server/config/config.js +++ b/server/config/config.js @@ -4,7 +4,12 @@ var config = { dev: 'development', test: 'testing', prod: 'production', - port: process.env.PORT || 3000 + port: process.env.PORT || 3000, + // 10 days in minutes + expireTime: 24 * 60 * 10, + secrets: { + jwt: process.env.JWT || 'gumball' + } }; process.env.NODE_ENV = process.env.NODE_ENV || config.dev; diff --git a/server/server.js b/server/server.js index 77440a8..94bebe2 100644 --- a/server/server.js +++ b/server/server.js @@ -2,6 +2,8 @@ var express = require('express'); var app = express(); var api = require('./api/api'); var config = require('./config/config'); +var logger = require('./util/logger'); +var auth = require('./auth/routes'); // db.url is different depending on NODE_ENV require('mongoose').connect(config.db.url); @@ -12,8 +14,14 @@ if (config.seed) { require('./middleware/appMiddlware')(app); // setup the api -app.use('/api/', api); +app.use('/api', api); +app.use('/auth', auth); // set up global error handling +app.use(function(err, req, res, next) { + logger.error(err.stack); + res.status(500); +}); + // export the app for testing module.exports = app; diff --git a/server/util/seed.js b/server/util/seed.js index 8c16375..b7d5872 100644 --- a/server/util/seed.js +++ b/server/util/seed.js @@ -7,9 +7,9 @@ var logger = require('./logger'); logger.log('Seeding the Database'); var users = [ - {username: 'Jimmylo'}, - {username: 'Xoko'}, - {username: 'katamon'} + {username: 'Jimmylo', password: 'test'}, + {username: 'Xoko', password: 'test'}, + {username: 'katamon', password: 'test'} ]; var categories = [ From 8ab6b445b599df58bca3b2f2dd6845373b43d531 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Fri, 24 Jul 2015 00:14:44 -0500 Subject: [PATCH 24/27] chore --- server/api/category/categoryRoutes.js | 9 ++++++--- server/api/post/postRoutes.js | 9 ++++++--- server/api/user/userModel.js | 2 -- server/api/user/userRoutes.js | 8 ++++++-- server/auth/auth.js | 9 ++++++++- server/auth/routes.js | 2 ++ server/server.js | 7 ++++++- server/util/logger.js | 4 ++-- 8 files changed, 36 insertions(+), 14 deletions(-) diff --git a/server/api/category/categoryRoutes.js b/server/api/category/categoryRoutes.js index 1433792..5545733 100644 --- a/server/api/category/categoryRoutes.js +++ b/server/api/category/categoryRoutes.js @@ -1,17 +1,20 @@ var router = require('express').Router(); var logger = require('../../util/logger'); var controller = require('./categoryController'); +var auth = require('../../auth/auth'); + +var checkUser = [auth.decodeToken(), auth.getFreshUser()]; // setup boilerplate route jsut to satisfy a request // for building router.param('id', controller.params); router.route('/') .get(controller.get) - .post(controller.post) + .post(checkUser, controller.post) router.route('/:id') .get(controller.getOne) - .put(controller.put) - .delete(controller.delete) + .put(checkUser, controller.put) + .delete(checkUser, controller.delete) module.exports = router; diff --git a/server/api/post/postRoutes.js b/server/api/post/postRoutes.js index a9d67f5..74fbd34 100644 --- a/server/api/post/postRoutes.js +++ b/server/api/post/postRoutes.js @@ -1,17 +1,20 @@ var router = require('express').Router(); var logger = require('../../util/logger'); var controller = require('./postController'); +var auth = require('../../auth/auth'); + +var checkUser = [auth.decodeToken(), auth.getFreshUser()]; // setup boilerplate route jsut to satisfy a request // for building router.param('id', controller.params); router.route('/') .get(controller.get) - .post(controller.post) + .post(checkUser ,controller.post) router.route('/:id') .get(controller.getOne) - .put(controller.put) - .delete(controller.delete) + .put(checkUser, controller.put) + .delete(checkUser, controller.delete) module.exports = router; diff --git a/server/api/user/userModel.js b/server/api/user/userModel.js index 032d1b5..b64b221 100644 --- a/server/api/user/userModel.js +++ b/server/api/user/userModel.js @@ -38,6 +38,4 @@ UserSchema.methods = { } }; - - module.exports = mongoose.model('user', UserSchema); diff --git a/server/api/user/userRoutes.js b/server/api/user/userRoutes.js index 1399680..c526cc4 100644 --- a/server/api/user/userRoutes.js +++ b/server/api/user/userRoutes.js @@ -1,6 +1,9 @@ var router = require('express').Router(); var logger = require('../../util/logger'); var controller = require('./userController'); +var auth = require('../../auth/auth'); +var checkUser = [auth.decodeToken(), auth.getFreshUser()]; + // setup boilerplate route jsut to satisfy a request // for building router.param('id', controller.params); @@ -11,6 +14,7 @@ router.route('/') router.route('/:id') .get(controller.getOne) - .put(controller.put) - .delete(controller.delete) + .put(checkUser, controller.put) + .delete(checkUser, controller.delete) + module.exports = router; diff --git a/server/auth/auth.js b/server/auth/auth.js index ff916fe..d79a84a 100644 --- a/server/auth/auth.js +++ b/server/auth/auth.js @@ -10,7 +10,7 @@ exports.decodeToken = function() { // if it is, place it on the headers where it should be // so checkToken can see it. See follow the 'Bearer 034930493' format // so checkToken can see it and decode it - if (req.query && req.query.hasOwnPropery('access_token')) { + if (req.query && req.query.hasOwnProperty('access_token')) { req.headers.authorization = 'Bearer ' + req.query.access_token; } @@ -55,14 +55,21 @@ exports.verifyUser = function() { return; } + // look user up in the DB so we can check + // if the passwords match for the username User.findOne({username: username}) .then(function(user) { if (!user) { res.status(401).send('No user with the given username'); } else { + // checking the passowords here if (!user.authenticate(password)) { res.status(401).send('Wrong password'); } else { + // if everything is good, + // then attach to req.user + // and call next so the controller + // can sign a token from the req.user._id req.user = user; next(); } diff --git a/server/auth/routes.js b/server/auth/routes.js index 1e9e87f..9aa7b7f 100644 --- a/server/auth/routes.js +++ b/server/auth/routes.js @@ -2,6 +2,8 @@ var router = require('express').Router(); var verifyUser = require('./auth').verifyUser; var controller = require('./controller'); +// before we send back a jwt, lets check +// the password and username match what is in the DB router.post('/signin', verifyUser(), controller.signin); module.exports = router; diff --git a/server/server.js b/server/server.js index 94bebe2..2f98f71 100644 --- a/server/server.js +++ b/server/server.js @@ -19,8 +19,13 @@ app.use('/auth', auth); // set up global error handling app.use(function(err, req, res, next) { + if (err.name === 'UnauthorizedError') { + res.status(401).send('Invalid token'); + return; + } + logger.error(err.stack); - res.status(500); + res.status(500).send('Oops'); }); // export the app for testing diff --git a/server/util/logger.js b/server/util/logger.js index f059746..d89eed5 100644 --- a/server/util/logger.js +++ b/server/util/logger.js @@ -22,11 +22,11 @@ var logger = { // turn the object to a string so we // can log all the properties and color it var string = JSON.stringify(arg, null, 2); - return string.magenta; + return string.cyan; } else { // coerce to string to color arg+=''; - return arg.magenta; + return arg.cyan; } }); From 291b1fda67f721cc2a94816d06a7a9aa44539df2 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Fri, 24 Jul 2015 00:33:43 -0500 Subject: [PATCH 25/27] chore --- server/server.js | 1 + server/util/logger.js | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/server/server.js b/server/server.js index 2f98f71..12686e1 100644 --- a/server/server.js +++ b/server/server.js @@ -19,6 +19,7 @@ app.use('/auth', auth); // set up global error handling app.use(function(err, req, res, next) { + // if error thrown from jwt validation check if (err.name === 'UnauthorizedError') { res.status(401).send('Invalid token'); return; diff --git a/server/util/logger.js b/server/util/logger.js index d89eed5..0067f37 100644 --- a/server/util/logger.js +++ b/server/util/logger.js @@ -14,6 +14,7 @@ var consoleLog = config.logging ? console.log.bind(console) : noop; var logger = { log: function() { + var tag = '[ ✨ LOG ✨ ]'.green; // arguments is an array like object with all the passed // in arguments to this function var args = _.toArray(arguments) @@ -22,11 +23,9 @@ var logger = { // turn the object to a string so we // can log all the properties and color it var string = JSON.stringify(arg, null, 2); - return string.cyan; + return tag + ' ' + string.cyan; } else { - // coerce to string to color - arg+=''; - return arg.cyan; + return tag + ' ' + arg.cyan; } }); @@ -37,7 +36,15 @@ var logger = { }, error: function() { - consoleLog.apply(console, _.toArray(arguments)); + var args = _.toArray(arguments) + .map(function(arg) { + arg = arg.stack || arg; + var name = arg.name || '[ ❌ ERROR ❌ ]'; + var log = name.yellow + ' ' + arg.red; + return log; + }); + + consoleLog.apply(console, args); } }; From 706ca55a9ff553d34b8db3df04d747c99418b451 Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Fri, 24 Jul 2015 00:57:59 -0500 Subject: [PATCH 26/27] chore --- server/api/category/categoryRoutes.js | 10 ++++------ server/api/post/postRoutes.js | 10 ++++------ server/api/user/userRoutes.js | 8 +++----- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/server/api/category/categoryRoutes.js b/server/api/category/categoryRoutes.js index 5545733..77748e6 100644 --- a/server/api/category/categoryRoutes.js +++ b/server/api/category/categoryRoutes.js @@ -3,18 +3,16 @@ var logger = require('../../util/logger'); var controller = require('./categoryController'); var auth = require('../../auth/auth'); -var checkUser = [auth.decodeToken(), auth.getFreshUser()]; -// setup boilerplate route jsut to satisfy a request -// for building +// lock down the right routes :) router.param('id', controller.params); router.route('/') .get(controller.get) - .post(checkUser, controller.post) + .post(controller.post) router.route('/:id') .get(controller.getOne) - .put(checkUser, controller.put) - .delete(checkUser, controller.delete) + .put(controller.put) + .delete(controller.delete) module.exports = router; diff --git a/server/api/post/postRoutes.js b/server/api/post/postRoutes.js index 74fbd34..3d10e67 100644 --- a/server/api/post/postRoutes.js +++ b/server/api/post/postRoutes.js @@ -3,18 +3,16 @@ var logger = require('../../util/logger'); var controller = require('./postController'); var auth = require('../../auth/auth'); -var checkUser = [auth.decodeToken(), auth.getFreshUser()]; -// setup boilerplate route jsut to satisfy a request -// for building +// lock down the right routes :) router.param('id', controller.params); router.route('/') .get(controller.get) - .post(checkUser ,controller.post) + .post(controller.post) router.route('/:id') .get(controller.getOne) - .put(checkUser, controller.put) - .delete(checkUser, controller.delete) + .put(controller.put) + .delete(controller.delete) module.exports = router; diff --git a/server/api/user/userRoutes.js b/server/api/user/userRoutes.js index c526cc4..a6b8919 100644 --- a/server/api/user/userRoutes.js +++ b/server/api/user/userRoutes.js @@ -2,10 +2,8 @@ var router = require('express').Router(); var logger = require('../../util/logger'); var controller = require('./userController'); var auth = require('../../auth/auth'); -var checkUser = [auth.decodeToken(), auth.getFreshUser()]; -// setup boilerplate route jsut to satisfy a request -// for building +// lock down the right routes :) router.param('id', controller.params); router.route('/') @@ -14,7 +12,7 @@ router.route('/') router.route('/:id') .get(controller.getOne) - .put(checkUser, controller.put) - .delete(checkUser, controller.delete) + .put(controller.put) + .delete(controller.delete) module.exports = router; From 5d56c843ddd5e13a9fe96f51b3eaca3cf9f6a56a Mon Sep 17 00:00:00 2001 From: Hendrixer Date: Fri, 24 Jul 2015 14:33:37 -0500 Subject: [PATCH 27/27] chore --- server/api/category/categoryRoutes.js | 2 +- server/api/user/userController.js | 3 +-- server/middleware/appMiddlware.js | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/server/api/category/categoryRoutes.js b/server/api/category/categoryRoutes.js index 77748e6..01af2df 100644 --- a/server/api/category/categoryRoutes.js +++ b/server/api/category/categoryRoutes.js @@ -7,7 +7,7 @@ var auth = require('../../auth/auth'); router.param('id', controller.params); router.route('/') - .get(controller.get) + .get( controller.get) .post(controller.post) router.route('/:id') diff --git a/server/api/user/userController.js b/server/api/user/userController.js index a3df1df..9bbadce 100644 --- a/server/api/user/userController.js +++ b/server/api/user/userController.js @@ -48,8 +48,7 @@ exports.put = function(req, res, next) { exports.post = function(req, res, next) { var newUser = new User(req.body); - console.log(newUser.encryptPassword) - newUser.hashedPassword = newUser.encryptPassword(newUser.password); + newUser.save(function(err, user) { if(err) {next(err);} diff --git a/server/middleware/appMiddlware.js b/server/middleware/appMiddlware.js index 380a5d6..9086014 100644 --- a/server/middleware/appMiddlware.js +++ b/server/middleware/appMiddlware.js @@ -1,9 +1,11 @@ var morgan = require('morgan'); var bodyParser = require('body-parser'); +var cors = require('cors'); // setup global middleware here module.exports = function(app) { app.use(morgan('dev')); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); + app.use(cors()); };