Skip to content

Commit 700f4de

Browse files
committed
Merge pull request #271 from CPSSD/a/user-option-items-per-page#167
Add items per page options
2 parents bf9d55c + 930f951 commit 700f4de

File tree

16 files changed

+258
-164
lines changed

16 files changed

+258
-164
lines changed

doc/db/user.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,48 @@ User Database Specifications
22
===========================
33

44
The user database is the database that holds data individual to each user,
5-
including authentification data.
6-
7-
It interfaces with the secretary, and is used in combination with the Feed
5+
including authentication data and is used in combination with the Feed
86
Database to create entries in the finished G2G database.
97

108
The user password will not be stored, instead, a salt and the hashed password
119
and salt will be stored. These are stored in the same string in bcrypt.
1210

11+
Tokens
12+
-------
13+
1314
Tokens represents the randomly generated API tokens. Currently they point to a
1415
true value but this could be modified such that each token had validation date
1516
and permissions.
1617

18+
Models
19+
-------
20+
1721
The `model` field holds a pickled scikit-learn `SGDClassifier`, ie. the
1822
classification model of that user. It is compiled from the data in the other
1923
fields, and is updated by the `update-user-model` Gearman worker.
2024

25+
Verification
26+
-------
27+
2128
When the user initially signs up, `verified` will be a token used to
2229
verify the address from the link in the email. It will be set to `true`
2330
permanently after verification.
2431

32+
Config
33+
------
34+
35+
Users have a set of default behaviour that they can set and these are stored
36+
in the `defaults` key.
37+
38+
#### Current Defaults:
39+
40+
```
41+
{
42+
// this refers to the default pagination size
43+
"page_length": int
44+
}
45+
```
46+
2547
Example Document
2648
----------------
2749

@@ -35,6 +57,7 @@ Example Document
3557
"password": "$2a$08$ThzPc3zm84JPb6LmvcfCkuXkwyh8H.Mn1VC4EKu9guksI9lbdb7Fa",
3658
"subscribed_feeds": ["news.ycombinator.com/rss", "pssd.computing.dcu.ie/rss.xml"],
3759
"words": {"butter":2, "milk":13, "antidisestablishmentarianism":-33},
60+
"defaults": {"page_length": 20}
3861
"tokens": {"add15f620657bb3fd8ce7fa9611f1aaba8717559295706a6d80f9e8cf58e81d7":true},
3962
"model": "ccopy_reg\n_reconstructor\np0\n(csklearn.linear_model.stochastic_gradient\nSGDClassifier\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS't_'\np6\ncnumpy.core.multiarray\nscalar\np7\n(cnumpy\ndtype\np8\n(S'f8'\np9\nI0\nI1\ntp10\nRp11\n(I3\nS'<'\np12\nNNNI-1\nI-1\nI0\ntp13\nbS'\\x00\\x00\\x00\\x00\\x00\\x98\\x8f@'\np14\ntp15\nRp16\nsS'n_jobs'\np17\nI1\nsS'shuffle'\np18\nI00\nsS'verbose'\np19\nI0\nsS'classes_'\np20\ncnumpy.core.multiarray\n_reconstruct\np21\n(cnumpy\nndarray\np22\n(I0\ntp23\nS'b'\np24\ntp25\nRp26\n(I1\n(I2\ntp27\ng8\n(S'i8'\np28\nI0\nI1\ntp29\nRp30\n(I3\nS'<'\np31\nNNNI-1\nI-1\nI0\ntp32\nbI00\nS'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\np33\ntp34\nbsS'class_weight'\np35\nNsS'fit_intercept'\np36\nI01\nsS'penalty'\np37\nS'l2'\np38\nsS'random_state'\np39\nNsS'loss_function'\np40\ncsklearn.linear_model.sgd_fast\nLog\np41\n(tRp42\nsS'C'\np43\nF1.0\nsS'n_iter'\np44\nI5\nsS'epsilon'\np45\nF0.1\nsS'learning_rate'\np46\nS'optimal'\np47\nsS'coef_'\np48\ng21\n(g22\n(I0\ntp49\ng24\ntp50\nRp51\n(I1\n(I1\nI2\ntp52\ng8\n(S'f8'\np53\nI0\nI1\ntp54\nRp55\n(I3\nS'<'\np56\nNNNI-1\nI-1\nI0\ntp57\nbI00\nS\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xdd}'\\xb4\\x00\\x94\\xd3\\xc2\"\np58\ntp59\nbsS'alpha'\np60\nF0.0001\nsS'intercept_'\np61\ng21\n(g22\n(I0\ntp62\ng24\ntp63\nRp64\n(I1\n(I1\ntp65\ng11\nI00\nS'\\x00\\x00\\x00\\x00\\x00\\x00\\x14\\xc0'\np66\ntp67\nbsS'_expanded_class_weight'\np68\ng21\n(g22\n(I0\ntp69\ng24\ntp70\nRp71\n(I1\n(I2\ntp72\ng11\nI00\nS'\\x00\\x00\\x00\\x00\\x00\\x00\\xf0?\\x00\\x00\\x00\\x00\\x00\\x00\\xf0?'\np73\ntp74\nbsS'warm_start'\np75\nI00\nsS'loss'\np76\nS'log'\np77\nsS'eta0'\np78\nF0.0\nsS'l1_ratio'\np79\nF0.15\nsS'power_t'\np80\nF0.5\nsb."
4063
}

server/controllers/bookmarks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module.exports = {
2727

2828
bookmarkModel.getBookmarks(req.session.username, bookmarks => {
2929
if (!bookmarks){
30-
bookmarks = []
30+
bookmarks = [];
3131
}
3232
// Filter the bookmarks
3333
var filtered_bookmarks = bookmarks.filter((feed, index, src) => {

server/controllers/streams.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@
66
*/
77

88
const getFeeds = require("../models/stream").getFeeds;
9+
const user = require("../controllers/users");
10+
const userModel = require("../models/user");
911
const _ = require("lodash");
1012

1113
// Stream listing
1214
module.exports = {
1315
index: (req, res) => {
1416

15-
// Get & verify the page length & number
1617
// This Lo-Dash function is lovely
1718
var page_length = _.toSafeInteger(req.query.page_length);
1819
if (page_length <= 0) {
19-
page_length = 20; // Default Page Length = 20
20+
page_length = req.session.page_length; // Use the user's default instead
2021
}
21-
var page = _.toSafeInteger(req.query.page); // defaults to 0 if undefined
2222

23+
var page = _.toSafeInteger(req.query.page); // defaults to 0 if undefined
2324
// Sort out the filters
2425
var keywords = [];
2526
if (typeof req.query.keywords != "undefined" && req.query.keywords.length > 1) {
@@ -30,7 +31,6 @@ module.exports = {
3031

3132
// Filter the feeds
3233
var filtered_feeds = feeds.filter((feed, index, src) => {
33-
3434
// Match with the filters
3535
return (typeof req.query.source == "undefined" || req.query.source.length < 1 || feed.feed == req.query.source) &&
3636
(keywords.length < 1 || keywords.every(val => feed.name.toLowerCase().includes(val)));
@@ -55,21 +55,21 @@ module.exports = {
5555
source: req.query.source || ""
5656
});
5757
});
58+
5859
},
5960

6061
plaintext: (req, res) => {
62+
res.type('.txt');
63+
64+
// This Lo-Dash function is lovely
6165
var page_length = _.toSafeInteger(req.query.page_length);
6266
if (page_length <= 0) {
63-
page_length = 20; // Default Page Length = 20
67+
page_length = req.session.page_length; // Use the user's default instead
6468
}
65-
var page = _.toSafeInteger(req.query.page);
66-
6769

70+
var page = _.toSafeInteger(req.query.page);
6871
var username = req.query.username;
69-
res.type('.txt');
70-
7172
getFeeds(username, feeds => {
72-
7373
var pageinated_feeds = _.slice(feeds, page*page_length, (page+1)*page_length);
7474
var next_page = page + 1;
7575
if ((page + 1) * page_length > feeds.length) next_page = 0;
@@ -81,5 +81,6 @@ module.exports = {
8181
page_length: page_length
8282
});
8383
});
84+
8485
}
8586
};

server/controllers/users.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ module.exports = {
9898

9999
// Set session vars and redirect
100100
req.session.username = user.username;
101+
req.session.page_length = user.page_length || userModel.defaultPageLength;
101102
req.session.subscribed_feeds = user.subscribed_feeds;
102103
req.session.verified = user.verified;
103104
req.session.msg = "Successfully logged in.";
@@ -198,6 +199,29 @@ module.exports = {
198199
});
199200
},
200201

202+
changePageLength: (req, res) => {
203+
var username = req.session.username;
204+
var page_length = _.toSafeInteger(req.body.page_length);
205+
206+
if (!(page_length == 5 || page_length == 10 || page_length == 20 || page_length == 50)) {
207+
req.session.msg = "Invalid page length";
208+
return res.redirect(302, "/user");
209+
}
210+
211+
userModel.findByUsername(username, user => {
212+
if (typeof user == "undefined") {
213+
req.session.msg = "Invalid username";
214+
return res.redirect(302, "/user");
215+
}
216+
217+
userModel.setPageLength(username, page_length, _ => {
218+
req.session.msg = "Successfully changed your defaults";
219+
req.session.page_length = page_length;
220+
return res.redirect(302, "/user");
221+
});
222+
});
223+
},
224+
201225
// Allow for password change
202226
changePassword: (req, res) => {
203227

@@ -251,7 +275,6 @@ module.exports = {
251275
// Load request vars & verify
252276
var username = req.session.username;
253277
var newEmail = req.body.newEmail;
254-
var oldEmail = req.body.oldEmail;
255278

256279
// Check validity of passwords
257280
if (newEmail.length < 5){
@@ -271,10 +294,7 @@ module.exports = {
271294
req.session.msg = "Invalid username.";
272295
return res.redirect(302, "/user");
273296
}
274-
if (user.email != oldEmail) {
275-
req.session.msg = "Incorrect current email.";
276-
return res.redirect(302, "/user");
277-
}
297+
278298
crypto.randomBytes(32, (err, buf) => {
279299
if (err) return res.status(500).render("signup", {err: "Failed to generate verification token:" + err, captcha: captcha_html});
280300
var token = buf.toString("hex");

server/middleware/db.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ module.exports = {
6969

7070
if (err) throw err;
7171

72+
return cb();
73+
});
74+
},
75+
76+
upsert: (db, collection, selector, cb) => {
77+
db.collection(collection).upsert(selector, {$set: data}, (err, data) => {
78+
79+
if (err) throw err;
80+
7281
return cb();
7382
});
7483
}

server/middleware/gearman.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ module.exports = {
3737
}
3838
client.connect( () => {
3939
if ('SECRETKEY' in process.env) {
40-
job_data['key'] = process.env['SECRETKEY'];
40+
job_data.key = process.env.SECRETKEY;
4141
}
4242
var bson_data = BSON.serialize(job_data, false, true, false);
4343
client.submitJob( job_name, data=bson_data , options=job_options);

server/middleware/routes.js

Lines changed: 41 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -24,113 +24,101 @@ function isAuthed(req, res, next) {
2424
}
2525
}
2626

27-
// User pages
27+
// Stream & Landing page
28+
router.get("/", (req, res, next) => {
29+
if (req.session.username) {
30+
if (process.env.ENVIRONMENT == "PRODUCTION" && typeof req.session.verified != "boolean") {
31+
res.redirect(302, "/user/verify");
32+
} else {
33+
res.locals.session = req.session;
34+
next();
35+
}
36+
} else {
37+
res.status(200).render('index');
38+
}
39+
}, streamController.index);
2840

29-
// Logout
30-
router.all("/user/logout", userController.logout);
3141

32-
// Signup
33-
router.all("/user/signup", userController.signup);
42+
// ## /user
3443

35-
// Login processing
44+
// ### Logout
45+
router.all("/user/logout", userController.logout);
46+
// ### Signup
47+
router.all("/user/signup", userController.signup);
48+
// ### Login processing
3649
router.post("/user/login", userController.login);
37-
38-
// Login form
50+
// ### Login form
3951
router.get("/user/login", (req, res) => {
4052
// Go to profile if user is already logged in
4153
if (typeof req.session.username != "undefined") return res.redirect(302, "/user");
4254

4355
res.render("login");
4456
});
45-
46-
// Verification action
57+
// ### Verification action
4758
router.get("/user/verify/:token", userController.verify);
48-
49-
// Verification asker
59+
// ### Verification asker
5060
router.get("/user/verify", (req, res) => {
5161
res.locals.session = req.session;
5262
res.render("verify_ask");
5363
});
54-
5564
// Profile
5665
router.get("/user", isAuthed, userController.profile);
57-
58-
// Summary sender
59-
router.get("/user/summaries/:key", userController.sendSummaries);
60-
6166
// Profile tokens
6267
router.get("/user/tokens", isAuthed, userController.profileTokens);
63-
6468
// Profile settings
6569
router.post("/user/change/password", isAuthed, userController.changePassword);
66-
67-
// Profile settings
6870
router.post("/user/change/email", isAuthed, userController.changeEmail);
71+
router.post("/user/change/page-length", isAuthed, userController.changePageLength);
72+
// Summary sender
73+
router.get("/user/summaries/:key", userController.sendSummaries);
6974

70-
// Profile settings
71-
router.post("/user/change/summaryinterval", isAuthed, userController.changeSummaryInterval);
75+
// ## /feeds
7276

73-
// Show interest
77+
// ### Profile settings
78+
router.post("/user/change/summaryinterval", isAuthed, userController.changeSummaryInterval);
79+
// ### Show interest
7480
router.post("/feeds/like", isAuthed, feedController.like);
75-
76-
// Show disinterest buttons
81+
// ### Show disinterest buttons
7782
router.post("/feeds/dislike", isAuthed, feedController.dislike);
78-
79-
// Add
83+
// ### Add
8084
router.get("/feeds/add", isAuthed, (req, res) => { res.render("feeds_add"); });
81-
82-
// Add processing
85+
// ### Add processing
8386
router.post("/feeds/add", isAuthed, feedController.add);
84-
85-
// Remove processing
87+
// ### Remove processing
8688
router.get("/feeds/remove", isAuthed, feedController.remove);
87-
88-
// List
89+
// ### List
8990
router.get("/feeds", isAuthed, feedController.index);
9091

91-
// Stream & Landing page
92-
router.get("/", (req, res, next) => {
93-
if (req.session.username) {
94-
if (process.env.ENVIRONMENT == "PRODUCTION" && typeof req.session.verified != "boolean") {
95-
res.redirect(302, "/user/verify");
96-
} else {
97-
res.locals.session = req.session;
98-
next();
99-
}
100-
} else {
101-
res.status(200).render('index');
102-
}
103-
}, streamController.index);
92+
// ## /bookmarks
10493

10594
// Display Bookmarks
10695
router.get("/bookmarks", isAuthed, bookmarkController.bookmarks);
107-
10896
// Add Bookmark
10997
router.post("/bookmarks/add", isAuthed, bookmarkController.add);
110-
11198
// Delete Bookmark
11299
router.post("/bookmarks/remove", isAuthed, bookmarkController.remove);
113100

114-
// Tokens (for API stuff!)
101+
// ## /token
102+
115103
router.get("/token/add", isAuthed, userController.addToken);
116104
router.get("/token/remove", isAuthed, userController.removeToken);
117105
router.get("/token/list", isAuthed, userController.listTokens);
118106

119-
// Plaintext Endpoint
107+
// ## Plaintext Endpoint
120108
router.get("/plaintext", userController.validToken, streamController.plaintext);
121109

122-
// Repo updater
110+
// ## Repo updater
123111
router.post("/pull/:token", updater.check, updater.run);
124112

125-
// Routes for ToS and Privacy Policy
113+
// ## Routes for ToS and Privacy Policy
126114
router.get("/terms", (req, res) => {
127115
return res.status(200).render("terms_of_service");
128116
});
129117
router.get("/privacy", (req, res) => {
130118
return res.status(200).render("privacy_policy");
131119
});
132120

133-
// 403 page
121+
// ## 403 page
134122
router.get("/forbidden", (req, res) => {
135123
res.locals.session = req.session;
136124
res.render("403");

server/models/bookmark.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ module.exports = {
5050
removeBookmark: (username, url, cb) => {
5151

5252
dbFuncs.transaction(db => dbFuncs.findOne(db, "bookmark", {username: username}, user => {
53-
53+
var index;
5454
// Check if the user is already subscribed to this feed
5555
for (var i = 0; i <= user.bookmarks.length; i++) {
5656
if (user.bookmarks[i].link == url){
57-
var index = i;
57+
index = i;
5858
break;
5959
} else if (i == user.bookmarks.length -1){
6060
return cb();
@@ -82,7 +82,7 @@ module.exports = {
8282
bookmarks: []
8383
}, cb));
8484
} else {
85-
cb(data.bookmarks)
85+
cb(data.bookmarks);
8686
}
8787
}));
8888
}

0 commit comments

Comments
 (0)