Skip to content

Commit cac1e73

Browse files
committed
Added solution for the exam
1 parent 20be50e commit cac1e73

28 files changed

Lines changed: 1985 additions & 0 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"directory": "public/bower_components"}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Fortune Cookies
2+
JavaScript Applications Exam, 10 September 2015
3+
4+
Implement a SPA application for fortune cookies.
5+
6+
## Application description
7+
8+
The application must provide the following functionality
9+
10+
* **Users** can **Register**, **Login** and **Logout** in the application
11+
* _Users provide `username` and `password`_
12+
* **Logged-in users** can:
13+
* **Share** a new fortune cookie
14+
* Providing `text` and `category`
15+
* **Re-share** an existing fortune cookie
16+
* This can be either:
17+
* A fortune cookie, shared by this user
18+
* A fortune cookie, shared by other user
19+
* **Like** or **dislike** a fortune cookie
20+
* **Get** their hourly fortune cookie
21+
* A random cookie from all shared cookies
22+
* This cookie is changed every hour
23+
* i.e. if the user asks for their fortune cookie at 11:30 and then again at 11:45, their fortune cookie will be the same
24+
* or if the user asks for their fortune cookie at 11:30 and then again at 12:00, the two fortune cookies may be different
25+
* It depends on the total count of fortune cookies
26+
* **All users** (even not logged in) can **see** fortune cookies, that are shared by any user
27+
* **Cookies** can be:
28+
* Sorted by `likes` or `shareDate`
29+
* Filtered by a single `category`
30+
* i.e. show only the fortune cookies in a given `category`
31+
32+
## Application routes
33+
34+
Implement at least the following routes in your app:
35+
36+
* `#/`
37+
* Redirects to `#/home`
38+
* `#/home`
39+
* Shows all fortune cookies
40+
* Available to all users, logged-in or not
41+
* Provides a way to sort the cookies by `likes` or `shareDate`
42+
* Provides a way to filter fortune cookies by `category`
43+
* `#/home?category=CCCC`
44+
* Shows only the fortune cookies in category **CCC**
45+
* `#/my-cookie`
46+
* Shows the current hourly fortune cookie for the logged-in user
47+
48+
49+
## Application requirements
50+
51+
**Mandatory** provide a good **User Experience(UX)**
52+
* No need to be pretty, just usable
53+
* Please do not make your colleagues wonder where to click, to share a fortune cookie
54+
55+
**Mandatory** use extensively the following libraries:
56+
57+
* jQuery
58+
* For AJAX
59+
* For DOM manipulation
60+
* Handlebars.js
61+
* For view templates
62+
63+
**Do NOT** use any of the following:
64+
* AngularJS
65+
* KendoUI framework
66+
* Data-binding, routing, ViewModels, etc...
67+
* Durandal
68+
* Backbone.js
69+
* Ember.js
70+
* Batman.js
71+
72+
You can use libraries for:
73+
* Notifications
74+
* toastr or any other
75+
* UI components
76+
* jQueryUI, KendoUI UI components, Twitter Bootstrap
77+
* Utils frameworks:
78+
* Moment, Underscore/Lodash
79+
* Libs for encryption
80+
* etc...
81+
* Routers
82+
* Only Sammy.js is allowed
83+
* Module loading
84+
* System.js, RequireJS or other
85+
86+
## Server routes
87+
88+
### Users
89+
90+
* `api/users`
91+
* GET
92+
* **Returns** all users
93+
* POST
94+
* **Registers** a new user
95+
* Needs **username** and **passHash** to be sent in the body of the request
96+
* `api/auth`
97+
* PUT
98+
* **Logs in** an user
99+
* Needs **username** and **passHash** to be sent in the body of the request
100+
* If the request is valid returns **username** and **authKey**
101+
102+
### Fortune Cookies
103+
104+
* `api/cookies`
105+
* GET
106+
* **Returns** all shared fortune cookies
107+
* POST
108+
* **Shares** or **re-shares** a new fortune cookie
109+
* User must be **logged-in**
110+
* Needs **text**, **category**, **img** to be sent in the body of the request and the current user's **authKey** as a header with name: `x-auth-key`
111+
* `img` is a string to an online image
112+
* `img` is optional and if not sent, a **default batman** image will be provided
113+
* `api/cookies/:id`
114+
* PUT
115+
* **Likes** or **dislikes** a fortune cookie
116+
* Needs `type` to be sent in the body of the request
117+
* `type` can only have values 'like' or 'dislike'
118+
119+
### My Fortune Cookies
120+
121+
* `api/my-cookie`
122+
* GET
123+
* **Returns** the hourly fortune cookie
124+
125+
### Categories
126+
* `api/categories`
127+
* GET
128+
* **Returns** an array with all categories
129+
130+
## Deliverables
131+
132+
* **ZIP** all your code, including the server
133+
* Remove only the `node_modules` folder
134+
* Send the ZIP file on the page, provided in http://telerikacademy.com
135+
136+
## Constraints and validation
137+
* Username
138+
* A string
139+
* Has length between 6 and 30
140+
* Can contain only Latin letters, digits and the characters '\_' and '.'
141+
* Fortune Cookie Text
142+
* A string
143+
* Has length between 6 and 30
144+
* Can contain any characters
145+
* Fortune Cookie Category
146+
* A string
147+
* Has length between 6 and 30
148+
* Can contain any characters
149+
* Fortune Cookie Img
150+
* A string
151+
* Must be a valid url address
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
var express = require('express'),
2+
bodyParser = require('body-parser'),
3+
lowdb = require('lowdb');
4+
5+
var db = lowdb('./data/data.json');
6+
db._.mixin(require('underscore-db'));
7+
8+
var app = express();
9+
app.use(bodyParser.json());
10+
11+
app.use(express.static('public'));
12+
13+
require('./utils/authorize-user')(app, db);
14+
15+
//User routes
16+
var usersController = require('./controllers/users-controller')(db);
17+
app.get('/api/users', usersController.get);
18+
app.post('/api/users', usersController.post);
19+
app.put('/api/auth', usersController.put);
20+
21+
// Fortune cookies
22+
var cookiesController = require('./controllers/cookies-controller')(db);
23+
app.get('/api/cookies', cookiesController.get);
24+
app.post('/api/cookies', cookiesController.post);
25+
app.put('/api/cookies/:id', cookiesController.put);
26+
//
27+
// My fortune cookies
28+
var myCookiesController = require('./controllers/my-cookies-controller')(db);
29+
app.get('/api/my-cookie', myCookiesController.get);
30+
31+
// Categories
32+
var categoriesController = require('./controllers/categories-controller')(db);
33+
app.get('/api/categories', categoriesController.get);
34+
35+
var port = process.env.PORT || 3000;
36+
app.listen(port, function() {
37+
console.log('Server is running at http://localhost:' + port);
38+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "Forture-Cookies-Sep-2015",
3+
"version": "0.0.0",
4+
"authors": [
5+
"AzSymDoncho az_sym@minkov.it>"
6+
],
7+
"license": "MIT",
8+
"ignore": [
9+
"**/.*",
10+
"node_modules",
11+
"bower_components",
12+
"public/bower_components",
13+
"test",
14+
"tests"
15+
],
16+
"dependencies": {
17+
"bootstrap": "~3.3.5",
18+
"handlebars": "~4.0.2",
19+
"jquery": "~2.1.4",
20+
"jquery-ui": "~1.11.4",
21+
"lodash": "~3.10.1",
22+
"moment": "~2.10.6",
23+
"sammy": "~0.7.6",
24+
"sha1": "http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js",
25+
"toastr": "~2.1.2"
26+
}
27+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[null]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
var _ = require('lodash');
2+
3+
module.exports = function(db) {
4+
5+
function get(req, res) {
6+
var categories = _.chain(db('cookies'))
7+
.map(function(cookie) {
8+
return cookie.category;
9+
}).uniq()
10+
.value();
11+
res.json({
12+
result: categories
13+
});
14+
}
15+
16+
return {
17+
get: get
18+
};
19+
};
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
var _ = require('lodash');
2+
const PAGE_SIZE = 10,
3+
DEFAULT_COOKIE_IMAGE = 'https://dayinthelifeofapurpleminion.files.wordpress.com/2014/12/batman-exam.jpg';
4+
5+
module.exports = function(db) {
6+
7+
function _validate(cookie) {
8+
9+
}
10+
11+
function get(req, res) {
12+
var cookies = _.chain(db('cookies'))
13+
.sortBy(function(cookie) {
14+
return -cookie.likes || (cookie.postDate - 0);
15+
});
16+
17+
res.json({
18+
result: cookies
19+
});
20+
}
21+
22+
function post(req, res) {
23+
var user = req.user;
24+
if (!user) {
25+
res.status(401)
26+
.json('User not authorized');
27+
return;
28+
}
29+
var cookie = req.body;
30+
var validationError = _validate(cookie);
31+
if (validationError) {
32+
res.status(400)
33+
.json(validationError.message);
34+
return;
35+
}
36+
cookie.userId = user.id;
37+
cookie.likes = 0;
38+
cookie.dislikes = 0;
39+
cookie.img = cookie.img || DEFAULT_COOKIE_IMAGE;
40+
cookie.shareDate = new Date();
41+
db('cookies').insert(cookie);
42+
res.json({
43+
result: cookie
44+
});
45+
}
46+
47+
function put(req, res) {
48+
var user = req.user;
49+
if (!user) {
50+
res.status(401)
51+
.json('User not authorized');
52+
return;
53+
}
54+
55+
var cookieId = req.params.id;
56+
var cookie = db('cookies').find({
57+
id: cookieId
58+
});
59+
60+
if (!cookie) {
61+
res.status(404)
62+
.json('Invalid cookie ID');
63+
return;
64+
}
65+
var type = req.body.type;
66+
if (['like', 'dislike'].indexOf(type) < 0) {
67+
res.status(400)
68+
.json('Request type must be either "like" or "dislike"');
69+
return;
70+
}
71+
72+
if (req.body.type === 'like') {
73+
cookie.likes += 1;
74+
} else {
75+
cookie.dislikes += 1;
76+
}
77+
db.save();
78+
79+
res.json({
80+
result: cookie
81+
});
82+
}
83+
84+
return {
85+
get: get,
86+
post: post,
87+
put: put
88+
};
89+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
var _ = require('lodash');
2+
const PAGE_SIZE = 10,
3+
DEFAULT_COOKIE_IMAGE = 'https://dayinthelifeofapurpleminion.files.wordpress.com/2014/12/batman-exam.jpg';
4+
5+
6+
module.exports = function(db) {
7+
8+
function getRandomCookie() {
9+
var cookies = db('cookies').value();
10+
var index = Math.floor(Math.random() * cookies.length);
11+
return cookies[index];
12+
}
13+
14+
function get(req, res) {
15+
var user = req.user;
16+
if (!user) {
17+
res.status(401)
18+
.json('User not authorized');
19+
return;
20+
}
21+
var myCookie;
22+
23+
if (user.myCookies) {
24+
myCookie = _.last(user.myCookies);
25+
var now = new Date().getHours();
26+
var myCookieTime = myCookie.hours;
27+
if (myCookieTime !== now) {
28+
myCookie = getRandomCookie();
29+
}
30+
} else {
31+
myCookie = getRandomCookie();
32+
}
33+
34+
user.myCookies = user.myCookies || [];
35+
36+
myCookie.hours = new Date().getHours();
37+
user.myCookies.push(myCookie);
38+
39+
db.save();
40+
41+
res.json({
42+
result: _.last(user.myCookies)
43+
});
44+
}
45+
46+
return {
47+
get: get
48+
};
49+
};

0 commit comments

Comments
 (0)