├── .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 |
148 | - a mistyped address
149 | - an out-of-date link
150 |
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 |
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 |
15 |
--------------------------------------------------------------------------------
/client/app/views/dashboard.html:
--------------------------------------------------------------------------------
1 | My Posts
2 |
3 |
9 |
--------------------------------------------------------------------------------
/client/app/views/login.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
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 |
20 |
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