├── .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 | List of links on {{page}} this is the place for me 5 | 6 | 7 |

List of links on {{page}}

8 | 9 | 14 |

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 | ![ScreenShot](lambda_file_sync.jpg) 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 | # 20 | # Header set Access-Control-Allow-Origin "*" 21 | # 22 | 23 | # ------------------------------------------------------------------------------ 24 | # | CORS-enabled images | 25 | # ------------------------------------------------------------------------------ 26 | 27 | # Send the CORS header for images when browsers request it. 28 | # https://developer.mozilla.org/en/CORS_Enabled_Image 29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 31 | 32 | 33 | 34 | 35 | SetEnvIf Origin ":" IS_CORS 36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 37 | 38 | 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | # | Web fonts access | 43 | # ------------------------------------------------------------------------------ 44 | 45 | # Allow access from all domains for web fonts 46 | 47 | 48 | 49 | Header set Access-Control-Allow-Origin "*" 50 | 51 | 52 | 53 | 54 | # ############################################################################## 55 | # # ERRORS # 56 | # ############################################################################## 57 | 58 | # ------------------------------------------------------------------------------ 59 | # | 404 error prevention for non-existing redirected folders | 60 | # ------------------------------------------------------------------------------ 61 | 62 | # Prevent Apache from returning a 404 error for a rewrite if a directory 63 | # with the same name does not exist. 64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews 65 | # http://www.webmasterworld.com/apache/3808792.htm 66 | 67 | Options -MultiViews 68 | 69 | # ------------------------------------------------------------------------------ 70 | # | Custom error messages / pages | 71 | # ------------------------------------------------------------------------------ 72 | 73 | # You can customize what Apache returns to the client in case of an error (see 74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: 75 | 76 | ErrorDocument 404 /404.html 77 | 78 | 79 | # ############################################################################## 80 | # # INTERNET EXPLORER # 81 | # ############################################################################## 82 | 83 | # ------------------------------------------------------------------------------ 84 | # | Better website experience | 85 | # ------------------------------------------------------------------------------ 86 | 87 | # Force IE to render pages in the highest available mode in the various 88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. 89 | 90 | 91 | Header set X-UA-Compatible "IE=edge" 92 | # `mod_headers` can't match based on the content-type, however, we only 93 | # want to send this header for HTML pages and not for the other resources 94 | 95 | Header unset X-UA-Compatible 96 | 97 | 98 | 99 | # ------------------------------------------------------------------------------ 100 | # | Cookie setting from iframes | 101 | # ------------------------------------------------------------------------------ 102 | 103 | # Allow cookies to be set from iframes in IE. 104 | 105 | # 106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 107 | # 108 | 109 | # ------------------------------------------------------------------------------ 110 | # | Screen flicker | 111 | # ------------------------------------------------------------------------------ 112 | 113 | # Stop screen flicker in IE on CSS rollovers (this only works in 114 | # combination with the `ExpiresByType` directives for images from below). 115 | 116 | # BrowserMatch "MSIE" brokenvary=1 117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 118 | # BrowserMatch "Opera" !brokenvary 119 | # SetEnvIf brokenvary 1 force-no-vary 120 | 121 | 122 | # ############################################################################## 123 | # # MIME TYPES AND ENCODING # 124 | # ############################################################################## 125 | 126 | # ------------------------------------------------------------------------------ 127 | # | Proper MIME types for all files | 128 | # ------------------------------------------------------------------------------ 129 | 130 | 131 | 132 | # Audio 133 | AddType audio/mp4 m4a f4a f4b 134 | AddType audio/ogg oga ogg 135 | 136 | # JavaScript 137 | # Normalize to standard type (it's sniffed in IE anyways): 138 | # http://tools.ietf.org/html/rfc4329#section-7.2 139 | AddType application/javascript js jsonp 140 | AddType application/json json 141 | 142 | # Video 143 | AddType video/mp4 mp4 m4v f4v f4p 144 | AddType video/ogg ogv 145 | AddType video/webm webm 146 | AddType video/x-flv flv 147 | 148 | # Web fonts 149 | AddType application/font-woff woff 150 | AddType application/vnd.ms-fontobject eot 151 | 152 | # Browsers usually ignore the font MIME types and sniff the content, 153 | # however, Chrome shows a warning if other MIME types are used for the 154 | # following fonts. 155 | AddType application/x-font-ttf ttc ttf 156 | AddType font/opentype otf 157 | 158 | # Make SVGZ fonts work on iPad: 159 | # https://twitter.com/FontSquirrel/status/14855840545 160 | AddType image/svg+xml svg svgz 161 | AddEncoding gzip svgz 162 | 163 | # Other 164 | AddType application/octet-stream safariextz 165 | AddType application/x-chrome-extension crx 166 | AddType application/x-opera-extension oex 167 | AddType application/x-shockwave-flash swf 168 | AddType application/x-web-app-manifest+json webapp 169 | AddType application/x-xpinstall xpi 170 | AddType application/xml atom rdf rss xml 171 | AddType image/webp webp 172 | AddType image/x-icon ico 173 | AddType text/cache-manifest appcache manifest 174 | AddType text/vtt vtt 175 | AddType text/x-component htc 176 | AddType text/x-vcard vcf 177 | 178 | 179 | 180 | # ------------------------------------------------------------------------------ 181 | # | UTF-8 encoding | 182 | # ------------------------------------------------------------------------------ 183 | 184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`. 185 | AddDefaultCharset utf-8 186 | 187 | # Force UTF-8 for certain file formats. 188 | 189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml 190 | 191 | 192 | 193 | # ############################################################################## 194 | # # URL REWRITES # 195 | # ############################################################################## 196 | 197 | # ------------------------------------------------------------------------------ 198 | # | Rewrite engine | 199 | # ------------------------------------------------------------------------------ 200 | 201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is 202 | # necessary for the following directives to work. 203 | 204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to 205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the 206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks 207 | 208 | # Also, some cloud hosting services require `RewriteBase` to be set: 209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site 210 | 211 | 212 | Options +FollowSymlinks 213 | # Options +SymLinksIfOwnerMatch 214 | RewriteEngine On 215 | # RewriteBase / 216 | 217 | 218 | # ------------------------------------------------------------------------------ 219 | # | Suppressing / Forcing the "www." at the beginning of URLs | 220 | # ------------------------------------------------------------------------------ 221 | 222 | # The same content should never be available under two different URLs especially 223 | # not with and without "www." at the beginning. This can cause SEO problems 224 | # (duplicate content), therefore, you should choose one of the alternatives and 225 | # redirect the other one. 226 | 227 | # By default option 1 (no "www.") is activated: 228 | # http://no-www.org/faq.php?q=class_b 229 | 230 | # If you'd prefer to use option 2, just comment out all the lines from option 1 231 | # and uncomment the ones from option 2. 232 | 233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 234 | 235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 236 | 237 | # Option 1: rewrite www.example.com → example.com 238 | 239 | 240 | RewriteCond %{HTTPS} !=on 241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 243 | 244 | 245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 246 | 247 | # Option 2: rewrite example.com → www.example.com 248 | 249 | # Be aware that the following might not be a good idea if you use "real" 250 | # subdomains for certain parts of your website. 251 | 252 | # 253 | # RewriteCond %{HTTPS} !=on 254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 256 | # 257 | 258 | 259 | # ############################################################################## 260 | # # SECURITY # 261 | # ############################################################################## 262 | 263 | # ------------------------------------------------------------------------------ 264 | # | Content Security Policy (CSP) | 265 | # ------------------------------------------------------------------------------ 266 | 267 | # You can mitigate the risk of cross-site scripting and other content-injection 268 | # attacks by setting a Content Security Policy which whitelists trusted sources 269 | # of content for your site. 270 | 271 | # The example header below allows ONLY scripts that are loaded from the current 272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't 273 | # work as-is for your site! 274 | 275 | # To get all the details you'll need to craft a reasonable policy for your site, 276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or 277 | # see the specification: http://w3.org/TR/CSP). 278 | 279 | # 280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'" 281 | # 282 | # Header unset Content-Security-Policy 283 | # 284 | # 285 | 286 | # ------------------------------------------------------------------------------ 287 | # | File access | 288 | # ------------------------------------------------------------------------------ 289 | 290 | # Block access to directories without a default document. 291 | # Usually you should leave this uncommented because you shouldn't allow anyone 292 | # to surf through every directory on your server (which may includes rather 293 | # private places like the CMS's directories). 294 | 295 | 296 | Options -Indexes 297 | 298 | 299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 300 | 301 | # Block access to hidden files and directories. 302 | # This includes directories used by version control systems such as Git and SVN. 303 | 304 | 305 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 306 | RewriteCond %{SCRIPT_FILENAME} -f 307 | RewriteRule "(^|/)\." - [F] 308 | 309 | 310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 311 | 312 | # Block access to backup and source files. 313 | # These files may be left by some text editors and can pose a great security 314 | # danger when anyone has access to them. 315 | 316 | 317 | Order allow,deny 318 | Deny from all 319 | Satisfy All 320 | 321 | 322 | # ------------------------------------------------------------------------------ 323 | # | Secure Sockets Layer (SSL) | 324 | # ------------------------------------------------------------------------------ 325 | 326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: 327 | # prevent `https://www.example.com` when your certificate only allows 328 | # `https://secure.example.com`. 329 | 330 | # 331 | # RewriteCond %{SERVER_PORT} !^443 332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 333 | # 334 | 335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 336 | 337 | # Force client-side SSL redirection. 338 | 339 | # If a user types "example.com" in his browser, the above rule will redirect him 340 | # to the secure version of the site. That still leaves a window of opportunity 341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the 342 | # request. The following header ensures that browser will ONLY connect to your 343 | # server via HTTPS, regardless of what the users type in the address bar. 344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ 345 | 346 | # 347 | # Header set Strict-Transport-Security max-age=16070400; 348 | # 349 | 350 | # ------------------------------------------------------------------------------ 351 | # | Server software information | 352 | # ------------------------------------------------------------------------------ 353 | 354 | # Avoid displaying the exact Apache version number, the description of the 355 | # generic OS-type and the information about Apache's compiled-in modules. 356 | 357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! 358 | 359 | # ServerTokens Prod 360 | 361 | 362 | # ############################################################################## 363 | # # WEB PERFORMANCE # 364 | # ############################################################################## 365 | 366 | # ------------------------------------------------------------------------------ 367 | # | Compression | 368 | # ------------------------------------------------------------------------------ 369 | 370 | 371 | 372 | # Force compression for mangled headers. 373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping 374 | 375 | 376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 378 | 379 | 380 | 381 | # Compress all output labeled with one of the following MIME-types 382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` 383 | # and can remove the `` and `` lines 384 | # as `AddOutputFilterByType` is still in the core directives). 385 | 386 | AddOutputFilterByType DEFLATE application/atom+xml \ 387 | application/javascript \ 388 | application/json \ 389 | application/rss+xml \ 390 | application/vnd.ms-fontobject \ 391 | application/x-font-ttf \ 392 | application/x-web-app-manifest+json \ 393 | application/xhtml+xml \ 394 | application/xml \ 395 | font/opentype \ 396 | image/svg+xml \ 397 | image/x-icon \ 398 | text/css \ 399 | text/html \ 400 | text/plain \ 401 | text/x-component \ 402 | text/xml 403 | 404 | 405 | 406 | 407 | # ------------------------------------------------------------------------------ 408 | # | Content transformations | 409 | # ------------------------------------------------------------------------------ 410 | 411 | # Prevent some of the mobile network providers from modifying the content of 412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. 413 | 414 | # 415 | # Header set Cache-Control "no-transform" 416 | # 417 | 418 | # ------------------------------------------------------------------------------ 419 | # | ETag removal | 420 | # ------------------------------------------------------------------------------ 421 | 422 | # Since we're sending far-future expires headers (see below), ETags can 423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags. 424 | 425 | # `FileETag None` is not enough for every server. 426 | 427 | Header unset ETag 428 | 429 | 430 | FileETag None 431 | 432 | # ------------------------------------------------------------------------------ 433 | # | Expires headers (for better cache control) | 434 | # ------------------------------------------------------------------------------ 435 | 436 | # The following expires headers are set pretty far in the future. If you don't 437 | # control versioning with filename-based cache busting, consider lowering the 438 | # cache time for resources like CSS and JS to something like 1 week. 439 | 440 | 441 | 442 | ExpiresActive on 443 | ExpiresDefault "access plus 1 month" 444 | 445 | # CSS 446 | ExpiresByType text/css "access plus 1 year" 447 | 448 | # Data interchange 449 | ExpiresByType application/json "access plus 0 seconds" 450 | ExpiresByType application/xml "access plus 0 seconds" 451 | ExpiresByType text/xml "access plus 0 seconds" 452 | 453 | # Favicon (cannot be renamed!) 454 | ExpiresByType image/x-icon "access plus 1 week" 455 | 456 | # HTML components (HTCs) 457 | ExpiresByType text/x-component "access plus 1 month" 458 | 459 | # HTML 460 | ExpiresByType text/html "access plus 0 seconds" 461 | 462 | # JavaScript 463 | ExpiresByType application/javascript "access plus 1 year" 464 | 465 | # Manifest files 466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" 467 | ExpiresByType text/cache-manifest "access plus 0 seconds" 468 | 469 | # Media 470 | ExpiresByType audio/ogg "access plus 1 month" 471 | ExpiresByType image/gif "access plus 1 month" 472 | ExpiresByType image/jpeg "access plus 1 month" 473 | ExpiresByType image/png "access plus 1 month" 474 | ExpiresByType video/mp4 "access plus 1 month" 475 | ExpiresByType video/ogg "access plus 1 month" 476 | ExpiresByType video/webm "access plus 1 month" 477 | 478 | # Web feeds 479 | ExpiresByType application/atom+xml "access plus 1 hour" 480 | ExpiresByType application/rss+xml "access plus 1 hour" 481 | 482 | # Web fonts 483 | ExpiresByType application/font-woff "access plus 1 month" 484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 485 | ExpiresByType application/x-font-ttf "access plus 1 month" 486 | ExpiresByType font/opentype "access plus 1 month" 487 | ExpiresByType image/svg+xml "access plus 1 month" 488 | 489 | 490 | 491 | # ------------------------------------------------------------------------------ 492 | # | Filename-based cache busting | 493 | # ------------------------------------------------------------------------------ 494 | 495 | # If you're not using a build process to manage your filename version revving, 496 | # you might want to consider enabling the following directives to route all 497 | # requests such as `/css/style.12345.css` to `/css/style.css`. 498 | 499 | # To understand why this is important and a better idea than `*.css?v231`, read: 500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring 501 | 502 | # 503 | # RewriteCond %{REQUEST_FILENAME} !-f 504 | # RewriteCond %{REQUEST_FILENAME} !-d 505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 506 | # 507 | 508 | # ------------------------------------------------------------------------------ 509 | # | File concatenation | 510 | # ------------------------------------------------------------------------------ 511 | 512 | # Allow concatenation from within specific CSS and JS files, e.g.: 513 | # Inside of `script.combined.js` you could have 514 | # 515 | # 516 | # and they would be included into this single file. 517 | 518 | # 519 | # 520 | # Options +Includes 521 | # AddOutputFilterByType INCLUDES application/javascript application/json 522 | # SetOutputFilter INCLUDES 523 | # 524 | # 525 | # Options +Includes 526 | # AddOutputFilterByType INCLUDES text/css 527 | # SetOutputFilter INCLUDES 528 | # 529 | # 530 | 531 | # ------------------------------------------------------------------------------ 532 | # | Persistent connections | 533 | # ------------------------------------------------------------------------------ 534 | 535 | # Allow multiple requests to be sent over the same TCP connection: 536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. 537 | 538 | # Enable if you serve a lot of static content but, be aware of the 539 | # possible disadvantages! 540 | 541 | # 542 | # Header set Connection Keep-Alive 543 | # 544 | -------------------------------------------------------------------------------- /client/app/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidancasey/S3DropboxFileSync/6561a72cf09304c32b99f29a5feb64d37b539651/client/app/images/yeoman.png -------------------------------------------------------------------------------- /client/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 |
27 |
28 |

S3 to Dropbox File Sync using AWS Lambda Function

29 |
30 | 31 |
32 | 33 | 36 |
37 | 38 | 39 | 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 | -------------------------------------------------------------------------------- /client/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc overview 5 | * @name clientApp 6 | * @description 7 | * # clientApp 8 | * 9 | * Main module of the application. 10 | */ 11 | angular 12 | .module('clientApp', [ 13 | 'ngAnimate', 14 | 'ngAria', 15 | 'ngCookies', 16 | 'ngMessages', 17 | 'ngResource', 18 | 'ngRoute', 19 | 'ngSanitize', 20 | 'ngTouch', 21 | 'angularFileUpload' 22 | ]) 23 | .config(function ($routeProvider) { 24 | $routeProvider 25 | .when('/', { 26 | templateUrl: 'views/main.html', 27 | controller: 'MainCtrl' 28 | }) 29 | .otherwise({ 30 | redirectTo: '/' 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /client/app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name clientApp.controller:MainCtrl 6 | * @description 7 | * # MainCtrl 8 | * Controller of the clientApp 9 | */ 10 | 11 | angular.module('clientApp') 12 | .controller('MainCtrl', function ($scope, $window, $http, $timeout, $upload) { 13 | $scope.awesomeThings = [ 14 | 'HTML5 Boilerplate', 15 | 'AngularJS', 16 | 'Karma' 17 | ]; 18 | 19 | $scope.registerDropBox = function () { 20 | $window.location.href = '/api/dropbox/authorise'; 21 | }; 22 | 23 | $scope.fileReaderSupported = window.FileReader != null; 24 | $scope.uploadRightAway = true; 25 | 26 | $scope.hasUploader = function (index) { 27 | return $scope.upload[index] != null; 28 | }; 29 | $scope.abort = function (index) { 30 | $scope.upload[index].abort(); 31 | $scope.upload[index] = null; 32 | }; 33 | $scope.angularVersion = window.location.hash.length > 1 ? window.location.hash.substring(1) : '1.2.0'; 34 | $scope.onFileSelect = function ($files) { 35 | $scope.selectedFiles = []; 36 | $scope.progress = []; 37 | if ($scope.upload && $scope.upload.length > 0) { 38 | for (var i = 0; i < $scope.upload.length; i++) { 39 | if ($scope.upload[i] != null) { 40 | $scope.upload[i].abort(); 41 | } 42 | } 43 | } 44 | $scope.upload = []; 45 | $scope.uploadResult = []; 46 | $scope.selectedFiles = $files; 47 | $scope.dataUrls = []; 48 | for (var i = 0; i < $files.length; i++) { 49 | var $file = $files[i]; 50 | if (window.FileReader && $file.type.indexOf('image') > -1) { 51 | var fileReader = new FileReader(); 52 | fileReader.readAsDataURL($files[i]); 53 | var loadFile = function (fileReader, index) { 54 | fileReader.onload = function (e) { 55 | $timeout(function () { 56 | $scope.dataUrls[index] = e.target.result; 57 | }); 58 | } 59 | }(fileReader, i); 60 | } 61 | $scope.progress[i] = -1; 62 | if ($scope.uploadRightAway) { 63 | $scope.start(i); 64 | } 65 | } 66 | }; 67 | 68 | $scope.start = function (index) { 69 | $scope.progress[index] = 0; 70 | $scope.errorMsg = null; 71 | $scope.upload[index] = $upload.upload({ 72 | url: '/api/upload', 73 | method: $scope.httpMethod, 74 | headers: {'my-header': 'my-header-value'}, 75 | data: { 76 | myModel: $scope.myModel 77 | }, 78 | file: $scope.selectedFiles[index], 79 | fileFormDataName: 'myFile' 80 | }).then(function (response) { 81 | console.log('response back is ' + response.data); 82 | $scope.upload = []; 83 | 84 | $scope.uploadResult.push(response.data); 85 | }, function (response) { 86 | if (response.status > 0) { 87 | $scope.errorMsg = response.status + ': ' + response.data; 88 | } 89 | }, function (evt) { 90 | // Math.min is to fix IE which reports 200% sometimes 91 | $scope.progress[index] = Math.min(100, parseInt(100.0 * evt.loaded / evt.total)); 92 | }) 93 | }; 94 | }); 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /client/app/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | // When true, asset path helpers are used, otherwise the regular CSS `url()` is used. 2 | // When there no function is defined, `fn('')` is parsed as string that equals the right hand side 3 | // NB: in Sass 3.3 there is a native function: function-exists(twbs-font-path) 4 | $bootstrap-sass-asset-helper: (twbs-font-path("") != unquote('twbs-font-path("")')) !default; 5 | 6 | // 7 | // Variables 8 | // -------------------------------------------------- 9 | 10 | 11 | //== Colors 12 | // 13 | //## Gray and brand colors for use across Bootstrap. 14 | 15 | $gray-base: #000 !default; 16 | $gray-darker: lighten($gray-base, 13.5%) !default; // #222 17 | $gray-dark: lighten($gray-base, 20%) !default; // #333 18 | $gray: lighten($gray-base, 33.5%) !default; // #555 19 | $gray-light: lighten($gray-base, 46.7%) !default; // #777 20 | $gray-lighter: lighten($gray-base, 93.5%) !default; // #eee 21 | 22 | 23 | $brand-primary: #404040 !default; 24 | $brand-success: #04BF9D !default; 25 | $brand-info: #34D0CA !default; 26 | $brand-warning: #f0ad4e !default; 27 | $brand-danger: #F53D54 !default; 28 | $brand-dark: #404040 !default; 29 | 30 | 31 | //== Scaffolding 32 | // 33 | // ## Settings for some of the most global styles. 34 | 35 | //** Background color for ``. 36 | $body-bg: #fbfbff !default; 37 | //** Global text color on ``. 38 | $text-color: $brand-dark !default; 39 | 40 | 41 | //** Global text color on ``. 42 | $text-color: $gray-dark !default; 43 | 44 | //** Global textual link color. 45 | $link-color: $brand-primary !default; 46 | //** Link hover color set via `darken()` function. 47 | $link-hover-color: darken($link-color, 15%) !default; 48 | //** Link hover decoration. 49 | $link-hover-decoration: underline !default; 50 | 51 | 52 | //== Typography 53 | // 54 | //## Font, line-height, and color for body text, headings, and more. 55 | 56 | $font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif !default; 57 | $font-family-serif: Georgia, "Times New Roman", Times, serif !default; 58 | //** Default monospace fonts for ``, ``, 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 | --------------------------------------------------------------------------------