├── .gitignore ├── AWS lambda functions ├── .gitignore └── dropbox-s3-sync │ ├── .gitignore │ ├── .npmignore │ ├── Gruntfile.js │ ├── config │ ├── config.js │ └── env │ │ └── all.js │ ├── event.json │ ├── index.js │ ├── inputs.txt │ ├── package.json │ └── template.html ├── README.md ├── client ├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── app │ ├── .buildignore │ ├── .htaccess │ ├── images │ │ └── yeoman.png │ ├── index.html │ ├── scripts │ │ ├── app.js │ │ └── controllers │ │ │ └── main.js │ ├── styles │ │ ├── _variables.scss │ │ ├── main.scss │ │ └── partials │ │ │ └── _signup.scss │ └── views │ │ └── main.html ├── bower.json ├── package.json └── test │ ├── .jshintrc │ ├── karma.conf.js │ └── spec │ └── controllers │ └── main.js ├── lambda_file_sync.jpg └── server ├── app.js ├── aws ├── aws.js └── index.js ├── bin └── www ├── config ├── config.js └── env │ └── all.js ├── dropbox ├── dropbox.js └── index.js ├── package.json ├── routes.js └── routes └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | .idea 4 | .tmp 5 | .sass-cache 6 | client/bower_components 7 | client/node_modules/ 8 | server/node_modules/ 9 | server/config/env/development.js 10 | -------------------------------------------------------------------------------- /AWS lambda functions/.gitignore: -------------------------------------------------------------------------------- 1 | link-scraper 2 | -------------------------------------------------------------------------------- /AWS lambda functions/dropbox-s3-sync/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | config/env/development.js 4 | -------------------------------------------------------------------------------- /AWS lambda functions/dropbox-s3-sync/.npmignore: -------------------------------------------------------------------------------- 1 | event.json 2 | Gruntfile.js 3 | dist 4 | *.iml -------------------------------------------------------------------------------- /AWS lambda functions/dropbox-s3-sync/Gruntfile.js: -------------------------------------------------------------------------------- 1 | var grunt = require('grunt'); 2 | grunt.loadNpmTasks('grunt-aws-lambda'); 3 | 4 | grunt.initConfig({ 5 | lambda_invoke: { 6 | default: { 7 | } 8 | }, 9 | lambda_deploy: { 10 | default: { 11 | options : { 12 | region : "eu-west-1", 13 | timeout :25 14 | }, 15 | function: 'dropbox-s3-sync' 16 | } 17 | }, 18 | lambda_package: { 19 | default: { 20 | } 21 | } 22 | }); 23 | 24 | grunt.registerTask('deploy', ['lambda_package', 'lambda_deploy']); -------------------------------------------------------------------------------- /AWS lambda functions/dropbox-s3-sync/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * Load environment configuration 7 | */ 8 | //module.exports = _.merge( 9 | // require('./env/all.js'), 10 | // require('./env/' + process.env.NODE_ENV + '.js') || {}); 11 | 12 | module.exports = _.merge( 13 | require('./env/all.js'), 14 | require('./env/development.js') || {}); -------------------------------------------------------------------------------- /AWS lambda functions/dropbox-s3-sync/config/env/all.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | dropbox :{appKey : "ADD YOUR OWN KEY", 5 | appSecret : "ADD YOUR OWN SECRET"}, 6 | 7 | aws:{accessKey:"ADD YOUR OWN KEY" , 8 | secretKey : "ADD YOUR OWN SECRET", 9 | tokenBucket :"NAME OF THE S3 BUCKET TO STORE BEARER TOKEN", 10 | fileUploadBucket :"NAME OF THE S3 BUCKET TO STORE BEARER TOKEN" 11 | } 12 | }; -------------------------------------------------------------------------------- /AWS lambda functions/dropbox-s3-sync/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "webpage": "http://www.morrissey-solo.com/" 3 | } -------------------------------------------------------------------------------- /AWS lambda functions/dropbox-s3-sync/index.js: -------------------------------------------------------------------------------- 1 | var AWS = require('aws-sdk'); 2 | var config = require('./config/config'); 3 | var Q = require('q'); 4 | var Dropbox = require("dropbox"); 5 | 6 | function getToken() { 7 | var deferred = Q.defer(); 8 | var params = {Bucket: config.aws.tokenBucket, Key: 'token' 9 | } 10 | 11 | var s3 = new AWS.S3(); 12 | 13 | s3.getObject(params, function (err, data) { 14 | if (err) { 15 | console.log(err, err.stack); 16 | deferred.reject(err); 17 | } 18 | else { 19 | deferred.resolve(data.Body.toString()); 20 | } 21 | }); 22 | return deferred.promise; 23 | }; 24 | 25 | function syncFile(fileName,fileStream, bearerToken) { 26 | var client = new Dropbox.Client({ 27 | key: config.dropbox.appKey, 28 | secret: config.dropbox.appSecret, 29 | token: bearerToken 30 | }); 31 | 32 | var result = client.writeFile(fileName, fileStream, function (error, stat) { 33 | if (error) { 34 | console.log(error); 35 | } 36 | }); 37 | return result; 38 | } 39 | 40 | function getFile(bucketName, key) { 41 | 42 | var deferred = Q.defer(); 43 | var params = {Bucket: bucketName, 44 | Key: key 45 | } 46 | 47 | var s3 = new AWS.S3(); 48 | 49 | s3.getObject(params, function (err, data) { 50 | if (err) { 51 | console.log(err, err.stack); 52 | deferred.reject(err); 53 | } 54 | else { 55 | deferred.resolve(data.Body) 56 | } 57 | }); 58 | return deferred.promise; 59 | }; 60 | 61 | exports.handler = function (event, context) { 62 | 63 | var srcBucket = event.Records[0].s3.bucket.name; 64 | var srcKey = event.Records[0].s3.object.key; 65 | 66 | getToken().then(function (token) { 67 | getFile(srcBucket,srcKey) 68 | .then(function (fileStream) { 69 | syncFile(srcKey,fileStream, token); 70 | }) 71 | .catch(function (error) { 72 | console.log(error); 73 | }) 74 | }).done(); 75 | }; -------------------------------------------------------------------------------- /AWS lambda functions/dropbox-s3-sync/inputs.txt: -------------------------------------------------------------------------------- 1 | { 2 | "Records":[ 3 | { 4 | "eventVersion":"2.0", 5 | "eventSource":"aws:s3", 6 | "awsRegion":"us-east-1", 7 | "eventTime":"1970-01-01T00:00:00.000Z", 8 | "eventName":"ObjectCreated:Put", 9 | "userIdentity":{ 10 | "principalId":"AIDAJDPLRKLG7UEXAMPLE" 11 | }, 12 | "requestParameters":{ 13 | "sourceIPAddress":"127.0.0.1" 14 | }, 15 | "responseElements":{ 16 | "x-amz-request-id":"C3D13FE58DE4C810", 17 | "x-amz-id-2":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" 18 | }, 19 | "s3":{ 20 | "s3SchemaVersion":"1.0", 21 | "configurationId":"testConfigRule", 22 | "bucket":{ 23 | "name":"aidansourcebucket", 24 | "ownerIdentity":{ 25 | "principalId":"A3NL1KOZZKExample" 26 | }, 27 | "arn":"arn:aws:s3:::aidansourcebucket" 28 | }, 29 | "object":{ 30 | "key":"Morrissey.jpg", 31 | "size":1024, 32 | "eTag":"d41d8cd98f00b204e9800998ecf8427e", 33 | "versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /AWS lambda functions/dropbox-s3-sync/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dropbox-s3-sync", 3 | "version": "1.0.0", 4 | "description": "AWS Lambda function to sync objects from an S# bucket to Dropbox", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "AWS Lamdba", 11 | "Dropbox" 12 | ], 13 | "dependencies": { 14 | "lodash": "~2.4.1", 15 | "q": "~1.1.2", 16 | "dropbox": "~0.10.3" 17 | }, 18 | "devDependencies": { 19 | "grunt": "0.4.*", 20 | "grunt-aws-lambda": "0.2.0", 21 | "aws-sdk": "2.0.23" 22 | }, 23 | "bundledDependencies": [ 24 | "lodash", 25 | "q", 26 | "dropbox" 27 | ], 28 | "author": "Aidan Casey", 29 | "license": "BSD-2-Clause" 30 | } 31 | -------------------------------------------------------------------------------- /AWS lambda functions/dropbox-s3-sync/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |15 | Generated {{time}} 16 |
17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## S3DropboxFileSync 2 | 3 | 4 | Upload files to an AWS S3 bucket and automatically sync them to a dropbox account using a an AWS Lambda function. 5 | 6 |  7 | 8 | 9 | ### Prerequisites 10 | * Node.js http://www.nodejs.org/ 11 | * Express http://expressjs.com/ 12 | * AngularJS http://angularjs.org/ 13 | * Grunt - Download and Install http://gruntjs.com 14 | * AWS Developer Key 15 | * Dropbox Developer key - https://www.dropbox.com/developers/core 16 | 17 | 18 | ### Install Prerequisites 19 | ``` 20 | $ npm install -g bower 21 | $ npm install -g nodemon 22 | 23 | ``` 24 | ### Technologies 25 | 26 | Client - Angular.js ,Bootstrap, Sass 27 | 28 | Server - Node.js, Express server 29 | 30 | DropBox File Sync - AWS Lambda function (written in node.js) 31 | 32 | 33 | ## Configuration Settings - Node.js App Server 34 | 35 | * Create the file server/config/env/development.js & specify the following value 36 | 37 | ``` 38 | 'use strict'; 39 | 40 | module.exports = { 41 | dropbox: {appKey: "YOUR DROPBOX APP KEY", 42 | appSecret: "YOUR DROPBOX APP SECRET"}, 43 | 44 | aws: { accessKey: "YOUR AWS ACCESS KEY", 45 | secretKey: "YOUR AWS SECRET KEY", 46 | tokenBucket: "NAME OF A PRIVATE S3 BUCKET TO STORE DROPBOX BEARER TOKEN", 47 | fileUploadBucket: "NAME OF AN S3 BUCKET TO UPLOAD FILES" 48 | } 49 | }; 50 | ``` 51 | 52 | ## Configuration Settings - AWS Lambda Function 53 | 54 | * Create the file AWS lambda functions/dropbox-s3-sync/config/env/development.js & specify the following value 55 | 56 | ``` 57 | 'use strict'; 58 | 59 | module.exports = { 60 | dropbox: {appKey: "YOUR DROPBOX APP KEY", 61 | appSecret: "YOUR DROPBOX APP SECRET"}, 62 | 63 | aws: { accessKey: "YOUR AWS ACCESS KEY", 64 | secretKey: "YOUR AWS SECRET KEY", 65 | tokenBucket: "NAME OF A PRIVATE S3 BUCKET TO STORE DROPBOX BEARER TOKEN", 66 | fileUploadBucket: "NAME OF AN S3 BUCKET TO UPLOAD FILES" 67 | } 68 | }; 69 | ``` 70 | 71 | ### To build the client web application 72 | ``` 73 | $ cd /client 74 | $ grunt serve 75 | 76 | ``` 77 | 78 | ### To run the app server 79 | ``` 80 | $ cd /server 81 | $ npm test 82 | ``` 83 | open a browser and navigate to http://localhost:3000 & off you go! 84 | 85 | ### To deploy the AWS Lambda function 86 | ``` 87 | $ cd "/AWS lambda functions/dropbox-s3-sync" 88 | $ grunt lambda_package lambda_deploy 89 | ``` 90 | 91 | ## Credits 92 | * To the very nifty grunt aws lambda task. 93 | * Good Blog post on streaming files to S3 from node.js 94 | 95 | 96 | ## License 97 | [The MIT License](http://opensource.org/licenses/MIT) -------------------------------------------------------------------------------- /client/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /client/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | bower_components 6 | -------------------------------------------------------------------------------- /client/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "globals": { 21 | "angular": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_script: 5 | - 'npm install -g bower grunt-cli' 6 | - 'bower install' 7 | -------------------------------------------------------------------------------- /client/Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2014-11-30 using generator-angular 0.10.0 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | module.exports = function (grunt) { 11 | 12 | // Load grunt tasks automatically 13 | require('load-grunt-tasks')(grunt); 14 | 15 | // Time how long tasks take. Can help when optimizing build times 16 | require('time-grunt')(grunt); 17 | 18 | // Configurable paths for the application 19 | var appConfig = { 20 | app: require('./bower.json').appPath || 'app', 21 | dist: '../server/dist' 22 | }; 23 | 24 | // Define the configuration for all the tasks 25 | grunt.initConfig({ 26 | 27 | // Project settings 28 | yeoman: appConfig, 29 | 30 | // Watches files for changes and runs tasks based on the changed files 31 | watch: { 32 | bower: { 33 | files: ['bower.json'], 34 | tasks: ['wiredep'] 35 | }, 36 | js: { 37 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], 38 | tasks: ['newer:jshint:all'], 39 | options: { 40 | livereload: '<%= connect.options.livereload %>' 41 | } 42 | }, 43 | jsTest: { 44 | files: ['test/spec/{,*/}*.js'], 45 | tasks: ['newer:jshint:test', 'karma'] 46 | }, 47 | compass: { 48 | files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 49 | tasks: ['compass:server', 'autoprefixer'] 50 | }, 51 | gruntfile: { 52 | files: ['Gruntfile.js'] 53 | }, 54 | livereload: { 55 | options: { 56 | livereload: '<%= connect.options.livereload %>' 57 | }, 58 | files: [ 59 | '<%= yeoman.app %>/{,*/}*.html', 60 | '.tmp/styles/{,*/}*.css', 61 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 62 | ] 63 | } 64 | }, 65 | 66 | // The actual grunt server settings 67 | connect: { 68 | options: { 69 | port: 9000, 70 | // Change this to '0.0.0.0' to access the server from outside. 71 | hostname: 'localhost', 72 | livereload: 35729 73 | }, 74 | livereload: { 75 | options: { 76 | open: true, 77 | middleware: function (connect) { 78 | return [ 79 | connect.static('.tmp'), 80 | connect().use( 81 | '/bower_components', 82 | connect.static('./bower_components') 83 | ), 84 | connect.static(appConfig.app) 85 | ]; 86 | } 87 | } 88 | }, 89 | test: { 90 | options: { 91 | port: 9001, 92 | middleware: function (connect) { 93 | return [ 94 | connect.static('.tmp'), 95 | connect.static('test'), 96 | connect().use( 97 | '/bower_components', 98 | connect.static('./bower_components') 99 | ), 100 | connect.static(appConfig.app) 101 | ]; 102 | } 103 | } 104 | }, 105 | dist: { 106 | options: { 107 | open: true, 108 | base: '<%= yeoman.dist %>' 109 | } 110 | } 111 | }, 112 | 113 | // Make sure code styles are up to par and there are no obvious mistakes 114 | jshint: { 115 | options: { 116 | jshintrc: '.jshintrc', 117 | reporter: require('jshint-stylish') 118 | }, 119 | all: { 120 | src: [ 121 | 'Gruntfile.js', 122 | '<%= yeoman.app %>/scripts/{,*/}*.js' 123 | ] 124 | }, 125 | test: { 126 | options: { 127 | jshintrc: 'test/.jshintrc' 128 | }, 129 | src: ['test/spec/{,*/}*.js'] 130 | } 131 | }, 132 | 133 | // Empties folders to start fresh 134 | clean: { 135 | dist: { 136 | files: [{ 137 | dot: true, 138 | src: [ 139 | '.tmp', 140 | '<%= yeoman.dist %>/{,*/}*', 141 | '!<%= yeoman.dist %>/.git{,*/}*' 142 | ] 143 | }] 144 | }, 145 | server: '.tmp' 146 | }, 147 | 148 | // Add vendor prefixed styles 149 | autoprefixer: { 150 | options: { 151 | browsers: ['last 1 version'] 152 | }, 153 | dist: { 154 | files: [{ 155 | expand: true, 156 | cwd: '.tmp/styles/', 157 | src: '{,*/}*.css', 158 | dest: '.tmp/styles/' 159 | }] 160 | } 161 | }, 162 | 163 | // Automatically inject Bower components into the app 164 | wiredep: { 165 | app: { 166 | src: ['<%= yeoman.app %>/index.html'], 167 | ignorePath: /\.\.\// 168 | }, 169 | sass: { 170 | src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 171 | ignorePath: /(\.\.\/){1,2}bower_components\// 172 | } 173 | }, 174 | 175 | // Compiles Sass to CSS and generates necessary files if requested 176 | compass: { 177 | options: { 178 | sassDir: '<%= yeoman.app %>/styles', 179 | cssDir: '.tmp/styles', 180 | generatedImagesDir: '.tmp/images/generated', 181 | imagesDir: '<%= yeoman.app %>/images', 182 | javascriptsDir: '<%= yeoman.app %>/scripts', 183 | fontsDir: '<%= yeoman.app %>/styles/fonts', 184 | importPath: './bower_components', 185 | httpImagesPath: '/images', 186 | httpGeneratedImagesPath: '/images/generated', 187 | httpFontsPath: '/styles/fonts', 188 | relativeAssets: false, 189 | assetCacheBuster: false, 190 | raw: 'Sass::Script::Number.precision = 10\n' 191 | }, 192 | dist: { 193 | options: { 194 | generatedImagesDir: '<%= yeoman.dist %>/images/generated' 195 | } 196 | }, 197 | server: { 198 | options: { 199 | debugInfo: true 200 | } 201 | } 202 | }, 203 | 204 | // Renames files for browser caching purposes 205 | filerev: { 206 | dist: { 207 | src: [ 208 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 209 | '<%= yeoman.dist %>/styles/{,*/}*.css', 210 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 211 | '<%= yeoman.dist %>/styles/fonts/*' 212 | ] 213 | } 214 | }, 215 | 216 | // Reads HTML for usemin blocks to enable smart builds that automatically 217 | // concat, minify and revision files. Creates configurations in memory so 218 | // additional tasks can operate on them 219 | useminPrepare: { 220 | html: '<%= yeoman.app %>/index.html', 221 | options: { 222 | dest: '<%= yeoman.dist %>', 223 | flow: { 224 | html: { 225 | steps: { 226 | js: ['concat', 'uglifyjs'], 227 | css: ['cssmin'] 228 | }, 229 | post: {} 230 | } 231 | } 232 | } 233 | }, 234 | 235 | // Performs rewrites based on filerev and the useminPrepare configuration 236 | usemin: { 237 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 238 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 239 | options: { 240 | assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images'] 241 | } 242 | }, 243 | 244 | // The following *-min tasks will produce minified files in the dist folder 245 | // By default, your `index.html`'s will take care of 246 | // minification. These next options are pre-configured if you do not wish 247 | // to use the Usemin blocks. 248 | // cssmin: { 249 | // dist: { 250 | // files: { 251 | // '<%= yeoman.dist %>/styles/main.css': [ 252 | // '.tmp/styles/{,*/}*.css' 253 | // ] 254 | // } 255 | // } 256 | // }, 257 | // uglify: { 258 | // dist: { 259 | // files: { 260 | // '<%= yeoman.dist %>/scripts/scripts.js': [ 261 | // '<%= yeoman.dist %>/scripts/scripts.js' 262 | // ] 263 | // } 264 | // } 265 | // }, 266 | // concat: { 267 | // dist: {} 268 | // }, 269 | 270 | imagemin: { 271 | dist: { 272 | files: [{ 273 | expand: true, 274 | cwd: '<%= yeoman.app %>/images', 275 | src: '{,*/}*.{png,jpg,jpeg,gif}', 276 | dest: '<%= yeoman.dist %>/images' 277 | }] 278 | } 279 | }, 280 | 281 | svgmin: { 282 | dist: { 283 | files: [{ 284 | expand: true, 285 | cwd: '<%= yeoman.app %>/images', 286 | src: '{,*/}*.svg', 287 | dest: '<%= yeoman.dist %>/images' 288 | }] 289 | } 290 | }, 291 | 292 | htmlmin: { 293 | dist: { 294 | options: { 295 | collapseWhitespace: true, 296 | conservativeCollapse: true, 297 | collapseBooleanAttributes: true, 298 | removeCommentsFromCDATA: true, 299 | removeOptionalTags: true 300 | }, 301 | files: [{ 302 | expand: true, 303 | cwd: '<%= yeoman.dist %>', 304 | src: ['*.html', 'views/{,*/}*.html'], 305 | dest: '<%= yeoman.dist %>' 306 | }] 307 | } 308 | }, 309 | 310 | // ng-annotate tries to make the code safe for minification automatically 311 | // by using the Angular long form for dependency injection. 312 | ngAnnotate: { 313 | dist: { 314 | files: [{ 315 | expand: true, 316 | cwd: '.tmp/concat/scripts', 317 | src: ['*.js', '!oldieshim.js'], 318 | dest: '.tmp/concat/scripts' 319 | }] 320 | } 321 | }, 322 | 323 | // Replace Google CDN references 324 | cdnify: { 325 | dist: { 326 | html: ['<%= yeoman.dist %>/*.html'] 327 | } 328 | }, 329 | 330 | // Copies remaining files to places other tasks can use 331 | copy: { 332 | dist: { 333 | files: [{ 334 | expand: true, 335 | dot: true, 336 | cwd: '<%= yeoman.app %>', 337 | dest: '<%= yeoman.dist %>', 338 | src: [ 339 | '*.{ico,png,txt}', 340 | '.htaccess', 341 | '*.html', 342 | 'views/{,*/}*.html', 343 | 'images/{,*/}*.{webp}', 344 | 'fonts/{,*/}*.*' 345 | ] 346 | }, { 347 | expand: true, 348 | cwd: '.tmp/images', 349 | dest: '<%= yeoman.dist %>/images', 350 | src: ['generated/*'] 351 | }, { 352 | expand: true, 353 | cwd: '.', 354 | src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*', 355 | dest: '<%= yeoman.dist %>' 356 | }] 357 | }, 358 | styles: { 359 | expand: true, 360 | cwd: '<%= yeoman.app %>/styles', 361 | dest: '.tmp/styles/', 362 | src: '{,*/}*.css' 363 | } 364 | }, 365 | 366 | // Run some tasks in parallel to speed up the build process 367 | concurrent: { 368 | server: [ 369 | 'compass:server' 370 | ], 371 | test: [ 372 | 'compass' 373 | ], 374 | dist: [ 375 | 'compass:dist', 376 | 'imagemin', 377 | 'svgmin' 378 | ] 379 | }, 380 | 381 | // Test settings 382 | karma: { 383 | unit: { 384 | configFile: 'test/karma.conf.js', 385 | singleRun: true 386 | } 387 | } 388 | }); 389 | 390 | 391 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { 392 | if (target === 'dist') { 393 | return grunt.task.run(['build', 'connect:dist:keepalive']); 394 | } 395 | 396 | grunt.task.run([ 397 | 'clean:server', 398 | 'wiredep', 399 | 'concurrent:server', 400 | 'autoprefixer', 401 | 'connect:livereload', 402 | 'watch' 403 | ]); 404 | }); 405 | 406 | grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) { 407 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 408 | grunt.task.run(['serve:' + target]); 409 | }); 410 | 411 | grunt.registerTask('test', [ 412 | 'clean:server', 413 | 'concurrent:test', 414 | 'autoprefixer', 415 | 'connect:test', 416 | 'karma' 417 | ]); 418 | 419 | grunt.registerTask('build', [ 420 | 'clean:dist', 421 | 'wiredep', 422 | 'useminPrepare', 423 | 'concurrent:dist', 424 | 'autoprefixer', 425 | 'concat', 426 | 'ngAnnotate', 427 | 'copy:dist', 428 | 'cdnify', 429 | 'cssmin', 430 | 'uglify', 431 | 'filerev', 432 | 'usemin', 433 | 'htmlmin' 434 | ]); 435 | 436 | grunt.registerTask('default', [ 437 | 'newer:jshint', 438 | 'test', 439 | 'build' 440 | ]); 441 | }; 442 | -------------------------------------------------------------------------------- /client/app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /client/app/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache Configuration File 2 | 3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access 4 | # to the main server config file (usually called `httpd.conf`), you should add 5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. 6 | 7 | # ############################################################################## 8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) # 9 | # ############################################################################## 10 | 11 | # ------------------------------------------------------------------------------ 12 | # | Cross-domain AJAX requests | 13 | # ------------------------------------------------------------------------------ 14 | 15 | # Enable cross-origin AJAX requests. 16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 17 | # http://enable-cors.org/ 18 | 19 | #`, ``, and ``.
59 | $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace !default;
60 | $font-family-base: $font-family-sans-serif !default;
61 |
62 | $font-size-base: 14px !default;
63 | $font-size-large: ceil(($font-size-base * 1.25)) !default; // ~18px
64 | $font-size-small: ceil(($font-size-base * 0.85)) !default; // ~12px
65 |
66 | $font-size-h1: floor(($font-size-base * 2.6)) !default; // ~36px
67 | $font-size-h2: floor(($font-size-base * 2.15)) !default; // ~30px
68 | $font-size-h3: ceil(($font-size-base * 1.7)) !default; // ~24px
69 | $font-size-h4: ceil(($font-size-base * 1.25)) !default; // ~18px
70 | $font-size-h5: $font-size-base !default;
71 | $font-size-h6: ceil(($font-size-base * 0.85)) !default; // ~12px
72 |
73 | //** Unit-less `line-height` for use in components like buttons.
74 | $line-height-base: 1.428571429 !default; // 20/14
75 | //** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
76 | $line-height-computed: floor(($font-size-base * $line-height-base)) !default; // ~20px
77 |
78 | //** By default, this inherits from the ``.
79 | $headings-font-family: inherit !default;
80 | $headings-font-weight: 500 !default;
81 | $headings-line-height: 1.1 !default;
82 | $headings-color: inherit !default;
83 |
84 |
85 | //== Iconography
86 | //
87 | //## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
88 |
89 | //** Load fonts from this directory.
90 |
91 | // [converter] Asset helpers such as Sprockets and Node.js Mincer do not resolve relative paths
92 | $icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/") !default;
93 |
94 | //** File name for all font files.
95 | $icon-font-name: "glyphicons-halflings-regular" !default;
96 | //** Element ID within SVG icon file.
97 | $icon-font-svg-id: "glyphicons_halflingsregular" !default;
98 |
99 |
100 | //== Components
101 | //
102 | //## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
103 |
104 | $padding-base-vertical: 6px !default;
105 | $padding-base-horizontal: 12px !default;
106 |
107 | $padding-large-vertical: 10px !default;
108 | $padding-large-horizontal: 16px !default;
109 |
110 | $padding-small-vertical: 5px !default;
111 | $padding-small-horizontal: 10px !default;
112 |
113 | $padding-xs-vertical: 1px !default;
114 | $padding-xs-horizontal: 5px !default;
115 |
116 | $line-height-large: 1.33 !default;
117 | $line-height-small: 1.5 !default;
118 |
119 | $border-radius-base: 4px !default;
120 | $border-radius-large: 6px !default;
121 | $border-radius-small: 3px !default;
122 |
123 | //** Global color for active items (e.g., navs or dropdowns).
124 | $component-active-color: #fff !default;
125 | //** Global background color for active items (e.g., navs or dropdowns).
126 | $component-active-bg: $brand-primary !default;
127 |
128 | //** Width of the `border` for generating carets that indicator dropdowns.
129 | $caret-width-base: 4px !default;
130 | //** Carets increase slightly in size for larger components.
131 | $caret-width-large: 5px !default;
132 |
133 |
134 | //== Tables
135 | //
136 | //## Customizes the `.table` component with basic values, each used across all table variations.
137 |
138 | //** Padding for ``s and ` `s.
139 | $table-cell-padding: 8px !default;
140 | //** Padding for cells in `.table-condensed`.
141 | $table-condensed-cell-padding: 5px !default;
142 |
143 | //** Default background color used for all tables.
144 | $table-bg: transparent !default;
145 | //** Background color used for `.table-striped`.
146 | $table-bg-accent: #f9f9f9 !default;
147 | //** Background color used for `.table-hover`.
148 | $table-bg-hover: #f5f5f5 !default;
149 | $table-bg-active: $table-bg-hover !default;
150 |
151 | //** Border color for table and cell borders.
152 | $table-border-color: #ddd !default;
153 |
154 |
155 | //== Buttons
156 | //
157 | //## For each of Bootstrap's buttons, define text, background and border color.
158 |
159 | $btn-font-weight: normal !default;
160 |
161 | $btn-default-color: #333 !default;
162 | $btn-default-bg: #fff !default;
163 | $btn-default-border: #ccc !default;
164 |
165 | $btn-primary-color: #fff !default;
166 | $btn-primary-bg: $brand-primary !default;
167 | $btn-primary-border: darken($btn-primary-bg, 5%) !default;
168 |
169 | $btn-success-color: #fff !default;
170 | $btn-success-bg: $brand-success !default;
171 | $btn-success-border: darken($btn-success-bg, 5%) !default;
172 |
173 | $btn-info-color: #fff !default;
174 | $btn-info-bg: $brand-info !default;
175 | $btn-info-border: darken($btn-info-bg, 5%) !default;
176 |
177 | $btn-warning-color: #fff !default;
178 | $btn-warning-bg: $brand-warning !default;
179 | $btn-warning-border: darken($btn-warning-bg, 5%) !default;
180 |
181 | $btn-danger-color: #fff !default;
182 | $btn-danger-bg: $brand-danger !default;
183 | $btn-danger-border: darken($btn-danger-bg, 5%) !default;
184 |
185 | $btn-link-disabled-color: $gray-light !default;
186 |
187 |
188 | //== Forms
189 | //
190 | //##
191 |
192 | //** `` background color
193 | $input-bg: #fff !default;
194 | //** `` background color
195 | $input-bg-disabled: $gray-lighter !default;
196 |
197 | //** Text color for ``s
198 | $input-color: $gray !default;
199 | //** `` border color
200 | $input-border: #ccc !default;
201 |
202 | // TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
203 | //** Default `.form-control` border radius
204 | $input-border-radius: $border-radius-base !default;
205 | //** Large `.form-control` border radius
206 | $input-border-radius-large: $border-radius-large !default;
207 | //** Small `.form-control` border radius
208 | $input-border-radius-small: $border-radius-small !default;
209 |
210 | //** Border color for inputs on focus
211 | $input-border-focus: #66afe9 !default;
212 |
213 | //** Placeholder text color
214 | $input-color-placeholder: #999 !default;
215 |
216 | //** Default `.form-control` height
217 | $input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;
218 | //** Large `.form-control` height
219 | $input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default;
220 | //** Small `.form-control` height
221 | $input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default;
222 |
223 | $legend-color: $gray-dark !default;
224 | $legend-border-color: #e5e5e5 !default;
225 |
226 | //** Background color for textual input addons
227 | $input-group-addon-bg: $gray-lighter !default;
228 | //** Border color for textual input addons
229 | $input-group-addon-border-color: $input-border !default;
230 |
231 | //** Disabled cursor for form controls and buttons.
232 | $cursor-disabled: not-allowed !default;
233 |
234 |
235 | //== Dropdowns
236 | //
237 | //## Dropdown menu container and contents.
238 |
239 | //** Background for the dropdown menu.
240 | $dropdown-bg: #fff !default;
241 | //** Dropdown menu `border-color`.
242 | $dropdown-border: rgba(0,0,0,.15) !default;
243 | //** Dropdown menu `border-color` **for IE8**.
244 | $dropdown-fallback-border: #ccc !default;
245 | //** Divider color for between dropdown items.
246 | $dropdown-divider-bg: #e5e5e5 !default;
247 |
248 | //** Dropdown link text color.
249 | $dropdown-link-color: $gray-dark !default;
250 | //** Hover color for dropdown links.
251 | $dropdown-link-hover-color: darken($gray-dark, 5%) !default;
252 | //** Hover background for dropdown links.
253 | $dropdown-link-hover-bg: #f5f5f5 !default;
254 |
255 | //** Active dropdown menu item text color.
256 | $dropdown-link-active-color: $component-active-color !default;
257 | //** Active dropdown menu item background color.
258 | $dropdown-link-active-bg: $component-active-bg !default;
259 |
260 | //** Disabled dropdown menu item background color.
261 | $dropdown-link-disabled-color: $gray-light !default;
262 |
263 | //** Text color for headers within dropdown menus.
264 | $dropdown-header-color: $gray-light !default;
265 |
266 | //** Deprecated `$dropdown-caret-color` as of v3.1.0
267 | $dropdown-caret-color: #000 !default;
268 |
269 |
270 | //-- Z-index master list
271 | //
272 | // Warning: Avoid customizing these values. They're used for a bird's eye view
273 | // of components dependent on the z-axis and are designed to all work together.
274 | //
275 | // Note: These variables are not generated into the Customizer.
276 |
277 | $zindex-navbar: 1000 !default;
278 | $zindex-dropdown: 1000 !default;
279 | $zindex-popover: 1060 !default;
280 | $zindex-tooltip: 1070 !default;
281 | $zindex-navbar-fixed: 1030 !default;
282 | $zindex-modal: 1040 !default;
283 |
284 |
285 | //== Media queries breakpoints
286 | //
287 | //## Define the breakpoints at which your layout will change, adapting to different screen sizes.
288 |
289 | // Extra small screen / phone
290 | //** Deprecated `$screen-xs` as of v3.0.1
291 | $screen-xs: 480px !default;
292 | //** Deprecated `$screen-xs-min` as of v3.2.0
293 | $screen-xs-min: $screen-xs !default;
294 | //** Deprecated `$screen-phone` as of v3.0.1
295 | $screen-phone: $screen-xs-min !default;
296 |
297 | // Small screen / tablet
298 | //** Deprecated `$screen-sm` as of v3.0.1
299 | $screen-sm: 768px !default;
300 | $screen-sm-min: $screen-sm !default;
301 | //** Deprecated `$screen-tablet` as of v3.0.1
302 | $screen-tablet: $screen-sm-min !default;
303 |
304 | // Medium screen / desktop
305 | //** Deprecated `$screen-md` as of v3.0.1
306 | $screen-md: 992px !default;
307 | $screen-md-min: $screen-md !default;
308 | //** Deprecated `$screen-desktop` as of v3.0.1
309 | $screen-desktop: $screen-md-min !default;
310 |
311 | // Large screen / wide desktop
312 | //** Deprecated `$screen-lg` as of v3.0.1
313 | $screen-lg: 1200px !default;
314 | $screen-lg-min: $screen-lg !default;
315 | //** Deprecated `$screen-lg-desktop` as of v3.0.1
316 | $screen-lg-desktop: $screen-lg-min !default;
317 |
318 | // So media queries don't overlap when required, provide a maximum
319 | $screen-xs-max: ($screen-sm-min - 1) !default;
320 | $screen-sm-max: ($screen-md-min - 1) !default;
321 | $screen-md-max: ($screen-lg-min - 1) !default;
322 |
323 |
324 | //== Grid system
325 | //
326 | //## Define your custom responsive grid.
327 |
328 | //** Number of columns in the grid.
329 | $grid-columns: 12 !default;
330 | //** Padding between columns. Gets divided in half for the left and right.
331 | $grid-gutter-width: 30px !default;
332 | // Navbar collapse
333 | //** Point at which the navbar becomes uncollapsed.
334 | $grid-float-breakpoint: $screen-sm-min !default;
335 | //** Point at which the navbar begins collapsing.
336 | $grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
337 |
338 |
339 | //== Container sizes
340 | //
341 | //## Define the maximum width of `.container` for different screen sizes.
342 |
343 | // Small screen / tablet
344 | $container-tablet: (720px + $grid-gutter-width) !default;
345 | //** For `$screen-sm-min` and up.
346 | $container-sm: $container-tablet !default;
347 |
348 | // Medium screen / desktop
349 | $container-desktop: (940px + $grid-gutter-width) !default;
350 | //** For `$screen-md-min` and up.
351 | $container-md: $container-desktop !default;
352 |
353 | // Large screen / wide desktop
354 | $container-large-desktop: (1140px + $grid-gutter-width) !default;
355 | //** For `$screen-lg-min` and up.
356 | $container-lg: $container-large-desktop !default;
357 |
358 |
359 | //== Navbar
360 | //
361 | //##
362 |
363 | // Basics of a navbar
364 | $navbar-height: 50px !default;
365 | $navbar-margin-bottom: $line-height-computed !default;
366 | $navbar-border-radius: $border-radius-base !default;
367 | $navbar-padding-horizontal: floor(($grid-gutter-width / 2)) !default;
368 | $navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2) !default;
369 | $navbar-collapse-max-height: 340px !default;
370 |
371 | $navbar-default-color: #777 !default;
372 | $navbar-default-bg: #f8f8f8 !default;
373 | $navbar-default-border: darken($navbar-default-bg, 6.5%) !default;
374 |
375 | // Navbar links
376 | $navbar-default-link-color: #777 !default;
377 | $navbar-default-link-hover-color: #333 !default;
378 | $navbar-default-link-hover-bg: transparent !default;
379 | $navbar-default-link-active-color: #555 !default;
380 | $navbar-default-link-active-bg: darken($navbar-default-bg, 6.5%) !default;
381 | $navbar-default-link-disabled-color: #ccc !default;
382 | $navbar-default-link-disabled-bg: transparent !default;
383 |
384 | // Navbar brand label
385 | $navbar-default-brand-color: $navbar-default-link-color !default;
386 | $navbar-default-brand-hover-color: darken($navbar-default-brand-color, 10%) !default;
387 | $navbar-default-brand-hover-bg: transparent !default;
388 |
389 | // Navbar toggle
390 | $navbar-default-toggle-hover-bg: #ddd !default;
391 | $navbar-default-toggle-icon-bar-bg: #888 !default;
392 | $navbar-default-toggle-border-color: #ddd !default;
393 |
394 |
395 | // Inverted navbar
396 | // Reset inverted navbar basics
397 | $navbar-inverse-color: lighten($gray-light, 15%) !default;
398 | $navbar-inverse-bg: #222 !default;
399 | $navbar-inverse-border: darken($navbar-inverse-bg, 10%) !default;
400 |
401 | // Inverted navbar links
402 | $navbar-inverse-link-color: lighten($gray-light, 15%) !default;
403 | $navbar-inverse-link-hover-color: #fff !default;
404 | $navbar-inverse-link-hover-bg: transparent !default;
405 | $navbar-inverse-link-active-color: $navbar-inverse-link-hover-color !default;
406 | $navbar-inverse-link-active-bg: darken($navbar-inverse-bg, 10%) !default;
407 | $navbar-inverse-link-disabled-color: #444 !default;
408 | $navbar-inverse-link-disabled-bg: transparent !default;
409 |
410 | // Inverted navbar brand label
411 | $navbar-inverse-brand-color: $navbar-inverse-link-color !default;
412 | $navbar-inverse-brand-hover-color: #fff !default;
413 | $navbar-inverse-brand-hover-bg: transparent !default;
414 |
415 | // Inverted navbar toggle
416 | $navbar-inverse-toggle-hover-bg: #333 !default;
417 | $navbar-inverse-toggle-icon-bar-bg: #fff !default;
418 | $navbar-inverse-toggle-border-color: #333 !default;
419 |
420 |
421 | //== Navs
422 | //
423 | //##
424 |
425 | //=== Shared nav styles
426 | $nav-link-padding: 10px 15px !default;
427 | $nav-link-hover-bg: $gray-lighter !default;
428 |
429 | $nav-disabled-link-color: $gray-light !default;
430 | $nav-disabled-link-hover-color: $gray-light !default;
431 |
432 | //== Tabs
433 | $nav-tabs-border-color: #ddd !default;
434 |
435 | $nav-tabs-link-hover-border-color: $gray-lighter !default;
436 |
437 | $nav-tabs-active-link-hover-bg: $body-bg !default;
438 | $nav-tabs-active-link-hover-color: $gray !default;
439 | $nav-tabs-active-link-hover-border-color: #ddd !default;
440 |
441 | $nav-tabs-justified-link-border-color: #ddd !default;
442 | $nav-tabs-justified-active-link-border-color: $body-bg !default;
443 |
444 | //== Pills
445 | $nav-pills-border-radius: $border-radius-base !default;
446 | $nav-pills-active-link-hover-bg: $component-active-bg !default;
447 | $nav-pills-active-link-hover-color: $component-active-color !default;
448 |
449 |
450 | //== Pagination
451 | //
452 | //##
453 |
454 | $pagination-color: $link-color !default;
455 | $pagination-bg: #fff !default;
456 | $pagination-border: #ddd !default;
457 |
458 | $pagination-hover-color: $link-hover-color !default;
459 | $pagination-hover-bg: $gray-lighter !default;
460 | $pagination-hover-border: #ddd !default;
461 |
462 | $pagination-active-color: #fff !default;
463 | $pagination-active-bg: $brand-primary !default;
464 | $pagination-active-border: $brand-primary !default;
465 |
466 | $pagination-disabled-color: $gray-light !default;
467 | $pagination-disabled-bg: #fff !default;
468 | $pagination-disabled-border: #ddd !default;
469 |
470 |
471 | //== Pager
472 | //
473 | //##
474 |
475 | $pager-bg: $pagination-bg !default;
476 | $pager-border: $pagination-border !default;
477 | $pager-border-radius: 15px !default;
478 |
479 | $pager-hover-bg: $pagination-hover-bg !default;
480 |
481 | $pager-active-bg: $pagination-active-bg !default;
482 | $pager-active-color: $pagination-active-color !default;
483 |
484 | $pager-disabled-color: $pagination-disabled-color !default;
485 |
486 |
487 | //== Jumbotron
488 | //
489 | //##
490 |
491 | $jumbotron-padding: 30px !default;
492 | $jumbotron-color: inherit !default;
493 | $jumbotron-bg: $gray-lighter !default;
494 | $jumbotron-heading-color: inherit !default;
495 | $jumbotron-font-size: ceil(($font-size-base * 1.5)) !default;
496 |
497 |
498 | //== Form states and alerts
499 | //
500 | //## Define colors for form feedback states and, by default, alerts.
501 |
502 | $state-success-text: #3c763d !default;
503 | $state-success-bg: #dff0d8 !default;
504 | $state-success-border: darken(adjust-hue($state-success-bg, -10), 5%) !default;
505 |
506 | $state-info-text: #31708f !default;
507 | $state-info-bg: #d9edf7 !default;
508 | $state-info-border: darken(adjust-hue($state-info-bg, -10), 7%) !default;
509 |
510 | $state-warning-text: #8a6d3b !default;
511 | $state-warning-bg: #fcf8e3 !default;
512 | $state-warning-border: darken(adjust-hue($state-warning-bg, -10), 5%) !default;
513 |
514 | $state-danger-text: #a94442 !default;
515 | $state-danger-bg: #f2dede !default;
516 | $state-danger-border: darken(adjust-hue($state-danger-bg, -10), 5%) !default;
517 |
518 |
519 | //== Tooltips
520 | //
521 | //##
522 |
523 | //** Tooltip max width
524 | $tooltip-max-width: 200px !default;
525 | //** Tooltip text color
526 | $tooltip-color: #fff !default;
527 | //** Tooltip background color
528 | $tooltip-bg: #000 !default;
529 | $tooltip-opacity: .9 !default;
530 |
531 | //** Tooltip arrow width
532 | $tooltip-arrow-width: 5px !default;
533 | //** Tooltip arrow color
534 | $tooltip-arrow-color: $tooltip-bg !default;
535 |
536 |
537 | //== Popovers
538 | //
539 | //##
540 |
541 | //** Popover body background color
542 | $popover-bg: #fff !default;
543 | //** Popover maximum width
544 | $popover-max-width: 276px !default;
545 | //** Popover border color
546 | $popover-border-color: rgba(0,0,0,.2) !default;
547 | //** Popover fallback border color
548 | $popover-fallback-border-color: #ccc !default;
549 |
550 | //** Popover title background color
551 | $popover-title-bg: darken($popover-bg, 3%) !default;
552 |
553 | //** Popover arrow width
554 | $popover-arrow-width: 10px !default;
555 | //** Popover arrow color
556 | $popover-arrow-color: $popover-bg !default;
557 |
558 | //** Popover outer arrow width
559 | $popover-arrow-outer-width: ($popover-arrow-width + 1) !default;
560 | //** Popover outer arrow color
561 | $popover-arrow-outer-color: fade_in($popover-border-color, 0.05) !default;
562 | //** Popover outer arrow fallback color
563 | $popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%) !default;
564 |
565 |
566 | //== Labels
567 | //
568 | //##
569 |
570 | //** Default label background color
571 | $label-default-bg: $gray-light !default;
572 | //** Primary label background color
573 | $label-primary-bg: $brand-primary !default;
574 | //** Success label background color
575 | $label-success-bg: $brand-success !default;
576 | //** Info label background color
577 | $label-info-bg: $brand-info !default;
578 | //** Warning label background color
579 | $label-warning-bg: $brand-warning !default;
580 | //** Danger label background color
581 | $label-danger-bg: $brand-danger !default;
582 |
583 | //** Default label text color
584 | $label-color: #fff !default;
585 | //** Default text color of a linked label
586 | $label-link-hover-color: #fff !default;
587 |
588 |
589 | //== Modals
590 | //
591 | //##
592 |
593 | //** Padding applied to the modal body
594 | $modal-inner-padding: 15px !default;
595 |
596 | //** Padding applied to the modal title
597 | $modal-title-padding: 15px !default;
598 | //** Modal title line-height
599 | $modal-title-line-height: $line-height-base !default;
600 |
601 | //** Background color of modal content area
602 | $modal-content-bg: #fff !default;
603 | //** Modal content border color
604 | $modal-content-border-color: rgba(0,0,0,.2) !default;
605 | //** Modal content border color **for IE8**
606 | $modal-content-fallback-border-color: #999 !default;
607 |
608 | //** Modal backdrop background color
609 | $modal-backdrop-bg: #000 !default;
610 | //** Modal backdrop opacity
611 | $modal-backdrop-opacity: .5 !default;
612 | //** Modal header border color
613 | $modal-header-border-color: #e5e5e5 !default;
614 | //** Modal footer border color
615 | $modal-footer-border-color: $modal-header-border-color !default;
616 |
617 | $modal-lg: 900px !default;
618 | $modal-md: 600px !default;
619 | $modal-sm: 300px !default;
620 |
621 |
622 | //== Alerts
623 | //
624 | //## Define alert colors, border radius, and padding.
625 |
626 | $alert-padding: 15px !default;
627 | $alert-border-radius: $border-radius-base !default;
628 | $alert-link-font-weight: bold !default;
629 |
630 | $alert-success-bg: $state-success-bg !default;
631 | $alert-success-text: $state-success-text !default;
632 | $alert-success-border: $state-success-border !default;
633 |
634 | $alert-info-bg: $state-info-bg !default;
635 | $alert-info-text: $state-info-text !default;
636 | $alert-info-border: $state-info-border !default;
637 |
638 | $alert-warning-bg: $state-warning-bg !default;
639 | $alert-warning-text: $state-warning-text !default;
640 | $alert-warning-border: $state-warning-border !default;
641 |
642 | $alert-danger-bg: $state-danger-bg !default;
643 | $alert-danger-text: $state-danger-text !default;
644 | $alert-danger-border: $state-danger-border !default;
645 |
646 |
647 | //== Progress bars
648 | //
649 | //##
650 |
651 | //** Background color of the whole progress component
652 | $progress-bg: #f5f5f5 !default;
653 | //** Progress bar text color
654 | $progress-bar-color: #fff !default;
655 | //** Variable for setting rounded corners on progress bar.
656 | $progress-border-radius: $border-radius-base !default;
657 |
658 | //** Default progress bar color
659 | $progress-bar-bg: $brand-primary !default;
660 | //** Success progress bar color
661 | $progress-bar-success-bg: $brand-success !default;
662 | //** Warning progress bar color
663 | $progress-bar-warning-bg: $brand-warning !default;
664 | //** Danger progress bar color
665 | $progress-bar-danger-bg: $brand-danger !default;
666 | //** Info progress bar color
667 | $progress-bar-info-bg: $brand-info !default;
668 |
669 |
670 | //== List group
671 | //
672 | //##
673 |
674 | //** Background color on `.list-group-item`
675 | $list-group-bg: #fff !default;
676 | //** `.list-group-item` border color
677 | $list-group-border: #ddd !default;
678 | //** List group border radius
679 | $list-group-border-radius: $border-radius-base !default;
680 |
681 | //** Background color of single list items on hover
682 | $list-group-hover-bg: #f5f5f5 !default;
683 | //** Text color of active list items
684 | $list-group-active-color: $component-active-color !default;
685 | //** Background color of active list items
686 | $list-group-active-bg: $component-active-bg !default;
687 | //** Border color of active list elements
688 | $list-group-active-border: $list-group-active-bg !default;
689 | //** Text color for content within active list items
690 | $list-group-active-text-color: lighten($list-group-active-bg, 40%) !default;
691 |
692 | //** Text color of disabled list items
693 | $list-group-disabled-color: $gray-light !default;
694 | //** Background color of disabled list items
695 | $list-group-disabled-bg: $gray-lighter !default;
696 | //** Text color for content within disabled list items
697 | $list-group-disabled-text-color: $list-group-disabled-color !default;
698 |
699 | $list-group-link-color: #555 !default;
700 | $list-group-link-hover-color: $list-group-link-color !default;
701 | $list-group-link-heading-color: #333 !default;
702 |
703 |
704 | //== Panels
705 | //
706 | //##
707 |
708 | $panel-bg: #fff !default;
709 | $panel-body-padding: 15px !default;
710 | $panel-heading-padding: 10px 15px !default;
711 | $panel-footer-padding: $panel-heading-padding !default;
712 | $panel-border-radius: $border-radius-base !default;
713 |
714 | //** Border color for elements within panels
715 | $panel-inner-border: #ddd !default;
716 | $panel-footer-bg: #f5f5f5 !default;
717 |
718 | $panel-default-text: $gray-dark !default;
719 | $panel-default-border: #ddd !default;
720 | $panel-default-heading-bg: #f5f5f5 !default;
721 |
722 | $panel-primary-text: #fff !default;
723 | $panel-primary-border: $brand-primary !default;
724 | $panel-primary-heading-bg: $brand-primary !default;
725 |
726 | $panel-success-text: $state-success-text !default;
727 | $panel-success-border: $state-success-border !default;
728 | $panel-success-heading-bg: $state-success-bg !default;
729 |
730 | $panel-info-text: $state-info-text !default;
731 | $panel-info-border: $state-info-border !default;
732 | $panel-info-heading-bg: $state-info-bg !default;
733 |
734 | $panel-warning-text: $state-warning-text !default;
735 | $panel-warning-border: $state-warning-border !default;
736 | $panel-warning-heading-bg: $state-warning-bg !default;
737 |
738 | $panel-danger-text: $state-danger-text !default;
739 | $panel-danger-border: $state-danger-border !default;
740 | $panel-danger-heading-bg: $state-danger-bg !default;
741 |
742 |
743 | //== Thumbnails
744 | //
745 | //##
746 |
747 | //** Padding around the thumbnail image
748 | $thumbnail-padding: 4px !default;
749 | //** Thumbnail background color
750 | $thumbnail-bg: $body-bg !default;
751 | //** Thumbnail border color
752 | $thumbnail-border: #ddd !default;
753 | //** Thumbnail border radius
754 | $thumbnail-border-radius: $border-radius-base !default;
755 |
756 | //** Custom text color for thumbnail captions
757 | $thumbnail-caption-color: $text-color !default;
758 | //** Padding around the thumbnail caption
759 | $thumbnail-caption-padding: 9px !default;
760 |
761 |
762 | //== Wells
763 | //
764 | //##
765 |
766 | $well-bg: #f5f5f5 !default;
767 | $well-border: darken($well-bg, 7%) !default;
768 |
769 |
770 | //== Badges
771 | //
772 | //##
773 |
774 | $badge-color: #fff !default;
775 | //** Linked badge text color on hover
776 | $badge-link-hover-color: #fff !default;
777 | $badge-bg: $gray-light !default;
778 |
779 | //** Badge text color in active nav link
780 | $badge-active-color: $link-color !default;
781 | //** Badge background color in active nav link
782 | $badge-active-bg: #fff !default;
783 |
784 | $badge-font-weight: bold !default;
785 | $badge-line-height: 1 !default;
786 | $badge-border-radius: 10px !default;
787 |
788 |
789 | //== Breadcrumbs
790 | //
791 | //##
792 |
793 | $breadcrumb-padding-vertical: 8px !default;
794 | $breadcrumb-padding-horizontal: 15px !default;
795 | //** Breadcrumb background color
796 | $breadcrumb-bg: #f5f5f5 !default;
797 | //** Breadcrumb text color
798 | $breadcrumb-color: #ccc !default;
799 | //** Text color of current page in the breadcrumb
800 | $breadcrumb-active-color: $gray-light !default;
801 | //** Textual separator for between breadcrumb elements
802 | $breadcrumb-separator: "/" !default;
803 |
804 |
805 | //== Carousel
806 | //
807 | //##
808 |
809 | $carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6) !default;
810 |
811 | $carousel-control-color: #fff !default;
812 | $carousel-control-width: 15% !default;
813 | $carousel-control-opacity: .5 !default;
814 | $carousel-control-font-size: 20px !default;
815 |
816 | $carousel-indicator-active-bg: #fff !default;
817 | $carousel-indicator-border-color: #fff !default;
818 |
819 | $carousel-caption-color: #fff !default;
820 |
821 |
822 | //== Close
823 | //
824 | //##
825 |
826 | $close-font-weight: bold !default;
827 | $close-color: #000 !default;
828 | $close-text-shadow: 0 1px 0 #fff !default;
829 |
830 |
831 | //== Code
832 | //
833 | //##
834 |
835 | $code-color: #c7254e !default;
836 | $code-bg: #f9f2f4 !default;
837 |
838 | $kbd-color: #fff !default;
839 | $kbd-bg: #333 !default;
840 |
841 | $pre-bg: #f5f5f5 !default;
842 | $pre-color: $gray-dark !default;
843 | $pre-border-color: #ccc !default;
844 | $pre-scrollable-max-height: 340px !default;
845 |
846 |
847 | //== Type
848 | //
849 | //##
850 |
851 | //** Horizontal offset for forms and lists.
852 | $component-offset-horizontal: 180px !default;
853 | //** Text muted color
854 | $text-muted: $gray-light !default;
855 | //** Abbreviations and acronyms border color
856 | $abbr-border-color: $gray-light !default;
857 | //** Headings small color
858 | $headings-small-color: $gray-light !default;
859 | //** Blockquote small color
860 | $blockquote-small-color: $gray-light !default;
861 | //** Blockquote font size
862 | $blockquote-font-size: ($font-size-base * 1.25) !default;
863 | //** Blockquote border color
864 | $blockquote-border-color: $gray-lighter !default;
865 | //** Page header border color
866 | $page-header-border-color: $gray-lighter !default;
867 | //** Width of horizontal description list titles
868 | $dl-horizontal-offset: $component-offset-horizontal !default;
869 | //** Horizontal line color.
870 | $hr-border: $gray-lighter !default;
871 |
--------------------------------------------------------------------------------
/client/app/styles/main.scss:
--------------------------------------------------------------------------------
1 | //$icon-font-path: "../bower_components/bootstrap-sass-official/assets/fonts/bootstrap/";
2 |
3 | @import "variables.scss";
4 |
5 |
6 | // bower:scss
7 | @import "bootstrap-sass-official/assets/stylesheets/_bootstrap.scss";
8 | // endbower
9 |
10 | @import "partials/signup";
11 |
12 |
13 | .browsehappy {
14 | margin: 0.2em 0;
15 | background: #ccc;
16 | color: #000;
17 | padding: 0.2em 0;
18 | }
19 |
20 | /* Space out content a bit */
21 | body {
22 | padding-top: 20px;
23 | padding-bottom: 20px;
24 | }
25 |
26 | /* Everything but the jumbotron gets side spacing for mobile first views */
27 | .header,
28 | .marketing,
29 | .footer {
30 | padding-left: 15px;
31 | padding-right: 15px;
32 | }
33 |
34 | /* Custom page header */
35 | .header {
36 | border-bottom: 1px solid #e5e5e5;
37 |
38 | /* Make the masthead heading the same height as the navigation */
39 | h3 {
40 | margin-top: 0;
41 | margin-bottom: 0;
42 | line-height: 40px;
43 | padding-bottom: 19px;
44 | }
45 | }
46 |
47 | /* Custom page footer */
48 | .footer {
49 | padding-top: 19px;
50 | color: #777;
51 | border-top: 1px solid #e5e5e5;
52 | }
53 |
54 | .container-narrow > hr {
55 | margin: 30px 0;
56 | }
57 |
58 | /* Main marketing message and sign up button */
59 | .jumbotron {
60 | text-align: center;
61 | border-bottom: 1px solid #e5e5e5;
62 |
63 | .btn {
64 | font-size: 21px;
65 | padding: 14px 24px;
66 | }
67 | }
68 |
69 | /* Supporting marketing content */
70 | .marketing {
71 | margin: 40px 0;
72 |
73 | p + h4 {
74 | margin-top: 28px;
75 | }
76 | }
77 |
78 | /* Responsive: Portrait tablets and up */
79 | @media screen and (min-width: 768px) {
80 | .container {
81 | max-width: 730px;
82 | }
83 |
84 | /* Remove the padding we set earlier */
85 | .header,
86 | .marketing,
87 | .footer {
88 | padding-left: 0;
89 | padding-right: 0;
90 | }
91 | /* Space out the masthead */
92 | .header {
93 | margin-bottom: 30px;
94 | }
95 | /* Remove the bottom border on the jumbotron for visual effect */
96 | .jumbotron {
97 | border-bottom: 0;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/client/app/styles/partials/_signup.scss:
--------------------------------------------------------------------------------
1 | .signup-container{
2 | margin-top: 5em;
3 | }
4 |
5 | .signup-header{
6 | text-align: center;
7 | color: $gray-light;
8 | margin-bottom: 1em;
9 | }
--------------------------------------------------------------------------------
/client/app/views/main.html:
--------------------------------------------------------------------------------
1 | Step 1, Authorise this app to use your Dropbox Account
2 |
3 | Step 2 , Upload it
4 | {{errorMsg}}
5 |
6 |
7 |
8 | {{($index + 1) + '.'}}
9 |
10 |
11 |
12 | {{progress[$index]}}%
13 |
14 |
16 | {{f.name}} - size: {{f.size}}B - type: {{f.type}}
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/client/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "angular": "^1.3.0",
6 | "json3": "^3.3.0",
7 | "es5-shim": "^4.0.0",
8 | "bootstrap-sass-official": "^3.2.0",
9 | "angular-animate": "^1.3.0",
10 | "angular-aria": "^1.3.0",
11 | "angular-cookies": "^1.3.0",
12 | "angular-messages": "^1.3.0",
13 | "angular-resource": "^1.3.0",
14 | "angular-route": "^1.3.0",
15 | "angular-sanitize": "^1.3.0",
16 | "angular-touch": "^1.3.0",
17 | "ng-file-upload": "~2.0.4"
18 | },
19 | "devDependencies": {
20 | "angular-mocks": "~1.3.0",
21 | "angular-scenario": "~1.3.0"
22 | },
23 | "appPath": "app"
24 | }
25 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.0.0",
4 | "dependencies": {},
5 | "devDependencies": {
6 | "grunt": "^0.4.1",
7 | "grunt-autoprefixer": "^0.7.3",
8 | "grunt-concurrent": "^0.5.0",
9 | "grunt-contrib-clean": "^0.5.0",
10 | "grunt-contrib-compass": "^0.7.2",
11 | "grunt-contrib-concat": "^0.4.0",
12 | "grunt-contrib-connect": "^0.7.1",
13 | "grunt-contrib-copy": "^0.5.0",
14 | "grunt-contrib-cssmin": "^0.9.0",
15 | "grunt-contrib-htmlmin": "^0.3.0",
16 | "grunt-contrib-imagemin": "^0.8.1",
17 | "grunt-contrib-jshint": "^0.10.0",
18 | "grunt-contrib-uglify": "^0.4.0",
19 | "grunt-contrib-watch": "^0.6.1",
20 | "grunt-filerev": "^0.2.1",
21 | "grunt-google-cdn": "^0.4.0",
22 | "grunt-newer": "^0.7.0",
23 | "grunt-ng-annotate": "^0.4.0",
24 | "grunt-svgmin": "^0.4.0",
25 | "grunt-usemin": "^2.1.1",
26 | "grunt-wiredep": "^1.7.0",
27 | "jshint-stylish": "^0.2.0",
28 | "load-grunt-tasks": "^0.4.0",
29 | "time-grunt": "^0.3.1",
30 | "karma-jasmine": "~0.3.2",
31 | "grunt-karma": "~0.9.0",
32 | "karma-phantomjs-launcher": "~0.1.4"
33 | },
34 | "engines": {
35 | "node": ">=0.10.0"
36 | },
37 | "scripts": {
38 | "test": "grunt test"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/client/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals": {
22 | "after": false,
23 | "afterEach": false,
24 | "angular": false,
25 | "before": false,
26 | "beforeEach": false,
27 | "browser": false,
28 | "describe": false,
29 | "expect": false,
30 | "inject": false,
31 | "it": false,
32 | "jasmine": false,
33 | "spyOn": false
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/client/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.12/config/configuration-file.html
3 | // Generated on 2014-11-30 using
4 | // generator-karma 0.8.3
5 |
6 | module.exports = function(config) {
7 | 'use strict';
8 |
9 | config.set({
10 | // enable / disable watching file and executing tests whenever any file changes
11 | autoWatch: true,
12 |
13 | // base path, that will be used to resolve files and exclude
14 | basePath: '../',
15 |
16 | // testing framework to use (jasmine/mocha/qunit/...)
17 | frameworks: ['jasmine'],
18 |
19 | // list of files / patterns to load in the browser
20 | files: [
21 | 'bower_components/angular/angular.js',
22 | 'bower_components/angular-mocks/angular-mocks.js',
23 | 'bower_components/angular-animate/angular-animate.js',
24 | 'bower_components/angular-aria/angular-aria.js',
25 | 'bower_components/angular-cookies/angular-cookies.js',
26 | 'bower_components/angular-messages/angular-messages.js',
27 | 'bower_components/angular-resource/angular-resource.js',
28 | 'bower_components/angular-route/angular-route.js',
29 | 'bower_components/angular-sanitize/angular-sanitize.js',
30 | 'bower_components/angular-touch/angular-touch.js',
31 | 'app/scripts/**/*.js',
32 | 'test/mock/**/*.js',
33 | 'test/spec/**/*.js'
34 | ],
35 |
36 | // list of files / patterns to exclude
37 | exclude: [],
38 |
39 | // web server port
40 | port: 8080,
41 |
42 | // Start these browsers, currently available:
43 | // - Chrome
44 | // - ChromeCanary
45 | // - Firefox
46 | // - Opera
47 | // - Safari (only Mac)
48 | // - PhantomJS
49 | // - IE (only Windows)
50 | browsers: [
51 | 'PhantomJS'
52 | ],
53 |
54 | // Which plugins to enable
55 | plugins: [
56 | 'karma-phantomjs-launcher',
57 | 'karma-jasmine'
58 | ],
59 |
60 | // Continuous Integration mode
61 | // if true, it capture browsers, run tests and exit
62 | singleRun: false,
63 |
64 | colors: true,
65 |
66 | // level of logging
67 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
68 | logLevel: config.LOG_INFO,
69 |
70 | // Uncomment the following lines if you are using grunt's server to run the tests
71 | // proxies: {
72 | // '/': 'http://localhost:9000/'
73 | // },
74 | // URL root prevent conflicts with the site root
75 | // urlRoot: '_karma_'
76 | });
77 | };
78 |
--------------------------------------------------------------------------------
/client/test/spec/controllers/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Controller: MainCtrl', function () {
4 |
5 | // load the controller's module
6 | beforeEach(module('clientApp'));
7 |
8 | var MainCtrl,
9 | scope;
10 |
11 | // Initialize the controller and a mock scope
12 | beforeEach(inject(function ($controller, $rootScope) {
13 | scope = $rootScope.$new();
14 | MainCtrl = $controller('MainCtrl', {
15 | $scope: scope
16 | });
17 | }));
18 |
19 | it('should attach a list of awesomeThings to the scope', function () {
20 | expect(scope.awesomeThings.length).toBe(3);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/lambda_file_sync.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aidancasey/S3DropboxFileSync/6561a72cf09304c32b99f29a5feb64d37b539651/lambda_file_sync.jpg
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var path = require('path');
3 | var favicon = require('serve-favicon');
4 | var logger = require('morgan');
5 | var cookieParser = require('cookie-parser');
6 | var bodyParser = require('body-parser');
7 |
8 | var routes = require('./routes');
9 | var busboy = require('connect-busboy');
10 | //var index = require('./routes/index');
11 |
12 |
13 | var app = express();
14 | app.use(busboy());
15 |
16 |
17 | /**
18 | * Route Imports
19 | */
20 |
21 | require('./routes')(app);
22 |
23 | app.use(logger('dev'));
24 | app.use(bodyParser.json());
25 | app.use(bodyParser.urlencoded({ extended: false }));
26 | app.use(cookieParser());
27 | app.set('view engine', 'html');
28 |
29 | /**
30 | * Development Settings
31 | */
32 |
33 | if (app.get('env') === 'development') {
34 | // This will change in production since we'll be using the dist folder
35 | app.use(express.static(path.join(__dirname, '../client')));
36 | // This covers serving up the index page
37 | app.use(express.static(path.join(__dirname, '../client/.tmp')));
38 | app.use(express.static(path.join(__dirname, '../client/app')));
39 |
40 | // Error Handling
41 | app.use(function (err, req, res, next) {
42 | res.status(err.status || 500);
43 | res.render('error', {
44 | message: err.message,
45 | error: err
46 | });
47 | });
48 | }
49 |
50 | /**
51 | * Production Settings
52 | */
53 | if (app.get('env') === 'production') {
54 |
55 | // changes it to use the optimized version for production
56 | app.use(express.static(path.join(__dirname, '/dist')));
57 |
58 | // production error handler
59 | // no stacktraces leaked to user
60 | app.use(function (err, req, res, next) {
61 | res.status(err.status || 500);
62 | res.render('error', {
63 | message: err.message,
64 | error: {}
65 | });
66 | });
67 | }
68 |
69 | /**
70 | * Routes
71 | */
72 | //app.use('/index', index);
73 |
74 | module.exports = app;
75 |
--------------------------------------------------------------------------------
/server/aws/aws.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var config = require('../config/config');
4 | var Q = require('q');
5 | var AWS = require('aws-sdk');
6 | AWS.config.region = 'eu-west-1';
7 |
8 | //AWS SDK relies on these env settings getting set,
9 |
10 | process.env['AWS_ACCESS_KEY_ID'] = config.aws.accessKey;
11 | process.env['AWS_SECRET_ACCESS_KEY'] = config.aws.secretKey;
12 |
13 | exports.storeToken = function (token) {
14 |
15 | var deferred = Q.defer();
16 | var s3bucket = new AWS.S3({params: {Bucket: config.aws.tokenBucket}});
17 | var data = {Key: 'token', Body: token };
18 |
19 | return s3bucket.putObject(data, function (error, data) {
20 | if (error) {
21 | deferred.reject(error);
22 | } else {
23 | deferred.resolve(token);
24 | }
25 | });
26 | };
27 |
28 | exports.getFile = function (name) {
29 | var deferred = Q.defer();
30 | var params = {Bucket: config.aws.fileUploadBucket, Key: name}
31 | var s3 = new AWS.S3();
32 |
33 | s3.getObject(params, function (err, data) {
34 | if (err) {
35 | console.log(err, err.stack);
36 | deferred.reject(err);
37 | }
38 | else {
39 | deferred.resolve(data.Body)
40 | }
41 | });
42 | return deferred.promise;
43 | };
44 |
45 |
46 | exports.postToS3 = function (buffer, filename, mimetype) {
47 | var s3bucket = new AWS.S3({params: {Bucket: config.aws.fileUploadBucket}}),
48 | deferred = Q.defer(),
49 | dataToPost = {
50 | Bucket: config.aws.fileUploadBucket,
51 | Key: filename,
52 | ACL: 'public-read',
53 | ContentType: mimetype
54 | };
55 |
56 | console.log(dataToPost);
57 |
58 | dataToPost.Body = buffer;
59 | s3bucket.putObject(dataToPost, function (err, data) {
60 | if (err) {
61 | deferred.reject(err.message);
62 | } else {
63 | deferred.resolve(data);
64 | }
65 | });
66 | return deferred.promise;
67 | };
68 |
69 | exports.getSignedUrl = function (fileName) {
70 | var s3 = new AWS.S3();
71 | var params = {Bucket: config.aws.fileUploadBucket, Key : fileName, Expires :20};
72 | return s3.getSignedUrl('getObject', params);
73 | };
--------------------------------------------------------------------------------
/server/aws/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(app) {
4 | var router = require('express').Router(),
5 | aws = require('./aws');
6 |
7 |
8 | router.get('/viewfile/', function(req,res){
9 | var fileName = req.query.file;
10 | var url =aws.getSignedUrl(fileName);
11 | res.send(url);
12 | });
13 |
14 | router.post('/', function (req, res) {
15 |
16 | req.pipe(req.busboy);
17 | req.busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
18 | if (!filename) {
19 | // If filename is not truthy it means there's no file
20 | return;
21 | }
22 | // Create the initial array containing the stream's chunks
23 | file.fileRead = [];
24 |
25 | file.on('data', function (chunk) {
26 | this.fileRead.push(chunk);
27 | });
28 |
29 | file.on('error', function (err) {
30 | console.log('Error while buffering the stream: ', err);
31 | res.send(error);
32 | });
33 |
34 | file.on('end', function () {
35 | var finalBuffer = Buffer.concat(this.fileRead);
36 |
37 | aws.postToS3(finalBuffer, filename, mimetype)
38 | .then(function (data) {
39 | res.send(200)
40 | })
41 | .catch(function (error) {
42 | console.log(error);
43 | res.send(error);
44 | })
45 | });
46 | });
47 | });
48 |
49 |
50 |
51 |
52 |
53 | return router;
54 | };
55 |
56 |
57 |
--------------------------------------------------------------------------------
/server/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | var debug = require('debug')('server');
3 | var app = require('../app');
4 |
5 | app.set('port', process.env.PORT || 3000);
6 |
7 | var server = app.listen(app.get('port'), function() {
8 | debug('Express server listening on port ' + server.address().port);
9 | });
10 |
--------------------------------------------------------------------------------
/server/config/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 |
5 | /**
6 | * Load environment configuration
7 | */
8 | module.exports = _.merge(
9 | require('./env/all.js'),
10 | require('./env/' + process.env.NODE_ENV + '.js') || {});
--------------------------------------------------------------------------------
/server/config/env/all.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | dropbox :{appKey : "ADD YOUR OWN KEY",
5 | appSecret : "ADD YOUR OWN SECRET"},
6 |
7 | aws:{ accessKey:"ADD YOUR OWN KEY" ,
8 | secretKey : "ADD YOUR OWN SECRET",
9 | tokenBucket :"NAME OF THE S3 BUCKET TO STORE BEARER TOKEN",
10 | fileUploadBucket :"NAME OF THE S3 BUCKET TO STORE BEARER TOKEN",
11 | }
12 | };
--------------------------------------------------------------------------------
/server/dropbox/dropbox.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var crypto = require('crypto'),
4 | url = require('url'),
5 | request = require('request'),
6 | Dropbox = require("dropbox"),
7 | Q = require('q');
8 |
9 | var config = require('../config/config');
10 | var APP_KEY = config.dropbox.appKey;
11 | var APP_SECRET = config.dropbox.appSecret;
12 |
13 | function generateCSRFToken() {
14 | return crypto.randomBytes(18).toString('base64')
15 | .replace(/\//g, '-').replace(/\+/g, '_');
16 | }
17 |
18 | function generateRedirectURI(req) {
19 | return url.format({
20 | protocol: req.protocol,
21 | host: req.headers.host,
22 | pathname: req.app.path() + '/api/dropbox/callback'
23 | });
24 | }
25 |
26 | exports.authenticate = function (req, res) {
27 | var csrfToken = generateCSRFToken();
28 | res.cookie('csrf', csrfToken);
29 | res.redirect(url.format({
30 | protocol: 'https',
31 | hostname: 'www.dropbox.com',
32 | pathname: '1/oauth2/authorize',
33 | query: {
34 | client_id: APP_KEY,
35 | response_type: 'code',
36 | state: csrfToken,
37 | redirect_uri: generateRedirectURI(req)
38 | }
39 | }));
40 | };
41 |
42 | exports.getBearerToken = function (req, res) {
43 | var deferred = Q.defer();
44 |
45 | if (req.query.error) {
46 | deferred.resolve(req.query.error);
47 | }
48 |
49 | Q.fcall(request.post, 'https://api.dropbox.com/1/oauth2/token', {
50 | form: {
51 | code: req.query.code,
52 | grant_type: 'authorization_code',
53 | redirect_uri: generateRedirectURI(req)
54 | },
55 | auth: {
56 | user: APP_KEY,
57 | pass: APP_SECRET
58 | }}, function (error, response, body) {
59 | if (error) {
60 | console.log(error)
61 | deferred.reject(error);
62 | } else {
63 | var data = JSON.parse(body);
64 | if (data.error) {
65 | deferred.reject(data.error);
66 | }
67 | deferred.resolve(data.access_token);
68 | }
69 | })
70 | .done();
71 | return deferred.promise;
72 | }
73 |
--------------------------------------------------------------------------------
/server/dropbox/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(app) {
4 | var router = require('express').Router(),
5 | dropbox = require('./dropbox');
6 |
7 |
8 |
9 | router.get('/authorise', function(req, res, next) {
10 | dropbox.authenticate(req, res);
11 | });
12 |
13 |
14 | router.get('/callback', function(req, res, next) {
15 | dropbox.getBearerToken(req, res)
16 | .then(function (token) {
17 | aws.storeToken(token)
18 | })
19 | .then(function () {
20 | res.send(200);
21 | })
22 | .catch(function (error) {
23 | console.log(error);
24 | res.send(error);
25 | })
26 | });
27 |
28 |
29 | return router;
30 | };
31 |
32 |
33 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "NODE_ENV=production nodemon ./bin/www",
7 | "test": "NODE_ENV=development nodemon ./bin/www"
8 | },
9 | "dependencies": {
10 | "express": "~4.9.0",
11 | "body-parser": "~1.8.1",
12 | "cookie-parser": "~1.3.3",
13 | "morgan": "~1.3.0",
14 | "serve-favicon": "~2.1.3",
15 | "debug": "~2.0.0",
16 | "crypto": "0.0.3",
17 | "url": "~0.10.1",
18 | "request": "~2.49.0",
19 | "dropbox": "~0.10.3",
20 | "path": "~0.4.9",
21 | "q": "~1.1.2",
22 | "lodash": "~2.4.1",
23 | "aws-sdk": "~2.0.31",
24 | "connect-busboy": "0.0.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/routes.js:
--------------------------------------------------------------------------------
1 | module.exports = function (app) {
2 | var dropbox = require('./dropbox');
3 | var aws = require('./aws');
4 |
5 | app.use('/api/dropbox', require('./dropbox')(app));
6 | app.use('/api/upload', require('./aws')(app));
7 |
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/server/routes/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | /* GET home page. */
5 | router.get('/', function (req, res) {
6 | res.render('index', { title: 'Express' });
7 | });
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------