├── .gitignore ├── LICENSE ├── README.md ├── assets ├── Book Later Popover.png ├── Book Now Popover.png ├── Main.png ├── Popover.png ├── app_logo.png ├── logo │ ├── Slack_Mark_Web.png │ ├── estimote-logo-square.de0bd239.png │ └── slack_rgb.png └── slackbooking.sketch ├── client ├── .csslintrc ├── .eslintrc ├── .npmignore ├── README.md ├── app │ ├── common │ │ ├── img │ │ │ ├── app_logo.png │ │ │ ├── large.png │ │ │ ├── medium.png │ │ │ ├── slack-login.png │ │ │ └── small.png │ │ ├── index.js │ │ ├── libs │ │ │ ├── index.js │ │ │ └── ng-cordova-beacon.js │ │ └── util │ │ │ ├── index.js │ │ │ └── services │ │ │ └── $exceptionHandler.js │ ├── entry-template.html │ ├── home │ │ ├── bookingModal.html │ │ ├── home.controller.js │ │ ├── home.html │ │ ├── index.js │ │ ├── room.service.js │ │ ├── states.js │ │ └── style.scss │ ├── index.js │ ├── layout.html │ ├── states.js │ ├── style.scss │ └── variable.scss ├── config.xml ├── gulpfile.js ├── hooks │ ├── README.md │ └── after_prepare │ │ └── 010_add_platform_class.js ├── package.json ├── plugins │ ├── android.json │ ├── cordova-plugin-transport-security │ │ ├── README.md │ │ ├── package.json │ │ └── plugin.xml │ ├── fetch.json │ └── ios.json ├── res │ ├── icon │ │ └── icon.png │ └── screen │ │ ├── android │ │ ├── screen-hdpi-portrait.png │ │ ├── screen-ldpi-portrait.png │ │ ├── screen-mdpi-portrait.png │ │ └── screen-xhdpi-portrait.png │ │ ├── ios │ │ ├── Default-667h.png │ │ ├── Default-736h.png │ │ ├── Default-Portrait@2x~ipad.png │ │ ├── screen-ipad-portrait-2x.png │ │ ├── screen-ipad-portrait.png │ │ ├── screen-iphone-portrait-2x.png │ │ ├── screen-iphone-portrait-568h-2x.png │ │ └── screen-iphone-portrait.png │ │ └── screen.png ├── webpack.config.js └── www │ ├── app.js │ ├── app.js.map │ ├── font │ ├── 05acfdb568b3df49ad31355b19495d4a.woff │ ├── 24712f6c47821394fba7942fbb52c3b2.ttf │ ├── 2c2ae068be3b089e0a5b59abb1831550.eot │ └── 621bd386841f74e0053cb8e67f8a0604.svg │ ├── img │ ├── 4021dd335fcfb21c9b3623ba15f532a3.png │ ├── 9b9c0c8a6872708ff7e672eb67fd5ea0.png │ └── f48708d60d03f89e15f391d2938d6ee3.png │ └── index.html ├── package.json └── server ├── README.md ├── package.json └── src ├── index.js ├── models ├── bookings.js └── rooms.js ├── plugins ├── bookings │ ├── bookings.js │ └── index.js ├── rooms │ ├── index.js │ └── rooms.js └── slack │ ├── index.js │ └── slack.js └── services └── slack.js /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | .idea 28 | .gradle 29 | local.properties 30 | 31 | # node.js 32 | # 33 | node_modules/ 34 | npm-debug.log 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 francois 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slack Booking 2 | 3 | Concept app for RealityHack #3: Reinvent the Workplace 4 | 5 | Meeting room booking system using Estimote Beacons and the Slack API. 6 | 7 | View README files within the 'client' or 'server' directory for running the demos. 8 | -------------------------------------------------------------------------------- /assets/Book Later Popover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/assets/Book Later Popover.png -------------------------------------------------------------------------------- /assets/Book Now Popover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/assets/Book Now Popover.png -------------------------------------------------------------------------------- /assets/Main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/assets/Main.png -------------------------------------------------------------------------------- /assets/Popover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/assets/Popover.png -------------------------------------------------------------------------------- /assets/app_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/assets/app_logo.png -------------------------------------------------------------------------------- /assets/logo/Slack_Mark_Web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/assets/logo/Slack_Mark_Web.png -------------------------------------------------------------------------------- /assets/logo/estimote-logo-square.de0bd239.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/assets/logo/estimote-logo-square.de0bd239.png -------------------------------------------------------------------------------- /assets/logo/slack_rgb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/assets/logo/slack_rgb.png -------------------------------------------------------------------------------- /assets/slackbooking.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/assets/slackbooking.sketch -------------------------------------------------------------------------------- /client/.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "adjoining-classes": false, 3 | "box-sizing": false, 4 | "box-model": false, 5 | "compatible-vendor-prefixes": false, 6 | "floats": false, 7 | "font-sizes": false, 8 | "gradients": false, 9 | "important": false, 10 | "known-properties": false, 11 | "outline-none": false, 12 | "qualified-headings": false, 13 | "regex-selectors": false, 14 | "shorthand": false, 15 | "text-indent": false, 16 | "unique-headings": false, 17 | "universal-selector": false, 18 | "unqualified-attributes": false 19 | } 20 | -------------------------------------------------------------------------------- /client/.eslintrc: -------------------------------------------------------------------------------- 1 | // "parser": "babel-eslint", 2 | // "plugins": [ 3 | // "angular" 4 | // ], 5 | 6 | { 7 | "env": { 8 | "browser": true, 9 | "node": true, 10 | "es6": false 11 | }, 12 | "globals": { 13 | "__DEV__": true, 14 | "__SERVER__": true 15 | }, 16 | "ecmaFeatures": { 17 | "jsx": false 18 | }, 19 | "rules": { 20 | // Strict mode 21 | "strict": 0, 22 | 23 | // Code style 24 | "indent": [2, 2], 25 | "quotes": [2, "single"], 26 | "no-unused-vars": 0, 27 | "no-underscore-dangle": 0 28 | } 29 | } -------------------------------------------------------------------------------- /client/.npmignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | /node_modules 3 | /dist 4 | /.idea 5 | *.iml 6 | /.idea/workspace.xml 7 | /platforms 8 | /plugins 9 | .DS_Store -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | #Slack booking - Client side 2 | 3 | ## Intro 4 | 5 | The client side of the slack booking app has been developed in Javascript to make the application available on Ios and Android with Cordova. 6 | 7 | The application will let you: 8 | 9 | * See the list of the meeting room near you (Beacons Technology with Estimote) 10 | * Filter the meeting rooms by availability 11 | * See information like assets, capacity and distance when available. 12 | * Book a meeting room and let your colleagues get a slack notification 13 | 14 | ## Libraries 15 | 16 | The application has been developed with: 17 | 18 | * Angularjs 1.4 19 | * ionic-sdk 20 | * webpack 21 | * gulp 22 | * cordova plugins 23 | 24 | ## How to get started 25 | 26 | Install Cordova and gulp 27 | 28 | ``` 29 | npm i cordova gulp -g 30 | ``` 31 | 32 | From the app folder, install all dependencies 33 | 34 | ``` 35 | npm i 36 | ``` 37 | 38 | ### Test the app 39 | 40 | To test the application on browser 41 | 42 | ``` 43 | gulp watch 44 | ``` 45 | 46 | **NB:** to make sure you won't get a cross-domain issue, webpack is configured with a proxy to redirect all `/api/` calls to the slack booking server side. Just change the line 57 of `gulpfile.js` with your own slackbooking server side. 47 | 48 | ### Build the app 49 | 50 | ``` 51 | gulp build 52 | ``` 53 | 54 | To build for ios: 55 | 56 | ``` 57 | cordova platform add ios 58 | cordova run ios --device 59 | ``` 60 | 61 | ## Limitation 62 | 63 | So far, Only IOS 8 has been used and tested to build the mobile application. Some adjustment needs to be done to adapt Android with the new realise of Cordova 6. 64 | -------------------------------------------------------------------------------- /client/app/common/img/app_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/app/common/img/app_logo.png -------------------------------------------------------------------------------- /client/app/common/img/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/app/common/img/large.png -------------------------------------------------------------------------------- /client/app/common/img/medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/app/common/img/medium.png -------------------------------------------------------------------------------- /client/app/common/img/slack-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/app/common/img/slack-login.png -------------------------------------------------------------------------------- /client/app/common/img/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/app/common/img/small.png -------------------------------------------------------------------------------- /client/app/common/index.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | /** 5 | * Module dependencies 6 | */ 7 | var angular = require('angular'); 8 | 9 | module.exports = angular 10 | 11 | .module('common', [ 12 | require('./libs').name, 13 | require('./util').name 14 | ]); 15 | -------------------------------------------------------------------------------- /client/app/common/libs/index.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | /** 5 | * Module dependencies 6 | */ 7 | require('angular'); 8 | 9 | // set the public path 10 | var scripts = global.document.getElementsByTagName('script'); 11 | var src = scripts[scripts.length - 1].getAttribute('src'); 12 | global.__webpack_public_path__ = src.substr(0, src.lastIndexOf('/') + 1); 13 | 14 | // Add Angular/Ionic dependencies 15 | require('angular-animate'); 16 | require('angular-sanitize'); 17 | require('angular-ui-router'); 18 | require('ionic-npm/js/ionic'); 19 | require('ionic-npm/js/ionic-angular'); 20 | require('ng-cordova'); 21 | 22 | var libsModule = module.exports = angular 23 | 24 | .module('common.libs', [ 25 | 'ionic', 26 | 'ngCordova', 27 | require('./ng-cordova-beacon').name 28 | ]) 29 | 30 | .run(function ($rootScope, $window) { 31 | $window.addEventListener('resize', function () { 32 | $rootScope.$broadcast('windowResize'); 33 | }); 34 | }); 35 | 36 | libsModule.ionicBootstrap = function (module, window) { 37 | if (!window || !window.document) { 38 | throw new Error('window and document objects required.'); 39 | } 40 | 41 | function onDeviceReady () { 42 | // bootstrap angular app 43 | angular.element(window.document).ready(function () { 44 | angular.bootstrap(window.document, [ 45 | module.name 46 | ]); 47 | }); 48 | 49 | // remove document deviceready listener 50 | window.document.removeEventListener('deviceready', onDeviceReady, false); 51 | } 52 | 53 | function onWindowLoad () { 54 | if (!(!window.cordova && !window.PhoneGap && !window.phonegap)) { 55 | // when on device add document deviceready listener 56 | window.document.addEventListener('deviceready', onDeviceReady, false); 57 | 58 | } else { 59 | // when on browser trigger onDeviceReady 60 | onDeviceReady(); 61 | } 62 | 63 | // remove window load listener 64 | window.removeEventListener('load', onWindowLoad, false); 65 | } 66 | 67 | // add window load listener 68 | window.addEventListener('load', onWindowLoad, false); 69 | }; 70 | -------------------------------------------------------------------------------- /client/app/common/libs/ng-cordova-beacon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ng-cordova-beacon 3 | * 4 | * Author: Nic Raboy - https://blog.nraboy.com 5 | */ 6 | module.exports = 7 | angular.module("ngCordovaBeacon", []) 8 | 9 | .factory("$cordovaBeacon", ["$rootScope", function($rootScope) { 10 | 11 | document.addEventListener("deviceready", onDeviceReady, false); 12 | 13 | function onDeviceReady() { 14 | 15 | var delegate = window.cordova ? new window.cordova.plugins.locationManager.Delegate() : {}; 16 | 17 | /* 18 | * Tells the delegate that one or more beacons are in range 19 | */ 20 | delegate.didRangeBeaconsInRegion = function(pluginResult) { 21 | $rootScope.$broadcast("$cordovaBeacon:didRangeBeaconsInRegion", pluginResult); 22 | }; 23 | 24 | delegate.didStartMonitoringForRegion = function(pluginResult) { 25 | $rootScope.$broadcast("$cordovaBeacon:didStartMonitoringForRegion", pluginResult); 26 | }; 27 | 28 | delegate.didDetermineStateForRegion = function(pluginResult) { 29 | $rootScope.$broadcast("$cordovaBeacon:didDetermineStateForRegion", pluginResult); 30 | }; 31 | 32 | delegate.didEnterRegion = function(pluginResult) { 33 | $rootScope.$broadcast('$cordovaBeacon:didEnterRegion', pluginResult); 34 | }; 35 | 36 | delegate.didExitRegion = function(pluginResult) { 37 | $rootScope.$broadcast('$cordovaBeacon:didExitRegion', pluginResult); 38 | }; 39 | 40 | window.cordova && window.cordova.plugins.locationManager.setDelegate(delegate); 41 | 42 | } 43 | 44 | return { 45 | 46 | /* 47 | * Requests permission to use location services while the app is in the foreground 48 | * 49 | * @param 50 | * @return 51 | */ 52 | requestWhenInUseAuthorization: function() { 53 | window.cordova && window.cordova.plugins.locationManager.requestWhenInUseAuthorization(); 54 | }, 55 | 56 | /* 57 | * Requests permission to use location services whenever the app is running 58 | * @param 59 | * @return 60 | */ 61 | requestAlwaysAuthorization: function() { 62 | window.cordova && window.cordova.plugins.locationManager.requestAlwaysAuthorization(); 63 | }, 64 | 65 | /* 66 | * Create a beacon monitor or range for 67 | * 68 | * @param string identifier 69 | * @param string uuid 70 | * @param int major 71 | * @param int minor 72 | * @return BeaconRegion 73 | */ 74 | createBeaconRegion: function(identifier, uuid, major, minor) { 75 | return window.cordova ? new window.cordova.plugins.locationManager.BeaconRegion(identifier, uuid, major, minor) : {}; 76 | }, 77 | 78 | /* 79 | * Starts the delivery of notifications for beacons in the specified region 80 | * 81 | * @param BeaconRegion beaconRegion 82 | * @return 83 | */ 84 | startRangingBeaconsInRegion: function(beaconRegion) { 85 | window.cordova && window.cordova.plugins.locationManager.startRangingBeaconsInRegion(beaconRegion); 86 | }, 87 | 88 | /* 89 | * Starts monitoring the specified region 90 | * 91 | * @param BeaconRegion beaconRegion 92 | * @return 93 | */ 94 | startMonitoringForRegion: function(beaconRegion) { 95 | window.cordova && window.cordova.plugins.locationManager.startMonitoringForRegion(beaconRegion); 96 | }, 97 | 98 | /* 99 | * Stops the delivery of notifications for the specified beacon region 100 | * 101 | * @param BeaconRegion beaconRegion 102 | * @return 103 | */ 104 | stopRangingBeaconsInRegion: function(beaconRegion) { 105 | window.cordova && window.cordova.plugins.locationManager.stopRangingBeaconsInRegion(beaconRegion); 106 | }, 107 | 108 | /* 109 | * Stops monitoring the specified region 110 | * 111 | * @param BeaconRegion beaconRegion 112 | * @return 113 | */ 114 | stopMonitoringForRegion: function(beaconRegion) { 115 | window.cordova && window.cordova.plugins.locationManager.stopMonitoringForRegion(beaconRegion); 116 | } 117 | 118 | }; 119 | 120 | }]); -------------------------------------------------------------------------------- /client/app/common/util/index.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | /** 5 | * Module dependencies 6 | */ 7 | var angular = require('angular'); 8 | 9 | module.exports = angular 10 | 11 | .module('common.util', []) 12 | 13 | .service('$exceptionHandler', require('./services/$exceptionHandler')); 14 | -------------------------------------------------------------------------------- /client/app/common/util/services/$exceptionHandler.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | module.exports = function () { 5 | return function (exception) { 6 | throw exception; 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /client/app/entry-template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {%= o.htmlWebpackPlugin.options.title || o.htmlWebpackPlugin.options.pkg.name || 'Webpack App'%} 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for (var chunk in o.htmlWebpackPlugin.files.chunks) { %} 15 | 16 | {% } %} 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /client/app/home/bookingModal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |
7 |

My cool help

8 |
9 | 10 | 14 | 17 | 18 | 19 | I cannot help you right now... 20 | 21 |
22 | -------------------------------------------------------------------------------- /client/app/home/home.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('angular'); 3 | var bookingModal = require('./bookingModal.html'); 4 | 5 | 6 | module.exports = angular 7 | .module('app.home.controller', []) 8 | .controller('appHome', appHome); 9 | 10 | /* @ngInject */ 11 | function appHome($scope, Rooms, $ionicModal, $cordovaBeacon, $timeout) { 12 | var vm = this; 13 | vm.title = 'SlackBooking'; 14 | 15 | vm.picture = { 16 | small: require('../common/img/small.png'), 17 | medium: require('../common/img/medium.png'), 18 | large: require('../common/img/large.png') 19 | }; 20 | 21 | 22 | vm.filter = [ 23 | { 24 | id: 0, 25 | label: "Near By", 26 | name: "nearBy" 27 | }, { 28 | id: 1, 29 | label: "Available", 30 | name: "available" 31 | }, { 32 | id: 2, 33 | label: "All", 34 | name: "all" 35 | } 36 | ]; 37 | 38 | vm.filterSelected = vm.filter[0]; 39 | 40 | vm.noRoom = false; 41 | vm.rooms = []; 42 | vm.roomAvailabel = false; 43 | 44 | 45 | vm.doRefresh = doRefresh; 46 | vm.updateFilter = updateFilter; 47 | vm.book = book; 48 | vm.selectAll = selectAll; 49 | vm.nbRoom = nbRoom; 50 | 51 | activate(); 52 | 53 | //////////////// 54 | function getList() { 55 | return Rooms.all().then(function (list) { 56 | vm.rooms = list; 57 | }) 58 | } 59 | 60 | function doRefresh() { 61 | Rooms.update().then(function () { 62 | getList().then(function () { 63 | updateFilter(); 64 | }); 65 | $scope.$broadcast('scroll.refreshComplete'); 66 | }); 67 | } 68 | 69 | function activate() { 70 | getList(); 71 | } 72 | 73 | function updateFilter() { 74 | _.forEach(vm.rooms, function (room) { 75 | if (vm.filterSelected.id === 0) { 76 | room.visible = (room.distance != null); 77 | } else if (vm.filterSelected.id == 1) { 78 | room.visible = !room.booked; 79 | } else { 80 | room.visible = true; 81 | } 82 | }); 83 | 84 | 85 | vm.roomAvailabel = !!_.find(vm.rooms, {'visible': true}); 86 | } 87 | 88 | function book(room) { 89 | if (!room.booked) { 90 | Rooms.book(room); 91 | } 92 | } 93 | 94 | function selectAll() { 95 | vm.filterSelected = vm.filter[2]; 96 | updateFilter(); 97 | } 98 | 99 | function nbRoom() { 100 | return Rooms.nbRoom(); 101 | } 102 | 103 | $scope.$on("$cordovaBeacon:didRangeBeaconsInRegion", function (event, pluginResult) { 104 | var beaconFound; 105 | //_.forEach(vm.rooms, function (room) { 106 | // room.distance = null; 107 | //}); 108 | _.forEach(pluginResult.beacons, function (beacon) { 109 | var id = beacon.uuid + ":" + beacon.major + ":" + beacon.minor; 110 | beaconFound = _.find(vm.rooms, {beaconId: id}); 111 | if (beaconFound) { 112 | $timeout(function () { 113 | beaconFound.distance = beacon.accuracy !== -1 ? Math.round(beacon.accuracy) : null; 114 | }, 0) 115 | 116 | } 117 | }); 118 | 119 | $timeout(function () { 120 | updateFilter(); 121 | }) 122 | }); 123 | } 124 | 125 | 126 | -------------------------------------------------------------------------------- /client/app/home/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{vm.title}} 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 | 15 |
16 |
17 | 20 |
21 |
22 |
23 | 27 | 30 | 31 |
32 |
33 |

{{room.name}}

34 |

{{room.location}}

35 | 36 | 39 |
40 |
41 | 42 | 43 |

Distance: {{room.distance}}m

44 |

Capacity: {{room.capacity}}

45 | {{label}} 46 | 47 |
48 |
49 | 50 |
51 |
52 |

No room found related to your filtering.

53 |

select all to view the status of all {{vm.nbRoom()}} in the building.

54 |
55 |
56 | 57 |
58 | 59 |
60 | -------------------------------------------------------------------------------- /client/app/home/index.js: -------------------------------------------------------------------------------- 1 | 2 | require('./style.scss'); 3 | 4 | module.exports = angular 5 | .module('app.home',[ 6 | require('./home.controller').name, 7 | require('./room.service').name 8 | ]) 9 | .config(['$stateProvider', require('./states')]); 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/app/home/room.service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by franciosdelpech on 4/9/16. 3 | */ 4 | 5 | 6 | var _ = require('lodash'); 7 | var angular = require('angular'); 8 | 9 | var API = ''; 10 | if(process && process.env && process.env.NODE_ENV === 'production'){ 11 | API = 'http://hacktimote.site/' 12 | } 13 | 14 | module.exports = angular 15 | .module('room.service', []) 16 | .factory('Rooms', Rooms); 17 | 18 | /* @ngInject */ 19 | function Rooms($q, $http) { 20 | var serverUrl = API + 'api'; 21 | 22 | 23 | /* 24 | { 25 | "name": "string", 26 | "beaconId": "string", 27 | "location": "string", 28 | "assets": ["string"], 29 | "capacity": "string", 30 | "capacityName": "string", 31 | 32 | "status": { 33 | "name": "string", 34 | "bookingId": "string" 35 | } 36 | } 37 | */ 38 | 39 | var rooms = null; 40 | 41 | return { 42 | all: getAll, 43 | get: getById, 44 | update: update, 45 | book: book, 46 | nbRoom: nbRoom 47 | }; 48 | 49 | function getAll() { 50 | if (!rooms) { 51 | return update(); 52 | } 53 | return $q.when(rooms); 54 | } 55 | 56 | function getById(id) { 57 | 58 | } 59 | 60 | function update() { 61 | return $http.get(serverUrl + '/rooms').then(function (result) { 62 | rooms = []; 63 | _.forEach(result.data.data, function (data) { 64 | var cap = _.parseInt(data.capacity); 65 | if (cap <= 2) { 66 | data.capacityName = "small"; 67 | } else if (cap <= 5) { 68 | data.capacityName = "medium"; 69 | } else { 70 | data.capacityName = "large"; 71 | } 72 | data.booked = data.status.name === 'Booked'; 73 | rooms.push(data); 74 | }); 75 | return rooms; 76 | }) 77 | } 78 | 79 | function book(room) { 80 | var end = new Date(); 81 | end.setHours(end.getHours()+1); 82 | 83 | var data = { 84 | "roomId": room._id, 85 | "start":new Date(), 86 | "end": end, 87 | "owner": "mobileApplication", 88 | "invitees": [ 89 | "my friends" 90 | ] 91 | }; 92 | return $http.post(serverUrl + '/booking',data).then(function (result) { 93 | room.status.name = "Booked"; 94 | room.booked = true; 95 | }) 96 | 97 | } 98 | 99 | function nbRoom(){ 100 | return rooms? rooms.length : 0; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /client/app/home/states.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function states($stateProvider) { 4 | $stateProvider 5 | .state('app.home', { 6 | url: '/home', 7 | parent: 'app', 8 | template: require('./home.html'), 9 | controller: 'appHome', 10 | controllerAs: 'vm' 11 | }); 12 | } 13 | 14 | module.exports = states; 15 | -------------------------------------------------------------------------------- /client/app/home/style.scss: -------------------------------------------------------------------------------- 1 | 2 | .bar-subheader { 3 | .item{ 4 | padding: 15px; 5 | } 6 | .item-select select { 7 | -moz-appearance: none; 8 | position: absolute; 9 | top: 0px; 10 | bottom: 0px; 11 | right: 0px; 12 | padding: 0px 35px 0px 0px; 13 | max-width: 100%; 14 | border: medium none; 15 | background: #FFF none repeat scroll 0 0; 16 | color: #333; 17 | text-indent: 0.01px; 18 | text-overflow: ""; 19 | white-space: nowrap; 20 | font-size: 14px; 21 | cursor: pointer; 22 | direction: rtl; 23 | } 24 | } 25 | .card{ 26 | background-color: white; 27 | &.booked{ 28 | background-color: rgba(128,128,128,0.16); 29 | } 30 | .item{ 31 | background-color: transparent; 32 | } 33 | } 34 | .item-button-right{ 35 | padding-right: 130px; 36 | } 37 | 38 | .label { 39 | background-color: rgba(#777, 0.6); 40 | margin: 3px; 41 | display: inline; 42 | padding: .2em .6em .3em; 43 | font-size: 75%; 44 | font-weight: 700; 45 | line-height: 1; 46 | color: #fff; 47 | text-align: center; 48 | white-space: nowrap; 49 | vertical-align: baseline; 50 | border-radius: .25em; 51 | } -------------------------------------------------------------------------------- /client/app/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies 4 | */ 5 | require('./style.scss'); 6 | 7 | var bootstrap = require('./common/libs'); 8 | 9 | /** 10 | * Setup App Module 11 | */ 12 | 13 | var appModule = module.exports = angular 14 | .module('app',[ 15 | bootstrap.name, 16 | require('./home').name 17 | ]) 18 | 19 | .config(['$stateProvider', require('./states')]) 20 | 21 | 22 | .config(function ($compileProvider) { 23 | $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|tel):/); 24 | }) 25 | 26 | .config(function ($urlRouterProvider) { 27 | 28 | $urlRouterProvider.otherwise('/app/home'); 29 | 30 | }) 31 | 32 | .run(function ($log, $rootScope, $ionicBackdrop, $timeout, $cordovaSplashscreen, $cordovaBeacon) { 33 | 34 | $log.debug('app module - run'); 35 | 36 | $rootScope.$on('$stateChangeStart', 37 | function (event, toState) { 38 | $log.debug('$stateChangeStart - name:', toState.name); 39 | }); 40 | 41 | $rootScope.$on('$stateChangeSuccess', 42 | function (event, toState) { 43 | $log.debug('$stateChangeSuccess - name:', toState.name); 44 | }); 45 | 46 | $rootScope.$on('$stateNotFound', 47 | function (event, unfoundState, fromState, fromParams) { 48 | $log.warn('$stateNotFound', { 49 | event : event, 50 | unfoundState : unfoundState, 51 | fromState : fromState, 52 | fromParams : fromParams 53 | }); 54 | }); 55 | 56 | $rootScope.$on('$stateChangeError', 57 | function (event, toState, toParams, fromState, fromParams, error) { 58 | $log.error('$stateChangeError', { 59 | event : event, 60 | toState : toState, 61 | toParams : toParams, 62 | fromState : fromState, 63 | fromParams : fromParams, 64 | error : error 65 | }); 66 | if (error) { 67 | throw error; 68 | } 69 | }); 70 | 71 | $cordovaBeacon.requestAlwaysAuthorization(); 72 | 73 | $cordovaBeacon.startRangingBeaconsInRegion($cordovaBeacon.createBeaconRegion("estimote", "B9407F30-F5F8-466E-AFF9-25556B57FE6D")); 74 | 75 | 76 | $ionicBackdrop.retain(); 77 | 78 | $timeout(function() { 79 | $ionicBackdrop.release(); 80 | }, 600); 81 | 82 | $timeout(function() { 83 | navigator.splashscreen && $cordovaSplashscreen.hide() 84 | }, 4000); 85 | }); 86 | 87 | // Bootstrap App Module 88 | bootstrap.ionicBootstrap(appModule, global); 89 | -------------------------------------------------------------------------------- /client/app/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /client/app/states.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | 5 | function states($stateProvider) { 6 | $stateProvider 7 | .state('app', { 8 | url: '/app', 9 | abstract: true, 10 | template: require('./layout.html') 11 | }); 12 | } 13 | module.exports = states; -------------------------------------------------------------------------------- /client/app/style.scss: -------------------------------------------------------------------------------- 1 | $ionicons-font-path: "../node_modules/ionic-npm/fonts" !default; 2 | 3 | @import "variable"; 4 | 5 | 6 | 7 | // include style that may include some global variable. 8 | @import "../node_modules/ionic-npm/scss/ionic"; 9 | 10 | 11 | .ellipsis { 12 | white-space: nowrap; 13 | overflow: hidden; 14 | text-overflow: ellipsis; 15 | } 16 | 17 | .button-link { 18 | text-decoration: underline !important; 19 | } 20 | 21 | .has-errors { 22 | border-bottom: 2px solid $assertive !important; 23 | } 24 | 25 | .no-errors { 26 | border-bottom: 2px solid $positive !important; 27 | } 28 | 29 | .no-margin { 30 | margin: 0 !important; 31 | } 32 | 33 | .blur { 34 | $blur: 5px !default; 35 | 36 | -webkit-filter: blur($blur); 37 | -moz-filter: blur($blur); 38 | -o-filter: blur($blur); 39 | -ms-filter: blur($blur); 40 | filter: blur($blur); 41 | margin: -$blur; // compensate blur 42 | } 43 | 44 | 45 | .ellipsis{ 46 | text-overflow: ellipsis; 47 | white-space: nowrap; 48 | overflow: hidden; 49 | 50 | } 51 | 52 | .clickable{ 53 | cursor: pointer; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /client/app/variable.scss: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | To customize the look and feel of se-app, you can override the variables 4 | 5 | For example, you might change some of the default colors: 6 | 7 | $light: #fff !default; 8 | $stable: #f8f8f8 !default; 9 | $calm: $se-life-green !default; // used as main color in the app 10 | $positive: $se-logo-green !default; 11 | $balanced: $se-sunflower-yellow !default; 12 | $energized: $se-honeysuckle-orange !default; 13 | $assertive: $se-fuchsia-red !default; 14 | $royal: $se-sky-blue !default; 15 | $dark: $se-dark-grey !default; 16 | 17 | */ 18 | 19 | //$calm: #ff828a !default; 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /client/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Slack Booking 4 | 5 | An app that change the way to have meeting 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /client/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | var gulp = require('gulp'), 7 | gutil = require('gulp-util'), 8 | path = require('path'), 9 | del = require('del'), 10 | open = require('open'), 11 | webpack = require('webpack'), 12 | WebpackDevServer = require('webpack-dev-server'), 13 | webpackConfig = require('./webpack.config.js'), 14 | minimist = require('minimist'); 15 | 16 | 17 | var argv = minimist(process.argv.slice(2)); 18 | var src = Object.create(null); 19 | 20 | var watch = false; 21 | 22 | 23 | gulp.task('webpack', function (callback) { 24 | webpack(webpackConfig, function (err, stats) { 25 | if (err) { 26 | throw new gutil.PluginError('webpack', err); 27 | } 28 | 29 | gutil.log('[webpack]', stats.toString({ 30 | colors: true 31 | })); 32 | 33 | callback(); 34 | }); 35 | }); 36 | 37 | gulp.task('webpack-dev-server', function (callback) { 38 | // modify some webpack config options 39 | var myConfig = Object.create(webpackConfig); 40 | myConfig.devtool = 'eval'; 41 | myConfig.debug = true; 42 | 43 | 44 | //myConfig.entry.app. 45 | myConfig.entry.app.unshift('webpack-dev-server/client?http://localhost:8080'); 46 | 47 | //publicPath: "/" + myConfig.output.publicPath, 48 | 49 | // Start a webpack-dev-server 50 | new WebpackDevServer(webpack(myConfig), { 51 | contentBase: path.join(__dirname, 'app'), 52 | stats: { 53 | colors: true 54 | }, 55 | proxy: { 56 | '/api/*': { 57 | target: 'http://hacktimote.site/', 58 | secure: false 59 | } 60 | } 61 | }).listen(8080, 'localhost', function (err) { 62 | if (err) throw new gutil.PluginError('webpack-dev-server', err); 63 | 64 | var startUrl = 'http://localhost:8080'; 65 | open(startUrl); 66 | 67 | gutil.log('[webpack-dev-server]', startUrl); 68 | }); 69 | }); 70 | 71 | 72 | // Bundle 73 | gulp.task('build:prod', function(callback) { 74 | // modify some webpack config options 75 | var myConfig = Object.create(webpackConfig); 76 | 77 | //myConfig.entry.devtool = 'inline'; 78 | 79 | 80 | myConfig.plugins = myConfig.plugins.concat( 81 | new webpack.optimize.DedupePlugin(), 82 | //new webpack.optimize.UglifyJsPlugin({ 83 | // sourceMap: false 84 | //}), 85 | new webpack.DefinePlugin({ 86 | 'process.env': { 87 | // This has effect on the react lib size 88 | 'NODE_ENV': JSON.stringify('production') 89 | } 90 | }) 91 | ); 92 | 93 | // run webpack 94 | webpack(myConfig, function(err, stats) { 95 | if(err) throw new gutil.PluginError('webpack:build', err); 96 | gutil.log('[webpack:build]', stats.toString({ 97 | colors: true 98 | })); 99 | callback(); 100 | }); 101 | }); 102 | 103 | gulp.task('clean', function (cb) { 104 | del([ 105 | 'www/**/*', 106 | '!www/.gitignore' 107 | ], { 108 | dot: true 109 | }, cb); 110 | }); 111 | 112 | gulp.task('clean-all', function (cb) { 113 | del([ 114 | 'www/**/*', 115 | '!www/.gitignore', 116 | 'node_modules', 117 | 'build' 118 | ], { 119 | dot: true 120 | }, cb); 121 | }); 122 | 123 | //gulp.task('install', ['webpack']); 124 | gulp.task('watch', ['webpack-dev-server']); 125 | gulp.task('default', ['install']); 126 | gulp.task('build', ['clean', 'build:prod']); -------------------------------------------------------------------------------- /client/hooks/README.md: -------------------------------------------------------------------------------- 1 | 21 | # Cordova Hooks 22 | 23 | This directory may contain scripts used to customize cordova commands. This 24 | directory used to exist at `.cordova/hooks`, but has now been moved to the 25 | project root. Any scripts you add to these directories will be executed before 26 | and after the commands corresponding to the directory name. Useful for 27 | integrating your own build systems or integrating with version control systems. 28 | 29 | __Remember__: Make your scripts executable. 30 | 31 | ## Hook Directories 32 | The following subdirectories will be used for hooks: 33 | 34 | after_build/ 35 | after_compile/ 36 | after_docs/ 37 | after_emulate/ 38 | after_platform_add/ 39 | after_platform_rm/ 40 | after_platform_ls/ 41 | after_plugin_add/ 42 | after_plugin_ls/ 43 | after_plugin_rm/ 44 | after_plugin_search/ 45 | after_prepare/ 46 | after_run/ 47 | after_serve/ 48 | before_build/ 49 | before_compile/ 50 | before_docs/ 51 | before_emulate/ 52 | before_platform_add/ 53 | before_platform_rm/ 54 | before_platform_ls/ 55 | before_plugin_add/ 56 | before_plugin_ls/ 57 | before_plugin_rm/ 58 | before_plugin_search/ 59 | before_prepare/ 60 | before_run/ 61 | before_serve/ 62 | pre_package/ <-- Windows 8 and Windows Phone only. 63 | 64 | ## Script Interface 65 | 66 | All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables: 67 | 68 | * CORDOVA_VERSION - The version of the Cordova-CLI. 69 | * CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios). 70 | * CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer) 71 | * CORDOVA_HOOK - Path to the hook that is being executed. 72 | * CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate) 73 | 74 | If a script returns a non-zero exit code, then the parent cordova command will be aborted. 75 | 76 | 77 | ## Writing hooks 78 | 79 | We highly recommend writting your hooks using Node.js so that they are 80 | cross-platform. Some good examples are shown here: 81 | 82 | [http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/) 83 | 84 | -------------------------------------------------------------------------------- /client/hooks/after_prepare/010_add_platform_class.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Add Platform Class 4 | // v1.0 5 | // Automatically adds the platform class to the body tag 6 | // after the `prepare` command. By placing the platform CSS classes 7 | // directly in the HTML built for the platform, it speeds up 8 | // rendering the correct layout/style for the specific platform 9 | // instead of waiting for the JS to figure out the correct classes. 10 | 11 | var fs = require('fs'); 12 | var path = require('path'); 13 | 14 | var rootdir = process.argv[2]; 15 | 16 | function addPlatformBodyTag(indexPath, platform) { 17 | // add the platform class to the body tag 18 | try { 19 | var platformClass = 'platform-' + platform; 20 | var cordovaClass = 'platform-cordova platform-webview'; 21 | 22 | var html = fs.readFileSync(indexPath, 'utf8'); 23 | 24 | var bodyTag = findBodyTag(html); 25 | if(!bodyTag) return; // no opening body tag, something's wrong 26 | 27 | if(bodyTag.indexOf(platformClass) > -1) return; // already added 28 | 29 | var newBodyTag = bodyTag; 30 | 31 | var classAttr = findClassAttr(bodyTag); 32 | if(classAttr) { 33 | // body tag has existing class attribute, add the classname 34 | var endingQuote = classAttr.substring(classAttr.length-1); 35 | var newClassAttr = classAttr.substring(0, classAttr.length-1); 36 | newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote; 37 | newBodyTag = bodyTag.replace(classAttr, newClassAttr); 38 | 39 | } else { 40 | // add class attribute to the body tag 41 | newBodyTag = bodyTag.replace('>', ' class="' + platformClass + ' ' + cordovaClass + '">'); 42 | } 43 | 44 | html = html.replace(bodyTag, newBodyTag); 45 | 46 | fs.writeFileSync(indexPath, html, 'utf8'); 47 | 48 | process.stdout.write('add to body class: ' + platformClass + '\n'); 49 | } catch(e) { 50 | process.stdout.write(e); 51 | } 52 | } 53 | 54 | function findBodyTag(html) { 55 | // get the body tag 56 | try{ 57 | return html.match(/])(.*?)>/gi)[0]; 58 | }catch(e){} 59 | } 60 | 61 | function findClassAttr(bodyTag) { 62 | // get the body tag's class attribute 63 | try{ 64 | return bodyTag.match(/ class=["|'](.*?)["|']/gi)[0]; 65 | }catch(e){} 66 | } 67 | 68 | if (rootdir) { 69 | 70 | // go through each of the platform directories that have been prepared 71 | var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []); 72 | 73 | for(var x=0; x=2.2.0", 24 | "eslint-loader": "^1.2.0", 25 | "exports-loader": "^0.6.2", 26 | "file-loader": "^0.8.5", 27 | "gulp": ">=3.9.0", 28 | "gulp-load-plugins": "^1.2.0", 29 | "gulp-util": "^3.0.7", 30 | "html-loader": "^0.4.0", 31 | "html-webpack-plugin": "^1.7.0", 32 | "json-loader": "^0.5.4", 33 | "lodash": ">=4.6.0", 34 | "minimist": "^1.2.0", 35 | "node-sass": "^3.4.2", 36 | "open": "0.0.5", 37 | "sass-loader": "^3.1.2", 38 | "style-loader": "^0.13.0", 39 | "url-loader": "^0.5.7", 40 | "webpack": ">=1.12.14", 41 | "webpack-dev-server": "^1.14.0", 42 | "image-webpack-loader": "^1.6.3" 43 | }, 44 | "scripts": { 45 | "build": "gulp build", 46 | "git-push": "git push origin master", 47 | "git-commit": "git add -A . && git commit -am 'feature update'" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/plugins/android.json: -------------------------------------------------------------------------------- 1 | { 2 | "prepare_queue": { 3 | "installed": [], 4 | "uninstalled": [] 5 | }, 6 | "config_munge": { 7 | "files": {} 8 | }, 9 | "installed_plugins": { 10 | "com.unarin.cordova.beacon": { 11 | "PACKAGE_NAME": "com.slackbooking.app" 12 | }, 13 | "cordova-plugin-console": { 14 | "PACKAGE_NAME": "com.slackbooking.app" 15 | }, 16 | "cordova-plugin-device": { 17 | "PACKAGE_NAME": "com.slackbooking.app" 18 | }, 19 | "cordova-plugin-dialogs": { 20 | "PACKAGE_NAME": "com.slackbooking.app" 21 | }, 22 | "cordova-plugin-estimote": { 23 | "PACKAGE_NAME": "com.slackbooking.app" 24 | }, 25 | "cordova-plugin-hockeyapp": { 26 | "PACKAGE_NAME": "com.slackbooking.app" 27 | }, 28 | "cordova-plugin-networkactivityindicator": { 29 | "PACKAGE_NAME": "com.slackbooking.app" 30 | }, 31 | "cordova-plugin-splashscreen": { 32 | "PACKAGE_NAME": "com.slackbooking.app" 33 | }, 34 | "cordova-plugin-statusbar": { 35 | "PACKAGE_NAME": "com.slackbooking.app" 36 | }, 37 | "cordova-plugin-vibration": { 38 | "PACKAGE_NAME": "com.slackbooking.app" 39 | }, 40 | "cordova-plugin-whitelist": { 41 | "PACKAGE_NAME": "com.slackbooking.app" 42 | }, 43 | "cordova-plugin-x-toast": { 44 | "PACKAGE_NAME": "com.slackbooking.app" 45 | }, 46 | "de.appplant.cordova.plugin.local-notification": { 47 | "PACKAGE_NAME": "com.slackbooking.app" 48 | }, 49 | "phonegap-plugin-push": { 50 | "PACKAGE_NAME": "com.slackbooking.app" 51 | } 52 | }, 53 | "dependent_plugins": { 54 | "cordova-plugin-se-transport-security": { 55 | "PACKAGE_NAME": "com.slackbooking.app" 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /client/plugins/cordova-plugin-transport-security/README.md: -------------------------------------------------------------------------------- 1 | ## App Transport Security Plugin for Apache Cordova [![npm version](https://badge.fury.io/js/cordova-plugin-transport-security.svg)](http://badge.fury.io/js/cordova-plugin-transport-security) 2 | 3 | **Cordova / PhoneGap Plugin to allow 'Arbitrary Loads' by adding a declaration to the Info.plist file to bypass the iOS 9 App Transport Security** 4 | 5 | ## Install 6 | 7 | #### Latest published version on npm (with Cordova CLI >= 5.0.0) 8 | 9 | ``` 10 | cordova plugin add cordova-plugin-transport-security 11 | ``` 12 | 13 | #### Latest version from GitHub 14 | 15 | ``` 16 | cordova plugin add https://github.com/leecrossley/cordova-plugin-transport-security.git 17 | ``` 18 | 19 | ## Apple guidance 20 | 21 | > App Transport Security (ATS) enforces best practices in the secure connections between an app and its back end. ATS prevents accidental disclosure, provides secure default behavior, and is easy to adopt; it is also on by default in iOS 9 and OS X v10.11. You should adopt ATS as soon as possible, regardless of whether you’re creating a new app or updating an existing one. 22 | 23 | > If you’re developing a new app, you should use HTTPS exclusively. If you have an existing app, you should use HTTPS as much as you can right now, and create a plan for migrating the rest of your app as soon as possible. In addition, your communication through higher-level APIs needs to be encrypted using TLS version 1.2 with forward secrecy. If you try to make a connection that doesn't follow this requirement, an error is thrown. If your app needs to make a request to an insecure domain, you have to specify this domain in your app's Info.plist file. 24 | 25 | Source: [iOS Developer Library](https://developer.apple.com/library/prerelease/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html#//apple_ref/doc/uid/TP40016198-SW14) 26 | 27 | It's important to note that this does not impact apps built with Xcode < 7 running on iOS 9. 28 | 29 | ## Usage 30 | In case you want specify your domain as an exception domain in ATS, open `plugin.xml`, set `NSAllowsArbitraryLoads` value (line:17) to `false` and add `NSExceptionDomains` just like this: 31 | 32 | ``` 33 | 34 | NSAllowsArbitraryLoads 35 | 36 | NSExceptionDomains 37 | 38 | www.github.com 39 | 40 | NSExceptionAllowsInsecureHTTPLoads 41 | 42 | NSIncludesSubdomains 43 | 44 | 45 | 46 | 47 | ``` 48 | Find more about App Transport Security: 49 | https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/ 50 | 51 | 52 | 53 | ## Platforms 54 | 55 | Applies to iOS (9+) only. 56 | 57 | ## License 58 | 59 | [MIT License](http://ilee.mit-license.org) 60 | -------------------------------------------------------------------------------- /client/plugins/cordova-plugin-transport-security/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-transport-security", 3 | "version": "0.1.1", 4 | "author": "Lee Crossley (http://ilee.co.uk/)", 5 | "description": "Cordova / PhoneGap Plugin to allow 'Arbitrary Loads' by adding a declaration to the Info.plist file to bypass the iOS 9 App Transport Security", 6 | "cordova": { 7 | "id": "cordova-plugin-transport-security", 8 | "platforms": [ 9 | "ios" 10 | ] 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/leecrossley/cordova-plugin-transport-security.git" 15 | }, 16 | "keywords": [ 17 | "cordova", 18 | "ios", 19 | "app", 20 | "security", 21 | "transport", 22 | "http", 23 | "https", 24 | "ats", 25 | "ssl", 26 | "tls", 27 | "nsapptransportsecurity", 28 | "nsallowsarbitraryloads", 29 | "ecosystem:cordova", 30 | "cordova-ios" 31 | ], 32 | "engines": [ 33 | { 34 | "name": "cordova", 35 | "version": ">=3.0.0" 36 | } 37 | ], 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/leecrossley/cordova-plugin-transport-security/issues" 41 | }, 42 | "homepage": "https://github.com/leecrossley/cordova-plugin-transport-security#readme" 43 | } 44 | -------------------------------------------------------------------------------- /client/plugins/cordova-plugin-transport-security/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | App Transport Security 4 | Lee Crossley (http://ilee.co.uk/) 5 | Cordova / PhoneGap Plugin to allow 'Arbitrary Loads' by adding 6 | a declaration to the Info.plist file to bypass the iOS 9 App Transport Security 7 | cordova, ios, app, security, transport, http, https, ats, 8 | ssl, tls, nsapptransportsecurity, nsallowsarbitraryloads 9 | MIT 10 | 11 | 12 | 13 | 14 | 15 | 16 | NSAllowsArbitraryLoads 17 | 18 | NSExceptionDomains 19 | 20 | hacktimote.site/ 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /client/plugins/fetch.json: -------------------------------------------------------------------------------- 1 | { 2 | "cordova-plugin-networkactivityindicator": { 3 | "source": { 4 | "type": "registry", 5 | "id": "cordova-plugin-networkactivityindicator@~0.1.1" 6 | }, 7 | "is_top_level": true, 8 | "variables": {} 9 | }, 10 | "cordova-plugin-x-toast": { 11 | "source": { 12 | "type": "registry", 13 | "id": "cordova-plugin-x-toast@~2.2.3" 14 | }, 15 | "is_top_level": true, 16 | "variables": {} 17 | }, 18 | "cordova-plugin-console": { 19 | "source": { 20 | "type": "registry", 21 | "id": "cordova-plugin-console@~1.0.2" 22 | }, 23 | "is_top_level": true, 24 | "variables": {} 25 | }, 26 | "cordova-plugin-device": { 27 | "source": { 28 | "type": "registry", 29 | "id": "cordova-plugin-device@~1.1.0" 30 | }, 31 | "is_top_level": true, 32 | "variables": {} 33 | }, 34 | "cordova-plugin-hockeyapp": { 35 | "source": { 36 | "type": "registry", 37 | "id": "cordova-plugin-hockeyapp@~1.3.0" 38 | }, 39 | "is_top_level": true, 40 | "variables": {} 41 | }, 42 | "cordova-plugin-splashscreen": { 43 | "source": { 44 | "type": "registry", 45 | "id": "cordova-plugin-splashscreen@~3.0.0" 46 | }, 47 | "is_top_level": true, 48 | "variables": {} 49 | }, 50 | "cordova-plugin-statusbar": { 51 | "source": { 52 | "type": "registry", 53 | "id": "cordova-plugin-statusbar@~2.0.0" 54 | }, 55 | "is_top_level": true, 56 | "variables": {} 57 | }, 58 | "cordova-plugin-vibration": { 59 | "source": { 60 | "type": "registry", 61 | "id": "cordova-plugin-vibration@~2.0.0" 62 | }, 63 | "is_top_level": true, 64 | "variables": {} 65 | }, 66 | "phonegap-plugin-push": { 67 | "source": { 68 | "type": "registry", 69 | "id": "phonegap-plugin-push@~1.4.5" 70 | }, 71 | "is_top_level": true, 72 | "variables": {} 73 | }, 74 | "cordova-plugin-dialogs": { 75 | "source": { 76 | "type": "registry", 77 | "id": "cordova-plugin-dialogs@~1.2.0" 78 | }, 79 | "is_top_level": true, 80 | "variables": {} 81 | }, 82 | "cordova-plugin-whitelist": { 83 | "source": { 84 | "type": "registry", 85 | "id": "cordova-plugin-whitelist@~1.2.0" 86 | }, 87 | "is_top_level": true, 88 | "variables": {} 89 | }, 90 | "cordova-plugin-estimote": { 91 | "source": { 92 | "type": "git", 93 | "url": "https://github.com/evothings/phonegap-estimotebeacons.git", 94 | "subdir": "." 95 | }, 96 | "is_top_level": true, 97 | "variables": {} 98 | }, 99 | "com.unarin.cordova.beacon": { 100 | "source": { 101 | "type": "git", 102 | "url": "git+ssh://git@github.com/petermetz/cordova-plugin-ibeacon", 103 | "subdir": "." 104 | }, 105 | "is_top_level": true, 106 | "variables": {} 107 | }, 108 | "de.appplant.cordova.plugin.local-notification": { 109 | "source": { 110 | "type": "git", 111 | "url": "https://github.com/katzer/cordova-plugin-local-notifications", 112 | "subdir": ".", 113 | "ref": "0.8.1" 114 | }, 115 | "is_top_level": true, 116 | "variables": {} 117 | } 118 | } -------------------------------------------------------------------------------- /client/plugins/ios.json: -------------------------------------------------------------------------------- 1 | { 2 | "prepare_queue": { 3 | "installed": [], 4 | "uninstalled": [] 5 | }, 6 | "config_munge": { 7 | "files": {} 8 | }, 9 | "installed_plugins": { 10 | "com.unarin.cordova.beacon": { 11 | "PACKAGE_NAME": "com.slackbooking.app" 12 | }, 13 | "cordova-plugin-console": { 14 | "PACKAGE_NAME": "com.slackbooking.app" 15 | }, 16 | "cordova-plugin-device": { 17 | "PACKAGE_NAME": "com.slackbooking.app" 18 | }, 19 | "cordova-plugin-dialogs": { 20 | "PACKAGE_NAME": "com.slackbooking.app" 21 | }, 22 | "cordova-plugin-estimote": { 23 | "PACKAGE_NAME": "com.slackbooking.app" 24 | }, 25 | "cordova-plugin-hockeyapp": { 26 | "PACKAGE_NAME": "com.slackbooking.app" 27 | }, 28 | "cordova-plugin-networkactivityindicator": { 29 | "PACKAGE_NAME": "com.slackbooking.app" 30 | }, 31 | "cordova-plugin-splashscreen": { 32 | "PACKAGE_NAME": "com.slackbooking.app" 33 | }, 34 | "cordova-plugin-statusbar": { 35 | "PACKAGE_NAME": "com.slackbooking.app" 36 | }, 37 | "cordova-plugin-vibration": { 38 | "PACKAGE_NAME": "com.slackbooking.app" 39 | }, 40 | "cordova-plugin-whitelist": { 41 | "PACKAGE_NAME": "com.slackbooking.app" 42 | }, 43 | "cordova-plugin-x-toast": { 44 | "PACKAGE_NAME": "com.slackbooking.app" 45 | }, 46 | "de.appplant.cordova.plugin.local-notification": { 47 | "PACKAGE_NAME": "com.slackbooking.app" 48 | }, 49 | "phonegap-plugin-push": { 50 | "PACKAGE_NAME": "com.slackbooking.app" 51 | } 52 | }, 53 | "dependent_plugins": { 54 | "cordova-plugin-se-transport-security": { 55 | "PACKAGE_NAME": "com.slackbooking.app" 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /client/res/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/icon/icon.png -------------------------------------------------------------------------------- /client/res/screen/android/screen-hdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/android/screen-hdpi-portrait.png -------------------------------------------------------------------------------- /client/res/screen/android/screen-ldpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/android/screen-ldpi-portrait.png -------------------------------------------------------------------------------- /client/res/screen/android/screen-mdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/android/screen-mdpi-portrait.png -------------------------------------------------------------------------------- /client/res/screen/android/screen-xhdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/android/screen-xhdpi-portrait.png -------------------------------------------------------------------------------- /client/res/screen/ios/Default-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/ios/Default-667h.png -------------------------------------------------------------------------------- /client/res/screen/ios/Default-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/ios/Default-736h.png -------------------------------------------------------------------------------- /client/res/screen/ios/Default-Portrait@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/ios/Default-Portrait@2x~ipad.png -------------------------------------------------------------------------------- /client/res/screen/ios/screen-ipad-portrait-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/ios/screen-ipad-portrait-2x.png -------------------------------------------------------------------------------- /client/res/screen/ios/screen-ipad-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/ios/screen-ipad-portrait.png -------------------------------------------------------------------------------- /client/res/screen/ios/screen-iphone-portrait-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/ios/screen-iphone-portrait-2x.png -------------------------------------------------------------------------------- /client/res/screen/ios/screen-iphone-portrait-568h-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/ios/screen-iphone-portrait-568h-2x.png -------------------------------------------------------------------------------- /client/res/screen/ios/screen-iphone-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/ios/screen-iphone-portrait.png -------------------------------------------------------------------------------- /client/res/screen/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/res/screen/screen.png -------------------------------------------------------------------------------- /client/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | var path = require('path'), 7 | webpack = require('webpack'), 8 | HtmlWebpackPlugin = require('html-webpack-plugin'), 9 | minimist = require('minimist'), 10 | ngAnnotatePlugin = require('ng-annotate-webpack-plugin'); 11 | 12 | 13 | var argv = minimist(process.argv.slice(2)); 14 | var DEBUG = !argv.release; 15 | var STYLE_LOADER = 'style-loader'; 16 | var CSS_LOADER = DEBUG ? 'css-loader' : 'css-loader?minimize'; 17 | 18 | var GLOBALS = { 19 | 'process.env.NODE_ENV': DEBUG ? '"development"' : '"production"', 20 | '__DEV__': DEBUG 21 | }; 22 | 23 | 24 | module.exports = { 25 | 26 | output: { 27 | path: path.join(__dirname, 'www'), 28 | filename: '[name].js', 29 | chunkFilename: '[chunkhash].js' 30 | }, 31 | 32 | devtool: 'source-map', 33 | 34 | cache: DEBUG, 35 | debug: DEBUG, 36 | 37 | stats: { 38 | colors: true, 39 | reasons: DEBUG 40 | }, 41 | 42 | entry: { 43 | app: ['./app/index.js'] 44 | }, 45 | 46 | module: { 47 | //preLoaders: [{ 48 | // test: /\.js$/, 49 | // exclude: /node_modules|dist|www|build/, 50 | // loader: 'eslint-loader' 51 | //}], 52 | 53 | loaders: [{ 54 | test: /\.css$/, 55 | loader: 'style-loader!css-loader' 56 | }, { 57 | test: /\.html$/, 58 | loader: 'html' 59 | }, { 60 | test: /\.json$/, 61 | loader: 'json' 62 | }, { 63 | test: /\.scss$/, 64 | loader: 'style!css!sass' 65 | }, { 66 | test: /\.(woff|ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 67 | loader: 'url?prefix=font/&limit=10000&name=font/[hash].[ext]' 68 | }, { 69 | test: /[\/]angular\.js$/, 70 | loader: 'exports?angular' 71 | }, { 72 | test: /[\/]ionic\.js$/, 73 | loader: 'exports?ionic' 74 | }, { 75 | test: /.*\.(gif|png|jpe?g)$/i, 76 | loaders: [ 77 | 'file?hash=sha512&digest=hex&name=img/[hash].[ext]', 78 | 'image-webpack?{progressive:true, optimizationLevel:1, interlaced: false, pngquant:{quality: "65-90", speed: 4}}' 79 | ] 80 | //' + DEBUG ? '1' : '7' + ' 81 | }] 82 | }, 83 | 84 | resolve: { 85 | moduleDirectories: [ 86 | 'node_modules' 87 | ], 88 | alis: {} 89 | }, 90 | 91 | plugins: [ 92 | new HtmlWebpackPlugin({ 93 | pkg: require('./package.json'), 94 | template: 'app/entry-template.html' 95 | }), 96 | new ngAnnotatePlugin({ 97 | add: true 98 | }) 99 | 100 | // new webpack.optimize.DedupePlugin(), 101 | // new webpack.optimize.UglifyJsPlugin() 102 | // new webpack.BannerPlugin(banner, options), 103 | // new webpack.optimize.DedupePlugin() 104 | ] 105 | }; -------------------------------------------------------------------------------- /client/www/font/05acfdb568b3df49ad31355b19495d4a.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/www/font/05acfdb568b3df49ad31355b19495d4a.woff -------------------------------------------------------------------------------- /client/www/font/24712f6c47821394fba7942fbb52c3b2.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/www/font/24712f6c47821394fba7942fbb52c3b2.ttf -------------------------------------------------------------------------------- /client/www/font/2c2ae068be3b089e0a5b59abb1831550.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/www/font/2c2ae068be3b089e0a5b59abb1831550.eot -------------------------------------------------------------------------------- /client/www/img/4021dd335fcfb21c9b3623ba15f532a3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/www/img/4021dd335fcfb21c9b3623ba15f532a3.png -------------------------------------------------------------------------------- /client/www/img/9b9c0c8a6872708ff7e672eb67fd5ea0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/www/img/9b9c0c8a6872708ff7e672eb67fd5ea0.png -------------------------------------------------------------------------------- /client/www/img/f48708d60d03f89e15f391d2938d6ee3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacktimote/SlackBooking/7b40a87a846ca13e7ba79c273b4969818010c6bf/client/www/img/f48708d60d03f89e15f391d2938d6ee3.png -------------------------------------------------------------------------------- /client/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | slackbooking 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slackbooking", 3 | "version": "0.0.4", 4 | "description": "New way of booking a room with Slack and Estimote", 5 | "licenses": [ 6 | { 7 | "type": "MIT" 8 | } 9 | ], 10 | "private": true, 11 | "scripts": { 12 | "git-push": "git push origin master", 13 | "git-commit": "git add -A . && git commit -am 'feature update'" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # Slack booking - Server Side 2 | 3 | ## Intro 4 | The server application is the backend of our mobile app. It is the API with which to book and list rooms. It is also the point of messaging to Slack. 5 | 6 | The server gives you a set of REST calls to allow for booking rooms, booking through Slack and listing rooms through the api or Slack. 7 | 8 | Documentation on the specific call can be found, when running the server, at '/documentation'. 9 | 10 | ## Libraries 11 | 12 | The server has been developed with: 13 | 14 | * Hapijs - http://hapijs.com/ 15 | * Mongodb - https://www.mongodb.com/ 16 | * Mongoose - http://mongoosejs.com/ 17 | * Joi - https://github.com/hapijs/joi 18 | * Unirest - http://unirest.io/nodejs.html 19 | * hapi-swagger - https://github.com/glennjones/hapi-swagger 20 | * slack-client - https://github.com/slackhq/node-slack-client 21 | 22 | 23 | ## How to get started 24 | 25 | Install all the dependecies that the server will use. 26 | 27 | npm install 28 | 29 | Start your MongoDB server 30 | 31 | mongod 32 | 33 | Start the server 34 | 35 | npm start 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slackbooking-server", 3 | "version": "0.0.1", 4 | "description": "Server side of Slack Booking", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "forever start ./src/index.js", 8 | "stop": "forever stop ./src/index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/R0muald/SlackBooking.git" 13 | }, 14 | "keywords": [ 15 | "slack", 16 | "estimote", 17 | "beacon", 18 | "booking" 19 | ], 20 | "author": "d0nkeyBOB", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/R0muald/SlackBooking/issues" 24 | }, 25 | "homepage": "https://github.com/R0muald/SlackBooking#readme", 26 | "dependencies": { 27 | "bluebird": "^3.3.4", 28 | "boom": "^3.1.2", 29 | "good": "^6.6.0", 30 | "good-console": "^5.3.1", 31 | "hapi": "^13.2.2", 32 | "hapi-swagger": "^4.3.1", 33 | "inert": "^3.2.0", 34 | "joi": "^8.0.5", 35 | "lodash": "^4.7.0", 36 | "moment": "latest", 37 | "mongoose": "^4.4.10", 38 | "node-uuid": "^1.4.7", 39 | "slack-client": "^2.0.4", 40 | "unirest": "^0.4.2", 41 | "vision": "^4.0.1" 42 | }, 43 | "devDependencies": {} 44 | } 45 | -------------------------------------------------------------------------------- /server/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hapi = require('hapi'); 4 | const Good = require('good'); 5 | const Inert = require('inert'); 6 | const Vision = require('vision'); 7 | const HapiSwagger = require('hapi-swagger'); 8 | const Pack = require('../package'); 9 | 10 | const server = new Hapi.Server(); 11 | server.connection({ 12 | port: 3000, 13 | host: 'localhost' 14 | }); 15 | 16 | const swagOptions = { 17 | info: { 18 | 'title': 'Slacktimote API Documentation', 19 | 'version': Pack.version, 20 | }, 21 | // 'host': 'localhost:3000', 22 | 'host': 'hacktimote.site', 23 | tags: [ 24 | { 25 | 'name': 'rooms', 26 | 'description': 'Room API calls' 27 | }, 28 | { 29 | 'name': 'bookings', 30 | 'description': 'Booking API calls' 31 | }, 32 | { 33 | 'name': 'slack', 34 | 'description': 'Slack API calls' 35 | } 36 | ] 37 | }; 38 | 39 | const mongoose = require('mongoose'); 40 | // mongoose.connect('mongodb://localhost:4321/slacktimote'); 41 | mongoose.connect('mongodb://localhost:27017/slacktimote'); 42 | 43 | const RoomModel = require('./plugins/rooms'); 44 | const BookingModel = require('./plugins/bookings'); 45 | const SlackRoutes = require('./plugins/slack'); 46 | 47 | server.route({ 48 | method: 'GET', 49 | path: '/', 50 | handler: (req, reply) => { 51 | reply('Slack Booking'); 52 | } 53 | }) 54 | 55 | server.register([ 56 | Inert, 57 | Vision, 58 | { 59 | register: require('hapi-swagger'), 60 | options: swagOptions 61 | }, 62 | { register: RoomModel }, 63 | { register: BookingModel }, 64 | { register: SlackRoutes }, 65 | { 66 | register: Good, 67 | options: { 68 | reporters: [{ 69 | reporter: require('good-console'), 70 | events: { 71 | response: '*', 72 | log: '*' 73 | } 74 | 75 | }] 76 | } 77 | } 78 | ], (err) => { 79 | 80 | if(err) { 81 | console.log(err); 82 | } 83 | server.start((err) => { 84 | if(err) { 85 | console.log(err); 86 | } 87 | server.log('Server running at: ', server.info.uri); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /server/src/models/bookings.js: -------------------------------------------------------------------------------- 1 | 2 | const mongoose = require('mongoose'); 3 | const Schema = mongoose.Schema; 4 | 5 | const BookingSchema = new Schema({ 6 | roomId: String, 7 | start: Date, 8 | end: Date, 9 | owner: String, 10 | invitees: Array 11 | }); 12 | 13 | module.exports = mongoose.model('bookings', BookingSchema); 14 | -------------------------------------------------------------------------------- /server/src/models/rooms.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const RoomSchema = new Schema({ 5 | name: String, 6 | beaconId: String, 7 | location: String, 8 | assets: Array, 9 | capacity: String, 10 | status: Object 11 | }); 12 | 13 | module.exports = mongoose.model('rooms', RoomSchema); 14 | 15 | -------------------------------------------------------------------------------- /server/src/plugins/bookings/bookings.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | 5 | module.exports = (function() { 6 | 7 | var Booking = {}; 8 | 9 | 10 | })(); 11 | -------------------------------------------------------------------------------- /server/src/plugins/bookings/index.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | const Boom = require('boom'); 5 | const uuid = require('node-uuid'); 6 | const Joi = require('joi'); 7 | const _ = require('lodash'); 8 | const BookingModel = require('../../models/bookings'); 9 | const RoomModel = require('../../models/rooms'); 10 | const Slack = require('../slack/slack.js'); 11 | 12 | exports.register = (plugin, options, next) => { 13 | 14 | var Booking = require('./bookings'); 15 | 16 | plugin.expose(Booking); 17 | 18 | plugin.route({ 19 | method: 'POST', 20 | path: '/api/booking', 21 | config: { 22 | tags: ['api'], 23 | description: 'Save booking data', 24 | notes: 'Save booking data', 25 | validate: { 26 | payload: { 27 | roomId: Joi.string().required(), 28 | start: Joi.date().required(), 29 | end: Joi.date().required(), 30 | owner: Joi.string().required(), 31 | invitees: Joi.array().items(Joi.string()) 32 | } 33 | } 34 | }, 35 | handler: function (request, reply) { 36 | 37 | let ownerId = request.payload.owner; 38 | let booking = new BookingModel(request.payload); 39 | 40 | booking.save(function (error, response) { 41 | if (error) { 42 | reply({ 43 | statusCode: 503, 44 | message: error 45 | }); 46 | } else { 47 | console.log(response); 48 | 49 | const status = { 50 | name: 'Booked', 51 | bookingId: response._id, 52 | ownerId: ownerId 53 | }; 54 | const updated = { 55 | status: status 56 | } 57 | 58 | RoomModel.findOneAndUpdate({_id: response.roomId}, updated, function (error, data) { 59 | if (error) { 60 | reply({ 61 | statusCode: 503, 62 | message: 'Failed to get data', 63 | }); 64 | } else { 65 | Slack.postMessageToSlack(data.name + ' has been booked for 1 hour. #' + data.location); 66 | reply({ 67 | statusCode: 200, 68 | message: 'Booking Saved' 69 | }); 70 | } 71 | }); 72 | } 73 | }); 74 | } 75 | }); 76 | 77 | plugin.route({ 78 | method: 'GET', 79 | config: { 80 | tags: ['api'], 81 | description: 'Get all Bookings', 82 | notes: 'Get all Bookings' 83 | }, 84 | path: '/api/bookings', 85 | handler: (request, reply) => { 86 | BookingModel.find({}, function (error, data) { 87 | if (error) { 88 | reply({ 89 | statusCode: 503, 90 | message: 'Failed to get data', 91 | data: error 92 | }); 93 | } else { 94 | reply({ 95 | statusCode: 200, 96 | message: 'Room Data Successfully Fetched', 97 | data: data 98 | }); 99 | } 100 | }); 101 | } 102 | }) 103 | 104 | plugin.route({ 105 | method: 'GET', 106 | path: '/api/booking/{id}', 107 | config: { 108 | tags: ['api'], 109 | description: 'Get booking by Id', 110 | notes: 'Get booking by Id', 111 | validate: { 112 | params: { 113 | id: Joi.string().required() 114 | } 115 | } 116 | }, 117 | handler: (request, reply) => { 118 | BookingModel.find({_id: request.params.id}, function (error, data) { 119 | if (error) { 120 | reply({ 121 | statusCode: 503, 122 | message: 'Failed to get data', 123 | data: error 124 | }); 125 | } else { 126 | if (data.length === 0) { 127 | reply({ 128 | statusCode: 200, 129 | message: 'Room Not Found', 130 | data: data 131 | }); 132 | } else { 133 | reply({ 134 | statusCode: 200, 135 | message: 'Room Data Successfully Fetched', 136 | data: data 137 | }); 138 | } 139 | } 140 | }); 141 | } 142 | }) 143 | 144 | plugin.route({ 145 | method: 'DELETE', 146 | path: '/api/booking/{id}', 147 | config: { 148 | tags: ['api'], 149 | description: 'Remove booking by id', 150 | notes: 'Remove booking by id', 151 | validate: { 152 | params: { 153 | id: Joi.string().required() 154 | } 155 | } 156 | }, 157 | handler: (request, reply) => { 158 | RoomModel.findOneAndRemove({_id: request.params.id}, function (error, data) { 159 | if (error) { 160 | reply({ 161 | statusCode: 503, 162 | message: 'Failed to remove booking', 163 | data: error 164 | }); 165 | } else { 166 | 167 | reply({ 168 | statusCode: 200, 169 | message: 'Room removed Successfully' 170 | }); 171 | } 172 | }); 173 | } 174 | }) 175 | 176 | next(); 177 | }; 178 | 179 | exports.register.attributes = { 180 | name: 'routes-bookings' 181 | }; 182 | -------------------------------------------------------------------------------- /server/src/plugins/rooms/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('boom'); 4 | const uuid = require('node-uuid'); 5 | const Joi = require('joi'); 6 | const unirest = require('unirest'); 7 | const _ = require('lodash'); 8 | const RoomModel = require('../../models/rooms'); 9 | 10 | exports.register = (plugin, options, next) => { 11 | 12 | plugin.route({ 13 | method: 'POST', 14 | path: '/api/room', 15 | config: { 16 | tags: ['api'], 17 | description: 'Save room data', 18 | notes: 'Save room data', 19 | validate: { 20 | payload: { 21 | name: Joi.string().required(), 22 | beaconId: Joi.string().required(), 23 | location: Joi.string().required(), 24 | assets: Joi.array().items(Joi.string()), 25 | capacity: Joi.string().required(), 26 | status: Joi.object({ 27 | name: Joi.string().required(), 28 | bookingId: Joi.string().allow('').optional(), 29 | ownerId: Joi.string().allow('').optional() 30 | }) 31 | } 32 | } 33 | }, 34 | handler: function (request, reply) { 35 | 36 | var room = new RoomModel(request.payload); 37 | 38 | room.save(function (error) { 39 | if (error) { 40 | reply({ 41 | statusCode: 503, 42 | message: error 43 | }); 44 | } else { 45 | reply({ 46 | statusCode: 201, 47 | message: 'Room Saved Successfully' 48 | }); 49 | } 50 | }); 51 | } 52 | }); 53 | 54 | plugin.route({ 55 | method: 'GET', 56 | config: { 57 | tags: ['api'], 58 | description: 'Get all Rooms', 59 | notes: 'Get all Rooms' 60 | }, 61 | path: '/api/rooms', 62 | handler: (request, reply) => { 63 | RoomModel.find({}, function (error, data) { 64 | if (error) { 65 | reply({ 66 | statusCode: 503, 67 | message: 'Failed to get data', 68 | data: error 69 | }); 70 | } else { 71 | reply({ 72 | statusCode: 200, 73 | message: 'Room Data Successfully Fetched', 74 | data: data 75 | }); 76 | } 77 | }); 78 | } 79 | }) 80 | 81 | plugin.route({ 82 | method: 'GET', 83 | path: '/api/room/{beaconId}', 84 | config: { 85 | tags: ['api'], 86 | description: 'Get room by UUID', 87 | notes: 'Get room by UUID', 88 | validate: { 89 | params: { 90 | beaconId: Joi.string().required() 91 | } 92 | } 93 | }, 94 | handler: (request, reply) => { 95 | RoomModel.find({beaconId: request.params.beaconId}, function (error, data) { 96 | if (error) { 97 | reply({ 98 | statusCode: 503, 99 | message: 'Failed to get data', 100 | data: error 101 | }); 102 | } else { 103 | if (data.length === 0) { 104 | reply({ 105 | statusCode: 200, 106 | message: 'Room Not Found', 107 | data: data 108 | }); 109 | } else { 110 | reply({ 111 | statusCode: 200, 112 | message: 'Room Data Successfully Fetched', 113 | data: data 114 | }); 115 | } 116 | } 117 | }); 118 | } 119 | }) 120 | 121 | plugin.route({ 122 | method: 'PUT', 123 | path: '/api/room/{id}', 124 | config: { 125 | tags: ['api'], 126 | description: 'Update status for room', 127 | notes: 'Update status for room', 128 | validate: { 129 | params: { 130 | id: Joi.string().required() 131 | }, 132 | payload: { 133 | status: Joi.object({ 134 | name: Joi.string().required(), 135 | bookingId: Joi.string().allow('').optional(), 136 | ownerId: Joi.string().allow('').optional() 137 | }) 138 | } 139 | } 140 | }, 141 | handler: (request, reply) => { 142 | RoomModel.findOneAndUpdate({_id: request.params.id}, request.payload, function (error, data) { 143 | if (error) { 144 | reply({ 145 | statusCode: 503, 146 | message: 'Failed to get data', 147 | data: error 148 | }); 149 | } else { 150 | if (data.length === 0) { 151 | reply({ 152 | statusCode: 200, 153 | message: 'Room Not Found', 154 | data: data 155 | }); 156 | } else { 157 | reply({ 158 | statusCode: 200, 159 | message: 'Room Data Successfully Fetched', 160 | data: data 161 | }); 162 | } 163 | } 164 | }); 165 | } 166 | }) 167 | 168 | plugin.route({ 169 | method: 'DELETE', 170 | path: '/api/room/{id}', 171 | config: { 172 | tags: ['api'], 173 | description: 'Remove room by id', 174 | notes: 'Remove room by id', 175 | validate: { 176 | params: { 177 | id: Joi.string().required() 178 | } 179 | } 180 | }, 181 | handler: (request, reply) => { 182 | RoomModel.findOneAndRemove({_id: request.params.id}, function (error, data) { 183 | if (error) { 184 | reply({ 185 | statusCode: 503, 186 | message: 'Failed to remove room', 187 | data: error 188 | }); 189 | } else { 190 | 191 | reply({ 192 | statusCode: 200, 193 | message: 'Room removed Successfully' 194 | }); 195 | } 196 | }); 197 | } 198 | }) 199 | 200 | next(); 201 | }; 202 | 203 | exports.register.attributes = { 204 | name: 'routes-rooms' 205 | }; 206 | -------------------------------------------------------------------------------- /server/src/plugins/rooms/rooms.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = (function() { 5 | 6 | var Room = {}; 7 | 8 | 9 | })(); 10 | -------------------------------------------------------------------------------- /server/src/plugins/slack/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 'use strict'; 4 | 5 | const Boom = require('boom'); 6 | const uuid = require('node-uuid'); 7 | const Joi = require('joi'); 8 | const unirest = require('unirest'); 9 | 10 | 11 | exports.register = (plugin, options, next) => { 12 | 13 | let config = { 14 | slash_token: 'wZjT5fyOXfeKCuMH05vm2Y3Z' 15 | }; 16 | 17 | let Slack = require('./slack') 18 | 19 | plugin.expose(Slack); 20 | 21 | plugin.route({ 22 | method: 'GET', 23 | config: { 24 | tags: ['api'], 25 | description: 'Get all Bookings', 26 | notes: 'Get all Bookings' 27 | }, 28 | path: '/api/slack', 29 | handler: (request, reply) => { 30 | if (request.query.token === config.slash_token) { 31 | const slacked = Slack.process(request.query); 32 | reply(slacked); 33 | } else { 34 | reply('Incorrect slash token') 35 | } 36 | } 37 | }) 38 | 39 | next(); 40 | }; 41 | 42 | 43 | exports.register.attributes = { 44 | name: 'routes-slack' 45 | }; 46 | -------------------------------------------------------------------------------- /server/src/plugins/slack/slack.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const unirest = require('unirest'); 4 | const RoomModel = require('../../models/rooms'); 5 | const BookingModel = require('../../models/bookings'); 6 | const _ = require('lodash'); 7 | const moment = require('moment'); 8 | const mongoose = require('mongoose'); 9 | 10 | module.exports = (function() { 11 | 12 | const Slack = {}; 13 | 14 | const postListToSlack = function(rooms) { 15 | 16 | let roomValue= ''; 17 | for(let room = 0; room < rooms.length; room++) { 18 | roomValue += '#' + rooms[room].location + ' | ' + rooms[room].name + '\n'; 19 | }; 20 | 21 | const message = { 22 | "text": "Here is a list of avaialable rooms.", 23 | "attachments": [ 24 | { 25 | "fields": [ 26 | { 27 | "title": "Rooms Available", 28 | "value": roomValue, 29 | "short": true 30 | } 31 | ] 32 | } 33 | ] 34 | }; 35 | 36 | unirest.post('https://hooks.slack.com/services/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') 37 | .headers({'Accept': 'application/json', 'Content-Type': 'application/json'}) 38 | .send(message) 39 | .end(function (response) { 40 | }); 41 | } 42 | 43 | Slack.postMessageToSlack = function(message) { 44 | 45 | unirest.post('https://hooks.slack.com/services/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') 46 | .headers({'Accept': 'application/json', 'Content-Type': 'application/json'}) 47 | .send({ 48 | "text": message 49 | }) 50 | .end(function (response) { 51 | return; 52 | }); 53 | } 54 | 55 | Slack.postErrorToSlack = function(error) { 56 | 57 | 58 | const message = { 59 | "text": "Command did not work \n" + error 60 | }; 61 | 62 | 63 | unirest.post('https://hooks.slack.com/services/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') 64 | .headers({'Accept': 'application/json', 'Content-Type': 'application/json'}) 65 | .send(message) 66 | .end(function (response) { 67 | return; 68 | }); 69 | } 70 | 71 | const bookRoom = function(room) { 72 | 73 | // This is not right, at all . . . . 74 | var now = moment().format('YYYY-MM-DD'); 75 | var hour = moment().add(1, 'h').format('YYYY-MM-DD'); 76 | let ownerId = 'slack-timote'; 77 | 78 | var payload = { 79 | "roomId": room[0]._id, 80 | "start": now, 81 | "end": hour, 82 | "owner": ownerId, 83 | "invitees": [ 84 | "string" 85 | ] 86 | }; 87 | 88 | var booking = new BookingModel(payload); 89 | 90 | booking.save(function (error, response) { 91 | 92 | if (error) { 93 | reply({ 94 | statusCode: 503, 95 | message: error 96 | }); 97 | } else { 98 | 99 | let status = { 100 | name: 'Booked', 101 | bookingId: response._id, 102 | ownerId: ownerId 103 | }; 104 | let updated = { 105 | status: status 106 | } 107 | RoomModel.findOneAndUpdate({_id: response.roomId}, updated, function (error, data) { 108 | if (error) { 109 | Slack.postErrorToSlack('Failed to book room. Try again later'); 110 | } else { 111 | Slack.postMessageToSlack(data.name + ' has been booked for 1 hour. #' + data.location); 112 | } 113 | }); 114 | } 115 | }); 116 | }; 117 | 118 | const getRoomId = function(reservation) { 119 | let query = RoomModel.find({'location': reservation}); 120 | 121 | query.exec(function (error, data) { 122 | if (error) { 123 | Slack.postErrorToSlack(error); 124 | } else { 125 | bookRoom(data); 126 | } 127 | }); 128 | } 129 | 130 | Slack.process = function(options) { 131 | 132 | if(options.text === '') { 133 | 134 | let query = RoomModel.find({'status.name': 'Available'}). 135 | select('name location'); 136 | 137 | query.exec(function (error, data) { 138 | if (error) { 139 | Slack.postErrorToSlack(error); 140 | } else { 141 | postListToSlack(data); 142 | } 143 | }); 144 | 145 | } else { 146 | let commandText = options.text; 147 | let commandArray = commandText.split(' '); 148 | 149 | if(commandArray.length == 2) { 150 | 151 | // const command = commandArray[0]; 152 | let reservation = commandArray[1]; 153 | reservation = reservation.replace("#", ""); 154 | getRoomId(reservation); 155 | 156 | } else if (commandArray.length < 2) { 157 | console.log('Not enough parameters to book. You must include a room number'); 158 | } else if (commandArray.length > 2) { 159 | console.log('You can only enter the command "/rooms book #{roomnumber}"'); 160 | } 161 | 162 | } 163 | } 164 | 165 | return Slack; 166 | 167 | })(); 168 | -------------------------------------------------------------------------------- /server/src/services/slack.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const unirest = require('unirest'); 4 | const RoomModel = require('../models/rooms'); 5 | 6 | module.exports = (function() { 7 | 8 | var postToSlack = function(attachment, options) { 9 | 10 | var req = unirest("POST", "https://slack.com/api/chat.postMessage"); 11 | req.headers({ 12 | "accept": "application/json" 13 | }); 14 | 15 | req.query = { 16 | "token": options.slack_token, 17 | "channel": options.channel_id, 18 | "username": 'SlackTimote', 19 | "attachments": JSON.stringify(attachment) 20 | }; 21 | 22 | req.end(function (res) { 23 | if (res.error) { 24 | console.log(res.error); 25 | }; 26 | console.log(res.body); 27 | }); 28 | 29 | } 30 | 31 | var getAvailableRooms = function() { 32 | 33 | RoomModel.find({'status.name': 'available'}). 34 | limit(5). 35 | select('mame location'). 36 | exec(function (error, data) { 37 | if (error) { 38 | console.log(error); 39 | } else { 40 | return data; 41 | } 42 | }); 43 | } 44 | 45 | var process = function(options) { 46 | 47 | var rooms = getAvailableRooms(); 48 | 49 | var attachment = [{ 50 | 'fallback': 'Required plain-text summary of the attachment', 51 | 'author_name': 'Success! You created an item!', 52 | 'title': 'Item: #1234', 53 | 'title_link': 'Rooms available', 54 | 'fields': rooms, 55 | 'color': '#36a64f' 56 | }]; 57 | 58 | postToSlack(attachment, options); 59 | } 60 | 61 | return { 62 | process: process 63 | } 64 | })(); 65 | --------------------------------------------------------------------------------