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/.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/.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/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 9c674be..a2ddf50 100644 --- a/bower.json +++ b/bower.json @@ -1,33 +1,47 @@ { - "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": { + "roboto-fontface": { + "main": [ + "./css/roboto/roboto-fontface.css", + "./fonts/Roboto/*" + ] + }, + "mdi": { + "main": [ + "./css/materialdesignicons.css", + "./fonts/*" + ] } + }, + "keywords": [ + "flowr" + ], + "homepage": "https://github.com/wiatrM/FlowR", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "angular": "^1.6.4", + "angular-ui-router": "^1.0.5", + "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": "^2.0.46", + "roboto-fontface": "^0.8.0", + "ngprogress": "^1.1.3" + } } diff --git a/client/src/app/app.js b/client/src/app/app.js index 5cd49ac..2a430a3 100644 --- a/client/src/app/app.js +++ b/client/src/app/app.js @@ -1,38 +1,31 @@ -(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' + + // 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) { - //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) { - - // Now set up the states - $stateProvider - // 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 - }); $urlRouterProvider.otherwise("/404"); $locationProvider.html5Mode(true); // 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 +33,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/home.controller.js b/client/src/app/core/controllers/home.controller.js new file mode 100644 index 0000000..43d149f --- /dev/null +++ b/client/src/app/core/controllers/home.controller.js @@ -0,0 +1,8 @@ +'use strict'; + +angular.module('flr.core') + .controller('homeController', + 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/main.controller.js b/client/src/app/core/controllers/main.controller.js new file mode 100644 index 0000000..c3df1d7 --- /dev/null +++ b/client/src/app/core/controllers/main.controller.js @@ -0,0 +1,13 @@ +'use strict'; + +angular.module('flr.core') + .controller('mainController', + function ($scope, $rootScope) { + + // Remove the splash screen + $scope.$on('$viewContentAnimationEnded', function (event) { + if (event.targetScope.$id === $scope.$id) { + $rootScope.$broadcast('splashScreen::remove'); + } + }); + }); 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/navigation.controller.js b/client/src/app/core/controllers/navigation.controller.js new file mode 100644 index 0000000..b807034 --- /dev/null +++ b/client/src/app/core/controllers/navigation.controller.js @@ -0,0 +1,24 @@ +'use strict'; + +angular.module('flr.core') + .controller('navigationController', + function ($mdSidenav) { + var vm = this; + + var originatorEv; + + vm.openMenu = openMenu; + vm.openSideMenu = openSideMenu; + + function openSideMenu() { + return function () { + $mdSidenav("navigation").toggle(); + }; + } + + function openMenu($mdMenu, ev) { + originatorEv = ev; + $mdMenu.open(ev); + } + + }); 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..262ae2d 100644 --- a/client/src/app/core/core.routes.js +++ b/client/src/app/core/core.routes.js @@ -5,48 +5,120 @@ angular.module('flr.core') .config(['$stateProvider', '$urlRouterProvider', '$locationProvider', 'AccessLevels', 'APP_NAME', function ($stateProvider, $urlRouterProvider, $locationProvider, AccessLevels, AppName) { + // Now set up the states $stateProvider - .state('app.anon', { + // NESTED VIEWS! all states in different modules with app. + .state('app', { abstract: true, // ui view will provide nested-views function - better than ng-view - template: '', + template: '' + }) + // prefix will include this abstract state with layout.html + .state('app.anon', { + abstract: true, data: { // Check with access service access: AccessLevels.anon + }, + views: { + '@': { + templateUrl: 'app/core/views/layout-anon.html', + controller: 'mainController as mVM' + }, + 'navbar@app.anon': { + templateUrl: 'app/core/views/partials/anon.navbar.html', + controller: 'navbarController as navVM' + }, + 'footer@app.anon': { + templateUrl: 'app/core/views/partials/anon.footer.html', + controller: 'footerController as footerVM' + } } }) .state('app.anon.home', { url: '/', - templateUrl: 'app/core/views/test.html', data: { access: AccessLevels.anon, pageTitle: AppName + }, + views: { + 'content@app.anon': { + templateUrl: 'app/core/views/home.html', + controller: 'homeController as homeVm' + } } }) .state('app.anon.login', { url: '/login', - templateUrl: 'app/core/views/login.html', - controller: 'loginController', data: { access: AccessLevels.anon, pageTitle: AppName + }, + views: { + 'content@app.anon': { + templateUrl: 'app/core/views/login.html', + controller: 'loginController as loginVm' + }, + 'navbar@app.anon': {} } }) .state('app.anon.signup', { url: '/signup', - templateUrl: 'app/core/views/signup.html', - controller: 'signupController', data: { access: AccessLevels.anon, pageTitle: AppName + }, + views: { + 'content@app.anon': { + templateUrl: 'app/core/views/signup.html', + controller: 'signupController as signVm' + }, + 'navbar@app.anon': {} } }) .state('app.anon.404', { url: '/404', - templateUrl: 'app/core/views/404.html', data: { access: AccessLevels.anon, pageTitle: '404' + }, + views: { + '@': { + templateUrl: 'app/core/views/404.html', + controller: 'mainController as mVM' + } + } + }) + .state('app.user', { + abstract: true, + data: { + access: AccessLevels.user + }, + views: { + '@': { + templateUrl: 'app/core/views/layout-user.html', + controller: 'mainController as mVM' + }, + 'navbar@app.user': { + templateUrl: 'app/core/views/partials/user.navbar.html', + controller: 'navigationController as navVM' + }, + 'sidebar@app.user': { + templateUrl: 'app/core/views/partials/user.sidebar.html', + controller: 'sidebarController as sideVM' + } + } + }) + .state('app.user.home', { + url: '/test', //temporary + data: { + access: AccessLevels.user, + pageTitle: AppName + }, + views: { + 'content@app.user': { + templateUrl: 'app/core/views/test.html' + } } }); } diff --git a/client/src/app/core/core.run.js b/client/src/app/core/core.run.js new file mode 100644 index 0000000..5135f70 --- /dev/null +++ b/client/src/app/core/core.run.js @@ -0,0 +1,10 @@ +'use strict'; + +angular.module('flr.core') + .run(['$rootScope', '$state', + function ($rootScope, $state) { + + $rootScope.state = $state; + + } + ]); diff --git a/client/src/app/core/core.themes.js b/client/src/app/core/core.themes.js index afd5bdb..154a395 100644 --- a/client/src/app/core/core.themes.js +++ b/client/src/app/core/core.themes.js @@ -1,31 +1,36 @@ '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', - 'contrastDefaultColor': 'light', - 'contrastDarkColors': '50 100 200 A100', - 'contrastStrongLightColors': '300 400 A200 A400' - })); - $mdThemingProvider.definePalette('app-light-blue', $mdThemingProvider.extendPalette('indigo', { - 'A200': '#40C4FF' - })); + /* Default Indigo-Pink theme*/ $mdThemingProvider.theme('default') - .primaryPalette('app-blue') - .accentPalette('app-light-blue'); + .primaryPalette('indigo') + .accentPalette('pink') + .warnPalette('red') + .backgroundPalette('grey'); + + $mdThemingProvider.theme('purple-green') + .primaryPalette('purple') + .accentPalette('green') + .warnPalette('red') + .backgroundPalette('grey'); + + $mdThemingProvider.theme('home') + .primaryPalette('green') + .accentPalette('amber') + .warnPalette('red') + .backgroundPalette('grey'); + + $mdThemingProvider.theme('indigo-blue-grey') + .primaryPalette('indigo') + .accentPalette('blue-grey') + .warnPalette('red') + .backgroundPalette('grey'); + $mdThemingProvider.setDefaultTheme('default'); + $mdThemingProvider.alwaysWatchTheme(true); } ]); diff --git a/client/src/app/core/directives/server-error.directive.js b/client/src/app/core/directives/server-error.directive.js new file mode 100644 index 0000000..13fa888 --- /dev/null +++ b/client/src/app/core/directives/server-error.directive.js @@ -0,0 +1,15 @@ +'use strict'; + +angular.module('flr.core') + .directive('serverError', + function () { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, element, attrs, ctrl) { + element.on('keydown', function () { + ctrl.$setValidity('server', true) + }); + } + } + }); diff --git a/client/src/app/core/directives/serverError.directive.js b/client/src/app/core/directives/serverError.directive.js deleted file mode 100644 index 83c523b..0000000 --- a/client/src/app/core/directives/serverError.directive.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -angular.module('flr.core') - .directive('serverError', function () { - return { - restrict: 'A', - require: 'ngModel', - link: function (scope, element, attrs, ctrl) { - element.on('keydown', function () { - ctrl.$setValidity('server', true) - }); - } - } - }); diff --git a/client/src/app/core/directives/splash-screen.directive.js b/client/src/app/core/directives/splash-screen.directive.js new file mode 100644 index 0000000..69d18e6 --- /dev/null +++ b/client/src/app/core/directives/splash-screen.directive.js @@ -0,0 +1,17 @@ +'use strict'; + +angular.module('flr.core') + .directive('splashScreen', + function ($animate) { + return { + restrict: 'E', + link: function (scope, iElement) { + var splashScreenRemoveEvent = scope.$on('splashScreen::remove', function () { + $animate.leave(iElement).then(function () { + splashScreenRemoveEvent(); + scope = iElement = null; + }); + }); + } + }; + }); diff --git a/client/src/app/core/services/auth.services.js b/client/src/app/core/services/auth.services.js index 2139f3c..9281401 100644 --- a/client/src/app/core/services/auth.services.js +++ b/client/src/app/core/services/auth.services.js @@ -8,4 +8,4 @@ angular.module('flr.core') mode: 2, admn: 3, suad: 4 -}) +}); diff --git a/client/src/app/core/views/404.html b/client/src/app/core/views/404.html index d94af9c..c7bab9b 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/home.html b/client/src/app/core/views/home.html index ad75659..bee64a1 100644 --- a/client/src/app/core/views/home.html +++ b/client/src/app/core/views/home.html @@ -1 +1,9 @@ -
home page
+ +
+

FlowR

+

FlowR it's free, open source project.

+
+
+

Description.

+
+
diff --git a/client/src/app/core/views/layout-anon.html b/client/src/app/core/views/layout-anon.html new file mode 100644 index 0000000..9138436 --- /dev/null +++ b/client/src/app/core/views/layout-anon.html @@ -0,0 +1,14 @@ +
+ +
+
+ + +
+
+ + +
+
+ +
diff --git a/client/src/app/core/views/layout-user.html b/client/src/app/core/views/layout-user.html new file mode 100644 index 0000000..1fd0da8 --- /dev/null +++ b/client/src/app/core/views/layout-user.html @@ -0,0 +1,17 @@ +
+ +
+
+ +
+ +
+
+ + +
+
+ +
+
+ diff --git a/client/src/app/core/views/layout.html b/client/src/app/core/views/layout.html deleted file mode 100644 index e9dbf8b..0000000 --- a/client/src/app/core/views/layout.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - -
- -
- - - - - diff --git a/client/src/app/core/views/login.html b/client/src/app/core/views/login.html index de1ec32..bb36c46 100644 --- a/client/src/app/core/views/login.html +++ b/client/src/app/core/views/login.html @@ -1,65 +1,66 @@ - - + +
+ Log in + +
+ + + + + + + + + + + + + + + + +
+ Don't have an account? + Create an account +
+ + +
diff --git a/client/src/app/core/views/partials/anon.footer.html b/client/src/app/core/views/partials/anon.footer.html new file mode 100644 index 0000000..4c38279 --- /dev/null +++ b/client/src/app/core/views/partials/anon.footer.html @@ -0,0 +1,15 @@ + diff --git a/client/src/app/core/views/partials/anon.navbar.html b/client/src/app/core/views/partials/anon.navbar.html new file mode 100644 index 0000000..05990d2 --- /dev/null +++ b/client/src/app/core/views/partials/anon.navbar.html @@ -0,0 +1,10 @@ + + + + FlowR + +
+ Log in + Sign up +
+
diff --git a/client/src/app/core/views/partials/user.navbar.html b/client/src/app/core/views/partials/user.navbar.html new file mode 100644 index 0000000..785307b --- /dev/null +++ b/client/src/app/core/views/partials/user.navbar.html @@ -0,0 +1,50 @@ + +
+
+ +
+ + + + +
+
+ + + + + + + Notifications + + + + + + + John Doe + + + + + + My Profile + + + + + + Settings + + + + + + + Logout + + + + +
+
diff --git a/client/src/app/core/views/partials/user.sidebar.html b/client/src/app/core/views/partials/user.sidebar.html new file mode 100644 index 0000000..f4fb15a --- /dev/null +++ b/client/src/app/core/views/partials/user.sidebar.html @@ -0,0 +1,13 @@ + + Sidenav + +

+ This sidenav is not showing anything now. +

+ + + + +
+ +
diff --git a/client/src/app/core/views/signup.html b/client/src/app/core/views/signup.html index 95fe318..89b4389 100644 --- a/client/src/app/core/views/signup.html +++ b/client/src/app/core/views/signup.html @@ -1,75 +1,74 @@ - - + + + + + + + + + + + + + + + I read and accept terms and conditions + + +
+ Sign Up + +
+
+ Already have an account? + Log in +
+ + +
diff --git a/client/src/app/core/views/splash-screen.html b/client/src/app/core/views/splash-screen.html new file mode 100644 index 0000000..b4fd605 --- /dev/null +++ b/client/src/app/core/views/splash-screen.html @@ -0,0 +1,70 @@ + +
+
+
+
+
+
+
+ +
+ diff --git a/client/src/app/core/views/test.html b/client/src/app/core/views/test.html index cb26e8d..9274dbd 100644 --- a/client/src/app/core/views/test.html +++ b/client/src/app/core/views/test.html @@ -1 +1,75 @@ -
Hello Angular!
+ + +
+
+
+ + + Welcome back, John Doe! + + + Your last login was 0 days ago. + + +
+
+
+ + + You have 0 task. + + + + + You have 0 notifications. + + + + + You have new 0 messages. + + +
+
+ + + You have completed 17 tasks. + + + + + Fun fact:
+ Cherophobia is the fear of fun. +
+
+ + + Have nice day :) + + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + +
+ +
diff --git a/client/src/css/main.css b/client/src/css/main.css deleted file mode 100644 index fd38e42..0000000 --- a/client/src/css/main.css +++ /dev/null @@ -1,67 +0,0 @@ -html { - height: 100% -} - -.login md-card{ - width: 400px; -} - -.signup md-card{ - width: 400px; -} - -span.or-pad { - padding-top: 10px; - padding-bottom: 10px; -} - -hr.or { - border: 0; - height: 1px; - background-image: -webkit-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); - background-image: -moz-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); - background-image: -ms-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); - background-image: -o-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); -} - -/* Facebook button style*/ -.md-button.md-raised.facebook-btn { - background-color: #2d5f9a; - color: white; - border: 1px black; -} - -.md-button.md-raised.facebook-btn:not([disabled]):hover { - background-color: rgba(45, 95, 154, 0.93); -} - -a.simple-link:link, a.simple-link:visited { - color: rgb(16, 108, 200); - text-decoration: none; -} - -a.simple-link:hover, a.simple-link:active { - color: rgb(16, 108, 200); - text-decoration: underline; -} - -.notfound { - background-color: #f1f1f1; - color: #fff; - font-size: 12em; - font-weight: bold; - font-family: Helvetica; - text-shadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0, 0, 0, .1), 0 0 5px rgba(0, 0, 0, .1), 0 1px 3px rgba(0, 0, 0, .3), 0 3px 5px rgba(0, 0, 0, .2), 0 5px 10px rgba(0, 0, 0, .25), 0 10px 10px rgba(0, 0, 0, .2), 0 20px 20px rgba(0, 0, 0, .15); -} - -/* Hint styles like ng-messages*/ -.hint { - position: absolute; - left: 2px; - right: auto; - bottom: 7px; - font-size: 12px; - line-height: 14px; - transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); - color: grey; -} diff --git a/client/src/favicon.ico b/client/src/favicon.ico new file mode 100644 index 0000000..c0cbdb9 Binary files /dev/null and b/client/src/favicon.ico differ diff --git a/client/src/index.template.html b/client/src/index.template.html index 8160542..88bb642 100644 --- a/client/src/index.template.html +++ b/client/src/index.template.html @@ -3,6 +3,7 @@ FlowR + @@ -11,7 +12,10 @@ -
+ + + +
diff --git a/client/src/less/main.less b/client/src/less/main.less new file mode 100644 index 0000000..d18f96b --- /dev/null +++ b/client/src/less/main.less @@ -0,0 +1,165 @@ +.height-100 { + height: 100vh; +} + +.main-container { + position: relative; + .content-container { + position: relative; + overflow: hidden; + } +} + +/* footer style */ +.footer { + border-top: 1px solid #bbb; +} + +.flowr-header { + font-size: 90px; + font-weight: 500; + text-shadow: 1px 1px 6px #969696; + line-height: 1; + margin-bottom: 2%; + margin-top: 5%; +} + +@sidenav-width: 240px; + +.user-sidenav { + width: @sidenav-width; + min-width: @sidenav-width; + max-width: @sidenav-width; +} + +.user-nav { + .logo-container { + width: @sidenav-width; + padding: 0 16px; + .logo { + min-width: 64px; + line-height: 32px; + border-radius: 2px; + font-size: 16px; + font-weight: 500; + text-align: center; + } + } +} + +/* Sign In (Login) and Sign Up (Register) pages style */ +.sign { + md-card { + width: 400px; + } + + button, md-button { + margin-bottom: 40px; + width: 220px; + } + + .or-pad { + padding-top: 10px; + padding-bottom: 10px; + } + + .or { + border: 0; + height: 1px; + background-image: -webkit-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); + background-image: -moz-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); + background-image: -ms-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); + background-image: -o-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); + } + + .hint { + color: grey; + bottom: 7px; + left: 2px; + right: auto; + font-size: 12px; + line-height: 14px; + position: absolute; + transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); + } + + .flowr { + font-size: 112px; + font-weight: 500; + text-shadow: 2px 2px 6px #969696; + line-height: 1; + margin-bottom: 2%; + margin-top: 5%; + } + + .facebook-btn { + background-color: #2d5f9a; + color: white; + border: 1px black; + + &:not([disabled]) { + &:hover { + background-color: RGBA(45, 95, 154, 0.93); + } + } + } + + a.simple-link { + &:link { + color: rgb(16, 108, 200); + text-decoration: none; + } + + &:visited { + color: rgb(16, 108, 200); + text-decoration: none; + } + + &:hover { + color: rgb(16, 108, 200); + text-decoration: underline; + } + + &:active { + color: rgb(16, 108, 200); + text-decoration: underline; + } + } +} + +/* error page style (404 etc) */ +.error-page { + background-color: #f0f0f0; + color: #000; + text-align: center; + + .content { + width: 90%; + max-width: 512px; + .code { + font-size: 112px; + font-weight: 500; + line-height: 1; + margin-bottom: 3.5%; + } + + .message { + color: rgba(0, 0, 0, 0.54); + font-size: 24px; + } + + .back-link { + color: #5d5a5a; + font-size: 15px; + margin-top: 4.5%; + } + } +} + +.content-only { + top: 0; + position: absolute; + bottom: 0; + right: 0; + left: 0; +} diff --git a/gulpfile.js b/gulpfile.js index fb24370..dca31d5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,6 +7,8 @@ var flatten = require('gulp-flatten'); var gulpFilter = require('gulp-filter'); var uglify = require('gulp-uglify'); var minifycss = require('gulp-minify-css'); +var minhtml = require('gulp-htmlmin'); +var less = require('gulp-less'); var rename = require('gulp-rename'); var concat = require('gulp-concat'); var bower = require('gulp-main-bower-files'); @@ -39,7 +41,7 @@ gulp.task('clean', function () { config.tmpDestination + '/**/*', '!' + config.tmpDestination + '/README.md', config.buildDestination + '/**/*', - '!' + config.buildDestination + '/README.md', + '!' + config.buildDestination + '/README.md' ]); }); @@ -47,7 +49,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 +59,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,19 +98,49 @@ 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); + +}); + +/** + * Task: Copy favicon. + * SAVE THEM TO client/dist + */ +gulp.task('favicon', function () { + return gulp.src(config.srcDestination + '/favicon.ico') + .pipe(gulp.dest(config.buildDestination)); }); /** @@ -130,6 +156,10 @@ gulp.task('app', function () { }); return gulp.src(config.srcDestination + '/app/**/*') .pipe(htmlFilter) + .pipe(config.production ? minhtml({ + collapseWhitespace: true, + removeComments: true + }) : util.noop()) .pipe(gulp.dest(config.buildDestination + '/app', { global: '.' })) @@ -161,7 +191,8 @@ gulp.task('inject', function () { return gulp.src(config.srcDestination + '/index.template.html') // inject bower files - .pipe(inject(gulp.src([config.buildDestination + '/js/**/*', config.buildDestination + '/css/**/*', config.buildDestination + '/fonts/**/*'], { + .pipe(inject( + gulp.src([config.buildDestination + '/js/**/*', config.buildDestination + '/css/**/*'], { read: false }), { name: 'bower', @@ -183,15 +214,33 @@ gulp.task('inject', function () { name: 'app', ignorePath: 'client/dist' })) + + //inject splash screen + .pipe(inject( + gulp.src(config.buildDestination + '/app/core/views/splash-screen.html'), { + starttag: '', + transform: function (filePath, file) { + // return file contents as string + return file.contents.toString('utf8') + } + })) + + // minify html on production + .pipe(config.production ? minhtml({ + collapseWhitespace: true, + removeComments: true + }) : util.noop()) .pipe(rename('index.html')) .pipe(gulp.dest(config.buildDestination)); }); /** - * Task: Copy CSS files. + * Task: Grab all app .less files from project, minify on production only, + * SAVE THEM TO client/dist */ -gulp.task('css', function () { - return gulp.src(config.srcDestination + '/css/**/*.css') +gulp.task('less', function () { + return gulp.src(config.srcDestination + '/less/**/*.less') + .pipe(less()) .pipe(concat('app.css')) .pipe(config.production ? minifycss() : util.noop()) //.pipe(config.production ? gzip() : util.noop()) // for now disabled @@ -202,7 +251,6 @@ gulp.task('css', function () { .pipe(gulp.dest(config.buildDestination + '/style')); }); - /** * Task: Main build task. NOTE: must be run at least ONCE after installation */ @@ -211,7 +259,7 @@ gulp.task('build', function (cb) { runSequence( 'clean', // those can be done async in paraell for speed-up - ['bower', 'app', 'css'], + ['bower', 'app', 'less', 'favicon'], // wait for finish than... 'inject', cb @@ -237,7 +285,7 @@ gulp.task('rebuild:bower', function (cb) { gulp.task('rebuild:app', function (cb) { // run tasks synchronously runSequence( - ['app', 'css'], + ['app', 'less'], cb ) }); @@ -264,7 +312,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 c75a0c6..d7281f8 100644 --- a/package.json +++ b/package.json @@ -1,80 +1,83 @@ { - "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.3.0", + "cldr-data": "^31.0.2", + "compression": "^1.6.2", + "cors": "^2.8.3", + "helmet": "^3.1.0", + "loopback": "^3.5.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", + "underscore": "^1.8.3" + }, + "devDependencies": { + "bower": "^1.8.0", + "chai": "^3.5.0", + "coveralls": "^2.13.0", + "del": "^3.0.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": "^5.0.1", + "gulp-flatten": "^0.3.1", + "gulp-gzip": "^1.4.0", + "gulp-htmlmin": "^3.0.0", + "gulp-inject": "^4.1.0", + "gulp-istanbul": "^1.1.2", + "gulp-jshint": "^2.0.4", + "gulp-less": "^3.3.2", + "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": "^8.0.0", + "gulp-sync": "^0.1.4", + "gulp-uglify": "^3.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.13", + "nsp": "^2.6.2", + "run-sequence": "^2.1.0", + "sinon": "^2.1.0", + "superagent": "^3.3.0", + "supertest": "^3.0.0" + }, + "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/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/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..d371cd2 100644 --- a/server/config.json +++ b/server/config.json @@ -1,25 +1,22 @@ { - "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": false, + "rest": { + "handleErrors": false, + "normalizeHttpPath": false, + "xml": false }, - "legacyExplorer": false + "json": { + "strict": false, + "limit": "100kb" + }, + "urlencoded": { + "extended": true, + "limit": "100kb" + }, + "cors": 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.development.json b/server/middleware.development.json index 2ebcb4a..adde6bc 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": false, + "log": false + } } + } } diff --git a/server/middleware.json b/server/middleware.json index 6df78ec..be2dbc2 100644 --- a/server/middleware.json +++ b/server/middleware.json @@ -1,61 +1,60 @@ { - "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": { + "debug": false, + "log": false, + "includeStack": true + } } + } } diff --git a/server/model-config.json b/server/model-config.json index b005c95..47093bb 100644 --- a/server/model-config.json +++ b/server/model-config.json @@ -1,35 +1,42 @@ { - "_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, + "options": { + "strictObjectIDCoercion": true } + }, + "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..d170ef0 --- /dev/null +++ b/server/models/account.json @@ -0,0 +1,26 @@ +{ + "name": "Account", + "base": "PersistedModel", + "idInjection": true, + "options": { + "validateUpsert": true + }, + "properties": { + "email": { + "type": "string", + "required": true + }, + "createdAt": { + "type": "date", + "required": true + }, + "lastModifiedAt": { + "type": "date", + "required": true + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} 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); -}