Back to blog
Blog

How to Build an AngularJS Events App

Dmytro Kutsaniuk's avatar

Dmytro Kutsaniuk

May 11, 2017

cover image

In this tutorial I'm going to show you how to create an "Events" app using a little bit of Node, Angular JS and Cosmic. For the sake of understanding how to consume Restful API’s, this tutorial will show how to make simple AJAX requests to the Cosmic API in order to retrieve, update, and delete data in our Cosmic Bucket. Let's get started.

TL;DR

Download the GitHub repo.
Check out the demo.

Getting Started:

First, let’s make a new directory to build our project in and lets also make a package.json file.

mkdir events-app
events-app$ touch package.json

Now, in your package.json, copy and paste the code below:

//events-app/package.json
{
  "name": "events-app",
  "version": "1.0.0",
  "main": "app-server.js",
  "engines": {
    "node": "4.1.2",
    "npm": "3.5.2"
  },
  "description": "",
  "dependencies": {
    "bower": "^1.7.7",
    "http-server": "^0.9.0",
    "gulp": "^3.9.1",
    "gulp-autoprefixer": "^3.1.0",
    "gulp-concat": "^2.6.0",
    "gulp-concat-css": "^2.2.0",
    "gulp-minify-css": "^1.2.4",
    "gulp-webserver": "^0.9.1",
    "wiredep": "^3.0.0",
    "express": "^4.13.3"
  },
  "scripts": {
    "postinstall": "bower install",
    "start": "npm run production",
    "production": "node app-server.js"
  },
  "author": "",
  "license": "ISC"
}
            

Second, let’s make a bower.json file.

events-app$ touch bower.json

Now, in your bower.json, copy and paste the code below:

//events-app/bower.json
{
  "name": "events-app",
  "description": "Events App",
  "version": "0.0.0",
  "homepage": "https://github.com/cosmicjs/events-app",
  "license": "MIT",
  "private": true,
  "dependencies": {
    "angular": "~1.4.x",
    "angular-mocks": "~1.4.x",
    "angular-bootstrap": "~1.1.x",
    "angular-cookies": "~1.4.x",
    "angular-route": "~1.4.x",
    "angular-ui-router": "0.2.x",
    "angular-resource": "1.4.x",
    "angular-animate": "~1.4.x",
    "ng-dialog": "0.6.1",
    "bootstrap": "3.3.x",
    "cr-acl": "",
    "angular-chosen-localytics": "*",
    "bootstrap-chosen": "*",
    "ng-flow": "^2.7.4",
    "angular-mask": "*",
    "checklist-model": "0.9.0",
    "angular-ui-notification": "^0.2.0",
    "angular-ui-calendar": "^1.0.2",
    "angular-ui-switch": "^0.1.1",
    "ng-scrollbars": "^0.0.11",
    "jquery.scrollbar": "*",
    "angular-nvd3": "*",
    "infinity-angular-chosen": "^0.2.0",
    "angular-flash-alert": "^2.4.0",
    "components-font-awesome": "^4.7.0",
    "textAngular": "^1.5.16",
    "angular-loading-bar": "^0.9.0"
  },
  "resolutions": {
    "angular": "~1.4.x"
  },
  "devDependencies": {
    "cr-acl": "^0.5.0"
  }
}
            

Config app server:

events-app$ touch app-server.js
//events-app/app-server.js
var express = require('express')
var app = express()
app.set('port', process.env.PORT || 3000)
app.use(express.static(__dirname))
var http = require('http').Server(app)
// Route
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html')
})
http.listen(app.get('port'), () => {
  console.log('Wedding Site listening on ' + app.get('port'))
})
            

What we're installing and why:

  1. We're going to use the AngularJS framework to a build single-page application.
  2. We're installing angular-ui-router for create multi views.
  3. We are going to use gulp for build all js and css files into one file.

Building our app:

Now we're going to build out our file structure a bit more so that we can organize our angular modules and js files. This is what our events-app directory should look like:

events-app
|----app
|       |----auth
|                 |----auth.ctrl.js
|                 |----auth.service.js
|       |----config
|                 |----config.js
|       |----event
|                 |----add
|                           |----event.add.ctrl.js
|                           |----event.add.mdl.js
|                 |----feed
|                           |----event.feed.ctrl.js
|                           |----event.feed.mdl.js
|                 |----profile
|                           |----event.profile.ctrl.js
|                           |----event.profile.mdl.js
|                 |----event.ctrl.js
|                 |----event.mdl.js
|                 |----event.service.js
|       |----user
|                 |----profile
|                           |----user.profile.ctrl.js
|                           |----user.profile.mdl.js
|                 |----settings
|                           |----user.settings.ctrl.js
|                           |----user.settings.mdl.js
|                 |----user.ctrl.js
|                 |----user.mdl.js
|                 |----user.service.js
|       |----main.mdl.js
|----dist
|       |----css
|       |----img
|       |----js
|----css
|----views
|----gulpfile.js
|----app-server.js
|----bower.json
|----package.json
            

Now we we will set up our index.html. Copy and paste the following code into your index.html file:

Here, we are going to target our "root" view to place our angular modules in later. The main.js file located in our dist directory is what our gulpfile.js file will spit out after bundling all of our angular modules.  Now, set up our gulpfile.js file to bundle all of our js files and export that bundle file to our dist directory. Copy the following code into your gulpfile.js file:

//events-app/gulpfile.js
'use strict';

var gulp = require('gulp'),
    webserver = require('gulp-webserver'),
    minifyCSS = require('gulp-minify-css'),
    concatCss = require('gulp-concat-css'),
    concat = require('gulp-concat'),
    wiredep = require('wiredep').stream,
    autoprefixer = require('gulp-autoprefixer');

gulp.task('css', function () {
  return gulp.src('css/**/*.css')
    .pipe(minifyCSS())
    .pipe(concat('main.min.css'))
    .pipe(autoprefixer())
    .pipe(gulp.dest('dist/css'));
});

gulp.task('js', function() {
  return gulp.src('app/**/**/*.js')
    .pipe(concat('main.js'))
    .pipe(gulp.dest('dist/js/'));
});

gulp.task('default', function () {
  gulp.watch('css/**/*.css', ['css']);
  gulp.watch('app/**/**/*.js', ['js']);
  gulp.watch('bower.json', ['bower']);
});

gulp.task('bower', function () {
  gulp.src('index.html')
    .pipe(wiredep({
      directory: 'bower_components'
    }))
    .pipe(gulp.dest(''));
});
            

After that we can create config.js. Copy and paste the following code into your app/config/config.js file:

(function () {
    'use strict';

    var app = angular
                .module('main');

    app.constant('BUCKET_SLUG', 'your-bucket-slug');
    app.constant('URL', 'https://api.cosmicjs.com/v1/');
    app.constant('MEDIA_URL', 'https://api.cosmicjs.com/v1/your-bucket-slug/media');
    app.constant('READ_KEY', 'your-read-key');
    app.constant('WRITE_KEY', 'your-write-key');
    app.constant('DEFAULT_EVENT_IMAGE', 'url-image');

})();
            

After that we can create our main module. Copy and paste the following code into the app/main.mdl.js file:

(function () {
    'use strict';

    angular
        .module('main', [
            'ui.router',
            'ui.bootstrap',
            'ngMask',
            'ngCookies',
            'ngRoute',
            'ngDialog',
            'cr.acl',
            'ui-notification',
            'ngFlash',
            'textAngular',
            'flow',
            'angular-loading-bar',

            'event',
            'user'
        ])
        .config(config)
        .run(run);

    config.$inject = ['$stateProvider', '$urlRouterProvider', 'cfpLoadingBarProvider', 'WRITE_KEY'];
    function config($stateProvider, $urlRouterProvider, cfpLoadingBarProvider, WRITE_KEY) {
        cfpLoadingBarProvider.includeSpinner = false;

        $urlRouterProvider.otherwise(function ($injector) {
            var $state = $injector.get("$state");
            var $location = $injector.get("$location");
            var crAcl = $injector.get("crAcl");

            var state = "";

            switch (crAcl.getRole()) {
                case 'ROLE_USER':
                    state = 'main.event.feed';
                    break;
            }

            if (state) $state.go(state);
            else $location.path('/login');
        });

        $stateProvider
            .state('main', {
                url: '/',
                abstract: true,
                templateUrl: '../views/main.html',
                controller: 'UserCtrl as global',
                data: {
                    is_granted: ['ROLE_USER']
                }
            })
            .state('auth', {
                url: '/login',
                templateUrl: '../views/auth/login.html',
                controller: 'AuthCtrl as auth',
                onEnter: ['AuthService', function(AuthService) {
                    AuthService.clearCredentials();
                }],
                data: {
                    is_granted: ['ROLE_GUEST']
                }
            });
    }

    run.$inject = ['$rootScope', '$cookieStore', '$http', 'crAcl', 'AuthService'];
    function run($rootScope, $cookieStore, $http, crAcl, AuthService) {

        $rootScope.globals = $cookieStore.get('globals') || {};
        $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

        crAcl
            .setInheritanceRoles({
                'ROLE_SUPER_ADMIN': ['ROLE_SUPER_ADMIN', 'ROLE_GUEST'],
                'ROLE_USER': ['ROLE_USER', 'ROLE_GUEST'],
                'ROLE_GUEST': ['ROLE_GUEST']
            });

        crAcl
            .setRedirect('auth');

        if ($rootScope.globals.currentUser) {

            crAcl.setRole($rootScope.globals.currentUser.metadata.role);
        }
        else {
            crAcl.setRole();
        }

    }

})();
            

Now we we will set up our Auth Controller. Copy and paste the following code into your auth.ctrl.js file:

(function () {
    'use strict';

    angular
        .module('main')
        .controller('AuthCtrl', AuthCtrl);

    function AuthCtrl(crAcl, $state, AuthService, Flash, $log) {
        var vm = this;

        vm.login = login;
        vm.register = register;

        vm.showRegisterForm = false;

        vm.loginForm = null;
        vm.registerForm = null;

        vm.credentials = {};
        vm.user = {};

        function login(credentials) {
            function success(response) {
                function success(response) {
                    if (response.data.status !== 'empty') {
                        var currentUser = response.data.objects[0];

                        crAcl.setRole(currentUser.metadata.role);
                        AuthService.setCredentials(currentUser);
                        $state.go('main.event.feed');
                    }
                    else
                        Flash.create('danger', 'Incorrect username or password');
                }

                function failed(response) {
                    $log.error(response);
                }

                if (response.data.status !== 'empty')
                    AuthService
                        .checkPassword(credentials)
                        .then(success, failed);
                else
                    Flash.create('danger', 'Incorrect username or password');

                $log.info(response);
            }

            function failed(response) {
                $log.error(response);
            }

            if (vm.loginForm.$valid)
                AuthService
                    .checkUsername(credentials)
                    .then(success, failed);
        }

        function register(credentials) {
            function success(response) {
                $log.info(response);

                var currentUser = response.data.object.metafields;

                Flash.create('success', 'You have successfully signed up!');
                vm.credentials = {
                    username: currentUser[0].value,
                    password: currentUser[3].value
                };
                vm.showRegisterForm = false;
            }

            function failed(response) {
                $log.error(response);
            }

            if (vm.registerForm.$valid)
                AuthService
                    .register(credentials)
                    .then(success, failed);
        }

    }
})();
            

Now we will create our Auth Service in app/auth/auth.service.js:

(function () {
    'use strict';

    angular
        .module('main')
        .service('AuthService', function ($http,
                                          $cookieStore,
                                          $q,
                                          $rootScope,
                                          URL, BUCKET_SLUG, READ_KEY, WRITE_KEY) {
            var authService = this;
            $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

            authService.checkUsername = function (credentials) {
                return $http.get(URL + BUCKET_SLUG + '/object-type/users/search', {
                    params: {
                        metafield_key: 'username',
                        metafield_value_has: credentials.username,
                        limit: 1,
                        read_key: READ_KEY
                    }
                });
            };
            authService.checkPassword = function (credentials) {
                return $http.get(URL + BUCKET_SLUG + '/object-type/users/search', {
                    ignoreLoadingBar: true,
                    params: {
                        metafield_key: 'password',
                        metafield_value: credentials.password,
                        limit: 1,
                        read_key: READ_KEY
                    }
                });
            };
            authService.register = function (user) {

                return $http.post(URL + BUCKET_SLUG + '/add-object', {
                    title: user.full_name,
                    type_slug: 'users',
                    slug: user.username,
                    metafields: [
                        {
                            key: "username",
                            type: "text",
                            value: user.username
                        },
                        {
                            key: "email",
                            type: "text",
                            value: user.email
                        },
                        {
                            key: "full_name",
                            type: "text",
                            value: user.full_name
                        },
                        {
                            key: "password",
                            type: "text",
                            value: user.password
                        },
                        {
                            key: "image",
                            type: "file",
                            value: "3b2180f0-2c40-11e7-85ac-e98751218524-1493421969_male.png"
                        },
                        {
                            key: "role",
                            type: "radio-buttons",
                            options: [
                                {
                                    value: "ROLE_USER"
                                },
                                {
                                    value: "ROLE_SUPER_ADMIN"
                                }
                            ],
                            value: "ROLE_USER"
                        }
                    ],

                    write_key: WRITE_KEY
                });
            };
            authService.setCredentials = function (user) {
                $rootScope.globals = {
                    currentUser: user
                };

                $cookieStore.put('globals', $rootScope.globals);
            };
            authService.clearCredentials = function () {
                var deferred = $q.defer();
                $cookieStore.remove('globals');

                if (!$cookieStore.get('globals')) {
                    $rootScope.globals = {};
                    deferred.resolve('Credentials clear success');
                } else {
                    deferred.reject('Can\'t clear credentials');
                }

                return deferred.promise;
            };
        });
})();
            

What's going on here:

  1. We are using the ui-router for config routes.
  2. We created Auth Service for our asynchronous calls to our Cosmic API.
  3. We created Auth Controller for checking credentials.

Create User Service for get and update User, copy and paste the code below:

(function () {
    'use strict';

    angular
        .module('main')
        .service('UserService', function ($http,
                                          $cookieStore,
                                          $q,
                                          $rootScope,
                                          URL, BUCKET_SLUG, READ_KEY, WRITE_KEY) {
            $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

            this.getCurrentUser = function (ignoreLoadingBar) {
                return $http.get(URL + BUCKET_SLUG + '/object/' + $rootScope.globals.currentUser.slug, {
                    ignoreLoadingBar: ignoreLoadingBar,
                    params: {
                        read_key: READ_KEY
                    }
                });
            };
            this.getUser = function (slug, ignoreLoadingBar) {
                return $http.get(URL + BUCKET_SLUG + '/object/' + slug, {
                    ignoreLoadingBar: ignoreLoadingBar,
                    params: {
                        read_key: READ_KEY
                    }
                });
            };
            this.updateUser = function (user) {
                user.write_key = WRITE_KEY;

                return $http.put(URL + BUCKET_SLUG + '/edit-object', user, {
                    ignoreLoadingBar: false
                });
            };

        });
})();
            

Create User Controller for get current user and log out, copy and paste the code below:

(function () {
    'use strict';

    angular
        .module('main')
        .controller('UserCtrl', UserCtrl);

    function UserCtrl($rootScope, $scope, $state, AuthService, Flash, $log) {
        var vm = this;

        vm.currentUser = $rootScope.globals.currentUser.metadata;

        vm.logout = logout;

        function logout() {
            function success(response) {
                $state.go('auth');

                $log.info(response);
            }

            function failed(response) {
                $log.error(response);
            }

            AuthService
                .clearCredentials()
                .then(success, failed);
        }

        $scope.state = $state;

    }
})();
            

Next we will create our Create User Module:

(function () {
    'use strict';

    angular
        .module('user', [
            'user.profile',
            'user.settings'
        ])
        .config(config);

    config.$inject = ['$stateProvider', '$urlRouterProvider'];
    function config($stateProvider, $urlRouterProvider) {

        $stateProvider
            .state('main.user', {
                url: 'user',
                abstract: true,
                data: {
                    is_granted: ['ROLE_USER']
                }
            });
    }
})();
            

What's going on here:

  1. We created User Service for our asynchronous calls to our Cosmic API.
  2. We created User Controller for getting current user and logout.

Next we will create our Event Service to get, update, add delete Events from the Cosmic API:

(function () {
    'use strict';

    angular
        .module('main')
        .service('EventService', function ($http,
                                          $cookieStore,
                                          $q,
                                          $rootScope,
                                          URL, BUCKET_SLUG, READ_KEY, WRITE_KEY, MEDIA_URL) {

            $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

            this.getEvents = function () {
                return $http.get(URL + BUCKET_SLUG + '/object-type/events', {
                    params: {
                        limit: 100,
                        read_key: READ_KEY
                    }
                });
            };
            this.getEventsByUsername = function (username, ignoreLoadingBar) {
                return $http.get(URL + BUCKET_SLUG + '/object-type/events/search',
                    {
                        ignoreLoadingBar: ignoreLoadingBar,
                        params: {
                            metafield_key: 'user',
                            metafield_object_slug: username,
                            limit: 10,
                            read_key: READ_KEY
                        }
                    }
                );
            };
            this.getEventById = function (slug) {
                return $http.get(URL + BUCKET_SLUG + '/object/' + slug, {
                    params: {
                        read_key: READ_KEY
                    }
                });
            };
            this.updateEvent = function (event) {
                event.write_key = WRITE_KEY;

                return $http.put(URL + BUCKET_SLUG + '/edit-object', event);
            };
            this.removeEvent = function (slug) {
                return $http.delete(URL + BUCKET_SLUG + '/' + slug, {
                    ignoreLoadingBar: true,
                    headers:{
                        'Content-Type': 'application/json'
                    },
                    data: {
                        write_key: WRITE_KEY
                    }
                });
            };
            this.createEvent = function (event) {
                event.write_key = WRITE_KEY;

                var beginDate = new Date(event.metafields[1].value);
                var endDate = new Date(event.metafields[2].value);

                event.metafields[1].value = beginDate.getFullYear() + '-' + (beginDate.getMonth() + 1) + '-' + beginDate.getDate();
                event.metafields[2].value = endDate.getFullYear() + '-' + (beginDate.getMonth() + 1) + '-' + endDate.getDate();

                event.slug = event.title;
                event.type_slug = 'events';

                event.metafields[4] = {
                    key: "user",
                    type: "object",
                    object_type: "users",
                    value: $rootScope.globals.currentUser._id
                };
                return $http.post(URL + BUCKET_SLUG + '/add-object', event);
            };
            this.upload = function (file) {
                var fd = new FormData();
                fd.append('media', file);
                fd.append('write_key', WRITE_KEY);

                var defer = $q.defer();

                var xhttp = new XMLHttpRequest();

                xhttp.upload.addEventListener("progress",function (e) {
                    defer.notify(parseInt(e.loaded * 100 / e.total));
                });
                xhttp.upload.addEventListener("error",function (e) {
                    defer.reject(e);
                });

                xhttp.onreadystatechange = function() {
                    if (xhttp.readyState === 4) {
                        defer.resolve(JSON.parse(xhttp.response)); //Outputs a DOMString by default
                    }
                };

                xhttp.open("post", MEDIA_URL, true);

                xhttp.send(fd);

                return defer.promise;
            }
        });
})();
            

Our Event Controller will get all events and remove events:

(function () {
    'use strict';

    angular
        .module('main')
        .controller('EventCtrl', EventCtrl);

    function EventCtrl(EventService, Notification, $log, $rootScope, DEFAULT_EVENT_IMAGE) {
        var vm = this;

        vm.getEvents = getEvents;
        vm.removeEvent = removeEvent;
        vm.DEFAULT_EVENT_IMAGE = DEFAULT_EVENT_IMAGE;

        function getEvents(username) {
            function success(response) {
                $log.info(response);

                vm.events = response.data.objects;
            }

            function failed(response) {
                $log.error(response);
            }
            console.log(username);

            EventService
                .getEventsByUsername(username)
                .then(success, failed);
        }

        function removeEvent(slug) {
            function success(response) {
                $log.info(response);

                getEvents($rootScope.globals.currentUser.metadata.username);

                Notification.success('Deleted');
            }

            function failed(response) {
                Notification.error(response.data.message);

                $log.error(response);
            }

            EventService
                .removeEvent(slug)
                .then(success, failed);
        }
    }
})();
            

Our Event Module will render our events view:

(function () {
    'use strict';

    angular
        .module('event', [
            'event.profile',
            'event.feed',
            'event.add'
        ])
        .config(config);

    config.$inject = ['$stateProvider', '$urlRouterProvider'];
    function config($stateProvider, $urlRouterProvider) {

        $stateProvider
            .state('main.event', {
                url: 'events',
                views: {
                    '': {
                        templateUrl: '../views/event/events.html',
                        controller: 'EventCtrl as vm'
                    }
                },
                data: {
                    is_granted: ['ROLE_USER']
                }
            });
    }

})();
            

What's going on here:

  1. We created Event Service for our asynchronous calls to our Cosmic API. We can create, update, remove and getting Events.
  2. We created Event Controller for getting all events and removing events.
  3. We created an Event Module to render our views.

Now let's create our Event Add Controller for adding events:

(function () {
    'use strict';

    angular
        .module('main')
        .controller('EventAddCtrl', EventAddCtrl);

    function EventAddCtrl(EventService, Notification, $state, $log, $scope, MEDIA_URL, DEFAULT_EVENT_IMAGE, $timeout) {
        var vm = this;

        vm.createEvent = createEvent;
        vm.cancelUpload = cancelUpload;
        vm.upload = upload;

        vm.dateBeginPicker = false;
        vm.dateEndPicker = false;
        vm.contentEditor = true;
        vm.uploadProgress = 0;

        vm.event = {
            title: null,
            slug: null,
            content: null,
            metafields: [
                {
                    key: "image",
                    type: "file",
                    value: null
                },
                {
                    key: "date_begin",
                    type: "date",
                    value: null
                },
                {
                    key: "date_end",
                    type: "date",
                    value: null
                },
                {
                    key: "type",
                    type: "select-dropdown",
                    options: [
                        {
                            key: "social",
                            value: "Social"
                        },
                        {
                            key: "fun",
                            value: "Fun"
                        }
                    ],
                    value: "Social"
                }
            ]
        };

        $timeout(function() {
            vm.event.metafields[1].value = new Date();
            vm.event.metafields[2].value = new Date();
        }, 100);

        vm.flow = {};
        vm.background = {
            'background-image': 'url(' + DEFAULT_EVENT_IMAGE + ')'
        };

        vm.flowConfig = {
            target: MEDIA_URL,
            singleFile: true
        };

        function createEvent() {
            if (vm.flow.files[0])
                upload();
            else
                _createEvent(vm.event);
        }

        function _createEvent(event) {
            function success(response) {
                $log.info(response);

                Notification.success(
                    {
                        message: 'Created',
                        delay: 800,
                        replaceMessage: true
                    }
                );

                $state.go('main.event');
            }

            function failed(response) {
                Notification.error(
                    {
                        message: response.data.error,
                        delay: 4000,
                        replaceMessage: true
                    }
                );

                $log.error(response);
            }

            EventService
                .createEvent(event)
                .then(success, failed);
        }

        function cancelUpload() {
            vm.flow.cancel();
            vm.background = {
                'background-image': 'url(' + DEFAULT_EVENT_IMAGE.url + ')'
            };
        }

        $scope.$watch('vm.flow.files[0].file.name', function () {
            if (!vm.flow.files[0]) {
                return ;
            }
            var fileReader = new FileReader();
            fileReader.readAsDataURL(vm.flow.files[0].file);
            fileReader.onload = function (event) {
                $scope.$apply(function () {
                    vm.background = {
                        'background-image': 'url(' + event.target.result + ')'
                    };
                });
            };
        });

        function upload() {
            EventService
                .upload(vm.flow.files[0].file)
                .then(function(response){

                    vm.event.metafields[0].value = response.media.name;

                    createEvent(vm.event);

                    vm.flow.cancel();
                    vm.uploadProgress = 0;

                }, function(){
                    console.log('failed :(');
                }, function(progress){
                    vm.uploadProgress = progress;
                });

        }

    }
})();
            

Next we create the Event Module:

(function () {
    'use strict';

    angular
        .module('event.add', [])
        .config(config);

    config.$inject = ['$stateProvider', '$urlRouterProvider'];
    function config($stateProvider, $urlRouterProvider) {

        $stateProvider
            .state('main.event.add', {
                url: '/add',
                views: {
                    '@main': {
                        templateUrl: '../views/event/event.profile.html',
                        controller: 'EventAddCtrl as vm'
                    }
                },
                data: {
                    is_granted: ['ROLE_USER']
                }
            });
    }

})();
            

What's going on here:

  1. We can add events.
  2. We can upload images.

Conclusion:

We were able to create a pretty complicated app to manage users, sessions, adding / editing events all through the Cosmic API. I hope you enjoyed this tutorial as much as I did, if you have any questions reach out to us on Twitter and join our community on Slack.