diff --git a/package.json b/package.json index 1de5e58e6..824132c32 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "babel-plugin-transform-flow-strip-types": "^6.18.0", "bluebird": "^3.4.5", "chalk": "^1.1.0", - "generator-ng-component": "~1.0.5", "glob": "^7.0.5", "gulp-babel": "^6.1.2", "gulp-beautify": "^2.0.0", @@ -58,6 +57,8 @@ "yeoman-welcome": "^1.0.1" }, "devDependencies": { + "babel-plugin-syntax-decorators": "^6.13.0", + "babel-plugin-syntax-export-extensions": "^6.13.0", "babel-plugin-transform-class-properties": "^6.19.0", "babel-preset-es2015": "^6.18.0", "chai": "^3.5.0", diff --git a/src/generators/app/index.js b/src/generators/app/index.js index d66e6f9c6..a28f5336c 100644 --- a/src/generators/app/index.js +++ b/src/generators/app/index.js @@ -5,10 +5,10 @@ import path from 'path'; import Promise from 'bluebird'; import { runCmd } from '../util'; import chalk from 'chalk'; -import {Base} from 'yeoman-generator'; -import {genBase} from '../generator-base'; +import { Base } from 'yeoman-generator'; +import { genBase } from '../generator-base'; import insight from '../insight-init'; -import {exec} from 'child_process'; +import { exec } from 'child_process'; import babelStream from 'gulp-babel'; import beaufityStream from 'gulp-beautify'; import tap from 'gulp-tap'; @@ -479,7 +479,9 @@ export class Generator extends Base { let babelPlugins = [ 'babel-plugin-syntax-flow', - 'babel-plugin-syntax-class-properties' + 'babel-plugin-syntax-class-properties', + 'babel-plugin-syntax-decorators', + 'babel-plugin-syntax-export-extensions', ]; if(this.filters.babel && !flow) { @@ -505,27 +507,6 @@ export class Generator extends Base { }, babelrc: false // don't grab the generator's `.babelrc` }), - beaufityStream({ - "indent_size": 2, - "indent_char": " ", - "indent_level": 0, - "indent_with_tabs": false, - "preserve_newlines": true, - "max_preserve_newlines": 10, - "jslint_happy": false, - "space_after_anon_function": false, - "brace_style": "collapse", - "keep_array_indentation": false, - "keep_function_indentation": false, - "space_before_conditional": true, - "break_chained_methods": true, - "eval_code": false, - "unescape_strings": false, - "wrap_line_length": 100, - "wrap_attributes": "auto", - "wrap_attributes_indent_size": 4, - "end_with_newline": true - }), eslint({ fix: true, configFile: path.join(genDir, 'templates/app/client/.eslintrc(babel)') @@ -538,14 +519,6 @@ export class Generator extends Base { */ if(this.filters.ts) { const modulesToFix = [ - ['angular', 'angular'], - ['ngCookies', 'angular-cookies'], - ['ngResource', 'angular-resource'], - ['ngSanitize', 'angular-sanitize'], - ['uiRouter', 'angular-ui-router'], - ['ngRoute', 'angular-route'], - ['uiBootstrap', 'angular-ui-bootstrap'], - ['ngMessages', 'angular-messages'], ['io', 'socket.io-client'] ]; function replacer(contents) { @@ -580,7 +553,6 @@ export class Generator extends Base { serverJsFilter.restore ]); - let self = this; this.sourceRoot(path.join(__dirname, '../../templates/app')); this.processDirectory('.', '.'); }, diff --git a/templates/app/.eslintrc b/templates/app/.eslintrc index 93dbcc287..b757332c7 100644 --- a/templates/app/.eslintrc +++ b/templates/app/.eslintrc @@ -140,7 +140,7 @@ "brace-style": 2, //enforce one true brace style "camelcase": 1, //require camel case names "comma-spacing": [2, {"before": false, "after": true}], //enforce spacing before and after comma - "comma-style": 2, //enforce one true comma style + "comma-style": ["error", "last"], //enforce one true comma style "computed-property-spacing": 2, //require or disallow padding inside computed properties "consistent-this": 2, //enforce consistent naming when capturing the current execution context "eol-last": 2, //enforce newline at the end of file, with no multiple empty lines @@ -170,7 +170,7 @@ "max-nested-callbacks": 2, //specify the maximum depth callbacks can be nested "max-params": 0, //limits the number of parameters that can be used in the function declaration. "max-statements": 0, //specify the maximum number of statement allowed in a function - "max-statements-per-line": 0, //enforce a maximum number of statements allowed per line + "max-statements-per-line": ["error", { "max": 1 }], //enforce a maximum number of statements allowed per line "new-cap": 0, //require a capital letter for constructors "new-parens": 2, //disallow the omission of parentheses when invoking a constructor with no arguments "newline-after-var": 0, //require or disallow an empty newline after variable declarations diff --git a/templates/app/_.babelrc b/templates/app/_.babelrc index 87789669f..31128679b 100644 --- a/templates/app/_.babelrc +++ b/templates/app/_.babelrc @@ -1,9 +1,16 @@ { - "presets": ["es2015"], + "presets": [ + "es2015", + "es2016", + "es2017", + "stage-0" + ], "plugins": [ <%_ if(filters.flow) { -%> "transform-flow-comments", <%_ } -%> - "transform-class-properties" + "angular2-annotations", + "transform-runtime", + "transform-decorators-legacy" ] } diff --git a/templates/app/_package.json b/templates/app/_package.json index 1730e940d..db363ade0 100644 --- a/templates/app/_package.json +++ b/templates/app/_package.json @@ -3,24 +3,8 @@ "version": "0.0.0", "main": "server/index.js", "dependencies": { - <%# CLIENT %> - "angular": "~1.5.5",<% if(filters.bootstrap) { if(filters.sass) { %> - "bootstrap-sass": "~3.3.7",<% } %> - "bootstrap": "~3.3.7",<% if(filters.oauth) { %> - "bootstrap-social": "^5.0.0",<% }} %> - "angular-animate": "~1.5.5", - "angular-aria": "~1.5.5", - "angular-resource": "~1.5.5", - "angular-cookies": "~1.5.5", - "angular-sanitize": "~1.5.5",<% if(filters.ngroute) { %> - "angular-route": "~1.5.5",<% } if(filters.uibootstrap) { %> - "angular-ui-bootstrap": "^2.0.1",<% } %> - "font-awesome": ">=4.1.0",<% if(filters.socketio) { %> - "angular-socket-io": "~0.7.0",<% } if(filters.uirouter) { %> - "angular-ui-router": "~0.3.1",<% } if(filters.auth) { %> - "angular-validation-match": "^1.9.0",<% } %> - <%# END CLIENT %> - "core-js": "^2.2.1", + "core-js": "^2.4.1", + "cors": "^2.8.1", "express": "^4.13.3", "morgan": "~1.7.0", "body-parser": "^1.13.3", @@ -31,10 +15,18 @@ "composable-middleware": "^0.3.0", "fast-json-patch": "^1.0.0", "lodash": "^4.6.1", - "lusca": "^1.3.0", - "babel-runtime": "^6.6.1", - "babel-polyfill": "^6.7.2",<% if(filters.pug) { %> - "pug": "2.0.0-beta4",<% } %><% if(filters.html) { %> + "lusca": "^1.4.1", + "babel-core": "^6.18.2", + "babel-plugin-angular2-annotations": "^5.1.0", + "babel-plugin-transform-decorators-legacy": "^1.3.4", + "babel-plugin-transform-runtime": "^6.6.0", + "babel-polyfill": "^6.16.0", + "babel-preset-es2015": "^6.18.0", + "babel-preset-es2016": "^6.16.0", + "babel-preset-es2017": "^6.16.0", + "babel-preset-stage-0": "^6.16.0", + "babel-runtime": "^6.18.0",<% if(filters.pug) { %> + "pug": "2.0.0-beta6",<% } %><% if(filters.html) { %> "ejs": "^2.5.3",<% } %><% if(filters.mongoose) { %> "mongoose": "^4.1.2", "bluebird": "^3.3.3", @@ -53,20 +45,56 @@ "socket.io-client": "^1.3.5", "socketio-jwt": "^4.2.0",<% } %> "serve-favicon": "^2.3.0", - "shrink-ray": "^0.1.3", - "sprint-js": "~0.1.0" + "shrink-ray": "^0.1.3" }, "devDependencies": { <%# CLIENT %> - "angular-mocks": "~1.5.5",<% if(filters.stylus) { %> - <%_ if(filters.bootstrap) { -%> - "bootstrap-styl": "^5.0.5",<% } %> + "@angularclass/match-control": "^2.0.0", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", + "zone.js": "^0.6.25", + "@angular/common": "^2.0.1", + "@angular/compiler": "^2.0.1", + "@angular/core": "^2.0.1", + "@angular/forms": "^2.0.1", + "@angular/http": "^2.0.1", + <%#"@angular/material": "^2.0.0-alpha.10",%> + "@angular/platform-browser": "^2.0.1", + "@angular/platform-browser-dynamic": "^2.0.1", + <% if(filters.ngroute) { %> + "@angular/router": "^2.0.1",<% } %> + "@angular/upgrade": "^2.0.1", + "@angularclass/hmr": "^1.2.1", + "angular2-universal": "2.1.0-rc.1", + "angular2-jwt": "^0.1.24", + <% if(filters.auth) { %> + "angular-validation-match": "^1.9.0",<% } %> + <% if(filters.uirouter) { %> + "ui-router-ng2": "^1.0.0-beta.3",<% } %> + + <% if(filters.bootstrap) { %> + "bootstrap": "~3.3.7", + <% if(filters.uibootstrap) { %> + "ng2-bootstrap": "~1.1.16",<% } %> + <% if(filters.sass) { %> + "bootstrap-sass": "~3.3.7",<% } %> + <% if(filters.stylus) { %> + "bootstrap-styl": "^5.0.8",<% } %> + <% if(filters.oauth) { %> + "bootstrap-social": "^5.1.1",<% } %> + <% } %> + + "font-awesome": ">=4.1.0", + <% if(filters.stylus) { %> "font-awesome-stylus": "^4.6.2",<% } %> + <%# CLIENT DEV %> + <%# END CLIENT %> + "autoprefixer": "^6.0.0", "babel-core": "^6.6.5", "babel-eslint": "^6.0.4", - "babel-register": "^6.6.5", + "babel-register": "^6.16.0", "browser-sync": "^2.8.0", "bs-fullscreen-message": "^1.0.0", <%_ if(filters.flow) { -%> @@ -92,7 +120,7 @@ "gulp-istanbul-enforcer": "^1.0.3", "gulp-load-plugins": "^1.0.0-rc.1", "gulp-mocha": "^2.1.3", - "gulp-node-inspector": "^0.1.0", + <%# "gulp-node-inspector": "^0.1.0", %> "gulp-plumber": "^1.0.1", "gulp-protractor": "^3.0.0", "gulp-rev": "^7.0.0", diff --git a/templates/app/client/__index.html b/templates/app/client/__index.html index 29826783d..c934094a7 100644 --- a/templates/app/client/__index.html +++ b/templates/app/client/__index.html @@ -25,6 +25,7 @@ ga('send', 'pageview'); + LOADING <% if (filters.ngroute) { %>
<% } %><% if (filters.uirouter) { %>
<% } %> diff --git a/templates/app/client/app/account(auth)/account.module.js b/templates/app/client/app/account(auth)/account.module.js new file mode 100644 index 000000000..9ae1ed74d --- /dev/null +++ b/templates/app/client/app/account(auth)/account.module.js @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +<%_ if (filters.uirouter) { -%> +import { UIRouterModule } from 'ui-router-ng2';<% } %> +<%_ if (filters.ngroute) { -%><% } %> +<%_ if(filters.oauth) { -%> +import { DirectivesModule } from '../../components/directives.module';<% } %> + +import { STATES } from './account.routes'; + +import { LoginComponent } from './login/login.component'; +import { SignupComponent } from './signup/signup.component'; +import { SettingsComponent } from './settings/settings.component'; + +export let AccountModule = @NgModule({ + imports: [ + FormsModule, + <%_ if (filters.uirouter) { -%> + UIRouterModule.forChild({ + states: STATES, + }),<% } %> + <%_ if (filters.ngroute) { -%><% } %> + DirectivesModule, + ], + declarations: [ + LoginComponent, + SignupComponent, + SettingsComponent, + ], +}) +class AccountModule {} diff --git a/templates/app/client/app/account(auth)/account.routes.js b/templates/app/client/app/account(auth)/account.routes.js index d15b803c5..eedab2494 100644 --- a/templates/app/client/app/account(auth)/account.routes.js +++ b/templates/app/client/app/account(auth)/account.routes.js @@ -1,72 +1,42 @@ -'use strict'; +import { LoginComponent } from './login/login.component'; +import { SignupComponent } from './signup/signup.component'; +import { SettingsComponent } from './settings/settings.component'; +import { AuthService } from '../../components/auth/auth.service'; -<%_ if (filters.uirouter) { _%> -export default function routes($stateProvider) { - 'ngInject'; - $stateProvider - .state('login', { - url: '/login', - template: require('./login/login.<%= templateExt %>'), - controller: 'LoginController', - controllerAs: 'vm' - }) - .state('logout', { - url: '/logout?referrer', - referrer: 'main', - template: '', - controller: function($state, Auth) { - 'ngInject'; - var referrer = $state.params.referrer - || $state.current.referrer - || 'main'; - Auth.logout(); - $state.go(referrer); - } - }) - .state('signup', { - url: '/signup', - template: require('./signup/signup.<%= templateExt %>'), - controller: 'SignupController', - controllerAs: 'vm' - }) - .state('settings', { - url: '/settings', - template: require('./settings/settings.<%= templateExt %>'), - controller: 'SettingsController', - controllerAs: 'vm', - authenticate: true - }); -}<% } %> -<%_ if (filters.ngroute) { _%> -export default function routes($routeProvider) { - 'ngInject'; - $routeProvider - .when('/login', { - template: require('./login/login.<%= templateExt %>'), - controller: 'LoginController', - controllerAs: 'vm' - }) - .when('/logout', { - name: 'logout', - referrer: '/', - template: '', - controller: function($location, $route, Auth) { - var referrer = $route.current.params.referrer || - $route.current.referrer || - '/'; - Auth.logout(); - $location.path(referrer); - } - }) - .when('/signup', { - template: require('./signup/signup.<%= templateExt %>'), - controller: 'SignupController', - controllerAs: 'vm' - }) - .when('/settings', { - template: require('./settings/settings.<%= templateExt %>'), - controller: 'SettingsController', - controllerAs: 'vm', - authenticate: true - }); -}<% } %> +<%_ if(filters.uirouter) { -%> +export const STATES = [{ + name: 'login', + url: '/login', + component: LoginComponent, +}, { + name: 'signup', + url: '/signup', + component: SignupComponent, +}, { + name: 'settings', + url: '/settings', + component: SettingsComponent, + data: { + authenticate: true, + }, +}, { + name: 'logout', + url: '/logout?referrer', + onEnter(trans, state) { + console.log('enter'); + // var referrer = $state.params.referrer + // || $state.current.referrer + // || 'main'; + // Auth.logout(); + // $state.go(referrer); + }, + resolve: [{ + provide: 'isLoggedIn', + useFactory: (AuthService) => { + console.log('resolve'); + return AuthService.isLoggedIn(); + }, + deps: [AuthService], + }], +}];<% } %> +<%_ if(filters.ngroute) { -%><% } %> diff --git a/templates/app/client/app/account(auth)/login/login(html).html b/templates/app/client/app/account(auth)/login/login(html).html index 2f72c7268..8c977d61a 100644 --- a/templates/app/client/app/account(auth)/login/login(html).html +++ b/templates/app/client/app/account(auth)/login/login(html).html @@ -6,40 +6,40 @@

Login

Admin account is admin@example.com / admin

-
+
- +
- +
-

+

Please enter your email and password.

-

+

Please enter a valid email.

-

{{ vm.errors.login }}

+

{{ errors.login }}

- - ui-sref="signup"<% } else { %>href="/signup"<% } %>> + uiSref="signup"<% } else { %>href="/signup"<% } %>> Register
-<% if (filters.oauth) { %> +<% if(filters.oauth) { %>
diff --git a/templates/app/client/app/account(auth)/login/login.component.js b/templates/app/client/app/account(auth)/login/login.component.js new file mode 100644 index 000000000..203e412ca --- /dev/null +++ b/templates/app/client/app/account(auth)/login/login.component.js @@ -0,0 +1,63 @@ +import { Component } from '@angular/core'; +<%_ if(filters.uirouter) { -%> +import { StateService } from 'ui-router-ng2';<% } %> +<%_ if(filters.ngroute) { -%><% } %> +import { AuthService } from '../../../components/auth/auth.service'; + +// @flow +<%_ if(filters.flow) { -%> +type User = { + name: string; + email: string; + password: string; +}; +<%_ } -%> +<%_ if(filters.ts) { -%> +interface User { + name: string; + email: string; + password: string; +} +<%_ } -%> + +export let LoginComponent = @Component({ + selector: 'login', + template: require('./login.<%=templateExt%>'), +}) +class LoginComponent { + user: User = { + name: '', + email: '', + password: '', + }; + errors = {login: undefined}; + submitted = false; + AuthService; + <%_ if(filters.ngroute) { -%><% } %> + <%_ if(filters.uirouter) { -%> + StateService;<% } %> + + static parameters = [AuthService, <% if(filters.ngroute) { %><% } else { %>StateService<% } %>]; + constructor(_AuthService_: AuthService, <% if(filters.ngroute) { %><% } else { %>_StateService_: StateService<% } %>) { + this.AuthService = _AuthService_; + <%_ if(filters.ngroute) { -%><% } %> + <%_ if(filters.uirouter) { -%> + this.StateService = _StateService_;<% } %> + } + + login() { + this.submitted = true; + + return this.AuthService.login({ + email: this.user.email, + password: this.user.password + }) + .then(() => { + // Logged in, redirect to home + this.StateService.go('main'); + }) + .catch(err => { + this.errors.login = err.message; + }); + } +} diff --git a/templates/app/client/app/account(auth)/login/login.controller.js b/templates/app/client/app/account(auth)/login/login.controller.js deleted file mode 100644 index a6dd073a4..000000000 --- a/templates/app/client/app/account(auth)/login/login.controller.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; -// @flow -<%_ if(filters.flow) { -%> -type User = { - name: string; - email: string; - password: string; -}; -<%_ } -%> -<%_ if(filters.ts) { -%> -interface User { - name: string; - email: string; - password: string; -} -<%_ } -%> - -export default class LoginController { - user: User = { - name: '', - email: '', - password: '' - }; - errors = {login: undefined}; - submitted = false; - Auth; - <%_ if(filters.ngroute) { -%> - $location; - <%_ } if(filters.uirouter) { -%> - $state;<% } %> - - /*@ngInject*/ - constructor(Auth<% if (filters.ngroute) { %>, $location<% } %><% if (filters.uirouter) { %>, $state<% } %>) { - this.Auth = Auth; - <%_ if(filters.ngroute) { -%> - this.$location = $location; - <%_ } if(filters.uirouter) { -%> - this.$state = $state;<% } %> - } - - login(form) { - this.submitted = true; - - if (form.$valid) { - this.Auth.login({ - email: this.user.email, - password: this.user.password - }) - .then(() => { - // Logged in, redirect to home - <% if (filters.ngroute) { %>this.$location.path('/');<% } %><% if (filters.uirouter) { %>this.$state.go('main');<% } %> - }) - .catch(err => { - this.errors.login = err.message; - }); - } - } -} diff --git a/templates/app/client/app/account(auth)/settings/settings(html).html b/templates/app/client/app/account(auth)/settings/settings(html).html index 690b0cf3f..eed1686fd 100644 --- a/templates/app/client/app/account(auth)/settings/settings(html).html +++ b/templates/app/client/app/account(auth)/settings/settings(html).html @@ -4,26 +4,25 @@

Change Password

- +
- -

- {{ vm.errors.other }} + +

+ {{ errors.other }}

-

+ [hidden]="!(form.newPassword.$error.minlength || form.newPassword.$error.required) || !(form.newPassword.$dirty || submitted)"> Password must be at least 3 characters.

@@ -31,20 +30,21 @@

Change Password

-

+ [hidden]="!form.confirmPassword.$error.match || !submitted"> Passwords must match.

-

{{ vm.message }}

+

{{ message }}

+
diff --git a/templates/app/client/app/account(auth)/settings/settings.controller.js b/templates/app/client/app/account(auth)/settings/settings.component.js similarity index 68% rename from templates/app/client/app/account(auth)/settings/settings.controller.js rename to templates/app/client/app/account(auth)/settings/settings.component.js index d6316b7a5..9e0a436b9 100644 --- a/templates/app/client/app/account(auth)/settings/settings.controller.js +++ b/templates/app/client/app/account(auth)/settings/settings.component.js @@ -1,4 +1,6 @@ -'use strict'; +import { Component } from '@angular/core'; +import { AuthService } from '../../../components/auth/auth.service'; + // @flow <%_ if(filters.flow) { -%> type User = { @@ -15,7 +17,11 @@ interface User { } <%_ } -%> -export default class SettingsController { +export let SettingsComponent = @Component({ + selector: 'settings', + template: require('./settings.<%=templateExt%>'), +}) +class SettingsComponent { user: User = { oldPassword: '', newPassword: '', @@ -24,11 +30,11 @@ export default class SettingsController { errors = {other: undefined}; message = ''; submitted = false; - Auth; + AuthService; - /*@ngInject*/ - constructor(Auth) { - this.Auth = Auth; + static parameters = [AuthService]; + constructor(_AuthService_: AuthService) { + this.AuthService = _AuthService_; } changePassword(form) { diff --git a/templates/app/client/app/account(auth)/signup/signup(html).html b/templates/app/client/app/account(auth)/signup/signup(html).html index 6b6cad4b7..1833c0e7a 100644 --- a/templates/app/client/app/account(auth)/signup/signup(html).html +++ b/templates/app/client/app/account(auth)/signup/signup(html).html @@ -4,75 +4,77 @@

Sign up

-
+ -
+
- -

+ +

A name is required

-
+
- -

- Doesn't look like a valid email. + +

+ Please enter a valid email address.

-

- What's your email address? -

-

- {{ vm.errors.email }} +

+ {{ errors.email }}

-
+
- -

- Password must be at least 3 characters. + +

+ Password must be between 8 and 128 characters.

-

- {{ vm.errors.password }} +

+ {{ errors.password }}

-
+
- -

- Passwords must match. + +

+ Please confirm your password. Passwords must match.

-<% if (filters.oauth) { %> +<% if(filters.oauth) { %>
diff --git a/templates/app/client/app/account(auth)/signup/signup.component.js b/templates/app/client/app/account(auth)/signup/signup.component.js new file mode 100644 index 000000000..c8138cab2 --- /dev/null +++ b/templates/app/client/app/account(auth)/signup/signup.component.js @@ -0,0 +1,80 @@ +// @flow +import { Component } from '@angular/core'; +<%_ if(filters.uirouter) { -%> +import { StateService } from 'ui-router-ng2';<% } %> +<%_ if(filters.ngroute) { -%><% } %> +import { AuthService } from '../../../components/auth/auth.service'; +import {ANGULARCLASS_MATCH_CONTROL_DIRECTIVES} from '@angularclass/match-control'; + +<%_ if(filters.flow) { -%> +type User = { + name: string; + email: string; + password: string; +};<% } %> +<%_ if(filters.ts) { -%> +interface User { + name: string; + email: string; + password: string; +}<% } %> + +export let SignupComponent = @Component({ + selector: 'signup', + template: require('./signup.<%=templateExt%>'), + directives: [...ANGULARCLASS_MATCH_CONTROL_DIRECTIVES] +}) +class SignupComponent { + user: User = { + name: '', + email: '', + password: '' + }; + errors = {}; + submitted = false; + AuthService; + <%_ if(filters.ngroute) { -%><% } %> + <%_ if(filters.uirouter) { -%> + StateService;<% } %> + + static parameters = [AuthService, <% if(filters.ngroute) { %><% } else { %>StateService<% } %>]; + constructor(_AuthService_: AuthService, <% if(filters.ngroute) { %><% } else { %>_StateService_: StateService<% } %>) { + this.AuthService = _AuthService_; + <%_ if(filters.ngroute) { -%><% } -%> + <%_ if(filters.uirouter) { -%> + this.StateService = _StateService_;<% } -%> + } + + register(form) { + this.submitted = true; + + return this.AuthService.createUser({ + name: this.user.name, + email: this.user.email, + password: this.user.password + }) + .then(() => { + // Account created, redirect to home + <% if(filters.ngroute) { %>this.$location.path('/');<% } -%> + <% if(filters.uirouter) { %>this.StateService.go('main');<% } -%> + }) + .catch(err => { + err = err.data; + this.errors = {}; + <%_ if(filters.mongooseModels) { -%> + // Update validity of form fields that match the mongoose errors + err.errors.forEach((error, field) => { + // form[field].$setValidity('mongoose', false); + this.errors[field] = error.message; + });<% } %> + <%_ if(filters.sequelizeModels) { -%> + // Update validity of form fields that match the sequelize errors + if(err.name) { + err.fields.forEach(field => { + // form[field].$setValidity('mongoose', false); + this.errors[field] = err.message; + }); + }<% } %> + }); + } +} diff --git a/templates/app/client/app/account(auth)/signup/signup.controller.js b/templates/app/client/app/account(auth)/signup/signup.controller.js deleted file mode 100644 index 85e71d28f..000000000 --- a/templates/app/client/app/account(auth)/signup/signup.controller.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; -// @flow -import angular from 'angular'; - -<%_ if(filters.flow) { -%> -type User = { - name: string; - email: string; - password: string; -}; -<%_ } -%> -<%_ if(filters.ts) { -%> -interface User { - name: string; - email: string; - password: string; -} -<%_ } -%> - -export default class SignupController { - user: User = { - name: '', - email: '', - password: '' - }; - errors = {}; - submitted = false; - Auth; - <%_ if(filters.ngroute) { -%> - $location; - <%_ } if(filters.uirouter) { -%> - $state;<% } %> - - /*@ngInject*/ - constructor(Auth<% if (filters.ngroute) { %>, $location<% } %><% if (filters.uirouter) { %>, $state<% } %>) { - this.Auth = Auth; - <%_ if(filters.ngroute) { -%> - this.$location = $location; - <%_ } if(filters.uirouter) { -%> - this.$state = $state;<% } %> - } - - register(form) { - this.submitted = true; - - if(form.$valid) { - return this.Auth.createUser({ - name: this.user.name, - email: this.user.email, - password: this.user.password - }) - .then(() => { - // Account created, redirect to home - <% if(filters.ngroute) { %>this.$location.path('/');<% } -%> - <% if(filters.uirouter) { %>this.$state.go('main');<% } -%> - }) - .catch(err => { - err = err.data; - this.errors = {}; - <%_ if(filters.mongooseModels) { -%> - // Update validity of form fields that match the mongoose errors - angular.forEach(err.errors, (error, field) => { - form[field].$setValidity('mongoose', false); - this.errors[field] = error.message; - });<% } %> - <%_ if(filters.sequelizeModels) { -%> - // Update validity of form fields that match the sequelize errors - if(err.name) { - angular.forEach(err.fields, field => { - form[field].$setValidity('mongoose', false); - this.errors[field] = err.message; - }); - }<% } %> - }); - } - } -} diff --git a/templates/app/client/app/app(css).css b/templates/app/client/app/app(css).css index d1b63a10f..742a27394 100644 --- a/templates/app/client/app/app(css).css +++ b/templates/app/client/app/app(css).css @@ -52,6 +52,10 @@ } }<% } %> +[hidden] { + display: none !important; +} + /* Component styles are injected through gulp */ /* inject:css */ @import 'admin/admin.css'; diff --git a/templates/app/client/app/app(less).less b/templates/app/client/app/app(less).less index 191118fbb..3876bd607 100644 --- a/templates/app/client/app/app(less).less +++ b/templates/app/client/app/app(less).less @@ -23,6 +23,11 @@ } } <% } %> + +[hidden] { + display: none !important; +} + /* inject:less */ @import 'admin/admin.less'; @import 'main/main.less'; diff --git a/templates/app/client/app/app(sass).scss b/templates/app/client/app/app(sass).scss index 1f774272e..21a563b1f 100644 --- a/templates/app/client/app/app(sass).scss +++ b/templates/app/client/app/app(sass).scss @@ -25,10 +25,18 @@ $fa-font-path: '/assets/fonts/font-awesome/'; .container { max-width: 730px; } +}<% } %> + +[hidden] { + display: none !important; +} + +.ng-valid[required], .ng-valid.required { + border-left: 5px solid #42A948; /* green */ } -<% } %> -// Component styles are injected through gulp -/* inject:scss */ + +.ng-invalid:not(form) { + border-left: 5px solid #a94442; /* red */ +} + @import 'admin/admin.scss'; -@import 'main/main.scss'; -/* endinject */ diff --git a/templates/app/client/app/app(stylus).styl b/templates/app/client/app/app(stylus).styl index a38c5b193..6597f3eda 100644 --- a/templates/app/client/app/app(stylus).styl +++ b/templates/app/client/app/app(stylus).styl @@ -22,6 +22,10 @@ $icon-font-path = '../assets/fonts/bootstrap/' .container max-width 730px <% } %> + +[hidden] + display: none !important + // Component styles are injected through gulp /* inject:styl */ @import "admin/admin" diff --git a/templates/app/client/app/app.component.js b/templates/app/client/app/app.component.js new file mode 100644 index 000000000..7f0859ba3 --- /dev/null +++ b/templates/app/client/app/app.component.js @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +export let AppComponent = @Component({ + selector: 'app', + template: ` + +
` +}) +class AppComponent {} diff --git a/templates/app/client/app/app.constants.js b/templates/app/client/app/app.constants.js index 885cf21a1..730aed801 100644 --- a/templates/app/client/app/app.constants.js +++ b/templates/app/client/app/app.constants.js @@ -1,6 +1 @@ -'use strict'; -import angular from 'angular'; - -export default angular.module('<%= scriptAppName %>.constants', []) - .constant('appConfig', require('../../server/config/environment/shared')) - .name; +export default from '../../server/config/environment/shared'; diff --git a/templates/app/client/app/app.js b/templates/app/client/app/app.js index 2937399dd..1c1e40634 100644 --- a/templates/app/client/app/app.js +++ b/templates/app/client/app/app.js @@ -1,81 +1,23 @@ -'use strict'; -import angular from 'angular'; -// import ngAnimate from 'angular-animate'; -import ngCookies from 'angular-cookies'; -import ngResource from 'angular-resource'; -import ngSanitize from 'angular-sanitize'; -<%_ if(filters.socketio) { _%> -import 'angular-socket-io';<% } %> -<%_ if(filters.ngroute) { _%> -const ngRoute = require('angular-route');<% } %> -<%_ if(filters.uirouter) { _%> -import uiRouter from 'angular-ui-router';<% } %> -<%_ if(filters.uibootstrap) { _%> -import uiBootstrap from 'angular-ui-bootstrap';<% } %> -// import ngMessages from 'angular-messages'; -<%_ if(filters.auth) { _%> -// import ngValidationMatch from 'angular-validation-match';<% } %> +import '!!style!css!sass!./app.<%= styleExt %>'; +import './polyfills'; -import {routeConfig} from './app.config'; +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -<%_ if(filters.auth) { _%> -import _Auth from '../components/auth/auth.module'; -import account from './account'; -import admin from './admin';<% } %> -import navbar from '../components/navbar/navbar.component'; -import footer from '../components/footer/footer.component'; -import main from './main/main.component'; -import constants from './app.constants'; -import util from '../components/util/util.module'; -<%_ if(filters.socketio) { _%> -import socket from '../components/socket/socket.service';<% } %> +// depending on the env mode, enable prod mode or add debugging modules +if(process.env.NODE_ENV === 'production') { + enableProdMode(); +} +import { AppModule } from './app.module'; -import './app.<%= styleExt %>'; +export function main() { + return platformBrowserDynamic().bootstrapModule(AppModule); +} -angular.module('<%= scriptAppName %>', [ - ngCookies, - ngResource, - ngSanitize, - <%_ if(filters.socketio) { %> - 'btford.socket-io',<% } %> - <%_ if(filters.ngroute) { %> - ngRoute,<% } _%> - <%_ if(filters.uirouter) { %> - uiRouter,<% } _%> - <%_ if(filters.uibootstrap) { %> - uiBootstrap,<% } %> - <%_ if(filters.auth) { %> - _Auth, - account, - admin,<% } _%> - navbar, - footer, - main, - constants, - <%_ if(filters.socketio) { _%> - socket,<% } %> - util -]) - .config(routeConfig) - <%_ if(filters.auth) { _%> - .run(function($rootScope, $location, Auth) { - 'ngInject'; - // Redirect to login if route requires auth and you're not logged in - $rootScope.$on('$stateChangeStart', function(event, next) { - Auth.isLoggedIn(function(loggedIn) { - if(next.authenticate && !loggedIn) { - $location.path('/login'); - } - }); - }); - })<% } %>; - -angular - .element(document) - .ready(() => { - angular.bootstrap(document, ['<%= scriptAppName %>'], { - strictDi: true - }); - }); +if(document.readyState === 'complete') { + main(); +} else { + document.addEventListener('DOMContentLoaded', main); +} diff --git a/templates/app/client/app/app.module.js b/templates/app/client/app/app.module.js new file mode 100644 index 000000000..cb9aed6f4 --- /dev/null +++ b/templates/app/client/app/app.module.js @@ -0,0 +1,102 @@ +// import angular from 'angular'; +// // import ngAnimate from 'angular-animate'; +// import ngCookies from 'angular-cookies'; +// import ngResource from 'angular-resource'; +// import ngSanitize from 'angular-sanitize'; +// <%_ if(filters.socketio) { _%> +// import 'angular-socket-io';<% } %> +// <%_ if(filters.ngroute) { _%> +// const ngRoute = require('angular-route');<% } %> +// <%_ if(filters.uirouter) { _%> +// import uiRouter from 'angular-ui-router';<% } %> +// <%_ if(filters.uibootstrap) { _%> +// import uiBootstrap from 'angular-ui-bootstrap';<% } %> +// // import ngMessages from 'angular-messages'; +// <%_ if(filters.auth) { _%> +// // import ngValidationMatch from 'angular-validation-match';<% } %> + +// import {routeConfig} from './app.config'; + +// <%_ if(filters.auth) { _%> +// import _Auth from '../components/auth/auth.module'; +// import account from './account'; +// import admin from './admin';<% } %> +// import navbar from '../components/navbar/navbar.component'; +// import footer from '../components/footer/footer.component'; +// import main from './main/main.component'; +// import constants from './app.constants'; +// import util from '../components/util/util.module'; +// <%_ if(filters.socketio) { _%> +// import socket from '../components/socket/socket.service';<% } %> + +// .config(routeConfig) +// <%_ if(filters.auth) { _%> +// .run(function($rootScope, $location, Auth) { +// 'ngInject'; +// // Redirect to login if route requires auth and you're not logged in +// $rootScope.$on('$stateChangeStart', function(event, next) { +// Auth.isLoggedIn(function(loggedIn) { +// if(next.authenticate && !loggedIn) { +// $location.path('/login'); +// } +// }); +// }); +// })<% } %>; + + +import { NgModule, ErrorHandler, Injectable } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { + HttpModule, + BaseRequestOptions, + RequestOptions, + RequestOptionsArgs, +} from '@angular/http'; +<%_ if (filters.uirouter) { -%> +import { UIRouterModule } from 'ui-router-ng2';<% } %> +import { provideAuth } from 'angular2-jwt'; + +import { AppComponent } from './app.component'; +import { MainModule } from './main/main.module'; +import { DirectivesModule } from '../components/directives.module'; +import { AccountModule } from './account/account.module'; +// import { AdminModule } from './admin/admin.module'; + +import constants from './app.constants'; + +let providers = [ + provideAuth({ + // Allow using AuthHttp while not logged in + noJwtError: true, + }) +]; + +if(constants.env === 'development') { + @Injectable() + class HttpOptions extends BaseRequestOptions { + merge(options/*:RequestOptionsArgs*/)/*:RequestOptions*/ { + options.url = `http://localhost:9000${options.url}`; + return super.merge(options); + } + } + + providers.push({ provide: RequestOptions, useClass: HttpOptions }); +} + +export let AppModule = @NgModule({ + providers, + imports: [ + BrowserModule, + HttpModule, + UIRouterModule.forRoot(), + MainModule, + DirectivesModule, + AccountModule, + // AdminModule, + ], + declarations: [ + AppComponent, + ], + bootstrap: [AppComponent] +}) +class AppModule {} diff --git a/templates/app/client/app/main/main(html).html b/templates/app/client/app/main/main(html).html index 13b811715..df7fe9fee 100644 --- a/templates/app/client/app/main/main(html).html +++ b/templates/app/client/app/main/main(html).html @@ -10,8 +10,11 @@

'Allo, 'Allo!

<% if (filters.socketio) { %> @@ -19,9 +22,9 @@

Features:

- + - +

<% } %> diff --git a/templates/app/client/app/main/main(pug).pug b/templates/app/client/app/main/main(pug).pug index 5084e5f22..9ad5d0ac9 100644 --- a/templates/app/client/app/main/main(pug).pug +++ b/templates/app/client/app/main/main(pug).pug @@ -8,15 +8,15 @@ header#banner.hero-unit .row .col-lg-12 h1.page-header Features: - ul.nav.nav-tabs.nav-stacked.col-md-4.col-lg-4.col-sm-6(ng-repeat='thing in $ctrl.awesomeThings') + ul.nav.nav-tabs.nav-stacked.col-md-4.col-lg-4.col-sm-6(ng-repeat='thing in awesomeThings') li - a(href='#', uib-tooltip='{{thing.info}}') - | {{thing.name}}<% if (filters.socketio) { %> - button.close(type='button', ng-click='$ctrl.deleteThing(thing)') ×<% } %><% if (filters.socketio) { %> + a(href='#', [tooltip]='{{thing.info}}') + | {{thing.name}}<% if (filters.models) { %> + button.close(type='button', (click)='deleteThing(thing)') ×<% } %><% if (filters.socketio) { %> form.thing-form label Syncs in realtime across clients p.input-group - input.form-control(type='text', placeholder='Add a new thing here.', ng-model='$ctrl.newThing') + input.form-control(type='text', placeholder='Add a new thing here.', ng-model='newThing') span.input-group-btn - button.btn.btn-primary(type='submit', ng-click='$ctrl.addThing()') Add New<% } %> + button.btn.btn-primary(type='submit', (click)='addThing()') Add New<% } %> diff --git a/templates/app/client/app/main/main.component.js b/templates/app/client/app/main/main.component.js index a1dc2479a..ed2927b76 100644 --- a/templates/app/client/app/main/main.component.js +++ b/templates/app/client/app/main/main.component.js @@ -1,40 +1,48 @@ -import angular from 'angular'; -<%_ if(filters.ngroute) { _%> -const ngRoute = require('angular-route');<% } _%> -<%_ if(filters.uirouter) { _%> -import uiRouter from 'angular-ui-router';<% } _%> - -import routing from './main.routes'; - -export class MainController { - $http; +import { Component, OnInit<% if(filters.socketio) { %>, OnDestroy<% } %> } from '@angular/core'; +import { Http } from '@angular/http'; +import { SocketService } from '../../components/socket/socket.service'; + +export let MainComponent = @Component({ + selector: 'main', + template: require('./main.<%=templateExt%>'), + styles: [require('./main.<%=styleExt%>')], +}) +class MainComponent implements OnInit<% if(filters.socketio) { %>, OnDestroy<% } %> { + Http; <%_ if(filters.socketio) { -%> - socket;<% } %> + SocketService;<% } %> awesomeThings = []; <%_ if(filters.models) { -%> newThing = '';<% } %> - /*@ngInject*/ - constructor($http<% if(filters.socketio) { %>, $scope, socket<% } %>) { - this.$http = $http; + static parameters = [Http, SocketService]; + constructor(_Http_: Http<% if(filters.socketio) { %>, _SocketService_: SocketService<% } %>) { + this.Http = _Http_; <%_ if(filters.socketio) { -%> - this.socket = socket; - - $scope.$on('$destroy', function() { - socket.unsyncUpdates('thing'); - });<% } %> + this.SocketService = _SocketService_;<% } %> } - $onInit() { - this.$http.get('/api/things').then(response => { - this.awesomeThings = response.data;<% if (filters.socketio) { %> - this.socket.syncUpdates('thing', this.awesomeThings);<% } %> - }); + ngOnInit() { + this.Http.get('/api/things') + .map(res => { + return res.json(); + <%_ if(filters.socketio) { -%> + // this.SocketService.syncUpdates('thing', this.awesomeThings);<% } %> + }) + .catch(err => Observable.throw(err.json().error || 'Server error')) + .subscribe(things => { + this.awesomeThings = things; + }); }<% if (filters.models) { %> + <%_ if(filters.socketio) { %> + + ngOnDestroy() { + this.SocketService.unsyncUpdates('thing'); + }<% } %> addThing() { - if (this.newThing) { - this.$http.post('/api/things', { name: this.newThing }); + if(this.newThing) { + this.Http.post('/api/things', { name: this.newThing }); this.newThing = ''; } } @@ -43,16 +51,3 @@ export class MainController { this.$http.delete('/api/things/' + thing._id); }<% } %> } - -export default angular.module('<%= scriptAppName %>.main', [ - <%_ if(filters.ngroute) { _%> - ngRoute<% } _%> - <%_ if(filters.uirouter) { _%> - uiRouter<% } _%> -]) - .config(routing) - .component('main', { - template: require('./main.<%= templateExt %>'), - controller: MainController - }) - .name; diff --git a/templates/app/client/app/main/main.module.js b/templates/app/client/app/main/main.module.js new file mode 100644 index 000000000..cad630bfa --- /dev/null +++ b/templates/app/client/app/main/main.module.js @@ -0,0 +1,48 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +<%_ if(filters.uirouter) { %> +import { UIRouterModule } from 'ui-router-ng2';<% } %> +<%_ if(filters.ngroute) { %> +import { RouterModule, Routes } from '@angular/router';<% } %> +<%_ if(filters.uibootstrap) { %> +import { TooltipModule } from 'ng2-bootstrap/ng2-bootstrap';<% } %> + +import { MainComponent } from './main.component'; +<%_ if(filters.socketio) { -%> +import { SocketService } from '../../components/socket/socket.service';<% } %> + +<%_ if(filters.ngroute) { _%> +export const ROUTES: Routes = [ + { path: '', component: MainComponent }, +];<% } %> +<%_ if(filters.uirouter) { _%> +export const STATES = [ + { name: 'main', url: '/', component: MainComponent }, +];<% } %> + +export let MainModule = @NgModule({ + imports: [ + BrowserModule, + FormsModule, + <%_ if(filters.ngroute) { _%> + RouterModule.forChild(ROUTES),<% } %> + <%_ if(filters.uirouter) { _%> + UIRouterModule.forChild({ + states: STATES, + }),<% } %> + <%_ if(filters.uibootstrap) { %> + TooltipModule,<% } %> + ], + declarations: [ + MainComponent, + ], + <%_ if(filters.socketio) { -%> + providers: [ + SocketService, + ],<% } %> + exports: [ + MainComponent, + ], +}) +class MainModule {} diff --git a/templates/app/client/app/main/main.routes.js b/templates/app/client/app/main/main.routes.js deleted file mode 100644 index 95c7968a9..000000000 --- a/templates/app/client/app/main/main.routes.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -<%_ if(filters.ngroute) { _%> -export default function routes($routeProvider) { - 'ngInject'; - $routeProvider - .when('/', { - template: '
' - }); -};<% } %> -<%_ if(filters.uirouter) { _%> -export default function routes($stateProvider) { - 'ngInject'; - $stateProvider - .state('main', { - url: '/', - template: '
' - }); -};<% } %> diff --git a/templates/app/client/app/polyfills.js b/templates/app/client/app/polyfills.js new file mode 100644 index 000000000..be86870ec --- /dev/null +++ b/templates/app/client/app/polyfills.js @@ -0,0 +1,17 @@ +import 'core-js/es6'; +import 'core-js/es7/reflect'; +import 'zone.js/dist/zone'; + +if(!ENV) { + var ENV = 'development'; +} + +if(ENV === 'production') { + // Production +} else { + // Development + + Error.stackTraceLimit = Infinity; + + require('zone.js/dist/long-stack-trace-zone'); +} diff --git a/templates/app/client/components/auth(auth)/auth.module.js b/templates/app/client/components/auth(auth)/auth.module.js index 1859bcd00..82f92e6df 100644 --- a/templates/app/client/components/auth(auth)/auth.module.js +++ b/templates/app/client/components/auth(auth)/auth.module.js @@ -1,32 +1,19 @@ 'use strict'; -import angular from 'angular'; -import constants from '../../app/app.constants'; -import util from '../util/util.module'; -import ngCookies from 'angular-cookies'; -import {authInterceptor} from './interceptor.service'; -import {routerDecorator} from './router.decorator'; -import {AuthService} from './auth.service'; -import {UserResource} from './user.service'; -<%_ if (filters.ngroute) { _%> -const ngRoute = require('angular-route');<% } %> -<%_ if (filters.uirouter) { _%> -import uiRouter from 'angular-ui-router';<% } %> +// import {authInterceptor} from './interceptor.service'; +// import {routerDecorator} from './router.decorator'; -function addInterceptor($httpProvider) { - 'ngInject'; - $httpProvider.interceptors.push('authInterceptor'); -} +// function addInterceptor($httpProvider) { +// $httpProvider.interceptors.push('authInterceptor'); +// } -export default angular.module('<%= scriptAppName %>.auth', [ - constants, - util, - ngCookies<% if(filters.ngroute) { %>, - ngRoute<% } if(filters.uirouter) { %>, - uiRouter<% } %> -]) - .factory('authInterceptor', authInterceptor) - .run(routerDecorator) - .factory('Auth', AuthService) - .factory('User', UserResource) - .config(['$httpProvider', addInterceptor]) - .name; +import { NgModule } from '@angular/core'; +import { AuthService } from './auth.service'; +import { UserService } from './user.service'; + +export let AuthModule = @NgModule({ + providers: [ + AuthService, + UserService + ] +}) +class AuthModule {} diff --git a/templates/app/client/components/auth(auth)/auth.service.js b/templates/app/client/components/auth(auth)/auth.service.js index e38a12632..d2753b0de 100644 --- a/templates/app/client/components/auth(auth)/auth.service.js +++ b/templates/app/client/components/auth(auth)/auth.service.js @@ -1,6 +1,15 @@ -'use strict'; +import { Injectable, EventEmitter, Output } from '@angular/core'; +import { Response } from '@angular/http'; +import { AuthHttp } from 'angular2-jwt'; +import { UserService } from './user.service'; +import { Http } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/toPromise'; +import { safeCb, extractData } from '../util'; +import { userRoles } from '../../app/app.constants'; + // @flow -class _User { +class User { _id: string = ''; name: string = ''; email: string = ''; @@ -8,206 +17,190 @@ class _User { $promise = undefined; } -export function AuthService($location, $http, $cookies, $q, appConfig, Util, User) { - 'ngInject'; - var safeCb = Util.safeCb; - var currentUser: _User = new _User(); - var userRoles = appConfig.userRoles || []; +export let AuthService = @Injectable() +class AuthService { + _currentUser: User = {}; + @Output() currentUserChanged = new EventEmitter(true); + userRoles = userRoles || []; + + static parameters = [Http, AuthHttp, UserService]; + constructor(_Http_: Http, _AuthHttp_: AuthHttp, _UserService_: UserService) { + this.Http = _Http_; + this.AuthHttp = _AuthHttp_; + this.UserService = _UserService_; + + if(localStorage.getItem('id_token')) { + this.UserService.get().toPromise() + .then(user => { + this.currentUser = user; + }) + .catch(err => { + console.log(err); + + localStorage.removeItem('id_token'); + }); + } + } + /** * Check if userRole is >= role * @param {String} userRole - role of current user * @param {String} role - role to check against */ - var hasRole = function(userRole, role) { + static hasRole(userRole, role) { return userRoles.indexOf(userRole) >= userRoles.indexOf(role); - }; + } - if($cookies.get('token') && $location.path() !== '/logout') { - currentUser = User.get(); + get currentUser() { + return this._currentUser; } - var Auth = { - /** - * Authenticate user and save token - * - * @param {Object} user - login info - * @param {Function} callback - function(error, user) - * @return {Promise} - */ - login({email, password}, callback?: Function) { - return $http.post('/auth/local', { email, password }) - .then(res => { - $cookies.put('token', res.data.token); - currentUser = User.get(); - return currentUser.$promise; - }) - .then(user => { - safeCb(callback)(null, user); - return user; - }) - .catch(err => { - Auth.logout(); - safeCb(callback)(err.data); - return $q.reject(err.data); - }); - }, - - /** - * Delete access token and user info - */ - logout() { - $cookies.remove('token'); - currentUser = new _User(); - }, - - /** - * Create a new user - * - * @param {Object} user - user info - * @param {Function} callback - function(error, user) - * @return {Promise} - */ - createUser(user, callback?: Function) { - return User.save(user, - function(data) { - $cookies.put('token', data.token); - currentUser = User.get(); - return safeCb(callback)(null, user); - }, - function(err) { - Auth.logout(); - return safeCb(callback)(err); - }).$promise; - }, - - /** - * Change password - * - * @param {String} oldPassword - * @param {String} newPassword - * @param {Function} callback - function(error, user) - * @return {Promise} - */ - changePassword(oldPassword, newPassword, callback?: Function) { - return User.changePassword({ id: currentUser._id }, { oldPassword, newPassword }, function() { - return safeCb(callback)(null); - }, function(err) { + set currentUser(user) { + this._currentUser = user; + this.currentUserChanged.emit(user); + } + + /** + * Authenticate user and save token + * + * @param {Object} user - login info + * @param {Function} [callback] - function(error, user) + * @return {Promise} + */ + login({email, password}, callback) { + return this.Http.post('/auth/local', { + email, + password + }) + .toPromise() + .then(extractData) + .then(res => { + localStorage.setItem('id_token', res.token); + return this.UserService.get().toPromise(); + }) + .then(user => { + this.currentUser = user; + localStorage.setItem('user', JSON.stringify(user)); + safeCb(callback)(null, user); + return user; + }) + .catch(err => { + this.logout(); + safeCb(callback)(err); + return Promise.reject(err); + }); + } + + /** + * Delete access token and user info + * @return {Promise} + */ + logout() { + localStorage.removeItem('user'); + localStorage.removeItem('id_token'); + this.currentUser = {}; + return Promise.resolve(); + } + + /** + * Create a new user + * + * @param {Object} user - user info + * @param {Function} callback - optional, function(error, user) + * @return {Promise} + */ + createUser(user, callback) { + return this.UserService.create(user).toPromise() + .then(data => { + localStorage.setItem('id_token', data.token); + return this.UserService.get().toPromise(); + }) + .then(_user => { + this.currentUser = _user; + return safeCb(callback)(null, _user); + }) + .catch(err => { + this.logout(); return safeCb(callback)(err); - }).$promise; - }, - - /** - * Gets all available info on a user - * - * @param {Function} [callback] - function(user) - * @return {Promise} - */ - getCurrentUser(callback?: Function) { - var value = _.get(currentUser, '$promise') - ? currentUser.$promise - : currentUser; - - return $q.when(value) - .then(user => { - safeCb(callback)(user); - return user; - }, () => { - safeCb(callback)({}); - return {}; - }); - }, - - /** - * Gets all available info on a user - * - * @return {Object} - */ - getCurrentUserSync() { - return currentUser; - }, - - /** - * Check if a user is logged in - * - * @param {Function} [callback] - function(is) - * @return {Promise} - */ - isLoggedIn(callback?: Function) { - return Auth.getCurrentUser(undefined) - .then(user => { - let is = _.get(user, 'role'); + }); + } - safeCb(callback)(is); - return is; - }); - }, - - /** - * Check if a user is logged in - * - * @return {Bool} - */ - isLoggedInSync() { - return !!_.get(currentUser, 'role'); - }, - - /** - * Check if a user has a specified role or higher - * - * @param {String} role - the role to check against - * @param {Function} [callback] - function(has) - * @return {Promise} - */ - hasRole(role, callback?: Function) { - return Auth.getCurrentUser(undefined) - .then(user => { - let has = hasRole(_.get(user, 'role'), role); + /** + * Change password + * + * @param {String} oldPassword + * @param {String} newPassword + * @param {Function} [callback] - function(error, user) + * @return {Promise} + */ + changePassword(oldPassword, newPassword, callback) { + return this.UserService.changePassword({id: this.currentUser._id}, oldPassword, newPassword) + .then(() => safeCb(callback)(null)) + .catch(err => safeCb(callback)(err)); + } - safeCb(callback)(has); - return has; - }); - }, - - /** - * Check if a user has a specified role or higher - * - * @param {String} role - the role to check against - * @return {Bool} - */ - hasRoleSync(role) { - return hasRole(_.get(currentUser, 'role'), role); - }, - - /** - * Check if a user is an admin - * (synchronous|asynchronous) - * - * @param {Function|*} callback - optional, function(is) - * @return {Bool|Promise} - */ - isAdmin() { - return Auth.hasRole - .apply(Auth, [].concat.apply(['admin'], arguments)); - }, - - /** - * Check if a user is an admin - * - * @return {Bool} - */ - isAdminSync() { - return Auth.hasRoleSync('admin'); - }, - - /** - * Get auth token - * - * @return {String} - a token string used for authenticating - */ - getToken() { - return $cookies.get('token'); - } - }; + /** + * Gets all available info on a user + * + * @param {Function} [callback] - function(user) + * @return {Promise} + */ + getCurrentUser(callback) { + safeCb(callback)(this.currentUser); + return Promise.resolve(this.currentUser); + } + + /** + * Gets all available info on a user + * + * @return {Object} + */ + getCurrentUserSync() { + return this.currentUser; + } + + /** + * Checks if user is logged in + * @returns {Promise} + */ + isLoggedIn(callback) { + let is = this.currentUser.hasOwnProperty('role'); + safeCb(callback)(is); + return Promise.resolve(is); + } + + /** + * Checks if user is logged in + * @returns {Boolean} + */ + isLoggedInSync() { + return this.currentUser.hasOwnProperty('role'); + } + + /** + * Check if a user is an admin + * + * @param {Function|*} callback - optional, function(is) + * @return {Promise} + */ + isAdmin(callback) { + return this.getCurrentUser().then(user => { + var is = user.role === 'admin'; + safeCb(callback)(is); + return is; + }); + } + + isAdminSync() { + return this.currentUser.role === 'admin'; + } - return Auth; + /** + * Get auth token + * + * @return {String} - a token string used for authenticating + */ + getToken() { + return localStorage.getItem('id_token'); + } } diff --git a/templates/app/client/components/auth(auth)/user.service.js b/templates/app/client/components/auth(auth)/user.service.js index a458a89a2..561284b53 100644 --- a/templates/app/client/components/auth(auth)/user.service.js +++ b/templates/app/client/components/auth(auth)/user.service.js @@ -1,21 +1,53 @@ 'use strict'; +import { Injectable } from '@angular/core'; +import { Response } from '@angular/http'; +import { AuthHttp } from 'angular2-jwt'; +import { Observable } from 'rxjs/Rx'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/toPromise'; -export function UserResource($resource) { - 'ngInject'; - return $resource('/api/users/:id/:controller', { - id: '@_id' - }, { - changePassword: { - method: 'PUT', - params: { - controller: 'password' - } - }, - get: { - method: 'GET', - params: { - id: 'me' - } - } - }); +// @flow +type UserType = { + // TODO: use Mongoose model + name: string; + email: string; +} + +export let UserService = @Injectable() +class UserService { + static parameters = [AuthHttp]; + constructor(authHttp: AuthHttp) { + this.AuthHttp = authHttp; + } + + handleError(err) { + Observable.throw(err.json().error || 'Server error'); + } + + query(): Observable { + return this.AuthHttp.get('/api/users/') + .map((res:Response) => res.json()) + .catch(this.handleError); + } + get(user = {id: 'me'}): Observable { + return this.AuthHttp.get(`/api/users/${user.id}`) + .map((res:Response) => res.json()) + .catch(this.handleError); + } + create(user: UserType) { + return this.AuthHttp.post('/api/users/', user) + .map((res:Response) => res.json()) + .catch(this.handleError); + } + changePassword(user, oldPassword, newPassword) { + return this.AuthHttp.put(`/api/users/${user.id}/password`, {oldPassword, newPassword}) + .map((res:Response) => res.json()) + .catch(this.handleError); + } + remove(user) { + return this.AuthHttp.delete(`/api/users/${user.id}`) + .map((res:Response) => res.json()) + .catch(this.handleError); + } } diff --git a/templates/app/client/components/directives.module.js b/templates/app/client/components/directives.module.js new file mode 100644 index 000000000..def4d52bc --- /dev/null +++ b/templates/app/client/components/directives.module.js @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { UIRouterModule } from 'ui-router-ng2'; +import { CollapseModule } from 'ng2-bootstrap/ng2-bootstrap'; + +import { AuthModule } from './auth/auth.module'; + +import { NavbarComponent } from './navbar/navbar.component'; +import { FooterComponent } from './footer/footer.component'; +<%_ if(filters.oauth) { -%> +import { OauthButtonsComponent } from './oauth-buttons/oauth-buttons.component';<% } %> + +export let DirectivesModule = @NgModule({ + imports: [ + CommonModule, + UIRouterModule.forChild(), + CollapseModule, + AuthModule, + ], + declarations: [ + NavbarComponent, + FooterComponent, + <%_ if(filters.oauth) { -%> + OauthButtonsComponent,<% } %> + ], + exports: [ + NavbarComponent, + FooterComponent, + <%_ if(filters.oauth) { -%> + OauthButtonsComponent,<% } %> + ] +}) +class DirectivesModule {} diff --git a/templates/app/client/components/footer/footer.component.js b/templates/app/client/components/footer/footer.component.js index 88b9312a7..87914ad78 100644 --- a/templates/app/client/components/footer/footer.component.js +++ b/templates/app/client/components/footer/footer.component.js @@ -1,10 +1,8 @@ -import angular from 'angular'; +import { Component } from '@angular/core'; -export class FooterComponent {} - -export default angular.module('directives.footer', []) - .component('footer', { - template: require('./footer.<%= templateExt %>'), - controller: FooterComponent - }) - .name; +export let FooterComponent = @Component({ + selector: 'footer', + template: require('./footer.html'), + styles: [require('./footer.scss')] +}) +class FooterComponent {} diff --git a/templates/app/client/components/modal(uibootstrap)/modal(css).css b/templates/app/client/components/modal(uibootstrap)/modal(css).css deleted file mode 100644 index ae0406856..000000000 --- a/templates/app/client/components/modal(uibootstrap)/modal(css).css +++ /dev/null @@ -1,23 +0,0 @@ -.modal-primary .modal-header, -.modal-info .modal-header, -.modal-success .modal-header, -.modal-warning .modal-header, -.modal-danger .modal-header { - color: #fff; - border-radius: 5px 5px 0 0; -} -.modal-primary .modal-header { - background: #428bca; -} -.modal-info .modal-header { - background: #5bc0de; -} -.modal-success .modal-header { - background: #5cb85c; -} -.modal-warning .modal-header { - background: #f0ad4e; -} -.modal-danger .modal-header { - background: #d9534f; -} diff --git a/templates/app/client/components/modal(uibootstrap)/modal(html).html b/templates/app/client/components/modal(uibootstrap)/modal(html).html deleted file mode 100644 index f04d0db03..000000000 --- a/templates/app/client/components/modal(uibootstrap)/modal(html).html +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/templates/app/client/components/modal(uibootstrap)/modal(less).less b/templates/app/client/components/modal(uibootstrap)/modal(less).less deleted file mode 100644 index dd1357d2c..000000000 --- a/templates/app/client/components/modal(uibootstrap)/modal(less).less +++ /dev/null @@ -1,25 +0,0 @@ -.modal-primary, -.modal-info, -.modal-success, -.modal-warning, -.modal-danger { - .modal-header { - color: #fff; - border-radius: 5px 5px 0 0; - } -} -.modal-primary .modal-header { - background: @brand-primary; -} -.modal-info .modal-header { - background: @brand-info; -} -.modal-success .modal-header { - background: @brand-success; -} -.modal-warning .modal-header { - background: @brand-warning; -} -.modal-danger .modal-header { - background: @brand-danger; -} diff --git a/templates/app/client/components/modal(uibootstrap)/modal(pug).pug b/templates/app/client/components/modal(uibootstrap)/modal(pug).pug deleted file mode 100644 index 71b4321b3..000000000 --- a/templates/app/client/components/modal(uibootstrap)/modal(pug).pug +++ /dev/null @@ -1,8 +0,0 @@ -.modal-header - button.close(ng-if='modal.dismissable', type='button', ng-click='$dismiss()') × - h4.modal-title(ng-if='modal.title', ng-bind='modal.title') -.modal-body - p(ng-if='modal.text', ng-bind='modal.text') - div(ng-if='modal.html', ng-bind-html='modal.html') -.modal-footer - button.btn(ng-repeat='button in modal.buttons', ng-class='button.classes', ng-click='button.click($event)', ng-bind='button.text') diff --git a/templates/app/client/components/modal(uibootstrap)/modal(sass).scss b/templates/app/client/components/modal(uibootstrap)/modal(sass).scss deleted file mode 100644 index 3b0b9d96a..000000000 --- a/templates/app/client/components/modal(uibootstrap)/modal(sass).scss +++ /dev/null @@ -1,25 +0,0 @@ -.modal-primary, -.modal-info, -.modal-success, -.modal-warning, -.modal-danger { - .modal-header { - color: #fff; - border-radius: 5px 5px 0 0; - } -} -.modal-primary .modal-header { - background: $brand-primary; -} -.modal-info .modal-header { - background: $brand-info; -} -.modal-success .modal-header { - background: $brand-success; -} -.modal-warning .modal-header { - background: $brand-warning; -} -.modal-danger .modal-header { - background: $brand-danger; -} diff --git a/templates/app/client/components/modal(uibootstrap)/modal(stylus).styl b/templates/app/client/components/modal(uibootstrap)/modal(stylus).styl deleted file mode 100644 index d394ee047..000000000 --- a/templates/app/client/components/modal(uibootstrap)/modal(stylus).styl +++ /dev/null @@ -1,23 +0,0 @@ -.modal-primary -.modal-info -.modal-success -.modal-warning -.modal-danger - .modal-header - color #fff - border-radius 5px 5px 0 0 - -.modal-primary .modal-header - background #428bca - -.modal-info .modal-header - background #5bc0de - -.modal-success .modal-header - background #5cb85c - -.modal-warning .modal-header - background #f0ad4e - -.modal-danger .modal-header - background #d9534f diff --git a/templates/app/client/components/modal(uibootstrap)/modal.service.js b/templates/app/client/components/modal(uibootstrap)/modal.service.js deleted file mode 100644 index 31de851db..000000000 --- a/templates/app/client/components/modal(uibootstrap)/modal.service.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; -import angular from 'angular'; - -export function Modal($rootScope, $uibModal) { - /** - * Opens a modal - * @param {Object} scope - an object to be merged with modal's scope - * @param {String} modalClass - (optional) class(es) to be applied to the modal - * @return {Object} - the instance $uibModal.open() returns - */ - function openModal(scope = {}, modalClass = 'modal-default') { - var modalScope = $rootScope.$new(); - - angular.extend(modalScope, scope); - - return $uibModal.open({ - template: require('./modal.<%= templateExt %>'), - windowClass: modalClass, - scope: modalScope - }); - } - - // Public API here - return { - - /* Confirmation modals */ - confirm: { - - /** - * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)') - * @param {Function} del - callback, ran when delete is confirmed - * @return {Function} - the function to open the modal (ex. myModalFn) - */ - delete(del = angular.noop) { - /** - * Open a delete confirmation modal - * @param {String} name - name or info to show on modal - * @param {All} - any additional args are passed straight to del callback - */ - return function() { - var args = Array.prototype.slice.call(arguments); - var name = args.shift(); - var deleteModal; - - deleteModal = openModal({ - modal: { - dismissable: true, - title: 'Confirm Delete', - html: '

Are you sure you want to delete ' + name + ' ?

', - buttons: [{ - classes: 'btn-danger', - text: 'Delete', - click: function(e) { - deleteModal.close(e); - } - }, { - classes: 'btn-default', - text: 'Cancel', - click: function(e) { - deleteModal.dismiss(e); - } - }] - } - }, 'modal-danger'); - - deleteModal.result.then(function(event) { - del.apply(event, args); - }); - }; - } - } - }; -} - -export default angular.module('<%= scriptAppName %>.Modal', []) - .factory('Modal', Modal) - .name; diff --git a/templates/app/client/components/navbar/navbar(html).html b/templates/app/client/components/navbar/navbar(html).html index 79d7b5a78..2b4284e55 100644 --- a/templates/app/client/components/navbar/navbar(html).html +++ b/templates/app/client/components/navbar/navbar(html).html @@ -1,7 +1,7 @@