<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>MEANie - The MEAN Stack Blog</title>

    <link rel="shortcut icon" href="/admin/_content/images/logo.png" type="image/png" />
    <link rel="icon" href="/admin/_content/images/logo.png" type="image/png" />

    <!-- bootstrap -->
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />

    <!-- jquery ui -->
    <link href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet">

    <!-- meanie -->
    <link href="_content/app.less" rel="stylesheet/less" type="text/css" />

    <script src="//cdnjs.cloudflare.com/ajax/libs/less.js/2.5.3/less.min.js"></script>
</head>
<body>
    <!-- header -->
    <header>
        <ul class="nav nav-tabs">
            <li ng-class="{active: activeTab === 'posts'}"><a href="#/posts">Posts</a></li>
            <li ng-class="{active: activeTab === 'pages'}"><a href="#/pages">Pages</a></li>
            <li ng-class="{active: activeTab === 'redirects'}"><a href="#/redirects">Redirects</a></li>
            <li ng-class="{active: activeTab === 'account'}"><a href="#/account">Account</a></li>
        </ul>
        <div class="logo">
            <img src="https://raw.githubusercontent.com/cornflourblue/meanie/master/client/admin/_content/images/logo.png" />
            MEANie
        </div>
        <div class="flash-message" ng-if="flash">
            <div class="{{'alert alert-' + flash.type}}" ng-bind="flash.message"></div>
        </div>
    </header>

    <!-- main -->
    <main ui-view></main>

    <!-- footer -->
    <footer></footer>

    <!-- jquery -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>

    <!-- underscore -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

    <!-- moment -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>

    <!-- angular -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.min.js"></script>

    <!-- ckeditor -->
    <script src="//cdn.ckeditor.com/4.5.7/full/ckeditor.js"></script>
    <script src="//cdn.ckeditor.com/4.5.7/full/adapters/jquery.js"></script>

    <!-- meanie -->
    <script src="app.js"></script>
    <script src="_directives/datepicker.directive.js"></script>
    <script src="_directives/tags.directive.js"></script>
    <script src="_directives/wysiwyg.directive.js"></script>
    <script src="_filters/csv.filter.js"></script>
    <script src="_filters/slugify.filter.js"></script>
    <script src="_services/alert.service.js"></script>
    <script src="_services/page.service.js"></script>
    <script src="_services/post.service.js"></script>
    <script src="_services/redirect.service.js"></script>
    <script src="_services/user.service.js"></script>
    <script src="account/index.controller.js"></script>
    <script src="pages/add-edit.controller.js"></script>
    <script src="pages/index.controller.js"></script>
    <script src="posts/add-edit.controller.js"></script>
    <script src="posts/index.controller.js"></script>
    <script src="redirects/add-edit.controller.js"></script>
    <script src="redirects/index.controller.js"></script>

    <!-- fake backend for plunk -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-mocks.js"></script>
    <script src="_helpers/fake-backend.js"></script>
</body>
</html>
/* DEFAULTS 
----------------------------------------- */
h1 {
    font-size: 28px;
    font-weight: normal;
}

body {
    padding: 20px 0;
    max-width: 1200px;
    margin: auto;
}

.flash-message {
    margin: 20px 20px 0 20px;

    .alert {
        white-space: pre;
    }
}

.form-group {
    .datepicker {
        width: 100px;
    }

    .generate-slug {
        cursor: pointer;
        font-size: 12px;
    }
}

.index-header {
    h1 {
        float: left;
    }

    .buttons {
        float: right;
        margin-top: 13px;
    }
}

.table {
    th {
        white-space: nowrap;
    }

    td {
        vertical-align: middle
    }

    td.action-icons {
        white-space: nowrap;
        text-align: right;

        .btn {
            padding: 0;
            width: 26px;
            height: 26px;
            line-height: 26px;
        }
    }
}


/* LAYOUT
----------------------------------------- */
header {
    position: relative;
    padding-top: 20px;

    .nav {
        li:first-child {
            margin-left: 20px;
        }
    }

    .logo {
        position: absolute; 
        top: 0;
        right: 20px; 
        font-size: 30px;

        img {
            vertical-align: initial;
        }
    }
}

main {
    padding: 0 20px 20px 20px;
}


/* BOOTSTRAP OVERRIDES
----------------------------------------- */
.nav-tabs > li.active > a, 
.nav-tabs > li.active > a:focus, 
.nav-tabs > li.active > a:hover {
    cursor: pointer;
}
(function () {
    'use strict';

    angular
        .module('app')
        .run(setupFakeBackend);

    // setup fake backend for backend-less development
    function setupFakeBackend($httpBackend) {
        // create fake user api endpoint
        createFakeUsersApiEndpoint($httpBackend);

        // create fake endpoints for posts, pages and redirects
        createFakeApiEndpoint($httpBackend, 'posts');
        createFakeApiEndpoint($httpBackend, 'pages');
        createFakeApiEndpoint($httpBackend, 'redirects');
      
        
        // pass through any urls not handled above so static files are served correctly
        $httpBackend.whenGET(/^\w+.*/).passThrough();
    }
    
    function createFakeApiEndpoint($httpBackend, entityName){
        // get all
        $httpBackend.whenGET('/api/' + entityName).respond(function (method, url, data) {
            var entities = JSON.parse(localStorage.getItem(entityName)) || [];
            return [200, entities, {}];
        });

        // get by id
        $httpBackend.whenGET(/\/api\/\w+\/\d+$/).respond(function (method, url, data) {
            var urlParts = url.split('/');
            var entityName = urlParts[urlParts.length - 2];
            var entities = JSON.parse(localStorage.getItem(entityName)) || [];
            var id = parseInt(urlParts[urlParts.length - 1]);
            for (var i = 0; i < entities.length; i++) {
                if (entities[i]._id === id) {
                    return [200, entities[i], {}];
                }
            }
        });
        
        // create
        $httpBackend.whenPOST('/api/' + entityName).respond(function (method, url, data) {
            var entities = JSON.parse(localStorage.getItem(entityName)) || [];

            // get parameters from post request
            var entity = JSON.parse(data);

            // save entity
            entity._id = entities.length + 1;
            entities.push(entity);
            localStorage.setItem(entityName, JSON.stringify(entities));
            
            return [200, {}, {}];
        });
        
        // update
        $httpBackend.whenPUT(/\/api\/\w+\/\d+$/).respond(function (method, url, data) {
            var entity = JSON.parse(data);
            var urlParts = url.split('/');
            var entityName = urlParts[urlParts.length - 2];
            var entities = JSON.parse(localStorage.getItem(entityName)) || [];
            var id = parseInt(urlParts[urlParts.length - 1]);
            for (var i = 0; i < entities.length; i++) {
                if (entities[i]._id === id) {
                    // save entity
                    entities[i] = entity;
                    localStorage.setItem(entityName, JSON.stringify(entities));
                    
                    return [200, {}, {}];
                }
            }
        });
        
        // delete
        $httpBackend.whenDELETE(/\/api\/\w+\/\d+$/).respond(function (method, url, data) {
            var urlParts = url.split('/');
            var entityName = urlParts[urlParts.length - 2];
            var entities = JSON.parse(localStorage.getItem(entityName)) || [];
            var id = parseInt(urlParts[urlParts.length - 1]);
            for (var i = 0; i < entities.length; i++) {
                if (entities[i]._id === id) {
                    // delete user
                    entities.splice(i, 1);
                    localStorage.setItem(entityName, JSON.stringify(entities));

                    return [200, {}, {}];
                }
            }
        });
    }
    
    function createFakeUsersApiEndpoint($httpBackend){
        var defaultUsers = [{ _id: 1, username: 'meanie', password: 'meanie' }];

        // get current
        $httpBackend.whenGET('/api/users/current').respond(function (method, url, data) {
            var users = JSON.parse(localStorage.getItem('users')) || defaultUsers;
            return [200, users[0], {}];
        });

        // update
        $httpBackend.whenPUT(/\/api\/users\/\d+$/).respond(function (method, url, data) {
            var users = JSON.parse(localStorage.getItem('users')) || defaultUsers;
            var user = JSON.parse(data);
            var urlParts = url.split('/');
            var id = parseInt(urlParts[urlParts.length - 1]);
            for (var i = 0; i < users.length; i++) {
                if (users[i]._id === id) {
                    // save user
                    users[i] = user;
                    localStorage.setItem('users', JSON.stringify(users));
                    
                    return [200, {}, {}];
                }
            }
        });
    }    
})();
(function () {
    'use strict';

    angular
        .module('app', ['ui.router', 'ngMockE2E'])
        .config(config)
        .run(run);

    function config($locationProvider, $stateProvider, $urlRouterProvider) {
        // default route
        $urlRouterProvider.otherwise("/posts");

        $stateProvider
            .state('posts', {
                url: '/posts',
                templateUrl: 'posts/index.view.html',
                controller: 'Posts.IndexController',
                controllerAs: 'vm',
                data: { activeTab: 'posts' }
            })
            .state('posts/add', {
                url: '/posts/add',
                templateUrl: 'posts/add-edit.view.html',
                controller: 'Posts.AddEditController',
                controllerAs: 'vm',
                data: { activeTab: 'posts' }
            })
            .state('posts/edit', {
                url: '/posts/edit/:_id',
                templateUrl: 'posts/add-edit.view.html',
                controller: 'Posts.AddEditController',
                controllerAs: 'vm',
                data: { activeTab: 'posts' }
            })
            .state('pages', {
                url: '/pages',
                templateUrl: 'pages/index.view.html',
                controller: 'Pages.IndexController',
                controllerAs: 'vm',
                data: { activeTab: 'pages' }
            })
            .state('pages/add', {
                url: '/pages/add',
                templateUrl: 'pages/add-edit.view.html',
                controller: 'Pages.AddEditController',
                controllerAs: 'vm',
                data: { activeTab: 'pages' }
            })
            .state('pages/edit', {
                url: '/pages/edit/:_id',
                templateUrl: 'pages/add-edit.view.html',
                controller: 'Pages.AddEditController',
                controllerAs: 'vm',
                data: { activeTab: 'pages' }
            })
            .state('redirects', {
                url: '/redirects',
                templateUrl: 'redirects/index.view.html',
                controller: 'Redirects.IndexController',
                controllerAs: 'vm',
                data: { activeTab: 'redirects' }
            })
            .state('redirects/add', {
                url: '/redirects/add',
                templateUrl: 'redirects/add-edit.view.html',
                controller: 'Redirects.AddEditController',
                controllerAs: 'vm',
                data: { activeTab: 'redirects' }
            })
            .state('redirects/edit', {
                url: '/redirects/edit/:_id',
                templateUrl: 'redirects/add-edit.view.html',
                controller: 'Redirects.AddEditController',
                controllerAs: 'vm',
                data: { activeTab: 'redirects' }
            })
            .state('account', {
                url: '/account',
                templateUrl: 'account/index.view.html',
                controller: 'Account.IndexController',
                controllerAs: 'vm',
                data: { activeTab: 'account' }
            });
    }

    function run($http, $rootScope, $window) {
        // add JWT token as default auth header
        $http.defaults.headers.common['Authorization'] = 'Bearer ' + $window.jwtToken;

        // update active tab on state change
        $rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
            $rootScope.activeTab = toState.data.activeTab;
        });
    }

    // manually bootstrap angular after the JWT token is retrieved from the server
    $(function () {
        // get JWT token from server
        $.get('_helpers/fake-jwt-token', function (token) {
            window.jwtToken = token;

            angular.bootstrap(document, ['app']);
        });
    });
})();
(function () {
    'use strict';

    angular
        .module('app')
        .directive('datepicker', Directive);

    function Directive($filter) {
        return {
            require: 'ngModel',
            link: function (scope, element, attr, ngModel) {
                // add class for custom styling
                element.addClass('datepicker');

                // enable jquery ui datepicker
                element.datepicker({ dateFormat: "dd/mm/yy" });

                // convert to dd/mm/yyyy for display
                ngModel.$formatters.push(function (date) {
                    if (!date)
                        return;

                    return moment(date).format('DD/MM/YYYY');
                });

                // convert to yyyy-mm-dd for storage
                ngModel.$parsers.push(function (date) {
                    if (!date)
                        return;

                    return moment(date, 'DD/MM/YYYY').format('YYYY-MM-DD');
                });
            }
        };
    }
})();
(function () {
    'use strict';

    angular
        .module('app')
        .controller('Account.IndexController', Controller);

    function Controller($window, UserService, AlertService) {
        var vm = this;

        vm.user = null;
        vm.saveUser = saveUser;

        initController();

        function initController() {
            // get current user
            UserService.GetCurrent().then(function (user) {
                vm.user = user;
            });
        }

        function saveUser() {
            UserService.Update(vm.user)
                .then(function () {
                    AlertService.Success('User updated');
                })
                .catch(function (error) {
                    AlertService.Error(error);
                });
        }
    }

})();
<div class="account-index">
    <h1>My Account</h1>
    <div class="row">
        <div class="col-sm-4">
            <form method="post">
                <div class="form-group">
                    <label for="username">Username</label>
                    <input type="text" id="username" class="form-control" ng-model="vm.user.username" required />
                </div>
                <div class="form-group">
                    <label for="password">Password</label>
                    <input type="password" id="password" class="form-control" ng-model="vm.user.password" />
                </div>
                <div class="form-group">
                    <button class="btn btn-primary" ng-click="vm.saveUser()">Update</button>
                </div>
            </form>
        </div>
    </div>
</div>
(function () {
    'use strict';

    angular
        .module('app')
        .controller('Pages.AddEditController', Controller);

    function Controller($stateParams, $location, $filter, PageService, AlertService) {
        var vm = this;

        vm.page = {};
        vm.savePage = savePage;
        vm.deletePage = deletePage;

        initController();

        function initController() {
            vm.loading = 0;

            if ($stateParams._id) {
                vm.loading += 1;
                PageService.GetById($stateParams._id)
                    .then(function (page) {
                        vm.loading -= 1;
                        vm.page = page;
                    });
            } else {
                // initialise with defaults
                vm.page = {
                    publish: true
                };
            }
        }

        function savePage() {
            PageService.Save(vm.page)
                .then(function () {
                    AlertService.Success('Page saved', true);
                    $location.path('/pages');
                })
                .catch(function (error) {
                    AlertService.Error(error);
                });
        }

        function deletePage() {
            PageService.Delete(vm.page._id)
                .then(function () {
                    AlertService.Success('Page deleted', true);
                    $location.path('/pages');
                })
                .catch(function (error) {
                    AlertService.Error(error);
                });
        }
    }

})();
<div class="pages-add-edit">
    <h1>Page Details</h1>
    <form ng-if="!vm.loading">
        <div class="form-group">
            <label for="title">Title</label>
            <input type="text" id="title" class="form-control" ng-model="vm.page.title" required />
        </div>
        <div class="form-group">
            <label for="slug">Slug</label>
            <input type="text" id="slug" class="form-control" ng-model="vm.page.slug" />
            <a class="generate-slug" ng-click="vm.page.slug = (vm.page.title | slugify)">Generate from title</a>
        </div>
        <div class="form-group">
            <label for="description">Description</label>
            <textarea id="description" class="form-control" ng-model="vm.page.description" rows="4"></textarea>
        </div>
        <div class="form-group">
            <label for="body">Body</label>
            <textarea class="form-control" ng-model="vm.page.body" wysiwyg></textarea>
        </div>
        <div class="checkbox">
            <label>
                <input type="checkbox" ng-model="vm.page.publish"> Publish
            </label>
        </div>
        <hr />
        <div class="form-group">
            <a class="btn btn-primary" ng-click="vm.savePage()">Save</a>
            <a class="btn btn-default" href="#/pages">Cancel</a>
            <a class="btn btn-danger" ng-click="vm.deletePage()" ng-show="vm.page._id">Delete</a>
        </div>
    </form>
</div>
(function () {
    'use strict';

    angular
        .module('app')
        .controller('Pages.IndexController', Controller);

    function Controller(PageService) {
        var vm = this;

        vm.pages = [];

        initController();

        function initController() {
            vm.loading = true;
            PageService.GetAll()
                .then(function (pages) {
                    vm.loading = false;
                    vm.pages = pages;
                });
        }
    }

})();
<div class="pages-index">
    <div class="index-header">
        <h1>Pages</h1>
        <div class="buttons">
            <a href="#/pages/add" class="btn btn-default">
                <i class="glyphicon glyphicon-plus"></i> New Page
            </a>
        </div>
    </div>
    <table class="pages-table table table-striped">
        <tr>
            <th>Title</th>
            <th class="text-center">Published</th>
            <th></th>
        </tr>
        <tr ng-repeat="page in vm.pages">
            <td>{{page.title}}</td>
            <td class="text-center"><i ng-if="page.publish" class="glyphicon glyphicon-ok" /></td>
            <td class="action-icons">
                <a href="#/pages/edit/{{page._id}}" class="btn btn-default"><i class="glyphicon glyphicon-pencil"></i></a>
            </td>
        </tr>
    </table>
</div>
(function () {
    'use strict';

    angular
        .module('app')
        .controller('Posts.AddEditController', Controller);

    function Controller($stateParams, $location, PostService, AlertService) {
        var vm = this;

        vm.post = {};
        vm.savePost = savePost;
        vm.deletePost = deletePost;

        initController();

        function initController() {
            vm.loading = 0;

            if ($stateParams._id) {
                vm.loading += 1;
                PostService.GetById($stateParams._id)
                    .then(function (post) {
                        vm.loading -= 1;
                        vm.post = post;
                    });
            } else {
                // initialise with defaults
                vm.post = {
                    publishDate: moment().format('YYYY-MM-DD'),
                    publish: true
                };
            }
        }

        function savePost() {
            PostService.Save(vm.post)
                .then(function () {
                    AlertService.Success('Post saved', true);
                    $location.path('/posts');
                })
                .catch(function (error) {
                    AlertService.Error(error);
                });
        }

        function deletePost() {
            PostService.Delete(vm.post._id)
                .then(function () {
                    AlertService.Success('Post deleted', true);
                    $location.path('/posts');
                })
                .catch(function (error) {
                    AlertService.Error(error);
                });
        }
    }

})();
<div class="posts-add-edit">
    <h1>Post Details</h1>
    <form ng-if="!vm.loading">
        <div class="form-group">
            <label for="title">Title</label>
            <input type="text" id="title" class="form-control" ng-model="vm.post.title" />
        </div>
        <div class="form-group">
            <label for="slug">Slug</label>
            <input type="text" id="slug" class="form-control" ng-model="vm.post.slug" />
            <a class="generate-slug" ng-click="vm.post.slug = (vm.post.title | slugify)">Generate from title</a>
        </div>
        <div class="form-group">
            <label for="summary">Summary</label>
            <textarea id="summary" class="form-control" ng-model="vm.post.summary" rows="4"></textarea>
        </div>
        <div class="form-group">
            <label for="body">Body</label>
            <textarea class="form-control" ng-model="vm.post.body" wysiwyg></textarea>
        </div>
        <div class="form-group">
            <label for="title">Tags</label>
            <input type="text" id="tags" class="form-control" ng-model="vm.post.tags" tags />
        </div>
        <div class="form-group">
            <label for="publish-date">Publish Date</label>
            <div class="input-group">
                <span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>
                <input type="text" id="publish-date" class="form-control" ng-model="vm.post.publishDate" datepicker />
            </div>
        </div>
        <div class="checkbox">
            <label>
                <input type="checkbox" ng-model="vm.post.publish"> Publish
            </label>
        </div>
        <hr />
        <div class="form-group">
            <a class="btn btn-primary" ng-click="vm.savePost()">Save</a>
            <a class="btn btn-default" href="#/posts">Cancel</a>
            <a class="btn btn-danger" ng-click="vm.deletePost()" ng-show="vm.post._id">Delete</a>
        </div>
    </form>
</div>
(function () {
    'use strict';

    angular
        .module('app')
        .controller('Posts.IndexController', Controller);

    function Controller(PostService) {
        var vm = this;

        vm.posts = [];

        initController();

        function initController() {
            vm.loading = true;
            PostService.GetAll()
                .then(function (posts) {
                    vm.loading = false;
                    vm.posts = posts;
                });
        }
    }

})();
<div class="posts-index">
    <div class="index-header">
        <h1>Posts</h1>
        <div class="buttons">
            <a href="#/posts/add" class="btn btn-default">
                <i class="glyphicon glyphicon-plus"></i> New Post
            </a>
        </div>
    </div>
    <table class="posts-table table table-striped">
        <tr>
            <th>Title</th>
            <th>Tags</th>
            <th>Publish Date</th>
            <th class="text-center">Published</th>
            <th></th>
        </tr>
        <tr ng-repeat="post in vm.posts">
            <td>{{post.title}}</td>
            <td>{{post.tags | csv}}</td>
            <td>{{post.publishDate | date:'dd/MM/yyyy'}}</td>
            <td class="text-center"><i ng-if="post.publish" class="glyphicon glyphicon-ok" /></td>
            <td class="action-icons">
                <a href="#/posts/edit/{{post._id}}" class="btn btn-default"><i class="glyphicon glyphicon-pencil"></i></a>
            </td>
        </tr>
    </table>
</div>
(function () {
    'use strict';

    angular
        .module('app')
        .controller('Redirects.AddEditController', Controller);

    function Controller($stateParams, $location, $filter, RedirectService, AlertService) {
        var vm = this;

        vm.redirect = {};
        vm.saveRedirect = saveRedirect;
        vm.deleteRedirect = deleteRedirect;

        initController();

        function initController() {
            vm.loading = 0;

            if ($stateParams._id) {
                vm.loading += 1;
                RedirectService.GetById($stateParams._id)
                    .then(function (redirect) {
                        vm.loading -= 1;
                        vm.redirect = redirect;
                    });
            }
        }

        function saveRedirect() {
            RedirectService.Save(vm.redirect)
                .then(function () {
                    AlertService.Success('Redirect saved', true);
                    $location.path('/redirects');
                })
                .catch(function (error) {
                    AlertService.Error(error);
                });
        }

        function deleteRedirect() {
            RedirectService.Delete(vm.redirect._id)
                .then(function () {
                    AlertService.Success('Redirect deleted', true);
                    $location.path('/redirects');
                })
                .catch(function (error) {
                    AlertService.Error(error);
                });
        }
    }

})();
<div class="redirects-add-edit">
    <h1>301 Redirect Details</h1>
    <form ng-if="!vm.loading">
        <div class="form-group">
            <label for="from">From (e.g. '/old-url')</label>
            <input type="text" id="from" class="form-control" ng-model="vm.redirect.from" required />
        </div>
        <div class="form-group">
            <label for="to">To (e.g. '/new-url')</label>
            <input type="text" id="to" class="form-control" ng-model="vm.redirect.to" />
        </div>
        <hr />
        <div class="form-group">
            <a class="btn btn-primary" ng-click="vm.saveRedirect()">Save</a>
            <a class="btn btn-default" href="#/redirects">Cancel</a>
            <a class="btn btn-danger" ng-click="vm.deleteRedirect()" ng-show="vm.redirect._id">Delete</a>
        </div>
    </form>
</div>
(function () {
    'use strict';

    angular
        .module('app')
        .controller('Redirects.IndexController', Controller);

    function Controller(RedirectService) {
        var vm = this;

        vm.redirects = [];

        initController();

        function initController() {
            vm.loading = true;
            RedirectService.GetAll()
                .then(function (redirects) {
                    vm.loading = false;
                    vm.redirects = redirects;
                });
        }
    }

})();
<div class="redirects-index">
    <div class="index-header">
        <h1>301 Redirects</h1>
        <div class="buttons">
            <a href="#/redirects/add" class="btn btn-default">
                <i class="glyphicon glyphicon-plus"></i> New Redirect
            </a>
        </div>
    </div>
    <table class="redirects-table table table-striped">
        <tr>
            <th>From</th>
            <th>To</th>
            <th></th>
        </tr>
        <tr ng-repeat="redirect in vm.redirects">
            <td>{{redirect.from}}</td>
            <td>{{redirect.to}}</td>
            <td class="action-icons">
                <a href="#/redirects/edit/{{redirect._id}}" class="btn btn-default"><i class="glyphicon glyphicon-pencil"></i></a>
            </td>
        </tr>
    </table>
</div>
(function () {
    'use strict';

    angular
        .module('app')
        .directive('tags', Directive);

    function Directive($filter) {
        return {
            require: 'ngModel',
            link: function (scope, element, attr, ngModel) {
                // convert to comma separated string for display
                ngModel.$formatters.push(function (tags) {
                    return $filter('csv')(tags);
                });

                // convert to array for storage
                ngModel.$parsers.push(function (tagsString) {
                    var tags = _.map(tagsString.split(','), function (tag) {
                        // trim any extra spaces
                        return tag.trim();
                    });

                    // remove any empty tags
                    tags = _.filter(tags, function (tag) { return tag; });

                    return tags;
                });
            }
        };
    }
})();
(function () {
    'use strict';

    angular
        .module('app')
        .directive('wysiwyg', Directive);

    function Directive($rootScope) {
        return {
            require: 'ngModel',
            link: function (scope, element, attr, ngModel) {
                var editorOptions;
                if (attr.wysiwyg === 'minimal') {
                    // minimal editor
                    editorOptions = {
                        height: 100,
                        toolbar: [
                            { name: 'basic', items: ['Bold', 'Italic', 'Underline'] },
                            { name: 'links', items: ['Link', 'Unlink'] },
                            { name: 'tools', items: ['Maximize'] },
                            { name: 'document', items: ['Source'] },
                        ],
                        removePlugins: 'elementspath',
                        resize_enabled: false,
                        allowedContent: true
                    };
                } else {
                    // regular editor
                    editorOptions = {
                        filebrowserImageUploadUrl: '/api/files/upload',
                        removeButtons: 'About,Form,Checkbox,Radio,TextField,Textarea,Select,Button,ImageButton,HiddenField,Save,CreateDiv,Language,BidiLtr,BidiRtl,Flash,Iframe,addFile,Styles',
                        allowedContent: true
                    };
                }

                // enable ckeditor
                var ckeditor = element.ckeditor(editorOptions);

                // update ngModel on change
                ckeditor.editor.on('change', function () {
                    ngModel.$setViewValue(this.getData());
                });
            }
        };
    }
})();
(function () {
    'use strict';

    angular
        .module('app')
        .filter('csv', Filter);

    function Filter() {
        return function (array) {
            if (!array)
                return;

            return array.toString().replace(/,/g, ', ');
        };
    }
})();
(function () {
    'use strict';

    angular
        .module('app')
        .filter('slugify', Filter);

    function Filter() {
        return function (input) {
            if (!input)
                return;

            // make lower case and trim
            var slug = input.toLowerCase().trim();

            // replace invalid chars with spaces
            slug = slug.replace(/[^a-z0-9\s-]/g, ' ');

            // replace multiple spaces or hyphens with a single hyphen
            slug = slug.replace(/[\s-]+/g, '-');

            return slug;
        };
    }
})();
(function () {
    'use strict';

    angular
        .module('app')
        .factory('AlertService', Service);

    function Service($rootScope) {
        var service = {};

        service.Success = Success;
        service.Error = Error;

        initService();

        return service;

        function initService() {
            $rootScope.$on('$locationChangeStart', function () {
                clearFlashMessage();
            });

            function clearFlashMessage() {
                var flash = $rootScope.flash;
                if (flash) {
                    if (!flash.keepAfterLocationChange) {
                        delete $rootScope.flash;
                    } else {
                        // only keep for a single location change
                        flash.keepAfterLocationChange = false;
                    }
                }
            }
        }

        function Success(message, keepAfterLocationChange) {
            $rootScope.flash = {
                message: message,
                type: 'success', 
                keepAfterLocationChange: keepAfterLocationChange
            };
        }

        function Error(message, keepAfterLocationChange) {
            $rootScope.flash = {
                message: message,
                type: 'danger',
                keepAfterLocationChange: keepAfterLocationChange
            };
        }
    }

})();
(function () {
    'use strict';

    angular
        .module('app')
        .factory('PageService', Service);

    function Service($http, $q) {
        var apiUrl = '/api/pages';
        var service = {};

        service.GetAll = GetAll;
        service.GetById = GetById;
        service.Save = Save;
        service.Delete = Delete;

        return service;

        function GetAll() {
            return $http.get(apiUrl).then(handleSuccess, handleError);
        }

        function GetById(_id) {
            return $http.get(apiUrl + '/' + _id).then(handleSuccess, handleError);
        }

        function Save(page) {
            if (page._id) {
                // update
                return $http.put(apiUrl + '/' + page._id, page).then(handleSuccess, handleError);
            } else {
                // create
                return $http.post(apiUrl, page).then(handleSuccess, handleError);
            }
        }

        function Delete(_id) {
            return $http.delete(apiUrl + '/' + _id).then(handleSuccess, handleError);
        }

        // private functions

        function handleSuccess(res) {
            return res.data;
        }

        function handleError(res) {
            return $q.reject(res.data);
        }
    }

})();
(function () {
    'use strict';

    angular
        .module('app')
        .factory('PostService', Service);

    function Service($http, $q) {
        var apiUrl = '/api/posts';
        var service = {};

        service.GetAll = GetAll;
        service.GetById = GetById;
        service.Save = Save;
        service.Delete = Delete;

        return service;

        function GetAll() {
            return $http.get(apiUrl).then(handleSuccess, handleError);
        }

        function GetById(_id) {
            return $http.get(apiUrl + '/' + _id).then(handleSuccess, handleError);
        }

        function Save(post) {
            if (post._id) {
                // update
                return $http.put(apiUrl + '/' + post._id, post).then(handleSuccess, handleError);
            } else {
                // create
                return $http.post(apiUrl, post).then(handleSuccess, handleError);
            }
        }

        function Delete(_id) {
            return $http.delete(apiUrl + '/' + _id).then(handleSuccess, handleError);
        }

        // private functions

        function handleSuccess(res) {
            return res.data;
        }

        function handleError(res) {
            return $q.reject(res.data);
        }
    }

})();
(function () {
    'use strict';

    angular
        .module('app')
        .factory('RedirectService', Service);

    function Service($http, $q) {
        var apiUrl = '/api/redirects';
        var service = {};

        service.GetAll = GetAll;
        service.GetById = GetById;
        service.Save = Save;
        service.Delete = Delete;

        return service;

        function GetAll() {
            return $http.get(apiUrl).then(handleSuccess, handleError);
        }

        function GetById(_id) {
            return $http.get(apiUrl + '/' + _id).then(handleSuccess, handleError);
        }

        function Save(redirect) {
            if (redirect._id) {
                // update
                return $http.put(apiUrl + '/' + redirect._id, redirect).then(handleSuccess, handleError);
            } else {
                // create
                return $http.post(apiUrl, redirect).then(handleSuccess, handleError);
            }
        }

        function Delete(_id) {
            return $http.delete(apiUrl + '/' + _id).then(handleSuccess, handleError);
        }

        // private functions

        function handleSuccess(res) {
            return res.data;
        }

        function handleError(res) {
            return $q.reject(res.data);
        }
    }

})();
(function () {
    'use strict';

    angular
        .module('app')
        .factory('UserService', Service);

    function Service($http, $q) {
        var service = {};

        service.GetCurrent = GetCurrent;
        service.Update = Update;

        return service;

        function GetCurrent() {
            return $http.get('/api/users/current').then(handleSuccess, handleError);
        }

        function Update(user) {
            return $http.put('/api/users/' + user._id, user).then(handleSuccess, handleError);
        }

        // private functions

        function handleSuccess(res) {
            return res.data;
        }

        function handleError(res) {
            return $q.reject(res.data);
        }
    }

})();
A Fake JWT Token