├── .eslintignore ├── .eslintrc ├── .gitignore ├── assets └── css │ └── site.css ├── config.js ├── gulpfile.js ├── index.html ├── package.json ├── readme.md ├── src-client ├── app.html ├── app.js ├── lib │ └── web-services.js ├── main.js └── pages │ ├── home.html │ └── home.js └── src-server ├── server.js └── services ├── books.js └── models └── books.js /.eslintignore: -------------------------------------------------------------------------------- 1 | assets 2 | dist-client 3 | dist-server 4 | dist-interfaces 5 | jspm_packages 6 | node_modules 7 | ./*.js 8 | ./index.html 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "ecmaFeatures": { 4 | "modules" : true 5 | }, 6 | "env": { 7 | "es6": true, 8 | "browser": true 9 | }, 10 | "rules": { 11 | "comma-dangle" : 2, 12 | "no-cond-assign" : 2, 13 | "no-console" : 0, 14 | "no-constant-condition" : 2, 15 | "no-control-regex" : 2, 16 | "no-debugger" : 2, 17 | "no-dupe-args" : 2, 18 | "no-dupe-keys" : 2, 19 | "no-duplicate-case" : 2, 20 | "no-empty-character-class" : 2, 21 | "no-empty" : 2, 22 | "no-ex-assign": 2, 23 | "no-extra-boolean-cast" : 2, 24 | "no-extra-parens" : 2, 25 | "no-extra-semi": 2, 26 | "no-func-assign": 2, 27 | "no-inner-declarations" : 2, 28 | "no-invalid-regexp" : 2, 29 | "no-irregular-whitespace" : 2, 30 | "no-negated-in-lhs" : 2, 31 | "no-obj-calls" : 2, 32 | "no-regex-spaces" : 2, 33 | "no-sparse-arrays" : 2, 34 | "no-unreachable" : 2, 35 | "use-isnan" : 2, 36 | "valid-jsdoc" : 0, 37 | "valid-typeof": 2, 38 | "no-unexpected-multiline" : 2, 39 | 40 | "accessor-pairs" : [2, {"getWithoutSet" : false}], 41 | "block-scoped-var" : 2, 42 | "complexity" : 0, 43 | "consistent-return" : 2, 44 | "curly" : 2, 45 | "default-case" : 2, 46 | "dot-notation" : 2, 47 | "dot-location" : [2, "property"], 48 | "eqeqeq" : 2, 49 | "guard-for-in" : 2, 50 | "no-alert" : 1, 51 | "no-caller" : 2, 52 | "no-div-regex" : 2, 53 | "no-else-return" : 2, 54 | "no-empty-label" : 2, 55 | "no-eq-null" : 2, 56 | "no-eval" : 2, 57 | "no-extend-native" : 2, 58 | "no-extra-bind" : 2, 59 | "no-fallthrough" : 2, 60 | "no-floating-decimal" : 2, 61 | "no-implicit-coercion": 2, 62 | "no-implied-eval" : 2, 63 | "no-invalid-this": 2, 64 | "no-iterator" : 2, 65 | "no-labels" : 2, 66 | "no-lone-blocks" : 2, 67 | "no-loop-func" : 2, 68 | "no-multi-spaces": 2, 69 | "no-multi-str" : 2, 70 | "no-native-reassign" : 2, 71 | "no-new-func" : 2, 72 | "no-new-wrappers": 2, 73 | "no-new" : 2, 74 | "no-octal-escape" : 2, 75 | "no-octal" : 2, 76 | "no-param-reassign" : 2, 77 | "no-process-env" : 2, 78 | "no-proto" : 2, 79 | "no-redeclare" : 2, 80 | "no-return-assign" : 2, 81 | "no-script-url" : 2, 82 | "no-self-compare" : 2, 83 | "no-sequences" : 2, 84 | "no-throw-literal" : 2, 85 | "no-unused-expressions" : 2, 86 | "no-useless-call" : 2, 87 | "no-useless-concat" : 2, 88 | "no-void" : 2, 89 | "no-warning-comments" : 2, 90 | "no-with" : 2, 91 | "radix" : 2, 92 | "vars-on-top": 0, 93 | "wrap-iife" : 2, 94 | "yoda" : 2, 95 | 96 | "strict" : 0, 97 | 98 | "init-declarations" : [2, "always"], 99 | "no-catch-shadow" : 2, 100 | "no-delete-var" : 2, 101 | "no-label-var" : 2, 102 | "no-shadow-restricted-names": 2, 103 | "no-shadow" : 0, 104 | "no-undef-init" : 2, 105 | "no-undef" : 2, 106 | "no-undefined" : 0, 107 | "no-unused-vars" : 1, 108 | "no-use-before-define" : 2, 109 | 110 | "array-bracket-spacing" : 2, 111 | "block-spacing" : 2, 112 | "brace-style" : [2, "1tbs", { "allowSingleLine" : true } ], 113 | "camelcase" : 0, 114 | "comma-spacing" : 2, 115 | "comma-style" : 2, 116 | "computed-property-spacing" : 2, 117 | "consistent-this" : [2, "self"], 118 | "eol-last": 2, 119 | "func-names" : 0, 120 | "func-style" : 0, 121 | "id-length" : 0, 122 | "id-match" : 0, 123 | "indent" : [2, 2], 124 | "key-spacing" : [2, { "beforeColon" : true, "afterColon": true }], 125 | "lines-around-comment" : [2, { "beforeBlockComment" : true, "beforeLineComment" : true }], 126 | "linebreak-style" : 0, 127 | "max-nested-callbacks" : [2, 10], 128 | "new-cap" : 0, 129 | "new-parens" : 2, 130 | "newline-after-var" : 2, 131 | "no-array-constructor" : 2, 132 | "no-continue" : 0, 133 | "no-inline-comments" : 2, 134 | "no-lonely-if" : 2, 135 | "no-mixed-spaces-and-tabs" : 2, 136 | "no-multiple-empty-lines" : 2, 137 | "no-nested-ternary" : 0, 138 | "no-new-object" : 2, 139 | "no-spaced-func" : 2, 140 | "no-ternary" : 0, 141 | "no-trailing-spaces" : 2, 142 | "no-underscore-dangle" : 0, 143 | "no-unneeded-ternary" : 2, 144 | "object-curly-spacing" : [2, "always"], 145 | "one-var" : 0, 146 | "operator-assignment" : [2, "always"], 147 | "operator-linebreak" : [2, "after"], 148 | "padded-blocks" : [2, "never"], 149 | "quote-props" : 0, 150 | "quotes" : [2, "single"], 151 | "semi-spacing" : [2, { "before" : false, "after" : true }], 152 | "semi" : 2, 153 | "sort-vars" : 2, 154 | "space-after-keywords" : 2, 155 | "space-before-blocks" : 0, 156 | "space-before-function-paren" : 2, 157 | "space-in-parens" : [2, "never"], 158 | "space-infix-ops" : 2, 159 | "space-return-throw-case" : 2, 160 | "space-unary-ops" : [2, { "words" : true, "nonwords" : false }], 161 | "spaced-comment": [2, "always"], 162 | "wrap-regex" : 2, 163 | 164 | "arrow-parens" : [2, "as-needed"], 165 | "arrow-spacing" : 2, 166 | "constructor-super" : 2, 167 | "generator-star-spacing" : 0, 168 | "no-class-assign" : 2, 169 | "no-const-assign" : 2, 170 | "no-dupe-class-members" : 2, 171 | "no-this-before-super" : 2, 172 | "no-var" : 2, 173 | "object-shorthand" : 2, 174 | "prefer-arrow-callback" : 2, 175 | "prefer-const" : 2, 176 | "prefer-spread" : 2, 177 | "prefer-reflect" : 0, 178 | "prefer-template" : 1, 179 | "require-yield": 2 180 | }, 181 | "env": { 182 | "mongo" : true, 183 | "node": true, 184 | "browser": true 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | jspm_packages 2 | node_modules 3 | dist-client 4 | dist-server 5 | 6 | -------------------------------------------------------------------------------- /assets/css/site.css: -------------------------------------------------------------------------------- 1 | .splash { 2 | text-align: center; 3 | margin: 10% 0 0 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | .splash .message { 8 | font-size: 72px; 9 | line-height: 72px; 10 | color: black; 11 | text-shadow: rgba(0, 0, 0, 0.5) 0 0 15px; 12 | text-transform: uppercase; 13 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 14 | } 15 | 16 | .splash .fa-spinner { 17 | text-align: center; 18 | display: inline-block; 19 | color: white; 20 | font-size: 72px; 21 | margin-top: 50px; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | System.config({ 2 | baseURL: "/", 3 | defaultJSExtensions: true, 4 | transpiler: "babel", 5 | babelOptions: { 6 | "optional": [ 7 | "runtime", 8 | "optimisation.modules.system" 9 | ] 10 | }, 11 | paths: { 12 | "github:*": "jspm_packages/github/*", 13 | "npm:*": "jspm_packages/npm/*" 14 | }, 15 | 16 | map: { 17 | "aurelia-animator-css": "github:aurelia/animator-css@0.16.0", 18 | "aurelia-binding": "github:aurelia/binding@0.9.1", 19 | "aurelia-bootstrapper": "github:aurelia/bootstrapper@0.17.0", 20 | "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.10.1", 21 | "aurelia-event-aggregator": "github:aurelia/event-aggregator@0.8.0", 22 | "aurelia-fetch-client": "github:aurelia/fetch-client@0.2.0", 23 | "aurelia-framework": "github:aurelia/framework@0.16.0", 24 | "aurelia-history": "github:aurelia/history@0.7.0", 25 | "aurelia-history-browser": "github:aurelia/history-browser@0.8.0", 26 | "aurelia-html-import-template-loader": "github:aurelia/html-import-template-loader@0.2.2", 27 | "aurelia-http-client": "github:aurelia/http-client@0.11.0", 28 | "aurelia-loader": "github:aurelia/loader@0.9.0", 29 | "aurelia-loader-default": "github:aurelia/loader-default@0.10.0", 30 | "aurelia-logging": "github:aurelia/logging@0.7.0", 31 | "aurelia-metadata": "github:aurelia/metadata@0.8.0", 32 | "aurelia-path": "github:aurelia/path@0.9.0", 33 | "aurelia-route-recognizer": "github:aurelia/route-recognizer@0.7.0", 34 | "aurelia-router": "github:aurelia/router@0.12.0", 35 | "aurelia-task-queue": "github:aurelia/task-queue@0.7.0", 36 | "aurelia-templating": "github:aurelia/templating@0.15.3", 37 | "aurelia-templating-binding": "github:aurelia/templating-binding@0.15.0", 38 | "aurelia-templating-resources": "github:aurelia/templating-resources@0.15.2", 39 | "aurelia-templating-router": "github:aurelia/templating-router@0.16.1", 40 | "babel": "npm:babel-core@5.8.25", 41 | "babel-runtime": "npm:babel-runtime@5.8.25", 42 | "bootstrap": "github:twbs/bootstrap@3.3.5", 43 | "core-js": "npm:core-js@1.2.0", 44 | "fetch": "github:github/fetch@0.9.0", 45 | "font-awesome": "npm:font-awesome@4.4.0", 46 | "jquery": "github:components/jquery@2.1.4", 47 | "mutationobservers": "npm:mutationobservers@0.0.1", 48 | "github:aurelia/animator-css@0.16.0": { 49 | "aurelia-metadata": "github:aurelia/metadata@0.8.0", 50 | "aurelia-templating": "github:aurelia/templating@0.15.3" 51 | }, 52 | "github:aurelia/binding@0.9.1": { 53 | "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.10.1", 54 | "aurelia-metadata": "github:aurelia/metadata@0.8.0", 55 | "aurelia-task-queue": "github:aurelia/task-queue@0.7.0", 56 | "core-js": "npm:core-js@0.9.18" 57 | }, 58 | "github:aurelia/bootstrapper@0.17.0": { 59 | "aurelia-event-aggregator": "github:aurelia/event-aggregator@0.8.0", 60 | "aurelia-framework": "github:aurelia/framework@0.16.0", 61 | "aurelia-history": "github:aurelia/history@0.7.0", 62 | "aurelia-history-browser": "github:aurelia/history-browser@0.8.0", 63 | "aurelia-loader-default": "github:aurelia/loader-default@0.10.0", 64 | "aurelia-logging-console": "github:aurelia/logging-console@0.7.1", 65 | "aurelia-router": "github:aurelia/router@0.12.0", 66 | "aurelia-templating": "github:aurelia/templating@0.15.3", 67 | "aurelia-templating-binding": "github:aurelia/templating-binding@0.15.0", 68 | "aurelia-templating-resources": "github:aurelia/templating-resources@0.15.2", 69 | "aurelia-templating-router": "github:aurelia/templating-router@0.16.1", 70 | "core-js": "npm:core-js@0.9.18" 71 | }, 72 | "github:aurelia/dependency-injection@0.10.1": { 73 | "aurelia-logging": "github:aurelia/logging@0.7.0", 74 | "aurelia-metadata": "github:aurelia/metadata@0.8.0", 75 | "core-js": "npm:core-js@0.9.18" 76 | }, 77 | "github:aurelia/event-aggregator@0.8.0": { 78 | "aurelia-logging": "github:aurelia/logging@0.7.0" 79 | }, 80 | "github:aurelia/fetch-client@0.2.0": { 81 | "core-js": "npm:core-js@0.9.18" 82 | }, 83 | "github:aurelia/framework@0.16.0": { 84 | "aurelia-binding": "github:aurelia/binding@0.9.1", 85 | "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.10.1", 86 | "aurelia-loader": "github:aurelia/loader@0.9.0", 87 | "aurelia-logging": "github:aurelia/logging@0.7.0", 88 | "aurelia-metadata": "github:aurelia/metadata@0.8.0", 89 | "aurelia-path": "github:aurelia/path@0.9.0", 90 | "aurelia-task-queue": "github:aurelia/task-queue@0.7.0", 91 | "aurelia-templating": "github:aurelia/templating@0.15.3", 92 | "core-js": "npm:core-js@0.9.18" 93 | }, 94 | "github:aurelia/history-browser@0.8.0": { 95 | "aurelia-history": "github:aurelia/history@0.7.0", 96 | "core-js": "npm:core-js@0.9.18" 97 | }, 98 | "github:aurelia/html-import-template-loader@0.2.2": { 99 | "aurelia-loader": "github:aurelia/loader@0.9.0", 100 | "webcomponentsjs": "github:webcomponents/webcomponentsjs@0.7.14" 101 | }, 102 | "github:aurelia/http-client@0.11.0": { 103 | "aurelia-path": "github:aurelia/path@0.9.0", 104 | "core-js": "npm:core-js@0.9.18" 105 | }, 106 | "github:aurelia/loader-default@0.10.0": { 107 | "aurelia-loader": "github:aurelia/loader@0.9.0", 108 | "aurelia-metadata": "github:aurelia/metadata@0.8.0" 109 | }, 110 | "github:aurelia/loader@0.9.0": { 111 | "aurelia-html-template-element": "github:aurelia/html-template-element@0.3.0", 112 | "aurelia-metadata": "github:aurelia/metadata@0.8.0", 113 | "aurelia-path": "github:aurelia/path@0.9.0", 114 | "core-js": "github:zloirock/core-js@0.8.4" 115 | }, 116 | "github:aurelia/logging-console@0.7.1": { 117 | "aurelia-logging": "github:aurelia/logging@0.7.0" 118 | }, 119 | "github:aurelia/metadata@0.8.0": { 120 | "core-js": "npm:core-js@0.9.18" 121 | }, 122 | "github:aurelia/route-recognizer@0.7.0": { 123 | "aurelia-path": "github:aurelia/path@0.9.0", 124 | "core-js": "npm:core-js@0.9.18" 125 | }, 126 | "github:aurelia/router@0.12.0": { 127 | "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.10.1", 128 | "aurelia-event-aggregator": "github:aurelia/event-aggregator@0.8.0", 129 | "aurelia-history": "github:aurelia/history@0.7.0", 130 | "aurelia-logging": "github:aurelia/logging@0.7.0", 131 | "aurelia-path": "github:aurelia/path@0.9.0", 132 | "aurelia-route-recognizer": "github:aurelia/route-recognizer@0.7.0", 133 | "core-js": "npm:core-js@0.9.18" 134 | }, 135 | "github:aurelia/templating-binding@0.15.0": { 136 | "aurelia-binding": "github:aurelia/binding@0.9.1", 137 | "aurelia-logging": "github:aurelia/logging@0.7.0", 138 | "aurelia-templating": "github:aurelia/templating@0.15.3" 139 | }, 140 | "github:aurelia/templating-resources@0.15.2": { 141 | "aurelia-binding": "github:aurelia/binding@0.9.1", 142 | "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.10.1", 143 | "aurelia-loader": "github:aurelia/loader@0.9.0", 144 | "aurelia-logging": "github:aurelia/logging@0.7.0", 145 | "aurelia-path": "github:aurelia/path@0.9.0", 146 | "aurelia-task-queue": "github:aurelia/task-queue@0.7.0", 147 | "aurelia-templating": "github:aurelia/templating@0.15.3", 148 | "core-js": "npm:core-js@0.9.18" 149 | }, 150 | "github:aurelia/templating-router@0.16.1": { 151 | "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.10.1", 152 | "aurelia-metadata": "github:aurelia/metadata@0.8.0", 153 | "aurelia-path": "github:aurelia/path@0.9.0", 154 | "aurelia-router": "github:aurelia/router@0.12.0", 155 | "aurelia-templating": "github:aurelia/templating@0.15.3" 156 | }, 157 | "github:aurelia/templating@0.15.3": { 158 | "aurelia-binding": "github:aurelia/binding@0.9.1", 159 | "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.10.1", 160 | "aurelia-html-template-element": "github:aurelia/html-template-element@0.3.0", 161 | "aurelia-loader": "github:aurelia/loader@0.9.0", 162 | "aurelia-logging": "github:aurelia/logging@0.7.0", 163 | "aurelia-metadata": "github:aurelia/metadata@0.8.0", 164 | "aurelia-path": "github:aurelia/path@0.9.0", 165 | "aurelia-task-queue": "github:aurelia/task-queue@0.7.0", 166 | "core-js": "npm:core-js@0.9.18" 167 | }, 168 | "github:jspm/nodelibs-os@0.1.0": { 169 | "os-browserify": "npm:os-browserify@0.1.2" 170 | }, 171 | "github:jspm/nodelibs-path@0.1.0": { 172 | "path-browserify": "npm:path-browserify@0.0.0" 173 | }, 174 | "github:jspm/nodelibs-process@0.1.1": { 175 | "process": "npm:process@0.10.1" 176 | }, 177 | "github:twbs/bootstrap@3.3.5": { 178 | "jquery": "github:components/jquery@2.1.4" 179 | }, 180 | "npm:babel-runtime@5.8.25": { 181 | "process": "github:jspm/nodelibs-process@0.1.1" 182 | }, 183 | "npm:core-js@0.9.18": { 184 | "fs": "github:jspm/nodelibs-fs@0.1.2", 185 | "process": "github:jspm/nodelibs-process@0.1.1", 186 | "systemjs-json": "github:systemjs/plugin-json@0.1.0" 187 | }, 188 | "npm:core-js@1.2.0": { 189 | "fs": "github:jspm/nodelibs-fs@0.1.2", 190 | "process": "github:jspm/nodelibs-process@0.1.1", 191 | "systemjs-json": "github:systemjs/plugin-json@0.1.0" 192 | }, 193 | "npm:font-awesome@4.4.0": { 194 | "css": "github:systemjs/plugin-css@0.1.18" 195 | }, 196 | "npm:mutationobservers@0.0.1": { 197 | "polymer-tools": "npm:polymer-tools@0.0.0", 198 | "polymer-weakmap": "npm:polymer-weakmap@2.0.0" 199 | }, 200 | "npm:os-browserify@0.1.2": { 201 | "os": "github:jspm/nodelibs-os@0.1.0" 202 | }, 203 | "npm:path-browserify@0.0.0": { 204 | "process": "github:jspm/nodelibs-process@0.1.1" 205 | }, 206 | "npm:polymer-tools@0.0.0": { 207 | "child_process": "github:jspm/nodelibs-child_process@0.1.0", 208 | "fs": "github:jspm/nodelibs-fs@0.1.2", 209 | "os": "github:jspm/nodelibs-os@0.1.0", 210 | "path": "github:jspm/nodelibs-path@0.1.0", 211 | "process": "github:jspm/nodelibs-process@0.1.1", 212 | "systemjs-json": "github:systemjs/plugin-json@0.1.0" 213 | } 214 | } 215 | }); 216 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var runSequence = require('run-sequence'); 3 | var changed = require('gulp-changed'); 4 | var plumber = require('gulp-plumber'); 5 | var babel = require('gulp-babel'); 6 | var sourcemaps = require('gulp-sourcemaps'); 7 | var assign = Object.assign || require('object.assign'); 8 | var del = require('del'); 9 | var vinylPaths = require('vinyl-paths'); 10 | var nodemon = require('gulp-nodemon'); 11 | 12 | /* 13 | Task to clean up the dist-client and dist-server 14 | directories 15 | */ 16 | gulp.task('clean', function() { 17 | return gulp.src(['dist-client/', 'dist-server']) 18 | .pipe(vinylPaths(del)); 19 | }); 20 | 21 | /* 22 | Task to compile server js code with babel 23 | */ 24 | gulp.task('build-server-js', function () { 25 | var compilerOptions = { 26 | modules: 'common', 27 | moduleIds: false, 28 | comments: false, 29 | compact: false, 30 | stage:2, 31 | optional: ["es7.decorators", "es7.classProperties"] 32 | }; 33 | return gulp.src('src-server/**/*.js') 34 | .pipe(plumber()) 35 | .pipe(changed('dist-server/', {extension: '.js'})) 36 | .pipe(sourcemaps.init()) 37 | .pipe(babel(assign({}, compilerOptions, {modules:'common'}))) 38 | .pipe(sourcemaps.write({includeContent: false, sourceRoot: '/src-server/' })) 39 | .pipe(gulp.dest('dist-server/')); 40 | }); 41 | 42 | /* 43 | Task to compile client js code with babel 44 | */ 45 | gulp.task('build-client-js', function () { 46 | var compilerOptions = { 47 | modules: 'system', 48 | moduleIds: false, 49 | comments: false, 50 | compact: false, 51 | stage:2, 52 | optional: ["es7.decorators", "es7.classProperties"] 53 | }; 54 | return gulp.src('src-client/**/*.js') 55 | .pipe(plumber()) 56 | .pipe(changed('dist-client/', {extension: '.js'})) 57 | .pipe(sourcemaps.init()) 58 | .pipe(babel(assign({}, compilerOptions, {modules:'system'}))) 59 | .pipe(sourcemaps.write({includeContent: false, sourceRoot: '/src-client/' })) 60 | .pipe(gulp.dest('dist-client/')); 61 | }); 62 | 63 | /* 64 | Task to copy client html files from src to dist 65 | */ 66 | gulp.task('build-client-html', function () { 67 | return gulp.src('src-client/**/*.html') 68 | .pipe(changed('dist-client/', {extension: '.html'})) 69 | .pipe(gulp.dest('dist-client/')); 70 | }); 71 | 72 | /* 73 | Task to clean and build the entire application 74 | */ 75 | gulp.task('build', function(callback) { 76 | return runSequence('clean', ['build-server-js', 'build-client-js', 'build-client-html'], callback); 77 | }); 78 | 79 | /* 80 | Task to start up the server with nodemon and 81 | rebuild/restart if any source changes 82 | */ 83 | gulp.task('nodemon', ['build'], function () { 84 | nodemon({ 85 | watch: ['./src-client', './src-server'], 86 | ext: 'js', 87 | script: './dist-server/server.js', 88 | tasks: ['build'] 89 | }); 90 | }); 91 | 92 | /* 93 | Point default task to nodemon 94 | */ 95 | gulp.task('default', ['nodemon']); 96 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | Aurelia Node 8 | 9 | 10 | 11 | 13 |
14 |
Loading...
15 | 16 |
17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia-node", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "./dist-server/server.js", 6 | "author": "", 7 | "license": "ISC", 8 | "dependencies": { 9 | "express": "^4.12.4", 10 | "body-parser": "^1.12.4", 11 | "mongoose": "^4.0.3", 12 | "require-dir": "^0.3.0" 13 | }, 14 | "devDependencies": { 15 | "babel": "^5.8.23", 16 | "del": "^1.1.0", 17 | "gulp": "^3.8.10", 18 | "gulp-babel": "^5.1.0", 19 | "gulp-changed": "^1.1.0", 20 | "gulp-nodemon": "^2.0.3", 21 | "gulp-plumber": "^0.6.6", 22 | "gulp-sourcemaps": "^1.3.0", 23 | "jspm": "^0.16.11", 24 | "object.assign": "^1.0.3", 25 | "run-sequence": "^1.0.2", 26 | "system-npm": "^0.3.2", 27 | "vinyl-paths": "^1.0.0" 28 | }, 29 | "jspm": { 30 | "dependencies": { 31 | "aurelia-animator-css": "github:aurelia/animator-css@^0.16.0", 32 | "aurelia-binding": "github:aurelia/binding@^0.9.1", 33 | "aurelia-bootstrapper": "github:aurelia/bootstrapper@^0.17.0", 34 | "aurelia-dependency-injection": "github:aurelia/dependency-injection@^0.10.1", 35 | "aurelia-event-aggregator": "github:aurelia/event-aggregator@^0.8.0", 36 | "aurelia-fetch-client": "github:aurelia/fetch-client@^0.2.0", 37 | "aurelia-framework": "github:aurelia/framework@^0.16.0", 38 | "aurelia-history": "github:aurelia/history@^0.7.0", 39 | "aurelia-history-browser": "github:aurelia/history-browser@^0.8.0", 40 | "aurelia-html-import-template-loader": "github:aurelia/html-import-template-loader@^0.2.2", 41 | "aurelia-http-client": "github:aurelia/http-client@^0.11.0", 42 | "aurelia-loader": "github:aurelia/loader@^0.9.0", 43 | "aurelia-loader-default": "github:aurelia/loader-default@^0.10.0", 44 | "aurelia-logging": "github:aurelia/logging@^0.7.0", 45 | "aurelia-metadata": "github:aurelia/metadata@^0.8.0", 46 | "aurelia-path": "github:aurelia/path@^0.9.0", 47 | "aurelia-route-recognizer": "github:aurelia/route-recognizer@^0.7.0", 48 | "aurelia-router": "github:aurelia/router@^0.12.0", 49 | "aurelia-task-queue": "github:aurelia/task-queue@^0.7.0", 50 | "aurelia-templating": "github:aurelia/templating@^0.15.3", 51 | "aurelia-templating-binding": "github:aurelia/templating-binding@^0.15.0", 52 | "aurelia-templating-resources": "github:aurelia/templating-resources@^0.15.2", 53 | "aurelia-templating-router": "github:aurelia/templating-router@^0.16.1", 54 | "bootstrap": "github:twbs/bootstrap@^3.3.5", 55 | "fetch": "github:github/fetch@^0.9.0", 56 | "font-awesome": "npm:font-awesome@^4.4.0", 57 | "mutationobservers": "npm:mutationobservers@^0.0.1" 58 | }, 59 | "devDependencies": { 60 | "babel": "npm:babel-core@^5.8.22", 61 | "babel-runtime": "npm:babel-runtime@^5.8.20", 62 | "core-js": "npm:core-js@^1.1.0" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Aurelia On Node 2 | 3 | This project demonstrates using aurelia with nodejs as a host and as a 4 | server for backend webservices. Babel is used for compiling both the 5 | client and server code, so this gives you a full ES6 stack to write with. 6 | 7 | ## Running This 8 | 9 | Dependencies: 10 | 11 | - node / npm 12 | - gulp-cli 13 | - mongo (installed locally with an empty database named "bookcollection") 14 | 15 | At the command line at the project root: 16 | 17 | ``` 18 | npm install 19 | jspm install 20 | gulp 21 | ``` 22 | 23 | And you should be all set. 24 | -------------------------------------------------------------------------------- /src-client/app.html: -------------------------------------------------------------------------------- 1 | 7 | 22 | -------------------------------------------------------------------------------- /src-client/app.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { inject } from 'aurelia-framework'; 3 | import { Router } from 'aurelia-router'; 4 | import bootstrap from 'bootstrap'; 5 | import 'fetch'; 6 | import 'babel/polyfill'; 7 | 8 | /* eslint-enable */ 9 | 10 | /* 11 | This is the view model for our root application view 12 | This view model defines variables for the outer shell of 13 | the application and provides routes for populating and navigating 14 | between child pages rendered in the on the 15 | corresponding view 16 | */ 17 | @inject(Router) 18 | export class App { 19 | 20 | // The constructor takes in the singleton instanced router 21 | // from the framework (specified by the @inject) and configures it 22 | // with the routes that we want for the "pages" in our application 23 | constructor (router) { 24 | this.router = router; 25 | this.router.configure(config => { 26 | config.title = 'Aurelia Node'; 27 | config.map([ 28 | { route : ['', 'home'], moduleId : './pages/home' } 29 | ]); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src-client/lib/web-services.js: -------------------------------------------------------------------------------- 1 | import { inject } from 'aurelia-framework'; 2 | import { HttpClient } from 'aurelia-fetch-client'; 3 | import 'fetch'; 4 | 5 | /* 6 | This is a helper class that wraps aurelia's fetch-client 7 | library to provide some default behavior for our requests 8 | 9 | In this configuration, all requests are sent as HTTP POSTs 10 | and we are always sending and receiving objects that should 11 | be serialized with JSON 12 | */ 13 | @inject(HttpClient) 14 | export class WebServices { 15 | constructor (http) { 16 | this.http = http; 17 | } 18 | 19 | // Serialize an object and send it to the server, and return 20 | // a promise that will deserialize the server response JSON and 21 | // give us back an object 22 | sendRequest (path, request) { 23 | return this.http.fetch( 24 | path, 25 | { 26 | body : JSON.stringify(request) || null, 27 | headers : { 28 | 'Content-Type' : 'application/json', 29 | 'Accept' : 'application/json' 30 | }, 31 | method : 'post', 32 | credentials : 'same-origin' 33 | } 34 | ).then(response => response.json()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src-client/main.js: -------------------------------------------------------------------------------- 1 | import { LogManager } from 'aurelia-framework'; 2 | import { ConsoleAppender } from 'aurelia-logging-console'; 3 | 4 | // Set up log manager defaults 5 | LogManager.addAppender(new ConsoleAppender()); 6 | LogManager.setLevel(LogManager.logLevel.debug); 7 | 8 | // Setup aurelia configuration 9 | export function configure (aurelia) { 10 | aurelia 11 | .use 12 | .defaultBindingLanguage() 13 | .defaultResources() 14 | .router() 15 | .eventAggregator() 16 | .history() 17 | .plugin('aurelia-html-import-template-loader') 18 | .globalResources(); 19 | 20 | // Start aurelia by mounting app.js/app.html as the root 21 | aurelia.start().then(a => a.setRoot('app', document.body)); 22 | } 23 | -------------------------------------------------------------------------------- /src-client/pages/home.html: -------------------------------------------------------------------------------- 1 | 8 | 37 | -------------------------------------------------------------------------------- /src-client/pages/home.js: -------------------------------------------------------------------------------- 1 | import { inject } from 'aurelia-framework'; 2 | import { WebServices } from '../lib/web-services'; 3 | 4 | /* 5 | This is a viewmodel for our simple demonstration 6 | page. This viewmodel defines properties bound to the 7 | home view and provides actions for sending and receiving 8 | book data from the server. 9 | */ 10 | @inject(WebServices) 11 | export class Home { 12 | 13 | // This constructor takes a singleton instance of our 14 | // webservices object. This is injected to ensure that 15 | // we always receive the same copy of this object rather than 16 | // making a new one each time the view is rendered. 17 | constructor (webServices) { 18 | this.webServices = webServices; 19 | 20 | this.books = []; 21 | this.newTitle = ''; 22 | this.newDescription = ''; 23 | } 24 | 25 | // When the view is attached, we should load up our list of books from 26 | // the server 27 | async activate () { 28 | await this.loadBooks(); 29 | } 30 | 31 | // This method requests books from the server and then stores 32 | // the returned list in this viewmodel's "books" property 33 | async loadBooks () { 34 | const result = await this.webServices.sendRequest('/api/books/list'); 35 | 36 | this.books = result.books; 37 | } 38 | 39 | // This method sends a new book to the server to save to the 40 | // database, based on the newTitle and newDescription properties 41 | // provided by this class and bound to the textboxs in the view 42 | async addBook () { 43 | await this.webServices.sendRequest( 44 | '/api/books/save', 45 | { _id : this.newTitle, description : this.newDescription } 46 | ); 47 | await this.loadBooks(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src-server/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import bodyParser from 'body-parser'; 3 | import path from 'path'; 4 | import requireDir from 'require-dir'; 5 | import mongoose from 'mongoose'; 6 | 7 | // Create a basic express application 8 | const app = express(); 9 | 10 | // Set express up to automatically parse incoming JSON requests 11 | // into the request object 12 | app.use(bodyParser.json()); 13 | 14 | // Set up static content paths 15 | app.use('/jspm_packages', express.static(`${__dirname}/../jspm_packages`)); 16 | app.use('/dist-client', express.static(`${__dirname}/../dist-client`)); 17 | app.use('/assets', express.static(`${__dirname}/../assets`)); 18 | 19 | // Any request without a path should render index.html 20 | app.get('/', (req, res) => { 21 | res.sendFile(path.resolve(`${__dirname}/../index.html`)); 22 | }); 23 | 24 | // Any request to /config.js should render the root config.js 25 | app.get('/config.js', (req, res) => { 26 | res.sendFile(path.resolve(`${__dirname}/../config.js`)); 27 | }); 28 | 29 | // Store the app globally for convenience 30 | global.app = app; 31 | 32 | // Include and run all files in the ./services folder 33 | requireDir('services'); 34 | 35 | // Connect to the database server 36 | mongoose.connect('mongodb://localhost:27017/bookcollection'); 37 | 38 | // If there's an error connecting to the database, 39 | // log the error and quit 40 | mongoose.connection.on('error', err => { 41 | console.error(`ERROR CONNECTING TO MONGO: ${err}`); 42 | process.exit(0); 43 | }); 44 | 45 | // Once we have successfully connected... 46 | mongoose.connection.on('connected', () => { 47 | console.log(`Connected to mongo, starting webserver`); 48 | let server = null; 49 | 50 | // Start the web server 51 | server = app.listen(3000, () => { 52 | const host = server.address().address; 53 | const port = server.address().port; 54 | 55 | console.log(`Server listening at http://${host}:${port}`); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src-server/services/books.js: -------------------------------------------------------------------------------- 1 | import Book from './models/books.js'; 2 | import express from 'express'; 3 | 4 | // Define a new subroute for requests that 5 | // start with /api/books 6 | const router = express.Router(); 7 | 8 | global.app.use('/api/books', router); 9 | 10 | 11 | // Handle /api/books/list by querying the database 12 | // for all books and returning all records from the 13 | // server and an error (if there was one) to the client 14 | router.post('/list', (req, res) => { 15 | Book.find((err, books) => { 16 | res.send({ books, err }); 17 | }); 18 | }); 19 | 20 | // Handle /api/books/save by creating a new book 21 | // from the posted body JSON: { _id: 'XXX', description: 'YYY' } 22 | // saving it to the database, and then return any error that 23 | // occurred to the user 24 | router.post('/save', (req, res) => { 25 | const book = new Book(); 26 | 27 | book._id = req.body._id; 28 | book.description = req.body.description; 29 | 30 | book.save(err => { 31 | res.send({ err }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src-server/services/models/books.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | // Define a schema that describes what a book 4 | // looks like in the database 5 | const BookSchema = new mongoose.Schema({ 6 | _id : String, 7 | description : String 8 | }); 9 | 10 | // Create a Book class from the schema and make it 11 | // available to anyone importing this file 12 | module.exports = mongoose.model('Book', BookSchema); 13 | --------------------------------------------------------------------------------