├── .gitignore ├── README.md ├── client ├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .yo-rc.json ├── Gruntfile.js ├── app │ ├── .buildignore │ ├── .htaccess │ ├── 404.html │ ├── favicon.ico │ ├── images │ │ └── yeoman.png │ ├── index.html │ ├── robots.txt │ ├── scripts │ │ ├── app.js │ │ ├── controllers │ │ │ ├── addpost.js │ │ │ ├── alerts.js │ │ │ ├── dashboard.js │ │ │ ├── login.js │ │ │ ├── main.js │ │ │ ├── menu.js │ │ │ ├── signup.js │ │ │ └── viewpost.js │ │ └── services │ │ │ ├── alerts.js │ │ │ └── user.js │ ├── styles │ │ └── main.css │ └── views │ │ ├── addpost.html │ │ ├── dashboard.html │ │ ├── login.html │ │ ├── main.html │ │ ├── signup.html │ │ └── viewpost.html ├── bower.json ├── client.iml ├── package.json └── test │ ├── .jshintrc │ ├── karma.conf.js │ └── spec │ ├── controllers │ ├── about.js │ ├── addpost.js │ ├── alerts.js │ ├── dashboard.js │ ├── login.js │ ├── main.js │ ├── menu.js │ ├── signup.js │ └── viewpost.js │ └── services │ ├── alerts.js │ └── user.js └── server ├── .gitignore ├── LICENSE ├── activator ├── activator-launch-1.3.2.jar ├── app ├── controllers │ ├── Application.java │ ├── Post.java │ └── Secured.java └── models │ ├── BlogPost.java │ ├── PostComment.java │ └── User.java ├── build.sbt ├── conf ├── application.conf ├── evolutions │ └── default │ │ └── 1.sql └── routes ├── project ├── build.properties └── plugins.sbt └── public ├── images └── favicon.png ├── javascripts └── hello.js └── stylesheets └── main.css /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angularjs-play-blog-app 2 | A simple blog application with basic authentication mechanism and the ability to make posts and comments built with [AngularJS](https://github.com/angular/angular.js) and [Play](https://github.com/play/play) 3 | 4 | ## Client 5 | To run AngularJS client app run `grunt serve` 6 | 7 | ## Server 8 | To run Play server app run `./activator "run 9090"` 9 | -------------------------------------------------------------------------------- /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/.yo-rc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /client/Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2015-05-08 using generator-angular 0.11.1 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: '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 | styles: { 48 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'], 49 | tasks: ['newer:copy:styles', '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 | proxies: [ 75 | { 76 | context: '/app', // the context of the data service 77 | host: 'localhost', // wherever the data service is running 78 | port: 9090, // the port that the data service is running on 79 | changeOrigin: true 80 | } 81 | ], 82 | livereload: { 83 | options: { 84 | open: true, 85 | 86 | middleware: function (connect) { 87 | var middlewares = []; 88 | 89 | // Setup the proxy 90 | middlewares.push(require('grunt-connect-proxy/lib/utils').proxyRequest); 91 | 92 | // Serve static files 93 | middlewares.push(connect.static('.tmp')); 94 | middlewares.push(connect().use( 95 | '/bower_components', 96 | connect.static('./bower_components') 97 | )); 98 | middlewares.push(connect().use( 99 | '/app/styles', 100 | connect.static('./app/styles') 101 | )); 102 | middlewares.push(connect.static(appConfig.app)); 103 | 104 | return middlewares; 105 | } 106 | } 107 | }, 108 | test: { 109 | options: { 110 | port: 9001, 111 | middleware: function (connect) { 112 | return [ 113 | connect.static('.tmp'), 114 | connect.static('test'), 115 | connect().use( 116 | '/bower_components', 117 | connect.static('./bower_components') 118 | ), 119 | connect.static(appConfig.app) 120 | ]; 121 | } 122 | } 123 | }, 124 | dist: { 125 | options: { 126 | open: true, 127 | base: '<%= yeoman.dist %>' 128 | } 129 | } 130 | }, 131 | 132 | // Make sure code styles are up to par and there are no obvious mistakes 133 | jshint: { 134 | options: { 135 | jshintrc: '.jshintrc', 136 | reporter: require('jshint-stylish') 137 | }, 138 | all: { 139 | src: [ 140 | 'Gruntfile.js', 141 | '<%= yeoman.app %>/scripts/{,*/}*.js' 142 | ] 143 | }, 144 | test: { 145 | options: { 146 | jshintrc: 'test/.jshintrc' 147 | }, 148 | src: ['test/spec/{,*/}*.js'] 149 | } 150 | }, 151 | 152 | // Empties folders to start fresh 153 | clean: { 154 | dist: { 155 | files: [{ 156 | dot: true, 157 | src: [ 158 | '.tmp', 159 | '<%= yeoman.dist %>/{,*/}*', 160 | '!<%= yeoman.dist %>/.git{,*/}*' 161 | ] 162 | }] 163 | }, 164 | server: '.tmp' 165 | }, 166 | 167 | // Add vendor prefixed styles 168 | autoprefixer: { 169 | options: { 170 | browsers: ['last 1 version'] 171 | }, 172 | server: { 173 | options: { 174 | map: true 175 | }, 176 | files: [{ 177 | expand: true, 178 | cwd: '.tmp/styles/', 179 | src: '{,*/}*.css', 180 | dest: '.tmp/styles/' 181 | }] 182 | }, 183 | dist: { 184 | files: [{ 185 | expand: true, 186 | cwd: '.tmp/styles/', 187 | src: '{,*/}*.css', 188 | dest: '.tmp/styles/' 189 | }] 190 | } 191 | }, 192 | 193 | // Automatically inject Bower components into the app 194 | wiredep: { 195 | app: { 196 | src: ['<%= yeoman.app %>/index.html'], 197 | ignorePath: /\.\.\// 198 | }, 199 | test: { 200 | devDependencies: true, 201 | src: '<%= karma.unit.configFile %>', 202 | ignorePath: /\.\.\//, 203 | fileTypes:{ 204 | js: { 205 | block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi, 206 | detect: { 207 | js: /'(.*\.js)'/gi 208 | }, 209 | replace: { 210 | js: '\'{{filePath}}\',' 211 | } 212 | } 213 | } 214 | } 215 | }, 216 | 217 | // Renames files for browser caching purposes 218 | filerev: { 219 | dist: { 220 | src: [ 221 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 222 | '<%= yeoman.dist %>/styles/{,*/}*.css', 223 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 224 | '<%= yeoman.dist %>/styles/fonts/*' 225 | ] 226 | } 227 | }, 228 | 229 | // Reads HTML for usemin blocks to enable smart builds that automatically 230 | // concat, minify and revision files. Creates configurations in memory so 231 | // additional tasks can operate on them 232 | useminPrepare: { 233 | html: '<%= yeoman.app %>/index.html', 234 | options: { 235 | dest: '<%= yeoman.dist %>', 236 | flow: { 237 | html: { 238 | steps: { 239 | js: ['concat', 'uglifyjs'], 240 | css: ['cssmin'] 241 | }, 242 | post: {} 243 | } 244 | } 245 | } 246 | }, 247 | 248 | // Performs rewrites based on filerev and the useminPrepare configuration 249 | usemin: { 250 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 251 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 252 | options: { 253 | assetsDirs: [ 254 | '<%= yeoman.dist %>', 255 | '<%= yeoman.dist %>/images', 256 | '<%= yeoman.dist %>/styles' 257 | ] 258 | } 259 | }, 260 | 261 | // The following *-min tasks will produce minified files in the dist folder 262 | // By default, your `index.html`'s will take care of 263 | // minification. These next options are pre-configured if you do not wish 264 | // to use the Usemin blocks. 265 | // cssmin: { 266 | // dist: { 267 | // files: { 268 | // '<%= yeoman.dist %>/styles/main.css': [ 269 | // '.tmp/styles/{,*/}*.css' 270 | // ] 271 | // } 272 | // } 273 | // }, 274 | // uglify: { 275 | // dist: { 276 | // files: { 277 | // '<%= yeoman.dist %>/scripts/scripts.js': [ 278 | // '<%= yeoman.dist %>/scripts/scripts.js' 279 | // ] 280 | // } 281 | // } 282 | // }, 283 | // concat: { 284 | // dist: {} 285 | // }, 286 | 287 | imagemin: { 288 | dist: { 289 | files: [{ 290 | expand: true, 291 | cwd: '<%= yeoman.app %>/images', 292 | src: '{,*/}*.{png,jpg,jpeg,gif}', 293 | dest: '<%= yeoman.dist %>/images' 294 | }] 295 | } 296 | }, 297 | 298 | svgmin: { 299 | dist: { 300 | files: [{ 301 | expand: true, 302 | cwd: '<%= yeoman.app %>/images', 303 | src: '{,*/}*.svg', 304 | dest: '<%= yeoman.dist %>/images' 305 | }] 306 | } 307 | }, 308 | 309 | htmlmin: { 310 | dist: { 311 | options: { 312 | collapseWhitespace: true, 313 | conservativeCollapse: true, 314 | collapseBooleanAttributes: true, 315 | removeCommentsFromCDATA: true, 316 | removeOptionalTags: true 317 | }, 318 | files: [{ 319 | expand: true, 320 | cwd: '<%= yeoman.dist %>', 321 | src: ['*.html', 'views/{,*/}*.html'], 322 | dest: '<%= yeoman.dist %>' 323 | }] 324 | } 325 | }, 326 | 327 | // ng-annotate tries to make the code safe for minification automatically 328 | // by using the Angular long form for dependency injection. 329 | ngAnnotate: { 330 | dist: { 331 | files: [{ 332 | expand: true, 333 | cwd: '.tmp/concat/scripts', 334 | src: '*.js', 335 | dest: '.tmp/concat/scripts' 336 | }] 337 | } 338 | }, 339 | 340 | // Replace Google CDN references 341 | cdnify: { 342 | dist: { 343 | html: ['<%= yeoman.dist %>/*.html'] 344 | } 345 | }, 346 | 347 | // Copies remaining files to places other tasks can use 348 | copy: { 349 | dist: { 350 | files: [{ 351 | expand: true, 352 | dot: true, 353 | cwd: '<%= yeoman.app %>', 354 | dest: '<%= yeoman.dist %>', 355 | src: [ 356 | '*.{ico,png,txt}', 357 | '.htaccess', 358 | '*.html', 359 | 'views/{,*/}*.html', 360 | 'images/{,*/}*.{webp}', 361 | 'styles/fonts/{,*/}*.*' 362 | ] 363 | }, { 364 | expand: true, 365 | cwd: '.tmp/images', 366 | dest: '<%= yeoman.dist %>/images', 367 | src: ['generated/*'] 368 | }, { 369 | expand: true, 370 | cwd: 'bower_components/bootstrap/dist', 371 | src: 'fonts/*', 372 | dest: '<%= yeoman.dist %>' 373 | }] 374 | }, 375 | styles: { 376 | expand: true, 377 | cwd: '<%= yeoman.app %>/styles', 378 | dest: '.tmp/styles/', 379 | src: '{,*/}*.css' 380 | } 381 | }, 382 | 383 | // Run some tasks in parallel to speed up the build process 384 | concurrent: { 385 | server: [ 386 | 'copy:styles' 387 | ], 388 | test: [ 389 | 'copy:styles' 390 | ], 391 | dist: [ 392 | 'copy:styles', 393 | 'imagemin', 394 | 'svgmin' 395 | ] 396 | }, 397 | 398 | // Test settings 399 | karma: { 400 | unit: { 401 | configFile: 'test/karma.conf.js', 402 | singleRun: true 403 | } 404 | } 405 | }); 406 | 407 | 408 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { 409 | if (target === 'dist') { 410 | return grunt.task.run(['build', 'connect:dist:keepalive']); 411 | } 412 | 413 | grunt.task.run([ 414 | 'clean:server', 415 | 'wiredep', 416 | 'concurrent:server', 417 | 'autoprefixer:server', 418 | 'configureProxies:server', 419 | 'connect:livereload', 420 | 'watch' 421 | ]); 422 | }); 423 | 424 | grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) { 425 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 426 | grunt.task.run(['serve:' + target]); 427 | }); 428 | 429 | grunt.registerTask('test', [ 430 | 'clean:server', 431 | 'wiredep', 432 | 'concurrent:test', 433 | 'autoprefixer', 434 | 'connect:test', 435 | 'karma' 436 | ]); 437 | 438 | grunt.registerTask('build', [ 439 | 'clean:dist', 440 | 'wiredep', 441 | 'useminPrepare', 442 | 'concurrent:dist', 443 | 'autoprefixer', 444 | 'concat', 445 | 'ngAnnotate', 446 | 'copy:dist', 447 | 'cdnify', 448 | 'cssmin', 449 | 'uglify', 450 | 'filerev', 451 | 'usemin', 452 | 'htmlmin' 453 | ]); 454 | 455 | grunt.registerTask('default', [ 456 | 'newer:jshint', 457 | 'test', 458 | 'build' 459 | ]); 460 | }; 461 | -------------------------------------------------------------------------------- /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/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /client/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsinyakov/angularjs-play-blog-app/87221ac08695fea104eab162bc179f381be70f1a/client/app/favicon.ico -------------------------------------------------------------------------------- /client/app/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsinyakov/angularjs-play-blog-app/87221ac08695fea104eab162bc179f381be70f1a/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 | 22 | 23 | 24 |
25 | 61 |
62 | 63 |
64 |
65 | {{ alert.msg }} 66 |
67 |
68 |
69 | 70 | 75 | 76 | 77 | 78 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /client/app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /client/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * @ngdoc overview 10 | * @name clientApp 11 | * @description 12 | * # clientApp 13 | * 14 | * Main module of the application. 15 | */ 16 | angular 17 | .module('clientApp', [ 18 | 'ngAnimate', 19 | 'ngCookies', 20 | 'ngResource', 21 | 'ngRoute', 22 | 'ngSanitize', 23 | 'ngTouch', 24 | 'ui.bootstrap' 25 | ]) 26 | .config(function ($routeProvider) { 27 | $routeProvider 28 | .when('/', { 29 | templateUrl: 'views/main.html', 30 | controller: 'MainCtrl' 31 | }) 32 | .when('/signup', { 33 | templateUrl: 'views/signup.html', 34 | controller: 'SignupCtrl' 35 | }) 36 | .when('/dashboard', { 37 | templateUrl: 'views/dashboard.html', 38 | controller: 'DashboardCtrl' 39 | }) 40 | .when('/login', { 41 | templateUrl: 'views/login.html', 42 | controller: 'LoginCtrl' 43 | }) 44 | .when('/addpost', { 45 | templateUrl: 'views/addpost.html', 46 | controller: 'AddpostCtrl' 47 | }) 48 | .when('/viewpost/:postId', { 49 | templateUrl: 'views/viewpost.html', 50 | controller: 'ViewpostCtrl' 51 | }) 52 | .otherwise({ 53 | redirectTo: '/' 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /client/app/scripts/controllers/addpost.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * @ngdoc function 10 | * @name clientApp.controller:AddpostCtrl 11 | * @description 12 | * # AddpostCtrl 13 | * Controller of the clientApp 14 | */ 15 | angular.module('clientApp') 16 | .controller('AddpostCtrl', function ($scope, $http, alertService, $location) { 17 | 18 | $scope.post = function() { 19 | var payload = { 20 | subject : $scope.subject, 21 | content: $scope.content 22 | }; 23 | $http.post('/app/post', payload) 24 | .error(function(data, status) { 25 | if(status === 400) { 26 | angular.forEach(data, function(value, key) { 27 | if(key === 'subject' || key === 'content') { 28 | alertService.add('danger', key + ' : ' + value); 29 | } else { 30 | alertService.add('danger', value.message); 31 | } 32 | }); 33 | } else if(status === 401) { 34 | $location.path('/login'); 35 | } else if(status === 500) { 36 | alertService.add('danger', 'Internal server error!'); 37 | } else { 38 | alertService.add('danger', data); 39 | } 40 | }) 41 | .success(function(data) { 42 | $scope.subject = ''; 43 | $scope.content = ''; 44 | alertService.add('success', data.success.message); 45 | }); 46 | }; 47 | }); 48 | -------------------------------------------------------------------------------- /client/app/scripts/controllers/alerts.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 'use strict'; 6 | 7 | /** 8 | * @ngdoc function 9 | * @name clientApp.controller:AlertsCtrl 10 | * @description 11 | * # AlertsCtrl 12 | * Controller of the clientApp 13 | */ 14 | angular.module('clientApp') 15 | .controller('AlertsCtrl', function ($scope, alertService) { 16 | $scope.alerts = alertService.get(); 17 | }); 18 | -------------------------------------------------------------------------------- /client/app/scripts/controllers/dashboard.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * @ngdoc function 10 | * @name clientApp.controller:DashboardCtrl 11 | * @description 12 | * # DashboardCtrl 13 | * Controller of the clientApp 14 | */ 15 | angular.module('clientApp') 16 | .controller('DashboardCtrl', function ($scope, $log, $http, alertService, $location) { 17 | 18 | $scope.loadPosts = function() { 19 | $http.get('/app/userposts') 20 | .error(function(data, status) { 21 | if(status === 401) { 22 | $location.path('/login'); 23 | } else { 24 | alertService.add('danger', data.error.message); 25 | } 26 | }) 27 | .success(function(data) { 28 | $scope.posts = data; 29 | }); 30 | }; 31 | 32 | $scope.loadPosts(); 33 | }); 34 | -------------------------------------------------------------------------------- /client/app/scripts/controllers/login.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * @ngdoc function 10 | * @name clientApp.controller:LoginCtrl 11 | * @description 12 | * # LoginCtrl 13 | * Controller of the clientApp 14 | */ 15 | angular.module('clientApp') 16 | .controller('LoginCtrl', function ($scope, userService, $location, $log, $http, alertService) { 17 | 18 | $scope.isAuthenticated = function() { 19 | if(userService.username) { 20 | $log.debug(userService.username); 21 | $location.path('/dashboard'); 22 | } else { 23 | $http.get('/app/isauthenticated') 24 | .error(function() { 25 | $location.path('/login'); 26 | }) 27 | .success(function(data) { 28 | if(data.hasOwnProperty('success')) { 29 | userService.username = data.success.user; 30 | $location.path('/dashboard'); 31 | } 32 | }); 33 | } 34 | }; 35 | 36 | $scope.isAuthenticated(); 37 | 38 | $scope.login = function() { 39 | 40 | var payload = { 41 | email : this.email, 42 | password : this.password 43 | }; 44 | 45 | $http.post('/app/login', payload) 46 | .error(function(data, status){ 47 | if(status === 400) { 48 | angular.forEach(data, function(value, key) { 49 | if(key === 'email' || key === 'password') { 50 | alertService.add('danger', key + ' : ' + value); 51 | } else { 52 | alertService.add('danger', value.message); 53 | } 54 | }); 55 | } else if(status === 401) { 56 | alertService.add('danger', 'Invalid login or password!'); 57 | } else if(status === 500) { 58 | alertService.add('danger', 'Internal server error!'); 59 | } else { 60 | alertService.add('danger', data); 61 | } 62 | }) 63 | .success(function(data){ 64 | $log.debug(data); 65 | if(data.hasOwnProperty('success')) { 66 | userService.username = data.success.user; 67 | $location.path('/dashboard'); 68 | } 69 | }); 70 | }; 71 | }); 72 | -------------------------------------------------------------------------------- /client/app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * @ngdoc function 10 | * @name clientApp.controller:MainCtrl 11 | * @description 12 | * # MainCtrl 13 | * Controller of the clientApp 14 | */ 15 | angular.module('clientApp') 16 | .controller('MainCtrl', function ($scope, $http) { 17 | $scope.getPosts = function() { 18 | $http.get('app/posts') 19 | .success(function(data) { 20 | $scope.posts = data; 21 | }); 22 | }; 23 | 24 | $scope.getPosts(); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /client/app/scripts/controllers/menu.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * @ngdoc function 10 | * @name clientApp.controller:MenuCtrl 11 | * @description 12 | * # MenuCtrl 13 | * Controller of the clientApp 14 | */ 15 | angular.module('clientApp') 16 | .controller('MenuCtrl', function ($scope, $http, userService, $location) { 17 | $scope.user = userService; 18 | 19 | $scope.logout = function() { 20 | $http.get('/app/logout') 21 | .success(function(data) { 22 | if(data.hasOwnProperty('success')) { 23 | userService.username = ''; 24 | $location.path('/login'); 25 | } 26 | }); 27 | }; 28 | 29 | $scope.$watch('user.username', function (newVal) { 30 | if(newVal === '') { 31 | $scope.isLoggedIn = false; 32 | } else { 33 | $scope.username = newVal; 34 | $scope.isLoggedIn = true; 35 | } 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /client/app/scripts/controllers/signup.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * @ngdoc function 10 | * @name clientApp.controller:SignupCtrl 11 | * @description 12 | * # SignupCtrl 13 | * Controller of the clientApp 14 | */ 15 | angular.module('clientApp') 16 | .controller('SignupCtrl', function ($scope, $http, $log, alertService, $location, userService) { 17 | 18 | $scope.signup = function() { 19 | var payload = { 20 | email : $scope.email, 21 | password : $scope.password 22 | }; 23 | 24 | $http.post('app/signup', payload) 25 | .error(function(data, status) { 26 | if(status === 400) { 27 | angular.forEach(data, function(value, key) { 28 | if(key === 'email' || key === 'password') { 29 | alertService.add('danger', key + ' : ' + value); 30 | } else { 31 | alertService.add('danger', value.message); 32 | } 33 | }); 34 | } 35 | if(status === 500) { 36 | alertService.add('danger', 'Internal server error!'); 37 | } 38 | }) 39 | .success(function(data) { 40 | if(data.hasOwnProperty('success')) { 41 | userService.username = $scope.email; 42 | $location.path('/dashboard'); 43 | } 44 | }); 45 | }; 46 | }); 47 | -------------------------------------------------------------------------------- /client/app/scripts/controllers/viewpost.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * @ngdoc function 10 | * @name clientApp.controller:ViewpostCtrl 11 | * @description 12 | * # ViewpostCtrl 13 | * Controller of the clientApp 14 | */ 15 | angular.module('clientApp') 16 | .controller('ViewpostCtrl', function ($scope, $http, $routeParams, alertService, $location, userService) { 17 | 18 | $scope.user = userService; 19 | $scope.params = $routeParams; 20 | $scope.postId = $scope.params.postId; 21 | 22 | $scope.viewPost = function() { 23 | $http.get('/app/post/' + $scope.postId) 24 | .error(function(data) { 25 | alertService.add('danger', data.error.message); 26 | }) 27 | .success(function(data) { 28 | $scope.post = data; 29 | }); 30 | }; 31 | 32 | $scope.viewPost(); 33 | 34 | $scope.addComment = function() { 35 | var payload = { 36 | postId: $scope.postId, 37 | comment: $scope.comment 38 | }; 39 | 40 | $http.post('/app/comment', payload) 41 | .error(function(data, status) { 42 | if(status === 400) { 43 | angular.forEach(data, function(value, key) { 44 | if(key === 'comment') { 45 | alertService.add('danger', key + ' : ' + value); 46 | } else { 47 | alertService.add('danger', value.message); 48 | } 49 | }); 50 | } else if(status === 401) { 51 | $location.path('/login'); 52 | } else if(status === 500) { 53 | alertService.add('danger', 'Internal server error!'); 54 | } else { 55 | alertService.add('danger', data); 56 | } 57 | }) 58 | .success(function(data) { 59 | alertService.add('success', data.success.message); 60 | $scope.comment = ''; 61 | $scope.viewPost(); 62 | }); 63 | }; 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /client/app/scripts/services/alerts.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * @ngdoc service 10 | * @name clientApp.alerts 11 | * @description 12 | * # alerts 13 | * Service in the clientApp. 14 | */ 15 | angular.module('clientApp') 16 | .factory('alertService', function($timeout) { 17 | 18 | var ALERT_TIMEOUT = 5000; 19 | 20 | function add(type, msg, timeout) { 21 | 22 | if (timeout) { 23 | $timeout(function(){ 24 | closeAlert(this); 25 | }, timeout); 26 | } else { 27 | $timeout(function(){ 28 | closeAlert(this); 29 | }, ALERT_TIMEOUT); 30 | } 31 | 32 | return alerts.push({ 33 | type: type, 34 | msg: msg, 35 | close: function() { 36 | return closeAlert(this); 37 | } 38 | }); 39 | } 40 | 41 | function closeAlert(alert) { 42 | return closeAlertIdx(alerts.indexOf(alert)); 43 | } 44 | 45 | function closeAlertIdx(index) { 46 | return alerts.splice(index, 1); 47 | } 48 | 49 | function clear(){ 50 | alerts = []; 51 | } 52 | 53 | function get() { 54 | return alerts; 55 | } 56 | 57 | var service = { 58 | add: add, 59 | closeAlert: closeAlert, 60 | closeAlertIdx: closeAlertIdx, 61 | clear: clear, 62 | get: get 63 | }, 64 | alerts = []; 65 | 66 | return service; 67 | } 68 | ); 69 | 70 | -------------------------------------------------------------------------------- /client/app/scripts/services/user.js: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * @ngdoc service 10 | * @name clientApp.user 11 | * @description 12 | * # user 13 | * Service in the clientApp. 14 | */ 15 | angular.module('clientApp') 16 | .factory('userService', function() { 17 | var username = ''; 18 | 19 | return { 20 | username : username 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /client/app/styles/main.css: -------------------------------------------------------------------------------- 1 | .browsehappy { 2 | margin: 0.2em 0; 3 | background: #ccc; 4 | color: #000; 5 | padding: 0.2em 0; 6 | } 7 | 8 | body { 9 | padding: 0; 10 | } 11 | 12 | /* Everything but the jumbotron gets side spacing for mobile first views */ 13 | .header, 14 | .marketing, 15 | .footer { 16 | padding-left: 15px; 17 | padding-right: 15px; 18 | } 19 | 20 | /* Custom page header */ 21 | .header { 22 | border-bottom: 1px solid #e5e5e5; 23 | margin-bottom: 10px; 24 | } 25 | /* Make the masthead heading the same height as the navigation */ 26 | .header h3 { 27 | margin-top: 0; 28 | margin-bottom: 0; 29 | line-height: 40px; 30 | padding-bottom: 19px; 31 | } 32 | 33 | /* Custom page footer */ 34 | .footer { 35 | padding-top: 19px; 36 | color: #777; 37 | border-top: 1px solid #e5e5e5; 38 | } 39 | 40 | .container-narrow > hr { 41 | margin: 30px 0; 42 | } 43 | 44 | /* Main marketing message and sign up button */ 45 | .jumbotron { 46 | text-align: center; 47 | border-bottom: 1px solid #e5e5e5; 48 | } 49 | .jumbotron .btn { 50 | font-size: 21px; 51 | padding: 14px 24px; 52 | } 53 | 54 | /* Supporting marketing content */ 55 | .marketing { 56 | margin: 40px 0; 57 | } 58 | .marketing p + h4 { 59 | margin-top: 28px; 60 | } 61 | 62 | /* Responsive: Portrait tablets and up */ 63 | @media screen and (min-width: 768px) { 64 | .container { 65 | max-width: 730px; 66 | } 67 | 68 | /* Remove the padding we set earlier */ 69 | .header, 70 | .marketing, 71 | .footer { 72 | padding-left: 0; 73 | padding-right: 0; 74 | } 75 | /* Space out the masthead */ 76 | .header { 77 | margin-bottom: 30px; 78 | } 79 | /* Remove the bottom border on the jumbotron for visual effect */ 80 | .jumbotron { 81 | border-bottom: 0; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /client/app/views/addpost.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 6 |
7 |
8 | 9 | 11 |
12 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /client/app/views/dashboard.html: -------------------------------------------------------------------------------- 1 |

My Posts

2 |
You have no posts. Add a post
3 |
4 |
5 | {{ post.subject }} | Comments 6 | {{ post.commentCount }} 7 |
8 |
9 | -------------------------------------------------------------------------------- /client/app/views/login.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 6 |
7 |
8 | 9 | 11 | 12 |
13 | 14 |
-------------------------------------------------------------------------------- /client/app/views/main.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ post.subject }}

4 |

5 | {{ post.content }} 6 |

7 |
8 | 9 | 12 |
13 | -------------------------------------------------------------------------------- /client/app/views/signup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 6 |
7 |
8 | 9 | 11 | 12 |
13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /client/app/views/viewpost.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ post.subject }}

4 |

5 | {{ post.content }} 6 |

7 |
8 | 9 | 11 |
12 | 13 |
14 | By: {{ comment.user.email }} 15 |
16 | {{ comment.content }} 17 |
18 | 19 |

Login to comment

20 |
21 |

Add comment

22 |
23 | 24 | 26 |
27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /client/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "^1.3.0", 6 | "bootstrap": "^3.2.0", 7 | "angular-animate": "^1.3.0", 8 | "angular-cookies": "^1.3.0", 9 | "angular-resource": "^1.3.0", 10 | "angular-route": "^1.3.0", 11 | "angular-sanitize": "^1.3.0", 12 | "angular-touch": "^1.3.0", 13 | "angular-bootstrap": "~0.13.0" 14 | }, 15 | "devDependencies": { 16 | "angular-mocks": "^1.3.0" 17 | }, 18 | "appPath": "app", 19 | "moduleName": "clientApp" 20 | } 21 | -------------------------------------------------------------------------------- /client/client.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "repository": {}, 6 | "devDependencies": { 7 | "grunt": "^0.4.5", 8 | "grunt-autoprefixer": "^2.0.0", 9 | "grunt-concurrent": "^1.0.0", 10 | "grunt-connect-proxy": "^0.2.0", 11 | "grunt-contrib-clean": "^0.6.0", 12 | "grunt-contrib-concat": "^0.5.0", 13 | "grunt-contrib-connect": "^0.9.0", 14 | "grunt-contrib-copy": "^0.7.0", 15 | "grunt-contrib-cssmin": "^0.12.0", 16 | "grunt-contrib-htmlmin": "^0.4.0", 17 | "grunt-contrib-imagemin": "^0.9.2", 18 | "grunt-contrib-jshint": "^0.11.0", 19 | "grunt-contrib-uglify": "^0.7.0", 20 | "grunt-contrib-watch": "^0.6.1", 21 | "grunt-filerev": "^2.1.2", 22 | "grunt-google-cdn": "^0.4.3", 23 | "grunt-karma": "^0.10.1", 24 | "grunt-newer": "^1.1.0", 25 | "grunt-ng-annotate": "^0.9.2", 26 | "grunt-svgmin": "^2.0.0", 27 | "grunt-usemin": "^3.0.0", 28 | "grunt-wiredep": "^2.0.0", 29 | "jasmine-core": "^2.3.2", 30 | "jshint-stylish": "^1.0.0", 31 | "karma": "^0.12.31", 32 | "karma-jasmine": "^0.3.5", 33 | "karma-phantomjs-launcher": "^0.1.4", 34 | "load-grunt-tasks": "^3.1.0", 35 | "time-grunt": "^1.0.0" 36 | }, 37 | "engines": { 38 | "node": ">=0.10.0" 39 | }, 40 | "scripts": { 41 | "test": "grunt test" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /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 | "jasmine": true, 22 | "globals": { 23 | "angular": false, 24 | "browser": false, 25 | "inject": false 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /client/test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.12/config/configuration-file.html 3 | // Generated on 2015-05-08 using 4 | // generator-karma 0.9.0 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:js 22 | 'bower_components/jquery/dist/jquery.js', 23 | 'bower_components/angular/angular.js', 24 | 'bower_components/bootstrap/dist/js/bootstrap.js', 25 | 'bower_components/angular-animate/angular-animate.js', 26 | 'bower_components/angular-cookies/angular-cookies.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 | 'bower_components/angular-bootstrap/ui-bootstrap-tpls.js', 32 | 'bower_components/angular-mocks/angular-mocks.js', 33 | // endbower 34 | 'app/scripts/**/*.js', 35 | 'test/mock/**/*.js', 36 | 'test/spec/**/*.js' 37 | ], 38 | 39 | // list of files / patterns to exclude 40 | exclude: [ 41 | ], 42 | 43 | // web server port 44 | port: 8080, 45 | 46 | // Start these browsers, currently available: 47 | // - Chrome 48 | // - ChromeCanary 49 | // - Firefox 50 | // - Opera 51 | // - Safari (only Mac) 52 | // - PhantomJS 53 | // - IE (only Windows) 54 | browsers: [ 55 | 'PhantomJS' 56 | ], 57 | 58 | // Which plugins to enable 59 | plugins: [ 60 | 'karma-phantomjs-launcher', 61 | 'karma-jasmine' 62 | ], 63 | 64 | // Continuous Integration mode 65 | // if true, it capture browsers, run tests and exit 66 | singleRun: false, 67 | 68 | colors: true, 69 | 70 | // level of logging 71 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 72 | logLevel: config.LOG_INFO 73 | 74 | // Uncomment the following lines if you are using grunt's server to run the tests 75 | // proxies: { 76 | // '/': 'http://localhost:9000/' 77 | // }, 78 | // URL root prevent conflicts with the site root 79 | // urlRoot: '_karma_' 80 | }); 81 | }; 82 | -------------------------------------------------------------------------------- /client/test/spec/controllers/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AboutCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('clientApp')); 7 | 8 | var AboutCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | AboutCtrl = $controller('AboutCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /client/test/spec/controllers/addpost.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AddpostCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('clientApp')); 7 | 8 | var AddpostCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | AddpostCtrl = $controller('AddpostCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /client/test/spec/controllers/alerts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AlertsCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('clientApp')); 7 | 8 | var AlertsCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | AlertsCtrl = $controller('AlertsCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /client/test/spec/controllers/dashboard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: DashboardCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('clientApp')); 7 | 8 | var DashboardCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | DashboardCtrl = $controller('DashboardCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /client/test/spec/controllers/login.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: LoginCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('clientApp')); 7 | 8 | var LoginCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | LoginCtrl = $controller('LoginCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /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 | }); 20 | -------------------------------------------------------------------------------- /client/test/spec/controllers/menu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MenuCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('clientApp')); 7 | 8 | var MenuCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MenuCtrl = $controller('MenuCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /client/test/spec/controllers/signup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: SignupCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('clientApp')); 7 | 8 | var SignupCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | SignupCtrl = $controller('SignupCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /client/test/spec/controllers/viewpost.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ViewpostCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('clientApp')); 7 | 8 | var ViewpostCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | ViewpostCtrl = $controller('ViewpostCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /client/test/spec/services/alerts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: alerts', function () { 4 | 5 | // load the service's module 6 | beforeEach(module('clientApp')); 7 | 8 | // instantiate service 9 | var alerts; 10 | beforeEach(inject(function (_alerts_) { 11 | alerts = _alerts_; 12 | })); 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /client/test/spec/services/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: user', function () { 4 | 5 | // load the service's module 6 | beforeEach(module('clientApp')); 7 | 8 | // instantiate service 9 | var user; 10 | beforeEach(inject(function (_user_) { 11 | user = _user_; 12 | })); 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | project/project 3 | project/target 4 | target 5 | tmp 6 | .history 7 | dist 8 | /.idea 9 | /*.iml 10 | /out 11 | /.idea_modules 12 | /.classpath 13 | /.project 14 | /RUNNING_PID 15 | /.settings 16 | -------------------------------------------------------------------------------- /server/LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the Apache 2 license, quoted below. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with 4 | the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 5 | 6 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 7 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 8 | language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /server/activator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ### ------------------------------- ### 4 | ### Helper methods for BASH scripts ### 5 | ### ------------------------------- ### 6 | 7 | realpath () { 8 | ( 9 | TARGET_FILE="$1" 10 | 11 | cd "$(dirname "$TARGET_FILE")" 12 | TARGET_FILE=$(basename "$TARGET_FILE") 13 | 14 | COUNT=0 15 | while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ] 16 | do 17 | TARGET_FILE=$(readlink "$TARGET_FILE") 18 | cd "$(dirname "$TARGET_FILE")" 19 | TARGET_FILE=$(basename "$TARGET_FILE") 20 | COUNT=$(($COUNT + 1)) 21 | done 22 | 23 | if [ "$TARGET_FILE" == "." -o "$TARGET_FILE" == ".." ]; then 24 | cd "$TARGET_FILE" 25 | TARGET_FILEPATH= 26 | else 27 | TARGET_FILEPATH=/$TARGET_FILE 28 | fi 29 | 30 | # make sure we grab the actual windows path, instead of cygwin's path. 31 | if ! is_cygwin; then 32 | echo "$(pwd -P)/$TARGET_FILE" 33 | else 34 | echo $(cygwinpath "$(pwd -P)/$TARGET_FILE") 35 | fi 36 | ) 37 | } 38 | 39 | # TODO - Do we need to detect msys? 40 | 41 | # Uses uname to detect if we're in the odd cygwin environment. 42 | is_cygwin() { 43 | local os=$(uname -s) 44 | case "$os" in 45 | CYGWIN*) return 0 ;; 46 | *) return 1 ;; 47 | esac 48 | } 49 | 50 | # This can fix cygwin style /cygdrive paths so we get the 51 | # windows style paths. 52 | cygwinpath() { 53 | local file="$1" 54 | if is_cygwin; then 55 | echo $(cygpath -w $file) 56 | else 57 | echo $file 58 | fi 59 | } 60 | 61 | # Make something URI friendly 62 | make_url() { 63 | url="$1" 64 | local nospaces=${url// /%20} 65 | if is_cygwin; then 66 | echo "/${nospaces//\\//}" 67 | else 68 | echo "$nospaces" 69 | fi 70 | } 71 | 72 | # Detect if we should use JAVA_HOME or just try PATH. 73 | get_java_cmd() { 74 | if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then 75 | echo "$JAVA_HOME/bin/java" 76 | else 77 | echo "java" 78 | fi 79 | } 80 | 81 | echoerr () { 82 | echo 1>&2 "$@" 83 | } 84 | vlog () { 85 | [[ $verbose || $debug ]] && echoerr "$@" 86 | } 87 | dlog () { 88 | [[ $debug ]] && echoerr "$@" 89 | } 90 | execRunner () { 91 | # print the arguments one to a line, quoting any containing spaces 92 | [[ $verbose || $debug ]] && echo "# Executing command line:" && { 93 | for arg; do 94 | if printf "%s\n" "$arg" | grep -q ' '; then 95 | printf "\"%s\"\n" "$arg" 96 | else 97 | printf "%s\n" "$arg" 98 | fi 99 | done 100 | echo "" 101 | } 102 | 103 | exec "$@" 104 | } 105 | addJava () { 106 | dlog "[addJava] arg = '$1'" 107 | java_args=( "${java_args[@]}" "$1" ) 108 | } 109 | addApp () { 110 | dlog "[addApp] arg = '$1'" 111 | sbt_commands=( "${app_commands[@]}" "$1" ) 112 | } 113 | addResidual () { 114 | dlog "[residual] arg = '$1'" 115 | residual_args=( "${residual_args[@]}" "$1" ) 116 | } 117 | addDebugger () { 118 | addJava "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1" 119 | } 120 | addConfigOpts () { 121 | dlog "[addConfigOpts] arg = '$*'" 122 | for item in $* 123 | do 124 | addJava "$item" 125 | done 126 | } 127 | # a ham-fisted attempt to move some memory settings in concert 128 | # so they need not be messed around with individually. 129 | get_mem_opts () { 130 | local mem=${1:-1024} 131 | local meta=$(( $mem / 4 )) 132 | (( $meta > 256 )) || meta=256 133 | (( $meta < 1024 )) || meta=1024 134 | 135 | # default is to set memory options but this can be overridden by code section below 136 | memopts="-Xms${mem}m -Xmx${mem}m" 137 | if [[ "${java_version}" > "1.8" ]]; then 138 | extmemopts="-XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=${meta}m" 139 | else 140 | extmemopts="-XX:PermSize=64m -XX:MaxPermSize=${meta}m" 141 | fi 142 | 143 | if [[ "${java_opts}" == *-Xmx* ]] || [[ "${java_opts}" == *-Xms* ]] || [[ "${java_opts}" == *-XX:MaxPermSize* ]] || [[ "${java_opts}" == *-XX:ReservedCodeCacheSize* ]] || [[ "${java_opts}" == *-XX:MaxMetaspaceSize* ]]; then 144 | # if we detect any of these settings in ${java_opts} we need to NOT output our settings. 145 | # The reason is the Xms/Xmx, if they don't line up, cause errors. 146 | memopts="" 147 | extmemopts="" 148 | fi 149 | 150 | echo "${memopts} ${extmemopts}" 151 | } 152 | require_arg () { 153 | local type="$1" 154 | local opt="$2" 155 | local arg="$3" 156 | if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then 157 | die "$opt requires <$type> argument" 158 | fi 159 | } 160 | is_function_defined() { 161 | declare -f "$1" > /dev/null 162 | } 163 | 164 | # If we're *not* running in a terminal, and we don't have any arguments, then we need to add the 'ui' parameter 165 | detect_terminal_for_ui() { 166 | [[ ! -t 0 ]] && [[ "${#residual_args}" == "0" ]] && { 167 | addResidual "ui" 168 | } 169 | # SPECIAL TEST FOR MAC 170 | [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]] && [[ "${#residual_args}" == "0" ]] && { 171 | echo "Detected MAC OSX launched script...." 172 | echo "Swapping to UI" 173 | addResidual "ui" 174 | } 175 | } 176 | 177 | # Processes incoming arguments and places them in appropriate global variables. called by the run method. 178 | process_args () { 179 | while [[ $# -gt 0 ]]; do 180 | case "$1" in 181 | -h|-help) usage; exit 1 ;; 182 | -v|-verbose) verbose=1 && shift ;; 183 | -d|-debug) debug=1 && shift ;; 184 | -mem) require_arg integer "$1" "$2" && app_mem="$2" && shift 2 ;; 185 | -jvm-debug) 186 | if echo "$2" | grep -E ^[0-9]+$ > /dev/null; then 187 | addDebugger "$2" && shift 188 | else 189 | addDebugger 9999 190 | fi 191 | shift ;; 192 | -java-home) require_arg path "$1" "$2" && java_cmd="$2/bin/java" && shift 2 ;; 193 | -D*) addJava "$1" && shift ;; 194 | -J*) addJava "${1:2}" && shift ;; 195 | *) addResidual "$1" && shift ;; 196 | esac 197 | done 198 | 199 | is_function_defined process_my_args && { 200 | myargs=("${residual_args[@]}") 201 | residual_args=() 202 | process_my_args "${myargs[@]}" 203 | } 204 | } 205 | 206 | # Actually runs the script. 207 | run() { 208 | # TODO - check for sane environment 209 | 210 | # process the combined args, then reset "$@" to the residuals 211 | process_args "$@" 212 | detect_terminal_for_ui 213 | set -- "${residual_args[@]}" 214 | argumentCount=$# 215 | 216 | #check for jline terminal fixes on cygwin 217 | if is_cygwin; then 218 | stty -icanon min 1 -echo > /dev/null 2>&1 219 | addJava "-Djline.terminal=jline.UnixTerminal" 220 | addJava "-Dsbt.cygwin=true" 221 | fi 222 | 223 | # run sbt 224 | execRunner "$java_cmd" \ 225 | "-Dactivator.home=$(make_url "$activator_home")" \ 226 | $(get_mem_opts $app_mem) \ 227 | ${java_opts[@]} \ 228 | ${java_args[@]} \ 229 | -jar "$app_launcher" \ 230 | "${app_commands[@]}" \ 231 | "${residual_args[@]}" 232 | 233 | local exit_code=$? 234 | if is_cygwin; then 235 | stty icanon echo > /dev/null 2>&1 236 | fi 237 | exit $exit_code 238 | } 239 | 240 | # Loads a configuration file full of default command line options for this script. 241 | loadConfigFile() { 242 | cat "$1" | sed '/^\#/d' 243 | } 244 | 245 | ### ------------------------------- ### 246 | ### Start of customized settings ### 247 | ### ------------------------------- ### 248 | usage() { 249 | cat < [options] 251 | 252 | Command: 253 | ui Start the Activator UI 254 | new [name] [template-id] Create a new project with [name] using template [template-id] 255 | list-templates Print all available template names 256 | -h | -help Print this message 257 | 258 | Options: 259 | -v | -verbose Make this runner chattier 260 | -d | -debug Set sbt log level to debug 261 | -mem Set memory options (default: $sbt_mem, which is $(get_mem_opts $sbt_mem)) 262 | -jvm-debug Turn on JVM debugging, open at the given port. 263 | 264 | # java version (default: java from PATH, currently $(java -version 2>&1 | grep version)) 265 | -java-home Alternate JAVA_HOME 266 | 267 | # jvm options and output control 268 | -Dkey=val Pass -Dkey=val directly to the java runtime 269 | -J-X Pass option -X directly to the java runtime 270 | (-J is stripped) 271 | 272 | # environment variables (read from context) 273 | JAVA_OPTS Environment variable, if unset uses "" 274 | SBT_OPTS Environment variable, if unset uses "" 275 | ACTIVATOR_OPTS Environment variable, if unset uses "" 276 | 277 | In the case of duplicated or conflicting options, the order above 278 | shows precedence: environment variables lowest, command line options highest. 279 | EOM 280 | } 281 | 282 | ### ------------------------------- ### 283 | ### Main script ### 284 | ### ------------------------------- ### 285 | 286 | declare -a residual_args 287 | declare -a java_args 288 | declare -a app_commands 289 | declare -r real_script_path="$(realpath "$0")" 290 | declare -r activator_home="$(realpath "$(dirname "$real_script_path")")" 291 | declare -r app_version="1.3.2" 292 | 293 | declare -r app_launcher="${activator_home}/activator-launch-${app_version}.jar" 294 | declare -r script_name=activator 295 | java_cmd=$(get_java_cmd) 296 | declare -r java_opts=( "${ACTIVATOR_OPTS[@]}" "${SBT_OPTS[@]}" "${JAVA_OPTS[@]}" "${java_opts[@]}" ) 297 | userhome="$HOME" 298 | if is_cygwin; then 299 | # cygwin sets home to something f-d up, set to real windows homedir 300 | userhome="$USERPROFILE" 301 | fi 302 | declare -r activator_user_home_dir="${userhome}/.activator" 303 | declare -r java_opts_config_home="${activator_user_home_dir}/activatorconfig.txt" 304 | declare -r java_opts_config_version="${activator_user_home_dir}/${app_version}/activatorconfig.txt" 305 | 306 | # Now check to see if it's a good enough version 307 | declare -r java_version=$("$java_cmd" -version 2>&1 | awk -F '"' '/version/ {print $2}') 308 | if [[ "$java_version" == "" ]]; then 309 | echo 310 | echo No java installations was detected. 311 | echo Please go to http://www.java.com/getjava/ and download 312 | echo 313 | exit 1 314 | elif [[ ! "$java_version" > "1.6" ]]; then 315 | echo 316 | echo The java installation you have is not up to date 317 | echo Activator requires at least version 1.6+, you have 318 | echo version $java_version 319 | echo 320 | echo Please go to http://www.java.com/getjava/ and download 321 | echo a valid Java Runtime and install before running Activator. 322 | echo 323 | exit 1 324 | fi 325 | 326 | # if configuration files exist, prepend their contents to the java args so it can be processed by this runner 327 | # a "versioned" config trumps one on the top level 328 | if [[ -f "$java_opts_config_version" ]]; then 329 | addConfigOpts $(loadConfigFile "$java_opts_config_version") 330 | elif [[ -f "$java_opts_config_home" ]]; then 331 | addConfigOpts $(loadConfigFile "$java_opts_config_home") 332 | fi 333 | 334 | run "$@" 335 | -------------------------------------------------------------------------------- /server/activator-launch-1.3.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsinyakov/angularjs-play-blog-app/87221ac08695fea104eab162bc179f381be70f1a/server/activator-launch-1.3.2.jar -------------------------------------------------------------------------------- /server/app/controllers/Application.java: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | package controllers; 7 | 8 | import com.fasterxml.jackson.databind.node.ObjectNode; 9 | import models.BlogPost; 10 | import models.User; 11 | import play.data.Form; 12 | import play.data.validation.Constraints; 13 | import play.libs.Json; 14 | import play.mvc.Controller; 15 | import play.mvc.Result; 16 | 17 | /* 18 | * This controller contains Blog app common logic 19 | */ 20 | public class Application extends Controller { 21 | 22 | public static Result signup() { 23 | Form signUpForm = Form.form(SignUp.class).bindFromRequest(); 24 | 25 | if ( signUpForm.hasErrors()) { 26 | return badRequest(signUpForm.errorsAsJson()); 27 | } 28 | SignUp newUser = signUpForm.get(); 29 | User existingUser = User.findByEmail(newUser.email); 30 | if(existingUser != null) { 31 | return badRequest(buildJsonResponse("error", "User exists")); 32 | } else { 33 | User user = new User(); 34 | user.setEmail(newUser.email); 35 | user.setPassword(newUser.password); 36 | user.save(); 37 | session().clear(); 38 | session("username", newUser.email); 39 | 40 | return ok(buildJsonResponse("success", "User created successfully")); 41 | } 42 | } 43 | 44 | public static Result login() { 45 | Form loginForm = Form.form(Login.class).bindFromRequest(); 46 | if (loginForm.hasErrors()) { 47 | return badRequest(loginForm.errorsAsJson()); 48 | } 49 | Login loggingInUser = loginForm.get(); 50 | User user = User.findByEmailAndPassword(loggingInUser.email, loggingInUser.password); 51 | if(user == null) { 52 | return badRequest(buildJsonResponse("error", "Incorrect email or password")); 53 | } else { 54 | session().clear(); 55 | session("username", loggingInUser.email); 56 | 57 | ObjectNode wrapper = Json.newObject(); 58 | ObjectNode msg = Json.newObject(); 59 | msg.put("message", "Logged in successfully"); 60 | msg.put("user", loggingInUser.email); 61 | wrapper.put("success", msg); 62 | return ok(wrapper); 63 | } 64 | } 65 | 66 | public static Result logout() { 67 | session().clear(); 68 | return ok(buildJsonResponse("success", "Logged out successfully")); 69 | } 70 | 71 | public static Result isAuthenticated() { 72 | if(session().get("username") == null) { 73 | return unauthorized(); 74 | } else { 75 | ObjectNode wrapper = Json.newObject(); 76 | ObjectNode msg = Json.newObject(); 77 | msg.put("message", "User is logged in already"); 78 | msg.put("user", session().get("username")); 79 | wrapper.put("success", msg); 80 | return ok(wrapper); 81 | } 82 | } 83 | 84 | public static Result getPosts() { 85 | return ok(Json.toJson(BlogPost.find.findList())); 86 | } 87 | 88 | public static Result getPost(Long id) { 89 | BlogPost blogPost = BlogPost.findBlogPostById(id); 90 | if(blogPost == null) { 91 | return notFound(buildJsonResponse("error", "Post not found")); 92 | } 93 | return ok(Json.toJson(blogPost)); 94 | } 95 | 96 | public static class UserForm { 97 | @Constraints.Required 98 | @Constraints.Email 99 | public String email; 100 | } 101 | 102 | public static class SignUp extends UserForm { 103 | @Constraints.Required 104 | @Constraints.MinLength(6) 105 | public String password; 106 | } 107 | 108 | public static class Login extends UserForm { 109 | @Constraints.Required 110 | public String password; 111 | } 112 | 113 | public static ObjectNode buildJsonResponse(String type, String message) { 114 | ObjectNode wrapper = Json.newObject(); 115 | ObjectNode msg = Json.newObject(); 116 | msg.put("message", message); 117 | wrapper.put(type, msg); 118 | return wrapper; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /server/app/controllers/Post.java: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | package controllers; 7 | 8 | import models.BlogPost; 9 | import models.PostComment; 10 | import models.User; 11 | import play.data.Form; 12 | import play.data.validation.Constraints; 13 | import play.libs.Json; 14 | import play.mvc.Controller; 15 | import play.mvc.Result; 16 | import play.mvc.Security; 17 | 18 | /* 19 | * This controller contains Posting and Commenting logic. All methods require user to be 20 | * authenticated. 21 | */ 22 | @Security.Authenticated(Secured.class) 23 | public class Post extends Controller { 24 | 25 | public static Result addPost() { 26 | Form postForm = Form.form(PostForm.class).bindFromRequest(); 27 | 28 | if (postForm.hasErrors()) { 29 | return badRequest(postForm.errorsAsJson()); 30 | } else { 31 | BlogPost newBlogPost = new BlogPost(); 32 | newBlogPost.commentCount = 0L; 33 | newBlogPost.subject = postForm.get().subject; 34 | newBlogPost.content = postForm.get().content; 35 | newBlogPost.user = getUser(); 36 | newBlogPost.save(); 37 | } 38 | return ok(Application.buildJsonResponse("success", "Post added successfully")); 39 | } 40 | 41 | private static User getUser() { 42 | return User.findByEmail(session().get("username")); 43 | } 44 | 45 | public static Result getUserPosts() { 46 | User user = getUser(); 47 | if(user == null) { 48 | return badRequest(Application.buildJsonResponse("error", "No such user")); 49 | } 50 | return ok(Json.toJson(BlogPost.findBlogPostsByUser(user))); 51 | } 52 | 53 | public static Result addComment() { 54 | Form commentForm = Form.form(CommentForm.class).bindFromRequest(); 55 | 56 | if (commentForm.hasErrors()) { 57 | return badRequest(commentForm.errorsAsJson()); 58 | } else { 59 | PostComment newComment = new PostComment(); 60 | BlogPost blogPost = BlogPost.findBlogPostById(commentForm.get().postId); 61 | blogPost.commentCount++; 62 | blogPost.save(); 63 | newComment.blogPost = blogPost; 64 | newComment.user = getUser(); 65 | newComment.content = commentForm.get().comment; 66 | newComment.save(); 67 | return ok(Application.buildJsonResponse("success", "Comment added successfully")); 68 | } 69 | } 70 | 71 | public static class PostForm { 72 | 73 | @Constraints.Required 74 | @Constraints.MaxLength(255) 75 | public String subject; 76 | 77 | @Constraints.Required 78 | public String content; 79 | 80 | } 81 | 82 | public static class CommentForm { 83 | 84 | @Constraints.Required 85 | public Long postId; 86 | 87 | @Constraints.Required 88 | public String comment; 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /server/app/controllers/Secured.java: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | package controllers; 7 | 8 | import play.mvc.Http.Context; 9 | import play.mvc.Result; 10 | import play.mvc.Security; 11 | 12 | /** 13 | * Implements basic authentication 14 | */ 15 | public class Secured extends Security.Authenticator { 16 | 17 | @Override 18 | public String getUsername(Context ctx) { 19 | return ctx.session().get("username"); 20 | } 21 | 22 | @Override 23 | public Result onUnauthorized(Context ctx) { 24 | return unauthorized(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/app/models/BlogPost.java: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | package models; 7 | 8 | import java.util.List; 9 | import javax.persistence.CascadeType; 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import javax.persistence.Id; 13 | import javax.persistence.ManyToOne; 14 | import javax.persistence.OneToMany; 15 | import play.data.validation.Constraints; 16 | import play.db.ebean.Model; 17 | 18 | /** 19 | * Model representing Blog Post 20 | */ 21 | @Entity 22 | public class BlogPost extends Model { 23 | 24 | @Id 25 | public Long id; 26 | 27 | @Column(length = 255, nullable = false) 28 | @Constraints.MaxLength(255) 29 | @Constraints.Required 30 | public String subject; 31 | 32 | @Column(columnDefinition = "TEXT") 33 | @Constraints.Required 34 | public String content; 35 | 36 | @ManyToOne 37 | public User user; 38 | 39 | public Long commentCount; 40 | 41 | @OneToMany(cascade = CascadeType.ALL) 42 | public List comments; 43 | 44 | public static final Finder find = new Finder( 45 | Long.class, BlogPost.class); 46 | 47 | public static List findBlogPostsByUser(final User user) { 48 | return find 49 | .where() 50 | .eq("user", user) 51 | .findList(); 52 | } 53 | 54 | public static BlogPost findBlogPostById(final Long id) { 55 | return find 56 | .where() 57 | .eq("id", id) 58 | .findUnique(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /server/app/models/PostComment.java: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | package models; 7 | 8 | import com.fasterxml.jackson.annotation.JsonIgnore; 9 | import java.util.List; 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import javax.persistence.Id; 13 | import javax.persistence.ManyToOne; 14 | import play.db.ebean.Model; 15 | 16 | /** 17 | * Model representing Comments to Blog Posts 18 | */ 19 | @Entity 20 | public class PostComment extends Model { 21 | 22 | @Id 23 | public Long id; 24 | 25 | @ManyToOne 26 | @JsonIgnore 27 | public BlogPost blogPost; 28 | 29 | @ManyToOne 30 | public User user; 31 | 32 | @Column(columnDefinition = "TEXT") 33 | public String content; 34 | 35 | public static final Finder find = new Finder( 36 | Long.class, PostComment.class); 37 | 38 | public static List findAllCommentsByPost(final BlogPost blogPost) { 39 | return find 40 | .where() 41 | .eq("post", blogPost) 42 | .findList(); 43 | } 44 | 45 | public static List findAllCommentsByUser(final User user) { 46 | return find 47 | .where() 48 | .eq("user", user) 49 | .findList(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /server/app/models/User.java: -------------------------------------------------------------------------------- 1 | //////// 2 | // This sample is published as part of the blog article at www.toptal.com/blog 3 | // Visit www.toptal.com/blog and subscribe to our newsletter to read great posts 4 | //////// 5 | 6 | package models; 7 | 8 | import com.fasterxml.jackson.annotation.JsonIgnore; 9 | import java.io.UnsupportedEncodingException; 10 | import java.security.MessageDigest; 11 | import java.security.NoSuchAlgorithmException; 12 | import java.util.List; 13 | import javax.persistence.CascadeType; 14 | import javax.persistence.Column; 15 | import javax.persistence.Entity; 16 | import javax.persistence.Id; 17 | import javax.persistence.OneToMany; 18 | import play.data.validation.Constraints; 19 | import play.db.ebean.Model; 20 | 21 | /** 22 | * Model representing a Blog user 23 | */ 24 | @Entity 25 | public class User extends Model { 26 | 27 | @Id 28 | public Long id; 29 | 30 | @Column(length = 255, unique = true, nullable = false) 31 | @Constraints.MaxLength(255) 32 | @Constraints.Required 33 | @Constraints.Email 34 | public String email; 35 | 36 | @Column(length = 64, nullable = false) 37 | private byte[] shaPassword; 38 | 39 | @OneToMany(cascade = CascadeType.ALL) 40 | @JsonIgnore 41 | public List posts; 42 | 43 | public void setPassword(String password) { 44 | this.shaPassword = getSha512(password); 45 | } 46 | 47 | public void setEmail(String email) { 48 | this.email = email.toLowerCase(); 49 | } 50 | 51 | public static final Finder find = new Finder( 52 | Long.class, User.class); 53 | 54 | public static User findByEmailAndPassword(String email, String password) { 55 | return find 56 | .where() 57 | .eq("email", email.toLowerCase()) 58 | .eq("shaPassword", getSha512(password)) 59 | .findUnique(); 60 | } 61 | 62 | public static User findByEmail(String email) { 63 | return find 64 | .where() 65 | .eq("email", email.toLowerCase()) 66 | .findUnique(); 67 | } 68 | 69 | public static byte[] getSha512(String value) { 70 | try { 71 | return MessageDigest.getInstance("SHA-512").digest(value.getBytes("UTF-8")); 72 | } 73 | catch (NoSuchAlgorithmException e) { 74 | throw new RuntimeException(e); 75 | } 76 | catch (UnsupportedEncodingException e) { 77 | throw new RuntimeException(e); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /server/build.sbt: -------------------------------------------------------------------------------- 1 | name := """server""" 2 | 3 | version := "1.0-SNAPSHOT" 4 | 5 | lazy val root = (project in file(".")).enablePlugins(PlayJava) 6 | 7 | scalaVersion := "2.11.1" 8 | 9 | libraryDependencies ++= Seq( 10 | javaJdbc, 11 | javaEbean, 12 | cache, 13 | javaWs 14 | ) 15 | -------------------------------------------------------------------------------- /server/conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # 8 | # This must be changed for production, but we recommend not changing it in this file. 9 | # 10 | # See http://www.playframework.com/documentation/latest/ApplicationSecret for more details. 11 | application.secret="f7QWXdAm@gj9fHt>hGKO1V5BC>qR>9nmIr6hx3udZ`n:4Fg_a9:0@tEsm]a