Browse Source

Sharp axe

Alan Colon 7 years ago
parent
commit
c9c05ffe1b

+ 1 - 0
.gitignore

@@ -2,3 +2,4 @@ node_modules/
 project.db
 .nyc_output/
 .vscode/
+public/

+ 24 - 0
app/api-service.js

@@ -0,0 +1,24 @@
+const app = require('./app')
+
+app.service('api', function($http) {
+  let opts = {
+    headers: {},
+    withCredentials: true
+  }
+  const api = (req) => req.then(res => {
+    // Enter post-processing here
+    return res.data
+  })
+  this.login = data => api($http.post('/api/auth/login', data, opts))
+  this.setToken = token => {
+    this.token = token
+    opts.headers.authentication = `Bearer ${token}`
+  }
+
+  this.crud = (apiPrefix) => ({
+    list: () => api($http.get(`${apiPrefix}`, opts)),
+    create: data => api($http.post(`${apiPrefix}`, data, opts)),
+    read: id => api($http.get(`${apiPrefix}/${id}`, opts)),
+    update: (id, data) => api($http.put(`${apiPrefix}/${id}`, data, opts))
+  })
+})

+ 30 - 0
app/app.js

@@ -0,0 +1,30 @@
+const angular = require('angular')
+const routes = require('./routes')
+require('angular-material/angular-material.css')
+require('angular-material-data-table/dist/md-data-table.min.css')
+require('angular-route')
+require('angular-material')
+require('angular-material-data-table')
+require('./async')
+window.jQuery = require('jquery')
+
+const es6Html = require('es6-string-html-template')
+window.html = es6Html.html
+window.raw = es6Html.raw
+window.encode = es6Html.encode
+
+const app = angular.module('app', ['ngRoute', 'ngMaterial', 'async', 'md.data.table'])
+
+app.config(($routeProvider, $locationProvider, $mdThemingProvider) => {
+  routes($routeProvider)
+  $locationProvider.html5Mode(true)
+  const palettes = ['red', 'pink', 'purple', 'deep-purple', 'indigo', 'blue', 'light-blue', 'cyan', 'teal', 'green', 'light-green', 'lime', 'yellow', 'amber', 'orange', 'deep-orange', 'brown', 'grey', 'blue-grey']
+  const randomPalette = () => palettes[Math.floor(Math.random() * palettes.length)]
+  $mdThemingProvider.theme('default')
+    .primaryPalette(randomPalette())
+    .accentPalette(randomPalette())
+    .warnPalette(randomPalette())
+})
+
+
+module.exports = app;

+ 186 - 0
app/assets/generic-logo.svg

@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="1425.8217"
+   height="353.73798"
+   id="svg4692"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="New document 8">
+  <defs
+     id="defs4694">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient5225">
+      <stop
+         style="stop-color:#2294fe;stop-opacity:1"
+         offset="0"
+         id="stop5227" />
+      <stop
+         style="stop-color:#014f96;stop-opacity:1"
+         offset="1"
+         id="stop5229" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 372.04724 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1052.3622 : 372.04724 : 1"
+       inkscape:persp3d-origin="526.18109 : 248.03149 : 1"
+       id="perspective4701" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5225"
+       id="linearGradient5284"
+       gradientUnits="userSpaceOnUse"
+       x1="-467.57132"
+       y1="-68.9729"
+       x2="-469.87463"
+       y2="488.42743" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5225"
+       id="linearGradient5311"
+       gradientUnits="userSpaceOnUse"
+       x1="-467.57132"
+       y1="-68.9729"
+       x2="-469.87463"
+       y2="488.42743" />
+  </defs>
+  <sodipodi:namedview
+     inkscape:document-units="mm"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.30699632"
+     inkscape:cx="642.17049"
+     inkscape:cy="175.65506"
+     inkscape:current-layer="layer1"
+     id="namedview4696"
+     showgrid="false"
+     inkscape:window-width="1099"
+     inkscape:window-height="851"
+     inkscape:window-x="605"
+     inkscape:window-y="226"
+     inkscape:window-maximized="0" />
+  <metadata
+     id="metadata4698">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(364.42539,8.4822578)">
+    <g
+       id="g5286">
+      <g
+         transform="translate(-14.426456,-21.336377)"
+         id="text4703"
+         style="font-size:227.36433411px;font-style:normal;font-weight:normal;fill:#666666;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+        <path
+           id="path5264"
+           d="m 145.494,192.3677 0,19.09505 c -5.77303,-3.1824 -11.58295,-5.55078 -17.42979,-7.10513 -5.77301,-1.62815 -11.61994,-2.44228 -17.5408,-2.44239 -13.248182,1.1e-4 -23.535816,4.21878 -30.862934,12.65602 -7.327209,8.36342 -10.990791,20.13129 -10.990757,35.30364 -3.4e-5,15.17247 3.663548,26.97735 10.990757,35.41466 7.327118,8.36335 17.614752,12.54502 30.862934,12.545 5.92086,2e-5 11.76779,-0.77711 17.5408,-2.33137 5.84684,-1.62824 11.65676,-4.03362 17.42979,-7.21615 l 0,18.87301 c -5.69902,2.66443 -11.61996,4.66275 -17.76284,5.99496 -6.06906,1.33221 -12.54509,1.99832 -19.42811,1.99832 -18.725045,0 -33.601407,-5.88394 -44.629129,-17.65182 -11.027781,-11.76785 -16.541656,-27.64337 -16.541644,-47.62661 -1.2e-5,-20.27916 5.550869,-36.22869 16.652662,-47.84865 11.175745,-11.61973 26.459172,-17.42965 45.850331,-17.42978 6.29092,1.3e-4 12.4339,0.66623 18.42894,1.99832 5.99486,1.25832 11.80478,3.18263 17.42979,5.77292" />
+        <path
+           id="path5266"
+           d="m 228.97934,201.91523 c -10.95381,1.1e-4 -19.61318,4.29279 -25.97815,12.87805 -6.36506,8.51145 -9.54756,20.20531 -9.54753,35.08161 -3e-5,14.87642 3.14547,26.60729 9.43651,35.19262 6.36497,8.51138 15.06135,12.76706 26.08917,12.76704 10.87966,2e-5 19.50203,-4.29266 25.86713,-12.87805 6.36492,-8.58534 9.54742,-20.2792 9.54753,-35.08161 -1.1e-4,-14.72828 -3.18261,-26.38513 -9.54753,-34.97059 -6.3651,-8.65928 -14.98747,-12.98896 -25.86713,-12.98907 m 0,-17.31877 c 17.76275,1.3e-4 31.71397,5.77304 41.85369,17.31877 10.1395,11.54594 15.2093,27.53248 15.20943,47.95966 -1.3e-4,20.35329 -5.06993,36.33983 -15.20943,47.95966 -10.13972,11.54585 -24.09094,17.31877 -41.85369,17.31877 -17.8369,0 -31.82512,-5.77292 -41.96471,-17.31877 -10.06562,-11.61983 -15.09842,-27.60637 -15.09841,-47.95966 -1e-5,-20.42718 5.03279,-36.41372 15.09841,-47.95966 10.13959,-11.54573 24.12781,-17.31864 41.96471,-17.31877" />
+        <path
+           id="path5268"
+           d="m 416.59933,211.46275 c 5.1067,-9.17736 11.21267,-15.94943 18.31793,-20.31625 7.10499,-4.36657 15.46832,-6.54991 25.09001,-6.55004 12.9519,1.3e-4 22.94348,4.55185 29.97479,13.65518 7.03093,9.02955 10.54649,21.90759 10.54669,38.63417 l 0,75.048 -20.53828,0 0,-74.38189 c -1.9e-4,-11.91582 -2.10952,-20.76022 -6.32802,-26.53324 -4.21884,-5.77282 -10.65786,-8.65927 -19.31708,-8.65938 -10.58384,1.1e-4 -18.94717,3.51566 -25.09001,10.54668 -6.14311,7.03122 -9.2146,16.61574 -9.21448,28.7536 l 0,70.27423 -20.53828,0 0,-74.38189 c -10e-5,-11.98983 -2.10943,-20.83423 -6.32801,-26.53324 -4.21876,-5.77282 -10.7318,-8.65927 -19.53912,-8.65938 -10.43573,1.1e-4 -18.72505,3.55267 -24.86798,10.6577 -6.14302,7.03122 -9.21451,16.57873 -9.21447,28.64258 l 0,70.27423 -20.53828,0 0,-124.33987 20.53828,0 0,19.31708 c 4.6627,-7.6231 10.25059,-13.24799 16.76368,-16.87469 6.51298,-3.62646 14.24721,-5.43974 23.20271,-5.43987 9.02935,1.3e-4 16.68957,2.29449 22.98067,6.8831 6.36491,4.58885 11.06465,11.24991 14.09925,19.98319" />
+        <path
+           id="path5270"
+           d="m 560.70036,293.28283 0,65.94454 -20.53828,0 0,-171.63343 20.53828,0 0,18.87301 c 4.29264,-7.40107 9.6955,-12.87794 16.20859,-16.43062 6.58699,-3.62646 14.43224,-5.43974 23.53576,-5.43987 15.09832,1.3e-4 27.34726,5.99508 36.74687,17.98487 9.47339,11.99002 14.21014,27.75452 14.21028,47.29356 -1.4e-4,19.53916 -4.73689,35.30367 -14.21028,47.29356 -9.39961,11.98992 -21.64855,17.98487 -36.74687,17.98487 -9.10352,0 -16.94877,-1.77628 -23.53576,-5.32885 -6.51309,-3.62657 -11.91595,-9.14045 -16.20859,-16.54164 m 69.49711,-43.40794 c -1.1e-4,-15.02432 -3.10861,-26.79219 -9.32549,-35.30364 -6.14308,-8.58527 -14.61743,-12.87795 -25.42307,-12.87806 -10.80579,1.1e-4 -19.31714,4.29279 -25.53408,12.87806 -6.14302,8.51145 -9.21451,20.27932 -9.21447,35.30364 -4e-5,15.02445 3.07145,26.82932 9.21447,35.41466 6.21694,8.51138 14.72829,12.76705 25.53408,12.76704 10.80564,10e-6 19.27999,-4.25566 25.42307,-12.76704 6.21688,-8.58534 9.32538,-20.39021 9.32549,-35.41466" />
+        <path
+           id="path5272"
+           d="m 741.77026,249.43082 c -16.5047,6e-5 -27.93951,1.88736 -34.30448,5.6619 -6.36505,3.77466 -9.54756,10.21368 -9.54753,19.31709 -3e-5,7.25319 2.36835,13.02611 7.10514,17.31877 4.81072,4.21869 11.32376,6.32802 19.53912,6.32801 11.32374,10e-6 20.39018,-3.99662 27.19935,-11.98992 6.883,-8.06725 10.32455,-18.76195 10.32465,-32.08412 l 0,-4.55173 -20.31625,0 m 40.74351,-8.43735 0,70.94034 -20.42726,0 0,-18.87302 c -4.66284,7.54922 -10.47276,13.13711 -17.42979,16.76368 -6.95718,3.55257 -15.46853,5.32885 -25.53408,5.32885 -12.73007,0 -22.86968,-3.55257 -30.41886,-10.6577 -7.47521,-7.17913 -11.2128,-16.76366 -11.21279,-28.7536 -10e-6,-13.98818 4.66273,-24.53486 13.98824,-31.64005 9.39946,-7.10506 23.38768,-10.65763 41.9647,-10.65771 l 28.64258,0 0,-1.99832 c -10e-5,-9.39941 -3.10859,-16.65256 -9.32549,-21.75947 -6.14307,-5.18072 -14.80244,-7.77113 -25.97815,-7.77124 -7.10519,1.1e-4 -14.02529,0.85124 -20.76032,2.5534 -6.73511,1.70238 -13.21114,4.25579 -19.42811,7.66023 l 0,-18.87302 c 7.47517,-2.88634 14.72832,-5.03268 21.75948,-6.43903 7.03107,-1.48011 13.87716,-2.22022 20.53828,-2.22035 17.9848,1.3e-4 31.41793,4.66287 40.29944,13.98823 8.88131,9.3256 13.32201,23.46184 13.32213,42.40878" />
+        <path
+           id="path5274"
+           d="m 928.50213,236.88581 0,75.048 -20.42726,0 0,-74.38189 c -1e-4,-11.76779 -2.29447,-20.57519 -6.8831,-26.42222 -4.58883,-5.84683 -11.47192,-8.77029 -20.6493,-8.7704 -11.02783,1.1e-4 -19.72421,3.51566 -26.08917,10.54668 -6.36506,7.03122 -9.54757,16.61574 -9.54752,28.7536 l 0,70.27423 -20.53829,0 0,-124.33987 20.53829,0 0,19.31708 c 4.88473,-7.47508 10.62064,-13.06297 17.20774,-16.76367 6.661,-3.70047 14.32122,-5.55076 22.98068,-5.55089 14.28418,1.3e-4 25.0899,4.44083 32.41718,13.32213 7.32705,8.80751 10.99063,21.79657 10.99075,38.96722" />
+        <path
+           id="path5276"
+           d="m 1021.2019,323.47965 c -5.7729,14.80234 -11.3978,24.46088 -16.8747,28.97563 -5.47688,4.51468 -12.80404,6.77204 -21.98147,6.77209 l -16.3196,0 0,-17.09674 11.98991,0 c 5.62487,-3e-5 9.99156,-1.33224 13.1001,-3.99664 3.10845,-2.66445 6.54999,-8.95544 10.32466,-18.87301 l 3.6636,-9.32549 -50.29107,-122.34155 21.64846,0 38.85621,97.25154 38.8562,-97.25154 21.6485,0 -54.6208,135.88571" />
+      </g>
+      <g
+         transform="translate(-416.34278,-21.336377)"
+         id="text4707"
+         style="font-size:102.52679443px;font-style:normal;font-weight:normal;fill:#666666;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+        <path
+           id="path5241"
+           d="m 489.94918,115.05966 c -5e-5,-6.67489 -1.38509,-11.84795 -4.15514,-15.519195 -2.73676,-3.671159 -6.59152,-5.506761 -11.5643,-5.506811 -4.93947,5e-5 -8.79423,1.835652 -11.5643,5.506811 -2.73674,3.671245 -4.10509,8.844305 -4.10508,15.519195 -10e-6,6.64157 1.36834,11.79794 4.10508,15.46913 2.77007,3.67121 6.62483,5.50682 11.5643,5.50681 4.97278,1e-5 8.82754,-1.8356 11.5643,-5.50681 2.77005,-3.67119 4.15509,-8.82756 4.15514,-15.46913 m 9.21139,21.72687 c -5e-5,9.54513 -2.11934,16.63723 -6.35786,21.27631 -4.23862,4.67243 -10.72998,7.00865 -19.47409,7.00867 -3.23736,-2e-5 -6.29113,-0.25033 -9.16133,-0.75093 -2.87023,-0.46727 -5.65701,-1.20151 -8.36033,-2.20273 l 0,-8.96108 c 2.70332,1.46847 5.37329,2.55315 8.0099,3.25403 2.63657,0.70085 5.32323,1.05128 8.05997,1.0513 6.04077,-2e-5 10.56302,-1.58531 13.56678,-4.75589 3.00367,-3.13721 4.50552,-7.89309 4.50557,-14.26764 l 0,-4.55563 c -1.9024,3.30409 -4.33874,5.77381 -7.30904,7.40916 -2.97038,1.63536 -6.52477,2.45303 -10.66319,2.45303 -6.87519,0 -12.41537,-2.6199 -16.62055,-7.85972 -4.20521,-5.2398 -6.30781,-12.18171 -6.3078,-20.82575 -1e-5,-8.67736 2.10259,-15.635962 6.3078,-20.87582 4.20518,-5.239758 9.74536,-7.859662 16.62055,-7.85972 4.13842,5.8e-5 7.69281,0.817735 10.66319,2.453034 2.9703,1.635409 5.40664,4.105127 7.30904,7.409163 l 0,-8.510525 9.21139,0 0,49.110738" />
+        <path
+           id="path5243"
+           d="m 566.09334,113.40761 0,4.50558 -42.35237,0 c 0.40048,6.34119 2.30283,11.1805 5.70705,14.51795 3.43757,3.30409 8.21013,4.95614 14.31771,4.95613 3.53767,1e-5 6.95856,-0.43386 10.26269,-1.30161 3.33742,-0.86773 6.6415,-2.16934 9.91226,-3.90483 l 0,8.71077 c -3.30414,1.40174 -6.69166,2.46972 -10.16257,3.20397 -3.471,0.73424 -6.99201,1.10136 -10.56306,1.10136 -8.94442,0 -16.03652,-2.60322 -21.27631,-7.80966 -5.20645,-5.20643 -7.80967,-12.24846 -7.80966,-21.12613 -1e-5,-9.17798 2.46971,-16.453634 7.40916,-21.82699 4.9728,-5.406632 11.6644,-8.109972 20.07483,-8.11003 7.54262,5.8e-5 13.49998,2.436401 17.8721,7.309039 4.40539,4.839364 6.60812,11.430841 6.60817,19.774451 m -9.21139,-2.70334 c -0.0668,-5.03953 -1.48521,-9.06116 -4.25526,-12.06492 -2.73676,-3.003666 -6.37459,-4.505522 -10.9135,-4.505572 -5.13971,5e-5 -9.26147,1.451844 -12.36529,4.355387 -3.07048,2.903635 -4.83933,6.992015 -5.30656,12.265165 l 32.84061,-0.0501" />
+        <path
+           id="path5245"
+           d="m 627.61943,109.90328 0,33.84185 -9.21139,0 0,-33.54148 c -4e-5,-5.30652 -1.03466,-9.2781 -3.10384,-11.914733 -2.06926,-2.636546 -5.1731,-3.954842 -9.31151,-3.954891 -4.97285,4.9e-5 -8.89436,1.585342 -11.76455,4.755882 -2.87024,3.170632 -4.30534,7.492632 -4.30533,12.966032 l 0,31.68919 -9.26145,0 0,-56.069338 9.26145,0 0,8.710772 c 2.20271,-3.370784 4.78924,-5.890565 7.7596,-7.559348 3.00369,-1.668674 6.45796,-2.503038 10.36282,-2.503096 6.44125,5.8e-5 11.31394,2.002532 14.61808,6.007429 3.30403,3.971626 4.95607,9.828861 4.95612,17.571731" />
+        <path
+           id="path5247"
+           d="m 694.05159,113.40761 0,4.50558 -42.35238,0 c 0.40048,6.34119 2.30283,11.1805 5.70706,14.51795 3.43756,3.30409 8.21012,4.95614 14.31771,4.95613 3.53767,1e-5 6.95856,-0.43386 10.26269,-1.30161 3.33741,-0.86773 6.64149,-2.16934 9.91226,-3.90483 l 0,8.71077 c -3.30414,1.40174 -6.69166,2.46972 -10.16257,3.20397 -3.471,0.73424 -6.99202,1.10136 -10.56307,1.10136 -8.94442,0 -16.03651,-2.60322 -21.27631,-7.80966 -5.20644,-5.20643 -7.80966,-12.24846 -7.80966,-21.12613 0,-9.17798 2.46972,-16.453634 7.40917,-21.82699 4.9728,-5.406632 11.6644,-8.109972 20.07482,-8.11003 7.54262,5.8e-5 13.49999,2.436401 17.87211,7.309039 4.40539,4.839364 6.60811,11.430841 6.60817,19.774451 m -9.21139,-2.70334 c -0.0668,-5.03953 -1.48522,-9.06116 -4.25527,-12.06492 -2.73676,-3.003666 -6.37458,-4.505522 -10.91349,-4.505572 -5.13972,5e-5 -9.26148,1.451844 -12.36529,4.355387 -3.07049,2.903635 -4.83934,6.992015 -5.30657,12.265165 l 32.84062,-0.0501" />
+        <path
+           id="path5249"
+           d="m 741.46022,96.28644 c -1.03465,-0.600694 -2.16939,-1.034564 -3.40421,-1.301609 -1.20152,-0.300323 -2.53651,-0.450508 -4.00495,-0.450557 -5.20647,4.9e-5 -9.21142,1.702152 -12.01486,5.106315 -2.77011,3.370871 -4.15516,8.226881 -4.15514,14.568011 l 0,29.53653 -9.26145,0 0,-56.069338 9.26145,0 0,8.710772 c 1.93571,-3.404159 4.45549,-5.923939 7.55935,-7.559348 3.10381,-1.668674 6.87513,-2.503038 11.31399,-2.503096 0.63408,5.8e-5 1.33495,0.05012 2.1026,0.150186 0.76758,0.06681 1.61863,0.183617 2.55316,0.350433 l 0.0501,9.461701" />
+        <path
+           id="path5251"
+           d="m 751.22228,87.675792 9.21139,0 0,56.069338 -9.21139,0 0,-56.069338 m 0,-21.826994 9.21139,0 0,11.664426 -9.21139,0 0,-11.664426" />
+        <path
+           id="path5253"
+           d="m 820.00736,89.828454 0,8.610649 c -2.60326,-1.435062 -5.22317,-2.503048 -7.85972,-3.203963 -2.60326,-0.734192 -5.23985,-1.101312 -7.90978,-1.101362 -5.97408,5e-5 -10.61315,1.902401 -13.91721,5.707058 -3.3041,3.771374 -4.95614,9.077924 -4.95613,15.919684 -10e-6,6.84182 1.65203,12.16506 4.95613,15.96975 3.30406,3.77134 7.94313,5.65701 13.91721,5.657 2.66993,1e-5 5.30652,-0.35043 7.90978,-1.0513 2.63655,-0.73423 5.25646,-1.81891 7.85972,-3.25402 l 0,8.51052 c -2.56989,1.20149 -5.23985,2.1026 -8.0099,2.70334 -2.73676,0.60075 -5.65703,0.90112 -8.76084,0.90112 -8.4438,0 -15.15209,-2.65328 -20.12489,-7.95985 -4.97282,-5.30655 -7.45923,-12.46539 -7.45922,-21.47656 -10e-6,-9.1446 2.50309,-16.336822 7.50929,-21.57668 5.03954,-5.239758 11.93139,-7.859662 20.67557,-7.85972 2.8368,5.8e-5 5.60689,0.300429 8.31027,0.901115 2.7033,0.567424 5.32321,1.435163 7.85972,2.603219" />
+        <path
+           id="path5255"
+           d="m 868.56741,65.848798 9.21139,0 0,77.896332 -9.21139,0 0,-77.896332" />
+        <path
+           id="path5257"
+           d="m 918.72946,94.133778 c -4.93947,5e-5 -8.8443,1.935775 -11.71449,5.807182 -2.87023,3.83812 -4.30534,9.1113 -4.30533,15.81956 -1e-5,6.70832 1.41841,11.99819 4.25527,15.86963 2.87019,3.83809 6.7917,5.75713 11.76455,5.75712 4.90603,1e-5 8.79416,-1.93572 11.66442,-5.80718 2.87017,-3.87144 4.30528,-9.14462 4.30533,-15.81957 -5e-5,-6.64151 -1.43516,-11.898 -4.30533,-15.769498 -2.87026,-3.904781 -6.75839,-5.857194 -11.66442,-5.857244 m 0,-7.809658 c 8.00986,5.8e-5 14.30097,2.603274 18.87334,7.809658 4.57226,5.206483 6.85842,12.415392 6.85848,21.626742 -6e-5,9.17804 -2.28622,16.38695 -6.85848,21.62675 -4.57237,5.20644 -10.86348,7.80966 -18.87334,7.80966 -8.04331,0 -14.3511,-2.60322 -18.92341,-7.80966 -4.53895,-5.2398 -6.80842,-12.44871 -6.80842,-21.62675 0,-9.21135 2.26947,-16.420259 6.80842,-21.626742 4.57231,-5.206384 10.8801,-7.8096 18.92341,-7.809658" />
+        <path
+           id="path5259"
+           d="m 996.5757,115.05966 c -4e-5,-6.67489 -1.38509,-11.84795 -4.15513,-15.519195 -2.73676,-3.671159 -6.59152,-5.506761 -11.56431,-5.506811 -4.93946,5e-5 -8.79423,1.835652 -11.5643,5.506811 -2.73673,3.671245 -4.10509,8.844305 -4.10507,15.519195 -2e-5,6.64157 1.36834,11.79794 4.10507,15.46913 2.77007,3.67121 6.62484,5.50682 11.5643,5.50681 4.97279,1e-5 8.82755,-1.8356 11.56431,-5.50681 2.77004,-3.67119 4.15509,-8.82756 4.15513,-15.46913 m 9.2114,21.72687 c -10e-5,9.54513 -2.1193,16.63723 -6.35787,21.27631 -4.23862,4.67243 -10.72997,7.00865 -19.47408,7.00867 -3.23736,-2e-5 -6.29114,-0.25033 -9.16133,-0.75093 -2.87023,-0.46727 -5.65701,-1.20151 -8.36034,-2.20273 l 0,-8.96108 c 2.70333,1.46847 5.3733,2.55315 8.00991,3.25403 2.63657,0.70085 5.32322,1.05128 8.05996,1.0513 6.04077,-2e-5 10.56303,-1.58531 13.56678,-4.75589 3.00367,-3.13721 4.50553,-7.89309 4.50557,-14.26764 l 0,-4.55563 c -1.90239,3.30409 -4.33874,5.77381 -7.30903,7.40916 -2.97038,1.63536 -6.52477,2.45303 -10.66319,2.45303 -6.87519,0 -12.41537,-2.6199 -16.62056,-7.85972 -4.2052,-5.2398 -6.3078,-12.18171 -6.3078,-20.82575 0,-8.67736 2.1026,-15.635962 6.3078,-20.87582 4.20519,-5.239758 9.74537,-7.859662 16.62056,-7.85972 4.13842,5.8e-5 7.69281,0.817735 10.66319,2.453034 2.97029,1.635409 5.40664,4.105127 7.30903,7.409163 l 0,-8.510525 9.2114,0 0,49.110738" />
+        <path
+           id="path5261"
+           d="m 1046.4875,94.133778 c -4.9395,5e-5 -8.8443,1.935775 -11.7145,5.807182 -2.8703,3.83812 -4.3054,9.1113 -4.3054,15.81956 0,6.70832 1.4184,11.99819 4.2553,15.86963 2.8702,3.83809 6.7917,5.75713 11.7646,5.75712 4.906,1e-5 8.7941,-1.93572 11.6644,-5.80718 2.8701,-3.87144 4.3053,-9.14462 4.3053,-15.81957 0,-6.64151 -1.4352,-11.898 -4.3053,-15.769498 -2.8703,-3.904781 -6.7584,-5.857194 -11.6644,-5.857244 m 0,-7.809658 c 8.0098,5.8e-5 14.3009,2.603274 18.8733,7.809658 4.5723,5.206483 6.8584,12.415392 6.8585,21.626742 -10e-5,9.17804 -2.2862,16.38695 -6.8585,21.62675 -4.5724,5.20644 -10.8635,7.80966 -18.8733,7.80966 -8.0434,0 -14.3511,-2.60322 -18.9235,-7.80966 -4.5389,-5.2398 -6.8084,-12.44871 -6.8084,-21.62675 0,-9.21135 2.2695,-16.420259 6.8084,-21.626742 4.5724,-5.206384 10.8801,-7.8096 18.9235,-7.809658" />
+      </g>
+      <g
+         transform="matrix(0.43788811,-0.43788811,0.43788811,0.43788811,-547.02034,450.07345)"
+         id="g5278">
+        <path
+           sodipodi:type="arc"
+           style="fill:url(#linearGradient5311);fill-opacity:1;stroke:none"
+           id="path4711"
+           sodipodi:cx="-469.87463"
+           sodipodi:cy="202.81734"
+           sodipodi:rx="285.61008"
+           sodipodi:ry="285.61008"
+           d="m -184.26456,202.81734 c 0,157.73809 -127.87198,285.61007 -285.61007,285.61007 -157.73809,0 -285.61008,-127.87198 -285.61008,-285.61007 0,-157.738093 127.87199,-285.61008 285.61008,-285.61008 157.73809,0 285.61007,127.871987 285.61007,285.61008 z"
+           transform="translate(1201.9688,-114.00788)" />
+        <path
+           d="m -184.26456,202.81734 c 0,157.73809 -127.87198,285.61007 -285.61007,285.61007 -157.73809,0 -285.61008,-127.87198 -285.61008,-285.61007 0,-157.738093 127.87199,-285.61008 285.61008,-285.61008 157.73809,0 285.61007,127.871987 285.61007,285.61008 z"
+           sodipodi:ry="285.61008"
+           sodipodi:rx="285.61008"
+           sodipodi:cy="202.81734"
+           sodipodi:cx="-469.87463"
+           id="path4713"
+           style="fill:#eeeeee;fill-opacity:1;stroke:none"
+           sodipodi:type="arc"
+           transform="matrix(0.60887098,0,0,0.60887098,1018.1872,79.030259)" />
+      </g>
+    </g>
+  </g>
+</svg>

+ 12 - 0
app/assets/index.js

@@ -0,0 +1,12 @@
+module.exports = {
+  style: require('./style.scss'),
+  genericLogo: require('./generic-logo.svg'),
+  menuIcon: require('@alancnet/material-design-icons/navigation_ic_menu_48px.svg'),
+  userIcon: require('@alancnet/material-design-icons/social_ic_person_48px.svg'),
+  addUserIcon: require('@alancnet/material-design-icons/social_ic_person_add_48px.svg'),
+  dashboardIcon: require('@alancnet/material-design-icons/action_ic_dashboard_48px.svg'),
+  createIcon: require('@alancnet/material-design-icons/content_ic_add_48px.svg'),
+  editIcon: require('@alancnet/material-design-icons/content_ic_create_48px.svg'),
+  deleteIcon: require('@alancnet/material-design-icons/action_ic_delete_48px.svg'),
+  saveIcon: require('@alancnet/material-design-icons/content_ic_save_48px.svg')
+}

+ 37 - 0
app/assets/style.scss

@@ -0,0 +1,37 @@
+body,
+ng-view,
+ng-view > *,
+app-user-area,
+app-home-area
+{
+  display: flex;
+  flex-direction: row;
+  flex-grow: 100
+}
+
+app-home-area {
+  .logo-container {
+    // padding-top: 10em;
+    padding-bottom: 5em;
+    text-align: center;
+  }
+  .logo {
+    max-width: 20em;
+  }
+  .home-content {
+    max-width: 400px;
+    margin-right: auto;
+    margin-left: auto;
+  }
+}
+
+md-sidenav {
+  .logo-container {
+    padding-top: 1em;
+    padding-bottom: 2em;
+    text-align: center;
+  }
+  .logo {
+    max-width: 80%;
+  }
+}

+ 31 - 0
app/async.js

@@ -0,0 +1,31 @@
+const angular = require('angular')
+const mod = angular.module('async', [])
+mod.run(($q, $window, $rootScope) => {
+  let timer = null
+  const then = $q.prototype.then
+  const setTimer = () => {
+    if (!timer) {
+      timer = setTimeout(() => {
+        timer = null
+        $rootScope.$digest()
+      })
+    }
+  }
+  $q.prototype.then = function(successCallback, errorCallback, notifyCallback) {
+    return then.call(
+      this,
+      successCallback && (val => {
+        setTimer()
+        return successCallback(val)
+      }),
+      errorCallback && (err => {
+        setTimer()
+        return errorCallback(err)
+      }),
+      notifyCallback && (msg => {
+        setTimer()
+        return errorCallback(err)
+      })
+    )
+ }
+})

+ 21 - 0
app/components/crud-pages/README.md

@@ -0,0 +1,21 @@
+# CRUD Pages
+
+This utility will create standard pages for listing, creating, reading, updating, and deleting standard entities.
+
+```javascript
+crudPages({
+  typeName,         // string (required): MyEntity
+  typePlural,       // string (optional): MyEntities
+  camelName,        // string (optional): myEntity
+  camelPlural,      // string (optional): myEntities
+  paramName,        // string (optional): my-entity
+  paramPlural,      // string (optional): my-entities
+  apiPrefix,        // string (optional): /api/my-entities
+  columns: [
+    {
+      fieldName:    // string (required): Mailing Address
+      camelName:    // string (optional): mailingAddress
+    }
+  ]
+})
+```

+ 75 - 0
app/components/crud-pages/details.js

@@ -0,0 +1,75 @@
+const _ = require('lodash')
+const app = require('../../app')
+const details = ({
+  typeName,
+  typePlural,
+  camelName,
+  camelPlural,
+  paramName,
+  paramPlural,
+  apiPrefix,
+  appPrefix,
+  columns
+}) => {
+  const defaultInput = column => html`
+    <md-input-container>
+      <label>${column.fieldName}</label>
+      <input type="${column.type || 'text'}" ng-model="model.${raw(column.camelName)}" />
+    </md-input-container>
+  `
+
+  app.component(`app${typeName}DetailsPage`, {
+
+    template: html`
+      <app-user-area>
+        <h1>${typeName}</h1>
+        <form name="form" ng-submit="ctrl.submit()">
+          ${columns.map(c => c.input || defaultInput(c))}
+
+          <div>
+            <md-button type="submit">Submit</md-button>
+          </div>
+
+        </form>
+      </app-user-area>
+    `,
+    controllerAs: 'ctrl',
+    controller: function(api, $scope, $routeParams, $mdToast) {
+      const crud = api.crud(apiPrefix)
+      let original
+      if ($routeParams.id === 'new') {
+        original = {}
+        $scope.model = Object.create(original)
+      } else {
+        crud.read($routeParams.id).then(model => {
+          original = model
+          $scope.model = Object.create(original)
+        })
+      }
+
+      this.submit = async () => {
+        try {
+          if ($routeParams.id === 'new') {
+            await crud.create($scope.model)
+          } else {
+            const obj = {}
+            for (var key in $scope.model) {
+              if ($scope.model.hasOwnProperty(key)) {
+                obj[key] = $scope.model[key]
+              }
+            }
+            await crud.update(original.id, obj)
+          }
+          $mdToast.showSimple(`${typeName} saved.`)
+        } catch (err) {
+          console.error(err)
+          $mdToast.showSimple(`Could not save ${typeName}: ${err.message || err}`)
+        }
+      }
+
+    }
+
+  })
+}
+
+module.exports = details

+ 52 - 0
app/components/crud-pages/index.js

@@ -0,0 +1,52 @@
+const app = require('../../app')
+const { pascal, camel, param } = require('change-case')
+const plural = require('plural')
+const list = require('./list')
+const details = require('./details')
+
+const crudPages = ({
+  typeName,
+  typePlural,
+  camelName,
+  camelPlural,
+  paramName,
+  paramPlural,
+  apiPrefix,
+  appPrefix,
+  columns
+}) => {
+  if (!typeName) throw new Error('typeName is required')
+  if (typeName !== pascal(typeName)) throw new Error('typeName should be PascalCased')
+  if (!typePlural) typePlural = plural(typeName)
+  if (typePlural !== pascal(typePlural)) throw new Error('typePlural should be PascalCased')
+  if (!camelName) camelName = camel(typeName)
+  if (camelName !== camel(camelName)) throw new Error('camelName should be camelCased')
+  if (!camelPlural) camelPlural = plural(camelName)
+  if (camelPlural !== camel(camelPlural)) throw new Error('camelPlural should be camelCased')
+  if (!paramName) paramName = param(typeName)
+  if (paramName !== param(paramName)) throw new Error('paramName should be param-cased')
+  if (!paramPlural) paramPlural = plural(paramName)
+  if (paramPlural !== param(paramPlural)) throw new Error('paramPlural should be param-cased')
+  if (!apiPrefix) apiPrefix = `/api/${paramPlural}`
+  if (!appPrefix) appPrefix = `/${paramPlural}`
+
+  columns = columns.map(column => Object.assign({}, column, {
+    camelName: column.camelName || camel(column.fieldName)
+  }))
+
+  const listComponentName = `app${typePlural}List`
+  const listComponentTag = `app-${paramName}-list`
+  const listPageComponentName = `app${typePlural}Page`
+  const listPageComponentTag = `app-${paramPlural}-page`
+
+  const args = { typeName, typePlural, camelName, camelPlural, paramName, paramPlural, apiPrefix, appPrefix, columns }
+
+  list(args)
+  details(args)
+}
+
+
+  // TODO: Create Read Update Delete pages...
+
+
+module.exports = crudPages

+ 68 - 0
app/components/crud-pages/list.js

@@ -0,0 +1,68 @@
+const app = require('../../app')
+const {editIcon, createIcon} = require('../../assets')
+const list = ({
+  typeName,
+  typePlural,
+  camelName,
+  camelPlural,
+  paramName,
+  paramPlural,
+  apiPrefix,
+  appPrefix,
+  columns
+}) => {
+  const defaultHeader = column => html`<th md-column>${column.fieldName}</th>`
+  const defaultCell = column => html`<td md-cell>{{${raw(camelName)}.${raw(column.camelName)}}}</td>`
+
+  app.component(`app${typePlural}Page`, {
+    template: html`
+      <app-user-area>
+        <h1>${typePlural}</h1>
+        <md-table-container>
+          <table md-table md-row-select md-auto-select  ng-model="ctrl.selected" md-progress="ctrl.promise">
+              <thead md-head md-order="query.order" md-on-reorder="ctrl.getRecords">
+                <tr md-row>
+                  ${columns.map(c => c.header || defaultHeader(c))}
+                  <th md-column>Actions</th>
+                </tr>
+              </thead>
+              <tbody md-body>
+                <tr md-row md-select="${camelName}" md-select-id="name" md-auto-select ng-repeat="${raw(camelName)} in ctrl.data track by ${raw(camelName)}.id">
+                  ${columns.map(c => c.cell || defaultCell(c))}
+                  <td md-cell>
+                    <md-button ng-href="${appPrefix}/{{${raw(camelName)}.id}}">
+                      <md-icon md-svg-icon="${editIcon}"></md-icon>
+                      Edit
+                    </md-button>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+        </md-table-container>
+        <div layout="row" layout-align="end">
+          <md-button class="md-fab" aria-label="Add ${typeName}" ng-href="${appPrefix}/new">
+            <md-icon md-svg-src="${createIcon}"></md-icon>
+          </md-button>
+        </div>
+      </app-user-area>
+    `,
+    controllerAs: 'ctrl',
+    controller: function(api) {
+      const crud = api.crud(apiPrefix)
+
+      this.selected = []
+      this.data = []
+
+      this.getRecords = () => {
+        this.promise = crud.list().then(data => {
+          this.data = data
+        })
+      }
+
+      this.getRecords()      
+    }
+
+  })
+}
+
+module.exports = list

+ 14 - 0
app/components/dashboard-page.js

@@ -0,0 +1,14 @@
+const app = require('../app')
+const { genericLogo } = require('../assets')
+
+app.component('appDashboardPage', {
+  template: html`
+    <app-user-area>
+      Dashboard goes here
+    </app-user-area>
+  `,
+  controllerAs: 'dashboard',
+  controller: function() {
+
+  }
+})

+ 20 - 0
app/components/home-area.js

@@ -0,0 +1,20 @@
+const app = require('../app')
+const { genericLogo } = require('../assets')
+
+app.component('appHomeArea', {
+  transclude: true,
+  template: html`
+    <div style="display: flex; align-items: center; justify-content: center; height: 100%; flex-grow: 100;">
+      <div style="flex-basis: auto">
+        <div class="logo-container">
+          <img class="logo" src="${genericLogo}" />
+        </div>
+        <div class="home-content" ng-transclude>
+          <!-- content -->
+        </div>
+      </div>
+    </div>
+  `,
+  controller: function() {
+  }
+})

+ 11 - 0
app/components/home-page.js

@@ -0,0 +1,11 @@
+const app = require('../app')
+
+app.component('appHomePage', {
+  template: html`
+    <app-home-area>
+      <div layout="row">
+        <md-button flex ng-href="login" class="md-raised md-primary">Login</md-button>
+      </div>
+    </app-home-area>
+  `
+})

+ 33 - 0
app/components/login-page.js

@@ -0,0 +1,33 @@
+const app = require('../app')
+
+app.component('appLoginPage', {
+  template: html`
+    <app-home-area>
+      <form name="form" ng-submit="login.submit(form)" class="md-inline-form" layout-align="center" layout="column" novalidate>
+        <md-input-container>
+          <label>Email</label>
+          <input type="email" required md-no-asterisk="true"  ng-model="login.model.email" />
+        </md-input-container>
+        <md-input-container>
+          <label>Password</label>
+          <input type="password" required md-no-asterisk="true"  ng-model="login.model.password" />
+        </md-input-container>
+        <md-button type="submit" class="md-raised md-primary">Login</md-button>
+        <md-button class="" href="/forgot-password">Forgot password</md-button>
+      </form>
+    </app-home-area>
+  `,
+  controllerAs: 'login',
+  controller: function(api, $mdToast, $location) {
+    this.submit = async form => {
+      try {
+        const res = await api.login(this.model)
+        api.setToken(res.token)
+        $location.url('/dashboard')
+      } catch (err) {
+        console.log($mdToast)
+        $mdToast.showSimple(`Login failed`)
+      }
+    }
+  }
+})

+ 15 - 0
app/components/test-page.js

@@ -0,0 +1,15 @@
+const app = require('../app')
+
+app.component('appTestPage', {
+  template: html`
+  <h2>Material Buttons</h2>
+  <p>
+    <md-button class="md-primary">Hello World</md-button>
+    <md-button class="md-accent">Hello World</md-button>
+    <md-button class="md-warn">Hello World</md-button>
+    <md-button class="">Hello World</md-button>
+  </p>
+  `,
+  controller: function() {
+  }
+})

+ 66 - 0
app/components/user-area.js

@@ -0,0 +1,66 @@
+const app = require('../app')
+const { genericLogo, menuIcon, userIcon, dashboardIcon } = require('../assets')
+
+
+app.component('appUserArea', {
+  transclude: true,
+  template: html`
+    <div layout="row" flex>
+      <md-sidenav flex
+        md-component-id="left"
+        class="md-sidenav-left"
+        md-is-locked-open="$mdMedia('gt-md')"
+        md-whiteframe="4"
+        layout="column">
+
+        <header>
+          <div class="logo-container">
+            <img class="logo" src="${genericLogo}" />
+          </div>          
+        </header>
+
+        <h3>
+          Intelligence
+        </h3>
+        <md-menu-item>
+          <md-button ng-href="/dashboard">
+            <md-icon md-svg-icon="${dashboardIcon}"></md-icon>
+            Dashboard
+          </md-button>
+        </md-menu-item>
+
+        <h3>
+          Administration
+        </h3>
+
+        <md-menu-item>
+          <md-button ng-href="/users">
+            <md-icon md-svg-icon="${userIcon}"></md-icon>
+            Users
+          </md-button>
+        </md-menu-item>
+      </md-sidenav>
+      <md-content flex>
+        <md-toolbar>
+          <div class="md-toolbar-tools">
+            <md-button class="md-icon-button" aria-label="Settings" ng-hide="$mdMedia('gt-md')" ng-click="ctrl.toggleNav()">
+              <md-icon md-svg-icon="${menuIcon}"></md-icon>
+            </md-button>
+          </div>
+        </md-toolbar>
+        <div layout-padding>
+          <div ng-transclude></div>
+        </div>
+      </md-content>
+    </div>
+  `,
+  controllerAs: 'ctrl',
+  controller: function($mdSidenav, $mdMedia, $scope) {
+    $scope.$mdMedia = $mdMedia
+    this.showNav = false
+    this.toggleNav = () => {
+      console.log('toggle')
+      $mdSidenav('left').toggle()
+    }
+  }
+})

+ 28 - 0
app/components/user-pages.js

@@ -0,0 +1,28 @@
+const crudPages = require('./crud-pages')
+
+crudPages({
+  typeName: 'User',
+  typePlural: 'Users',
+  camelName: 'user',
+  camelPlural: 'users',
+  snakeName: 'user',
+  snakePlural: 'users',
+  apiPrefix: '/api/users',
+  columns: [
+    {
+      fieldName: 'Name',
+      camelName: 'name',
+      header: html`<th md-column md-order-by="nameToLower"><span>Name</span></th>`,
+      cell: html`<td md-cell>{{user.name}}</td>`
+    },
+    {
+      fieldName: 'Email',
+      camelName: 'email'
+    },
+    {
+      fieldName: 'Password',
+      camelName: 'password',
+      type: 'password'
+    }
+  ]
+})

+ 9 - 0
app/index.html

@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html ng-app="app">
+<head>
+  <base href="/" />
+  <title>Project App</title>
+  <script type="text/javascript" src="index.js"></script>
+</head>
+  <body flex layout="row"><ng-view></ng-view></body>  
+</html>

+ 8 - 0
app/index.js

@@ -0,0 +1,8 @@
+require('./components/home-area')
+require('./components/login-page')
+require('./components/test-page')
+require('./components/user-area')
+require('./components/home-page')
+require('./components/dashboard-page')
+require('./components/user-pages')
+require('./api-service')

+ 9 - 0
app/routes.js

@@ -0,0 +1,9 @@
+module.exports = function($routeProvider) {
+  $routeProvider.when('/test', {template: '<app-test-page />'})
+  $routeProvider.when('/login', {template: '<app-login-page />'})
+  $routeProvider.when('/dashboard', {template: '<app-dashboard-page />'})
+  $routeProvider.when('/', {template: '<app-home-page />'})
+  $routeProvider.when('/users', {template: '<app-users-page />'})
+  $routeProvider.when('/users/:id', {template: '<app-user-details-page />'})
+  $routeProvider.otherwise({template: '<h1>404</h1>'})
+}

+ 1 - 3
bin/project.js

@@ -27,9 +27,7 @@ const main = async () => {
   })
 
   vorpal.command('server', 'Runs the web server')
-  .action(() => new Promise(() => {
-    server.init()
-  }))
+  .action(() => server.start())
 
   vorpal.delimiter('project>')
   if (process.argv.length > 2) {

+ 2 - 1
config.js

@@ -16,6 +16,7 @@ module.exports = {
     host: null,
     port: null,
     storage: 'project.db',
-    operatorsAliases: false
+    operatorsAliases: false,
+    logging: false
   }
 }

+ 0 - 13
lib/controllers/auth/index.js

@@ -1,16 +1,3 @@
-const Joi = require('joi')
-const loginModel = require('../../models/login')
-const hapiLogin = require('hapi-login')
-
-const init = async server => {
-  // await server.auth.strategy('jwt', 'jwt', {
-  //   key: process.env.JWT_SECRET || 'dev',          // Never Share your secret key
-  //   validate: controllers.auth.validate,
-  //   verifyOptions: { algorithms: [ 'HS256' ] } // pick a strong algorithm
-  // })
-}
-
 module.exports = {
-  init,
   login: require('./login')
 }

+ 6 - 6
lib/controllers/auth/login.js

@@ -6,10 +6,10 @@ const aguid = require('aguid')
 const { User, Session } = require('../../database')
 
 module.exports = {
-  post: async (request, h) => {
-    const user = await User.find({where: {email: request.payload.email}})
+  post: async (req, res) => {
+    const user = await User.find({where: {email: req.body.email}})
     if (user) {
-      const success = await bcrypt.compare(request.payload.password, user.password)
+      const success = await bcrypt.compare(req.body.password, user.password)
       if (success) {
         const sid = aguid()
         const exp = Math.floor(Date.now()/1000) + config.auth.jwtExpires
@@ -19,12 +19,12 @@ module.exports = {
           endTimestamp: exp
         })
         const token = JWT.sign({sid, exp}, config.auth.jwtSecret);
-        return {
+        res.status(200).send({
           user: user.sanitize(),
           token
-        }
+        })
       }
     }
-    return h.response('Login failed').code(401)
+    return res.send(401)
   }
 }

+ 27 - 0
lib/controllers/crud-controller.js

@@ -0,0 +1,27 @@
+const crudController = ({
+  Type
+}) => ({
+  list: async (req, res) => {
+    const data = (await Type.findAll()).map(d => d.sanitize ? d.sanitize() : d)
+    res.status(200).send(data && data.sanitize ? data.sanitize() : data)
+  },
+  create: async (req, res) => {
+    const data = (await Type.create(req.body))
+    res.status(200).send(data && data.sanitize ? data.sanitize() : data)
+  },
+  read: async (req, res) => {
+    const data = (await Type.findOne({where: {id: req.params.id}}))
+    res.status(200).send(data && data.sanitize ? data.sanitize() : data)
+  },
+  update: async (req, res) => {
+    const data = (await Type.update(req.body, { where: { id: req.params.id } }))
+    res.status(200).send(data && data.sanitize ? data.sanitize() : data)
+  },
+  delete: async (req, res) => {
+    const data = (await Type.delete({ where: { id: req.params.id } }))
+    res.status(200).send(data && data.sanitize ? data.sanitize() : data)
+  }
+  // TODO: Create, Read, Update, Delete
+})
+
+module.exports = crudController

+ 2 - 1
lib/controllers/index.js

@@ -1,3 +1,4 @@
 module.exports = {
-  auth: require('./auth')
+  auth: require('./auth'),
+  user: require('./user')
 }

+ 6 - 0
lib/controllers/user.js

@@ -0,0 +1,6 @@
+const crudController = require('./crud-controller')
+const { User } = require('../database')
+
+module.exports = crudController({
+  Type: User
+})

+ 35 - 0
lib/crud-route.js

@@ -0,0 +1,35 @@
+const { pascal, camel, param } = require('change-case')
+const plural = require('plural')
+const asyncHandler = require('express-async-handler')
+
+module.exports = ({
+  app,
+  controller,
+  typeName,
+  typePlural,
+  camelName,
+  camelPlural,
+  paramName,
+  paramPlural,
+  apiPrefix  
+}) => {
+  if (!typeName) throw new Error('typeName is required')
+  if (typeName !== pascal(typeName)) throw new Error('typeName should be PascalCased')
+  if (!typePlural) typePlural = plural(typeName)
+  if (typePlural !== pascal(typePlural)) throw new Error('typePlural should be PascalCased')
+  if (!camelName) camelName = camel(typeName)
+  if (camelName !== camel(camelName)) throw new Error('camelName should be camelCased')
+  if (!camelPlural) camelPlural = plural(camelName)
+  if (camelPlural !== camel(camelPlural)) throw new Error('camelPlural should be camelCased')
+  if (!paramName) paramName = param(typeName)
+  if (paramName !== camel(paramName)) throw new Error('paramName should be param-cased')
+  if (!paramPlural) paramPlural = plural(paramName)
+  if (paramPlural !== camel(paramPlural)) throw new Error('paramPlural should be param-cased')
+  if (apiPrefix) apiPrefix = `/${paramPlural}`
+
+  if (controller.list) app.get(`/api/${paramPlural}`, asyncHandler(controller.list))
+  if (controller.create) app.post(`/api/${paramPlural}`, asyncHandler(controller.create))
+  if (controller.read) app.get(`/api/${paramPlural}/:id`, asyncHandler(controller.read))
+  if (controller.update) app.put(`/api/${paramPlural}/:id`, asyncHandler(controller.update))
+  if (controller.delete) app.delete(`/api/${paramPlural}/:id`, asyncHandler(controller.delete))
+}

+ 8 - 0
lib/routes.js

@@ -0,0 +1,8 @@
+const crudRoute = require('./crud-route')
+const asyncHandler = require('express-async-handler')
+const C = require('./controllers')
+
+module.exports = app => {
+  app.post('/api/auth/login', asyncHandler(C.auth.login.post))
+  crudRoute({ app, controller: C.user, typeName: 'User' })
+}

+ 28 - 11
lib/server.js

@@ -1,14 +1,31 @@
-const { Server } = require('hapi')
-const controllers = require('./controllers')
+const express = require('express')
+const C = require('./controllers')
 const config = require('../config')
+const path = require('path')
+const chalk = require('chalk')
+const asyncHandler = require('express-async-handler')
+const bodyParser = require('body-parser')
+const routes = require('./routes')
 
-const server = new Server(config.server)
-server.route({ method: 'POST', path: '/auth/login', handler: controllers.auth.login.post })
+const app = express()
+app.use(bodyParser.json())
 
-server.init = async () => {
-  await controllers.auth.init(server)
-  await server.start()
-  console.log(`Server running at ${server.info.port}`)
-}
-
-module.exports = server
+routes(app)
+app.use(express.static('./public'))
+app.all('/api/*', (req, res) => {
+  res.send(404)
+})
+app.get('*', (req, res) => {
+  res.sendFile(path.join(process.cwd(), 'public/index.html'));
+})
+app.start = () => new Promise((resolve, reject) => {
+  try {
+    const listener = app.listen(config.server.port, function() {
+      app.address = listener.address()
+      console.log(`Server running at ${chalk.underline(chalk.blue(`http://localhost:${app.address.port}`))}`)
+    })
+  } catch (e) {
+    reject(e)
+  }
+})
+module.exports = app

+ 19 - 0
nodemon.json

@@ -0,0 +1,19 @@
+{
+  "ignore": [
+    ".git",
+    "node_modules/**/node_modules",
+    "public/**"
+  ],
+  "watch": [
+    "test/",
+    "lib/",
+    "app/",
+    "bin/",
+    "config.js",
+    "webpack.config.js"
+  ],
+  "env": {
+    "NODE_ENV": "development"
+  },
+  "ext": "js,json,html,css,scss"
+}

File diff suppressed because it is too large
+ 6836 - 713
package-lock.json


+ 41 - 7
package.json

@@ -4,30 +4,64 @@
   "description": "",
   "main": "index.js",
   "scripts": {
-    "test": "nyc mocha"
+    "test": "nyc mocha",
+    "build": "webpack",
+    "start": "nodemon -w webpack.config.js --exec \"webpack -w\" & nodemon -w config.js -w lib --exec \"node bin/project.js server\"",
+    "startx": "nodemon --exec \"webpack && node bin/project.js server\""
   },
   "author": "",
   "license": "ISC",
   "dependencies": {
+    "@alancnet/icomoon-svg": "^2.0.0",
+    "@alancnet/material-design-icons": "^1.0.0",
     "aguid": "^2.0.0",
+    "angular": "^1.6.10",
+    "angular-animate": "^1.7.0",
+    "angular-aria": "^1.7.0",
+    "angular-material": "^1.1.9",
+    "angular-material-data-table": "^0.10.10",
+    "angular-messages": "^1.7.0",
+    "angular-route": "^1.6.10",
+    "angular1-ui-bootstrap4": "^2.4.22",
     "as-table": "^1.0.32",
     "axios": "^0.18.0",
     "bcrypt": "^2.0.1",
+    "body-parser": "^1.18.3",
     "chalk": "^2.4.1",
-    "hapi": "^17.4.0",
-    "hapi-auth-jwt2": "^8.1.0",
-    "hapi-login": "^1.2.2",
-    "joi": "^13.2.0",
+    "change-case": "^3.0.2",
+    "codebase": "git+ssh://git@git.alanc.net:10222/alancnet/codebase.git",
+    "cors": "^2.8.4",
+    "es6-string-html-template": "^1.0.2",
+    "express": "^4.16.3",
+    "express-async-handler": "^1.1.3",
+    "jquery": "^3.3.1",
+    "jsonwebtoken": "^8.2.2",
     "lodash": "^4.17.10",
+    "material-design-icons": "^3.0.1",
+    "node-sass": "^4.9.0",
     "password-prompt": "^1.0.4",
+    "plural": "^1.1.0",
     "sequelize": "^4.37.6",
-    "vision": "^5.3.2",
     "vorpal": "^1.12.0"
   },
   "devDependencies": {
+    "babel-loader": "^7.1.4",
+    "babel-preset-es2015": "^6.24.1",
     "chai": "^4.1.2",
+    "copy-webpack-plugin": "^4.5.1",
+    "css-loader": "^0.28.11",
+    "exports-loader": "^0.7.0",
+    "file-loader": "^1.1.11",
+    "html-loader": "^0.5.5",
+    "markup-inline-loader": "^0.2.2",
     "mocha": "^5.1.1",
     "nyc": "^11.7.1",
-    "sqlite3": "^4.0.0"
+    "raw-loader": "^0.5.1",
+    "sass-loader": "^7.0.1",
+    "sqlite3": "^4.0.0",
+    "style-loader": "^0.21.0",
+    "webpack": "^4.6.0",
+    "webpack-cli": "^2.1.3",
+    "webpack-livereload-plugin": "^2.1.1"
   }
 }

+ 50 - 0
webpack.config.js

@@ -0,0 +1,50 @@
+const path = require('path')
+const Copy = require('copy-webpack-plugin')
+const LiveReloadPlugin = require('webpack-livereload-plugin')
+
+
+module.exports = {
+  devtool: 'source-map',
+
+  mode: process.env.NODE_ENV || 'development',
+  
+  plugins: [
+    new Copy([
+      { from: 'app/index.html', to: 'index.html' }
+    ]),
+    new LiveReloadPlugin({
+      appendScriptTag: true
+    })
+  ],
+
+  entry: './app/index.js',
+  
+  output: {
+    path: path.resolve(__dirname, 'public'),
+    filename: 'index.js'
+  },
+  module:{
+    rules:[
+      // {
+      //   test:/\.js$/,
+      //   exclude: /node_modules/,
+      //   use: {
+      //     loader: 'babel-loader',
+      //     options: { presets: ['es2015'] }
+      //   }
+      // },
+      {
+        test:/\.(s*)css$/,
+        use:['style-loader','css-loader', 'sass-loader']
+      },
+      {
+        test:/\.(?:svg|eot|woff2|woff|ttf)$/,
+        use:['file-loader']
+      },
+      {
+        test:/\.html$/,
+        use:['html-loader']
+      },
+    ]
+  }
+};

Some files were not shown because too many files changed in this diff