<repo_name>oliie/aurelia-app<file_sep>/src/App/Views/login.ts
import { autoinject } from 'aurelia-framework';
import { APIService } from '../Services/APIService';
import { Router } from 'aurelia-router';
import { Configuration } from '../Configs/configuration';

@autoinject
export class Login {
    constructor(
        private API: APIService,
        private router: Router,
        private config: Configuration
    ) { }

    username: string = 'kursportal';
    password: string = '<PASSWORD>';

    authorize() {
        return this.API.login(this.username, this.password);
    }

    canActivate() {
        let isLoggedIn: boolean  = this.API.isTokenValid();
        let stayOnPage: boolean = true;

        this.config.isLoggedIn = isLoggedIn;
        isLoggedIn ? this.router.navigate(this.config.landingRoute) : stayOnPage;
    }
}
<file_sep>/src/app.ts
import { autoinject } from 'aurelia-framework';
import { Configuration } from './App/Configs/configuration';
import { Router, RouterConfiguration } from 'aurelia-router';

@autoinject
export class App {
    constructor(
        private router: Router,
        private config: Configuration
    ) { }

    configureRouter(routerConfig: RouterConfiguration, router: Router) {
        routerConfig.title = this.config.projectTitle;

        routerConfig.map([
            {
                route: ['', 'login'],
                name: 'login',
                moduleId: 'App/Views/login',
                nav: false,
                title: 'Login'
            }, {
                route: 'welcome',
                name: 'welcome',
                moduleId: 'App/Views/welcome',
                nav: true,
                title: 'Welcome'
            }, {
                route: 'users',
                name: 'users',
                moduleId: 'App/Views/users',
                nav: false,
                title: 'Github Users'
            }, {
                route: 'child-router',
                name: 'child-router',
                moduleId: 'App/Views/child-router',
                nav: true,
                title: 'Child Router'
            }, {
                route: 'custom-form',
                name: 'custom-form',
                moduleId: 'App/Views/custom-form',
                nav: true,
                title: 'Custom Form'
            }
        ]);

        this.router = router;
    }
}
<file_sep>/src/App/Filters/my-filter.ts
export class MyFilterValueConverter {
    toView(value) {
        return value + ' transformed!!!';
    }
}
<file_sep>/src/App/Views/custom-form.ts
import { autoinject } from 'aurelia-framework';
import { Validation } from 'aurelia-validation';
import { Configuration } from '../Configs/configuration';
import { APIService } from '../Services/APIService';

@autoinject
export class CustomForm {
    constructor(
        private config: Configuration,
        private validation: Validation,
        private API: APIService
    ) {
        this.validation.on(this.validateForm)
            .ensure('firstName')
            .hasMinLength(3)
            .isNotEmpty();
    }

    firstName:  string = 'Oliver';
    lastName:   string = 'Praesto';

    get fullName(): string {
        return `${this.firstName} ${this.lastName}`;
    }

    canActivate() {
        return this.API.isTokenValid();
    }

    validateForm(): void {
        if (this.firstName.length !== 2) {
            console.log('Error:', 'First Name not equals to 2 chars');
        } else {
            alert(`Welcome, ${this.fullName}!`);
        }
    }
}
<file_sep>/src/App/Configs/configuration.ts
export class Configuration {
    projectTitle:       string  = 'Innorelia';
    baseApiUrl:         string  = 'http://cinapi.azurewebsites.net/';
    landingRoute:       string  = 'welcome';
    notAuthorizedRoute: string  = 'login';

    isLoggedIn:         boolean;
}<file_sep>/src/App/Services/APIService.ts
import { autoinject } from 'aurelia-framework';
import { HttpClient } from 'aurelia-fetch-client';
import { Configuration } from '../Configs/configuration';
import { Router } from 'aurelia-router';

export interface IPutRequest {
    entityName: string;
    entityId: string;
    attributes: any;
}

export interface IDeleteRequest {
    entityName: string;
    entityId: string;
}

export interface IPostRequest {
    entityName: string;
    attributes: any;
}

@autoinject
export class APIService {
    constructor(
        private http: HttpClient,
        private config: Configuration,
        private router: Router
    ) {
        http.configure(config => {
            config
                .useStandardConfiguration()
                .withBaseUrl(this.config.baseApiUrl);
        });
    }

    private get getToken() {
        return sessionStorage.getItem('token');
    }

    private headers: any = {
        'Authorization': `Bearer ${this.getToken}`,
        'X-Requested-With': 'XMLHttpRequest'
    };

    private errorHandler(
        errorMessage: any
    ) {
        return console.error('CUSTOM ERROR:', errorMessage);
    }

    private baseApiRequest(
        service: string,
        method: string,
        params: any
    ) {
        return this.http.fetch(service, {
            method: method,
            headers: this.headers,
            body: {
                attributes: params
            }
        })
        .then(response => response.json())
        .catch(this.errorHandler);
    }

    private setSessionAndLogin(
        response: any,
        successRoute: string
    ): void {
        sessionStorage.setItem('token', (<any>response).access_token);
        sessionStorage.setItem('session', JSON.stringify(response));
        this.router.navigate(successRoute);
    }

    /**
     * Sets token and session in `sessionStorage` and redirects to `successRoute`.
     *
     * @method login
     * @param  {string} username CWP Username
     * @param  {string} password <PASSWORD>
     * @param  {string} successRoute Route to redirect to after successful login.
     */
    login(
        username: string,
        password: string
    ) {
        return this.http.fetch('token', {
            method: 'POST',
            body: `username=${username}&password=${<PASSWORD>}&grant_type=password`
        })
        .then(response => response.json())
        .catch(this.errorHandler)
        .then(response => {
            let hasResponse: boolean = !!response;
            this.config.isLoggedIn = hasResponse;

            if ( hasResponse ) {
                this.setSessionAndLogin(response, this.config.landingRoute);
            }
        });
    }

    /**
     * Returns `true` if token is valid.
     *
     * @method isTokenValid
     */
    isTokenValid() {
        let hasSession: boolean = !!sessionStorage.getItem('session');
        let session: any = hasSession ? JSON.parse(sessionStorage.getItem('session')) : false;
        let now: any = new Date();

        if ( hasSession ) {
            let expires: any = new Date(session['.expires']);
            let isTokenValid: boolean = (expires > now);

            this.config.isLoggedIn = isTokenValid;
            return (typeof isTokenValid === 'boolean') ? isTokenValid : false;
        }

        this.config.isLoggedIn = false;
        return false;
    }

    /**
     * Gets JSON data from CWP Feed (XML-based definition)
     *
     * @method get
     * @param  {string} feedName Name of CWP Feed
     * @param  {any} parameters? Optional parameters to CWP Feed (defined by {querystring:field_name})
     */
    get(
        feedName: string,
        parameters?: any
    ) {
        return this.baseApiRequest(`API/Feed/${feedName}`, 'GET', parameters);
    }

    /**
     * Posts data to fields in defined entity. Requires a CWP Policy
     *
     * @method post
     * @param {IPostRequest} parameters { entityName: string, attributes?: { fieldName: value } }
     */
    post(
        parameters: IPostRequest
    ) {
        return this.baseApiRequest(`API/Entity`, 'POST', parameters);
    }

    /**
     * Updates data to fields in defined entity. Requires a CWP Policy
     *
     * @method put
     * @param {IPutRequest} parameters { entityName: string, entityId: GUID, attributes: { fieldName: value } }
     */
    put(
        parameters: IPutRequest
    ) {
        return this.baseApiRequest(`API/Entity`, 'PUT', parameters);
    }

    /**
     * Delets post. Requires a CWP Policy
     *
     * @method delete
     * @param {IDeleteRequest} parameters { entityName: string, entityId: GUID }
     */
    delete(
        parameters: IDeleteRequest
    ) {
        return this.baseApiRequest(`API/Entity`, 'DELETE', parameters);
    }

    /**
     * Special method to communicate with custom plugins.
     *
     * @method action
     * @param  {string} pluginName Name of the Plugin. Requires CWP Policy with plugin name as `entity`.
     * @param  {any} parameters Object with keys required by plugin.
     */
    action(
        pluginName: string,
        parameters: any
    ) {
        return this.baseApiRequest(`API/Action/${pluginName}`, 'ACTION', parameters);
    }
}
<file_sep>/src/App/Views/welcome.ts
import { autoinject } from 'aurelia-framework';
import { APIService } from '../Services/APIService';
import { Configuration } from '../Configs/configuration';
import { Router } from 'aurelia-router';

@autoinject
export class Welcome {

    constructor(
        private API: APIService,
        private config: Configuration,
        private router: Router
    ) { }

    heading: string = 'Welcome to the Aurelia Navigation App!';
    firstName: string = 'John';
    user: string = 'oliverpraesto';
    lastName: string = 'Doe';
    previousValue: string = this.fullName;
    alreadyLoggedInRoute: string = this.config.landingRoute;

    canActivate() {
        return this.API.isTokenValid();
    }

    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }

    submit() {
        this.previousValue = this.fullName;
        alert(`Welcome, ${this.fullName}!`);
    }

    canDeactivate() {
        if (this.fullName !== this.previousValue) {
            return confirm('Are you sure you want to leave?');
        }
    }
}
<file_sep>/TODO.md
# Todo

* `[✔]` Create: APIService Class
  * `[✔]` Provide it with CRUD, Action and Login
* `[✔]` Create: default Login-page
* Create: DOCUMENTATION.md with default layout and a screenshot
    * `[✔]` Make .bat take a variable for output documentation name
    * `[✔]` Hide the .bat when finished
    * Update template Innofactor-way
* `[✔]` Create: Yo-Scaffolder (Innorelia?) to:
    * `[✔]` Create Views
      * `[✔]` Prompt for SCSS and Router-setup
    * `[✔]` Create Widgets
      * `[✔]` Prompt if: Views, ViewModel or Both
    * `[✔]` Create Filters
    * Fix scaffold glitch when using `yo` in VS Code
* `[✔]` Handle: Routing with authentication
* `[✔]` Add: `canActivate()` with token-check.
* `[✔]` Configure: TS Lint
* Create: Bundle/Deploy-script to server
* Create: Form-sponge for values
* Create: General and customizable loader for APIService
* Create: Guide for test-cases. Best practice
* Add: Meta tag for mobile devices
* Add: Revision-tags (bundles.js)
* Move: styles/bundle.css to only dist?
* Update: Set Require-Auth in App.ts for routes (<http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/securing-your-app/2>)
* Add: Validation (wait for support of validatejs/validation)
* Add: Animation-CSS (<https://github.com/gooy/aurelia-animator-velocity>? aurelia-animator-CSS?)
* Customize: README for this project
  * Installation:
    * Typings
    * NPM
    * JSPM
    * Bower
  * Documentation
  * Yeoman / Yo
  * SCSS + BEM
  * Publishing / Deployment <http://ilikekillnerds.com/2015/09/aurelia-jspm-deployment-best-practices-unknowns/>
  * E2E
  * Unit Testing
  * ES Lint and how to "fix all problems (VS Code)"
* Research: MVC5 Aurelia TS instead?
* Research: Extend .eslint own Global-vars?
* Email: Karin & Jonas vad webbapplikationen är och varför vi behöver den
<file_sep>/src/App/Views/child-router.ts
import { autoinject } from 'aurelia-framework';
import { Router, RouterConfiguration } from 'aurelia-router';
import { APIService } from '../Services/APIService';

@autoinject
export class ChildRouter {
    heading = 'Child Router';
    router: Router;

    constructor(
        private API: APIService
    ) { }

    canActivate() {
        return this.API.isTokenValid();
    }

    configureRouter(config: RouterConfiguration, router: Router) {
        config.map([
            { route: ['', 'welcome'], name: 'welcome',       moduleId: 'App/Views/welcome',      nav: true, title: 'Welcome' },
            { route: 'users',         name: 'users',         moduleId: 'App/Views/users',        nav: true, title: 'Github Users' },
            { route: 'child-router',  name: 'child-router',  moduleId: 'App/Views/child-router', nav: true, title: 'Child Router' },
            { route: 'custom-form',   name: 'custom-form',   moduleId: 'App/Views/custom-form',  nav: true, title: 'Custom Form' }
        ]);

        this.router = router;
    }
}
<file_sep>/src/App/Filters/github.ts
export class GithubValueConverter {
    toView(value) {
        return '@' + value;
    }

    fromView(value) {
        return value.replace('@', '');
    }
}
<file_sep>/src/App/Widgets/nav-bar.ts
import { autoinject, bindable } from 'aurelia-framework';
import { Router } from 'aurelia-router';
import { Configuration } from '../Configs/configuration';
import { APIService } from '../Services/APIService';

@autoinject
export class NavBar {
    @bindable router: Router = null;

    constructor(
        private API: APIService,
        private config: Configuration
    ) { }

    get isLoggedIn(): boolean {
        return this.config.isLoggedIn;
    }

    logOut() {
        sessionStorage.clear();
        this.config.isLoggedIn = false;
        this.router.navigate(this.config.notAuthorizedRoute);
    }
}