From faae2a6b8f7a90119a27fadf5821d6a5caf4373c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20=C5=81apa?= Date: Sun, 18 Dec 2016 16:35:56 +0100 Subject: [PATCH 1/7] update packages, change editorconfig, test model for postgres db, test travis config for postgres --- .editorconfig | 8 ++ .travis.yml | 9 +- bower.json | 62 +++++++------- package.json | 153 +++++++++++++++++----------------- server/bin/automigrate.js | 32 +++++++ server/bin/discover-schema.js | 12 +++ server/component-config.json | 6 +- server/config.json | 44 +++++----- server/datasources.json | 18 +++- server/middleware.json | 113 ++++++++++++------------- server/model-config.json | 70 ++++++++-------- server/models/account.js | 5 ++ server/models/account.json | 23 +++++ 13 files changed, 327 insertions(+), 228 deletions(-) create mode 100644 server/bin/automigrate.js create mode 100644 server/bin/discover-schema.js create mode 100644 server/models/account.js create mode 100644 server/models/account.json diff --git a/.editorconfig b/.editorconfig index 18810d5..af6fb96 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,3 +11,11 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true + +[*.json] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.travis.yml b/.travis.yml index 9d32cd3..afc8909 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,12 @@ language: node_js node_js: - 6 +addons: + postgresql: "9.4" +services: + - postgresql +before_script: + - psql -c 'create database vagrant;' -U postgres + - psql -c "CREATE USER vagrant WITH PASSWORD 'vagrant';" -U postgres after_success: - - "./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js" \ No newline at end of file + - "./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js" diff --git a/bower.json b/bower.json index 9c674be..503a67c 100644 --- a/bower.json +++ b/bower.json @@ -1,33 +1,33 @@ { - "name": "flowr", - "description": "Task Flow, ToDo List, GTD and Time tracker at once.", - "main": "", - "authors": [ - "wiatrM ", - "lapek " - ], - "license": "Apache-2.0", - "overrides": {}, - "keywords": [ - "flowr" - ], - "homepage": "https://github.com/wiatrM/FlowR", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "angular": "1.5.8", - "angular-ui-router": "^0.3.2", - "angular-resource": "^1.5.8", - "angular-animate": "^1.5.8", - "angular-touch": "^1.5.8", - "angular-material": "^1.1.1", - "satellizer": "^0.15.5", - "mdi": "^1.7.22", - "roboto-fontface": "^0.6.0" - } + "name": "flowr", + "description": "Task Flow, ToDo List, GTD and Time tracker at once.", + "main": "", + "authors": [ + "wiatrM ", + "lapek " + ], + "license": "Apache-2.0", + "overrides": {}, + "keywords": [ + "flowr" + ], + "homepage": "https://github.com/wiatrM/FlowR", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "angular": "1.6.0", + "angular-ui-router": "^0.3.2", + "angular-resource": "^1.6.0", + "angular-animate": "^1.6.0", + "angular-touch": "^1.6.0", + "angular-material": "^1.1.1", + "satellizer": "^0.15.5", + "mdi": "^1.7.22", + "roboto-fontface": "^0.6.0" + } } diff --git a/package.json b/package.json index c75a0c6..ba35e22 100644 --- a/package.json +++ b/package.json @@ -1,80 +1,81 @@ { - "name": "FlowR", - "description": "Task Flow, ToDo List, GTD and Time tracker at once.", - "version": "0.0.1", - "main": "server/server.js", - "contributors": [ - { - "name": "wiatrM", - "email": "mwiatr.dev@gmail.com" - }, - { - "name": "lapek", - "email": "" - } - ], - "repository": { - "type": "git", - "url": "https://github.com/wiatrM/FlowR.git" + "name": "FlowR", + "description": "Task Flow, ToDo List, GTD and Time tracker at once.", + "version": "0.0.1", + "main": "server/server.js", + "contributors": [ + { + "name": "wiatrM", + "email": "mwiatr.dev@gmail.com" }, - "license": "Apache-2.0", - "dependencies": { - "async": "^2.0.1", - "cldr-data": "^30.0.1", - "compression": "^1.6.2", - "cors": "^2.7.1", - "helmet": "^3.1.0", - "loopback": "^2.36.0", - "loopback-boot": "^2.23.0", - "loopback-component-explorer": "^2.7.0", - "loopback-connector-postgresql": "^2.7.0", - "loopback-satellizer-integration": "0.0.1", - "serve-favicon": "^2.3.0", - "strong-error-handler": "^1.0.1", - "underscore": "^1.8.3" - }, - "devDependencies": { - "bower": "^1.8.0", - "chai": "^3.5.0", - "coveralls": "^2.11.12", - "del": "^2.2.1", - "eslint-config-loopback": "^4.0.0", - "event-stream": "^3.3.4", - "gulp": "^3.9.1", - "gulp-angular-filesort": "^1.1.1", - "gulp-concat": "^2.6.1", - "gulp-debug": "^3.0.0", - "gulp-filter": "^4.0.0", - "gulp-flatten": "^0.3.0", - "gulp-gzip": "^1.4.0", - "gulp-inject": "^4.1.0", - "gulp-istanbul": "^1.1.1", - "gulp-jshint": "^2.0.3", - "gulp-main-bower-files": "^1.5.2", - "gulp-minify-css": "^1.2.4", - "gulp-mocha": "^3.0.1", - "gulp-rename": "^1.2.2", - "gulp-rev": "^7.1.0", - "gulp-sync": "^0.1.4", - "gulp-uglify": "^2.0.0", - "gulp-util": "^3.0.7", - "istanbul": "^0.4.4", - "jshint": "^2.9.4", - "jshint-stylish": "^2.2.1", - "lcov-result-merger": "^1.2.0", - "mocha": "^3.1.2", - "mocha-lcov-reporter": "^1.2.0", - "nsp": "^2.6.2", - "run-sequence": "^1.2.2", - "sinon": "^1.17.5", - "superagent": "^2.1.0", - "supertest": "^2.0.1" - }, - "scripts": { - "start": "node .", - "posttest": "nsp check", - "test": "gulp test:all", - "cover": "istanbul cover _mocha", - "coveralls": "npm run cover -- --report lcovonly && cat ./coverage/lcov.info | coveralls" + { + "name": "lapek", + "email": "piotrlapa.dt@gmail.com" } + ], + "repository": { + "type": "git", + "url": "https://github.com/wiatrM/FlowR.git" + }, + "license": "Apache-2.0", + "dependencies": { + "async": "^2.1.4", + "cldr-data": "^30.0.2", + "compression": "^1.6.2", + "cors": "^2.8.1", + "helmet": "^3.1.0", + "loopback": "^2.36.0", + "loopback-boot": "^2.23.0", + "loopback-component-explorer": "^2.7.0", + "loopback-connector-postgresql": "^2.7.0", + "loopback-satellizer-integration": "0.0.1", + "serve-favicon": "^2.3.2", + "strong-error-handler": "^1.1.1", + "underscore": "^1.8.3" + }, + "devDependencies": { + "bower": "^1.8.0", + "chai": "^3.5.0", + "coveralls": "^2.11.15", + "del": "^2.2.2", + "eslint-config-loopback": "^6.1.0", + "event-stream": "^3.3.4", + "gulp": "^3.9.1", + "gulp-angular-filesort": "^1.1.1", + "gulp-concat": "^2.6.1", + "gulp-debug": "^3.0.0", + "gulp-filter": "^4.0.0", + "gulp-flatten": "^0.3.1", + "gulp-gzip": "^1.4.0", + "gulp-inject": "^4.1.0", + "gulp-istanbul": "^1.1.1", + "gulp-jshint": "^2.0.4", + "gulp-main-bower-files": "^1.6.0", + "gulp-minify-css": "^1.2.4", + "gulp-mocha": "^3.0.1", + "gulp-rename": "^1.2.2", + "gulp-rev": "^7.1.2", + "gulp-sync": "^0.1.4", + "gulp-uglify": "^2.0.0", + "gulp-util": "^3.0.7", + "istanbul": "^0.4.5", + "jshint": "^2.9.4", + "jshint-stylish": "^2.2.1", + "lcov-result-merger": "^1.2.0", + "mocha": "^3.2.0", + "mocha-lcov-reporter": "^1.2.0", + "nock": "^9.0.2", + "nsp": "^2.6.2", + "run-sequence": "^1.2.2", + "sinon": "^1.17.6", + "superagent": "^3.3.0", + "supertest": "^2.0.1" + }, + "scripts": { + "start": "node .", + "posttest": "nsp check", + "test": "gulp test:all", + "cover": "istanbul cover _mocha", + "coveralls": "npm run cover -- --report lcovonly && cat ./coverage/lcov.info | coveralls" + } } diff --git a/server/bin/automigrate.js b/server/bin/automigrate.js new file mode 100644 index 0000000..53c9205 --- /dev/null +++ b/server/bin/automigrate.js @@ -0,0 +1,32 @@ +var path = require('path'); + +var app = require(path.resolve(__dirname, '../server/')); +var ds = app.datasources.postgres; +ds.automigrate('Account', function(err) { + if (err) throw err; + + var accounts = [ + { + email: 'john.doe@ibm.com', + createdAt: new Date(), + lastModifiedAt: new Date() + }, + { + email: 'jane.doe@ibm.com', + createdAt: new Date(), + lastModifiedAt: new Date() + } + ]; + var count = accounts.length; + accounts.forEach(function(account) { + app.models.Account.create(account, function(err, model) { + if (err) throw err; + + console.log('Created:', model); + + count--; + if (count === 0) + ds.disconnect(); + }); + }); +}); diff --git a/server/bin/discover-schema.js b/server/bin/discover-schema.js new file mode 100644 index 0000000..11b6492 --- /dev/null +++ b/server/bin/discover-schema.js @@ -0,0 +1,12 @@ +var path = require('path'); + +var app = require(path.resolve(__dirname, '../server/')); +var ds = app.datasources.postgres; +ds.discoverSchema('account', {schema: 'public'}, function(err, schema) { + if (err) throw err; + + var json = JSON.stringify(schema, null, ' '); + console.log(json); + + ds.disconnect(); +}); diff --git a/server/component-config.json b/server/component-config.json index 4491fff..f36959a 100644 --- a/server/component-config.json +++ b/server/component-config.json @@ -1,5 +1,5 @@ { - "loopback-component-explorer": { - "mountPath": "/explorer" - } + "loopback-component-explorer": { + "mountPath": "/explorer" + } } diff --git a/server/config.json b/server/config.json index fb9eb5c..0d8ea9c 100644 --- a/server/config.json +++ b/server/config.json @@ -1,25 +1,25 @@ { - "restApiRoot": "/api", - "host": "0.0.0.0", - "port": 3000, - "remoting": { - "context": { - "enableHttpContext": false - }, - "rest": { - "normalizeHttpPath": false, - "xml": false - }, - "json": { - "strict": false, - "limit": "100kb" - }, - "urlencoded": { - "extended": true, - "limit": "100kb" - }, - "cors": false, - "handleErrors": false + "restApiRoot": "/api", + "host": "0.0.0.0", + "port": 3000, + "remoting": { + "context": { + "enableHttpContext": false }, - "legacyExplorer": false + "rest": { + "normalizeHttpPath": false, + "xml": false + }, + "json": { + "strict": false, + "limit": "100kb" + }, + "urlencoded": { + "extended": true, + "limit": "100kb" + }, + "cors": false, + "handleErrors": false + }, + "legacyExplorer": false } diff --git a/server/datasources.json b/server/datasources.json index 9b61457..38e6aa5 100644 --- a/server/datasources.json +++ b/server/datasources.json @@ -1,6 +1,16 @@ { - "db": { - "name": "db", - "connector": "memory" - } + "db": { + "name": "db", + "connector": "memory" + }, + "postgres": { + "host": "/var/run/postgresql/", + "port": 5432, + "database": "vagrant", + "password": "vagrant", + "name": "postgres", + "user": "vagrant", + "debug": true, + "connector": "postgresql" + } } diff --git a/server/middleware.json b/server/middleware.json index 6df78ec..5e9cd3d 100644 --- a/server/middleware.json +++ b/server/middleware.json @@ -1,61 +1,58 @@ { - "initial:before": { - "loopback#favicon": {} - }, - "initial": { - "compression": {}, - "cors": { - "params": { - "origin": true, - "credentials": true, - "maxAge": 86400 - } - }, - "helmet#xssFilter": {}, - "helmet#frameguard": { - "params": [ - "deny" - ] - }, - "helmet#hsts": { - "params": { - "maxAge": 0, - "includeSubdomains": true - } - }, - "helmet#hidePoweredBy": {}, - "helmet#ieNoOpen": {}, - "helmet#noSniff": {}, - "helmet#noCache": { - "enabled": false - } - }, - "session": { - }, - "auth": { - }, - "parse": { - }, - "routes": { - "loopback#rest": { - "paths": [ - "${restApiRoot}" - ] - } - }, - "files": { - "loopback#static": { - "params": "$!../client/dist" - } - }, - "final": { - "loopback#urlNotFound": {} - }, - "final:after": { - "strong-error-handler": { - "params": { - "includeStack": true - } - } + "initial:before": { + "loopback#favicon": {} + }, + "initial": { + "compression": {}, + "cors": { + "params": { + "origin": true, + "credentials": true, + "maxAge": 86400 + } + }, + "helmet#xssFilter": {}, + "helmet#frameguard": { + "params": [ + "deny" + ] + }, + "helmet#hsts": { + "params": { + "maxAge": 0, + "includeSubdomains": true + } + }, + "helmet#hidePoweredBy": {}, + "helmet#ieNoOpen": {}, + "helmet#noSniff": {}, + "helmet#noCache": { + "enabled": false + } + }, + "session": {}, + "auth": {}, + "parse": {}, + "routes": { + "loopback#rest": { + "paths": [ + "${restApiRoot}" + ] + } + }, + "files": { + "loopback#static": { + "params": "$!../client/dist" + } + }, + "final": { + "loopback#urlNotFound": {} + }, + "final:after": { + "strong-error-handler": { + "params": { + "includeStack": true + } } + } } diff --git a/server/model-config.json b/server/model-config.json index b005c95..f31c450 100644 --- a/server/model-config.json +++ b/server/model-config.json @@ -1,35 +1,39 @@ { - "_meta": { - "sources": [ - "loopback/common/models", - "loopback/server/models", - "../common/models", - "./models" - ], - "mixins": [ - "loopback/common/mixins", - "loopback/server/mixins", - "../common/mixins", - "./mixins" - ] - }, - "User": { - "dataSource": "db" - }, - "AccessToken": { - "dataSource": "db", - "public": false - }, - "ACL": { - "dataSource": "db", - "public": false - }, - "RoleMapping": { - "dataSource": "db", - "public": false - }, - "Role": { - "dataSource": "db", - "public": false - } + "_meta": { + "sources": [ + "loopback/common/models", + "loopback/server/models", + "../common/models", + "./models" + ], + "mixins": [ + "loopback/common/mixins", + "loopback/server/mixins", + "../common/mixins", + "./mixins" + ] + }, + "User": { + "dataSource": "db" + }, + "AccessToken": { + "dataSource": "db", + "public": false + }, + "ACL": { + "dataSource": "db", + "public": false + }, + "RoleMapping": { + "dataSource": "db", + "public": false + }, + "Role": { + "dataSource": "db", + "public": false + }, + "Account": { + "dataSource": "postgres", + "public": true + } } diff --git a/server/models/account.js b/server/models/account.js new file mode 100644 index 0000000..699498b --- /dev/null +++ b/server/models/account.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function(Account) { + +}; diff --git a/server/models/account.json b/server/models/account.json new file mode 100644 index 0000000..c4802fe --- /dev/null +++ b/server/models/account.json @@ -0,0 +1,23 @@ +{ + "name": "Account", + "base": "PersistedModel", + "idInjection": true, + "options": { + "validateUpsert": true + }, + "properties": { + "email": { + "type": "string" + }, + "createdAt": { + "type": "date" + }, + "lastModifiedAt": { + "type": "date" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} From 3ad01fcb764aa600bf700ea86556ae6d6b6f9c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20=C5=81apa?= Date: Sun, 26 Mar 2017 16:32:57 +0200 Subject: [PATCH 2/7] update loopback version to 3.5 --- .gitignore | 3 ++ bower.json | 12 +++--- package.json | 18 ++++----- server/boot/authentication.js | 6 ++- server/boot/root.js | 8 ++++ server/config.json | 11 ++---- server/middleware.development.json | 14 +++---- server/middleware.json | 2 +- server/model-config.json | 5 ++- server/server.js | 62 ++++++++++++++---------------- 10 files changed, 75 insertions(+), 66 deletions(-) create mode 100644 server/boot/root.js diff --git a/.gitignore b/.gitignore index 8ac85b4..43934b0 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,9 @@ client/bower_components # Cloud9 editor .c9 +# Visual Studio Code editor +.vscode + # Bower specific .bower-tmp .bower-registry diff --git a/bower.json b/bower.json index 503a67c..ceceae4 100644 --- a/bower.json +++ b/bower.json @@ -20,14 +20,14 @@ "tests" ], "dependencies": { - "angular": "1.6.0", - "angular-ui-router": "^0.3.2", - "angular-resource": "^1.6.0", - "angular-animate": "^1.6.0", - "angular-touch": "^1.6.0", + "angular": "1.6.3", + "angular-ui-router": "^0.4.2", + "angular-resource": "^1.6.3", + "angular-animate": "^1.6.3", + "angular-touch": "^1.6.3", "angular-material": "^1.1.1", "satellizer": "^0.15.5", "mdi": "^1.7.22", - "roboto-fontface": "^0.6.0" + "roboto-fontface": "^0.7.0" } } diff --git a/package.json b/package.json index ba35e22..e5adc0f 100644 --- a/package.json +++ b/package.json @@ -20,17 +20,17 @@ "license": "Apache-2.0", "dependencies": { "async": "^2.1.4", - "cldr-data": "^30.0.2", + "cldr-data": "^31.0.1", "compression": "^1.6.2", "cors": "^2.8.1", "helmet": "^3.1.0", - "loopback": "^2.36.0", + "loopback": "^3.5.0", "loopback-boot": "^2.23.0", - "loopback-component-explorer": "^2.7.0", + "loopback-component-explorer": "^4.1.1", "loopback-connector-postgresql": "^2.7.0", "loopback-satellizer-integration": "0.0.1", "serve-favicon": "^2.3.2", - "strong-error-handler": "^1.1.1", + "strong-error-handler": "^2.0.0", "underscore": "^1.8.3" }, "devDependencies": { @@ -38,13 +38,13 @@ "chai": "^3.5.0", "coveralls": "^2.11.15", "del": "^2.2.2", - "eslint-config-loopback": "^6.1.0", + "eslint-config-loopback": "^8.0.0", "event-stream": "^3.3.4", "gulp": "^3.9.1", "gulp-angular-filesort": "^1.1.1", "gulp-concat": "^2.6.1", "gulp-debug": "^3.0.0", - "gulp-filter": "^4.0.0", + "gulp-filter": "^5.0.0", "gulp-flatten": "^0.3.1", "gulp-gzip": "^1.4.0", "gulp-inject": "^4.1.0", @@ -52,7 +52,7 @@ "gulp-jshint": "^2.0.4", "gulp-main-bower-files": "^1.6.0", "gulp-minify-css": "^1.2.4", - "gulp-mocha": "^3.0.1", + "gulp-mocha": "^4.1.0", "gulp-rename": "^1.2.2", "gulp-rev": "^7.1.2", "gulp-sync": "^0.1.4", @@ -67,9 +67,9 @@ "nock": "^9.0.2", "nsp": "^2.6.2", "run-sequence": "^1.2.2", - "sinon": "^1.17.6", + "sinon": "^2.1.0", "superagent": "^3.3.0", - "supertest": "^2.0.1" + "supertest": "^3.0.0" }, "scripts": { "start": "node .", diff --git a/server/boot/authentication.js b/server/boot/authentication.js index 8219ed9..8e88d4b 100644 --- a/server/boot/authentication.js +++ b/server/boot/authentication.js @@ -1,4 +1,6 @@ +'use strict'; + module.exports = function enableAuthentication(server) { - // enable authentication - server.enableAuth(); + // enable authentication + server.enableAuth(); }; diff --git a/server/boot/root.js b/server/boot/root.js new file mode 100644 index 0000000..6adce90 --- /dev/null +++ b/server/boot/root.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(server) { + // Install a `/` route that returns server status + var router = server.loopback.Router(); + router.get('/', server.loopback.status()); + server.use(router); +}; diff --git a/server/config.json b/server/config.json index 0d8ea9c..d371cd2 100644 --- a/server/config.json +++ b/server/config.json @@ -3,10 +3,9 @@ "host": "0.0.0.0", "port": 3000, "remoting": { - "context": { - "enableHttpContext": false - }, + "context": false, "rest": { + "handleErrors": false, "normalizeHttpPath": false, "xml": false }, @@ -18,8 +17,6 @@ "extended": true, "limit": "100kb" }, - "cors": false, - "handleErrors": false - }, - "legacyExplorer": false + "cors": false + } } diff --git a/server/middleware.development.json b/server/middleware.development.json index 2ebcb4a..071c11a 100644 --- a/server/middleware.development.json +++ b/server/middleware.development.json @@ -1,10 +1,10 @@ { - "final:after": { - "strong-error-handler": { - "params": { - "debug": true, - "log": true - } - } + "final:after": { + "strong-error-handler": { + "params": { + "debug": true, + "log": true + } } + } } diff --git a/server/middleware.json b/server/middleware.json index 5e9cd3d..b147c37 100644 --- a/server/middleware.json +++ b/server/middleware.json @@ -15,7 +15,7 @@ "helmet#frameguard": { "params": [ "deny" - ] + ] }, "helmet#hsts": { "params": { diff --git a/server/model-config.json b/server/model-config.json index f31c450..47093bb 100644 --- a/server/model-config.json +++ b/server/model-config.json @@ -26,7 +26,10 @@ }, "RoleMapping": { "dataSource": "db", - "public": false + "public": false, + "options": { + "strictObjectIDCoercion": true + } }, "Role": { "dataSource": "db", diff --git a/server/server.js b/server/server.js index 57af255..3ef041c 100644 --- a/server/server.js +++ b/server/server.js @@ -1,49 +1,45 @@ +'use strict'; + var loopback = require('loopback'); var boot = require('loopback-boot'); var http = require('http'); var app = module.exports = loopback(); +app.start = function (done) { + // start the web server + return app.listen(function () { + app.emit('started'); + var baseUrl = app.get('url').replace(/\/$/, ''); + console.log('Web server listening at: %s', baseUrl); + if (app.get('loopback-component-explorer')) { + var explorerPath = app.get('loopback-component-explorer').mountPath; + console.log('Browse your REST API at %s%s', baseUrl, explorerPath); + } + done(); + }); +}; + +app.close = function (cb) { + app.removeAllListeners('started'); + app.removeAllListeners('loaded'); + console.log('Server closed'); + cb(); +}; + + +// Bootstrap the application, configure models, datasources and middleware. +// Sub-apps like REST API are mounted via boot scripts. boot(app, __dirname, function (err) { if (err) throw err; - var isMain = require.main === module; - app.start = function (done) { - var port = app.get('port'); - var host = app.get('host'); - var httpServer = http.createServer(app).listen(port, host, function () { - if (isMain) - printServerListeningMsg('http', host, port); - app.emit('started'); - - if (app.get('loopback-component-explorer')) { - var baseUrl = host.replace(/\/$/, ''); - var explorerPath = app.get('loopback-component-explorer').mountPath; - console.log('Browse your REST API at http://%s%s', baseUrl, explorerPath); - } - - app.close = function (cb) { - app.removeAllListeners('started'); - app.removeAllListeners('loaded'); - httpServer.close(function () { - console.log('Server closed'); - cb(); - }); - }; - done(); - }); - }; - - if (isMain) + // start the server if `$ node server.js` + if (require.main === module) { app.start(function () { console.log('Server started!') }); + } app.loaded = true; app.emit('loaded'); }); - -function printServerListeningMsg(protocol, host, port) { - var url = protocol + '://' + host + ':' + port; - console.log('Web server listening at', url); -} From 3b1e01cf5add48ca49d6d68a13ae58f945a57a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20=C5=81apa?= Date: Sat, 15 Apr 2017 12:34:26 +0200 Subject: [PATCH 3/7] Fix 0% coverage: gulp-mocha 4.x.x not supported by gulp-istanbul. Fix vagrant up fail in 1.9.3. --- Vagrantfile.windows | 8 ++++---- bower.json | 10 +++++----- gulpfile.js | 9 +++++++-- package.json | 18 ++++++++--------- server/bin/automigrate.js | 32 ------------------------------ server/bin/discover-schema.js | 12 ----------- server/middleware.development.json | 4 ++-- server/middleware.json | 4 +++- server/models/account.json | 9 ++++++--- 9 files changed, 36 insertions(+), 70 deletions(-) delete mode 100644 server/bin/automigrate.js delete mode 100644 server/bin/discover-schema.js diff --git a/Vagrantfile.windows b/Vagrantfile.windows index 7393197..360172b 100644 --- a/Vagrantfile.windows +++ b/Vagrantfile.windows @@ -21,13 +21,13 @@ Vagrant.configure("2") do |config| end # Add port-forward for Express app - config.vm.network :forwarded_port, guest: 3000, host: 3000 + config.vm.network :forwarded_port, guest: 3000, host: 3000, host_ip: "127.0.0.1" # Add port-forward for Livereload - config.vm.network :forwarded_port, guest: 35729, host: 35729 + config.vm.network :forwarded_port, guest: 35729, host: 35729, host_ip: "127.0.0.1" # Add port-forward for PostgreSQL - config.vm.network :forwarded_port, guest: 5432, host: 5432 + config.vm.network :forwarded_port, guest: 5432, host: 5433, host_ip: "127.0.0.1" # Add port-forward for NGINX - config.vm.network :forwarded_port, guest: 80, host: 80, auto_correct: true + config.vm.network :forwarded_port, guest: 80, host: 80, host_ip: "127.0.0.1", auto_correct: true config.ssh.forward_agent = true diff --git a/bower.json b/bower.json index ceceae4..764bf67 100644 --- a/bower.json +++ b/bower.json @@ -20,12 +20,12 @@ "tests" ], "dependencies": { - "angular": "1.6.3", + "angular": "^1.6.4", "angular-ui-router": "^0.4.2", - "angular-resource": "^1.6.3", - "angular-animate": "^1.6.3", - "angular-touch": "^1.6.3", - "angular-material": "^1.1.1", + "angular-resource": "^1.6.4", + "angular-animate": "^1.6.4", + "angular-touch": "^1.6.4", + "angular-material": "^1.1.3", "satellizer": "^0.15.5", "mdi": "^1.7.22", "roboto-fontface": "^0.7.0" diff --git a/gulpfile.js b/gulpfile.js index fb24370..0156b6a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -39,7 +39,7 @@ gulp.task('clean', function () { config.tmpDestination + '/**/*', '!' + config.tmpDestination + '/README.md', config.buildDestination + '/**/*', - '!' + config.buildDestination + '/README.md', + '!' + config.buildDestination + '/README.md' ]); }); @@ -264,7 +264,12 @@ gulp.task('test:api', function () { reportOpts: { dir: './coverage/api-test-coverage' } - })); + })) + .once('error', function () { + process.exit(1); + }).once('end', function () { + process.exit(); + }); }); }); diff --git a/package.json b/package.json index e5adc0f..7fcae7e 100644 --- a/package.json +++ b/package.json @@ -19,15 +19,15 @@ }, "license": "Apache-2.0", "dependencies": { - "async": "^2.1.4", - "cldr-data": "^31.0.1", + "async": "^2.3.0", + "cldr-data": "^31.0.2", "compression": "^1.6.2", - "cors": "^2.8.1", + "cors": "^2.8.3", "helmet": "^3.1.0", "loopback": "^3.5.0", - "loopback-boot": "^2.23.0", - "loopback-component-explorer": "^4.1.1", - "loopback-connector-postgresql": "^2.7.0", + "loopback-boot": "^2.24.0", + "loopback-component-explorer": "^4.2.0", + "loopback-connector-postgresql": "^3.0.0", "loopback-satellizer-integration": "0.0.1", "serve-favicon": "^2.3.2", "strong-error-handler": "^2.0.0", @@ -36,7 +36,7 @@ "devDependencies": { "bower": "^1.8.0", "chai": "^3.5.0", - "coveralls": "^2.11.15", + "coveralls": "^2.13.0", "del": "^2.2.2", "eslint-config-loopback": "^8.0.0", "event-stream": "^3.3.4", @@ -52,7 +52,7 @@ "gulp-jshint": "^2.0.4", "gulp-main-bower-files": "^1.6.0", "gulp-minify-css": "^1.2.4", - "gulp-mocha": "^4.1.0", + "gulp-mocha": "3.0.1", "gulp-rename": "^1.2.2", "gulp-rev": "^7.1.2", "gulp-sync": "^0.1.4", @@ -64,7 +64,7 @@ "lcov-result-merger": "^1.2.0", "mocha": "^3.2.0", "mocha-lcov-reporter": "^1.2.0", - "nock": "^9.0.2", + "nock": "^9.0.13", "nsp": "^2.6.2", "run-sequence": "^1.2.2", "sinon": "^2.1.0", diff --git a/server/bin/automigrate.js b/server/bin/automigrate.js deleted file mode 100644 index 53c9205..0000000 --- a/server/bin/automigrate.js +++ /dev/null @@ -1,32 +0,0 @@ -var path = require('path'); - -var app = require(path.resolve(__dirname, '../server/')); -var ds = app.datasources.postgres; -ds.automigrate('Account', function(err) { - if (err) throw err; - - var accounts = [ - { - email: 'john.doe@ibm.com', - createdAt: new Date(), - lastModifiedAt: new Date() - }, - { - email: 'jane.doe@ibm.com', - createdAt: new Date(), - lastModifiedAt: new Date() - } - ]; - var count = accounts.length; - accounts.forEach(function(account) { - app.models.Account.create(account, function(err, model) { - if (err) throw err; - - console.log('Created:', model); - - count--; - if (count === 0) - ds.disconnect(); - }); - }); -}); diff --git a/server/bin/discover-schema.js b/server/bin/discover-schema.js deleted file mode 100644 index 11b6492..0000000 --- a/server/bin/discover-schema.js +++ /dev/null @@ -1,12 +0,0 @@ -var path = require('path'); - -var app = require(path.resolve(__dirname, '../server/')); -var ds = app.datasources.postgres; -ds.discoverSchema('account', {schema: 'public'}, function(err, schema) { - if (err) throw err; - - var json = JSON.stringify(schema, null, ' '); - console.log(json); - - ds.disconnect(); -}); diff --git a/server/middleware.development.json b/server/middleware.development.json index 071c11a..adde6bc 100644 --- a/server/middleware.development.json +++ b/server/middleware.development.json @@ -2,8 +2,8 @@ "final:after": { "strong-error-handler": { "params": { - "debug": true, - "log": true + "debug": false, + "log": false } } } diff --git a/server/middleware.json b/server/middleware.json index b147c37..be2dbc2 100644 --- a/server/middleware.json +++ b/server/middleware.json @@ -15,7 +15,7 @@ "helmet#frameguard": { "params": [ "deny" - ] + ] }, "helmet#hsts": { "params": { @@ -51,6 +51,8 @@ "final:after": { "strong-error-handler": { "params": { + "debug": false, + "log": false, "includeStack": true } } diff --git a/server/models/account.json b/server/models/account.json index c4802fe..d170ef0 100644 --- a/server/models/account.json +++ b/server/models/account.json @@ -7,13 +7,16 @@ }, "properties": { "email": { - "type": "string" + "type": "string", + "required": true }, "createdAt": { - "type": "date" + "type": "date", + "required": true }, "lastModifiedAt": { - "type": "date" + "type": "date", + "required": true } }, "validations": [], From 30e120b3469a1a202ae5bccab35e6cee149630f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20=C5=81apa?= Date: Mon, 17 Apr 2017 15:48:09 +0200 Subject: [PATCH 4/7] Fix for missing fonts --- bower.json | 15 ++++++++++++++- gulpfile.js | 38 ++++++++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/bower.json b/bower.json index 764bf67..4c7f7ad 100644 --- a/bower.json +++ b/bower.json @@ -7,7 +7,20 @@ "lapek " ], "license": "Apache-2.0", - "overrides": {}, + "overrides": { + "roboto-fontface": { + "main": [ + "./css/roboto/roboto-fontface.css", + "./fonts/Roboto/*" + ] + }, + "mdi": { + "main": [ + "./css/materialdesignicons.css", + "./fonts/*" + ] + } + }, "keywords": [ "flowr" ], diff --git a/gulpfile.js b/gulpfile.js index 0156b6a..8c95b67 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -47,7 +47,7 @@ gulp.task('clean', function () { * Task: Grab all main vendor files from bower dep list, concat, rename, minify/uglify on production only, * SAVE THEM TO client/dist */ -gulp.task('bower', function () { +gulp.task('bower', ['fonts'], function () { var jsFilter = gulpFilter('**/*.js', { restore: true }); @@ -57,12 +57,6 @@ gulp.task('bower', function () { var lessFilter = gulpFilter('**/*.less', { restore: true }); - var robotoFontsFilter = gulpFilter('**/roboto-fontface/**/*.{eot,svg,ttf,woff,woff2}', { - restore: true - }); - var fontsFilter = gulpFilter(['**/*.{eot,svg,ttf,woff,woff2}', '!**/roboto-fontface/**/*.{eot,svg,ttf,woff,woff2}'], { - restore: true - }); // get all main bower files list return gulp.src('./bower.json') @@ -102,18 +96,38 @@ gulp.task('bower', function () { .pipe(lessFilter) .pipe(concat('vendor.less')) .pipe(gulp.dest(config.buildDestination + '/less')) - .pipe(lessFilter.restore) + .pipe(lessFilter.restore); + +}); + +/** + * Task: Grab specific fonts files from bower dep list + * SAVE THEM TO client/dist + */ +gulp.task('fonts', function () { + var robotoFontsFilter = gulpFilter('**/roboto-fontface/**/*.{eot,svg,ttf,woff,woff2}', { + restore: true + }); + var mdiFontsFilter = gulpFilter('**/mdi/**/*.{eot,svg,ttf,woff,woff2}', { + restore: true + }); + + // get all main bower files list + return gulp.src('./bower.json') + .pipe(bower()) + .pipe(debug()) // roboto fonts .pipe(robotoFontsFilter) .pipe(flatten({includeParents: -1})) .pipe(gulp.dest(config.buildDestination + '/fonts')) .pipe(robotoFontsFilter.restore) - // others fonts - .pipe(fontsFilter) + + // mdi fonts + .pipe(mdiFontsFilter) .pipe(flatten()) - .pipe(gulp.dest(config.buildDestination + '/fonts'))// bug: still include /roboto-fontface/ - .pipe(fontsFilter.restore); + .pipe(gulp.dest(config.buildDestination + '/fonts')) + .pipe(mdiFontsFilter.restore); }); From 432e3d8d46b73763f8cf6750603d7a5af43e3d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20=C5=81apa?= Date: Mon, 22 May 2017 20:02:10 +0200 Subject: [PATCH 5/7] Migrate UI-Router to 1+; Add simple layout sketch --- bower.json | 5 +- client/src/app/app.js | 65 +++++++------ .../app/core/controllers/footer.controller.js | 8 ++ .../app/core/controllers/login.controller.js | 1 + .../app/core/controllers/navbar.controller.js | 8 ++ .../core/controllers/sidebar.controller.js | 8 ++ client/src/app/core/core.routes.js | 47 +++++++-- client/src/app/core/core.themes.js | 92 ++++++++++++++---- client/src/app/core/views/404.html | 16 ++- client/src/app/core/views/layout.html | 37 ++++--- client/src/app/core/views/login.html | 2 +- .../app/core/views/partials/anon.footer.html | 4 + .../app/core/views/partials/anon.navbar.html | 9 ++ .../app/core/views/partials/anon.sidebar.html | 11 +++ client/src/app/core/views/signup.html | 2 +- client/src/css/main.css | 60 +++++++++--- client/src/favicon.ico | Bin 0 -> 1150 bytes client/src/index.template.html | 3 +- gulpfile.js | 16 ++- 19 files changed, 297 insertions(+), 97 deletions(-) create mode 100644 client/src/app/core/controllers/footer.controller.js create mode 100644 client/src/app/core/controllers/navbar.controller.js create mode 100644 client/src/app/core/controllers/sidebar.controller.js create mode 100644 client/src/app/core/views/partials/anon.footer.html create mode 100644 client/src/app/core/views/partials/anon.navbar.html create mode 100644 client/src/app/core/views/partials/anon.sidebar.html create mode 100644 client/src/favicon.ico diff --git a/bower.json b/bower.json index 4c7f7ad..50b22f6 100644 --- a/bower.json +++ b/bower.json @@ -34,13 +34,14 @@ ], "dependencies": { "angular": "^1.6.4", - "angular-ui-router": "^0.4.2", + "angular-ui-router": "^1.0.3", "angular-resource": "^1.6.4", "angular-animate": "^1.6.4", "angular-touch": "^1.6.4", "angular-material": "^1.1.3", "satellizer": "^0.15.5", "mdi": "^1.7.22", - "roboto-fontface": "^0.7.0" + "roboto-fontface": "^0.7.0", + "ngprogress": "^1.1.3" } } diff --git a/client/src/app/app.js b/client/src/app/app.js index 5cd49ac..0682e29 100644 --- a/client/src/app/app.js +++ b/client/src/app/app.js @@ -1,26 +1,27 @@ -(function() { +(function () { 'use strict'; angular.module('flr', [ - 'ui.router', - 'ngResource', - 'ngAnimate', - 'ngMessages', - 'ngMaterial', - 'satellizer', + 'ui.router', + 'ngResource', + 'ngAnimate', + 'ngMessages', + 'ngMaterial', + 'satellizer', + 'ngProgress', - //shared modules - 'flr.config', - 'flr.core' + //shared modules + 'flr.config', + 'flr.core' - // other modules will be lazy-loaded from router (fe. flr.admin if user is admin) - ]) - .config(['$stateProvider', '$urlRouterProvider', '$locationProvider', '$provide', '$httpProvider', '$authProvider', - function($stateProvider, $urlRouterProvider, $locationProvider, $provide, $httpProvider, $authProvider) { + // other modules will be lazy-loaded from router (fe. flr.admin if user is admin) + ]) + .config(['$stateProvider', '$urlRouterProvider', '$locationProvider', '$provide', '$httpProvider', '$authProvider', + function ($stateProvider, $urlRouterProvider, $locationProvider, $provide, $httpProvider, $authProvider) { // Now set up the states $stateProvider - // NESTED VIEWS! all states in different modules with app. prefix will include this abstract state with layout.html + // NESTED VIEWS! all states in different modules with app. prefix will include this abstract state with layout.html .state('app', { abstract: true, templateUrl: '/app/core/views/layout.html' // layout template @@ -30,9 +31,9 @@ // error handling - $provide.factory('ErrorInterceptor', ['$q', function($q) { + $provide.factory('ErrorInterceptor', ['$q', function ($q) { return { - responseError: function(rejection) { + responseError: function (rejection) { console.error(rejection.data.code + ': ' + rejection.data.message); return $q.reject(rejection); } @@ -40,27 +41,27 @@ }]); $httpProvider.interceptors.push('ErrorInterceptor'); - + // Satellizer config $authProvider.baseUrl = '/'; - $authProvider.loginUrl = 'api/Users/login'; + $authProvider.loginUrl = 'api/Users/login'; $authProvider.signupUrl = 'api/Users'; $authProvider.tokenName = 'id'; $authProvider.loginOnSignup = false; //turn off automatically login after register - // // Facebook - // $authProvider.facebook({ - // name: 'facebook', - // url: '/auth/facebook', - // authorizationEndpoint: 'https://www.facebook.com/v2.5/dialog/oauth', - // redirectUri: window.location.origin + '/', - // requiredUrlParams: ['display', 'scope'], - // scope: ['email'], - // scopeDelimiter: ',', - // display: 'popup', - // oauthType: '2.0', - // popupOptions: { width: 580, height: 400 } - // }); + /* // Facebook + $authProvider.facebook({ + name: 'facebook', + url: '/auth/facebook', + authorizationEndpoint: 'https://www.facebook.com/v2.5/dialog/oauth', + redirectUri: window.location.origin + '/', + requiredUrlParams: ['display', 'scope'], + scope: ['email'], + scopeDelimiter: ',', + display: 'popup', + oauthType: '2.0', + popupOptions: { width: 580, height: 400 } + }); */ } ]); })(); diff --git a/client/src/app/core/controllers/footer.controller.js b/client/src/app/core/controllers/footer.controller.js new file mode 100644 index 0000000..c4585c5 --- /dev/null +++ b/client/src/app/core/controllers/footer.controller.js @@ -0,0 +1,8 @@ +'use strict'; + +angular.module('flr.core') + .controller('footerController', + function () { + var vm = this; + + }); diff --git a/client/src/app/core/controllers/login.controller.js b/client/src/app/core/controllers/login.controller.js index f8f3992..6e85ca5 100644 --- a/client/src/app/core/controllers/login.controller.js +++ b/client/src/app/core/controllers/login.controller.js @@ -28,6 +28,7 @@ angular.module('flr.core') }) .catch(function (response) { vm.errorMessage = 'Invalid email or password.'; + vm.user.password = ''; console.log("error: ", response.data.error.message); }); } diff --git a/client/src/app/core/controllers/navbar.controller.js b/client/src/app/core/controllers/navbar.controller.js new file mode 100644 index 0000000..09289e6 --- /dev/null +++ b/client/src/app/core/controllers/navbar.controller.js @@ -0,0 +1,8 @@ +'use strict'; + +angular.module('flr.core') + .controller('navbarController', + function () { + var vm = this; + + }); diff --git a/client/src/app/core/controllers/sidebar.controller.js b/client/src/app/core/controllers/sidebar.controller.js new file mode 100644 index 0000000..2d57cca --- /dev/null +++ b/client/src/app/core/controllers/sidebar.controller.js @@ -0,0 +1,8 @@ +'use strict'; + +angular.module('flr.core') + .controller('sidebarController', + function () { + var vm = this; + + }); diff --git a/client/src/app/core/core.routes.js b/client/src/app/core/core.routes.js index b510fda..4b05f0d 100644 --- a/client/src/app/core/core.routes.js +++ b/client/src/app/core/core.routes.js @@ -9,44 +9,77 @@ angular.module('flr.core') .state('app.anon', { abstract: true, // ui view will provide nested-views function - better than ng-view - template: '', data: { // Check with access service access: AccessLevels.anon + }, + views: { + 'main@': { + templateUrl: 'app/core/views/layout.html' + }, + 'navbar@app': { + templateUrl: 'app/core/views/partials/anon.navbar.html', + controller: 'navbarController' + }, + 'sidebar@app': { + templateUrl: 'app/core/views/partials/anon.sidebar.html', + controller: 'sidebarController' + }, + 'footer@app': { + templateUrl: 'app/core/views/partials/anon.footer.html', + controller: 'footerController' + } } }) .state('app.anon.home', { url: '/', - templateUrl: 'app/core/views/test.html', data: { access: AccessLevels.anon, pageTitle: AppName + }, + views: { + 'content@app': { + templateUrl: 'app/core/views/test.html' + } } }) .state('app.anon.login', { url: '/login', - templateUrl: 'app/core/views/login.html', - controller: 'loginController', data: { access: AccessLevels.anon, pageTitle: AppName + }, + views: { + 'content@app': { + templateUrl: 'app/core/views/login.html', + controller: 'loginController as loginVm' + } } }) .state('app.anon.signup', { url: '/signup', - templateUrl: 'app/core/views/signup.html', - controller: 'signupController', data: { access: AccessLevels.anon, pageTitle: AppName + }, + views: { + 'content@app': { + templateUrl: 'app/core/views/signup.html', + controller: 'signupController as signVm' + } } }) .state('app.anon.404', { url: '/404', - templateUrl: 'app/core/views/404.html', + preload: true, data: { access: AccessLevels.anon, pageTitle: '404' + }, + views: { + 'content@app': { + templateUrl: 'app/core/views/404.html' + } } }); } diff --git a/client/src/app/core/core.themes.js b/client/src/app/core/core.themes.js index afd5bdb..23003df 100644 --- a/client/src/app/core/core.themes.js +++ b/client/src/app/core/core.themes.js @@ -1,31 +1,85 @@ 'use strict'; angular.module('flr.core') - + .config(['$mdThemingProvider', function ($mdThemingProvider) { - $mdThemingProvider.definePalette('app-blue', $mdThemingProvider.extendPalette('blue', { - '50': '#DCEFFF', - '100': '#AAD1F9', - '200': '#7BB8F5', - '300': '#4C9EF1', - '400': '#1C85ED', - '500': '#106CC8', - '600': '#0159A2', - '700': '#025EE9', - '800': '#014AB6', - '900': '#013583', + $mdThemingProvider.definePalette('app-blue', { + '50': 'e8eaf6', + '100': 'c5cbe9', + '200': '9fa8da', + '300': '7985cb', + '400': '5c6bc0', + '500': '3f51b5', + '600': '394aae', + '700': '3140a5', + '800': '29379d', + '900': '1b278d', + 'A100': 'c6cbff', + 'A200': '939dff', + 'A400': '606eff', + 'A700': '4757ff', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': [ + '50', + '100', + '200', + '300', + 'A100', + 'A200' + ], + 'contrastLightColors': [ + '400', + '500', + '600', + '700', + '800', + '900', + 'A400', + 'A700' + ] + }); + + $mdThemingProvider.definePalette('app-blueGrey', { + '50': 'e9ebec', + '100': 'c7ced1', + '200': 'a2adb2', + '300': '7d8c93', + '400': '61737b', + '500': '455a64', + '600': '3e525c', + '700': '364852', + '800': '2e3f48', + '900': '1f2e36', + 'A100': '7dcdff', + 'A200': '4abaff', + 'A400': '17a7ff', + 'A700': '009cfc', 'contrastDefaultColor': 'light', - 'contrastDarkColors': '50 100 200 A100', - 'contrastStrongLightColors': '300 400 A200 A400' - })); - $mdThemingProvider.definePalette('app-light-blue', $mdThemingProvider.extendPalette('indigo', { - 'A200': '#40C4FF' - })); + 'contrastDarkColors': [ + '50', + '100', + '200', + '300', + 'A100', + 'A200', + 'A400' + ], + 'contrastLightColors': [ + '400', + '500', + '600', + '700', + '800', + '900', + 'A700' + ] + }); + $mdThemingProvider.theme('default') .primaryPalette('app-blue') - .accentPalette('app-light-blue'); + .accentPalette('app-blueGrey'); } ]); diff --git a/client/src/app/core/views/404.html b/client/src/app/core/views/404.html index d94af9c..53df472 100644 --- a/client/src/app/core/views/404.html +++ b/client/src/app/core/views/404.html @@ -1,3 +1,13 @@ -
-

404

-
+ +
+
+
+ 404 +
+
+ Sorry but we couldn’t find the page you are looking for +
+ back to home +
+
+
diff --git a/client/src/app/core/views/layout.html b/client/src/app/core/views/layout.html index e9dbf8b..3c3aec8 100644 --- a/client/src/app/core/views/layout.html +++ b/client/src/app/core/views/layout.html @@ -1,17 +1,22 @@ - - - - - - - - - -
- +
+ + + + +
+
+ + + + + + + + +
+ + + + +
- - - - - diff --git a/client/src/app/core/views/login.html b/client/src/app/core/views/login.html index de1ec32..fb80665 100644 --- a/client/src/app/core/views/login.html +++ b/client/src/app/core/views/login.html @@ -1,4 +1,4 @@ - +