├── .gitattributes ├── .gitignore ├── .travis.yml ├── CreateServerBuild.sh ├── LICENSE ├── README.md ├── back-end ├── .gitignore ├── BypathServer.njsproj ├── BypathServer.sln ├── README.md ├── constants │ ├── env-dev.json │ ├── env-prod.json │ └── environment.js ├── data │ ├── SQLData.js │ ├── data_manager.js │ ├── endpointBuilder.js │ ├── getMeASoda.js │ ├── justPlainCoke.js │ └── stockTheFridge.js ├── database │ ├── database.js │ ├── db_firebase.js │ └── db_postgis.js ├── package.json ├── resources │ ├── endpoints.json │ ├── keys.txt │ ├── nyc_endpoint.json │ ├── parking.json │ ├── resources.txt │ └── sf_endpoint.json ├── rest │ ├── rest_server.js │ ├── routes │ │ ├── data.js │ │ ├── incident.js │ │ ├── index.js │ │ ├── nav.js │ │ └── parking.js │ └── views │ │ └── addIncident.html ├── server.js ├── test │ ├── incident-test.js │ ├── openFridge.js │ ├── top-shelf-example-boston311.json │ ├── top-shelf-example-nyc311.json │ └── top-shelf-example-sf311.json ├── test_server.js └── util │ ├── ext_resource_manager.js │ └── modules.js ├── chatbot ├── .project ├── .pydevproject ├── Boston311.py ├── Bot.py ├── FacebookEndpoint.py ├── FirebaseAPI.py ├── IrcEndpoint.py ├── LICENSE.txt ├── client.py ├── images │ └── .gitignore ├── main.py ├── models.py ├── stickers.py └── utils.py ├── front-end ├── .bowerrc ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .yo-rc.json ├── README.md ├── app │ ├── .eslintrc │ ├── app.js │ ├── index.html │ └── main │ │ ├── assets │ │ └── images │ │ │ ├── boston311.png │ │ │ ├── danger-hump.png │ │ │ ├── falling-person.png │ │ │ ├── graffiti-icon.png │ │ │ ├── lawnmower.png │ │ │ ├── snow_plow_truck.png │ │ │ ├── snowflake-icon.png │ │ │ ├── snowflake.png │ │ │ └── yo@2x.png │ │ ├── constants │ │ ├── config-const.js │ │ ├── env-dev.json │ │ └── env-prod.json │ │ ├── controllers │ │ ├── main.controller.js │ │ ├── map.controller.js │ │ └── parking.controller.js │ │ ├── filters │ │ ├── casetype.filter.js │ │ ├── id.filter.js │ │ ├── incidenttype.filter.js │ │ ├── type.filter.js │ │ └── unique.filter.js │ │ ├── main.js │ │ ├── services │ │ ├── database.factory.js │ │ ├── geolocation.factory.js │ │ ├── map.factory.js │ │ ├── marker.factory.js │ │ ├── tilesets.factory.js │ │ └── utilities.factory.js │ │ ├── styles │ │ ├── _customType.scss │ │ ├── _map.scss │ │ └── main.scss │ │ └── templates │ │ ├── add.html │ │ ├── map.html │ │ └── tabs.html ├── bower.json ├── config.xml ├── gulp │ ├── building.js │ ├── configuring.js │ ├── cordova.js │ ├── injecting.js │ ├── linting.js │ ├── testing.js │ └── watching.js ├── gulpfile.js ├── hooks │ ├── README.md │ └── after_prepare │ │ └── update_platform_config.js ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── res │ ├── android │ │ ├── default │ │ │ └── icon.png │ │ └── set1 │ │ │ └── icon.png │ └── ios │ │ ├── default │ │ └── icon.png │ │ └── set1 │ │ └── icon.png └── test │ ├── karma │ └── .eslintrc │ └── protractor │ ├── .eslintrc │ └── main.js ├── tools ├── .gitignore ├── tools.sln └── tools │ ├── README.md │ ├── css │ ├── bootstrap.min.css │ ├── jquery.dataTables.min.css │ └── style.css │ ├── images │ └── tool.png │ ├── index.html │ ├── js │ ├── api │ │ ├── data │ │ │ ├── SQLData.js │ │ │ ├── data_manager.js │ │ │ ├── endpointBuilder.js │ │ │ ├── getMeASoda.js │ │ │ ├── justPlainCoke.js │ │ │ └── stockTheFridge.js │ │ ├── database │ │ │ └── db_firebase.js │ │ ├── resources │ │ │ ├── endpoints.json │ │ │ ├── keys.txt │ │ │ ├── nyc_endpoint.json │ │ │ └── resources.txt │ │ └── util │ │ │ ├── ext_resource_manager.js │ │ │ └── modules.js │ ├── app.js │ ├── controller │ │ ├── addNewDataSource.js │ │ ├── addParkingData.js │ │ ├── databaseRefresh.js │ │ └── mainPage.js │ ├── libs │ │ ├── angular-resource.min.js │ │ ├── angular-route.min.js │ │ ├── angular.min.js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── jquery.dataTables.min.js │ │ └── jquery.min.js │ ├── routes.js │ └── services.js │ ├── package.json │ ├── run.cmd │ ├── run.sh │ ├── tools.njsproj │ └── views │ ├── addNewDataSource.html │ ├── addParking.html │ ├── dbRefresh.html │ ├── header.html │ └── home.html └── v2 ├── app ├── app.ts ├── pages │ ├── about │ │ ├── about.html │ │ ├── about.scss │ │ └── about.ts │ ├── contact │ │ ├── contact.html │ │ ├── contact.scss │ │ └── contact.ts │ ├── home │ │ ├── home.html │ │ ├── home.scss │ │ └── home.ts │ └── tabs │ │ ├── tabs.html │ │ └── tabs.ts └── theme │ ├── app.core.scss │ ├── app.ios.scss │ ├── app.md.scss │ ├── app.variables.scss │ └── app.wp.scss ├── config.xml ├── gulpfile.js ├── hooks ├── README.md └── after_prepare │ └── 010_add_platform_class.js ├── ionic.config.json ├── package.json ├── tsconfig.json ├── tslint.json ├── typings.json ├── typings ├── globals │ └── es6-shim │ │ ├── index.d.ts │ │ └── typings.json └── index.d.ts └── www └── index.html /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # src/snowranger/snowranger/__pycache__/ 3 | # src/snowranger/conditions/migrations/__pycache__/ 4 | # src/snowranger/conditions/__pycache__ 5 | __pycache__/ 6 | 7 | 8 | 9 | 10 | mydatabase 11 | 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # C extensions 18 | *.so 19 | 20 | # Distribution / packaging 21 | .Python 22 | env/ 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | 38 | # PyInstaller 39 | # Usually these files are written by a python script from a template 40 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 41 | *.manifest 42 | *.spec 43 | 44 | # Installer logs 45 | pip-log.txt 46 | pip-delete-this-directory.txt 47 | 48 | # Unit test / coverage reports 49 | htmlcov/ 50 | .tox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *,cover 57 | .hypothesis/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | mydatabase 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | #Ipython Notebook 74 | .ipynb_checkpoints 75 | 76 | #Eclipse project files 77 | 78 | # .project 79 | # .pydevproject 80 | node_modules/ 81 | plugins/ 82 | resources/ 83 | 84 | 85 | scratch/ 86 | 87 | 88 | bower_content 89 | 90 | # web 91 | web/bower_components 92 | 93 | web/www/* 94 | 95 | .directory 96 | 97 | .browse.* 98 | 99 | mobile/platforms/ios/ 100 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4.2' 4 | before_install: 5 | - cd front-end 6 | install: 7 | - npm install 8 | - npm run build-dev & 9 | # - gulp --cordova "prepare" 10 | before_script: 11 | - "export DISPLAY=:99.0" 12 | - "sh -e /etc/init.d/xvfb start" 13 | - sleep 3 # give xvfb some time to start 14 | script: 15 | - cd ../back-end 16 | - npm install 17 | - npm run start-dev & 18 | - sleep 5 19 | - npm test 20 | - cd ../front-end 21 | - npm test 22 | -------------------------------------------------------------------------------- /CreateServerBuild.sh: -------------------------------------------------------------------------------- 1 | rsync -av --progress back-end/ ../BypathServerBuild --exclude /node_modules --exclude BypathServer.njsproj --exclude README.md --exclude test_server.js --exclude /.vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Code for Boston 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 | # ByPath 2 | 3 | ### An app to help make cities more accessible to those with low-mobility. 4 | 5 | [![Build Status](https://travis-ci.org/codeforboston/bypath.svg?branch=master)](https://travis-ci.org/codeforboston/bypath) 6 | 7 | ![Imgur](http://i.imgur.com/WSI4l7y.png) 8 | 9 | We're working to integrate existing 311 data and services with user-generated advisories in an effort to enable, and make transparent, systems for maintaining civilized life. 10 | 11 | Originally begun as an initiative to help enable mobility for low-mobility folk in times of inclement weather, our project has widened in scope to take into consideration the warmer months as well, along with an acknowledgement that mobility, more broadly considered, means __access__. 12 | 13 | Existing 311 data references civil complaints of all kinds - from rodent issues to graffiti to potholes to snowplow requests - all of which can make an impact on the accessibility and living conditions in our fair city. 14 | 15 | We've expanded our scope to include NYC and San Francisco. 16 | 17 | ### What we're running. 18 | - Ionic, for iOS and Web (Android coming soon.) 19 | - Firebase 20 | - AngularJS 21 | - NodeJS 22 | - Google Maps API 23 | 24 | ### Devs 25 | - Alex: wants a real job. 26 | - Andrew: weirdly good at math. 27 | 28 | ### Alumni and Friends 29 | 30 | - Isaac: has a bigger mustache than you. 31 | - Addu: likes wires and sparky things. 32 | - Edwin: went to a camp about shoes. 33 | - Pooja: is probably somewhere talking to people. 34 | - Anatoli: is on a mountain in the Himalayas drinking tea. 35 | - Ryan: built our demo site! 36 | 37 | ### Builds 38 | - iOS - https://itunes.apple.com/us/app/bypath/id1106925083?mt=8 39 | - web - http://bypath.bitballoon.com 40 | -------------------------------------------------------------------------------- /back-end/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | node_modules 25 | build/Release 26 | 27 | # Dependency directories 28 | 29 | jspm_packages 30 | 31 | # Optional npm cache directory 32 | .npm 33 | 34 | # Optional REPL history 35 | .node_repl_history 36 | 37 | # Dat files 38 | *.dat 39 | 40 | #Local server config 41 | .vs -------------------------------------------------------------------------------- /back-end/BypathServer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "SnowrangerNode", "SnowrangerNode\SnowrangerNode.njsproj", "{0C3DAEF8-8B10-4E4F-AA42-5E8483798FAA}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {0C3DAEF8-8B10-4E4F-AA42-5E8483798FAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {0C3DAEF8-8B10-4E4F-AA42-5E8483798FAA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {0C3DAEF8-8B10-4E4F-AA42-5E8483798FAA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {0C3DAEF8-8B10-4E4F-AA42-5E8483798FAA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /back-end/README.md: -------------------------------------------------------------------------------- 1 | This project is for the back end server for the snowranger project 2 | 3 | This project was created using visual studio but it should run on any os with the proper node.js instalation. 4 | 5 | All of the dependencies are located in the package.json file. 6 | 7 | ----- MODULES ----- 8 | Each module will be contained in a folder under root. 9 | 10 | List of current modules: 11 | auth- This module will handle all of teh autorization from server to 3rd party services and server to client 12 | 13 | data- This module will handle managing the data in the database. It will go out and sync to other dbs as well as recieve data from clients and push it to the db 14 | 15 | database- This module handles all calls to and from the db. Right now it is using firebase as the db 16 | 17 | rest- This modules is the rest server. It will pass all calls recieved to other modules for processing 18 | 19 | util- This modules has utility funtions -------------------------------------------------------------------------------- /back-end/constants/env-dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "ACCESS_ORIGIN_URL" : "http://localhost:3000" 3 | } -------------------------------------------------------------------------------- /back-end/constants/env-prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "ACCESS_ORIGIN_URL" : "http://bypath.herokuapp.com" 3 | } -------------------------------------------------------------------------------- /back-end/constants/environment.js: -------------------------------------------------------------------------------- 1 | /* environment.js 2 | * this module will handle environemnt variable "injection". 3 | */ 4 | 5 | var path = require('path'); 6 | var envs = require('envs'); 7 | var fs = require('fs'); 8 | 9 | var DEFAULT_ENV = 'env-prod.json'; 10 | var CONSTANTS_PATH = path.dirname(require.main.filename) + '/constants/'; 11 | 12 | module.exports = { 13 | getEnvironment: function() { 14 | // Handle environment settings. 15 | var output; 16 | if (envs('NODE_ENV') == 'DEV') { 17 | output = JSON.parse(fs.readFileSync(CONSTANTS_PATH + 'env-dev.json', 'utf8')); 18 | } 19 | else if (envs('NODE_ENV') == 'PROD') { 20 | output = JSON.parse(fs.readFileSync(CONSTANTS_PATH + 'env-prod.json', 'utf8')); 21 | } 22 | else { 23 | output = JSON.parse(fs.readFileSync(CONSTANTS_PATH + DEFAULT_ENV, 'utf8')); 24 | } 25 | console.log(output); 26 | return output; 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /back-end/data/SQLData.js: -------------------------------------------------------------------------------- 1 | /* boston311.js 2 | * This will go out and grab relivent data from the boston's 311 data and send it to the firebase 3 | */ 4 | 5 | // Includes 6 | var modules = require('./../util/modules.js'); 7 | var cronJob = require('cron').CronJob; 8 | var request = require('request'); 9 | 10 | 11 | 12 | module.exports = SqlScheduleQuery; 13 | 14 | function SqlScheduleQuery(){ 15 | SqlScheduleQuery.prototype.init = function(url, key, query, updatePath){ 16 | this.url = url; 17 | this.key = key; 18 | this.query = query; 19 | this.updatePath = updatePath; 20 | } 21 | 22 | SqlScheduleQuery.prototype.run = function(cronSchedule, callback){ 23 | this.cJob = new cronJob(cronSchedule, function(){ 24 | console.log('cron job start'); 25 | retieveData(this.updatePath, this.url, this.query, this.key, callback); 26 | console.log('cron job end'); 27 | }.bind(this), null, true, 'UTC'); 28 | } 29 | 30 | SqlScheduleQuery.prototype.forceUpdate = function (callback) { 31 | retieveData(this.updatePath, this.url, this.query, this.key, callback); 32 | } 33 | 34 | SqlScheduleQuery.prototype.test = function(){ 35 | console.log(this.url + " " + this.query + " " + this.key); 36 | } 37 | 38 | function retieveData(updatePath, url, query, key, callback) { 39 | var db = modules.getModule('db'); 40 | console.log("Update path: " + updatePath); 41 | 42 | // db.getLastUpdated(updateSource, function(data){ 43 | // if(data[0] === undefined) 44 | // console.log('this source does not exist'); 45 | // db.addNewSourceUpdate(updateSource); 46 | // } 47 | // else 48 | // { 49 | // console.log('data recieved for the db'); 50 | // console.log(data); 51 | // 52 | // var date = new Date(new Date().setDate(new Date().getDate() - 30)).toISOString(); 53 | // date = date.replace('Z', ''); 54 | // 55 | // db.setSourceUpdated(updateSource, date); 56 | // } 57 | // }); 58 | 59 | 60 | db.getLastUpdated(updatePath, function (data) { 61 | var date = data; 62 | if (data === null) { 63 | console.log('Date for ' + updatePath + " does not exist yet"); 64 | // Create a formated date that we can use if one does not exist 65 | var date = new Date(new Date().setDate(new Date().getDate() - 30)).toISOString(); 66 | date = date.replace('Z', ''); 67 | 68 | db.addNewSourceUpdate(updatePath); 69 | } 70 | 71 | sqlQuery(date, url, query, key, function (res) { 72 | // Add the result of the query to the db 73 | callback(res); 74 | 75 | // Might want to do some checks to make sure there were no errors when 76 | // sending the data to the db before setting the last upated time 77 | console.log('Query completed'); 78 | db.setSourceUpdated(updatePath, new Date().toISOString().replace('Z', '')); 79 | console.log('Update ' + updatePath + ' date set'); 80 | }); 81 | }); 82 | } 83 | 84 | // TODO: maybe the getMeASoda module injection belongs somewhere closer to here? 85 | function sqlQuery(date, url, query, key, callback){ 86 | // Need to add based on case types 87 | query = query.replace('$date', date); 88 | console.log(query); 89 | var stmnt = url + query; 90 | 91 | var options = { 92 | method: 'GET', 93 | url: stmnt, 94 | headers: { 95 | 'X-App-Token': key 96 | } 97 | }; 98 | 99 | var req = request(options, function (error, response, body) { 100 | if (error) { 101 | console.log(error); 102 | } 103 | else { 104 | callback(body); 105 | } 106 | }); 107 | } 108 | } -------------------------------------------------------------------------------- /back-end/data/data_manager.js: -------------------------------------------------------------------------------- 1 | /* data_manager.js 2 | * this module will get and manage the data that is stored in the database 3 | */ 4 | 5 | var builder = require('./endpointBuilder.js'); 6 | var modules = require('./../util/modules.js'); 7 | 8 | var scheduleTile = 0; 9 | var scheduleAdvance = 5; 10 | // These are the data sources 11 | var endpoints = []; 12 | 13 | function getNextSchedule(){ 14 | scheduleTile += scheduleAdvance; 15 | return "00 " + ("0" + scheduleTile).slice(-2) + " * * * *"; 16 | } 17 | 18 | module.exports = { 19 | init: function (){ 20 | }, 21 | 22 | start: function (){ 23 | 24 | var resourceMgr = modules.getModule('resource_manager'); 25 | eps = resourceMgr.getResource('endpoints'); 26 | 27 | console.log(endpoints); 28 | 29 | endpoints = builder.createEndpoints(eps); 30 | 31 | for (i in endpoints) { 32 | endpoints[i].init(); 33 | endpoints[i].print(); 34 | } 35 | 36 | console.log('data manager module started'); 37 | 38 | // Begin scheduler at 5 mins because most 311's 39 | // post their data on the hour so I want to wait until 40 | // after that before cheking for new stuff 41 | var nextSchedule = 5; 42 | 43 | for (i in endpoints) { 44 | endpoints[i].start(getNextSchedule()); 45 | } 46 | }, 47 | 48 | // Create an object from json 49 | // endpointJson is a json object of the data to be added 50 | // The format this is in is { "" : { data } } 51 | // data has the query info and the mapping info 52 | // Refer to endpointBuilder.js addIems function for more info on the strucutre 53 | addEndpoint: function (endpointJson){ 54 | 55 | for (i in endpointJson) { 56 | var endpoint = builder.createEndpoint(i, endpointJson[i]); 57 | 58 | endpoint.init(); 59 | 60 | if (endpoint.isVaild()) { 61 | endpoint.forceUpdate(); 62 | endpoint.start(getNextSchedule()); 63 | 64 | endpoints.push(endpoint); 65 | 66 | return true; 67 | } 68 | } 69 | 70 | console.log('invalid data for endpoint'); 71 | 72 | return false; 73 | }, 74 | 75 | forceUpdate: function () { 76 | for (i in endpoints) { 77 | endpoints[i].forceUpdate(); 78 | } 79 | }, 80 | } 81 | -------------------------------------------------------------------------------- /back-end/data/getMeASoda.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Should return a nice SoQL query string for to use in your SalesForce SQL queries. 3 | * Note: will not handle any other SODA specific input queryables like $limit, $order... 4 | * Also currently it assumes that the base url will supply the `$where=` prefix. 5 | * 6 | * @param {Object} options Dr. Pepper AND Sprite?? Ew. 7 | * @return {String} Put'r into the url. 8 | * ie var query = "SELECT * WHERE open_dt > '$date' AND CASE_STATUS = 'Open' AND (STARTS_WITH(case_title, 'Unsafe/Dangerous Conditions') OR STARTS_WITH(case_title, 'Ground Maintenance') OR STARTS_WITH(case_title, 'Request for Snow Plowing') OR STARTS_WITH(case_title, 'Park Maintenance'))"; 9 | */ 10 | // : 11 | // { 12 | // @param {String} match_attr ie 'type', 'case_title'... 13 | // @param {Array.} matching_array ie ['Rodents', 'Snow Plowing'] 14 | // @param [optional] {String} open_dt in whatever weird iso format thingey 15 | // @param [optional] {String} case_status ie 'Open' or 'Closed' or null 16 | // } 17 | exports.buildMeAQuery = function(options) { 18 | // function query(options) { 19 | 20 | var match_attr = options['matchAttr']; 21 | var matching_array = options['matchingArray']; 22 | var open_dt = options['openDt']; 23 | var case_status = ['caseStatus']; 24 | 25 | // Safety belts. 26 | // 27 | // TODO: find out why this array is actually an object. 28 | if (typeof matching_array !== 'object') return console.log('getMeASoda.query params requires an array for matching, got ' + typeof matching_array + ', it was: ' + matching_array); 29 | if (typeof match_attr !== 'string') return console.log('getMeASoda.query params requires a string for what to match to') 30 | 31 | 32 | // Make a big string. 33 | // 34 | 35 | var s = 'SELECT * WHERE '; 36 | 37 | // Optionally add starting date and case status. 38 | if (open_dt) s += "open_dt > '" + open_dt + "' AND " 39 | if (case_status) s += "CASE_STATUS = '" + case_status + "' AND "; 40 | 41 | // Matching attrs is not optional. 42 | s += '('; 43 | 44 | // For all strings in matching_array matching match_attr. 45 | for (var i = 0; i < matching_array.length; i++) { 46 | s += 'STARTS_WITH(' + match_attr + ", '" + matching_array[i] + "')" 47 | if (i < matching_array.length - 1) s += ' OR '; 48 | } 49 | 50 | s += ')'; 51 | 52 | return s; 53 | }; 54 | 55 | 56 | // Test it. 57 | // 58 | // var util = require('util'); // .inspect 59 | // var justPlainCoke = require('./justPlainCoke.js'); 60 | // console.log(query(justPlainCoke)); 61 | // => SELECT * WHERE CASE_STATUS = 'Open' AND open_dt > '$date' AND (STARTS_WITH(case_title, 'Unsafe/Dangerous Conditions') OR STARTS_WITH(case_title, 'Ground Maintenance') OR STARTS_WITH(case_title, 'Request for Snow Plowing') OR STARTS_WITH(case_title, 'Park Maintenance')) 62 | 63 | -------------------------------------------------------------------------------- /back-end/data/justPlainCoke.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | caseStatus : 'Open', 3 | openDt : '\$date', 4 | matchAttr : 'case_title', 5 | matchingArray : ['Unsafe/Dangerous Conditions','Ground Maintenance','Request for Snow Plowing','Park Maintenance'] 6 | }; 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /back-end/database/database.js: -------------------------------------------------------------------------------- 1 | /* db.js 2 | * this is the module for accessing the postgis db 3 | */ 4 | 5 | // Includes 6 | var post_db = require('./db_postgis.js'); 7 | 8 | // Public methods 9 | module.exports = { 10 | init: function (){ 11 | //db.init(); 12 | }, 13 | 14 | start: function (){ 15 | //db.start(); 16 | }, 17 | 18 | getIssue: function (id, onComplete){ 19 | }, 20 | 21 | addIssue: function (issue){ 22 | post_db.addIssue(issue); 23 | }, 24 | 25 | updateIssue: function (itemId, values){ 26 | }, 27 | 28 | getIssuesWithinDist (latitude, longitude, dist, callback){ 29 | post_db.getIssuesWithinDist(latitude, longitude, dist, callback); 30 | }, 31 | 32 | addParking: function (parking) { 33 | post_db.addParking(parking); 34 | }, 35 | 36 | getParkingWithinDist: function (latitude, longitude, dist, callback){ 37 | post_db.getParkingWithinDist(latitude, longitude, dist, callback); 38 | }, 39 | 40 | addNav: function (nav) { 41 | post_db.addParking(nav); 42 | }, 43 | 44 | getNavWithinDist (latitude, longitude, dist, callback){ 45 | post_db.getNavWithinDist(latitude, longitude, dist, callback); 46 | }, 47 | 48 | getLastUpdated: function (source, callback){ 49 | post_db.getLastUpdated(source, callback); 50 | }, 51 | 52 | //source is the name of the source being updated eg: boston_311 53 | // Date is an ISO standard date eg: 2016-05-03T23:13:34.046 54 | setSourceUpdated: function (source, date){ 55 | post_db.setSourceUpdated 56 | }, 57 | 58 | addNewSourceUpdate: function(source){ 59 | post_db.addNewSourceUpdate(source); 60 | } 61 | } -------------------------------------------------------------------------------- /back-end/database/db_firebase.js: -------------------------------------------------------------------------------- 1 | /* db_firebase.js 2 | * this is the module for accessing the firebase db 3 | */ 4 | 5 | // Includes 6 | var firebase = require('firebase'); 7 | var modules = require('./../util/modules.js'); 8 | // Constants 9 | var MASTER = 'MASTER'; 10 | var VALUE = 'VALUE' 11 | var PATH = 'PATH'; 12 | var DATA = 'DATA'; 13 | 14 | // Private vars 15 | var firebase_url; 16 | var fbRef; 17 | 18 | // Public methods 19 | module.exports = { 20 | init: function (){ 21 | // Nothing initialize 22 | //firebase_url = 'https://alexdev.firebaseio.com/'//resourceMgr.getResource('firebase_url'); 23 | //fbRef = new firebase(firebase_url); 24 | }, 25 | 26 | start: function (){ 27 | var resourceMgr = modules.getModule('resource_manager'); 28 | firebase_url = resourceMgr.getResource('firebase_url'); 29 | fbRef = new firebase(firebase_url); 30 | 31 | console.log('firebase module started'); 32 | }, 33 | 34 | getItem: function (path, onComplete){ 35 | // Async call the callback once the request has completed 36 | fbRef.child(path).once('value', function (snapshot) { 37 | onComplete(snapshot.val()); 38 | }); 39 | }, 40 | 41 | addNewItem: function (data){ 42 | // generate the schema from the data passed in 43 | var result = generateSchema(data); 44 | 45 | // Get the master 46 | master = result[MASTER]; 47 | 48 | // We set the id of the relitive tables based on what is generated when master is added to the db 49 | var response = fbRef.child(master[PATH]).push(master[DATA]); 50 | 51 | var values = result[VALUE]; 52 | 53 | // Iter though each item in the schema and set them on the db relitive to their path 54 | for (i in values) { 55 | var item = values[i]; 56 | 57 | fbRef.child(item[PATH] + '/' + response.key()).set(item[DATA]); 58 | } 59 | }, 60 | 61 | // values will be in the format of 62 | // [{path: path, data: value }, ...] 63 | // Say we are updating the title it would be 64 | // itemId = "-KCSxk6n0DImMtOLx88K" 65 | // values = [{path:title, value:'tree in park'}] 66 | updateItem: function (itemId, values){ 67 | for(i in values){ 68 | var value = values[i]; 69 | 70 | setItem(value[PATH] + "/" + itemId, value[DATA]); 71 | } 72 | }, 73 | 74 | addItem: function (path, value){ 75 | fbRef.child(path).push(value); 76 | }, 77 | 78 | setItem: function (path, value){ 79 | fbRef.child(path).set(value); 80 | }, 81 | 82 | 83 | } 84 | 85 | // Private functions 86 | function generateSchema(data) { 87 | // Grab change the data's format to fit the db 88 | // in the form of : 89 | var values = []; 90 | 91 | values.push(createSchemaItem('/open', data['open'])); 92 | values.push(createSchemaItem('/type', data['type'])); 93 | values.push(createSchemaItem('/title', data['title'])); 94 | values.push(createSchemaItem('/location', data['loc'] || null)); 95 | values.push(createSchemaItem('/geo', data['geo'])); 96 | values.push(createSchemaItem('/source', data['source'])); 97 | // moar values 98 | /* 99 | values.push(createSchemaItem('/open_dt', data['open_dt'])); 100 | values.push(createSchemaItem('/closed_dt', data['closed_dt'])); 101 | values.push(createSchemaItem('/case_status', data['case_status'])); 102 | values.push(createSchemaItem('/ontime_status', data['ontime_status'])); 103 | values.push(createSchemaItem('/neighborhood', data['neighborhood'])); 104 | */ 105 | var output = { 106 | MASTER: createSchemaItem('/master', { 'id': data['id'] }), 107 | VALUE : values 108 | } 109 | 110 | return output; 111 | } 112 | 113 | // Simple helper function for normailzing the data being sent to the server 114 | function createSchemaItem(path, item) { 115 | return { PATH : path, DATA : item }; 116 | } 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /back-end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BypathServer", 3 | "version": "0.0.0", 4 | "description": "Server for the bypath apis", 5 | "main": "server.js", 6 | "author": { 7 | "name": "Alex", 8 | "email": "" 9 | }, 10 | "dependencies": { 11 | "body-parser": "^1.15.0", 12 | "cookie-parser": "^1.4.1", 13 | "cron": "^1.1.0", 14 | "envs": "^0.1.6", 15 | "express": "^4.13.4", 16 | "firebase": "^2.4.1", 17 | "fs-extra": "^0.27.0", 18 | "fs-extra-promise": "^0.3.1", 19 | "pg": "^4.5.5", 20 | "request": "^2.71.0", 21 | "request-promise": "^2.0.1", 22 | "sync-request": "^3.0.0", 23 | "underscore": "^1.8.3" 24 | }, 25 | "devDependencies": { 26 | "chai": "^3.5.0", 27 | "mocha": "^2.5.3" 28 | }, 29 | "scripts": { 30 | "start-dev": "NODE_ENV=DEV npm start", 31 | "test": "mocha test" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /back-end/resources/endpoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "boston_311": { 3 | "url": "https://data.cityofboston.gov/resource/wc8w-nujj.json?$query=", 4 | "key": "soda_key", 5 | "query": "SELECT * WHERE open_dt > '$date' AND CASE_STATUS = 'Open' AND (STARTS_WITH(case_title, 'Unsafe/Dangerous Conditions') OR STARTS_WITH(case_title, 'Ground Maintenance') OR STARTS_WITH(case_title, 'Request for Snow Plowing') OR STARTS_WITH(case_title, 'Park Maintenance')) LIMIT 100", 6 | "map": { 7 | "id": "case_enquiry_id", 8 | "title":"case_title", 9 | "type": "type", 10 | "location": "location", 11 | "open": "open_dt", 12 | "latitude": "latitude", 13 | "longitude": "longitude" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /back-end/resources/keys.txt: -------------------------------------------------------------------------------- 1 | soda_key=k7chiGNz0GPFKd4dS03IEfKuE -------------------------------------------------------------------------------- /back-end/resources/nyc_endpoint.json: -------------------------------------------------------------------------------- 1 | { 2 | "new_york_city_311": { 3 | "url": "https://data.cityofnewyork.us/resource/fhrw-4uyv.json?$query=", 4 | "key": "soda_key", 5 | "query": "SELECT * WHERE created_date > '$date' AND status = 'Open' AND (STARTS_WITH(complaint_type, 'Street Condition') OR STARTS_WITH(complaint_type, 'General Construction/Plumbing') OR STARTS_WITH(complaint_type, 'Street Light Condition') OR STARTS_WITH(complaint_type, 'Illegal Parking')) LIMIT 100", 6 | "map": { 7 | "id": "unique_key", 8 | "title": "descriptor", 9 | "type": "complaint_type", 10 | "location": "incident_address", 11 | "open": "created_date", 12 | "latitude": "latitude", 13 | "longitude": "longitude" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /back-end/resources/parking.json: -------------------------------------------------------------------------------- 1 | { 2 | "cambridge_parking": { 3 | "url": "https://data.cambridgema.gov/resource/t8h9-i4u2.json?$query=", 4 | "key": "soda_key", 5 | "query": "SELECT *", 6 | "map": { 7 | "id": "case_enquiry_id", 8 | "title":"case_title", 9 | "type": "type", 10 | "location": "location", 11 | "open": "open_dt", 12 | "latitude": "latitude", 13 | "longitude": "longitude" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /back-end/resources/resources.txt: -------------------------------------------------------------------------------- 1 | firebase_url=https://snowranger.firebaseio.com/ 2 | endpoints=load(endpoints.json) 3 | -------------------------------------------------------------------------------- /back-end/resources/sf_endpoint.json: -------------------------------------------------------------------------------- 1 | { 2 | "san_francisco_311": { 3 | "url": "https://data.sfgov.org/resource/vw6y-z8j6.json?$query=", 4 | "key": "soda_key", 5 | "query": "SELECT * WHERE opened > '$date' AND status = 'Open' AND STARTS_WITH(category, 'Sidewalk or Curb') LIMIT 100", 6 | "map": { 7 | "id": "case_id", 8 | "title":"request_details", 9 | "type": "category", 10 | "location": "address", 11 | "open": "opened", 12 | "latitude": "point.latitude", 13 | "longitude": "point.longitude" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /back-end/rest/rest_server.js: -------------------------------------------------------------------------------- 1 | /* rest_server.js 2 | * this will hold all of the rest calls for the server 3 | */ 4 | 5 | // Includes 6 | var express = require('express'); 7 | 8 | var index = require('./routes/index.js'); 9 | var incident = require('./routes/incident.js'); 10 | var parking = require('./routes/parking.js'); 11 | var data = require('./routes/data.js'); 12 | var nav = require('./routes/nav.js'); 13 | var env = require('../constants/environment.js'); 14 | 15 | var ENV = env.getEnvironment(); 16 | 17 | // private memeber vars 18 | var app; 19 | var port; 20 | 21 | // public methods 22 | module.exports = { 23 | init: function (){ 24 | app = express(); 25 | 26 | app.use('/', index); 27 | app.use('/incident', incident); 28 | app.use('/parking', parking); 29 | app.use('/data', data); 30 | app.use('/nav', nav); 31 | }, 32 | 33 | start: function (){ 34 | port = process.env.PORT || 8080; 35 | console.log('rest server module started'); 36 | var server = app.listen(port, function(){ 37 | console.log('Server started on port: ' + port); 38 | }); 39 | } 40 | } 41 | 42 | // private methods -------------------------------------------------------------------------------- /back-end/rest/routes/data.js: -------------------------------------------------------------------------------- 1 | // Includes 2 | var express = require('express'); 3 | var bodyParser = require('body-parser'); 4 | var path = require('path'); 5 | var modules = require('./../../util/modules.js'); 6 | 7 | var router = express.Router(); 8 | var urlencodedParser = bodyParser.urlencoded({ extended: false }); 9 | 10 | router.post('/addEndpoint', urlencodedParser, function (req, res) { 11 | console.log('got something'); 12 | 13 | var dm = modules.getModule('data_manager'); 14 | var j = req.body; 15 | 16 | var newEndpoint = {}; 17 | newEndpoint[j.name] = { 18 | 'url': j.url, 19 | 'key': j.key, 20 | 'query': j.query, 21 | 'map': { 22 | 'id': j.id, 23 | 'title': j.title, 24 | 'type': j.type, 25 | 'location': j.location, 26 | 'open': j.open, 27 | 'latitude': j.latitude, 28 | 'longitude': j.longitude 29 | } 30 | }; 31 | 32 | var endpointAdded = dm.addEndpoint(newEndpoint); 33 | 34 | if (endpointAdded) { 35 | res.end('endpoint added'); 36 | } 37 | else { 38 | res.end('invalid endpoint data'); 39 | } 40 | }); 41 | 42 | router.get('/force', function(req, res){ 43 | var dm = modules.getModule('data_manager'); 44 | dm.forceUpdate(); 45 | 46 | res.end('updated'); 47 | }); 48 | 49 | module.exports = router; -------------------------------------------------------------------------------- /back-end/rest/routes/incident.js: -------------------------------------------------------------------------------- 1 | /* incident.js 2 | * this file contains all the controllers for modify, add, delete for incidents 3 | */ 4 | 5 | // Includes 6 | var express = require('express'); 7 | var bodyParser = require('body-parser'); 8 | var path = require('path'); 9 | 10 | var modules = require('./../../util/modules.js'); 11 | var env = require('../../constants/environment.js'); 12 | 13 | var ENV = env.getEnvironment(); 14 | 15 | var router = express.Router(); 16 | var urlencodedParser = bodyParser.urlencoded({ extended: false }); 17 | 18 | 19 | router.get('/', function (req, res) { 20 | res.sendFile(path.resolve(__dirname + "/../views/addIncident.html")); 21 | }); 22 | 23 | router.get('/get', function (req, res) { 24 | db = modules.getModule('db'); 25 | 26 | var latitude = req.query.x; 27 | var longitude = req.query.y; 28 | var dist = req.query.d; 29 | 30 | 31 | console.log('Coords(' + latitude + ', ' + longitude + ') at Distance: ' + dist); 32 | 33 | db.getIssuesWithinDist(latitude, longitude, dist, function(data){ 34 | if (data[0] === undefined) { 35 | res.end('null'); 36 | } 37 | else { 38 | console.log('data recieved from database'); 39 | console.log('Number of items: ' + data.length); 40 | 41 | res.setHeader('Access-Control-Allow-Origin', ENV.ACCESS_ORIGIN_URL);// convert this to a resource 42 | // Request methods you wish to allow 43 | res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); 44 | 45 | // Request headers you wish to allow 46 | res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); 47 | 48 | // Set to true if you need the website to include cookies in the requests sent 49 | // to the API (e.g. in case you use sessions) 50 | res.setHeader('Access-Control-Allow-Credentials', true); 51 | res.end(JSON.stringify(data)); 52 | 53 | console.log(data); 54 | } 55 | }); 56 | 57 | //res.end('recieved'); 58 | }); 59 | 60 | router.post('/addNew', urlencodedParser, function (req, res) { 61 | // Create the item for the db 62 | console.log('Adding new'); 63 | item = { 64 | 'id': '23456',//req.body.id, 65 | 'title':req.body.title, 66 | 'type': req.body.type, 67 | 'open': new Date().toISOString().replace('Z', ''), 68 | 'geo': req.body.geo 69 | }; 70 | 71 | // Get the database module and give it the item to push to the db 72 | db = modules.getModule('db'); 73 | db.addNewItem(item); 74 | 75 | // Just give them the json that was submitted to the db 76 | res.end(JSON.stringify(item)); 77 | }); 78 | 79 | router.post('/update', urlencodedParser, function(req, res){ 80 | var id = req.body.id; 81 | var values = req.body.values; 82 | 83 | db = modules.getModule('db'); 84 | 85 | for(i in values){ 86 | var value = JSON.parse(values[i]); 87 | 88 | db.setItem(value['path'] +'/'+ id, value['value']); 89 | } 90 | 91 | res.end('thanks'); 92 | }); 93 | 94 | router.post('/add', urlencodedParser, function(req, res){ 95 | // Get the path to add 96 | // Get the vaule to add 97 | var path = req.body.path; 98 | var value = req.body.value; 99 | 100 | console.log('Add recieved'); 101 | console.log(req); 102 | 103 | db = modules.getModule('db'); 104 | //fb.addItem(path, value); 105 | 106 | res.end('thanks'); 107 | 108 | }); 109 | 110 | module.exports = router; 111 | -------------------------------------------------------------------------------- /back-end/rest/routes/index.js: -------------------------------------------------------------------------------- 1 | /* index.js 2 | * this constains the controls for the first pages 3 | */ 4 | 5 | var express = require('express'); 6 | var router = express.Router(); 7 | 8 | router.get('/', function (req, res) { 9 | console.log("hi there"); 10 | res.send("Keep them comming"); 11 | }); 12 | 13 | module.exports = router; -------------------------------------------------------------------------------- /back-end/rest/routes/nav.js: -------------------------------------------------------------------------------- 1 | /* incident.js 2 | * this file contains all the controllers for modify, add, delete for incidents 3 | */ 4 | 5 | // Includes 6 | var express = require('express'); 7 | var bodyParser = require('body-parser'); 8 | var path = require('path'); 9 | 10 | var modules = require('./../../util/modules.js'); 11 | var env = require('../../constants/environment.js'); 12 | 13 | var ENV = env.getEnvironment(); 14 | 15 | var router = express.Router(); 16 | var urlencodedParser = bodyParser.urlencoded({ extended: false }); 17 | 18 | router.get('/get', function (req, res) { 19 | db = modules.getModule('db'); 20 | 21 | var latitude = req.query.x; 22 | var longitude = req.query.y; 23 | var dist = req.query.d; 24 | 25 | 26 | console.log('Coords(' + latitude + ', ' + longitude + ') at Distance: ' + dist); 27 | 28 | db.getNavWithinDist(latitude, longitude, dist, function(data){ 29 | if (data[0] === undefined) { 30 | res.end('null'); 31 | } 32 | else { 33 | console.log('data recieved from database'); 34 | console.log('Number of items: ' + data.length); 35 | 36 | res.setHeader('Access-Control-Allow-Origin', ENV.ACCESS_ORIGIN_URL);// convert this to a resource 37 | // Request methods you wish to allow 38 | res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); 39 | 40 | // Request headers you wish to allow 41 | res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); 42 | 43 | // Set to true if you need the website to include cookies in the requests sent 44 | // to the API (e.g. in case you use sessions) 45 | res.setHeader('Access-Control-Allow-Credentials', true); 46 | res.end(JSON.stringify(data)); 47 | } 48 | }); 49 | 50 | //res.end('recieved'); 51 | }); 52 | 53 | router.post('/add', urlencodedParser, function (req, res) { 54 | db = modules.getModule('db'); 55 | 56 | var j = req.body; 57 | var parsed = JSON.parse(j['object']); 58 | 59 | for(var i in parsed){ 60 | 61 | db.addNav(parsed[i]); 62 | console.log(parsed[i]); 63 | } 64 | }); 65 | 66 | module.exports = router; 67 | -------------------------------------------------------------------------------- /back-end/rest/routes/parking.js: -------------------------------------------------------------------------------- 1 | /* incident.js 2 | * this file contains all the controllers for modify, add, delete for incidents 3 | */ 4 | 5 | // Includes 6 | var express = require('express'); 7 | var bodyParser = require('body-parser'); 8 | var path = require('path'); 9 | 10 | var modules = require('./../../util/modules.js'); 11 | var env = require('../../constants/environment.js'); 12 | 13 | var ENV = env.getEnvironment(); 14 | 15 | var router = express.Router(); 16 | var urlencodedParser = bodyParser.urlencoded({ extended: false }); 17 | 18 | router.get('/get', function (req, res) { 19 | db = modules.getModule('db'); 20 | 21 | var latitude = req.query.x; 22 | var longitude = req.query.y; 23 | var dist = req.query.d; 24 | 25 | 26 | console.log('Coords(' + latitude + ', ' + longitude + ') at Distance: ' + dist); 27 | 28 | db.getParkingWithinDist(latitude, longitude, dist, function(data){ 29 | if (data[0] === undefined) { 30 | res.end('null'); 31 | } 32 | else { 33 | console.log('data recieved from database'); 34 | console.log('Number of items: ' + data.length); 35 | 36 | res.setHeader('Access-Control-Allow-Origin', ENV.ACCESS_ORIGIN_URL);// convert this to a resource 37 | // Request methods you wish to allow 38 | res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); 39 | 40 | // Request headers you wish to allow 41 | res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); 42 | 43 | // Set to true if you need the website to include cookies in the requests sent 44 | // to the API (e.g. in case you use sessions) 45 | res.setHeader('Access-Control-Allow-Credentials', true); 46 | res.end(JSON.stringify(data)); 47 | } 48 | }); 49 | 50 | //res.end('recieved'); 51 | }); 52 | 53 | router.post('/add', urlencodedParser, function (req, res) { 54 | db = modules.getModule('db'); 55 | 56 | var j = req.body; 57 | var parsed = JSON.parse(j['object']); 58 | 59 | for(var i in parsed){ 60 | db.addParking(parsed[i]); 61 | console.log(parsed[i]); 62 | } 63 | }); 64 | 65 | module.exports = router; 66 | -------------------------------------------------------------------------------- /back-end/rest/views/addIncident.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | Title:
11 | Type:
12 | Location:
13 | Geo latitude:
14 | Geo longitude: 15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /back-end/server.js: -------------------------------------------------------------------------------- 1 | /* server.js 2 | * this is going to be the main entry point for the application 3 | * this will launch all of the processes and maanged the modules 4 | */ 5 | 6 | // Includes 7 | var modules = require('./util/modules.js'); 8 | var rest_server = require('./rest/rest_server.js'); 9 | var db = require('./database/database.js'); 10 | var fs = require('fs'); 11 | var resource_manager = require('./util/ext_resource_manager.js'); 12 | var data_manager = require('./data/data_manager.js'); 13 | var env = require('./constants/environment.js'); 14 | 15 | var ENV = env.getEnvironment(); 16 | 17 | // Application entry point 18 | function main(){ 19 | var server = new Server(); 20 | server.init(); 21 | } 22 | 23 | function Server(){ 24 | // Private vars 25 | var MODULE_NAME_IDX = 0; 26 | var MODULE_OBJECT_IDX = 1; 27 | 28 | // Public methods 29 | Server.prototype.init = function () { 30 | console.log('starting server'); 31 | // System is the list of systems that are created and used by this server 32 | // They are in the format of [0]module name, [1]module object 33 | // I might want to move this so it is confined 34 | moduleList = [ 35 | ['rest_server', rest_server], 36 | ['db', db], 37 | ['resource_manager', resource_manager], 38 | ['data_manager', data_manager] 39 | ]; 40 | 41 | createModules(moduleList); 42 | startModules(); 43 | } 44 | 45 | // Private methods 46 | function createModules(moduleList) { 47 | for (i in moduleList) { 48 | var modName = moduleList[i][MODULE_NAME_IDX]; 49 | var modObject = moduleList[i][MODULE_OBJECT_IDX]; 50 | 51 | // The order we initialize and add the object to the modules list 52 | // shouldn't matter since module::init() should not ref other modules 53 | modules.addModule(modName, modObject); 54 | 55 | modObject.init(); 56 | } 57 | } 58 | 59 | function startModules(){ 60 | // I might want to move this to the modules module 61 | var mods = modules.getModules(); 62 | 63 | // Iter though the modules and tell them to start 64 | for (i in mods) { 65 | mods[i].start(); 66 | } 67 | } 68 | } 69 | 70 | // Start the program 71 | main(); 72 | -------------------------------------------------------------------------------- /back-end/test/incident-test.js: -------------------------------------------------------------------------------- 1 | var assert = require("chai").assert; 2 | var request = require("request"); 3 | 4 | describe("/incident/get", function () { 5 | it("returns some data", function (done) { 6 | var url = "http://localhost:8080/incident/get?x=43&y=-71&d=1"; 7 | request(url, function (err, response, body) { 8 | var bodyObj = JSON.parse(body); 9 | assert.isNull(err); 10 | assert.isArray(bodyObj); 11 | done(); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /back-end/test/openFridge.js: -------------------------------------------------------------------------------- 1 | // This is just a test module to make sure that stockTheFridge is keeping things ice cold. 2 | 3 | var fridge = require('../data/stockTheFridge.js'); 4 | var fs = require('fs-extra-promise'); 5 | 6 | exports.topShelf = function() { 7 | fridge.topShelf('new_york_city_311', function(r) { 8 | fs.writeFileAsync('./top-shelf-example-nyc311.json', JSON.stringify(r)); 9 | }); 10 | } 11 | 12 | // TODO: not really a test... 13 | // exports.fridge = fridge.fridayNight(); 14 | -------------------------------------------------------------------------------- /back-end/test/top-shelf-example-boston311.json: -------------------------------------------------------------------------------- 1 | ["Schedule a Bulk Item Pickup","Request for Snow Plowing","Requests for Street Cleaning","Missed Trash/Recycling/Yard Waste/Bulk Item","Street Light Outages","Pothole Repair (Internal)","Request for Pothole Repair","Graffiti Removal","Sidewalk Repair (Make Safe)","Parking Enforcement","Schedule a Bulk Item Pickup SS","Unsatisfactory Living Conditions","Tree Maintenance Requests","Request for Recycling Cart","General Comments For a Program or Policy","Sign Repair","Traffic Signal Repair","Sticker Request","Rodent Activity","Pick up Dead Animal"] -------------------------------------------------------------------------------- /back-end/test/top-shelf-example-nyc311.json: -------------------------------------------------------------------------------- 1 | ["HEATING","Street Light Condition","Street Condition","PLUMBING","GENERAL CONSTRUCTION","HEAT/HOT WATER","Blocked Driveway","Water System","PAINT - PLASTER","Illegal Parking","Traffic Signal Condition","NONCONST","Noise","Sewer","ELECTRIC","Dirty Conditions","General Construction/Plumbing","Damaged Tree","Noise - Commercial","Noise - Street/Sidewalk"] -------------------------------------------------------------------------------- /back-end/test/top-shelf-example-sf311.json: -------------------------------------------------------------------------------- 1 | ["Street and Sidewalk Cleaning","Graffiti Public Property","Graffiti Private Property","Abandoned Vehicle","General Requests","Damaged Property","SFHA Requests","Sewer Issues","Streetlights","Tree Maintenance","MUNI Feedback","Street Defects","Litter Receptacles","Illegal Postings","Sign Repair","Rec and Park Requests","Sidewalk or Curb","Temporary Sign Request","311 External Request","Blocked Street or SideWalk"] -------------------------------------------------------------------------------- /back-end/test_server.js: -------------------------------------------------------------------------------- 1 | /* server.js 2 | * this is going to be the main entry point for the application 3 | * this will launch all of the processes and maanged the modules 4 | */ 5 | 6 | // Application entry point 7 | function main(){ 8 | var server = new Server(); 9 | server.init(); 10 | } 11 | 12 | // Includes 13 | var modules = require('./util/modules.js'); 14 | 15 | var rest_server = require('./rest/rest_server.js'); 16 | var db = require('./database/database.js'); 17 | var resource_manager = require('./util/ext_resource_manager.js'); 18 | var data_manager = require('./data/data_manager.js'); 19 | 20 | function Server(){ 21 | // Private vars 22 | var MODULE_NAME_IDX = 0; 23 | var MODULE_OBJECT_IDX = 1; 24 | 25 | // Public methods 26 | Server.prototype.init = function () { 27 | console.log('starting server'); 28 | // System is the list of systems that are created and used by this server 29 | // They are in the format of [0]module name, [1]module object 30 | // I might want to move this so it is confined 31 | moduleList = [ 32 | //['rest_server', rest_server], 33 | ['db', db], 34 | ['resource_manager', resource_manager], 35 | //['data_manager', data_manager] 36 | ]; 37 | 38 | createModules(moduleList); 39 | startModules(); 40 | } 41 | 42 | // Private methods 43 | function createModules(moduleList) { 44 | for (i in moduleList) { 45 | var modName = moduleList[i][MODULE_NAME_IDX]; 46 | var modObject = moduleList[i][MODULE_OBJECT_IDX]; 47 | 48 | // The order we initialize and add the object to the modules list 49 | // shouldn't matter since module::init() should not ref other modules 50 | modules.addModule(modName, modObject); 51 | 52 | modObject.init(); 53 | } 54 | } 55 | 56 | function startModules(){ 57 | // I might want to move this to the modules module 58 | var mods = modules.getModules(); 59 | 60 | // Iter though the modules and tell them to start 61 | for (i in mods) { 62 | mods[i].start(); 63 | } 64 | 65 | var db = modules.getModule('db'); 66 | 67 | var updateSource = 'test'; 68 | 69 | db.getLastUpdated(updateSource, function(data){ 70 | 71 | if(data){ 72 | console.log('data recieved for the db'); 73 | console.log(data); 74 | 75 | var date = new Date(new Date().setDate(new Date().getDate() - 30)).toISOString(); 76 | date = date.replace('Z', ''); 77 | 78 | db.setSourceUpdated(updateSource, date); 79 | } 80 | else 81 | { 82 | console.log('this source does not exist'); 83 | db.addNewSourceUpdate(updateSource); 84 | } 85 | }); 86 | 87 | // issue = { 88 | // opened: new Date().toISOString(), 89 | // source: "test data", 90 | // type: "new test data", 91 | // title: "hop this works", 92 | // address: "123 fake st", 93 | // latitude: "23", 94 | // longitude: "36" 95 | // }; 96 | // 97 | // db.addIssue(issue); 98 | 99 | var point = {x:"43", y:"76"}; 100 | db.getIssuesWithinDist(point, 2, function(data){ 101 | console.log(data); 102 | }); 103 | } 104 | } 105 | 106 | // Start the program 107 | main(); -------------------------------------------------------------------------------- /back-end/util/ext_resource_manager.js: -------------------------------------------------------------------------------- 1 | /* key_manager.js 2 | * this file will load all of the keys from an external file 3 | * so that we can move the sensitive data out of the repo 4 | */ 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | 9 | var RESOURCE_PATH = '../resources/'; 10 | var KEY_FILE = 'keys.txt'; 11 | 12 | // list 13 | var keys = []; 14 | var keysLoaded = false; 15 | 16 | var RESOURCE_FILE = 'resources.txt'; 17 | 18 | // list 19 | var resources = []; 20 | var resourcesLoaded = false; 21 | 22 | // Currently this reads lines and splits on '=' 23 | // I would like to change it to reading a json file 24 | // Also add encryption so keys are not stored in plain text 25 | function loadKeysFromFile(file){ 26 | if (!keysLoaded) { 27 | var data = getLinesFromFile(file); 28 | keys = load(data); 29 | } 30 | } 31 | 32 | function loadResourcesFromFile(file){ 33 | if (!resourcesLoaded) { 34 | var data = getLinesFromFile(file); 35 | resources = load(data); 36 | } 37 | } 38 | 39 | function getLinesFromFile(file){ 40 | var fp = path.join(__dirname, RESOURCE_PATH + file); 41 | var f = fs.readFileSync(fp).toString(); 42 | var values = f.split('\n'); 43 | 44 | // We need to go though and remove all carage returns and new lines chars 45 | for (i in values) { 46 | values[i] = values[i].replace('\r', ''); 47 | } 48 | 49 | return values; 50 | } 51 | 52 | function load(data){ 53 | var output = []; 54 | 55 | for (i in data) { 56 | var line = data[i]; 57 | var idx = line.indexOf('='); 58 | 59 | if (idx > -1) { 60 | // Split the string into id and key 61 | var id = line.substring(0, idx); 62 | var value = line.substring(idx + 1).trim(); 63 | 64 | if(value.lastIndexOf('load', 0) === 0){ 65 | // we need to get the file and load it into the key 66 | var st = value.indexOf('('); 67 | var end = value.indexOf(')'); 68 | 69 | var filePath = value.substring(st + 1, end); 70 | 71 | var file = fs.readFileSync(path.join(__dirname, RESOURCE_PATH + filePath)).toString(); 72 | 73 | var delim = filePath.indexOf('.'); 74 | 75 | var ext = filePath.substring(delim+1).toUpperCase(); 76 | console.log(filePath); 77 | console.log(ext); 78 | 79 | if (ext === 'JSON'){ 80 | console.log('We have a json file') 81 | value = JSON.parse(file); 82 | 83 | for (i in value){ 84 | console.log("key: " + i); 85 | console.log("value: " + value[i]); 86 | } 87 | } 88 | else{ 89 | value = file; 90 | } 91 | 92 | } 93 | 94 | // Set the id, key pair 95 | output[id] = value; 96 | } 97 | } 98 | 99 | return output; 100 | } 101 | 102 | function loadKeys () { 103 | keys = []; 104 | keysLoaded = false; 105 | 106 | loadKeysFromFile(KEY_FILE); 107 | } 108 | 109 | function loadResources () { 110 | resources = []; 111 | resourcesLoaded = false; 112 | 113 | loadResourcesFromFile(RESOURCE_FILE); 114 | } 115 | 116 | module.exports = { 117 | init: function (){ 118 | // The only thing to do is load the keys 119 | loadKeys(); 120 | loadResources(); 121 | }, 122 | 123 | start: function (){ 124 | console.log('resource manager module started'); 125 | // Nothing to do on start 126 | }, 127 | 128 | getKey: function (id){ 129 | 130 | // Check if the key exists before trying to get it out 131 | if (id in keys) { 132 | return keys[id]; 133 | } 134 | }, 135 | 136 | getResource: function (id){ 137 | if (id in resources) { 138 | return resources[id]; 139 | } 140 | }, 141 | } -------------------------------------------------------------------------------- /back-end/util/modules.js: -------------------------------------------------------------------------------- 1 | /* modules.js 2 | * this manages the modules for the system 3 | */ 4 | var modules = []; 5 | 6 | module.exports = { 7 | 8 | addModule: function (id, object) { 9 | // Throw an error if the id has alread beed added 10 | if (id in modules) { 11 | console.log("id: " + id + " in ServerSytems.systems array already exists"); 12 | throw "err"; 13 | } 14 | modules[id] = object; 15 | }, 16 | 17 | // Need a better way to access the modules 18 | // I only want read access to modules not write 19 | getModules: function () { 20 | return modules; 21 | }, 22 | 23 | getModule: function (id) { 24 | if (id in modules) { 25 | return modules[id]; 26 | } 27 | return null; 28 | } 29 | } -------------------------------------------------------------------------------- /chatbot/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | IrcBot 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /chatbot/.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /${PROJECT_DIR_NAME} 5 | 6 | python 3.0 7 | Default 8 | 9 | -------------------------------------------------------------------------------- /chatbot/Boston311.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import FirebaseAPI 3 | from datetime import datetime, timedelta 4 | 5 | class Boston311Endpoint: 6 | caseTypes = {'Ground Maintenance', 'Unsafe/Dangerous Conditions', 'Park Maintenance', 'Request for Snow Plowing'} 7 | 8 | def __init__(self): 9 | fb = FirebaseAPI.FB() 10 | 11 | lastChekedItemId = fb.getLastChecked('/Notifications/311') or (datetime.now() - timedelta(days = 30)).isoformat('T') 12 | currentCheckedItemId = lastChekedItemId 13 | 14 | itemsAdded = 0; 15 | 16 | for type in self.caseTypes: 17 | 18 | r = requests.get( 19 | "https://data.cityofboston.gov/resource/wc8w-nujj.json?$query=" + self.generateSelectStatement(lastChekedItemId, type),#SELECT * WHERE CASE_ENQUIRY_ID > " + str(lastChekedItemId) + " and CASE_STATUS = 'Open'", 20 | headers={"X-App-Token":"k7chiGNz0GPFKd4dS03IEfKuE"}) 21 | 22 | itemsAdded = len(r.json()) 23 | 24 | #print(r.json()) 25 | for item in r.json(): 26 | result = self.BuildData(item) 27 | 28 | if (result != 0): 29 | fb.post('/Notifications/311', result) 30 | else: 31 | itemsAdded -= 1 32 | 33 | 34 | #print(item['case_enquiry_id']) 35 | 36 | fb.setLastChecked('/Notifications/311', datetime.now().isoformat('T')) 37 | 38 | print(str(itemsAdded) + " items have been added") 39 | 40 | def BuildData(self, entry): 41 | try: 42 | id = entry['case_enquiry_id'] 43 | title = entry['case_title'] 44 | loc = entry['location'] 45 | lat = entry['latitude'] 46 | lon = entry['longitude'] 47 | geo = entry['geocoded_location'] 48 | type = entry['type'] 49 | 50 | return {'location': loc, 'case_enquiry_id': id,'case_title': title, 'latitude': lat, 'longitude': lon, 'geo-coords': geo, 'type': type} 51 | except: 52 | return 0 53 | 54 | 55 | def generateSelectStatement(self, date, caseType): 56 | statement = "SELECT * WHERE open_dt > '" + str(date) + "' and CASE_STATUS = 'Open' AND STARTS_WITH(case_title, '" + caseType + "')" 57 | 58 | ''' 59 | cases = "" 60 | for type in self.caseTypes: 61 | if cases != "": 62 | cases += " OR " 63 | 64 | cases +="STARTS_WITH(case_title, ('" + type + "')" 65 | statement += cases 66 | ''' 67 | print(statement) 68 | return statement 69 | 70 | def Connect(self): 71 | pass 72 | 73 | def listen(self): 74 | pass 75 | #longitude 76 | #latitude 77 | #geocoded_location -------------------------------------------------------------------------------- /chatbot/Bot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import threading 3 | import requests 4 | import FirebaseAPI 5 | 6 | imageFolder = "images/" 7 | 8 | class Bot: 9 | name = None 10 | downloadImages = False 11 | endpoints = [] 12 | trds = [] 13 | 14 | def __init__(self, name, downloadImages = False): 15 | self.name = name 16 | self.downloadImages = downloadImages 17 | self.firebase = FirebaseAPI.getFBApplication() 18 | 19 | def AddEndpoint(self, endpoint): 20 | self.endpoints.append(endpoint) 21 | 22 | endpoint.Connect() 23 | endpoint.listen() 24 | # I'm threading so I can have more than one endpoint listening at a time 25 | # the data should be coming async 26 | #t = threading.Thread(target=endpoint.listen) 27 | # classifying as a daemon, so they will die when the main dies 28 | #t.daemon = True 29 | 30 | # begins, must come after daemon definition 31 | #t.start() 32 | #self.trds.append(t) 33 | 34 | # Endpoint is where the data is coming from, It's most likley coming form facebook 35 | # User is a list, the first element should be the user's name, after that is data for the endpoint for replying to the user 36 | # Message is just what the user wrote, I need to be able to parse it out 37 | # Image is a list. The first element should be the image's name, the second element should be it's url 38 | def RecieveMessage(self, endpoint, user, message, image = None): 39 | print("Bot recieved message: '%s' from user: %s"%(message, user[0])) 40 | 41 | img = '' 42 | # Need to do some checks to make sure we have valid image 43 | if image != None: 44 | img = image[0] 45 | print("Image %s has been submitted"%(image[0])) 46 | print("Url: %s"%(image[1])) 47 | 48 | if self.downloadImages: 49 | f = open(imageFolder + image[0] + '.jpg','wb') 50 | f.write(requests.get(image[1]).content) 51 | f.close() 52 | 53 | self.addToFirebase('/Notifications' + endpoint.path, {'user': user[0], 'message': message, 'image': img}) 54 | 55 | 56 | output = '%s message %s has been received' %(user[0], message) 57 | endpoint.send(user[0], output) 58 | ''' 59 | user = message[0] 60 | server = message[1] 61 | mType = message[2] 62 | channel = message[3] 63 | cmd = message[4] 64 | msg = message[5] 65 | 66 | if cmd.find("Alert") != -1: 67 | print("Alert recieved: " + msg) 68 | endpoint.SendMessage("User {}, we recieved your alert for {}".format(user, msg)) 69 | ''' 70 | 71 | #put single in firebase 72 | def addToFirebase(self, path, data): 73 | self.firebase.post(path, data) 74 | 75 | #put list in firebase 76 | def multAddToFirebase(self, path, datas): 77 | for data in datas: 78 | self.addToFirebase(path, data) -------------------------------------------------------------------------------- /chatbot/FacebookEndpoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # I need to figure out how I want to deal with these classes 4 | 5 | class FacebookEndpoint: 6 | pass -------------------------------------------------------------------------------- /chatbot/FirebaseAPI.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import json 3 | from firebase import firebase 4 | 5 | fb_url = 'https://alexdev.firebaseio.com/' 6 | 7 | def getFBApplication(): 8 | return firebase.FirebaseApplication(fb_url, None) 9 | 10 | def __getData(entry): 11 | lat = entry['latitude'] 12 | lon = entry['longitude'] 13 | geo = entry['geocoded_location'] 14 | type = entry['type'] 15 | 16 | return {'latitude': lat, 'longitude': lon, 'geo-coords': geo, 'type': type} 17 | 18 | class FB: 19 | last_checked_key = 'last_checked' 20 | 21 | 22 | def __init__(self): 23 | self.firebaseApplication = getFBApplication() 24 | 25 | def post(self, path, data): 26 | self.firebaseApplication.post(path, data) 27 | 28 | def getLastChecked(self, path): 29 | return self.firebaseApplication.get(path + '/' + self.last_checked_key, None) or 0 30 | 31 | def setLastChecked(self, path, last_checked): 32 | self.firebaseApplication.patch(path, {self.last_checked_key: last_checked}) 33 | 34 | def Run(): 35 | fb = FB() 36 | last = fb.getLastChecked('/Notifications/311') 37 | print(last) 38 | 39 | last += 10 40 | 41 | fb.setLastChecked('/Notifications/311', last) 42 | 43 | return 44 | bp = os.path.dirname(__file__) 45 | fp = os.path.abspath(os.path.join(bp, "..", "..", "311-data-example.json")) 46 | f = open(fp, "r") 47 | data = f.read() 48 | d = json.loads(data, strict=False) 49 | for entry in d: 50 | if entry['case_status'] != 'Open': 51 | if 'case_title' in entry: 52 | type = entry['case_title'] 53 | if type == "Request for Snow Plowing" or type == 'Unsafe/Dangerous Conditions' or type == 'Park Maintenance' or type == 'Ground Maintenance': 54 | 55 | fb.post('/Notifications/311', __getData(entry)) 56 | 57 | -------------------------------------------------------------------------------- /chatbot/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | New BSD License 3 | 4 | Copyright (c) 2015, Taehoon Kim 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * The names of its contributors may not be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /chatbot/images/.gitignore: -------------------------------------------------------------------------------- 1 | #I need to include this file because it is required to download the images 2 | #but I don't want to add the downloaded images to the repo 3 | * 4 | 5 | 6 | !.gitignore -------------------------------------------------------------------------------- /chatbot/main.py: -------------------------------------------------------------------------------- 1 | from client import * 2 | from Bot import * 3 | from IrcEndpoint import * 4 | 5 | import FirebaseAPI 6 | import Boston311 7 | from datetime import datetime, timedelta 8 | 9 | bot = None 10 | 11 | def main(): 12 | CreateBot() 13 | #ConnectToIRC() 14 | #FB_Test() 15 | #ConnectToFacebook() 16 | B311Test() 17 | 18 | while 1: 19 | pass 20 | 21 | def CreateBot(): 22 | global bot 23 | bot = Bot("notificationbot", True) 24 | 25 | def FB_Test(): 26 | FirebaseAPI.Run() 27 | 28 | def B311Test(): 29 | three = Boston311.Boston311Endpoint() 30 | 31 | def ConnectToIRC(): 32 | global bot 33 | 34 | irc = IrcEndpoint(bot) 35 | 36 | bot.AddEndpoint(irc) 37 | 38 | pass 39 | 40 | def ConnectToFacebook(): 41 | 42 | cl = Client("alexswdevtest@gmail.com", 100011287874549, "facebookpassword", bot) 43 | 44 | bot.AddEndpoint(cl) 45 | 46 | 47 | main() 48 | -------------------------------------------------------------------------------- /chatbot/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import sys 3 | 4 | class Base(): 5 | def __repr__(self): 6 | uni = self.__unicode__() 7 | return uni.encode('utf-8') if sys.version_info < (3, 0) else uni 8 | 9 | def __unicode__(self): 10 | return u'<%s %s (%s)>' % (self.type.upper(), self.name, self.url) 11 | 12 | class User(Base): 13 | def __init__(self, data): 14 | if data['type'] != 'user': 15 | raise Exception("[!] %s <%s> is not a user" % (data['text'], data['path'])) 16 | self.uid = data['uid'] 17 | self.type = data['type'] 18 | self.photo = data['photo'] 19 | self.url = data['path'] 20 | self.name = data['text'] 21 | self.score = data['score'] 22 | 23 | self.data = data 24 | 25 | class Thread(): 26 | def __init__(self, **entries): 27 | self.__dict__.update(entries) 28 | 29 | class Message(): 30 | def __init__(self, **entries): 31 | self.__dict__.update(entries) 32 | -------------------------------------------------------------------------------- /chatbot/stickers.py: -------------------------------------------------------------------------------- 1 | LIKES={ 2 | 'l': '369239383222810', 3 | 'm': '369239343222814', 4 | 's': '369239263222822' 5 | } 6 | LIKES['large'] = LIKES['l'] 7 | LIKES['medium'] =LIKES['m'] 8 | LIKES['small'] = LIKES['s'] 9 | -------------------------------------------------------------------------------- /chatbot/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | from time import time 4 | from random import random 5 | import pprint 6 | 7 | USER_AGENTS = [ 8 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311 you do not need bs4 to be installed in your system python path, uninstall it and keep it in your virtualenv..90 Safari/537.36", 9 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/601.1.10 (KHTML, like Gecko) Version/8.0.5 Safari/601.1.10", 10 | "Mozilla/5.0 (Windows NT 6.3; WOW64; ; NCT50_AAP285C84A1328) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", 11 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", 12 | "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", 13 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6" 14 | ] 15 | 16 | def now(): 17 | return int(time()*1000) 18 | 19 | def get_json(text): 20 | #Make sure to sanitize the text before trying to parse it 21 | sanText = text.replace('/"', '"') 22 | return json.loads(re.sub(r"for.*(.*;.*;.*).*;", '', sanText.encode('utf-8').decode("unicode-escape"), 1)) 23 | 24 | def print_json(j): 25 | print("Printing Json\n\n") 26 | pprint.pprint(j) 27 | 28 | print("\n\nEnd\n") 29 | 30 | def digit_to_char(digit): 31 | if digit < 10: 32 | return str(digit) 33 | return chr(ord('a') + digit - 10) 34 | 35 | def str_base(number,base): 36 | if number < 0: 37 | return '-' + str_base(-number, base) 38 | (d, m) = divmod(number, base) 39 | if d > 0: 40 | return str_base(d, base) + digit_to_char(m) 41 | return digit_to_char(m) 42 | 43 | def generateMessageID(client_id=None): 44 | k = now() 45 | l = int(random() * 4294967295) 46 | return ("<%s:%s-%s@mail.projektitan.com>" % (k, l, client_id)) 47 | -------------------------------------------------------------------------------- /front-end/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /front-end/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // "extends": "eslint:recommended", 3 | // "env": { 4 | // "node": true 5 | // }, 6 | // "rules": { 7 | // // overrides from 'recommended' 8 | // "comma-dangle": 0, 9 | // "no-console": 0, 10 | 11 | // // strict 12 | // "strict": [2, "global"], 13 | 14 | // // formatting, spaces, operators 15 | // // "indent": [1, 1, {"SwitchCase": 1}], 16 | // "quotes": [1, "single"], 17 | // "semi": [1, "always"], 18 | // "space-after-keywords": [1, "always"], 19 | // "space-before-blocks": [1, "always"], 20 | // "space-before-function-paren": [1, "always"], 21 | // "dot-location": [1, "property"], 22 | // "space-infix-ops": 1, 23 | // "key-spacing": [1, {"beforeColon": false, "afterColon": true}], 24 | // "operator-linebreak": [1, "after"], 25 | // "no-spaced-func": 1, 26 | // "comma-spacing": [1, {"before": false, "after": true}], 27 | // "no-multiple-empty-lines": [1, {"max": 1}], 28 | // "camelcase": [1, {"properties": "always"}], 29 | // "no-extra-semi" : 1, 30 | 31 | // // coding style 32 | // "curly": 1, 33 | // "wrap-iife": [1, "inside"], 34 | // "eqeqeq": 1, 35 | // "no-use-before-define": [1, "nofunc"], 36 | // "no-unused-vars": 1, 37 | // "no-unexpected-multiline": 1, 38 | // "no-multi-str": 1, 39 | // "no-irregular-whitespace": 1, 40 | // "key-spacing": 1, 41 | 42 | // // os/git options 43 | // "no-trailing-spaces": 1, 44 | // "linebreak-style": 0, 45 | // "eol-last": 1 46 | // } 47 | } 48 | -------------------------------------------------------------------------------- /front-end/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /front-end/.gitignore: -------------------------------------------------------------------------------- 1 | # temp files 2 | .DS_Store 3 | .tmp 4 | .sass-cache 5 | 6 | # npm & bower 7 | /node_modules/ 8 | /app/bower_components/ 9 | 10 | # custom gulp settings 11 | /gulp/.gulp_settings.json 12 | 13 | # ionic fonts 14 | /app/main/assets/fonts/ionicons.* 15 | 16 | # cordova 17 | /platforms/ 18 | /plugins/ 19 | /res/*/current/ 20 | 21 | # build folder 22 | /www/** 23 | 24 | # IDE 25 | *.sublime-project 26 | *.sublime-workspace 27 | -------------------------------------------------------------------------------- /front-end/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-m-ionic": { 3 | "answers": { 4 | "appName": "ByPath", 5 | "appId": "org.codeforboston.bypath", 6 | "ionicCss": false, 7 | "bowerPackages": [ 8 | "angular-dynamic-locale#~0.1.27", 9 | "angular-translate#~2.11.0", 10 | "angular-translate-loader-static-files#~2.11.0", 11 | "localforage#~1.4.0" 12 | ], 13 | "platforms": [ 14 | "ios", 15 | "android" 16 | ], 17 | "plugins": [ 18 | "cordova-plugin-device", 19 | "cordova-plugin-dialogs", 20 | "ionic-plugin-keyboard", 21 | "cordova-plugin-network-information", 22 | "cordova-plugin-splashscreen", 23 | "cordova-plugin-statusbar" 24 | ], 25 | "appModule": "ByPath", 26 | "ecosystems": [] 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /front-end/app/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc", 3 | "env": { 4 | "node": false, 5 | "browser": true 6 | }, 7 | "globals": { 8 | "angular": true, 9 | "ionic": true, 10 | "localforage": true 11 | }, 12 | "rules": { 13 | "no-console": 1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /front-end/app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('ByPath', [ 4 | 'main' 5 | ]) 6 | 7 | .config(function($logProvider) { 8 | $logProvider.debugEnabled(false); 9 | }) 10 | 11 | .config(function($ionicConfigProvider) { 12 | $ionicConfigProvider.tabs.position("bottom"); 13 | }); 14 | -------------------------------------------------------------------------------- /front-end/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ByPath 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 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |

ByPath

80 |
81 | 82 | 83 | 84 | 85 | 86 |
87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /front-end/app/main/assets/images/boston311.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/app/main/assets/images/boston311.png -------------------------------------------------------------------------------- /front-end/app/main/assets/images/danger-hump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/app/main/assets/images/danger-hump.png -------------------------------------------------------------------------------- /front-end/app/main/assets/images/falling-person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/app/main/assets/images/falling-person.png -------------------------------------------------------------------------------- /front-end/app/main/assets/images/graffiti-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/app/main/assets/images/graffiti-icon.png -------------------------------------------------------------------------------- /front-end/app/main/assets/images/lawnmower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/app/main/assets/images/lawnmower.png -------------------------------------------------------------------------------- /front-end/app/main/assets/images/snow_plow_truck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/app/main/assets/images/snow_plow_truck.png -------------------------------------------------------------------------------- /front-end/app/main/assets/images/snowflake-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/app/main/assets/images/snowflake-icon.png -------------------------------------------------------------------------------- /front-end/app/main/assets/images/snowflake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/app/main/assets/images/snowflake.png -------------------------------------------------------------------------------- /front-end/app/main/assets/images/yo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/app/main/assets/images/yo@2x.png -------------------------------------------------------------------------------- /front-end/app/main/constants/config-const.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .constant('Config', { 6 | 7 | // gulp environment: injects environment vars 8 | ENV: { 9 | /*inject-env*/ 10 | 'BYPATH_API': 'http://localhost:8080', 11 | 'MAPBOX_API': 'https://api.mapbox.com/v4/{mapid}/{z}/{x}/{y}.{format}?access_token={apikey}', 12 | 'GOOGLE_MAPS_API': 'https://maps.googleapis.com/maps/api' 13 | /*endinject*/ 14 | }, 15 | 16 | // gulp build-vars: injects build vars 17 | BUILD: { 18 | /*inject-build*/ 19 | /*endinject*/ 20 | } 21 | 22 | }); -------------------------------------------------------------------------------- /front-end/app/main/constants/env-dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "BYPATH_API" : "http://localhost:8080", 3 | "MAPBOX_API" : "https://api.mapbox.com/v4/{mapid}/{z}/{x}/{y}.{format}?access_token={apikey}", 4 | "GOOGLE_MAPS_API" : "https://maps.googleapis.com/maps/api" 5 | } 6 | -------------------------------------------------------------------------------- /front-end/app/main/constants/env-prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "BYPATH_API" : "http://bypath-api.herokuapp.com", 3 | "MAPBOX_API" : "https://api.mapbox.com/v4/{mapid}/{z}/{x}/{y}.{format}?access_token={apikey}", 4 | "GOOGLE_MAPS_API" : "https://maps.googleapis.com/maps/api" 5 | } 6 | -------------------------------------------------------------------------------- /front-end/app/main/controllers/main.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .controller('MainCtrl', function($rootScope, $state, $log, $timeout) { 6 | var mainCtrl = this; 7 | 8 | $rootScope.space = {}; 9 | }); 10 | -------------------------------------------------------------------------------- /front-end/app/main/controllers/map.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .controller('MapCtrl', function($rootScope, $scope, $state, $log, $filter, Config, Database, Map, TileSets) { 6 | 7 | var mapCtrl = this; 8 | mapCtrl.incidents = {}; 9 | mapCtrl.incidentsGeotagged = []; 10 | mapCtrl.filters = {}; 11 | mapCtrl.popup = 12 | '
\ 13 |

{{ incidentSelected.title }}Map

\ 14 |

{{ incidentSelected.address }}

\ 15 | Opened: \ 16 | {{ incidentSelected.opened | date:"MMM dd" }}\ 17 |
'; 18 | 19 | mapCtrl.queryBackend = true; 20 | // Default scope values. 21 | $scope.incidentSelected = {}; 22 | $scope.markers = []; 23 | $scope.tiles = { 24 | name: "Streets Basic", 25 | url: Config.ENV.MAPBOX_API, 26 | type: "xyz", 27 | options: { 28 | mapid: "mapbox.streets-basic", 29 | format: "png", 30 | apikey: "pk.eyJ1IjoiYWVsYXdzb24iLCJhIjoiY2luMnBtdGMxMGJta3Y5a2tqd29sZWlzayJ9.wxiPp_RFGYXGB2SzXZvfaA" 31 | } 32 | }; 33 | $scope.center = { 34 | lat: 42.39137720000001, 35 | lng: -71.1473425, 36 | zoom: 12 37 | }; 38 | 39 | // Initialize map data. 40 | function initMapData() { 41 | <<<<<<< 24ca23996040f37c5d489825cd77a88ae795e394 42 | getMapMarkers($scope.center.lat, $scope.center.lng, 0.35); 43 | ======= 44 | Database.getIssues($scope.center.lat, $scope.center.lng, 0.35, function(incidents) { 45 | mapCtrl.incidents = incidents; 46 | generateMapMarkers(); 47 | }); 48 | // Map.drawLocation(); 49 | >>>>>>> userMapCircle&changesToMap.controller.js(?) 50 | }; 51 | 52 | // Set events on the map. 53 | function initMapEvents(map) { 54 | $scope.$on('leafletDirectiveMap.map.moveend', function() { 55 | var viewport = Map.getCurrentViewport(map); 56 | getMapMarkers(viewport.latitude, viewport.longitude, viewport.distance); 57 | }); 58 | $scope.$on('leafletDirectiveMarker.map.click', function(e, args) { 59 | $scope.incidentSelected = args.model.model; 60 | }); 61 | 62 | }; 63 | 64 | function positionSuccess(position) { 65 | $log.debug("Updated user position."); 66 | $scope.center = Map.getCenterObject(position.coords.latitude, position.coords.longitude, 12); 67 | }; 68 | 69 | function positionError(error) { 70 | $log.debug("Failed to update user position."); 71 | }; 72 | 73 | function generateMapMarkers() { 74 | var markers = []; 75 | angular.forEach(mapCtrl.incidents, function(value, key) { 76 | mapCtrl.filters[value.type] = value.type; 77 | var extendedObj = angular.extend(value, { 78 | 'markablePosition': { 79 | latitude: value.latitude, 80 | longitude: value.longitude 81 | } 82 | }); 83 | if (value.latitude && value.longitude) { 84 | markers[value.id] = { 85 | group: 'all', 86 | model: value, 87 | lat: value.latitude, 88 | lng: value.longitude, 89 | message: mapCtrl.popup, 90 | getMessageScope: function() { return $scope; }, 91 | focus: false, 92 | draggable: false 93 | }; 94 | } 95 | }); 96 | 97 | $scope.markers = markers; 98 | }; 99 | 100 | function getMapMarkers(lat, lng, dist){ 101 | if (mapCtrl.queryBackend){ 102 | Database.getIssues(lat, lng, dist, function(incidents) { 103 | mapCtrl.incidents = incidents; 104 | generateMapMarkers(); 105 | }); 106 | mapCtrl.queryBackend = false; 107 | setTimeout(queryReady, 3000); 108 | } 109 | }; 110 | 111 | function queryReady() { 112 | mapCtrl.queryBackend = true; 113 | } 114 | 115 | Map.initMap(positionSuccess, positionError, initMapData, initMapEvents); 116 | }); 117 | -------------------------------------------------------------------------------- /front-end/app/main/controllers/parking.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .controller('ParkingCtrl', function($rootScope, $scope, $state, $log, $filter, Config, Database, Map, TileSets) { 6 | 7 | var parkingCtrl = this; 8 | 9 | parkingCtrl.incidents = {}; 10 | parkingCtrl.incidentsGeotagged = []; 11 | parkingCtrl.filters = {}; 12 | parkingCtrl.popup = 13 | '
\ 14 |

{{ incidentSelected.title }}

\ 15 |

{{ incidentSelected.address }}

\ 16 | Opened: \ 17 | {{ incidentSelected.opened | date:"MMM dd" }}\ 18 |
'; 19 | 20 | // Default scope values. 21 | $scope.incidentSelected = {}; 22 | $scope.markers = []; 23 | $scope.tiles = { 24 | name: "Streets Basic", 25 | url: Config.ENV.MAPBOX_API, 26 | type: "xyz", 27 | options: { 28 | mapid: "mapbox.streets-basic", 29 | format: "png", 30 | apikey: "pk.eyJ1IjoiYWVsYXdzb24iLCJhIjoiY2luMnBtdGMxMGJta3Y5a2tqd29sZWlzayJ9.wxiPp_RFGYXGB2SzXZvfaA" 31 | } 32 | }; 33 | $scope.center = { 34 | lat: 42.39137720000001, 35 | lng: -71.1473425, 36 | zoom: 12 37 | }; 38 | 39 | // Initialize map data. 40 | function initMapData() { 41 | Database.getParking($scope.center.lat, $scope.center.lng, 0.35, function(parking) { 42 | console.log(parking); 43 | parkingCtrl.incidents = parking; 44 | generateMapMarkers(); 45 | }); 46 | }; 47 | 48 | // Set events on the map. 49 | function initMapEvents(map) { 50 | // Returns an function that on call will stop the callback 51 | var unregisterMapMove = $scope.$on('leafletDirectiveMap.map.moveend', function() { 52 | var viewport = Map.getCurrentViewport(map); 53 | Database.getParking(viewport.latitude, viewport.longitude, viewport.distance, function(parking) { 54 | console.log(parking); 55 | parkingCtrl.incidents = parking; 56 | generateMapMarkers(); 57 | }); 58 | }); 59 | 60 | var unregisterMapClick = $scope.$on('leafletDirectiveMarker.map.click', function(e, args) { 61 | $scope.incidentSelected = args.model.model; 62 | }); 63 | }; 64 | 65 | function positionSuccess(position) { 66 | $log.debug("Updated user position."); 67 | $scope.center = Map.getCenterObject(position.coords.latitude, position.coords.longitude, 12); 68 | }; 69 | 70 | function positionError(error) { 71 | $log.debug("Failed to update user position."); 72 | }; 73 | 74 | function generateMapMarkers() { 75 | var markers = []; 76 | angular.forEach(parkingCtrl.incidents, function(value, key) { 77 | parkingCtrl.filters[value.type] = value.type; 78 | var extendedObj = angular.extend(value, { 79 | 'markablePosition': { 80 | latitude: value.latitude, 81 | longitude: value.longitude 82 | } 83 | }); 84 | if (value.latitude && value.longitude) { 85 | markers[value.id] = { 86 | group: 'all', 87 | model: value, 88 | lat: value.latitude, 89 | lng: value.longitude, 90 | message: parkingCtrl.popup, 91 | getMessageScope: function() { return $scope; }, 92 | focus: false, 93 | draggable: false 94 | }; 95 | } 96 | }); 97 | 98 | $scope.markers = markers; 99 | }; 100 | 101 | Map.initMap(positionSuccess, positionError, initMapData, initMapEvents); 102 | }); 103 | -------------------------------------------------------------------------------- /front-end/app/main/filters/casetype.filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | /** 6 | * Return only cases that match the given case types. 7 | * @param {Array.} complaints The array to be filtered. 8 | * @param {Object} caseTypesToMatch Desired case types to show in list or on map. ie { "action": true, "family": false } 9 | * @return {Array.} Filtered array. 10 | */ 11 | // http://stackoverflow.com/questions/15868248/how-to-filter-multiple-values-or-operation-in-angularjs 12 | // http://jsfiddle.net/Xesued/Bw77D/7/ 13 | .filter('whereCaseType', function () { 14 | return function (complaints, caseTypesToMatch) { 15 | var items = { 16 | types: caseTypesToMatch, 17 | out: [] 18 | }; 19 | angular.forEach(complaints, function (value, key) { 20 | if (typeof value['type'] === 'undefined') { return; } 21 | if (this.types[value['type']]) { 22 | this.out.push(value); 23 | } 24 | }, items); 25 | 26 | return items.out; 27 | }; 28 | }) 29 | -------------------------------------------------------------------------------- /front-end/app/main/filters/id.filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .filter('threeOneOneById', function () { 6 | return function (input) { 7 | return 'threeOneOneById filter: ' + input; 8 | }; 9 | }) 10 | -------------------------------------------------------------------------------- /front-end/app/main/filters/incidenttype.filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .filter('incidentType', function() { 6 | return function (incidents, filters) { 7 | var filteredItems = { 8 | filters: filters, 9 | items: [] 10 | }; 11 | angular.forEach(incidents, function (value, key) { 12 | filters.forEach(function(filter) { 13 | if (value.type == filter) { 14 | filteredItems.items.push(value); 15 | } 16 | }); 17 | }, filteredItems); 18 | 19 | return filteredItems.items; 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /front-end/app/main/filters/type.filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .filter('byType', function ($log) { 6 | return function (cases, types) { 7 | $log.log('types', types); 8 | var items = { 9 | types: types, 10 | out: [] 11 | }; 12 | angular.forEach(cases, function (value, key) { 13 | // $log.log('value.type', value.type); 14 | // $log.log('types.indexOf(value.type)', types.indexOf(value.type)); 15 | if (types.indexOf(value.type) > -1) { 16 | this.out.push(value); 17 | } 18 | }, items); 19 | 20 | return items.out; 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /front-end/app/main/filters/unique.filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .filter('unique', function () { 6 | 7 | return function (items, filterOn) { 8 | 9 | if (filterOn === false) { 10 | return items; 11 | } 12 | 13 | if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) { 14 | var hashCheck = {}, newItems = []; 15 | 16 | var extractValueToCompare = function (item) { 17 | if (angular.isObject(item) && angular.isString(filterOn)) { 18 | return item[filterOn]; 19 | } else { 20 | return item; 21 | } 22 | }; 23 | 24 | angular.forEach(items, function (item) { 25 | var valueToCheck, isDuplicate = false; 26 | 27 | for (var i = 0; i < newItems.length; i++) { 28 | if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) { 29 | isDuplicate = true; 30 | break; 31 | } 32 | } 33 | if (!isDuplicate) { 34 | newItems.push(item); 35 | } 36 | 37 | }); 38 | items = newItems; 39 | } 40 | return items; 41 | }; 42 | }) 43 | -------------------------------------------------------------------------------- /front-end/app/main/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main', [ 4 | 'ionic', 5 | 'ngCordova', 6 | 'ui.router', 7 | 'leaflet-directive' 8 | ]) 9 | 10 | .config(function ($stateProvider, $urlRouterProvider) { 11 | $urlRouterProvider.otherwise('/main/map'); 12 | $stateProvider 13 | .state('main', { 14 | url: '/main', 15 | abstract: true, 16 | templateUrl: 'main/templates/tabs.html', 17 | controller: 'MainCtrl as mainCtrl' 18 | }) 19 | .state('main.map', { 20 | url: '/map', 21 | views: { 22 | 'tab-map': { 23 | templateUrl: 'main/templates/map.html', 24 | controller: 'MapCtrl as mapCtrl' 25 | } 26 | } 27 | }) 28 | .state('main.parking', { 29 | url: '/parking', 30 | views:{ 31 | 'tab-parking': { 32 | templateUrl: 'main/templates/map.html', 33 | controller: 'ParkingCtrl as parkingCtrl' 34 | } 35 | } 36 | }) 37 | }); -------------------------------------------------------------------------------- /front-end/app/main/services/geolocation.factory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .factory('Geolocation', function($cordovaGeolocation, $log, $q, $http, Config) { 6 | 7 | var options = { 8 | timeout: 10000, 9 | enableHighAccuracy: true 10 | }; 11 | 12 | /** 13 | * Sets the user position from the device. 14 | * Returns a promise containing the position. 15 | * 16 | * @param {} 17 | * @return {Promise} 18 | */ 19 | function getUserPosition() { 20 | $log.debug('Getting current location.'); 21 | return $cordovaGeolocation.getCurrentPosition(options); 22 | }; 23 | 24 | /** 25 | * Watches the user position from the device. 26 | * Returns a promise containing the position. 27 | * 28 | * @param {} 29 | * @return {Promise} 30 | */ 31 | function watchUserPosition() { 32 | $log.debug('Watch user location.'); 33 | return $cordovaGeolocation.watchPosition(options); 34 | }; 35 | 36 | /** 37 | * Gets nearby city for a given latitude and longitude. 38 | * Returns a promise for the location of a nearby city. 39 | * 40 | * @param {Float, Float} 41 | * @return {Promise} 42 | */ 43 | function getNearByCity(latitude, longitude) { 44 | $log.debug('Get nearby city.'); 45 | var url = Config.ENV.GOOGLE_MAPS_API + '/geocode/json?latlng=' + latitude + ',' + longitude + '&sensor=true'; 46 | return $http({ 47 | method: 'GET', 48 | url: url 49 | }); 50 | } 51 | 52 | return { 53 | getUserPosition: getUserPosition, 54 | watchUserPosition: watchUserPosition, 55 | getNearByCity: getNearByCity 56 | }; 57 | }); 58 | -------------------------------------------------------------------------------- /front-end/app/main/services/map.factory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .factory('Map', function(Geolocation, leafletData) { 6 | 7 | var watchId; 8 | 9 | function startWatchingUserPosition() { 10 | return Geolocation.watchUserPosition({ 11 | frequency : 1000, 12 | timeout : 3000, 13 | enableHighAccuracy: true 14 | }); 15 | }; 16 | 17 | function initMap(positionSuccess, positionError, mapData, mapEvents) { 18 | this.watchId = startWatchingUserPosition(); 19 | this.watchId 20 | .then(null, positionError, positionSuccess) 21 | .then(initMapSettings(mapData, mapEvents)); 22 | }; 23 | 24 | function initMapSettings(mapData, mapEvents) { 25 | leafletData.getMap("map") 26 | .then(function(map) { 27 | mapData(); 28 | mapEvents(map); 29 | // setInterval() 30 | L.circle([42.33, -71.12], 3000).addTo(map); 31 | }); 32 | 33 | }; 34 | 35 | function Viewport() { 36 | Viewport.prototype.latitude; 37 | Viewport.prototype.longitude; 38 | Viewport.prototype.distance; 39 | }; 40 | 41 | function getCurrentViewport(map) { 42 | var v = new Viewport(); 43 | var coords = map.getCenter(); 44 | var ne = map.getBounds().getNorthEast(); 45 | var sw = map.getBounds().getSouthWest(); 46 | var dist = Math.max(Math.abs(ne.lat - sw.lat), Math.abs(ne.lng - sw.lng)); 47 | v.latitude = coords.lat; 48 | v.longitude = coords.lng; 49 | v.distance = dist; 50 | 51 | return v; 52 | }; 53 | 54 | function getCenterObject(latitude, longitude, zoom) { 55 | return { 56 | lat: latitude, 57 | lng: longitude, 58 | zoom: zoom 59 | }; 60 | }; 61 | 62 | function drawLocation() { 63 | L.circle([42,-71], 7000).addTo(map); 64 | } 65 | 66 | return { 67 | watchId : watchId, 68 | initMap : initMap, 69 | getCenterObject: getCenterObject, 70 | getCurrentViewport: getCurrentViewport, 71 | drawLocation: drawLocation 72 | }; 73 | }); -------------------------------------------------------------------------------- /front-end/app/main/services/marker.factory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .factory('MarkerFactory', function MarkerFactory ($log, $http, $q, ThreeOneOne, Utils, Geo) { 6 | 7 | /*---------- Parse 311 data into map markers static array ----------*/ 8 | 9 | // Expects {data: {data: [{object}, {object}...]}} 10 | var parseDataToMarkers = function (data) { 11 | 12 | var markers = []; 13 | 14 | for (var i = 0; i < data.data.length; i++) { 15 | 16 | // Add to Geofire. 17 | ThreeOneOne.addToGeofire( 18 | // --> key 19 | data.data[i].case_enquiry_id, 20 | // --> location 21 | [ 22 | parseFloat(data.data[i].latitude), 23 | parseFloat(data.data[i].longitude) 24 | ] 25 | ); 26 | 27 | // Push to map marker array. 28 | markers.push({ 29 | id: data.data[i].case_enquiry_id, 30 | 31 | description: data.data[i].case_title, 32 | location: { 33 | latitude: data.data[i].latitude, 34 | longitude: data.data[i].longitude 35 | }, 36 | address: data.data[i].location, 37 | case_status: data.data[i].case_status, 38 | open_dt: data.data[i].open_dt, 39 | closed_dt: data.data[i].closed_dt 40 | }); 41 | } 42 | 43 | // Finally, set constructed array to also hold path urls for associated images and return. 44 | return Utils.setIcons(markers); 45 | }; 46 | 47 | return { 48 | // currentMarkers: MarkerFactory.currentMarkers, 49 | parseDataToMarkers: parseDataToMarkers 50 | }; 51 | }); 52 | -------------------------------------------------------------------------------- /front-end/app/main/services/tilesets.factory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .factory('TileSets', ['Config', function(Config) { 6 | 7 | function getTileSet(tileset) { 8 | switch (tileset) { 9 | case "Streets Basic": 10 | default: 11 | return { 12 | name: "Streets Basic", 13 | url: Config.ENV.MAPBOX_API, 14 | type: "xyz", 15 | options: { 16 | mapid: "mapbox.streets-basic", 17 | format: "png", 18 | apikey: "pk.eyJ1IjoiYWVsYXdzb24iLCJhIjoiY2luMnBtdGMxMGJta3Y5a2tqd29sZWlzayJ9.wxiPp_RFGYXGB2SzXZvfaA" 19 | } 20 | }; 21 | } 22 | }; 23 | 24 | return { 25 | getTileSet: getTileSet 26 | }; 27 | }]); -------------------------------------------------------------------------------- /front-end/app/main/services/utilities.factory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('main') 4 | 5 | .factory('Utils', function ($log) { 6 | 7 | function matchIcon (string) { 8 | var desc = string; 9 | var icon_path = ''; 10 | 11 | var groundy = /ground/i; 12 | var snowy = /snow/i; 13 | var parky = /park/i; 14 | var dangery = /danger/i; 15 | 16 | if (desc.match(groundy)) { 17 | icon_path = 'main/assets/images/danger-hump.png'; 18 | } 19 | else if (desc.match(snowy)) { 20 | icon_path = 'main/assets/images/snow_plow_truck.png'; 21 | } 22 | else if (desc.match(parky)) { 23 | icon_path = 'main/assets/images/lawnmower.png'; 24 | } 25 | else if (desc.match(dangery)) { 26 | icon_path = 'main/assets/images/falling-person.png'; 27 | } 28 | else { 29 | icon_path = 'main/assets/images/snowflake-icon.png'; 30 | } 31 | return icon_path; 32 | }; 33 | 34 | var setIcons = function (objArray) { 35 | var dataWithIcons = []; 36 | 37 | for (var i = 0; i < objArray.length; i++) { 38 | var a = objArray[i]; 39 | a.icon = matchIcon(a.description); 40 | dataWithIcons.push(a); 41 | } 42 | 43 | return dataWithIcons; 44 | }; 45 | 46 | return { 47 | matchIcon: matchIcon, 48 | setIcons: setIcons 49 | }; 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /front-end/app/main/styles/_customType.scss: -------------------------------------------------------------------------------- 1 | // 2 | .type { 3 | &.type-small { 4 | font-size: .7em; 5 | } 6 | &.type-biggest { 7 | font-size: 64px; 8 | } 9 | 10 | // COLORS 11 | &.type-standard { 12 | color: $light; 13 | } 14 | &.type-balanced { 15 | color: $balanced; 16 | } 17 | &.type-julia-green { 18 | color: #00aa16; 19 | } 20 | &.type-positive { 21 | color: $positive; 22 | } 23 | &.type-serious { 24 | color: $dark; 25 | } 26 | &.type-stable { 27 | color: darken($stable, 10%); 28 | } 29 | &.type-assertive { 30 | color: $assertive; 31 | } 32 | &.type-calm { 33 | color: $calm; 34 | } 35 | &.type-muted { 36 | color: gray; 37 | } 38 | &.type-dark { 39 | color: $dark; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /front-end/app/main/styles/_map.scss: -------------------------------------------------------------------------------- 1 | // You've got to force the map to take up space. 2 | #map { 3 | height: 100%; 4 | width: 100%; 5 | } 6 | 7 | .optionscontainer { 8 | height: auto; 9 | width: 100%; 10 | } 11 | 12 | .incidentSelector { 13 | margin-bottom: 10px; 14 | } 15 | 16 | .optionsContainer { 17 | padding: 10px; 18 | } 19 | 20 | .filterLabelContainer { 21 | display: block; 22 | } 23 | 24 | .filterLabelContainer h4 { 25 | margin-top: 0px; 26 | margin-bottom: 10px; 27 | } 28 | 29 | .filterCountContainer { 30 | display: block; 31 | } 32 | 33 | .filterSelectContainer { 34 | display: block; 35 | } 36 | -------------------------------------------------------------------------------- /front-end/app/main/styles/main.scss: -------------------------------------------------------------------------------- 1 | /* 2 | To customize the look and feel of Ionic, you can override the variables 3 | in ionic's _variables.scss file. 4 | 5 | For example, you might change some of the default colors: 6 | */ 7 | 8 | $light: #fff !default; 9 | $stable: #f8f8f8 !default; 10 | $positive: #4a87ee !default; 11 | $calm: #43cee6 !default; 12 | $balanced: #66cc33 !default; 13 | $energized: #f0b840 !default; 14 | $assertive: #ef4e3a !default; 15 | $royal: #8a6de9 !default; 16 | $dark: #444 !default; 17 | 18 | // The path for our ionicons font files, relative to the built & temporary main.css 19 | $ionicons-font-path: "../assets/fonts" !default; 20 | 21 | // Include all of Ionic 22 | @import "../../bower_components/ionic/scss/ionic"; 23 | 24 | /// Note that the convention uses _partialname and looks in relative path. 25 | // Import page styles. 26 | @import './map'; 27 | @import './customType'; 28 | 29 | .text-muted { 30 | color: darken(lightgray, 10%); 31 | } 32 | 33 | .test { 34 | border: 1px solid red; 35 | } 36 | -------------------------------------------------------------------------------- /front-end/app/main/templates/add.html: -------------------------------------------------------------------------------- 1 |
Hello World
-------------------------------------------------------------------------------- /front-end/app/main/templates/map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /front-end/app/main/templates/tabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /front-end/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "ionic": "~1.3.0", 4 | "angular": "~1.5.0", 5 | "angular-animate": "~1.5.0", 6 | "angular-sanitize": "~1.5.0", 7 | "angular-ui-router": "~0.2.15", 8 | "ngCordova": "~0.1.17-alpha", 9 | "angular-dynamic-locale": "~0.1.27", 10 | "angular-translate": "~2.11.0", 11 | "angular-translate-loader-static-files": "~2.11.0", 12 | "localforage": "~1.4.0", 13 | "firebase": "~2.4.2", 14 | "angularfire": "^1.2.0", 15 | "geofire": "^4.1.0", 16 | "angular-leaflet-directive": "^0.10.0", 17 | "leaflet.markercluster": "^0.5.0" 18 | }, 19 | "devDependencies": { 20 | "angular-mocks": "~1.5.0" 21 | }, 22 | "resolutions": { 23 | "angular": "~1.5.0", 24 | "angular-animate": "~1.5.0", 25 | "angular-sanitize": "~1.5.0", 26 | "angular-ui-router": "~0.2.15", 27 | "angular-translate": "~2.11.0", 28 | "firebase": "~2.4.2", 29 | "angularfire": "^1.2.0", 30 | "geofire": "^4.1.0" 31 | }, 32 | "name": "by-path", 33 | "private": true 34 | } 35 | -------------------------------------------------------------------------------- /front-end/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ByPath 4 | 5 | A sample Apache Cordova application that responds to the deviceready event. 6 | 7 | 8 | Apache Cordova Team 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 | -------------------------------------------------------------------------------- /front-end/gulp/building.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // gulp 3 | var gulp = require('gulp'); 4 | var options = gulp.options; 5 | var paths = gulp.paths; 6 | // plugins 7 | var $ = require('gulp-load-plugins')(); 8 | // modules 9 | var del = require('del'); 10 | var vinylPaths = require('vinyl-paths'); 11 | 12 | var buildDependencies = [ 13 | options['force-build'] ? 'linting' : 'linting-throw', 14 | 'build-app', 15 | 'build-templates', 16 | 'build-assets' 17 | ]; 18 | 19 | gulp.task('build', buildDependencies, function () { 20 | return gulp.src(paths.dist + '/**/*') 21 | .pipe($.size({showFiles: true})); 22 | }); 23 | 24 | gulp.task('clean', function () { 25 | // pattern is windows-friendly according to https://github.com/mwaylabs/generator-m-ionic/issues/223#issuecomment-196060284 26 | return gulp.src(['.tmp/**/*.*', paths.dist + '/**/*.*']) 27 | .pipe(vinylPaths(del)); 28 | }); 29 | 30 | // concatenate files in build:blocks inside index.html 31 | // and copy to build folder destinations 32 | gulp.task('build-app', ['clean', 'inject-all'], function () { 33 | var jsFilter = $.filter('**/*.js', {restore: true}); 34 | var cssFilter = $.filter('**/*.css', {restore: true}); 35 | 36 | var stream = gulp.src('app/index.html') // main html file 37 | .pipe($.useref({searchPath: '{.tmp,app}'})); // all assets (without index.html) 38 | 39 | if (options.minify) { 40 | stream 41 | .pipe(jsFilter) 42 | .pipe($.ngAnnotate({ 43 | add: true, 44 | sourcemap: true 45 | })) 46 | .pipe($.uglify()) 47 | .pipe(jsFilter.restore) 48 | .pipe(cssFilter) 49 | .pipe($.csso()) 50 | .pipe(cssFilter.restore); 51 | } 52 | 53 | stream.pipe(gulp.dest(paths.dist)); 54 | 55 | return stream; 56 | }); 57 | 58 | // copy templates 59 | gulp.task('build-templates', ['clean'], function () { 60 | return gulp.src(paths.templates) 61 | .pipe($.if(options.minify, $.htmlmin({ 62 | removeComments: true, 63 | removeCommentsFromCDATA: true, 64 | collapseWhitespace: true, 65 | conservativeCollapse: true, 66 | collapseInlineTagWhitespace: true 67 | }))) 68 | .pipe(gulp.dest(paths.dist)); 69 | }); 70 | 71 | // copy assets, wait for fonts 72 | gulp.task('build-assets', ['clean', 'bower-fonts'], function () { 73 | return gulp.src('app/*/assets/**/*') 74 | .pipe($.if(options.minify, $.imagemin())) 75 | .pipe(gulp.dest(paths.dist)); 76 | }); 77 | -------------------------------------------------------------------------------- /front-end/gulp/configuring.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | // gulp 4 | var gulp = require('gulp'); 5 | var options = gulp.options; 6 | // other 7 | var chalk = require('chalk'); 8 | var minimist = require('minimist'); 9 | var fs = require('fs'); 10 | var xml2js = require('xml2js'); 11 | 12 | gulp.task('config', function () { 13 | var parser = new xml2js.Parser(); 14 | var builder = new xml2js.Builder({ 15 | renderOpts: { 16 | pretty: true, 17 | indent: ' ' 18 | }, 19 | xmldec: { 20 | version: '1.0', 21 | encoding: 'utf-8' 22 | } 23 | }); 24 | // read & parse file 25 | var xmlFile = fs.readFileSync('config.xml'); 26 | parser.parseString(xmlFile, function (err, result) { 27 | // get values 28 | if (options.getWidgetAttr) { 29 | console.log(result.widget.$[options.getWidgetAttr]); 30 | } 31 | // update values 32 | if (options.setWidgetAttr) { 33 | var split = options.setWidgetAttr.split('='); 34 | var key = split[0]; 35 | var value = split[1]; 36 | result.widget.$[key] = value; 37 | } 38 | 39 | if (options.setName) { 40 | result.widget.name = options.setName; 41 | } 42 | if (options.setDescription) { 43 | result.widget.description = options.setDescription; 44 | } 45 | if (options.setAuthor) { 46 | var splits = options.setAuthor.split('---'); 47 | if (splits[0]) { 48 | result.widget.author[0]._ = splits[0]; 49 | } 50 | if (splits[1]) { 51 | result.widget.author[0].$.email = splits[1]; 52 | } 53 | if (splits[2]) { 54 | result.widget.author[0].$.href = splits[2]; 55 | } 56 | } 57 | // write file 58 | var xml = builder.buildObject(result); 59 | fs.writeFileSync('config.xml', xml); 60 | }); 61 | }); 62 | 63 | gulp.task('defaults', function () { 64 | var filePath = './gulp/.gulp_settings.json'; 65 | var exists = fs.existsSync(filePath); 66 | 67 | var fileContent = {}; 68 | if (exists) { 69 | fileContent = JSON.parse(fs.readFileSync(filePath, 'utf-8')); 70 | } 71 | var defaults = fileContent.defaults; 72 | 73 | // set 74 | if (options.set) { 75 | if (typeof options.set !== 'string') { 76 | console.log(chalk.red('Use like this: --set=\' --flag1 --flag2=value\'')); 77 | return; 78 | } 79 | 80 | var newDefaults = minimist(options.set.split(' ')); 81 | if (!defaults) { 82 | fileContent.defaults = defaults = {}; 83 | } 84 | 85 | var setTask = newDefaults._[0]; 86 | delete newDefaults._; 87 | defaults[setTask] = newDefaults; 88 | 89 | console.log(chalk.green('set defaults for task \'' + setTask + '\': '), newDefaults); 90 | } 91 | // clear 92 | else if (options.clear) { 93 | var clearTask = options.clear; 94 | if (typeof clearTask !== 'string') { 95 | console.log(chalk.red('Use like this: --clear ')); 96 | return; 97 | } 98 | if (!defaults || !defaults[clearTask]) { 99 | console.log(chalk.yellow('Nothing to clear')); 100 | return; 101 | } 102 | 103 | delete defaults[clearTask]; 104 | console.log(chalk.yellow('cleared defaults for task \'' + clearTask + '\'')); 105 | 106 | // last? -> delete defaults object 107 | if (!Object.keys(defaults).length) { 108 | delete fileContent.defaults; 109 | } 110 | } 111 | // show 112 | else { 113 | if (defaults) { 114 | console.log(chalk.green('defaults:')); 115 | for (var key in defaults) { 116 | console.log(chalk.green(key + ': '), defaults[key]); 117 | } 118 | } 119 | else { 120 | console.log(chalk.yellow('no defaults yet')); 121 | } 122 | } 123 | 124 | // write changes to file 125 | if (options.clear || options.set) { 126 | fs.writeFileSync(filePath, JSON.stringify(fileContent, undefined, 2)); 127 | } 128 | }); 129 | -------------------------------------------------------------------------------- /front-end/gulp/cordova.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // gulp 3 | var gulp = require('gulp'); 4 | var options = gulp.options; 5 | // plugins 6 | var $ = require('gulp-load-plugins')(); 7 | // packages 8 | var path = require('path'); 9 | var del = require('del'); 10 | var vinylPaths = require('vinyl-paths'); 11 | 12 | var runCordova = function (command, stream) { 13 | // allow to overwrite command from option.cordova with parameter 14 | command = typeof command === 'string' ? command : options.cordova; 15 | // create new stream if not provided 16 | stream = stream || gulp.src(''); 17 | return stream 18 | .pipe($.shell([ 19 | // needs explicit cross-platform path 20 | path.join('node_modules/cordova/bin/cordova ') + command 21 | ])); 22 | }; 23 | 24 | gulp.task('cordova', runCordova); 25 | gulp.task('cordova-only-resources', ['resources'], runCordova); 26 | gulp.task('cordova-with-build', ['build', 'resources'], runCordova); 27 | 28 | // Handle resources 29 | gulp.task('clean-res', function () { 30 | return gulp.src('res/*/current/*') 31 | .pipe(vinylPaths(del)); 32 | }); 33 | 34 | gulp.task('resources', ['clean-res'], function () { 35 | var setFolder = options.res || 'default'; 36 | 37 | var resourceFiles = 'res/*/' + setFolder + '/**/*'; 38 | return gulp.src(resourceFiles) 39 | .pipe($.rename(function (path) { 40 | path.dirname = path.dirname.replace('/' + setFolder, '/current'); 41 | })) 42 | .pipe(gulp.dest('res')); 43 | }); 44 | -------------------------------------------------------------------------------- /front-end/gulp/linting.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // gulp 3 | var gulp = require('gulp'); 4 | var paths = gulp.paths; 5 | // plugins 6 | var $ = require('gulp-load-plugins')(); 7 | 8 | // all linting tasks 9 | gulp.task('linting', ['eslint', 'jsonlint']); 10 | gulp.task('linting-throw', ['eslint-throw', 'jsonlint-throw']); 11 | 12 | // check app and test for eslint errors 13 | var eslint = function (fail) { 14 | return function () { 15 | return gulp.src(paths.jsFiles.concat(paths.karma).concat(paths.protractor)) 16 | .pipe($.eslint()) 17 | .pipe($.eslint.format()) 18 | .pipe($.if(fail, $.eslint.failOnError())); 19 | }; 20 | }; 21 | gulp.task('eslint', eslint()); 22 | gulp.task('eslint-throw', eslint(true)); 23 | 24 | // check app for jsonlint errors 25 | var jsonlint = function (fail) { 26 | var failReporter = function (file) { 27 | throw new Error(file.path + '\n' + file.jsonlint.message); 28 | }; 29 | return function () { 30 | return gulp.src(paths.jsonFiles) 31 | .pipe($.jsonlint()) 32 | .pipe($.jsonlint.reporter(fail ? failReporter : undefined)); 33 | }; 34 | }; 35 | gulp.task('jsonlint', jsonlint()); 36 | gulp.task('jsonlint-throw', jsonlint(true)); 37 | 38 | // eslint task for contributors 39 | gulp.task('contrib-linting', ['linting'], function () { 40 | return gulp.src(paths.contrib) 41 | .pipe($.eslint()) 42 | .pipe($.eslint.format()); 43 | }); 44 | -------------------------------------------------------------------------------- /front-end/gulp/testing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // gulp 3 | var gulp = require('gulp'); 4 | var path = require('path'); 5 | var paths = gulp.paths; 6 | var options = gulp.options; 7 | // plugins 8 | var $ = require('gulp-load-plugins')(); 9 | // modules 10 | var Server = require('karma').Server; 11 | var bs = require('browser-sync'); 12 | 13 | // KARMA 14 | function runKarma (singleRun, done) { 15 | new Server({ 16 | configFile: path.join(__dirname, '/../karma.conf.js'), 17 | singleRun: singleRun, 18 | autoWatch: !singleRun 19 | }, done).start(); 20 | } 21 | 22 | gulp.task('karma', ['linting'], function (done) { 23 | runKarma(true, done); 24 | }); 25 | 26 | gulp.task('karma:auto', ['linting'], function (done) { 27 | runKarma(false, done); 28 | }); 29 | 30 | // PROTRACTOR 31 | // Downloads the selenium webdriver 32 | gulp.task('webdriver-update', $.protractor.webdriver_update); 33 | gulp.task('webdriver-standalone', $.protractor.webdriver_standalone); 34 | 35 | function runProtractor (done) { 36 | gulp.src(paths.protractor) 37 | .pipe($.protractor.protractor({ 38 | configFile: 'protractor.conf.js' 39 | })) 40 | .on('error', function (err) { 41 | // Make sure failed tests cause gulp to exit non-zero 42 | throw err; 43 | }) 44 | .on('end', function () { 45 | bs.exit(); 46 | done(); 47 | 48 | // uh why isn't the process stopping yet? 49 | setTimeout(function () { 50 | console.log('Looks like everything passed. Manually stopping the process'); 51 | process.exit(0); 52 | }, 5000); 53 | }); 54 | } 55 | 56 | gulp.task('protractor', ['serve', 'linting', 'webdriver-update'], function (done) { 57 | runProtractor(done); 58 | }); 59 | 60 | var protractorBuildDeps = ['serve-build', 'webdriver-update']; 61 | if (options.build !== false) { 62 | protractorBuildDeps.push('build'); 63 | } 64 | gulp.task('protractor-build', protractorBuildDeps, function (done) { 65 | gulp.start('linting'); 66 | 67 | runProtractor(done); 68 | }); 69 | -------------------------------------------------------------------------------- /front-end/gulp/watching.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // gulp 3 | var gulp = require('gulp'); 4 | var paths = gulp.paths; 5 | var options = gulp.options; 6 | // modules 7 | var bs = require('browser-sync').create(); 8 | var chalk = require('chalk'); 9 | 10 | var bsInit = function (paths, openOverride) { 11 | var bsOptions = { 12 | server: { 13 | baseDir: paths 14 | } 15 | }; 16 | if (options.proxyMapTo && options.proxyPath) { 17 | var url = require('url'); 18 | var proxy = require('proxy-middleware'); 19 | var proxyOptions = url.parse(options.proxyMapTo); 20 | proxyOptions.route = options.proxyPath; 21 | 22 | bsOptions.server.middleware = [proxy(proxyOptions)]; 23 | 24 | console.log('[' + chalk.green('proxy') + '] ' + chalk.bold('Proxy Configuration:')); 25 | console.log(chalk.dim(' ---------------------------------------')); 26 | console.log(' Path: ' + chalk.green(options.proxyPath)); 27 | console.log(' Map to: ' + chalk.green(options.proxyMapTo)); 28 | console.log(chalk.dim(' ---------------------------------------')); 29 | } 30 | if (options.open === false) { 31 | bsOptions.open = false; 32 | } 33 | if (openOverride !== undefined) { 34 | bsOptions.open = openOverride; 35 | } 36 | bs.init(bsOptions); 37 | }; 38 | 39 | // WATCH 40 | gulp.task('watch', ['inject-all'], function () { 41 | 42 | // browser sync server 43 | bsInit(['app', '.tmp']); 44 | 45 | var watchFiles = paths.jsFiles 46 | .concat([ 47 | 'app/index.html', 48 | '.tmp/*/styles/*.css', // each module's css 49 | 'app/*/assets/**/*' 50 | ]) 51 | .concat(paths.templates); 52 | 53 | // start linting and watching 54 | gulp.start('linting'); 55 | gulp.watch(watchFiles, function (event) { 56 | console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); 57 | if (event.type === 'changed') { 58 | bs.reload(); 59 | gulp.start('linting'); 60 | } 61 | else { // added or deleted 62 | // inject in index (implicitly reloads) 63 | gulp.start('inject-all'); 64 | } 65 | }); 66 | // watch for changes in scss 67 | gulp.watch('app/*/styles/**/*.scss', ['styles']); 68 | // watch for changes in environment files and new config files 69 | gulp.watch([ 70 | 'app/main/constants/env-*.json', 71 | 'app/*/constants/*config-const.js' 72 | ], ['environment']); 73 | }); 74 | 75 | // WATCH-BUILD 76 | var watchBuildDeps = []; 77 | if (options.build !== false) { 78 | watchBuildDeps.push('build'); 79 | } 80 | gulp.task('watch-build', watchBuildDeps, function () { 81 | bsInit(paths.dist); 82 | gulp.watch(paths.dist + '/**/*', function () { 83 | bs.reload(); 84 | }); 85 | }); 86 | 87 | // SERVE TASKS FOR PROTRACTOR 88 | gulp.task('serve', ['inject-all'], function () { 89 | bsInit(['app', '.tmp'], false); 90 | }); 91 | gulp.task('serve-build', ['build'], function () { 92 | bsInit(['app', '.tmp'], false); 93 | }); 94 | -------------------------------------------------------------------------------- /front-end/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var gulp = require('gulp'); 3 | var minimist = require('minimist'); 4 | var requireDir = require('require-dir'); 5 | var chalk = require('chalk'); 6 | var fs = require('fs'); 7 | 8 | // config 9 | gulp.paths = { 10 | bowerComponents: 'app/bower_components', 11 | dist: 'www', 12 | jsFiles: ['app/**/*.js', '!app/bower_components/**/*.js'], 13 | jsonFiles: ['app/**/*.json', '!app/bower_components/**/*.json'], 14 | templates: ['app/*/templates/**/*'], 15 | contrib: ['gulpfile.js', 'gulp/**/*.js', 'hooks/**/*.js'], 16 | karma: ['test/karma/**/*.js'], 17 | protractor: ['test/protractor/**/*.js'] 18 | }; 19 | 20 | // OPTIONS 21 | var options = gulp.options = minimist(process.argv.slice(2)); 22 | 23 | // load .gulp_settings.json 24 | var task = options._[0]; // only for first task 25 | var gulpSettings; 26 | if (fs.existsSync('./gulp/.gulp_settings.json')) { 27 | gulpSettings = require('./gulp/.gulp_settings.json'); 28 | var defaults = gulpSettings.defaults; 29 | if (defaults) { 30 | // defaults present for said task? 31 | if (task && task.length && defaults[task]) { 32 | var taskDefaults = defaults[task]; 33 | // copy defaults to options object 34 | for (var key in taskDefaults) { 35 | // only if they haven't been explicitly set 36 | if (options[key] === undefined) { 37 | options[key] = taskDefaults[key]; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | // environment 45 | options.env = options.env || 'dev'; 46 | // print options 47 | if (defaults && defaults[task]) { 48 | console.log(chalk.green('defaults for task \'' + task + '\': '), defaults[task]); 49 | } 50 | // cordova command one of cordova's build commands? 51 | if (options.cordova) { 52 | var cmds = ['build', 'run', 'emulate', 'prepare', 'serve']; 53 | for (var i = 0, cmd; ((cmd = cmds[i])); i++) { 54 | if (options.cordova.indexOf(cmd) >= 0) { 55 | options.cordovaBuild = true; 56 | break; 57 | } 58 | } 59 | } 60 | 61 | // load tasks 62 | requireDir('./gulp'); 63 | 64 | // default task 65 | gulp.task('default', function () { 66 | // cordova build command & gulp build 67 | if (options.cordovaBuild && options.build !== false) { 68 | return gulp.start('cordova-with-build'); 69 | } 70 | // cordova build command & no gulp build 71 | else if (options.cordovaBuild && options.build === false) { 72 | return gulp.start('cordova-only-resources'); 73 | } 74 | // cordova non-build command 75 | else if (options.cordova) { 76 | return gulp.start('cordova'); 77 | } 78 | // just watch when cordova option not present 79 | else { 80 | return gulp.start('watch'); 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /front-end/hooks/README.md: -------------------------------------------------------------------------------- 1 | 21 | # Cordova Hooks 22 | 23 | Cordova Hooks represent special scripts which could be added by application and plugin developers or even by your own build system to customize cordova commands. See Hooks Guide for more details: http://cordova.apache.org/docs/en/edge/guide_appdev_hooks_index.md.html#Hooks%20Guide. 24 | -------------------------------------------------------------------------------- /front-end/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed Jul 01 2015 13:51:22 GMT+0200 (CEST) 3 | 4 | 'use strict'; 5 | 6 | module.exports = function (config) { 7 | // retrieve main files from bower 8 | var wiredep = require('wiredep'); 9 | var bowerFiles = wiredep({devDependencies: true}).js; 10 | 11 | config.set({ 12 | 13 | // base path that will be used to resolve all patterns (eg. files, exclude) 14 | basePath: '', 15 | 16 | // frameworks to use 17 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 18 | frameworks: ['jasmine', 'angular-filesort'], 19 | 20 | // sort app/**/*.js files 21 | angularFilesort: { 22 | whitelist: [ 23 | 'app/!(bower_components)/**/*.js' 24 | ] 25 | }, 26 | 27 | // list of files / patterns to load in the browser 28 | files: bowerFiles.concat([ 29 | // other 30 | 'app/!(bower_components)/**/*.js', 31 | // test 32 | 'test/karma/**/*.js', 33 | // templates 34 | 'app/**/templates/*.html' 35 | ]), 36 | 37 | // list of files to exclude 38 | exclude: [], 39 | 40 | // preprocess matching files before serving them to the browser 41 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 42 | preprocessors: { 43 | 'app/**/templates/*.html': ['ng-html2js'] 44 | }, 45 | 46 | // use template cache to avoid unexpected $http requests from ui-router 47 | // https://github.com/angular-ui/ui-router/issues/212#issuecomment-69974072 48 | ngHtml2JsPreprocessor: { 49 | moduleName: 'ngHtml2Js', 50 | stripPrefix: 'app/' // the path must be relative to the app.js 51 | }, 52 | 53 | // test results reporter to use 54 | // possible values: 'dots', 'progress' 55 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 56 | reporters: ['progress'], 57 | 58 | // web server port 59 | port: 9876, 60 | 61 | // enable / disable colors in the output (reporters and logs) 62 | colors: true, 63 | 64 | // level of logging 65 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 66 | logLevel: config.LOG_INFO, 67 | 68 | // enable / disable watching file and executing tests whenever any file changes 69 | autoWatch: true, 70 | 71 | // start these browsers 72 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 73 | browsers: ['PhantomJS'], 74 | 75 | // Continuous Integration mode 76 | // if true, Karma captures browsers, runs the tests and exits 77 | singleRun: false 78 | }); 79 | }; 80 | -------------------------------------------------------------------------------- /front-end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "engines": { 4 | "node": ">=4.0.0" 5 | }, 6 | "scripts": { 7 | "postinstall": "bower install", 8 | "test": "gulp protractor --env=dev", 9 | "build-dev": "gulp default --env=dev" 10 | }, 11 | "devDependencies": { 12 | "bower": "^1.7.9", 13 | "browser-sync": "^2.8.3", 14 | "chalk": "^1.1.0", 15 | "cordova": "^6.0.0", 16 | "del": "^2.0.0", 17 | "elementtree": "^0.1.6", 18 | "eslint": "^2.1.0", 19 | "gulp": "^3.9.0", 20 | "gulp-angular-filesort": "^1.1.1", 21 | "gulp-autoprefixer": "^3.0.1", 22 | "gulp-changed": "^1.3.0", 23 | "gulp-csso": "^2.0.0", 24 | "gulp-eslint": "^2.0.0", 25 | "gulp-filter": "^4.0.0", 26 | "gulp-htmlmin": "^1.3.0", 27 | "gulp-if": "^2.0.0", 28 | "gulp-imagemin": "^2.3.0", 29 | "gulp-inject": "^4.0.0", 30 | "gulp-jsonlint": "^1.1.0", 31 | "gulp-load-plugins": "^1.0.0-rc", 32 | "gulp-natural-sort": "^0.1.0", 33 | "gulp-ng-annotate": "^2.0.0", 34 | "gulp-plumber": "^1.0.1", 35 | "gulp-protractor": "^2.1.0", 36 | "gulp-rename": "^1.2.2", 37 | "gulp-sass": "^2.0.2", 38 | "gulp-shell": "^0.5.1", 39 | "gulp-size": "^2.0.0", 40 | "gulp-sourcemaps": "^1.5.2", 41 | "gulp-uglify": "^1.3.0", 42 | "gulp-useref": "^3.0.4", 43 | "karma": "^0.13.9", 44 | "karma-angular-filesort": "^1.0.0", 45 | "karma-jasmine": "^0.3.6", 46 | "karma-ng-html2js-preprocessor": "^0.2.0", 47 | "karma-phantomjs-launcher": "^1.0.0", 48 | "lodash": "^4.3.0", 49 | "main-bower-files": "^2.9.0", 50 | "minimist": "^1.2.0", 51 | "phantomjs-prebuilt": "^2.1.4", 52 | "plist": "^1.1.0", 53 | "proxy-middleware": "^0.15.0", 54 | "require-dir": "^0.3.0", 55 | "vinyl-paths": "^2.0.0", 56 | "wiredep": "^4.0.0", 57 | "xml2js": "^0.4.9", 58 | "yeoman-test": "^1.1.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /front-end/protractor.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // An example configuration file. 4 | exports.config = { 5 | // The address of a running selenium server. 6 | //seleniumAddress: 'http://localhost:4444/wd/hub', 7 | //seleniumServerJar: deprecated, this should be set on node_modules/protractor/config.json 8 | 9 | // Capabilities to be passed to the webdriver instance. 10 | capabilities: { 11 | 'browserName': 'firefox' 12 | }, 13 | 14 | baseUrl: 'http://localhost:3000', 15 | 16 | // Spec patterns are relative to the current working directly when 17 | // protractor is called. 18 | specs: ['test/protractor/**/*.js'], 19 | }; 20 | -------------------------------------------------------------------------------- /front-end/res/android/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/res/android/default/icon.png -------------------------------------------------------------------------------- /front-end/res/android/set1/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/res/android/set1/icon.png -------------------------------------------------------------------------------- /front-end/res/ios/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/res/ios/default/icon.png -------------------------------------------------------------------------------- /front-end/res/ios/set1/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/front-end/res/ios/set1/icon.png -------------------------------------------------------------------------------- /front-end/test/karma/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../app/.eslintrc", 3 | "env": { 4 | "jasmine": true 5 | }, 6 | "globals": { 7 | "inject": true, 8 | "module": true 9 | }, 10 | "rules": { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /front-end/test/protractor/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../app/.eslintrc", 3 | "env": { 4 | "jasmine": true, 5 | "protractor": true 6 | }, 7 | "globals": { 8 | }, 9 | "rules": { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /front-end/test/protractor/main.js: -------------------------------------------------------------------------------- 1 | describe('bypath main map', function() { 2 | it('should load up the main screen', function() { 3 | browser.get('http://localhost:3000'); 4 | var navigateIcon = element.all(by.className('ion-ios-navigate')); 5 | expect(navigateIcon.count()).toEqual(1); 6 | }); 7 | 8 | it('there are a bunch of leaflet markers, too', function () { 9 | browser.wait(function () { 10 | return element(by.className('leaflet-marker-icon')).isPresent(); 11 | }, 10000); 12 | var leafletIcons = element.all(by.className('leaflet-marker-icon')); 13 | expect(leafletIcons.count()).toBeGreaterThan(3); 14 | }) 15 | }); 16 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | .ntvs* 3 | .tmp 4 | node_modules 5 | -------------------------------------------------------------------------------- /tools/tools.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "tools", "tools\tools.njsproj", "{25DDD189-62AE-46AB-894B-C45840684B33}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {25DDD189-62AE-46AB-894B-C45840684B33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {25DDD189-62AE-46AB-894B-C45840684B33}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {25DDD189-62AE-46AB-894B-C45840684B33}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {25DDD189-62AE-46AB-894B-C45840684B33}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /tools/tools/README.md: -------------------------------------------------------------------------------- 1 | # tools 2 | Overview 3 | The tools platform has been created in nwjs (Node Webkits) to create a desktop application for making tools. 4 | 5 | Instalation 6 | To get this working you will need to download nwjs @ http://nwjs.io/. I am using the SDK but I belive that the normal version will work. 7 | Once download you need to install/extract it to any location on your computer 8 | To run the program you need to put the following into the command line 9 | "/path/to/nwjs/install/nw /path/to/the/tools/folder/." 10 | If you cd to ./snowranger/tools/tools/ you will only need a '.' instead of the entire path. 11 | You can see a run.cmd(widnows) and run.sh(linux) for reference on how this is done. 12 | 13 | Current tools 14 | Database Refresh - This will force a refresh to the our firedatabase. 15 | 16 | ---- 17 | 18 | 19 | snowranger/tools/tools (master>) $ /Users/ia/Applications/nwjs.app/Contents/MacOS/nwjs . -------------------------------------------------------------------------------- /tools/tools/css/style.css: -------------------------------------------------------------------------------- 1 | .tool-panel{ 2 | height: 300px; 3 | } 4 | 5 | .title{ 6 | font-size: 24px; 7 | line-height: normal; 8 | } 9 | 10 | #wrapper { 11 | padding-left: 0; 12 | -webkit-transition: all 0.5s ease; 13 | -moz-transition: all 0.5s ease; 14 | -o-transition: all 0.5s ease; 15 | transition: all 0.5s ease; 16 | } 17 | 18 | #wrapper.toggled { 19 | padding-left: 250px; 20 | } 21 | 22 | #sidebar-wrapper { 23 | z-index: 1000; 24 | position: fixed; 25 | left: 250px; 26 | width: 0; 27 | height: 100%; 28 | margin-left: -250px; 29 | overflow-y: auto; 30 | background: #000; 31 | -webkit-transition: all 0.5s ease; 32 | -moz-transition: all 0.5s ease; 33 | -o-transition: all 0.5s ease; 34 | transition: all 0.5s ease; 35 | } 36 | 37 | #wrapper.toggled #sidebar-wrapper { 38 | width: 250px; 39 | } 40 | 41 | #page-content-wrapper { 42 | width: 100%; 43 | position: absolute; 44 | padding: 15px; 45 | } 46 | 47 | #wrapper.toggled #page-content-wrapper { 48 | position: absolute; 49 | margin-right: -250px; 50 | } 51 | 52 | /* Sidebar Styles */ 53 | 54 | .sidebar-nav { 55 | position: absolute; 56 | top: 0; 57 | width: 250px; 58 | margin: 0; 59 | padding: 0; 60 | list-style: none; 61 | } 62 | 63 | .sidebar-nav li { 64 | text-indent: 20px; 65 | line-height: 40px; 66 | } 67 | 68 | .sidebar-nav li a { 69 | display: block; 70 | text-decoration: none; 71 | color: #999999; 72 | } 73 | 74 | .sidebar-nav li a:hover { 75 | text-decoration: none; 76 | color: #fff; 77 | background: rgba(255,255,255,0.2); 78 | } 79 | 80 | .sidebar-nav li a:active, 81 | .sidebar-nav li a:focus { 82 | text-decoration: none; 83 | } 84 | 85 | .sidebar-nav > .sidebar-brand { 86 | height: 65px; 87 | font-size: 18px; 88 | line-height: 60px; 89 | } 90 | 91 | .sidebar-nav > .sidebar-brand a { 92 | color: #999999; 93 | } 94 | 95 | .sidebar-nav > .sidebar-brand a:hover { 96 | color: #fff; 97 | background: none; 98 | } 99 | 100 | @media(min-width:768px) { 101 | #wrapper { 102 | padding-left: 250px; 103 | } 104 | 105 | #wrapper.toggled { 106 | padding-left: 0; 107 | } 108 | 109 | #sidebar-wrapper { 110 | width: 250px; 111 | } 112 | 113 | #wrapper.toggled #sidebar-wrapper { 114 | width: 0; 115 | } 116 | 117 | #page-content-wrapper { 118 | padding: 20px; 119 | position: relative; 120 | } 121 | 122 | #wrapper.toggled #page-content-wrapper { 123 | position: relative; 124 | margin-right: 0; 125 | } 126 | } -------------------------------------------------------------------------------- /tools/tools/images/tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/bypath/98939dca34e58ab91244a7eb3ec4b4c59c3561fc/tools/tools/images/tool.png -------------------------------------------------------------------------------- /tools/tools/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 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 | -------------------------------------------------------------------------------- /tools/tools/js/api/data/SQLData.js: -------------------------------------------------------------------------------- 1 | /* boston311.js 2 | * This will go out and grab relivent data from the boston's 311 data and send it to the firebase 3 | */ 4 | 5 | // Includes 6 | var modules = require('./../util/modules.js'); 7 | var cronJob = require('cron').CronJob; 8 | var request = require('request'); 9 | 10 | 11 | 12 | module.exports = SqlScheduleQuery; 13 | 14 | function SqlScheduleQuery(){ 15 | SqlScheduleQuery.prototype.init = function(url, key, query, updatePath){ 16 | this.url = url; 17 | this.key = key; 18 | this.query = query; 19 | this.updatePath = updatePath; 20 | } 21 | 22 | SqlScheduleQuery.prototype.run = function(cronSchedule, callback){ 23 | this.cJob = new cronJob(cronSchedule, function(){ 24 | console.log('cron job start'); 25 | retieveData(this.updatePath, this.url, this.query, this.key, callback); 26 | console.log('cron job end'); 27 | }.bind(this), null, true, 'UTC'); 28 | } 29 | 30 | SqlScheduleQuery.prototype.forceUpdate = function (callback){ 31 | retieveData(this.updatePath, this.url, this.query, this.key, callback); 32 | } 33 | 34 | SqlScheduleQuery.prototype.test = function(){ 35 | console.log(this.url + " " + this.query + " " + this.key); 36 | } 37 | 38 | function retieveData(updatePath, url, query, key, callback) { 39 | var db = modules.getModule('firebase'); 40 | console.log(this.updatePath); 41 | db.getItem(updatePath, function (data) { 42 | var date = data; 43 | if (data === null) { 44 | // Create a formated date that we can use if one does not exist 45 | var date = new Date(new Date().setDate(new Date().getDate() - 30)).toISOString(); 46 | date = date.replace('Z', ''); 47 | } 48 | 49 | sqlQuery(date, url, query, key, function (res) { 50 | // Add the result of the query to the db 51 | callback(res); 52 | 53 | // Might want to do some checks to make sure there were no errors when 54 | // sending the data to the db before setting the last upated time 55 | console.log('Query completed'); 56 | db.setItem(updatePath, new Date().toISOString().replace('Z', '')); 57 | console.log('Update ' + updatePath + ' date set'); 58 | }); 59 | }); 60 | } 61 | 62 | // TODO: maybe the getMeASoda module injection belongs somewhere closer to here? 63 | function sqlQuery(date, url, query, key, callback){ 64 | // Need to add based on case types 65 | query = query.replace('$date', date); 66 | console.log(query); 67 | var stmnt = url + query; 68 | 69 | var options = { 70 | method: 'GET', 71 | url: stmnt, 72 | headers: { 73 | 'X-App-Token': key 74 | } 75 | }; 76 | 77 | var req = request(options, function (error, response, body) { 78 | console.log('response: ' + response.statusCode); 79 | if (error) { 80 | console.log(error); 81 | } 82 | else { 83 | callback(body); 84 | } 85 | }); 86 | } 87 | } -------------------------------------------------------------------------------- /tools/tools/js/api/data/data_manager.js: -------------------------------------------------------------------------------- 1 | /* data_manager.js 2 | * this module will get and manage the data that is stored in the database 3 | */ 4 | 5 | var builder = require('./endpointBuilder.js'); 6 | var modules = require('./../util/modules.js'); 7 | 8 | // These are the data sources 9 | var endpoints = []; 10 | 11 | module.exports = { 12 | init: function (){ 13 | //endpoints.push(b311); 14 | //endpoints.push(sf311); 15 | //endpoints.push(nyc311); 16 | 17 | var resourceMgr = modules.getModule('resource_manager'); 18 | eps = resourceMgr.getResource('endpoints'); 19 | 20 | console.log(endpoints); 21 | 22 | endpoints = builder.createEndpoints(eps); 23 | 24 | for (i in endpoints) { 25 | endpoints[i].init(); 26 | endpoints[i].print(); 27 | } 28 | }, 29 | 30 | start: function (){ 31 | return; 32 | console.log('data manager module started'); 33 | 34 | // Begin scheduler at 5 mins because most 311's 35 | // post their data on the hour so I want to wait until 36 | // after that before cheking for new stuff 37 | var nextSchedule = 5; 38 | 39 | for (i in endpoints) { 40 | 41 | // Normal Scheduling. 42 | // Have every scheduled even run 5 mins after 43 | var timer = "* " + ("0" + nextSchedule).slice(-2) + " * * * *"; 44 | nextSchedule += 5; 45 | 46 | // Faster timer for testing 47 | //var timer = ("0" + nextSchedule).slice(-2) + " * * * * *"; 48 | //nextSchedule += 30; 49 | //console.log(timer); 50 | 51 | endpoints[i].start(timer); 52 | } 53 | }, 54 | 55 | forceUpdate: function (){ 56 | for (i in endpoints) { 57 | endpoints[i].forceUpdate(); 58 | } 59 | }, 60 | 61 | getEndpoints: function (){ 62 | return endpoints; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tools/tools/js/api/data/getMeASoda.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Should return a nice SoQL query string for to use in your SalesForce SQL queries. 3 | * Note: will not handle any other SODA specific input queryables like $limit, $order... 4 | * Also currently it assumes that the base url will supply the `$where=` prefix. 5 | * 6 | * @param {Object} options Dr. Pepper AND Sprite?? Ew. 7 | * @return {String} Put'r into the url. 8 | * ie var query = "SELECT * WHERE open_dt > '$date' AND CASE_STATUS = 'Open' AND (STARTS_WITH(case_title, 'Unsafe/Dangerous Conditions') OR STARTS_WITH(case_title, 'Ground Maintenance') OR STARTS_WITH(case_title, 'Request for Snow Plowing') OR STARTS_WITH(case_title, 'Park Maintenance'))"; 9 | */ 10 | // : 11 | // { 12 | // @param {String} match_attr ie 'type', 'case_title'... 13 | // @param {Array.} matching_array ie ['Rodents', 'Snow Plowing'] 14 | // @param [optional] {String} open_dt in whatever weird iso format thingey 15 | // @param [optional] {String} case_status ie 'Open' or 'Closed' or null 16 | // } 17 | exports.query = function(options) { 18 | // function query(options) { 19 | 20 | var matching_array = options['matchingArray']; 21 | var match_attr = options['matchAttr']; 22 | var open_dt = options['openDt']; 23 | var case_status = ['caseStatus']; 24 | 25 | // Safety belts. 26 | // 27 | // TODO: find out why this array is actually an object. 28 | if (typeof matching_array !== 'object') return console.log('getMeASoda.query params requires an array for matching, got ' + typeof matching_array + ', it was: ' + matching_array); 29 | if (typeof match_attr !== 'string') return console.log('getMeASoda.query params requires a string for what to match to') 30 | 31 | 32 | // Make a big string. 33 | // 34 | 35 | var s = 'SELECT * WHERE '; 36 | 37 | // Optionally add starting date and case status. 38 | if (open_dt) s += "open_dt > '" + open_dt + "' AND " 39 | if (case_status) s += "CASE_STATUS = '" + case_status + "' AND "; 40 | 41 | // Matching attrs is not optional. 42 | s += '('; 43 | 44 | // For all strings in matching_array matching match_attr. 45 | for (var i = 0; i < matching_array.length; i++) { 46 | s += 'STARTS_WITH(' + match_attr + ", '" + matching_array[i] + "')" 47 | if (i < matching_array.length - 1) s += ' OR '; 48 | } 49 | 50 | s += ')'; 51 | 52 | return s; 53 | }; 54 | 55 | 56 | // Test it. 57 | // 58 | // var util = require('util'); // .inspect 59 | // var justPlainCoke = require('./justPlainCoke.js'); 60 | // console.log(query(justPlainCoke)); 61 | // => SELECT * WHERE CASE_STATUS = 'Open' AND open_dt > '$date' AND (STARTS_WITH(case_title, 'Unsafe/Dangerous Conditions') OR STARTS_WITH(case_title, 'Ground Maintenance') OR STARTS_WITH(case_title, 'Request for Snow Plowing') OR STARTS_WITH(case_title, 'Park Maintenance')) 62 | 63 | -------------------------------------------------------------------------------- /tools/tools/js/api/data/justPlainCoke.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | caseStatus : 'Open', 3 | openDt : '\$date', 4 | matchAttr : 'case_title', 5 | matchingArray : ['Unsafe/Dangerous Conditions','Ground Maintenance','Request for Snow Plowing','Park Maintenance'] 6 | }; 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tools/tools/js/api/database/db_firebase.js: -------------------------------------------------------------------------------- 1 | /* db_firebase.js 2 | * this is the module for accessing the firebase db 3 | */ 4 | 5 | // Includes 6 | var firebase = require('firebase'); 7 | var modules = require('./../util/modules.js'); 8 | // Constants 9 | var MASTER = 'MASTER'; 10 | var VALUE = 'VALUE' 11 | var PATH = 'PATH'; 12 | var DATA = 'DATA'; 13 | 14 | // Private vars 15 | var firebase_url; 16 | var fbRef; 17 | 18 | // Public methods 19 | module.exports = { 20 | init: function (){ 21 | // Nothing initialize 22 | //firebase_url = 'https://alexdev.firebaseio.com/'//resourceMgr.getResource('firebase_url'); 23 | //fbRef = new firebase(firebase_url); 24 | }, 25 | 26 | start: function (){ 27 | var resourceMgr = modules.getModule('resource_manager'); 28 | firebase_url = resourceMgr.getResource('firebase_url'); 29 | fbRef = new firebase(firebase_url); 30 | 31 | console.log('firebase module started'); 32 | }, 33 | 34 | getItem: function (path, onComplete){ 35 | // Async call the callback once the request has completed 36 | fbRef.child(path).once('value', function (snapshot) { 37 | onComplete(snapshot.val()); 38 | }); 39 | }, 40 | 41 | addNewItem: function (data){ 42 | // generate the schema from the data passed in 43 | var result = generateSchema(data); 44 | 45 | // Get the master 46 | master = result[MASTER]; 47 | 48 | // We set the id of the relitive tables based on what is generated when master is added to the db 49 | var response = fbRef.child(master[PATH]).push(master[DATA]); 50 | 51 | var values = result[VALUE]; 52 | 53 | // Iter though each item in the schema and set them on the db relitive to their path 54 | for (i in values) { 55 | var item = values[i]; 56 | 57 | fbRef.child(item[PATH] + '/' + response.key()).set(item[DATA]); 58 | } 59 | }, 60 | 61 | // values will be in the format of 62 | // [{path: path, data: value }, ...] 63 | // Say we are updating the title it would be 64 | // itemId = "-KCSxk6n0DImMtOLx88K" 65 | // values = [{path:title, value:'tree in park'}] 66 | updateItem: function (itemId, values){ 67 | for(i in values){ 68 | var value = values[i]; 69 | 70 | setItem(value[PATH] + "/" + itemId, value[DATA]); 71 | } 72 | }, 73 | 74 | addItem: function (path, value){ 75 | fbRef.child(path).push(value); 76 | }, 77 | 78 | setItem: function (path, value){ 79 | fbRef.child(path).set(value); 80 | }, 81 | 82 | 83 | } 84 | 85 | // Private functions 86 | function generateSchema(data) { 87 | // Grab change the data's format to fit the db 88 | // in the form of : 89 | var values = []; 90 | 91 | values.push(createSchemaItem('/open', data['open'])); 92 | values.push(createSchemaItem('/type', data['type'])); 93 | values.push(createSchemaItem('/title', data['title'])); 94 | values.push(createSchemaItem('/location', data['loc'] || null)); 95 | values.push(createSchemaItem('/geo', data['geo'])); 96 | values.push(createSchemaItem('/source', data['source'])); 97 | // moar values 98 | /* 99 | values.push(createSchemaItem('/open_dt', data['open_dt'])); 100 | values.push(createSchemaItem('/closed_dt', data['closed_dt'])); 101 | values.push(createSchemaItem('/case_status', data['case_status'])); 102 | values.push(createSchemaItem('/ontime_status', data['ontime_status'])); 103 | values.push(createSchemaItem('/address', data['location'])); 104 | values.push(createSchemaItem('/short_address', data['location_street_name'])); 105 | values.push(createSchemaItem('/neighborhood', data['neighborhood'])); 106 | */ 107 | var output = { 108 | MASTER: createSchemaItem('/master', { 'id': data['id'] }), 109 | VALUE : values 110 | } 111 | 112 | return output; 113 | } 114 | 115 | // Simple helper function for normailzing the data being sent to the server 116 | function createSchemaItem(path, item) { 117 | return { PATH : path, DATA : item }; 118 | } 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /tools/tools/js/api/resources/endpoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "boston_311": { 3 | "url": "https://data.cityofboston.gov/resource/wc8w-nujj.json?$query=", 4 | "key": "soda_key", 5 | "query": "SELECT * WHERE open_dt > '$date' AND CASE_STATUS = 'Open' AND (STARTS_WITH(case_title, 'Unsafe/Dangerous Conditions') OR STARTS_WITH(case_title, 'Ground Maintenance') OR STARTS_WITH(case_title, 'Request for Snow Plowing') OR STARTS_WITH(case_title, 'Park Maintenance')) LIMIT 100", 6 | "map": { 7 | "id": "case_enquiry_id", 8 | "title":"case_title", 9 | "type": "type", 10 | "location": "location", 11 | "open": "open_dt", 12 | "latitude": "latitude", 13 | "longitude": "longitude" 14 | } 15 | }, 16 | 17 | "san_francisco_311": { 18 | "url": "https://data.sfgov.org/resource/vw6y-z8j6.json?$query=", 19 | "key": "soda_key", 20 | "query": "SELECT * WHERE opened > '$date' AND status = 'Open' AND STARTS_WITH(category, 'Sidewalk or Curb') LIMIT 100", 21 | "map": { 22 | "id": "case_id", 23 | "title":"request_details", 24 | "type": "category", 25 | "location": "address", 26 | "open": "opened", 27 | "latitude": "point.latitude", 28 | "longitude": "point.longitude" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tools/tools/js/api/resources/keys.txt: -------------------------------------------------------------------------------- 1 | soda_key=k7chiGNz0GPFKd4dS03IEfKuE -------------------------------------------------------------------------------- /tools/tools/js/api/resources/nyc_endpoint.json: -------------------------------------------------------------------------------- 1 | { 2 | "new_york_city_311": { 3 | "url": "https://data.cityofnewyork.us/resource/fhrw-4uyv.json?$query=", 4 | "key": "soda_key", 5 | "query": "SELECT * WHERE created_date > '$date' AND status = 'Open' AND (STARTS_WITH(complaint_type, 'Street Condition') OR STARTS_WITH(complaint_type, 'General Construction/Plumbing') OR STARTS_WITH(complaint_type, 'Street Light Condition') OR STARTS_WITH(complaint_type, 'Illegal Parking'))", 6 | "map": { 7 | "id": "unique_key", 8 | "title": "descriptor", 9 | "type": "complaint_type", 10 | "location": "incident_address", 11 | "open": "created_date", 12 | "latitude": "latitude", 13 | "longitude": "longitude" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tools/tools/js/api/resources/resources.txt: -------------------------------------------------------------------------------- 1 | firebase_url=https://snowranger.firebaseio.com/ 2 | endpoints=load(endpoints.json) 3 | server_url=http://codenamesnowranger.herokuapp.com/ 4 | addEndpoint_path=data/addEndpoint 5 | -------------------------------------------------------------------------------- /tools/tools/js/api/util/ext_resource_manager.js: -------------------------------------------------------------------------------- 1 | /* key_manager.js 2 | * this file will load all of the keys from an external file 3 | * so that we can move the sensitive data out of the repo 4 | */ 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | 9 | var KEY_FILE = 'keys.txt'; 10 | 11 | // list 12 | var keys = []; 13 | var keysLoaded = false; 14 | 15 | var RESOURCE_FILE = 'resources.txt'; 16 | 17 | // list 18 | var resources = []; 19 | var resourcesLoaded = false; 20 | 21 | // Currently this reads lines and splits on '=' 22 | // I would like to change it to reading a json file 23 | // Also add encryption so keys are not stored in plain text 24 | function loadKeysFromFile(file){ 25 | if (!keysLoaded) { 26 | var data = getLinesFromFile(file); 27 | keys = load(data); 28 | } 29 | } 30 | 31 | function loadResourcesFromFile(file){ 32 | if (!resourcesLoaded) { 33 | var data = getLinesFromFile(file); 34 | resources = load(data); 35 | } 36 | } 37 | 38 | function getLinesFromFile(file){ 39 | var fp = path.join(__dirname, '../resources/' + file); 40 | var f = fs.readFileSync(fp).toString(); 41 | var values = f.split('\n'); 42 | 43 | // We need to go though and remove all carage returns and new lines chars 44 | for (i in values) { 45 | values[i] = values[i].replace('\r', ''); 46 | } 47 | 48 | return values; 49 | } 50 | 51 | function load(data){ 52 | var output = []; 53 | 54 | for (i in data) { 55 | var line = data[i]; 56 | var idx = line.indexOf('='); 57 | 58 | if (idx > -1) { 59 | // Split the string into id and key 60 | var id = line.substring(0, idx); 61 | var value = line.substring(idx + 1).trim(); 62 | 63 | if(value.lastIndexOf('load', 0) === 0){ 64 | // we need to get the file and load it into the key 65 | var st = value.indexOf('('); 66 | var end = value.indexOf(')'); 67 | 68 | var filePath = value.substring(st + 1, end); 69 | 70 | var file = fs.readFileSync(path.join(__dirname, '../resources/' + filePath)).toString(); 71 | 72 | var delim = filePath.indexOf('.'); 73 | 74 | var ext = filePath.substring(delim+1).toUpperCase(); 75 | console.log(filePath); 76 | console.log(ext); 77 | 78 | if (ext === 'JSON'){ 79 | console.log('We have a json file') 80 | value = JSON.parse(file); 81 | 82 | for (i in value){ 83 | console.log("key: " + i); 84 | console.log("value: " + value[i]); 85 | } 86 | } 87 | else{ 88 | value = file; 89 | } 90 | 91 | } 92 | 93 | // Set the id, key pair 94 | output[id] = value; 95 | } 96 | } 97 | 98 | return output; 99 | } 100 | 101 | function loadKeys () { 102 | keys = []; 103 | keysLoaded = false; 104 | 105 | loadKeysFromFile(KEY_FILE); 106 | } 107 | 108 | function loadResources () { 109 | resources = []; 110 | resourcesLoaded = false; 111 | 112 | loadResourcesFromFile(RESOURCE_FILE); 113 | } 114 | 115 | module.exports = { 116 | init: function (){ 117 | // The only thing to do is load the keys 118 | loadKeys(); 119 | loadResources(); 120 | }, 121 | 122 | start: function (){ 123 | console.log('resource manager module started'); 124 | // Nothing to do on start 125 | }, 126 | 127 | getKey: function (id){ 128 | 129 | // Check if the key exists before trying to get it out 130 | if (id in keys) { 131 | return keys[id]; 132 | } 133 | }, 134 | 135 | getResource: function (id){ 136 | if (id in resources) { 137 | return resources[id]; 138 | } 139 | }, 140 | 141 | print: function (){ 142 | for (i in keys) { 143 | console.log(keys[i]); 144 | } 145 | for (i in resources) { 146 | console.log(resources[i]); 147 | } 148 | }, 149 | } -------------------------------------------------------------------------------- /tools/tools/js/api/util/modules.js: -------------------------------------------------------------------------------- 1 | /* modules.js 2 | * this manages the modules for the system 3 | */ 4 | var modules = []; 5 | 6 | module.exports = { 7 | 8 | addModule: function (id, object) { 9 | // Throw an error if the id has alread beed added 10 | if (id in modules) { 11 | console.log("id: " + id + " in ServerSytems.systems array already exists"); 12 | throw "err"; 13 | } 14 | modules[id] = object; 15 | }, 16 | 17 | // Need a better way to access the modules 18 | // I only want read access to modules not write 19 | getModules: function () { 20 | return modules; 21 | }, 22 | 23 | getModule: function (id) { 24 | if (id in modules) { 25 | return modules[id]; 26 | } 27 | return null; 28 | } 29 | } -------------------------------------------------------------------------------- /tools/tools/js/app.js: -------------------------------------------------------------------------------- 1 | toolsApp = angular.module('toolsApp', ['ngResource', 'ngRoute']) 2 | .config([ 3 | '$compileProvider', 4 | function ($compileProvider) { 5 | // This is for allowing angular to bind to href for moving between views 6 | // Without it chrome makes the links as unsafe and will not link to anything 7 | $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension):/); 8 | } 9 | ]); -------------------------------------------------------------------------------- /tools/tools/js/controller/addNewDataSource.js: -------------------------------------------------------------------------------- 1 | toolsApp.controller("addNewDataSourceCtrl", function ($scope) { 2 | var modules = require('./js/api/util/modules.js'); 3 | var request = require('request'); 4 | 5 | var name = $('#name'); 6 | var url = $('#url'); 7 | var key = $('#key'); 8 | var query = $('#query'); 9 | 10 | var id = $('#id'); 11 | var title = $('#title'); 12 | var type = $('#type'); 13 | var location = $('#location'); 14 | var open = $('#open'); 15 | var latitude = $('#latitude'); 16 | var longitude = $('#longitude'); 17 | 18 | var tst = {'help': 'me', 'do': 'something'} 19 | 20 | $('#add_new_source').click(function () { 21 | var result = {}; 22 | 23 | result = { 24 | 'name': name.val(), 25 | 'url': url.val(), 26 | 'key': key.val(), 27 | 'query': query.val(), 28 | 'id': id.val(), 29 | 'title': title.val(), 30 | 'type': type.val(), 31 | 'location': location.val(), 32 | 'open': open.val(), 33 | 'latitude': latitude.val(), 34 | 'longitude': longitude.val() 35 | }; 36 | 37 | var rm = modules.getModule('resource_manager'); 38 | var call_url = rm.getResource('server_url') + rm.getResource('addEndpoint_path'); 39 | 40 | $.post(call_url, result).done(function (data) { console.log("data loaded: " + data);}); 41 | }); 42 | }); -------------------------------------------------------------------------------- /tools/tools/js/controller/addParkingData.js: -------------------------------------------------------------------------------- 1 | toolsApp.controller("addParkingCtrl", function ($scope, $location,$http) { 2 | 3 | var address = $('#address'); 4 | var lat = $('#lat'); 5 | var lng = $('#lng'); 6 | var url = $('#url'); 7 | 8 | var table; 9 | 10 | var columnsDef = [{ 11 | "sTitle": "Address", 12 | "mData": "address", 13 | "aTargets": [0] 14 | }, { 15 | "sTitle": "Latitude", 16 | "mData": "latitude", 17 | "aTargets": [1] 18 | }, { 19 | "sTitle": "Longitude", 20 | "mData": "longitude", 21 | "aTargets": [2] 22 | } ]; 23 | 24 | $(document).ready(function(){ 25 | table = $('#table').DataTable({ 26 | "aoColumnDefs":columnsDef 27 | }); 28 | }); 29 | 30 | $('#add').click(function () { 31 | var result = {}; 32 | result = { 33 | 'address': address.val(), 34 | 'latitude': lat.val(), 35 | 'longitude': lng.val() 36 | } 37 | table.rows.add([result]).draw(); 38 | }); 39 | 40 | $('#submit').click(function(){ 41 | var items = GetTableContense(); 42 | var call_url = "http://localhost:8080/parking/add"; 43 | 44 | if(items.length > 0){ 45 | console.log("Sending items to " + call_url); 46 | console.log(items); 47 | 48 | var jsonData=angular.toJson(items); 49 | var objectToSerialize={'object':jsonData}; 50 | 51 | $http({ 52 | url: call_url, 53 | method: "POST", 54 | data: $.param(objectToSerialize), 55 | headers: { 56 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 57 | } 58 | }); 59 | 60 | //$.post(call_url, items).done(function (data) { console.log("data loaded: " + data);}); 61 | } 62 | else{ 63 | console.log("Not enough data. Only " + items.length + " items in the array"); 64 | } 65 | 66 | }); 67 | 68 | $('#connect').click(function () { 69 | var stmnt = url.val();//'https://data.cambridgema.gov/resource/t8h9-i4u2.json?$query=SELECT * LIMIT 10'; 70 | var key = 'k7chiGNz0GPFKd4dS03IEfKuE'; 71 | 72 | $http({ 73 | url: stmnt, 74 | method: "GET", 75 | headers: { 76 | 'X-App-Token': key 77 | } 78 | }).then(function(response) { 79 | var data = response['data']; 80 | 81 | var output = []; 82 | 83 | for(i in data){ 84 | var lat = data[i]['latitude']; 85 | var lng = data[i]['longitude']; 86 | var address = data[i]['street_number'] + " " + data[i]['location_address']; 87 | 88 | result = { 89 | 'address': address, 90 | 'latitude': lat, 91 | 'longitude': lng 92 | } 93 | output.push(result); 94 | } 95 | 96 | table.rows.add(output).draw(); 97 | }); 98 | }); 99 | 100 | function GetTableContense(){ 101 | var output = []; 102 | var length = table.rows().data().length; 103 | 104 | for(var i = 0; i < length; i++){ 105 | 106 | output.push(table.rows(i).data()[0]); 107 | } 108 | 109 | return output; 110 | } 111 | }); -------------------------------------------------------------------------------- /tools/tools/js/controller/databaseRefresh.js: -------------------------------------------------------------------------------- 1 | toolsApp.controller("dbRefreshCtrl", function ($scope, $location) { 2 | var modules = require('./js/api/util/modules.js'); 3 | 4 | var dm = modules.getModule('data_manager'); 5 | console.log(dm); 6 | console.log(dm.getEndpoints()); 7 | //dm.forceUpdate(); 8 | 9 | $('#refresh_button').click(function () { 10 | dm.forceUpdate(); 11 | }); 12 | }); -------------------------------------------------------------------------------- /tools/tools/js/controller/mainPage.js: -------------------------------------------------------------------------------- 1 | toolsApp.controller("mainPageCtrl", function ($scope, toolsFactory, $location) { 2 | var MODULE_NAME_IDX = 0; 3 | var MODULE_OBJECT_IDX = 1; 4 | 5 | var modules = require('./js/api/util/modules.js'); 6 | 7 | function init(){ 8 | if (!$scope.systemsInitialized) { 9 | var firebase = require('./js/api/database/db_firebase.js'); 10 | var resource_manager = require('./js/api/util/ext_resource_manager.js'); 11 | var data_manager = require('./js/api/data/data_manager.js'); 12 | 13 | console.log(modules); 14 | console.log(resource_manager); 15 | 16 | 17 | console.log('starting server'); 18 | // System is the list of systems that are created and used by this server 19 | // They are in the format of [0]module name, [1]module object 20 | // I might want to move this so it is confined 21 | moduleList = [ 22 | ['firebase', firebase], 23 | ['resource_manager', resource_manager], 24 | ['data_manager', data_manager] 25 | ]; 26 | 27 | createModules(moduleList); 28 | startModules(); 29 | 30 | $scope.systemsInitialized = true; 31 | } 32 | } 33 | 34 | function createModules(moduleList) { 35 | for (i in moduleList) { 36 | var modName = moduleList[i][MODULE_NAME_IDX]; 37 | var modObject = moduleList[i][MODULE_OBJECT_IDX]; 38 | 39 | // The order we initialize and add the object to the modules list 40 | // shouldn't matter since module::init() should not ref other modules 41 | modules.addModule(modName, modObject); 42 | 43 | modObject.init(); 44 | } 45 | 46 | console.log('Modules initialized'); 47 | } 48 | 49 | function startModules() { 50 | // I might want to move this to the modules module 51 | var mods = modules.getModules(); 52 | 53 | // Iter though the modules and tell them to start 54 | for (i in mods) { 55 | mods[i].start(); 56 | } 57 | 58 | console.log('Modules started'); 59 | } 60 | 61 | function start(){ 62 | // List of tools available 63 | $scope.tools = [ 64 | { 65 | 'name': 'Home', 66 | 'url': '' 67 | }, 68 | { 69 | 'name': 'Database Refresh', 70 | 'url': 'dbRefresh' 71 | }, 72 | { 73 | 'name': 'Add New Data Source', 74 | 'url': 'addNewSource' 75 | }, 76 | { 77 | 'name': 'Add Parking Data', 78 | 'url': 'addParking' 79 | } 80 | ]; 81 | 82 | $scope.header = "views/header.html"; 83 | } 84 | 85 | init(); 86 | start(); 87 | }); -------------------------------------------------------------------------------- /tools/tools/js/libs/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.14 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(H,a,A){'use strict';function D(p,g){g=g||{};a.forEach(g,function(a,c){delete g[c]});for(var c in p)!p.hasOwnProperty(c)||"$"===c.charAt(0)&&"$"===c.charAt(1)||(g[c]=p[c]);return g}var v=a.$$minErr("$resource"),C=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;a.module("ngResource",["ng"]).factory("$resource",["$http","$q",function(p,g){function c(a,c){this.template=a;this.defaults=c||{};this.urlParams={}}function t(n,w,l){function r(h,d){var e={};d=x({},w,d);s(d,function(b,d){u(b)&&(b=b());var k;if(b&& 7 | b.charAt&&"@"==b.charAt(0)){k=h;var a=b.substr(1);if(null==a||""===a||"hasOwnProperty"===a||!C.test("."+a))throw v("badmember",a);for(var a=a.split("."),f=0,c=a.length;f 2 | 3 | 4 | 11.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | tools 7 | tools 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Debug 16 | 2.0 17 | 25ddd189-62ae-46ab-894b-c45840684b33 18 | . 19 | js\app.js 20 | False 21 | 22 | 23 | . 24 | . 25 | v4.0 26 | {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD} 27 | ShowAllFiles 28 | False 29 | 30 | 31 | true 32 | 33 | 34 | true 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 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /tools/tools/views/addNewDataSource.html: -------------------------------------------------------------------------------- 1 | 
2 |

Create a new data source

3 | 4 |
5 |

Query

6 |
7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 | 23 |

Map

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 |
-------------------------------------------------------------------------------- /tools/tools/views/addParking.html: -------------------------------------------------------------------------------- 1 |
2 |

Add Parking Data

3 |
4 | 5 |
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 |
-------------------------------------------------------------------------------- /tools/tools/views/dbRefresh.html: -------------------------------------------------------------------------------- 1 | 
2 |

Database Refresh Tool

3 | 4 |

5 | Click to force update the database 6 |

7 | 8 |
-------------------------------------------------------------------------------- /tools/tools/views/header.html: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /tools/tools/views/home.html: -------------------------------------------------------------------------------- 1 | 
2 |

Bypath Tools

3 |

4 | Over to the left is a list of tools that help manage the data for the Bypath product. 5 |

6 |
-------------------------------------------------------------------------------- /v2/app/app.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Platform, ionicBootstrap} from 'ionic-angular'; 3 | import {StatusBar} from 'ionic-native'; 4 | import {TabsPage} from './pages/tabs/tabs'; 5 | 6 | 7 | @Component({ 8 | template: '' 9 | }) 10 | export class MyApp { 11 | 12 | private rootPage: any; 13 | 14 | constructor(private platform: Platform) { 15 | this.rootPage = TabsPage; 16 | 17 | platform.ready().then(() => { 18 | // Okay, so the platform is ready and our plugins are available. 19 | // Here you can do any higher level native things you might need. 20 | StatusBar.styleDefault(); 21 | }); 22 | } 23 | } 24 | 25 | ionicBootstrap(MyApp); 26 | -------------------------------------------------------------------------------- /v2/app/pages/about/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | About 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /v2/app/pages/about/about.scss: -------------------------------------------------------------------------------- 1 | .about { 2 | } 3 | -------------------------------------------------------------------------------- /v2/app/pages/about/about.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {NavController} from 'ionic-angular'; 3 | 4 | @Component({ 5 | templateUrl: 'build/pages/about/about.html' 6 | }) 7 | export class AboutPage { 8 | constructor(private navCtrl: NavController) { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /v2/app/pages/contact/contact.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Contact 5 | 6 | 7 | 8 | 9 | 10 | 11 | Follow us on Twitter 12 | 13 | 14 | @ionicframework 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /v2/app/pages/contact/contact.scss: -------------------------------------------------------------------------------- 1 | .contact { 2 | } 3 | -------------------------------------------------------------------------------- /v2/app/pages/contact/contact.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {NavController} from 'ionic-angular'; 3 | 4 | @Component({ 5 | templateUrl: 'build/pages/contact/contact.html' 6 | }) 7 | export class ContactPage { 8 | constructor(private navCtrl: NavController) { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /v2/app/pages/home/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Home 4 | 5 | 6 | 7 | 8 |

Welcome to Ionic!

9 |

10 | This starter project comes with simple tabs-based layout for apps 11 | that are going to primarily use a Tabbed UI. 12 |

13 |

14 | Take a look at the app/ directory to add or change tabs, 15 | update any existing page or create new pages. 16 |

17 |
18 | -------------------------------------------------------------------------------- /v2/app/pages/home/home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | } 3 | -------------------------------------------------------------------------------- /v2/app/pages/home/home.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {NavController} from 'ionic-angular'; 3 | 4 | @Component({ 5 | templateUrl: 'build/pages/home/home.html' 6 | }) 7 | export class HomePage { 8 | constructor(private navCtrl: NavController) { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /v2/app/pages/tabs/tabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /v2/app/pages/tabs/tabs.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {HomePage} from '../home/home'; 3 | import {AboutPage} from '../about/about'; 4 | import {ContactPage} from '../contact/contact'; 5 | 6 | @Component({ 7 | templateUrl: 'build/pages/tabs/tabs.html' 8 | }) 9 | export class TabsPage { 10 | 11 | private tab1Root: any; 12 | private tab2Root: any; 13 | private tab3Root: any; 14 | 15 | constructor() { 16 | // this tells the tabs component which Pages 17 | // should be each tab's root Page 18 | this.tab1Root = HomePage; 19 | this.tab2Root = AboutPage; 20 | this.tab3Root = ContactPage; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /v2/app/theme/app.core.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/v2/theming/ 2 | 3 | 4 | // App Shared Imports 5 | // -------------------------------------------------- 6 | // These are the imports which make up the design of this app. 7 | // By default each design mode includes these shared imports. 8 | // App Shared Sass variables belong in app.variables.scss. 9 | 10 | @import "../pages/about/about"; 11 | 12 | @import "../pages/contact/contact"; 13 | 14 | @import "../pages/home/home"; 15 | -------------------------------------------------------------------------------- /v2/app/theme/app.ios.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/v2/theming/ 2 | 3 | 4 | // App Shared Variables 5 | // -------------------------------------------------- 6 | // Shared Sass variables go in the app.variables.scss file 7 | @import "app.variables"; 8 | 9 | 10 | // App iOS Variables 11 | // -------------------------------------------------- 12 | // iOS only Sass variables can go here 13 | 14 | 15 | // Ionic iOS Sass 16 | // -------------------------------------------------- 17 | // Custom App variables must be declared before importing Ionic. 18 | // Ionic will use its default values when a custom variable isn't provided. 19 | @import "ionic.ios"; 20 | 21 | 22 | // App Shared Sass 23 | // -------------------------------------------------- 24 | // All Sass files that make up this app goes into the app.core.scss file. 25 | // For simpler CSS overrides, custom app CSS must come after Ionic's CSS. 26 | @import "app.core"; 27 | 28 | 29 | // App iOS Only Sass 30 | // -------------------------------------------------- 31 | // CSS that should only apply to the iOS app 32 | -------------------------------------------------------------------------------- /v2/app/theme/app.md.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/v2/theming/ 2 | 3 | 4 | // App Shared Variables 5 | // -------------------------------------------------- 6 | // Shared Sass variables go in the app.variables.scss file 7 | @import "app.variables"; 8 | 9 | 10 | // App Material Design Variables 11 | // -------------------------------------------------- 12 | // Material Design only Sass variables can go here 13 | 14 | 15 | // Ionic Material Design Sass 16 | // -------------------------------------------------- 17 | // Custom App variables must be declared before importing Ionic. 18 | // Ionic will use its default values when a custom variable isn't provided. 19 | @import "ionic.md"; 20 | 21 | 22 | // App Shared Sass 23 | // -------------------------------------------------- 24 | // All Sass files that make up this app goes into the app.core.scss file. 25 | // For simpler CSS overrides, custom app CSS must come after Ionic's CSS. 26 | @import "app.core"; 27 | 28 | 29 | // App Material Design Only Sass 30 | // -------------------------------------------------- 31 | // CSS that should only apply to the Material Design app 32 | -------------------------------------------------------------------------------- /v2/app/theme/app.variables.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/v2/theming/ 2 | 3 | // Ionic Shared Functions 4 | // -------------------------------------------------- 5 | // Makes Ionic Sass functions available to your App 6 | 7 | @import "globals.core"; 8 | 9 | // App Shared Variables 10 | // -------------------------------------------------- 11 | // To customize the look and feel of this app, you can override 12 | // the Sass variables found in Ionic's source scss files. Setting 13 | // variables before Ionic's Sass will use these variables rather than 14 | // Ionic's default Sass variable values. App Shared Sass imports belong 15 | // in the app.core.scss file and not this file. Sass variables specific 16 | // to the mode belong in either the app.ios.scss or app.md.scss files. 17 | 18 | 19 | // App Shared Color Variables 20 | // -------------------------------------------------- 21 | // It's highly recommended to change the default colors 22 | // to match your app's branding. Ionic uses a Sass map of 23 | // colors so you can add, rename and remove colors as needed. 24 | // The "primary" color is the only required color in the map. 25 | // Both iOS and MD colors can be further customized if colors 26 | // are different per mode. 27 | 28 | $colors: ( 29 | primary: #387ef5, 30 | secondary: #32db64, 31 | danger: #f53d3d, 32 | light: #f4f4f4, 33 | dark: #222, 34 | favorite: #69BB7B 35 | ); 36 | -------------------------------------------------------------------------------- /v2/app/theme/app.wp.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/v2/theming/ 2 | 3 | 4 | // App Shared Variables 5 | // -------------------------------------------------- 6 | // Shared Sass variables go in the app.variables.scss file 7 | @import "app.variables"; 8 | 9 | 10 | // App Windows Variables 11 | // -------------------------------------------------- 12 | // Windows only Sass variables can go here 13 | 14 | 15 | // Ionic Windows Sass 16 | // -------------------------------------------------- 17 | // Custom App variables must be declared before importing Ionic. 18 | // Ionic will use its default values when a custom variable isn't provided. 19 | @import "ionic.wp"; 20 | 21 | 22 | // App Shared Sass 23 | // -------------------------------------------------- 24 | // All Sass files that make up this app goes into the app.core.scss file. 25 | // For simpler CSS overrides, custom app CSS must come after Ionic's CSS. 26 | @import "app.core"; 27 | 28 | 29 | // App Windows Only Sass 30 | // -------------------------------------------------- 31 | // CSS that should only apply to the Windows app 32 | -------------------------------------------------------------------------------- /v2/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | MyIoApp 4 | An Ionic Framework and Cordova project. 5 | Ionic Framework Team 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 | -------------------------------------------------------------------------------- /v2/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | gulpWatch = require('gulp-watch'), 3 | del = require('del'), 4 | runSequence = require('run-sequence'), 5 | argv = process.argv; 6 | 7 | 8 | /** 9 | * Ionic hooks 10 | * Add ':before' or ':after' to any Ionic project command name to run the specified 11 | * tasks before or after the command. 12 | */ 13 | gulp.task('serve:before', ['watch']); 14 | gulp.task('emulate:before', ['build']); 15 | gulp.task('deploy:before', ['build']); 16 | gulp.task('build:before', ['build']); 17 | 18 | // we want to 'watch' when livereloading 19 | var shouldWatch = argv.indexOf('-l') > -1 || argv.indexOf('--livereload') > -1; 20 | gulp.task('run:before', [shouldWatch ? 'watch' : 'build']); 21 | 22 | /** 23 | * Ionic Gulp tasks, for more information on each see 24 | * https://github.com/driftyco/ionic-gulp-tasks 25 | * 26 | * Using these will allow you to stay up to date if the default Ionic 2 build 27 | * changes, but you are of course welcome (and encouraged) to customize your 28 | * build however you see fit. 29 | */ 30 | var buildBrowserify = require('ionic-gulp-browserify-typescript'); 31 | var buildSass = require('ionic-gulp-sass-build'); 32 | var copyHTML = require('ionic-gulp-html-copy'); 33 | var copyFonts = require('ionic-gulp-fonts-copy'); 34 | var copyScripts = require('ionic-gulp-scripts-copy'); 35 | var tslint = require('ionic-gulp-tslint'); 36 | 37 | var isRelease = argv.indexOf('--release') > -1; 38 | 39 | gulp.task('watch', ['clean'], function(done){ 40 | runSequence( 41 | ['sass', 'html', 'fonts', 'scripts'], 42 | function(){ 43 | gulpWatch('app/**/*.scss', function(){ gulp.start('sass'); }); 44 | gulpWatch('app/**/*.html', function(){ gulp.start('html'); }); 45 | buildBrowserify({ watch: true }).on('end', done); 46 | } 47 | ); 48 | }); 49 | 50 | gulp.task('build', ['clean'], function(done){ 51 | runSequence( 52 | ['sass', 'html', 'fonts', 'scripts'], 53 | function(){ 54 | buildBrowserify({ 55 | minify: isRelease, 56 | browserifyOptions: { 57 | debug: !isRelease 58 | }, 59 | uglifyOptions: { 60 | mangle: false 61 | } 62 | }).on('end', done); 63 | } 64 | ); 65 | }); 66 | 67 | gulp.task('sass', buildSass); 68 | gulp.task('html', copyHTML); 69 | gulp.task('fonts', copyFonts); 70 | gulp.task('scripts', copyScripts); 71 | gulp.task('clean', function(){ 72 | return del('www/build'); 73 | }); 74 | gulp.task('lint', tslint); 75 | -------------------------------------------------------------------------------- /v2/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 | -------------------------------------------------------------------------------- /v2/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ionic 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 | --------------------------------------------------------------------------------