From 1808acb2dee6a14f7e7669765a503f2a177ec076 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Sun, 15 Nov 2015 01:41:01 -0500 Subject: [PATCH 01/71] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9966f17c..8ed641bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "FlairHQ", - "version": "2.2.3", + "version": "2.3.0", "description": "A project to allow easy adding of flair applications for subreddits (focusing initially on /r/pokemontrades) and easy moderation for moderators.", "scripts": { "start": "node ./node_modules/sails/bin/sails.js lift" From 9c5edadb5304f82a9834b71b85e8a5e9818fc1de Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Fri, 13 Nov 2015 00:26:29 -0500 Subject: [PATCH 02/71] Added backend for modmail archiving --- api/models/Modmail.js | 37 +++++++++++++++++++++++++++++++++++++ api/services/Modmails.js | 38 ++++++++++++++++++++++++++++++++++++++ api/services/Reddit.js | 9 +++++++++ 3 files changed, 84 insertions(+) create mode 100644 api/models/Modmail.js create mode 100644 api/services/Modmails.js diff --git a/api/models/Modmail.js b/api/models/Modmail.js new file mode 100644 index 00000000..93755970 --- /dev/null +++ b/api/models/Modmail.js @@ -0,0 +1,37 @@ +module.exports = { + + types: { + stringOrNull: function (val) { + return typeof val === 'string' || val === null; + } + }, + + autoPK: false, + + attributes: { + name: { //The fullname ('t4_' + base36id) of the message + columnName: 'id', + type: 'string', + unique: true, + primaryKey: true + }, + subject: 'string', //Subject of the message + body: 'string', //Body of the message + author: 'string', //Username of the message author + subreddit: { //The subreddit that the modmail was sent to + enum: ['pokemontrades', 'SVExchange'] + }, + first_message_name: { //The fullname of the first message in this chain, or null if this is the first message + stringOrNull: true + }, + created_utc: { //The UTC timestamp of when the message was created + type: 'integer' + }, + parent_id: { //The fullname of the parent message, or null if this is the first message + stringOrNull: true + }, + distinguished: { //This will be 'moderator' if the author was a mod, 'admin' if the author was a reddit admin, or null otherwise + enum: ['moderator', 'admin', null], + } + } +}; diff --git a/api/services/Modmails.js b/api/services/Modmails.js new file mode 100644 index 00000000..517e2069 --- /dev/null +++ b/api/services/Modmails.js @@ -0,0 +1,38 @@ +var relevantKeys = ['name', 'subject', 'body', 'author', 'subreddit', 'first_message_name', 'created_utc', 'parent_id', 'distinguished']; +var addModmailsToDatabase = async function (batch) { + if (!batch) { + return; + } + let modmails = batch.data.children; + for (let i = 0; i < modmails.length; i++) { + let compressed = {}; + for (let j = 0; j < relevantKeys.length; j++) { + compressed[relevantKeys[j]] = modmails[i].data[relevantKeys[j]]; + } + await Modmail.findOrCreate(compressed.name, compressed); + //Recursively add the replies to the database + await addModmailsToDatabase(modmails[i].data.replies); + } +}; +exports.updateArchive = async function (subreddit) { + let most_recent = await Modmail.find({subreddit: subreddit, limit: 1, sort: 'created_utc DESC'}); + var before = most_recent[0].first_message_name || most_recent[0].name; + if (!before) { + console.log('Archived modmail for ' + subreddit + ' could not be found for some reason. Recreating from scratch.'); + await exports.createArchiveFromScratch(subreddit); + return; + } + while (before !== null) { + let batch = await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit, undefined, before); + before = batch.data.before; + await addModmailsToDatabase(batch); + } +}; +exports.createArchiveFromScratch = async function (subreddit) { + var after = ''; + while (after !== null) { + let batch = await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit, after); + after = batch.data.after; + await addModmailsToDatabase(batch); + } +}; diff --git a/api/services/Reddit.js b/api/services/Reddit.js index 29feaf60..0df39a4e 100644 --- a/api/services/Reddit.js +++ b/api/services/Reddit.js @@ -137,6 +137,15 @@ exports.checkModeratorStatus = async function (refreshToken, username, subreddit return res.data.children.length !== 0; }; +exports.getModmail = async function (refreshToken, subreddit, after, before, limit) { + //after/before (mutually exclusive): Fullname of a modmail -- will start retrieving modmail from after/before this point + //limit: The limit on the number of modmails to display, max 100 + limit = limit || 100; + var url = 'https://oauth.reddit.com/r/' + subreddit + '/about/message/inbox?show=all&count=102&limit=' + limit; + url += after ? '&after=' + after : before ? '&before=' + before : ''; + return makeRequest(refreshToken, 'GET', url, undefined, 20); +}; + var updateRateLimits = function (res) { if (res && res.headers && res.headers['x-ratelimit-remaining'] && res.headers['x-ratelimit-reset']) { left = res.headers['x-ratelimit-remaining']; From 2d3a1b022a72cf3301026ad8151fbccc59429850 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Fri, 13 Nov 2015 01:24:04 -0500 Subject: [PATCH 03/71] Added daily task for modmail archives --- config/schedule.js | 19 +++++++++++++++++++ package.json | 1 + 2 files changed, 20 insertions(+) create mode 100644 config/schedule.js diff --git a/config/schedule.js b/config/schedule.js new file mode 100644 index 00000000..6135345f --- /dev/null +++ b/config/schedule.js @@ -0,0 +1,19 @@ +module.exports.schedule = { + sailsInContext: true, + tasks: { + updateModmail: { + cron : "0 8 * * *", + task : function (context, sails) { + console.log('[Daily task]: Updating modmail archives...'); + Promise.all([Modmails.updateArchive('pokemontrades'), Modmails.updateArchive('SVExchange')]).then(function (results) { + console.log('[Daily task]: Finished updating modmail archives.'); + }, function (error) { + console.log('There was an issue updating the modmail archives.'); + console.log(error); + + }); + }, + context : {} + } + } +}; diff --git a/package.json b/package.json index 8ed641bb..867883b3 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "sails": "~0.11.2", "sails-disk": "~0.10.8", "sails-hook-babel": "^5.0.1", + "sails-hook-schedule": "^0.2.1", "sails-mongo": "^0.11.4", "sails.io.js": "^0.11.7", "sha256": "^0.2.0", From 306a2d320c15cfce31665fa5beb4e3d6b2262f54 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Fri, 13 Nov 2015 01:41:20 -0500 Subject: [PATCH 04/71] Fix crash when there are no existing archives --- api/.eslintrc | 3 ++- api/models/Modmail.js | 2 +- api/services/Modmails.js | 6 +++--- api/services/Reddit.js | 2 +- config/schedule.js | 1 - 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/.eslintrc b/api/.eslintrc index e60f82fd..bf16da21 100644 --- a/api/.eslintrc +++ b/api/.eslintrc @@ -15,6 +15,7 @@ "Sessions": true, "Game": true, "Application": true, - "ModNote": true + "ModNote": true, + "Modmail": true } } \ No newline at end of file diff --git a/api/models/Modmail.js b/api/models/Modmail.js index 93755970..53d835bd 100644 --- a/api/models/Modmail.js +++ b/api/models/Modmail.js @@ -31,7 +31,7 @@ module.exports = { stringOrNull: true }, distinguished: { //This will be 'moderator' if the author was a mod, 'admin' if the author was a reddit admin, or null otherwise - enum: ['moderator', 'admin', null], + enum: ['moderator', 'admin', null] } } }; diff --git a/api/services/Modmails.js b/api/services/Modmails.js index 517e2069..2707117d 100644 --- a/api/services/Modmails.js +++ b/api/services/Modmails.js @@ -16,12 +16,12 @@ var addModmailsToDatabase = async function (batch) { }; exports.updateArchive = async function (subreddit) { let most_recent = await Modmail.find({subreddit: subreddit, limit: 1, sort: 'created_utc DESC'}); - var before = most_recent[0].first_message_name || most_recent[0].name; - if (!before) { - console.log('Archived modmail for ' + subreddit + ' could not be found for some reason. Recreating from scratch.'); + if (!most_recent.length) { + console.log('Modmail archives for /r/' + subreddit + ' could not be found for some reason. Recreating from scratch...'); await exports.createArchiveFromScratch(subreddit); return; } + var before = most_recent[0].first_message_name || most_recent[0].name; while (before !== null) { let batch = await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit, undefined, before); before = batch.data.before; diff --git a/api/services/Reddit.js b/api/services/Reddit.js index 0df39a4e..fb3b3752 100644 --- a/api/services/Reddit.js +++ b/api/services/Reddit.js @@ -141,7 +141,7 @@ exports.getModmail = async function (refreshToken, subreddit, after, before, lim //after/before (mutually exclusive): Fullname of a modmail -- will start retrieving modmail from after/before this point //limit: The limit on the number of modmails to display, max 100 limit = limit || 100; - var url = 'https://oauth.reddit.com/r/' + subreddit + '/about/message/inbox?show=all&count=102&limit=' + limit; + var url = 'https://oauth.reddit.com/r/' + subreddit + '/message/moderator?show=all&count=102&limit=' + limit; url += after ? '&after=' + after : before ? '&before=' + before : ''; return makeRequest(refreshToken, 'GET', url, undefined, 20); }; diff --git a/config/schedule.js b/config/schedule.js index 6135345f..c6f45c46 100644 --- a/config/schedule.js +++ b/config/schedule.js @@ -10,7 +10,6 @@ module.exports.schedule = { }, function (error) { console.log('There was an issue updating the modmail archives.'); console.log(error); - }); }, context : {} From 773db64fa3ac62ad5ffad73998c6322e1bc96287 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Fri, 13 Nov 2015 18:02:41 -0500 Subject: [PATCH 05/71] Improved ban function --- api/controllers/UserController.js | 159 ++++++++++++++---------------- api/services/Ban.js | 78 ++++++++++----- api/services/Reddit.js | 32 +++--- assets/js/banCtrl.js | 30 +++--- views/home/banuser.ejs | 9 +- 5 files changed, 166 insertions(+), 142 deletions(-) diff --git a/api/controllers/UserController.js b/api/controllers/UserController.js index b3da899f..edd2e760 100644 --- a/api/controllers/UserController.js +++ b/api/controllers/UserController.js @@ -179,27 +179,28 @@ module.exports = { }); }, - ban: function (req, res) { + ban: async function (req, res) { /* Form parameters: - req.params.username: The user who is being banned (String) - req.params.banNote: The ban reason to go on the mod log (not visible to banned user, 300 characters max) (String) - req.params.banMessage: The note that gets sent with the "you are banned" PM (String) - req.params.banlistEntry: The ban reason to appear on the public banlist (String) - req.params.duration: The number of days that the user will be banned for. (Integer) - req.params.additionalFCs: A list of additional friend codes that should be banned. (Array of Strings) - Ban process: - 1. Ban user from /r/pokemontrades - 2. Ban user from /r/SVExchange - 3. Add "BANNED USER" to user's flair on /r/pokemontrades - 4. Add "BANNED USER" to user's flair on /r/SVExchange - 5. Add user's friend code to /r/pokemontrades AutoModerator config (2 separate lists) - 6. Add user's friend code to /r/SVExchange AutoModerator config (2 separate lists) - 7. Add a usernote for the user on /r/pokemontrades - 8. Add a usernote for the user on /r/SVExchange - 9. Remove all of the user's TSV threads on /r/SVExchange - 10. Add user's info to banlist wiki on /r/pokemontrades - 11. Locally ban user from FlairHQ - */ + req.params.username: The user who is being banned (String) + req.params.banNote: The ban reason to go on the mod log (not visible to banned user, 300 characters max) (String) + req.params.banMessage: The note that gets sent with the "you are banned" PM (String) + req.params.banlistEntry: The ban reason to appear on the public banlist (String) + req.params.duration: The number of days that the user will be banned for. (Integer) + req.params.knownAlt: Known alt of the user for the public banlist (String) + req.params.additionalFCs: A list of additional friend codes that should be banned. (Array of Strings) + Ban process: + 1. Ban user from /r/pokemontrades + 2. Ban user from /r/SVExchange + 3. Add "BANNED USER" to user's flair on /r/pokemontrades + 4. Add "BANNED USER" to user's flair on /r/SVExchange + 5. Add user's friend code to /r/pokemontrades AutoModerator config (2 separate lists) + 6. Add user's friend code to /r/SVExchange AutoModerator config (2 separate lists) + 7. Add a usernote for the user on /r/pokemontrades + 8. Add a usernote for the user on /r/SVExchange + 9. Remove all of the user's TSV threads on /r/SVExchange + 10. Add user's info to banlist wiki on /r/pokemontrades + 11. Locally ban user from FlairHQ + */ req.params = req.allParams(); @@ -226,6 +227,10 @@ module.exports = { return res.status(400).json({error: "Invalid duration"}); } + if (req.params.knownAlt && (typeof req.params.knownAlt !== 'string' || !req.params.knownAlt.match(/^[A-Za-z0-9_-]{1,20}$/))) { + return res.status(400).json({error: "Invalid username of alt"}); + } + if (!(req.params.additionalFCs instanceof Array)) { return res.status(400).json({error: "Invalid friendcode list"}); } @@ -235,84 +240,66 @@ module.exports = { } } console.log("/u/" + req.user.name + ": Started process to ban /u/" + req.params.username); - User.findOne(req.params.username, function (finding_user_error, user) { - Reddit.getBothFlairs(req.user.redToken, req.params.username).then(function (flairs) { - var flair1 = flairs[0]; - var flair2 = flairs[1]; - if (flair1 && flair1.flair_css_class && flair1.flair_text) { - if (flair1.flair_css_class.indexOf(' ') === -1) { - flair1.flair_css_class += ' banned'; - } else { - flair1.flair_css_class = flair1.flair_css_class.substring(0, flair1.flair_css_class.indexOf(' ')) + ' banned'; - } - } else { - flair1 = {flair_css_class: 'default banned'}; - flair1.flair_text = ''; - } - if (flair2 && flair2.flair_text) { - if (flair2.flair_css_class) { - flair2.flair_css_class += ' banned'; - } - else { - flair2.flair_css_class = 'banned'; - } - } else { - flair2 = {flair_css_class: 'banned'}; - flair2.flair_text = ''; - } - var logged_fcs; - if (user) { - logged_fcs = user.loggedFriendCodes; - } - var unique_fcs = _.union( - flair1.flair_text.match(/(\d{4}-){2}\d{4}/g), - flair2.flair_text.match(/(\d{4}-){2}\d{4}/g), - logged_fcs, - req.params.additionalFCs - ); - var igns = flair1.flair_text.substring(flair1.flair_text.indexOf("||") + 3); - var promises = []; - promises.push(Ban.banFromSub(req.user.redToken, req.params.username, req.params.banMessage, req.params.banNote, 'pokemontrades', req.params.duration)); - promises.push(Ban.banFromSub(req.user.redToken, req.params.username, req.params.banMessage, req.params.banNote, 'SVExchange', req.params.duration)); - promises.push(Ban.addUsernote(req.user.redToken, req.user.name, 'pokemontrades', req.params.username, req.params.banNote, req.params.duration)); - promises.push(Ban.addUsernote(req.user.redToken, req.user.name, 'SVExchange', req.params.username, req.params.banNote, req.params.duration)); - if (!req.params.duration) { - promises.push(Ban.giveBannedUserFlair(req.user.redToken, req.params.username, flair1.flair_css_class, flair1.flair_text, 'pokemontrades')); - promises.push(Ban.giveBannedUserFlair(req.user.redToken, req.params.username, flair2.flair_css_class, flair2.flair_text, 'SVExchange')); - promises.push(Ban.updateAutomod(req.user.redToken, req.params.username, 'pokemontrades', unique_fcs)); - promises.push(Ban.updateAutomod(req.user.redToken, req.params.username, 'SVExchange', unique_fcs)); - promises.push(Ban.removeTSVThreads(req.user.redToken, req.params.username)); - promises.push(Ban.updateBanlist(req.user.redToken, req.params.username, req.params.banlistEntry, unique_fcs, igns)); - promises.push(Ban.localBanUser(req.params.username)); + var user; + try { + user = await User.findOne(req.params.username); + if (!user) { + if (await Reddit.checkUsernameAvailable(req.params.username)) { + console.log("Ban aborted (user does not exist)"); + return res.status(404).json({error: "That user does not exist."}); } - Promise.all(promises).then(function () { - res.ok(); - }, function (error) { - console.log(error); - res.status(500).json(error); - }); - Event.create({ - user: req.user.name, - type: "banUser", - content: "Banned /u/" + req.params.username - }).exec(function () { - }); - }, function (err) { - return res.serverError(err); + } + } catch (err) { + console.log(err); + return res.status(500).json(err); + } + Reddit.getBothFlairs(req.user.redToken, req.params.username).then(function (flairs) { + var logged_fcs = user ? user.loggedFriendCodes : []; + var fc_match = /(\d{4}-){2}\d{4}/g; + var unique_fcs = _.union(flairs[0].flair_text.match(fc_match), flairs[1].flair_text.match(fc_match), logged_fcs, req.params.additionalFCs); + var igns = flairs[0].flair_text.substring(flairs[0].flair_text.indexOf("||") + 3); + var promises = []; + promises.push(Ban.banFromSub(req.user.redToken, req.params.username, req.params.banMessage, req.params.banNote, 'pokemontrades', req.params.duration)); + promises.push(Ban.banFromSub(req.user.redToken, req.params.username, req.params.banMessage, req.params.banNote, 'SVExchange', req.params.duration)); + if (!req.params.duration) { + promises.push(Ban.giveBannedUserFlair(req.user.redToken, req.params.username, flairs[0] && flairs[0].flair_css_class, flairs[0] && flairs[0].flair_text, 'pokemontrades')); + promises.push(Ban.giveBannedUserFlair(req.user.redToken, req.params.username, flairs[0] && flairs[1].flair_css_class, flairs[1] && flairs[1].flair_text, 'SVExchange')); + promises.push(Ban.updateAutomod(req.user.redToken, req.params.username, 'pokemontrades', unique_fcs)); + promises.push(Ban.updateAutomod(req.user.redToken, req.params.username, 'SVExchange', unique_fcs)); + promises.push(Ban.addUsernote(req.user.redToken, req.user.name, 'pokemontrades', req.params.username, req.params.banNote)); + promises.push(Ban.addUsernote(req.user.redToken, req.user.name, 'SVExchange', req.params.username, req.params.banNote)); + promises.push(Ban.removeTSVThreads(req.user.redToken, req.params.username)); + promises.push(Ban.updateBanlist(req.user.redToken, req.params.username, req.params.banlistEntry, unique_fcs, igns, req.params.knownAlt)); + promises.push(Ban.localBanUser(req.params.username)); + } + Promise.all(promises).then(function(result) { + console.log('Process to ban /u/' + req.params.username + 'was completed successfully.'); + res.ok(); + }, function(error) { + console.log(error); + res.status(error.statusCode || 500).json(error); }); + Event.create({ + user: req.user.name, + type: "banUser", + content: "Banned /u/" + req.params.username + }); + }, function (err) { + console.log(err); + res.status(500).json(err); }); }, setLocalBan: function (req, res) { - User.update(req.allParams().username, {banned: req.allParams().ban}).exec(function (err, user) { + User.update(req.allParams().username, {banned: req.allParams().ban}).exec(function (err, users) { if (err) { console.log(err); return res.serverError(err); } - if (!user) { + if (!users.length) { return res.notFound(); } - return res.ok(user[0]); + return res.ok(users[0]); }); }, diff --git a/api/services/Ban.js b/api/services/Ban.js index cd7de2ae..6b2a3171 100644 --- a/api/services/Ban.js +++ b/api/services/Ban.js @@ -1,3 +1,4 @@ +var _ = require('lodash'); exports.banFromSub = async function (redToken, username, banMessage, banNote, subreddit, duration) { try { await Reddit.banUser(redToken, username, banMessage, banNote, subreddit, duration); @@ -9,8 +10,15 @@ exports.banFromSub = async function (redToken, username, banMessage, banNote, su }; //Give the 'BANNED USER' flair on a subreddit -exports.giveBannedUserFlair = async function (redToken, username, css_class, flair_text, subreddit) { +exports.giveBannedUserFlair = async function (redToken, username, current_css_class, current_flair_text, subreddit) { try { + var flair_text = current_flair_text || ''; + var css_class; + if (subreddit === 'pokemontrades') { + css_class = current_css_class ? current_css_class.replace(/ [^ ]*/, '') + ' banned' : 'default banned'; + } else { + css_class = current_css_class ? current_css_class + ' banned' : 'banned'; + } await Reddit.setFlair(redToken, username, css_class, flair_text, subreddit); console.log('Changed ' + username + '\'s flair to ' + css_class + ' on /r/' + subreddit); return 'Changed ' + username + '\'s flair to ' + css_class + ' on /r/' + subreddit; @@ -38,7 +46,10 @@ exports.updateAutomod = async function (redToken, username, subreddit, friend_co var end_delimiter_index = lines[fclist_indices[listno]].lastIndexOf(punctuation[0]); var before_end = lines[fclist_indices[listno]].substring(0, end_delimiter_index); for (var i = 0; i < friend_codes.length; i++) { - before_end += punctuation[1] + friend_codes[i].replace(/-/g, punctuation[2]) + punctuation[3]; + let formatted = friend_codes[i].replace(/-/g, punctuation[2]); + if (lines[fclist_indices[listno]].indexOf(formatted) === -1) { + before_end += punctuation[1] + formatted + punctuation[3]; + } } lines[fclist_indices[listno]] = before_end + lines[fclist_indices[listno]].substring(end_delimiter_index); } @@ -66,18 +77,41 @@ exports.removeTSVThreads = async function (redToken, username) { return output; }; //Update the public banlist with the user's information -exports.updateBanlist = async function (redToken, username, banlistEntry, friend_codes, igns) { +exports.updateBanlist = async function (redToken, username, banlistEntry, friend_codes, igns, knownAlt) { + var valid_FCs = friend_codes.filter(Flairs.validFC); + if (valid_FCs.length) { + friend_codes = valid_FCs; + } var current_list = await Reddit.getWikiPage(redToken, 'pokemontrades', 'banlist'); var lines = current_list.replace(/\r/g, '').split("\n"); var start_index = lines.indexOf('[//]:# (BEGIN BANLIST)') + 3; - if (start_index == 2) { - console.log('Error: Could not find start marker in public banlist'); - throw {error: 'Error while parsing public banlist'}; + var end_index = lines.indexOf('[//]:# (END BANLIST)') + if (start_index === 2 || end_index === -1) { + console.log('Error: Could not find parsing marker in public banlist'); + throw {error: 'Error: Could not find parsing marker in public banlist'}; + } + var updated_content = ''; + for (let i = start_index; i < end_index; i++) { + if (knownAlt && lines[i].match(new RegExp('/u/' + knownAlt)) || _.intersection(lines[i].match(/(\d{4}-){2}\d{4}/g), friend_codes).length) { + // User was an alt account, modify the existing line instead of creating a new one + let blocks = lines[i].split(' | '); + if (blocks.length !== 4) { + break; + } + blocks[0] += ', /u/' + username; + blocks[1] = _.union(blocks[1].match(/(\d{4}-){2}\d{4}/g), friend_codes).join(', '); + blocks[3] = _.union(blocks[3].split(', '), [igns]).join(', '); + let new_line = blocks.join(' | '); + var updated_content = lines.slice(0, start_index).concat(new_line).concat(lines.slice(start_index, i)).concat(lines.slice(i + 1)).join('\n'); + } + } + if (!updated_content) { + // User was probably not an alt, create a new line + let new_line = ['/u/' + username, friend_codes.join(', '), banlistEntry, igns].join(' | '); + updated_content = lines.slice(0, start_index).concat(new_line).concat(lines.slice(start_index)).join('\n'); } - var line_to_add = '/u/' + username + ' | ' + friend_codes.join(', ') + ' | ' + banlistEntry + ' | ' + igns; - var content = lines.slice(0, start_index).join("\n") + "\n" + line_to_add + "\n" + lines.slice(start_index).join("\n"); try { - await Reddit.editWikiPage(redToken, 'pokemontrades', 'banlist', content, ''); + await Reddit.editWikiPage(redToken, 'pokemontrades', 'banlist', updated_content, ''); } catch (e) { console.log(e); throw {error: 'Failed to update public banlist'}; @@ -85,23 +119,15 @@ exports.updateBanlist = async function (redToken, username, banlistEntry, friend console.log('Added /u/' + username + ' to public banlist'); return 'Added /u/' + username + ' to public banlist'; }; -exports.localBanUser = async function (username) { - User.findOne({name: username}).exec(function (err, user) { - if (!user) { - console.log('/u/' + username + ' was not locally banned because that user does not exist in the FlairHQ database.'); - return '/u/' + username + ' was not locally banned because that user does not exist in the FlairHQ database.'; - } - else { - user.banned = true; - user.save(function (err) { - if (err) { - throw {error: 'Error banning user from local FlairHQ database'}; - } - console.log('Banned /u/' + username + ' from local FlairHQ database'); - return 'Banned /u/' + username + ' from local FlairHQ database'; - }); - } - }); +exports.localBanUser = async function(username) { + try { + let update = await User.update(username, {banned: true}); + console.log('Updated local banlist'); + return update; + } catch (err) { + console.log(err); + throw {error: 'Failed to locally ban /u/' + username}; + } }; exports.addUsernote = function (redToken, modname, subreddit, username, banNote, duration) { var type = duration ? 'ban' : 'permban'; diff --git a/api/services/Reddit.js b/api/services/Reddit.js index fb3b3752..ecc7fadc 100644 --- a/api/services/Reddit.js +++ b/api/services/Reddit.js @@ -3,7 +3,7 @@ var request = require("request-promise"), NodeCache = require('node-cache'), left = 600, resetTime = moment().add(600, "seconds"), - userAgent = "Webpage:hq.porygon.co:v" + sails.config.version; + userAgent = "Webpage:hq.porygon.co/info:v" + sails.config.version; var cache = new NodeCache({stdTTL: 3480}); // Cached tokens expire after 58 minutes, leave a bit of breathing room in case stuff is slow exports.refreshToken = async function(refreshToken) { @@ -23,6 +23,8 @@ exports.refreshToken = async function(refreshToken) { "Content-Type": "application/x-www-form-urlencoded", "Content-Length": data.length } + }).catch(function (error) { + throw {statusCode: 502, error: 'Error retrieving token; Reddit responded with status code ' + error.statusCode}; }); if (body && body.access_token) { cache.set(refreshToken, body.access_token); @@ -33,13 +35,13 @@ exports.refreshToken = async function(refreshToken) { }; var makeRequest = async function (refreshToken, requestType, url, data, rateLimitRemainingThreshold) { - let token = await exports.refreshToken(refreshToken); if (left < rateLimitRemainingThreshold && moment().before(resetTime)) { - throw "Rate limited"; + throw {statusCode: 504, error: "Rate limited"}; + } + var headers = {"User-Agent": userAgent}; + if (url.indexOf("oauth.reddit.com") !== -1) { + headers.Authorization = "bearer " + await exports.refreshToken(refreshToken); } - var headers = { - Authorization: "bearer " + token, "User-Agent": userAgent - }; var options = { url: url, headers: headers, @@ -47,20 +49,18 @@ var makeRequest = async function (refreshToken, requestType, url, data, rateLimi method: requestType, formData: data }; - let response = await request(options); + let response = await request(options).catch(function (error) { + console.log('Reddit error: ' + requestType + ' request sent to ' + url + ' returned ' + error.statusCode + + ' - ' + error.statusMessage + '\nForm data sent: ' + JSON.stringify(data)); + throw {statusCode: 502, error: 'Reddit responded with status code ' + error.statusCode}; + }); updateRateLimits(response); var bodyJson; try { bodyJson = JSON.parse(response.body); } catch (error) { console.log("Error with parsing: " + response.body); - throw "Error with parsing: " + response.body; - } - - if (response.statusCode !== 200) { - console.log('Reddit error: ' + requestType + ' request sent to ' + url + ' returned ' + response.statusCode + - ' - ' + response.statusMessage + '\nForm data sent: ' + JSON.stringify(data)); - throw response.statusMessage; + throw {error: "Error with parsing: " + response.body}; } return bodyJson; }; @@ -86,6 +86,10 @@ exports.setFlair = function (refreshToken, name, cssClass, text, subreddit) { return makeRequest(refreshToken, 'POST', url, data, 5); }; +exports.checkUsernameAvailable = async function (name) { + return makeRequest(undefined, 'GET', 'https://www.reddit.com/api/username_available.json?user=' + name, undefined, 10); +}; + exports.banUser = function (refreshToken, username, ban_message, note, subreddit, duration) { var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : subreddit; var url = 'https://oauth.reddit.com/r/' + actual_sub + '/api/friend'; diff --git a/assets/js/banCtrl.js b/assets/js/banCtrl.js index fa1a84b4..27b25765 100644 --- a/assets/js/banCtrl.js +++ b/assets/js/banCtrl.js @@ -9,6 +9,7 @@ module.exports = function ($scope) { banMessage: "", banlistEntry: "", duration: "", + knownAlt: "", additionalFCs: "" }; @@ -31,18 +32,17 @@ module.exports = function ($scope) { return; } - if ($scope.banInfo.username.substring(0, 3) === '/u/') { - $scope.banInfo.username = $scope.banInfo.username.substring(3); - } - - else if ($scope.banInfo.username.substring(0, 2) === 'u/') { - $scope.banInfo.username = $scope.banInfo.username.substring(2); - } + var names = ['username', 'knownAlt']; + for (let i = 0; i < names.length; i++) { - if (!$scope.banInfo.username.match(/^[A-Za-z0-9_-]{1,20}$/)) { - $scope.banError = "Invalid username"; - $scope.indexSpin.ban = false; - return; + if ($scope.banInfo[names[i]].match(/^\/?u\//)) { + $scope.banInfo[names[i]] = $scope.banInfo[names[i]].substring($scope.banInfo[names[i]].indexOf('u/') + 2); + } + if ($scope.banInfo[names[i]] && !$scope.banInfo[names[i]].match(/^[A-Za-z0-9_-]{1,20}$/)) { + $scope.banError = "Invalid " + names[i]; + $scope.indexSpin.ban = false; + return; + } } if ($scope.banInfo.banNote.length > 300) { @@ -79,6 +79,7 @@ module.exports = function ($scope) { "banMessage": $scope.banInfo.banMessage, "banlistEntry": $scope.banInfo.banlistEntry, "duration": parseInt($scope.banInfo.duration), + "knownAlt": $scope.banInfo.knownAlt, "additionalFCs": FCs }; @@ -91,6 +92,7 @@ module.exports = function ($scope) { $scope.banInfo.banMessage = ""; $scope.banInfo.banlistEntry = ""; $scope.banInfo.duration = ""; + $scope.banInfo.knownAlt = ""; $scope.banInfo.additionalFCs = ""; $scope.indexOk.ban = true; window.setTimeout(function () { @@ -99,11 +101,11 @@ module.exports = function ($scope) { }, 1500); $scope.$apply(); } else { - $scope.indexOk = false; + $scope.indexOk.ban = false; if (res.body.error) { - $scope.banError = "Something went wrong; you might have to do stuff manually. Error " + res.statusCode + ": " + res.body.error; + $scope.banError = "Error " + res.statusCode + ": " + res.body.error; } else { - $scope.banError = "Something went wrong; you might have to do stuff manually."; + $scope.banError = "Error " + res.statusCode; } $scope.$apply(); } diff --git a/views/home/banuser.ejs b/views/home/banuser.ejs index 0caebed0..18d1a5d8 100644 --- a/views/home/banuser.ejs +++ b/views/home/banuser.ejs @@ -10,7 +10,7 @@ + title="The reason for the ban. The banned user will not see this. 300 characters max."> @@ -20,7 +20,7 @@ + title="The ban reason that will get listed on the public banlist page, e.g. 'Scamming'. This field will be ignored if the user is an alt of another banned user."> @@ -29,6 +29,11 @@ + + + + From 0e0af2bc6eaa792c24c3868cb7343b78629db45a Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Sat, 14 Nov 2015 12:37:29 -0500 Subject: [PATCH 06/71] Got rid of lint --- api/controllers/UserController.js | 2 +- api/services/Ban.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/controllers/UserController.js b/api/controllers/UserController.js index edd2e760..71604278 100644 --- a/api/controllers/UserController.js +++ b/api/controllers/UserController.js @@ -272,7 +272,7 @@ module.exports = { promises.push(Ban.updateBanlist(req.user.redToken, req.params.username, req.params.banlistEntry, unique_fcs, igns, req.params.knownAlt)); promises.push(Ban.localBanUser(req.params.username)); } - Promise.all(promises).then(function(result) { + Promise.all(promises).then(function () { console.log('Process to ban /u/' + req.params.username + 'was completed successfully.'); res.ok(); }, function(error) { diff --git a/api/services/Ban.js b/api/services/Ban.js index 6b2a3171..11d2e78a 100644 --- a/api/services/Ban.js +++ b/api/services/Ban.js @@ -85,7 +85,7 @@ exports.updateBanlist = async function (redToken, username, banlistEntry, friend var current_list = await Reddit.getWikiPage(redToken, 'pokemontrades', 'banlist'); var lines = current_list.replace(/\r/g, '').split("\n"); var start_index = lines.indexOf('[//]:# (BEGIN BANLIST)') + 3; - var end_index = lines.indexOf('[//]:# (END BANLIST)') + var end_index = lines.indexOf('[//]:# (END BANLIST)'); if (start_index === 2 || end_index === -1) { console.log('Error: Could not find parsing marker in public banlist'); throw {error: 'Error: Could not find parsing marker in public banlist'}; @@ -102,7 +102,7 @@ exports.updateBanlist = async function (redToken, username, banlistEntry, friend blocks[1] = _.union(blocks[1].match(/(\d{4}-){2}\d{4}/g), friend_codes).join(', '); blocks[3] = _.union(blocks[3].split(', '), [igns]).join(', '); let new_line = blocks.join(' | '); - var updated_content = lines.slice(0, start_index).concat(new_line).concat(lines.slice(start_index, i)).concat(lines.slice(i + 1)).join('\n'); + updated_content = lines.slice(0, start_index).concat(new_line).concat(lines.slice(start_index, i)).concat(lines.slice(i + 1)).join('\n'); } } if (!updated_content) { From ae34ad8a809a170a94b6c502f2316311cf76603b Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Wed, 18 Nov 2015 15:32:05 +0000 Subject: [PATCH 07/71] Update Readme.md It was woefully out of date. --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7e27ff52..6ffa326e 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,14 @@ ## Setup -1. Make sure you have a recent version of Node and NPM installed (NPM is usually bundled with Node these days). -1. Clone the repository `git clone https://github.com/yamanickill/reddit-flair-apps.git` -1. Navigate into the directory `reddit-flair-apps` +1. Make sure you have Node (>=4.0.0), and MongoDB installed +1. Clone the repository `git clone https://github.com/YaManicKill/flairhq.git` +1. Navigate into the directory `flairhq` 1. Run `npm install` to install the dependencies -1. Set up your MongoDB settings `config/local.js` -1. Start your MongoDB from the command line with `sudo mongod` +1. Copy config/local.example.js to config/local.js +1. Create a reddit app on [https://www.reddit.com/prefs/apps](https://www.reddit.com/prefs/apps) +1. Copy the id and secret to config/local.js +1. Use something like https://github.com/xoru/easy-oauth to get a refresh token for a moderator on the subs +1. Start your MongoDB 1. Start sails with `npm start` 1. Open `http://localhost:1337` in your browser From dd7cf669496212d114054bec21a1ec1ad8dcdafb Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Thu, 19 Nov 2015 16:49:48 +0000 Subject: [PATCH 08/71] Reorganise search, and everything So, I decided we needed to refactor stuff...again. The search folder (inside assets/) is the best example we have so far. But I think it still needs a wee bit of reorganisation. But it's much better than it was. Everything else needs a lot more work done, but stuff is reorganised slightly (views folder will eventually disappear, but is at least with all the other assets now). --- .eslintrc | 4 - .sailsrc | 3 + api/controllers/SearchController.js | 61 ++----- assets/{js => }/adminCtrl.js | 0 assets/{js => }/app.js | 14 +- assets/{js => }/banCtrl.js | 0 assets/{js => }/indexCtrl.js | 0 assets/js/dependencies/mask.min.js | 2 - assets/js/searchCtrl.js | 140 ---------------- assets/{js => }/ngReallyClick.js | 0 assets/{js => }/numberPadding.js | 0 assets/{js => }/refCtrl.js | 0 assets/search/README.md | 13 ++ assets/search/header.ejs | 47 ++++++ assets/search/log/controller.js | 13 ++ assets/search/log/form.ejs | 6 + assets/search/log/result.ejs | 7 + assets/search/main.ejs | 39 +++++ assets/search/ref/controller.js | 21 +++ assets/search/ref/form.ejs | 23 +++ assets/search/ref/result.ejs | 6 + assets/search/search.controller.js | 158 ++++++++++++++++++ assets/search/search.module.js | 11 ++ assets/search/types.js | 6 + assets/{js => }/sharedClientFunctions.js | 4 +- assets/{js => }/userCtrl.js | 2 +- {views => assets/views}/403.ejs | 0 {views => assets/views}/404.ejs | 0 {views => assets/views}/500.ejs | 0 {views => assets/views}/auth/index.ejs | 0 {views => assets/views}/home/applist.ejs | 0 {views => assets/views}/home/banlist.ejs | 0 {views => assets/views}/home/banuser.ejs | 0 {views => assets/views}/home/editProfile.ejs | 0 .../views}/home/editreference.ejs | 0 {views => assets/views}/home/flairApply.ejs | 0 {views => assets/views}/home/flairMod.ejs | 0 {views => assets/views}/home/flairText.ejs | 0 {views => assets/views}/home/header.ejs | 39 +---- {views => assets/views}/home/index.ejs | 0 {views => assets/views}/home/info.ejs | 0 {views => assets/views}/home/modEdit.ejs | 0 {views => assets/views}/home/profileInfo.ejs | 0 {views => assets/views}/home/reference.ejs | 0 {views => assets/views}/home/references.ejs | 0 .../views}/home/viewreference.ejs | 0 {views => assets/views}/layout.ejs | 4 +- {views => assets/views}/privacyPolicy.ejs | 0 config/routes.js | 36 ++-- config/views.js | 6 +- package.json | 4 +- tasks/config/browserify.js | 2 +- tasks/config/copy.js | 2 +- tasks/config/eslint.js | 2 +- views/search/logs.ejs | 46 ----- views/search/refs.ejs | 64 ------- 56 files changed, 411 insertions(+), 374 deletions(-) rename assets/{js => }/adminCtrl.js (100%) rename assets/{js => }/app.js (78%) rename assets/{js => }/banCtrl.js (100%) rename assets/{js => }/indexCtrl.js (100%) delete mode 100644 assets/js/dependencies/mask.min.js delete mode 100644 assets/js/searchCtrl.js rename assets/{js => }/ngReallyClick.js (100%) rename assets/{js => }/numberPadding.js (100%) rename assets/{js => }/refCtrl.js (100%) create mode 100644 assets/search/README.md create mode 100644 assets/search/header.ejs create mode 100644 assets/search/log/controller.js create mode 100644 assets/search/log/form.ejs create mode 100644 assets/search/log/result.ejs create mode 100644 assets/search/main.ejs create mode 100644 assets/search/ref/controller.js create mode 100644 assets/search/ref/form.ejs create mode 100644 assets/search/ref/result.ejs create mode 100644 assets/search/search.controller.js create mode 100644 assets/search/search.module.js create mode 100644 assets/search/types.js rename assets/{js => }/sharedClientFunctions.js (98%) rename assets/{js => }/userCtrl.js (99%) rename {views => assets/views}/403.ejs (100%) rename {views => assets/views}/404.ejs (100%) rename {views => assets/views}/500.ejs (100%) rename {views => assets/views}/auth/index.ejs (100%) rename {views => assets/views}/home/applist.ejs (100%) rename {views => assets/views}/home/banlist.ejs (100%) rename {views => assets/views}/home/banuser.ejs (100%) rename {views => assets/views}/home/editProfile.ejs (100%) rename {views => assets/views}/home/editreference.ejs (100%) rename {views => assets/views}/home/flairApply.ejs (100%) rename {views => assets/views}/home/flairMod.ejs (100%) rename {views => assets/views}/home/flairText.ejs (100%) rename {views => assets/views}/home/header.ejs (53%) rename {views => assets/views}/home/index.ejs (100%) rename {views => assets/views}/home/info.ejs (100%) rename {views => assets/views}/home/modEdit.ejs (100%) rename {views => assets/views}/home/profileInfo.ejs (100%) rename {views => assets/views}/home/reference.ejs (100%) rename {views => assets/views}/home/references.ejs (100%) rename {views => assets/views}/home/viewreference.ejs (100%) rename {views => assets/views}/layout.ejs (93%) rename {views => assets/views}/privacyPolicy.ejs (100%) delete mode 100644 views/search/logs.ejs delete mode 100644 views/search/refs.ejs diff --git a/.eslintrc b/.eslintrc index 08d18509..d4817bf4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,10 +14,6 @@ 2, 2 ], - "linebreak-style": [ - 2, - "unix" - ], "semi": [ 2, "always" diff --git a/.sailsrc b/.sailsrc index fa89f5e1..22bdb33c 100644 --- a/.sailsrc +++ b/.sailsrc @@ -1,5 +1,8 @@ { "generators": { "modules": {} + }, + "paths": { + "views": "./assets/views" } } \ No newline at end of file diff --git a/api/controllers/SearchController.js b/api/controllers/SearchController.js index f09688e6..689be14d 100644 --- a/api/controllers/SearchController.js +++ b/api/controllers/SearchController.js @@ -1,47 +1,22 @@ -/* global module, Reference, Search, User */ - -module.exports = { - - refView: function(req, res) { - return res.view("search/refs", {searchTerm: decodeURIComponent(req.params.searchterm)}); - }, - - logView: function(req, res) { - return res.view("search/logs", {searchTerm: decodeURIComponent(req.params.searchterm)}); - }, - - refs: function (req, res) { - var params = req.allParams(); - var searchData = { - description: params.keyword - }; - - if (params.user) { - searchData.user = params.user; - } - - if (params.categories) { - searchData.categories = params.categories.split(","); - } - - searchData.skip = params.skip || 0; - - Search.refs(searchData, function (results) { - return res.ok(results); +var searchTypes = require("../../assets/search/types.js"); +var exportObject = {}; + +for (let i = 0; i < searchTypes.length; i++) { + // Let's programmatically add the views, because we can. + let type = searchTypes[i]; + exportObject[type.short + "View"] = function (req, res) { + return res.view("../search/main", { + searchType: type.short, + searchTerm: decodeURIComponent(req.params.searchterm) }); - }, + }; +} - log: function (req, res) { - var params = req.allParams(); - var searchData = { - keyword: params.keyword - }; +for (let i = 0; i < searchTypes.length; i++) { + // And here we will programmatically add the search functions + let type = searchTypes[i]; + exportObject[type.short] = type.controller; +} - searchData.skip = params.skip || 0; - - Search.logs(searchData, function (results) { - return res.ok(results); - }); - } -}; +module.exports = exportObject; \ No newline at end of file diff --git a/assets/js/adminCtrl.js b/assets/adminCtrl.js similarity index 100% rename from assets/js/adminCtrl.js rename to assets/adminCtrl.js diff --git a/assets/js/app.js b/assets/app.js similarity index 78% rename from assets/js/app.js rename to assets/app.js index cc5a15d3..2fdf33b5 100644 --- a/assets/js/app.js +++ b/assets/app.js @@ -7,15 +7,15 @@ var indexCtrl = require('./indexCtrl'); var adminCtrl = require('./adminCtrl'); var banCtrl = require('./banCtrl'); var userCtrl = require('./userCtrl'); -var searchCtrl = require('./searchCtrl'); +require('./search/search.module'); require('angular-spinner'); require('angular-md'); require('angular-bootstrap-npm'); require('angular-mask'); require('bootstrap'); require('./ngReallyClick'); -require('../common/tooltipModule'); -require('../common/genericTooltipModule'); +require('./common/tooltipModule'); +require('./common/genericTooltipModule'); require('./numberPadding'); //require('spin'); @@ -26,7 +26,8 @@ var fapp = ng.module("fapp", [ 'yaru22.md', 'tooltipModule', 'genericTooltipModule', - 'ngMask' + 'ngMask', + 'fapp.search' ]); fapp.factory('UserFactory', function () { @@ -46,15 +47,14 @@ fapp.factory('UserFactory', function () { fapp.controller("referenceCtrl", ['$scope', '$filter', refCtrl]); fapp.controller("indexCtrl", ['$scope', indexCtrl]); fapp.controller("userCtrl", ['$scope', '$filter', '$location', 'UserFactory', userCtrl]); -fapp.controller("searchCtrl", ['$scope', '$timeout', 'UserFactory', searchCtrl]); fapp.controller("adminCtrl", ['$scope', adminCtrl]); fapp.controller("banCtrl", ['$scope', banCtrl]); // Bug fix for iOS safari $(function () { $("[data-toggle='collapse']").click(function () { - // For some reason, iOS safari doesn't let collapse work on a div if it - // doesn't have a click handler. The click handler doesn't need to do anything. + // For some reason, iOS safari doesn't let collapse work on a div if it + // doesn't have a click handler. The click handler doesn't need to do anything. }); }); diff --git a/assets/js/banCtrl.js b/assets/banCtrl.js similarity index 100% rename from assets/js/banCtrl.js rename to assets/banCtrl.js diff --git a/assets/js/indexCtrl.js b/assets/indexCtrl.js similarity index 100% rename from assets/js/indexCtrl.js rename to assets/indexCtrl.js diff --git a/assets/js/dependencies/mask.min.js b/assets/js/dependencies/mask.min.js deleted file mode 100644 index 06f7df13..00000000 --- a/assets/js/dependencies/mask.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(){"use strict";angular.module("ngMask",[])}(),function(){"use strict";angular.module("ngMask").directive("mask",["$log","$timeout","MaskService",function(a,b,c){return{restrict:"A",require:"ngModel",compile:function(d,e){function f(a){"number"==typeof a&&(b.cancel(g),g=b(function(){var b=a+1,c=d[0];if(c.setSelectionRange)c.focus(),c.setSelectionRange(a,b);else if(c.createTextRange){var e=c.createTextRange();e.collapse(!0),e.moveEnd("character",b),e.moveStart("character",a),e.select()}}))}if(!e.mask||!e.ngModel)return void a.info("Mask and ng-model attributes are required!");var g,h,i=c.create();return{pre:function(a,b,c){h=i.generateRegex({mask:c.mask,repeat:c.repeat||c.maskRepeat,clean:"true"===(c.clean||c.maskClean),limit:"true"===(c.limit||c.maskLimit||"true"),restrict:c.restrict||c.maskRestrict||"select",validate:"true"===(c.validate||c.maskValidate||"true"),model:c.ngModel,value:c.ngValue})},post:function(c,d,e,g){h.then(function(){function h(b){b=b||"";var c=i.getViewValue(b),d=k.maskWithoutOptionals||"",e=c.withDivisors(!0),h=c.withoutDivisors(!0);try{var j=i.getRegex(e.length-1),l=i.getRegex(d.length-1),m=j.test(e)||l.test(e),n=b.length-e.length===1,o=d.length-e.length>0;if("accept"!==k.restrict)if("select"!==k.restrict||m&&!n)"reject"!==k.restrict||m||(c=i.removeWrongPositions(e),e=c.withDivisors(!0),h=c.withoutDivisors(!0));else{var p=b[b.length-1],q=e[e.length-1];p!==q&&o&&(e+=p);var r=i.getFirstWrongPosition(e);angular.isDefined(r)&&f(r)}k.limit||(e=c.withDivisors(!1),h=c.withoutDivisors(!1)),k.validate&&g.$dirty&&(l.test(e)||g.$isEmpty(g.$modelValue)?g.$setValidity("mask",!0):g.$setValidity("mask",!1)),b!==e&&(g.$setViewValue(angular.copy(e),"input"),g.$render())}catch(s){throw a.error("[mask - parseViewValue]"),s}return k.clean?h:e}var j,k=i.getOptions();g.$parsers.push(h),d.on("click input paste keyup",function(){j=b(function(){b.cancel(j),h(d.val()),c.$apply()},100)});var l=c.$watch(e.ngModel,function(a){angular.isDefined(a)&&(h(a),l())});k.value&&c.$evalAsync(function(){g.$setViewValue(angular.copy(k.value),"input"),g.$render()})})}}}}}])}(),function(){"use strict";angular.module("ngMask").factory("MaskService",["$q","OptionalService","UtilService",function(a,b,c){function d(){function d(a,b){var c;try{var d=t[a],e=C[d],f=h(a);e?c="("+e.source+")":(i(a)||(z.push(a),A[a]=d),c="(\\"+d+")")}catch(g){throw g}return(f||b)&&(c+="?"),new RegExp(c)}function e(a,b){var c,f;try{var g=d(a,b);c=g;var i=h(a),j=g.source;if(i&&u>a+1){var k=e(a+1,!0).elementOptionalRegex();j+=k.source}f=new RegExp(j)}catch(l){throw l}return{elementRegex:function(){return c},elementOptionalRegex:function(){return f}}}function f(c){var d=a.defer();s=c;try{var f=c.mask,g=c.repeat;g&&(f=Array(parseInt(g)+1).join(f)),w=b.getOptionals(f).fromMaskWithoutOptionals(),s.maskWithoutOptionals=t=b.removeOptionals(f),u=t.length;for(var h,i=0;u>i;i++){var l=e(i),m=l.elementRegex(),n=l.elementOptionalRegex(),o=h?h.source+n.source:n.source;o=new RegExp(o),h=h?h.source+m.source:m.source,h=new RegExp(h),B.push(o)}j(),v=k(t).length,d.resolve({options:s,divisors:z,divisorElements:A,optionalIndexes:w,optionalDivisors:x,optionalDivisorsCombinations:y})}catch(p){throw d.reject(p),p}return d.promise}function g(a){var b;try{b=B[a]?B[a].source:""}catch(c){throw c}return new RegExp("^"+b+"$")}function h(a){return c.inArray(a,w)}function i(a){return c.inArray(a,z)}function j(){function a(a,b){return a-b}for(var b=z.sort(a),c=w.sort(a),d=0;d=e)break;x[e]=x[e]?x[e].concat(e-f):[e-f],A[e-f]=A[e]}}function k(a){try{if(z.length>0&&a){for(var b=Object.keys(A),d=[],e=b.length-1;e>=0;e--){var f=A[b[e]];f&&d.push(f)}d=c.uniqueArray(d);var g=new RegExp("[\\"+d.join("\\")+"]","g");return a.replace(g,"")}return a}catch(h){throw h}}function l(a,b){function d(a,b){for(var c=b,d=0;d0){for(var e=[],f=Object.keys(x),h=0;h=0;h--){var j=angular.copy(b);j=l(j,y[h]);var k=j.join(""),m=g(t.length-1);if(m.test(k)){d=!1,b=j;break}}}return d&&(b=l(b,z)),b.join("")}function n(){return s}function o(a){try{var b=k(a),c=m(b);return{withDivisors:function(a){return a?c.substr(0,u):c},withoutDivisors:function(a){return a?b.substr(0,v):b}}}catch(d){throw d}}function p(a,b){var c=[];if(!a)return 0;for(var d=0;dk;++k)e[h]=i[k],b.apply(c,e);else for(var k=0;j>k;++k)e[h]=i[k],d(h+1);e.pop()}c||(c=this);for(var e=[],f=a.length-1,g=[],h=a.length;h--;)g[h]=a[h].length;d(0)}function b(a,b){var c;try{c=b.indexOf(a)>-1}catch(d){throw d}return c}function c(a){for(var b={},c=[],d=0,e=a.length;e>d;++d)b.hasOwnProperty(a[d])||(c.push(a[d]),b[a[d]]=1);return c}return{lazyProduct:a,inArray:b,uniqueArray:c}}])}(); -//# sourceMappingURL=ngMask.min.map \ No newline at end of file diff --git a/assets/js/searchCtrl.js b/assets/js/searchCtrl.js deleted file mode 100644 index 40640ccc..00000000 --- a/assets/js/searchCtrl.js +++ /dev/null @@ -1,140 +0,0 @@ -var socket = require("socket.io-client"); -var io = require("sails.io.js")(socket); -var _ = require("lodash"); -var $ = require("jquery"); - -module.exports = function ($scope, $timeout, UserFactory) { - - $scope.user = {}; - $scope.$watch( - UserFactory.getUser, - function (newU, oldU) { - if (newU !== oldU) { - $scope.user = UserFactory.getUser(); - for (var i = 0; i < $scope.potentialSearches.length; i++) { - if ($scope.user.isMod || !$scope.potentialSearches[i].modOnly) { - $scope.searchInfo.searches.push($scope.potentialSearches[i]); - } - } - if (!$scope.searchInfo.search) { - $scope.searchInfo.search = $scope.searchInfo.searches[0]; - } - } - } - ); - - $scope.potentialSearches = [{long: "ref", short: "s", name: "References"}, {long: "log", short: "l", name: "Logs", modOnly: true}]; - - $scope.searchInfo = { - keyword: "", - category: [], - user: "", - searches: [] - }; - - $scope.setSearch = function (long) { - $scope.searchInfo.search = _.find($scope.potentialSearches, function (item) { - return item.long === long; - }); - }; - $scope.searchInfo.uriKeyword = function () { - return encodeURIComponent($scope.searchInfo.keyword.replace(/\//g, "%2F")); - }; - $scope.searching = false; - $scope.searchResults = []; - $scope.searchedFor = ""; - $scope.lastSearch = ""; - $scope.numberSearched = 0; - - $scope.toggleCategory = function (name) { - var index = $scope.searchInfo.category.indexOf(name); - - if (index > -1) { - $scope.searchInfo.category.splice(index, 1); - } else { - $scope.searchInfo.category.push(name); - } - - if ($scope.searchInfo.keyword) { - $scope.search(); - } - }; - - $scope.getMore = function () { - if ($scope.searching) { - return; - } - $scope.numberSearched += 20; - $scope.search($scope.numberSearched); - }; - - $scope.search = function (skip) { - $('.search-results').show(); - $scope.searching = true; - if (!$scope.searchInfo.keyword) { - $scope.searching = false; - $scope.searchResults = []; - $scope.numberSearched = 0; - return; - } - - if (!skip) { - $scope.numberSearched = 0; - skip = 0; - } - - var url = "/search/" + $scope.searchInfo.search.long; - url += "?keyword=" + $scope.searchInfo.keyword; - if ($scope.searchInfo.category.length > 0) { - url += "&categories=" + $scope.searchInfo.category; - } - if ($scope.searchInfo.user) { - url += "&user=" + $scope.searchInfo.user; - } - url += "&skip=" + skip; - - $scope.searchedFor = url; - io.socket.get(url, function (data, res) { - if (res.statusCode === 200 && $scope.searchedFor === url) { - if (skip) { - $scope.searchResults = $scope.searchResults.concat(data); - } else { - $scope.searchResults = data; - } - $scope.searching = false; - $scope.$apply(); - } else if (res.statusCode !== 200) { - // Some error - console.log(res); - } - }); - }; - - var searchTimeout; - $scope.searchMaybe = function () { - if (searchTimeout) { - $timeout.cancel(searchTimeout); - } - - searchTimeout = $timeout(function () { - if (!_.isEqual($scope.lastSearch, $scope.searchInfo)) { - $scope.lastSearch = _.cloneDeep($scope.searchInfo); - $scope.search(); - console.log("searching " + $scope.searchInfo); - } - }, 500); - }; - - $scope.submit = function () { - if ($scope.searchInfo.keyword) { - window.location.href = "/search/" + $scope.searchInfo.search.short + "/" + $scope.searchInfo.uriKeyword(); - } - }; - - $timeout(function () { - if ($scope.searchInfo.keyword) { - $scope.search(); - } - }, 300); - $scope.getFlairs(); -}; diff --git a/assets/js/ngReallyClick.js b/assets/ngReallyClick.js similarity index 100% rename from assets/js/ngReallyClick.js rename to assets/ngReallyClick.js diff --git a/assets/js/numberPadding.js b/assets/numberPadding.js similarity index 100% rename from assets/js/numberPadding.js rename to assets/numberPadding.js diff --git a/assets/js/refCtrl.js b/assets/refCtrl.js similarity index 100% rename from assets/js/refCtrl.js rename to assets/refCtrl.js diff --git a/assets/search/README.md b/assets/search/README.md new file mode 100644 index 00000000..f819ba4b --- /dev/null +++ b/assets/search/README.md @@ -0,0 +1,13 @@ +# Adding a new search function (client side) + +Assuming you have done everything client, side, because that is simple, there are a few small things that are not +obvious with the client side code. + +Firstly, you need to add a new directory (obvious) and have a form.ejs and result.ejs files. These are for the +advanced form (for the advanced page) and the results, for both the advanced page and the dropdown from the header. + +Then you need to add an option to the ./header.ejs file pointing to the right result.ejs file. The reason this can't be +automated, is that we can't loop over the directories in ejs, unless we have an array somewhere of what ones we have, and +that feels messier than this. Maybe. I don't know, we can probably create one sometime or figure out a nicer way to do it. + +I'm sure there is a nice way somewhere... \ No newline at end of file diff --git a/assets/search/header.ejs b/assets/search/header.ejs new file mode 100644 index 00000000..62ad159a --- /dev/null +++ b/assets/search/header.ejs @@ -0,0 +1,47 @@ + \ No newline at end of file diff --git a/assets/search/log/controller.js b/assets/search/log/controller.js new file mode 100644 index 00000000..1dd052bd --- /dev/null +++ b/assets/search/log/controller.js @@ -0,0 +1,13 @@ +/* global Search */ +module.exports = function (req, res) { + var params = req.allParams(); + var searchData = { + keyword: params.keyword + }; + + searchData.skip = params.skip || 0; + + Search.logs(searchData, function (results) { + return res.ok(results); + }); +}; \ No newline at end of file diff --git a/assets/search/log/form.ejs b/assets/search/log/form.ejs new file mode 100644 index 00000000..f4c2ae56 --- /dev/null +++ b/assets/search/log/form.ejs @@ -0,0 +1,6 @@ +
+ + +
\ No newline at end of file diff --git a/assets/search/log/result.ejs b/assets/search/log/result.ejs new file mode 100644 index 00000000..25a5705e --- /dev/null +++ b/assets/search/log/result.ejs @@ -0,0 +1,7 @@ +

+ /u/{{result.user}} - {{result.createdAt | date:'yyyy-MM-dd HH:mm:ss'}} UTC +

+ +

+ {{result.content}}. +

\ No newline at end of file diff --git a/assets/search/main.ejs b/assets/search/main.ejs new file mode 100644 index 00000000..84ad8b73 --- /dev/null +++ b/assets/search/main.ejs @@ -0,0 +1,39 @@ +
+
+
+
+
+

Advanced Search - <%- searchType %>

+
+
+
+
+ <%- partial(searchType + '/form.ejs') %> +
+
+ +
+
+
diff --git a/assets/search/ref/controller.js b/assets/search/ref/controller.js new file mode 100644 index 00000000..597e7818 --- /dev/null +++ b/assets/search/ref/controller.js @@ -0,0 +1,21 @@ +/* global Search */ +module.exports = function (req, res) { + var params = req.allParams(); + var searchData = { + description: params.keyword + }; + + if (params.user) { + searchData.user = params.user; + } + + if (params.categories) { + searchData.categories = params.categories.split(","); + } + + searchData.skip = params.skip || 0; + + Search.refs(searchData, function (results) { + return res.ok(results); + }); +}; \ No newline at end of file diff --git a/assets/search/ref/form.ejs b/assets/search/ref/form.ejs new file mode 100644 index 00000000..7839c6b1 --- /dev/null +++ b/assets/search/ref/form.ejs @@ -0,0 +1,23 @@ +
+ + + + +
+
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/assets/search/ref/result.ejs b/assets/search/ref/result.ejs new file mode 100644 index 00000000..3553a973 --- /dev/null +++ b/assets/search/ref/result.ejs @@ -0,0 +1,6 @@ +

+ /u/{{result.user}} and /u/{{result.user2}} +

+

+ {{result.description || result.gave + " for " + result.got}} +

\ No newline at end of file diff --git a/assets/search/search.controller.js b/assets/search/search.controller.js new file mode 100644 index 00000000..c97130c4 --- /dev/null +++ b/assets/search/search.controller.js @@ -0,0 +1,158 @@ +var socket = require("socket.io-client"); +var io = require("sails.io.js")(socket); +var _ = require("lodash"); +var $ = require("jquery"); + +module.exports = function ($scope, $timeout, UserFactory) { + var vm = this; + + vm.user = {}; + vm.potentialSearches = require("./types.js"); + vm.input = { + keyword: "", + category: [], + user: "", + search: "", + uriKeyword: uriKeyword + }; + vm.inProgress = false; + vm.results = []; + + vm.getSearch = getSearch; + vm.toggleCategory = toggleCategory; + vm.searchMaybe = searchMaybe; + vm.getMore = getMore; + vm.searchesForUser = searchesForUser; + vm.submit = submit; + + //////////////////////////////// + + vm.numberSearched = 0; + var lastSearch; + + $timeout(function () { + if (vm.input.keyword) { + search(); + } + }, 300); + + $scope.$watch( + UserFactory.getUser, + function (newU, oldU) { + if (newU !== oldU) { + vm.user = UserFactory.getUser(); + if (!$scope.onSearchPage) { + vm.input.search = "ref"; + } + } + } + ); + + function searchesForUser() { + return _.filter(vm.potentialSearches, userAllowedSearch); + } + + function userAllowedSearch(search) { + // Either all users can access, or only mods + if (!search.modOnly) { + return true; + } else { + return vm.user.isMod; + } + } + + function getSearch() { + return _.find(vm.potentialSearches, function (item) { + return item.short === vm.input.search; + }); + } + + function uriKeyword() { + return encodeURIComponent(vm.input.keyword.replace(/\//g, "%2F")); + } + + function toggleCategory(name) { + var index = vm.input.category.indexOf(name); + + if (index > -1) { + vm.input.category.splice(index, 1); + } else { + vm.input.category.push(name); + } + + if (vm.input.keyword) { + search(); + } + } + + function getMore() { + if (vm.inProgress) { + return; + } + vm.numberSearched += 20; + search(vm.numberSearched); + } + + var searchTimeout; + function searchMaybe() { + if (searchTimeout) { + $timeout.cancel(searchTimeout); + } + + searchTimeout = $timeout(function () { + if (!_.isEqual(lastSearch, vm.input)) { + lastSearch = _.cloneDeep(vm.input); + search(); + } + }, 500); + } + + function submit() { + if (vm.input.keyword) { + window.location.href = "/search/" + vm.getSearch().short + "/" + vm.input.uriKeyword(); + } + } + + function search(skip) { + console.log("Searching"); + $('.search-results').show(); + vm.inProgress = true; + if (!vm.input.keyword) { + vm.inProgress = false; + vm.results = []; + vm.numberSearched = 0; + return; + } + + if (!skip) { + vm.numberSearched = 0; + skip = 0; + } + + var url = "/search/" + vm.getSearch().short; + url += "?keyword=" + vm.input.keyword; + if (vm.input.category.length > 0) { + url += "&categories=" + vm.input.category; + } + if (vm.input.user) { + url += "&user=" + vm.input.user; + } + url += "&skip=" + skip; + + var searchedFor = url; + io.socket.get(url, function (data, res) { + if (res.statusCode === 200 && searchedFor === url) { + if (skip) { + vm.results = vm.results.concat(data); + } else { + vm.results = data; + } + vm.inProgress = false; + $scope.$apply(); + } else if (res.statusCode !== 200) { + // Some error + console.log(res); + } + }); + } +}; diff --git a/assets/search/search.module.js b/assets/search/search.module.js new file mode 100644 index 00000000..3af7ad95 --- /dev/null +++ b/assets/search/search.module.js @@ -0,0 +1,11 @@ +/** + * + */ + +var angular = require("angular"); +var controller = require("./search.controller.js"); + +var searchModule = angular.module("fapp.search", []); +searchModule.controller("SearchController", controller); + +module.exports = searchModule; \ No newline at end of file diff --git a/assets/search/types.js b/assets/search/types.js new file mode 100644 index 00000000..638291c6 --- /dev/null +++ b/assets/search/types.js @@ -0,0 +1,6 @@ +var types = [ + {"short": "ref", "name": "References", controller: require("./ref/controller.js")}, + {"short": "log", "name": "Logs", controller: require("./log/controller.js"), "modOnly": true} +]; + +module.exports = types; \ No newline at end of file diff --git a/assets/js/sharedClientFunctions.js b/assets/sharedClientFunctions.js similarity index 98% rename from assets/js/sharedClientFunctions.js rename to assets/sharedClientFunctions.js index 08d155da..91f5904f 100644 --- a/assets/js/sharedClientFunctions.js +++ b/assets/sharedClientFunctions.js @@ -1,7 +1,7 @@ var socket = require("socket.io-client"); var io = require("sails.io.js")(socket); -var referenceService = require('../../api/services/References.js'); -var flairService = require('../../api/services/Flairs.js'); +var referenceService = require('../api/services/References.js'); +var flairService = require('../api/services/Flairs.js'); module.exports = { addRepeats: function ($scope) { diff --git a/assets/js/userCtrl.js b/assets/userCtrl.js similarity index 99% rename from assets/js/userCtrl.js rename to assets/userCtrl.js index 8fa36915..e52fa119 100644 --- a/assets/js/userCtrl.js +++ b/assets/userCtrl.js @@ -65,7 +65,7 @@ module.exports = function ($scope, $filter, $location, UserFactory) { {name: "misc", display: "Miscellaneous"} ]; - $scope.onSearchPage = $location.absUrl().indexOf('search') === -1; + $scope.onSearchPage = $location.absUrl().indexOf('search') !== -1; sharedService.addRepeats($scope); $scope.applyFlair = function () { $scope.errors.flairApp = ""; diff --git a/views/403.ejs b/assets/views/403.ejs similarity index 100% rename from views/403.ejs rename to assets/views/403.ejs diff --git a/views/404.ejs b/assets/views/404.ejs similarity index 100% rename from views/404.ejs rename to assets/views/404.ejs diff --git a/views/500.ejs b/assets/views/500.ejs similarity index 100% rename from views/500.ejs rename to assets/views/500.ejs diff --git a/views/auth/index.ejs b/assets/views/auth/index.ejs similarity index 100% rename from views/auth/index.ejs rename to assets/views/auth/index.ejs diff --git a/views/home/applist.ejs b/assets/views/home/applist.ejs similarity index 100% rename from views/home/applist.ejs rename to assets/views/home/applist.ejs diff --git a/views/home/banlist.ejs b/assets/views/home/banlist.ejs similarity index 100% rename from views/home/banlist.ejs rename to assets/views/home/banlist.ejs diff --git a/views/home/banuser.ejs b/assets/views/home/banuser.ejs similarity index 100% rename from views/home/banuser.ejs rename to assets/views/home/banuser.ejs diff --git a/views/home/editProfile.ejs b/assets/views/home/editProfile.ejs similarity index 100% rename from views/home/editProfile.ejs rename to assets/views/home/editProfile.ejs diff --git a/views/home/editreference.ejs b/assets/views/home/editreference.ejs similarity index 100% rename from views/home/editreference.ejs rename to assets/views/home/editreference.ejs diff --git a/views/home/flairApply.ejs b/assets/views/home/flairApply.ejs similarity index 100% rename from views/home/flairApply.ejs rename to assets/views/home/flairApply.ejs diff --git a/views/home/flairMod.ejs b/assets/views/home/flairMod.ejs similarity index 100% rename from views/home/flairMod.ejs rename to assets/views/home/flairMod.ejs diff --git a/views/home/flairText.ejs b/assets/views/home/flairText.ejs similarity index 100% rename from views/home/flairText.ejs rename to assets/views/home/flairText.ejs diff --git a/views/home/header.ejs b/assets/views/home/header.ejs similarity index 53% rename from views/home/header.ejs rename to assets/views/home/header.ejs index 9681e873..5abaa6a6 100644 --- a/views/home/header.ejs +++ b/assets/views/home/header.ejs @@ -20,44 +20,7 @@ - + diff --git a/views/privacyPolicy.ejs b/assets/views/privacyPolicy.ejs similarity index 100% rename from views/privacyPolicy.ejs rename to assets/views/privacyPolicy.ejs diff --git a/config/routes.js b/config/routes.js index 1b6f60a4..5d85f671 100644 --- a/config/routes.js +++ b/config/routes.js @@ -1,3 +1,4 @@ +"use strict"; /** * Route Mappings * (sails.config.routes) @@ -193,26 +194,21 @@ module.exports.routes = { '/version' : { controller : 'home', action : 'version' - }, - - '/search/s/:searchterm' : { - controller : 'search', - action : 'refView' - }, - - '/search/l/:searchterm' : { - controller : 'search', - action : 'logView' - }, - - '/search/ref' : { - controller : 'search', - action : 'refs' - }, - - '/search/log' : { - controller : 'search', - action : 'log' } }; + +var searchTypes = require("../assets/search/types.js"); + +for (let i = 0; i < searchTypes.length; i++) { + // Programatically add the routes for searches + let type = searchTypes[i]; + module.exports.routes['/search/' + type.short] = { + controller: 'search', + action: type.short + }; + module.exports.routes['/search/' + type.short + "/:searchterm"] = { + controller: 'search', + action: type.short + "View" + }; +} \ No newline at end of file diff --git a/config/views.js b/config/views.js index b1cc11e0..feff3adb 100644 --- a/config/views.js +++ b/config/views.js @@ -57,7 +57,11 @@ module.exports.views = { * * ****************************************************************************/ - layout: 'layout' + layout: 'layout', + + locals: { + allTypes: require('../assets/search/types.js') + } /**************************************************************************** * * diff --git a/package.json b/package.json index 867883b3..170c6dea 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,9 @@ "jquery-browserify": "^1.8.1", "lodash": "^3.10.1", "marked": "^0.3.5", + "mocha": "^2.3.4", "moment": "~2.10.6", + "ng-mask": "^3.0.12", "node-cache": "^3.0.0", "node-sha1": "^1.0.1", "pako": "^0.2.8", @@ -70,7 +72,7 @@ }, "browser": { "jquery": "jquery-browserify", - "angular-mask": "./assets/js/dependencies/mask.min.js", + "angular-mask": "./node_modules/ng-mask/dist/ngMask.js", "angular-md": "./node_modules/angular-md/dist/angular-md.js", "regex": "./assets/common/regexCommon.js" } diff --git a/tasks/config/browserify.js b/tasks/config/browserify.js index f9c25008..ad9ebcdf 100644 --- a/tasks/config/browserify.js +++ b/tasks/config/browserify.js @@ -3,7 +3,7 @@ module.exports = function (grunt) { grunt.config.set('browserify', { dev: { files: { - ".tmp/public/js/app.js": "assets/js/app.js" + ".tmp/public/js/app.js": "assets/app.js" }, options: { transform: [["babelify"]] diff --git a/tasks/config/copy.js b/tasks/config/copy.js index cd2f8d84..3968335d 100644 --- a/tasks/config/copy.js +++ b/tasks/config/copy.js @@ -20,7 +20,7 @@ module.exports = function (grunt) { files: [{ expand: true, cwd: './assets', - src: ['**/*.!(coffee|less|js)'], + src: ['**/*.!(coffee|less)'], dest: '.tmp/public' }] }, diff --git a/tasks/config/eslint.js b/tasks/config/eslint.js index b5b8b258..7117cbcb 100644 --- a/tasks/config/eslint.js +++ b/tasks/config/eslint.js @@ -12,7 +12,7 @@ module.exports = function (grunt) { grunt.config.set('eslint', { - target: ['Gruntfile.js', 'app.js', 'api/**/*.js', 'tasks/**/*.js', 'assets/js/*.js', 'assets/common/*.js'] + target: ['Gruntfile.js', 'app.js', 'api/**/*.js', 'tasks/**/*.js', 'assets/**/*.js'] }); grunt.loadNpmTasks('grunt-eslint'); diff --git a/views/search/logs.ejs b/views/search/logs.ejs deleted file mode 100644 index 2e5a1320..00000000 --- a/views/search/logs.ejs +++ /dev/null @@ -1,46 +0,0 @@ -
-
- -
-
-
-

Log Viewer

-
- -
-
-
-
- - -
-
- -
- -
-
- -
diff --git a/views/search/refs.ejs b/views/search/refs.ejs deleted file mode 100644 index 9d3d284f..00000000 --- a/views/search/refs.ejs +++ /dev/null @@ -1,64 +0,0 @@ -
-
- -
-
-
-

Advanced Search

-
- -
-
-
-
- - - - -
-
-
- -
-
- -
-
-
- -
- -
-
- -
From bffe5ccfef97aa7b6faa0222ed5cf2c86c6766fa Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Sat, 21 Nov 2015 12:14:21 +0000 Subject: [PATCH 09/71] Add user search. --- api/services/Search.js | 33 ++++++++++++++++++++++++++++++ assets/search/header.ejs | 2 +- assets/search/main.ejs | 2 +- assets/search/search.controller.js | 9 ++++++++ assets/search/types.js | 1 + assets/search/user/controller.js | 13 ++++++++++++ assets/search/user/form.ejs | 6 ++++++ assets/search/user/result.ejs | 8 ++++++++ 8 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 assets/search/user/controller.js create mode 100644 assets/search/user/form.ejs create mode 100644 assets/search/user/result.ejs diff --git a/api/services/Search.js b/api/services/Search.js index 4baa1156..e4258b9d 100644 --- a/api/services/Search.js +++ b/api/services/Search.js @@ -77,4 +77,37 @@ module.exports.logs = function (searchData, cb) { Event.find(appData).exec(function (err, apps) { cb(apps); }); +}; + +module.exports.users = function (searchData, cb) { + var data = { + "$or": [ + { + "_id": { + "$regex": "(?i)" + searchData.keyword + } + }, + { + "flair.ptrades.flair_text": { + "$regex": "(?i)" + searchData.keyword + } + }, + { + "flair.svex.flair_text": { + "$regex": "(?i)" + searchData.keyword + } + } + ] + }; + + // We can't do deep searching on the flair using waterline, so let's use mongo natively + // I guess this means we can't use any other databases in the future anymore. Ach well. + User.native(function (err, collection) { + collection.find(data) + .limit(20) + .skip(0) + .toArray(function (err, results) { + cb(results); + }); + }); }; \ No newline at end of file diff --git a/assets/search/header.ejs b/assets/search/header.ejs index 62ad159a..6dcae159 100644 --- a/assets/search/header.ejs +++ b/assets/search/header.ejs @@ -27,7 +27,7 @@ Searching...
- diff --git a/assets/search/main.ejs b/assets/search/main.ejs index 84ad8b73..c23d50a5 100644 --- a/assets/search/main.ejs +++ b/assets/search/main.ejs @@ -21,7 +21,7 @@ Searching... <%- partial(searchType + '/result.ejs') %> diff --git a/assets/search/search.controller.js b/assets/search/search.controller.js index c97130c4..0de83b47 100644 --- a/assets/search/search.controller.js +++ b/assets/search/search.controller.js @@ -24,6 +24,7 @@ module.exports = function ($scope, $timeout, UserFactory) { vm.getMore = getMore; vm.searchesForUser = searchesForUser; vm.submit = submit; + vm.linkAddress = linkAddress; //////////////////////////////// @@ -48,6 +49,14 @@ module.exports = function ($scope, $timeout, UserFactory) { } ); + function linkAddress (result) { + if (vm.input.search === 'ref') { + return '/u/' + result.user; + } else if (vm.input.search === 'user') { + return '/u/' + result.name; + } + } + function searchesForUser() { return _.filter(vm.potentialSearches, userAllowedSearch); } diff --git a/assets/search/types.js b/assets/search/types.js index 638291c6..09c3ff12 100644 --- a/assets/search/types.js +++ b/assets/search/types.js @@ -1,5 +1,6 @@ var types = [ {"short": "ref", "name": "References", controller: require("./ref/controller.js")}, + {"short": "user", "name": "Users", controller: require("./user/controller.js")}, {"short": "log", "name": "Logs", controller: require("./log/controller.js"), "modOnly": true} ]; diff --git a/assets/search/user/controller.js b/assets/search/user/controller.js new file mode 100644 index 00000000..8f71ba37 --- /dev/null +++ b/assets/search/user/controller.js @@ -0,0 +1,13 @@ +/* global Search */ +module.exports = function (req, res) { + var params = req.allParams(); + var searchData = { + keyword: params.keyword + }; + + searchData.skip = params.skip || 0; + + Search.users(searchData, function (results) { + return res.ok(results); + }); +}; \ No newline at end of file diff --git a/assets/search/user/form.ejs b/assets/search/user/form.ejs new file mode 100644 index 00000000..f4c2ae56 --- /dev/null +++ b/assets/search/user/form.ejs @@ -0,0 +1,6 @@ +
+ + +
\ No newline at end of file diff --git a/assets/search/user/result.ejs b/assets/search/user/result.ejs new file mode 100644 index 00000000..22ead2b5 --- /dev/null +++ b/assets/search/user/result.ejs @@ -0,0 +1,8 @@ +

+ /u/{{result._id}} +

+ +

+ /r/PokemonTrades: {{result.flair.ptrades.flair_text}}
+ /r/SVExchange: {{result.flair.svex.flair_text}} +

\ No newline at end of file From 835c9524293452e117beb8726829222d4734b245 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Wed, 18 Nov 2015 19:46:15 -0500 Subject: [PATCH 10/71] Changed banned TSV thread behavior, refactored modmail archiving --- api/.eslintrc | 3 +- api/controllers/FlairController.js | 6 +-- api/controllers/UserController.js | 6 +-- api/services/Ban.js | 19 ++++----- api/services/Modmails.js | 34 +++++----------- api/services/Reddit.js | 65 +++++++++++++++++++++++------- 6 files changed, 79 insertions(+), 54 deletions(-) diff --git a/api/.eslintrc b/api/.eslintrc index bf16da21..029263d0 100644 --- a/api/.eslintrc +++ b/api/.eslintrc @@ -16,6 +16,7 @@ "Game": true, "Application": true, "ModNote": true, - "Modmail": true + "Modmail": true, + "Modmails": true } } \ No newline at end of file diff --git a/api/controllers/FlairController.js b/api/controllers/FlairController.js index 1cfdaa8f..0dc2f399 100644 --- a/api/controllers/FlairController.js +++ b/api/controllers/FlairController.js @@ -83,7 +83,7 @@ module.exports = { css_class = app.flair; } } - Reddit.setFlair(req.user.redToken, user.name, css_class, flair, app.sub).then(function () { + Reddit.setUserFlair(req.user.redToken, user.name, css_class, flair, app.sub).then(function () { Event.create({ type: "flairTextChange", user: req.user.name, @@ -173,8 +173,8 @@ module.exports = { var newsvFlair = _.get(req, "user.flair.svex.flair_css_class") || ""; newsvFlair = newsvFlair.replace(/2/, ""); var promises = []; - promises.push(Reddit.setFlair(refreshToken, req.user.name, newPFlair, flairs.ptrades, "PokemonTrades")); - promises.push(Reddit.setFlair(refreshToken, req.user.name, newsvFlair, flairs.svex, "SVExchange")); + promises.push(Reddit.setUserFlair(refreshToken, req.user.name, newPFlair, flairs.ptrades, "PokemonTrades")); + promises.push(Reddit.setUserFlair(refreshToken, req.user.name, newsvFlair, flairs.svex, "SVExchange")); Promise.all(promises).then(function () { var ipAddress = req.headers['x-forwarded-for'] || req.ip; Event.create([{ diff --git a/api/controllers/UserController.js b/api/controllers/UserController.js index 71604278..01707c7e 100644 --- a/api/controllers/UserController.js +++ b/api/controllers/UserController.js @@ -268,12 +268,12 @@ module.exports = { promises.push(Ban.updateAutomod(req.user.redToken, req.params.username, 'SVExchange', unique_fcs)); promises.push(Ban.addUsernote(req.user.redToken, req.user.name, 'pokemontrades', req.params.username, req.params.banNote)); promises.push(Ban.addUsernote(req.user.redToken, req.user.name, 'SVExchange', req.params.username, req.params.banNote)); - promises.push(Ban.removeTSVThreads(req.user.redToken, req.params.username)); + promises.push(Ban.markTSVThreads(req.user.redToken, req.params.username)); promises.push(Ban.updateBanlist(req.user.redToken, req.params.username, req.params.banlistEntry, unique_fcs, igns, req.params.knownAlt)); promises.push(Ban.localBanUser(req.params.username)); } Promise.all(promises).then(function () { - console.log('Process to ban /u/' + req.params.username + 'was completed successfully.'); + console.log('Process to ban /u/' + req.params.username + ' was completed successfully.'); res.ok(); }, function(error) { console.log(error); @@ -283,7 +283,7 @@ module.exports = { user: req.user.name, type: "banUser", content: "Banned /u/" + req.params.username - }); + }).exec(function () {}); }, function (err) { console.log(err); res.status(500).json(err); diff --git a/api/services/Ban.js b/api/services/Ban.js index 11d2e78a..c494a0b6 100644 --- a/api/services/Ban.js +++ b/api/services/Ban.js @@ -19,7 +19,7 @@ exports.giveBannedUserFlair = async function (redToken, username, current_css_cl } else { css_class = current_css_class ? current_css_class + ' banned' : 'banned'; } - await Reddit.setFlair(redToken, username, css_class, flair_text, subreddit); + await Reddit.setUserFlair(redToken, username, css_class, flair_text, subreddit); console.log('Changed ' + username + '\'s flair to ' + css_class + ' on /r/' + subreddit); return 'Changed ' + username + '\'s flair to ' + css_class + ' on /r/' + subreddit; } catch (err) { @@ -64,15 +64,16 @@ exports.updateAutomod = async function (redToken, username, subreddit, friend_co console.log(output); return output; }; -//Remove the user's TSV threads on /r/SVExchange. -exports.removeTSVThreads = async function (redToken, username) { - var response = await Reddit.searchTSVThreads(redToken, username); - var removeTSVPromises = []; - response.data.children.forEach(function (entry) { - removeTSVPromises.push(Reddit.removePost(redToken, entry.data.id, 'false')); +//Lock and give flair to the user's TSV threads. +exports.markTSVThreads = async function (redToken, username) { + var threads = await Reddit.searchTSVThreads(redToken, username); + var tsv_promises = []; + threads.forEach(function (entry) { + tsv_promises.push(Reddit.lockPost(redToken, entry.data.id)); + tsv_promises.push(Reddit.setLinkFlair(redToken, entry.data.subreddit, entry.data.id, 'banned', '[Banned User] Trainer Shiny Value')); }); - await Promise.all(removeTSVPromises); - var output = 'Removed /u/' + username + '\'s TSV threads (' + response.data.children.length.toString() + ' total)'; + await Promise.all(tsv_promises); + var output = 'Marked and locked /u/' + username + '\'s TSV threads (' + threads.length.toString() + ' total)'; console.log(output); return output; }; diff --git a/api/services/Modmails.js b/api/services/Modmails.js index 2707117d..1a5ae20c 100644 --- a/api/services/Modmails.js +++ b/api/services/Modmails.js @@ -1,38 +1,24 @@ var relevantKeys = ['name', 'subject', 'body', 'author', 'subreddit', 'first_message_name', 'created_utc', 'parent_id', 'distinguished']; -var addModmailsToDatabase = async function (batch) { - if (!batch) { - return; - } - let modmails = batch.data.children; +var addModmailsToDatabase = async function (modmails) { + var promises = []; for (let i = 0; i < modmails.length; i++) { let compressed = {}; for (let j = 0; j < relevantKeys.length; j++) { compressed[relevantKeys[j]] = modmails[i].data[relevantKeys[j]]; } - await Modmail.findOrCreate(compressed.name, compressed); - //Recursively add the replies to the database - await addModmailsToDatabase(modmails[i].data.replies); + promises.push(Modmail.findOrCreate(compressed.name, compressed)); + if (modmails[i].data.replies) { + promises.push(addModmailsToDatabase(modmails[i].data.replies.data.children)); + } } + return Promise.all(promises); }; exports.updateArchive = async function (subreddit) { let most_recent = await Modmail.find({subreddit: subreddit, limit: 1, sort: 'created_utc DESC'}); if (!most_recent.length) { console.log('Modmail archives for /r/' + subreddit + ' could not be found for some reason. Recreating from scratch...'); - await exports.createArchiveFromScratch(subreddit); - return; - } - var before = most_recent[0].first_message_name || most_recent[0].name; - while (before !== null) { - let batch = await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit, undefined, before); - before = batch.data.before; - await addModmailsToDatabase(batch); - } -}; -exports.createArchiveFromScratch = async function (subreddit) { - var after = ''; - while (after !== null) { - let batch = await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit, after); - after = batch.data.after; - await addModmailsToDatabase(batch); + return addModmailsToDatabase(await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit)); } + let before = most_recent[0].first_message_name || most_recent[0].name; + return addModmailsToDatabase(await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit, undefined, before)); }; diff --git a/api/services/Reddit.js b/api/services/Reddit.js index ecc7fadc..aabfd904 100644 --- a/api/services/Reddit.js +++ b/api/services/Reddit.js @@ -1,6 +1,7 @@ var request = require("request-promise"), moment = require('moment'), NodeCache = require('node-cache'), + _ = require('lodash'), left = 600, resetTime = moment().add(600, "seconds"), userAgent = "Webpage:hq.porygon.co/info:v" + sails.config.version; @@ -34,7 +35,7 @@ exports.refreshToken = async function(refreshToken) { } }; -var makeRequest = async function (refreshToken, requestType, url, data, rateLimitRemainingThreshold) { +var makeRequest = async function (refreshToken, requestType, url, data, rateLimitRemainingThreshold, silentErrors) { if (left < rateLimitRemainingThreshold && moment().before(resetTime)) { throw {statusCode: 504, error: "Rate limited"}; } @@ -50,9 +51,11 @@ var makeRequest = async function (refreshToken, requestType, url, data, rateLimi formData: data }; let response = await request(options).catch(function (error) { - console.log('Reddit error: ' + requestType + ' request sent to ' + url + ' returned ' + error.statusCode + - ' - ' + error.statusMessage + '\nForm data sent: ' + JSON.stringify(data)); - throw {statusCode: 502, error: 'Reddit responded with status code ' + error.statusCode}; + if (!silentErrors) { + console.log('Reddit error: ' + requestType + ' request sent to ' + url + ' returned ' + error.statusCode); + console.log('Form data sent: ' + JSON.stringify(data)); + } + throw {statusCode: error.statusCode, error: '(Reddit response)'}; }); updateRateLimits(response); var bodyJson; @@ -65,6 +68,18 @@ var makeRequest = async function (refreshToken, requestType, url, data, rateLimi return bodyJson; }; +var getEntireListing = async function (refreshToken, endpoint, query, rateThreshold, after, before) { + var url = endpoint + query + (query ? '&' : '?') + 'count=102&limit=100' + (after ? '&after=' + after : '') + (before ? '&before=' + before : ''); + var batch = await makeRequest(refreshToken, 'GET', url, undefined, rateThreshold, after, before); + var results = batch.data.children; + if ((after || after === undefined) && batch.data.after === null || before && batch.data.before === null) { + return results; + } + after = before ? undefined : batch.data.after; + before = before ? batch.data.before : undefined; + return _.union(results, await getEntireListing(refreshToken, endpoint, query, rateThreshold, after, before)); +}; + exports.getFlair = async function (refreshToken, user, subreddit) { var url = 'https://oauth.reddit.com/r/' + subreddit + '/api/flairselector'; var data = {name: user}; @@ -79,13 +94,19 @@ exports.getBothFlairs = async function (refreshToken, user) { return Promise.all([ptradesFlairPromise, svexFlairPromise]); }; -exports.setFlair = function (refreshToken, name, cssClass, text, subreddit) { +exports.setUserFlair = function (refreshToken, name, cssClass, text, subreddit) { var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : subreddit; var url = 'https://oauth.reddit.com/r/' + actual_sub + '/api/flair'; var data = {api_type: 'json', css_class: cssClass, name: name, text: text}; return makeRequest(refreshToken, 'POST', url, data, 5); }; +exports.setLinkFlair = function (refreshToken, subreddit, link_id, cssClass, text) { + var url = 'https://oauth.reddit.com/r/' + subreddit + '/api/flair'; + var data = {api_type: 'json', css_class: cssClass, link: 't3_' + link_id, text: text}; + return makeRequest(refreshToken, 'POST', url, data, 5); +}; + exports.checkUsernameAvailable = async function (name) { return makeRequest(undefined, 'GET', 'https://www.reddit.com/api/username_available.json?user=' + name, undefined, 10); }; @@ -113,8 +134,15 @@ exports.editWikiPage = function (refreshToken, subreddit, page, content, reason) exports.searchTSVThreads = function (refreshToken, username) { var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : 'SVExchange'; - var url = 'https://oauth.reddit.com/r/' + actual_sub + '/search?q=flair%3Ashiny+AND+author%3A' + username + '&restrict_sr=on&sort=new&t=all'; - return makeRequest(refreshToken, 'GET', url, undefined, 15); + var query = '(flair:"Trainer Shiny Value" OR flair:"[Banned User] Trainer Shiny Value") AND author:' + username; + return exports.search(refreshToken, actual_sub, query, true, 'new', 'all'); +}; + +exports.search = function (refreshToken, subreddit, query, restrict_sr, sort, time) { + var querystring = '?q=' + encodeURIComponent(query) + (restrict_sr ? '&restrict_sr=on' : '') + + (sort ? '&sort=' + sort : '') + (time ? '&t=' + time : ''); + var endpoint = 'https://oauth.reddit.com/r/' + subreddit + '/search'; + return getEntireListing(refreshToken, endpoint, querystring, 10); }; exports.removePost = function (refreshToken, id, isSpam) { @@ -123,6 +151,19 @@ exports.removePost = function (refreshToken, id, isSpam) { return makeRequest(refreshToken, 'POST', url, data, 5); }; +exports.lockPost = function (refreshToken, post_id) { + var url = 'https://oauth.reddit.com/api/lock'; + var data = {id: 't3_' + post_id}; + /* Attempting to lock an archived post results in a 400 response. The request is still considered successful if this happens, so the error is + * returned instead of being thrown. */ + return makeRequest(refreshToken, 'POST', url, data, 5, true).catch(function (error) { + if (error.statusCode === 400) { + return error; + } + throw error; + }); +}; + exports.sendPrivateMessage = function (refreshToken, subject, text, recipient) { var url = 'https://oauth.reddit.com/api/compose'; var data = {api_type: 'json', subject: subject, text: text, to: recipient}; @@ -141,13 +182,9 @@ exports.checkModeratorStatus = async function (refreshToken, username, subreddit return res.data.children.length !== 0; }; -exports.getModmail = async function (refreshToken, subreddit, after, before, limit) { - //after/before (mutually exclusive): Fullname of a modmail -- will start retrieving modmail from after/before this point - //limit: The limit on the number of modmails to display, max 100 - limit = limit || 100; - var url = 'https://oauth.reddit.com/r/' + subreddit + '/message/moderator?show=all&count=102&limit=' + limit; - url += after ? '&after=' + after : before ? '&before=' + before : ''; - return makeRequest(refreshToken, 'GET', url, undefined, 20); +exports.getModmail = async function (refreshToken, subreddit, after, before) { + var endpoint = 'https://oauth.reddit.com/r/' + subreddit + '/message/moderator'; + return getEntireListing(refreshToken, endpoint, '', 20, after, before); }; var updateRateLimits = function (res) { From ffc0fd96b17b243c0d8d94c7b25b26689b4dcb35 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Fri, 20 Nov 2015 12:54:12 -0500 Subject: [PATCH 11/71] Simplified listing logic --- api/services/Reddit.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/services/Reddit.js b/api/services/Reddit.js index aabfd904..b467a2c0 100644 --- a/api/services/Reddit.js +++ b/api/services/Reddit.js @@ -72,11 +72,11 @@ var getEntireListing = async function (refreshToken, endpoint, query, rateThresh var url = endpoint + query + (query ? '&' : '?') + 'count=102&limit=100' + (after ? '&after=' + after : '') + (before ? '&before=' + before : ''); var batch = await makeRequest(refreshToken, 'GET', url, undefined, rateThreshold, after, before); var results = batch.data.children; - if ((after || after === undefined) && batch.data.after === null || before && batch.data.before === null) { - return results; - } after = before ? undefined : batch.data.after; before = before ? batch.data.before : undefined; + if (!after && !before) { + return results; + } return _.union(results, await getEntireListing(refreshToken, endpoint, query, rateThreshold, after, before)); }; From 31eef163de878c9bfae7aacb8a137e8379b21020 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Fri, 20 Nov 2015 13:23:00 -0500 Subject: [PATCH 12/71] Add modmails as a bulk query rather than using a separate query for each --- api/services/Modmails.js | 14 +++++++------- api/services/Reddit.js | 11 +++++------ config/local.example.js | 3 ++- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/api/services/Modmails.js b/api/services/Modmails.js index 1a5ae20c..cba5c030 100644 --- a/api/services/Modmails.js +++ b/api/services/Modmails.js @@ -1,24 +1,24 @@ var relevantKeys = ['name', 'subject', 'body', 'author', 'subreddit', 'first_message_name', 'created_utc', 'parent_id', 'distinguished']; -var addModmailsToDatabase = async function (modmails) { - var promises = []; +var makeModmailObjects = function (modmails) { + var all_modmails = []; for (let i = 0; i < modmails.length; i++) { let compressed = {}; for (let j = 0; j < relevantKeys.length; j++) { compressed[relevantKeys[j]] = modmails[i].data[relevantKeys[j]]; } - promises.push(Modmail.findOrCreate(compressed.name, compressed)); + all_modmails.push(compressed); if (modmails[i].data.replies) { - promises.push(addModmailsToDatabase(modmails[i].data.replies.data.children)); + all_modmails = all_modmails.concat(makeModmailObjects(modmails[i].data.replies.data.children)); } } - return Promise.all(promises); + return all_modmails; }; exports.updateArchive = async function (subreddit) { let most_recent = await Modmail.find({subreddit: subreddit, limit: 1, sort: 'created_utc DESC'}); if (!most_recent.length) { console.log('Modmail archives for /r/' + subreddit + ' could not be found for some reason. Recreating from scratch...'); - return addModmailsToDatabase(await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit)); + return Modmail.findOrCreate(makeModmailObjects(await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit))); } let before = most_recent[0].first_message_name || most_recent[0].name; - return addModmailsToDatabase(await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit, undefined, before)); + return Modmail.findOrCreate(makeModmailObjects(await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit, undefined, before))); }; diff --git a/api/services/Reddit.js b/api/services/Reddit.js index b467a2c0..39a1faa2 100644 --- a/api/services/Reddit.js +++ b/api/services/Reddit.js @@ -3,8 +3,7 @@ var request = require("request-promise"), NodeCache = require('node-cache'), _ = require('lodash'), left = 600, - resetTime = moment().add(600, "seconds"), - userAgent = "Webpage:hq.porygon.co/info:v" + sails.config.version; + resetTime = moment().add(600, "seconds"); var cache = new NodeCache({stdTTL: 3480}); // Cached tokens expire after 58 minutes, leave a bit of breathing room in case stuff is slow exports.refreshToken = async function(refreshToken) { @@ -20,7 +19,7 @@ exports.refreshToken = async function(refreshToken) { json: true, headers: { "Authorization": auth, - "User-Agent": userAgent, + "User-Agent": sails.config.reddit.userAgent, "Content-Type": "application/x-www-form-urlencoded", "Content-Length": data.length } @@ -35,11 +34,11 @@ exports.refreshToken = async function(refreshToken) { } }; -var makeRequest = async function (refreshToken, requestType, url, data, rateLimitRemainingThreshold, silentErrors) { +var makeRequest = async function (refreshToken, requestType, url, data, rateLimitRemainingThreshold, silenceErrors) { if (left < rateLimitRemainingThreshold && moment().before(resetTime)) { throw {statusCode: 504, error: "Rate limited"}; } - var headers = {"User-Agent": userAgent}; + var headers = {"User-Agent": sails.config.reddit.userAgent}; if (url.indexOf("oauth.reddit.com") !== -1) { headers.Authorization = "bearer " + await exports.refreshToken(refreshToken); } @@ -51,7 +50,7 @@ var makeRequest = async function (refreshToken, requestType, url, data, rateLimi formData: data }; let response = await request(options).catch(function (error) { - if (!silentErrors) { + if (!silenceErrors) { console.log('Reddit error: ' + requestType + ' request sent to ' + url + ' returned ' + error.statusCode); console.log('Form data sent: ' + JSON.stringify(data)); } diff --git a/config/local.example.js b/config/local.example.js index f91afcb3..198a6351 100644 --- a/config/local.example.js +++ b/config/local.example.js @@ -7,7 +7,8 @@ module.exports = { clientID: "CLIENT ID GOES HERE", clientIDSecret: "SECRET ID GOES HERE", redirectURL: "http://localhost:1337/auth/reddit/callback", - adminRefreshToken: "ADMIN REFRESH TOKEN GOES HERE" + adminRefreshToken: "ADMIN REFRESH TOKEN GOES HERE", + userAgent: 'FlairHQ development version by /u/DEVELOPERS_USERNAME || hq.porygon.co/info || v' + require('../package.json').version }, connections: { "default": "mongo", From e591ec76a023adab13f81e7d05d424b422557529 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Sun, 22 Nov 2015 15:38:00 -0500 Subject: [PATCH 13/71] Allow regular users to do user search --- config/policies.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config/policies.js b/config/policies.js index 2ed19fe8..2e6372f4 100644 --- a/config/policies.js +++ b/config/policies.js @@ -55,8 +55,10 @@ module.exports.policies = { SearchController: { '*': mod, - refs: user, - refView: user + ref: user, + refView: user, + user: user, + userView: user }, UserController: { From 35bd7b5ce0f3e35ea80acb1813699ace7b4ac641 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Sun, 22 Nov 2015 13:51:52 -0500 Subject: [PATCH 14/71] Added modmail search, refactored some small things --- api/services/Search.js | 28 ++++++++++++++++++++++- assets/search/header.ejs | 4 +++- assets/search/main.ejs | 8 +++++-- assets/search/modmail/controller.js | 13 +++++++++++ assets/search/modmail/form.ejs | 8 +++++++ assets/search/modmail/result.ejs | 6 +++++ assets/search/search.controller.js | 35 ++++++++++++++++++----------- assets/search/types.js | 7 +++--- assets/userCtrl.js | 2 ++ config/policies.js | 4 +--- config/routes.js | 1 - 11 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 assets/search/modmail/controller.js create mode 100644 assets/search/modmail/form.ejs create mode 100644 assets/search/modmail/result.ejs diff --git a/api/services/Search.js b/api/services/Search.js index e4258b9d..a717c03d 100644 --- a/api/services/Search.js +++ b/api/services/Search.js @@ -110,4 +110,30 @@ module.exports.users = function (searchData, cb) { cb(results); }); }); -}; \ No newline at end of file +}; + +module.exports.modmails = function (searchData, cb) { + var words = searchData.keyword.split(' '); + var fields = ['body', 'author', 'subject']; + var requirements = []; + for (let i = 0; i < words.length; i++) { + var current_req = {'$or': []}; + for (let j = 0; j < fields.length; j++) { + let obj = {}; + obj[fields[j]] = {'contains': words[i]}; + current_req['$or'].push(obj); + } + requirements.push(current_req); + } + //Finds modmails where all of the words in the search query appear somewhere in either the body, subject, or author. + var mailData = { + limit: 20, + skip: searchData.skip ? parseInt(searchData.skip) : 0, + sort: 'created_utc DESC', + '$and': requirements + }; + + Modmail.find(mailData).exec(function (err, mail) { + cb(mail); + }); +}; diff --git a/assets/search/header.ejs b/assets/search/header.ejs index 6dcae159..7c676f46 100644 --- a/assets/search/header.ejs +++ b/assets/search/header.ejs @@ -10,8 +10,10 @@
diff --git a/assets/search/modmail/controller.js b/assets/search/modmail/controller.js new file mode 100644 index 00000000..0d9914d8 --- /dev/null +++ b/assets/search/modmail/controller.js @@ -0,0 +1,13 @@ +/* global Search */ +module.exports = function (req, res) { + var params = req.allParams(); + var searchData = { + keyword: params.keyword + }; + + searchData.skip = params.skip || 0; + + Search.modmails(searchData, function (results) { + return res.ok(results); + }); +}; \ No newline at end of file diff --git a/assets/search/modmail/form.ejs b/assets/search/modmail/form.ejs new file mode 100644 index 00000000..5e106951 --- /dev/null +++ b/assets/search/modmail/form.ejs @@ -0,0 +1,8 @@ +
+ + + +
diff --git a/assets/search/modmail/result.ejs b/assets/search/modmail/result.ejs new file mode 100644 index 00000000..809dd016 --- /dev/null +++ b/assets/search/modmail/result.ejs @@ -0,0 +1,6 @@ +

+ {{result.subject}} by /u/{{result.author}} +

+

+ {{result.created_utc * 1000 | date:'yyyy-MM-dd HH:mm:ss'}} {{timezoneOffset}}
{{result.body}} +

diff --git a/assets/search/search.controller.js b/assets/search/search.controller.js index 0de83b47..dc19926a 100644 --- a/assets/search/search.controller.js +++ b/assets/search/search.controller.js @@ -16,15 +16,18 @@ module.exports = function ($scope, $timeout, UserFactory) { uriKeyword: uriKeyword }; vm.inProgress = false; + vm.done = false; vm.results = []; vm.getSearch = getSearch; vm.toggleCategory = toggleCategory; + vm.changeSearchType = changeSearchType; vm.searchMaybe = searchMaybe; vm.getMore = getMore; vm.searchesForUser = searchesForUser; vm.submit = submit; vm.linkAddress = linkAddress; + vm.searchedFor = ''; //////////////////////////////// @@ -32,7 +35,7 @@ module.exports = function ($scope, $timeout, UserFactory) { var lastSearch; $timeout(function () { - if (vm.input.keyword) { + if (vm.input.keyword && vm.input.search) { search(); } }, 300); @@ -42,8 +45,8 @@ module.exports = function ($scope, $timeout, UserFactory) { function (newU, oldU) { if (newU !== oldU) { vm.user = UserFactory.getUser(); - if (!$scope.onSearchPage) { - vm.input.search = "ref"; + if (!vm.input.search) { + vm.input.search = 'ref'; } } } @@ -53,7 +56,9 @@ module.exports = function ($scope, $timeout, UserFactory) { if (vm.input.search === 'ref') { return '/u/' + result.user; } else if (vm.input.search === 'user') { - return '/u/' + result.name; + return '/u/' + result._id; + } else if (vm.input.search === 'modmail') { + return 'https://reddit.com/message/messages/' + result.name.substring(3); } } @@ -63,11 +68,7 @@ module.exports = function ($scope, $timeout, UserFactory) { function userAllowedSearch(search) { // Either all users can access, or only mods - if (!search.modOnly) { - return true; - } else { - return vm.user.isMod; - } + return vm.user.isMod || !search.modOnly; } function getSearch() { @@ -80,6 +81,14 @@ module.exports = function ($scope, $timeout, UserFactory) { return encodeURIComponent(vm.input.keyword.replace(/\//g, "%2F")); } + function changeSearchType () { + //Clear the existing results since they're no longer relevant + vm.results = []; + if (vm.input.keyword) { + search(); + } + } + function toggleCategory(name) { var index = vm.input.category.indexOf(name); @@ -123,7 +132,6 @@ module.exports = function ($scope, $timeout, UserFactory) { } function search(skip) { - console.log("Searching"); $('.search-results').show(); vm.inProgress = true; if (!vm.input.keyword) { @@ -147,15 +155,16 @@ module.exports = function ($scope, $timeout, UserFactory) { url += "&user=" + vm.input.user; } url += "&skip=" + skip; - - var searchedFor = url; + vm.done = false; + vm.searchedFor = url; io.socket.get(url, function (data, res) { - if (res.statusCode === 200 && searchedFor === url) { + if (res.statusCode === 200 && vm.searchedFor === url) { if (skip) { vm.results = vm.results.concat(data); } else { vm.results = data; } + vm.done = !data.length; vm.inProgress = false; $scope.$apply(); } else if (res.statusCode !== 200) { diff --git a/assets/search/types.js b/assets/search/types.js index 09c3ff12..84b310f0 100644 --- a/assets/search/types.js +++ b/assets/search/types.js @@ -1,7 +1,8 @@ var types = [ - {"short": "ref", "name": "References", controller: require("./ref/controller.js")}, - {"short": "user", "name": "Users", controller: require("./user/controller.js")}, - {"short": "log", "name": "Logs", controller: require("./log/controller.js"), "modOnly": true} + {"short": "ref", "name": "References", controller: require("./ref/controller.js"), "modOnly": false}, + {"short": "user", "name": "Users", controller: require("./user/controller.js"), "modOnly": false}, + {"short": "log", "name": "Logs", controller: require("./log/controller.js"), "modOnly": true}, + {"short": "modmail", "name": "Modmails", controller: require("./modmail/controller.js"), "modOnly": true} ]; module.exports = types; \ No newline at end of file diff --git a/assets/userCtrl.js b/assets/userCtrl.js index e52fa119..a2ff11af 100644 --- a/assets/userCtrl.js +++ b/assets/userCtrl.js @@ -66,6 +66,8 @@ module.exports = function ($scope, $filter, $location, UserFactory) { ]; $scope.onSearchPage = $location.absUrl().indexOf('search') !== -1; + let timezone = -new Date().getTimezoneOffset()/60; + $scope.timezoneOffset = 'UTC' + (timezone > 0 ? '+' + timezone : timezone < 0 ? timezone : ''); sharedService.addRepeats($scope); $scope.applyFlair = function () { $scope.errors.flairApp = ""; diff --git a/config/policies.js b/config/policies.js index 2e6372f4..3537c25a 100644 --- a/config/policies.js +++ b/config/policies.js @@ -56,9 +56,7 @@ module.exports.policies = { SearchController: { '*': mod, ref: user, - refView: user, - user: user, - userView: user + refView: user }, UserController: { diff --git a/config/routes.js b/config/routes.js index 5d85f671..a2592b3e 100644 --- a/config/routes.js +++ b/config/routes.js @@ -195,7 +195,6 @@ module.exports.routes = { controller : 'home', action : 'version' } - }; var searchTypes = require("../assets/search/types.js"); From b51cad9ebedbd21dfd00946d572dd0bfc83fe959 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Sun, 22 Nov 2015 19:46:01 +0000 Subject: [PATCH 15/71] Move tooltip stuff to feature folder, condense to 1 module. --- assets/app.js | 4 +-- assets/common/genericTooltipModule.js | 33 ------------------- .../label.view.html} | 0 .../tooltip.module.js} | 8 ++++- .../tooltip.view.html} | 0 assets/views/home/flairText.ejs | 4 +-- 6 files changed, 10 insertions(+), 39 deletions(-) delete mode 100644 assets/common/genericTooltipModule.js rename assets/{common/tooltipView.html => tooltip/label.view.html} (100%) rename assets/{common/tooltipModule.js => tooltip/tooltip.module.js} (81%) rename assets/{common/genericTooltipView.html => tooltip/tooltip.view.html} (100%) diff --git a/assets/app.js b/assets/app.js index 2fdf33b5..774e5671 100644 --- a/assets/app.js +++ b/assets/app.js @@ -14,8 +14,7 @@ require('angular-bootstrap-npm'); require('angular-mask'); require('bootstrap'); require('./ngReallyClick'); -require('./common/tooltipModule'); -require('./common/genericTooltipModule'); +require('./tooltip/tooltip.module'); require('./numberPadding'); //require('spin'); @@ -25,7 +24,6 @@ var fapp = ng.module("fapp", [ 'numberPaddingModule', 'yaru22.md', 'tooltipModule', - 'genericTooltipModule', 'ngMask', 'fapp.search' ]); diff --git a/assets/common/genericTooltipModule.js b/assets/common/genericTooltipModule.js deleted file mode 100644 index 175175ff..00000000 --- a/assets/common/genericTooltipModule.js +++ /dev/null @@ -1,33 +0,0 @@ -var ng = require("angular"); -var $ = require('jquery'); - -ng.module("genericTooltipModule", []).directive("ngGenericTooltip", function () { - return { - restrict: 'E', - replace: true, - scope: { - title: '@title' - }, - templateUrl: '/common/genericTooltipView.html', - transclude: true, - link: function (scope, element) { - var thisElement = $(element[0]).find('[data-toggle=tooltip]'); - thisElement.tooltip({ - html: true, - trigger: 'manual', - title: scope.title - }).on("mouseenter", function () { - thisElement.tooltip("show"); - $(".tooltip").on("mouseleave", function () { - thisElement.tooltip('hide'); - }); - }).on("mouseleave", function () { - setTimeout(function () { - if (!$(".tooltip:hover").length) { - thisElement.tooltip("hide"); - } - }, 100); - }); - } - }; -}); \ No newline at end of file diff --git a/assets/common/tooltipView.html b/assets/tooltip/label.view.html similarity index 100% rename from assets/common/tooltipView.html rename to assets/tooltip/label.view.html diff --git a/assets/common/tooltipModule.js b/assets/tooltip/tooltip.module.js similarity index 81% rename from assets/common/tooltipModule.js rename to assets/tooltip/tooltip.module.js index 6d22459c..56f780b0 100644 --- a/assets/common/tooltipModule.js +++ b/assets/tooltip/tooltip.module.js @@ -9,7 +9,13 @@ ng.module("tooltipModule", []).directive("ngTooltip", function () { title: '@title', label: '@label' }, - templateUrl: '/common/tooltipView.html', + templateUrl: function (tElement, tAttrs) { + if (tAttrs.unlabeled) { + return '/tooltip/tooltip.view.html'; + } else { + return '/tooltip/label.view.html'; + } + }, transclude: true, link: function (scope, element) { var thisElement = $(element[0]).find('[data-toggle=tooltip]'); diff --git a/assets/common/genericTooltipView.html b/assets/tooltip/tooltip.view.html similarity index 100% rename from assets/common/genericTooltipView.html rename to assets/tooltip/tooltip.view.html diff --git a/assets/views/home/flairText.ejs b/assets/views/home/flairText.ejs index c32f0f55..ec55d7ff 100644 --- a/assets/views/home/flairText.ejs +++ b/assets/views/home/flairText.ejs @@ -58,11 +58,11 @@
- + - +
From 05f8dc504b59438acdaf8f7f48e895dc058ab5bd Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 06:52:59 +0000 Subject: [PATCH 16/71] Re-add line endings. --- .eslintrc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.eslintrc b/.eslintrc index d4817bf4..0b9dca5a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -10,6 +10,10 @@ }, "rules": { "no-console": 0, + "linebreak-style": [ + 2, + "unix" + ], "indent": [ 2, 2 From 5c306a61eb403234b24d779243ee15377e673114 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 10:13:36 +0000 Subject: [PATCH 17/71] Change to use snudown --- assets/app.js | 8 +++----- assets/markdown/markdown.module.js | 25 +++++++++++++++++++++++++ package.json | 4 ++-- 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 assets/markdown/markdown.module.js diff --git a/assets/app.js b/assets/app.js index 774e5671..693644ee 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1,6 +1,5 @@ var ng = require('angular'); var $ = require('jquery'); -var marked = require('marked'); var refCtrl = require('./refCtrl'); var indexCtrl = require('./indexCtrl'); @@ -8,8 +7,8 @@ var adminCtrl = require('./adminCtrl'); var banCtrl = require('./banCtrl'); var userCtrl = require('./userCtrl'); require('./search/search.module'); +require('./markdown/markdown.module'); require('angular-spinner'); -require('angular-md'); require('angular-bootstrap-npm'); require('angular-mask'); require('bootstrap'); @@ -22,10 +21,10 @@ var fapp = ng.module("fapp", [ 'angularSpinner', 'ngReallyClickModule', 'numberPaddingModule', - 'yaru22.md', 'tooltipModule', 'ngMask', - 'fapp.search' + 'fapp.search', + 'fapp.md' ]); fapp.factory('UserFactory', function () { @@ -56,5 +55,4 @@ $(function () { }); }); -window.marked = marked; ng.bootstrap(document, ['fapp']); \ No newline at end of file diff --git a/assets/markdown/markdown.module.js b/assets/markdown/markdown.module.js new file mode 100644 index 00000000..97dfbd70 --- /dev/null +++ b/assets/markdown/markdown.module.js @@ -0,0 +1,25 @@ +/** + * A module to give us a nice, easy way to use markdown in the app + */ + +var angular = require("angular"); +var SnuOwnd = require("snuownd"); + +module.exports = angular.module("fapp.md", []) + .directive("md", function () { + return { + restrict: "E", + require: "?ngModel", + link: function ($scope, $elem, $attrs, ngModel) { + if (!ngModel) { + var html = SnuOwnd.getParser().render($elem.text()); + $elem.html(html); + return; + } + ngModel.$render = function () { + var html = SnuOwnd.getParser().render(ngModel.$viewValue || ""); + $elem.html(html); + }; + } + }; + }); \ No newline at end of file diff --git a/package.json b/package.json index 170c6dea..40bf5716 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "dependencies": { "angular": "1.3.x", "angular-bootstrap-npm": "^0.14.2", - "angular-md": "^1.0.0", "angular-spinner": "^0.7.0", "angular-ui-mask": "~1.4.3", "async": "~1.4.2", @@ -46,7 +45,6 @@ "include-all": "~0.1.3", "jquery-browserify": "^1.8.1", "lodash": "^3.10.1", - "marked": "^0.3.5", "mocha": "^2.3.4", "moment": "~2.10.6", "ng-mask": "^3.0.12", @@ -67,6 +65,8 @@ "sails-mongo": "^0.11.4", "sails.io.js": "^0.11.7", "sha256": "^0.2.0", + "snudown-js": "^1.4.0", + "snuownd": "^1.1.0", "socket.io-browserify": "^0.9.6", "socket.io-client": "^1.3.7" }, From 1f892c9d4de64cc31b1d7718e7b14aa97df29202 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 11:07:54 +0000 Subject: [PATCH 18/71] Add livereload This will reload the page automatically after anything runs on a watch task. --- assets/views/layout.ejs | 1 + tasks/config/watch.js | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/assets/views/layout.ejs b/assets/views/layout.ejs index cc368da6..5423d981 100644 --- a/assets/views/layout.ejs +++ b/assets/views/layout.ejs @@ -23,6 +23,7 @@ }); + <% if(sails.config.environment == 'development' ){ %> <% } %> diff --git a/tasks/config/watch.js b/tasks/config/watch.js index fbdd36d4..b2d30052 100644 --- a/tasks/config/watch.js +++ b/tasks/config/watch.js @@ -36,7 +36,12 @@ module.exports = function (grunt) { 'sails-linker:devStyles', 'eslint', 'browserify:dev' - ] + ], + + options: { + livereload: true, + livereloadOnError: false + } } }); From 93fbeaecf42e73c2842df421ee6c7dd61bd43ceb Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 12:02:05 +0000 Subject: [PATCH 19/71] Remap user and sub urls to point to reddit --- assets/markdown/markdown.module.js | 5 +++-- assets/markdown/remapURLs.js | 4 ++++ test/unit/markdown/remapURLs.test.js | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 assets/markdown/remapURLs.js create mode 100644 test/unit/markdown/remapURLs.test.js diff --git a/assets/markdown/markdown.module.js b/assets/markdown/markdown.module.js index 97dfbd70..0b5f2a3f 100644 --- a/assets/markdown/markdown.module.js +++ b/assets/markdown/markdown.module.js @@ -4,6 +4,7 @@ var angular = require("angular"); var SnuOwnd = require("snuownd"); +var remapURLs = require("./remapURLs"); module.exports = angular.module("fapp.md", []) .directive("md", function () { @@ -12,12 +13,12 @@ module.exports = angular.module("fapp.md", []) require: "?ngModel", link: function ($scope, $elem, $attrs, ngModel) { if (!ngModel) { - var html = SnuOwnd.getParser().render($elem.text()); + var html = remapURLs(SnuOwnd.getParser().render($elem.text())); $elem.html(html); return; } ngModel.$render = function () { - var html = SnuOwnd.getParser().render(ngModel.$viewValue || ""); + var html = remapURLs(SnuOwnd.getParser().render(ngModel.$viewValue || "")); $elem.html(html); }; } diff --git a/assets/markdown/remapURLs.js b/assets/markdown/remapURLs.js new file mode 100644 index 00000000..f0a8695f --- /dev/null +++ b/assets/markdown/remapURLs.js @@ -0,0 +1,4 @@ +module.exports = function (value) { + var regex = /(href=")(\/[ur]\/[a-zA-Z_\/-]+)(")/g; + return value.replace(regex, "$1http://www.reddit.com$2$3"); +}; \ No newline at end of file diff --git a/test/unit/markdown/remapURLs.test.js b/test/unit/markdown/remapURLs.test.js new file mode 100644 index 00000000..b5e64432 --- /dev/null +++ b/test/unit/markdown/remapURLs.test.js @@ -0,0 +1,16 @@ +var assert = require("chai").assert; +var remapURLs = require("../../../assets/markdown/remapURLs"); + +describe("replaceUserURLS", function () { + it("Replaces /u/test with http://www.reddit.com/u/test", function () { + var test = remapURLs(''); + assert.equal(test, '', "Not mapping user urls correctly.") + }); +}); + +describe("replaceSubURLS", function () { + it("Replaces /u/test with http://www.reddit.com/u/test", function () { + var test = remapURLs(''); + assert.equal(test, '', "Not mapping sub urls correctly.") + }); +}); \ No newline at end of file From 5af4de7dc6f08de8d6287336ffb88ed487ead1ee Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 12:06:23 +0000 Subject: [PATCH 20/71] Use snudown-js as it is a direct recompile More likely to be accurate. --- assets/markdown/markdown.module.js | 6 +++--- package.json | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/assets/markdown/markdown.module.js b/assets/markdown/markdown.module.js index 0b5f2a3f..6fc7a892 100644 --- a/assets/markdown/markdown.module.js +++ b/assets/markdown/markdown.module.js @@ -3,7 +3,7 @@ */ var angular = require("angular"); -var SnuOwnd = require("snuownd"); +var Snudown = require("snudown-js"); var remapURLs = require("./remapURLs"); module.exports = angular.module("fapp.md", []) @@ -13,12 +13,12 @@ module.exports = angular.module("fapp.md", []) require: "?ngModel", link: function ($scope, $elem, $attrs, ngModel) { if (!ngModel) { - var html = remapURLs(SnuOwnd.getParser().render($elem.text())); + var html = remapURLs(Snudown.markdown($elem.text())); $elem.html(html); return; } ngModel.$render = function () { - var html = remapURLs(SnuOwnd.getParser().render(ngModel.$viewValue || "")); + var html = remapURLs(Snudown.markdown(ngModel.$viewValue || "")); $elem.html(html); }; } diff --git a/package.json b/package.json index 40bf5716..832ee6ac 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "sails.io.js": "^0.11.7", "sha256": "^0.2.0", "snudown-js": "^1.4.0", - "snuownd": "^1.1.0", "socket.io-browserify": "^0.9.6", "socket.io-client": "^1.3.7" }, From c7db094dd6a267dfb5c38d2efda3afd6cbb367ef Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 12:06:33 +0000 Subject: [PATCH 21/71] Forward reddit links to https, not http --- assets/markdown/remapURLs.js | 2 +- test/unit/markdown/remapURLs.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/markdown/remapURLs.js b/assets/markdown/remapURLs.js index f0a8695f..900c3376 100644 --- a/assets/markdown/remapURLs.js +++ b/assets/markdown/remapURLs.js @@ -1,4 +1,4 @@ module.exports = function (value) { var regex = /(href=")(\/[ur]\/[a-zA-Z_\/-]+)(")/g; - return value.replace(regex, "$1http://www.reddit.com$2$3"); + return value.replace(regex, "$1https://www.reddit.com$2$3"); }; \ No newline at end of file diff --git a/test/unit/markdown/remapURLs.test.js b/test/unit/markdown/remapURLs.test.js index b5e64432..b104b877 100644 --- a/test/unit/markdown/remapURLs.test.js +++ b/test/unit/markdown/remapURLs.test.js @@ -4,13 +4,13 @@ var remapURLs = require("../../../assets/markdown/remapURLs"); describe("replaceUserURLS", function () { it("Replaces /u/test with http://www.reddit.com/u/test", function () { var test = remapURLs(''); - assert.equal(test, '', "Not mapping user urls correctly.") + assert.equal(test, '', "Not mapping user urls correctly.") }); }); describe("replaceSubURLS", function () { it("Replaces /u/test with http://www.reddit.com/u/test", function () { var test = remapURLs(''); - assert.equal(test, '', "Not mapping sub urls correctly.") + assert.equal(test, '', "Not mapping sub urls correctly.") }); }); \ No newline at end of file From 3b05facbe3d3b4e2492e86cbd3639bc0b5a50aac Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 12:16:57 +0000 Subject: [PATCH 22/71] Add flairhq link next to user links --- assets/markdown/remapURLs.js | 6 ++++-- test/unit/markdown/remapURLs.test.js | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/assets/markdown/remapURLs.js b/assets/markdown/remapURLs.js index 900c3376..bd6cb3d2 100644 --- a/assets/markdown/remapURLs.js +++ b/assets/markdown/remapURLs.js @@ -1,4 +1,6 @@ module.exports = function (value) { - var regex = /(href=")(\/[ur]\/[a-zA-Z_\/-]+)(")/g; - return value.replace(regex, "$1https://www.reddit.com$2$3"); + var userRegex = /()(.*)(<\/a>)/g; + var usersReplaced = value.replace(userRegex, "$1https://www.reddit.com$2$3$4$5 ($1$2$3FlairHQ$5)"); + var subRegex = /()(.*)(<\/a>)/g; + return usersReplaced.replace(subRegex, "$1https://www.reddit.com$2$3$4$5"); }; \ No newline at end of file diff --git a/test/unit/markdown/remapURLs.test.js b/test/unit/markdown/remapURLs.test.js index b104b877..344ae352 100644 --- a/test/unit/markdown/remapURLs.test.js +++ b/test/unit/markdown/remapURLs.test.js @@ -3,14 +3,14 @@ var remapURLs = require("../../../assets/markdown/remapURLs"); describe("replaceUserURLS", function () { it("Replaces /u/test with http://www.reddit.com/u/test", function () { - var test = remapURLs(''); - assert.equal(test, '', "Not mapping user urls correctly.") + var test = remapURLs('/u/test'); + assert.equal(test, '/u/test (FlairHQ)', "Not mapping user urls correctly.") }); }); describe("replaceSubURLS", function () { it("Replaces /u/test with http://www.reddit.com/u/test", function () { - var test = remapURLs(''); - assert.equal(test, '', "Not mapping sub urls correctly.") + var test = remapURLs('/r/test'); + assert.equal(test, '/r/test', "Not mapping sub urls correctly.") }); }); \ No newline at end of file From 7a8468f31304a602d639ea4d1b08d717c17e3097 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 12:37:22 +0000 Subject: [PATCH 23/71] Shorten comment schpeil Fixes #423 --- assets/views/home/reference.ejs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/assets/views/home/reference.ejs b/assets/views/home/reference.ejs index 3727180f..c98e0633 100644 --- a/assets/views/home/reference.ejs +++ b/assets/views/home/reference.ejs @@ -41,11 +41,8 @@
-

Leave a comment

-

Sometimes you might want to leave a comment for a user.

-

If you traded with them, let the world know how good they - were at that. Maybe they hatched your egg for you and you want - to tell the world how nice they are. Whatever you want.

+

Leave a comment below

+

Leave feedback on this user by typing your comment below. reddit markdown is supported.

From dc06c2cadf6faf5d8de94b4df66f2ec472202ef6 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 12:39:07 +0000 Subject: [PATCH 24/71] Fix inconsistent capitalisation Fixes #424 --- assets/views/home/index.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/views/home/index.ejs b/assets/views/home/index.ejs index 0579165b..f8e7aea7 100644 --- a/assets/views/home/index.ejs +++ b/assets/views/home/index.ejs @@ -16,7 +16,7 @@

Public Profile - Set flair text + Set Flair Text Apply for Flair

From 0dd2cfad882c5926e68e58d23937e0d29e39d761 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 13:48:34 +0000 Subject: [PATCH 25/71] Render some things serverside for users This means that users without javascript will at least be able to log in, and see they are logged in. Dropdowns still don't work. --- assets/views/home/header.ejs | 13 +++++++++---- assets/views/layout.ejs | 6 ++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/assets/views/home/header.ejs b/assets/views/home/header.ejs index 5abaa6a6..035e8fe9 100644 --- a/assets/views/home/header.ejs +++ b/assets/views/home/header.ejs @@ -21,7 +21,9 @@
diff --git a/assets/views/layout.ejs b/assets/views/layout.ejs index 5423d981..9413072e 100644 --- a/assets/views/layout.ejs +++ b/assets/views/layout.ejs @@ -38,10 +38,12 @@ -
+
From 2610f16ee940728b9e18e7858bc2aea5e6ee3364 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 20:19:50 +0000 Subject: [PATCH 26/71] Fix 2 small issues with the auto-link mapping. --- assets/markdown/remapURLs.js | 8 ++--- assets/views/layout.ejs | 2 +- test/unit/markdown/remapURLs.test.js | 46 ++++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/assets/markdown/remapURLs.js b/assets/markdown/remapURLs.js index bd6cb3d2..38165a81 100644 --- a/assets/markdown/remapURLs.js +++ b/assets/markdown/remapURLs.js @@ -1,6 +1,6 @@ module.exports = function (value) { - var userRegex = /()(.*)(<\/a>)/g; - var usersReplaced = value.replace(userRegex, "$1https://www.reddit.com$2$3$4$5 ($1$2$3FlairHQ$5)"); - var subRegex = /()(.*)(<\/a>)/g; - return usersReplaced.replace(subRegex, "$1https://www.reddit.com$2$3$4$5"); + var userRegex = /()\/?\2(.*?)(<\/a>)/g; + var userReplaced = value.replace(userRegex, "$1https://www.reddit.com$2$3$2$5 ($1$2$3FlairHQ$5)"); + var subRegex = /()\/?\2(.*?)(<\/a>)/g; + return userReplaced.replace(subRegex, "$1https://www.reddit.com$2$3$2$5"); }; \ No newline at end of file diff --git a/assets/views/layout.ejs b/assets/views/layout.ejs index 9413072e..3d084589 100644 --- a/assets/views/layout.ejs +++ b/assets/views/layout.ejs @@ -23,7 +23,6 @@ }); - <% if(sails.config.environment == 'development' ){ %> <% } %> @@ -65,5 +64,6 @@ + <% if(sails.config.environment == 'development' ){ %> <% } %> diff --git a/test/unit/markdown/remapURLs.test.js b/test/unit/markdown/remapURLs.test.js index 344ae352..fefdf1e7 100644 --- a/test/unit/markdown/remapURLs.test.js +++ b/test/unit/markdown/remapURLs.test.js @@ -1,16 +1,50 @@ var assert = require("chai").assert; var remapURLs = require("../../../assets/markdown/remapURLs"); +var userUrl = "/u/test"; +var userUrlAfter = "/u/test (FlairHQ)"; +var userUrlWithExtra = "/u/testsomething else"; +var userUrlWithExtraAfter = "/u/test (FlairHQ)something else"; +var userUrlWrong = "/u/not_test"; +var userUrlWrongAfter = userUrlWrong; + +var subUrl = "/r/test"; +var subUrlAfter = "/r/test"; +var subUrlWithExtra = "/r/testsomething else"; +var subUrlWithExtraAfter = "/r/testsomething else"; +var subUrlWrong = "/r/not-test"; +var subUrlWrongAfter = subUrlWrong; + describe("replaceUserURLS", function () { - it("Replaces /u/test with http://www.reddit.com/u/test", function () { - var test = remapURLs('/u/test'); - assert.equal(test, '/u/test (FlairHQ)', "Not mapping user urls correctly.") + it("Replaces " + userUrl + " with " + userUrlAfter, function () { + var test = remapURLs(userUrl); + assert.equal(test, userUrlAfter, "Not mapping user urls correctly."); + }); + + it("Replaces " + userUrlWithExtra + " with " + userUrlWithExtraAfter, function () { + var test = remapURLs(userUrlWithExtra); + assert.equal(test, userUrlWithExtraAfter, "Not mapping user urls correctly."); + }); + + it("Replaces " + userUrlWrong + " with " + userUrlWrongAfter, function () { + var test = remapURLs(userUrlWrong); + assert.equal(test, userUrlWrongAfter, "Not mapping user urls correctly."); }); }); describe("replaceSubURLS", function () { - it("Replaces /u/test with http://www.reddit.com/u/test", function () { - var test = remapURLs('/r/test'); - assert.equal(test, '/r/test', "Not mapping sub urls correctly.") + it("Replaces " + subUrl + " with " + subUrlAfter, function () { + var test = remapURLs(subUrl); + assert.equal(test, subUrlAfter, "Not mapping sub urls correctly."); + }); + + it("Replaces " + subUrlWithExtra + " with " + subUrlWithExtraAfter, function () { + var test = remapURLs(subUrlWithExtra); + assert.equal(test, subUrlWithExtraAfter, "Not mapping sub urls correctly."); + }); + + it("Replaces " + subUrlWrong + " with " + subUrlWrongAfter, function () { + var test = remapURLs(subUrlWrong); + assert.equal(test, subUrlWrongAfter, "Not mapping sub urls correctly."); }); }); \ No newline at end of file From 8eb90c2cf8bffeb11dc3ca21d7abe0a3057cbf1d Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 20:21:24 +0000 Subject: [PATCH 27/71] Add verbose log level to example. --- config/local.example.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/local.example.js b/config/local.example.js index 198a6351..1eb8a31d 100644 --- a/config/local.example.js +++ b/config/local.example.js @@ -27,5 +27,10 @@ module.exports = { port: 27017, db: 'fapp', collection: 'sessions' + }, + + // This should only be used for development + log: { + level: "verbose" } }; From 5668e82643ffa05b080574e8b1db9abacae39f93 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 23 Nov 2015 20:23:09 +0000 Subject: [PATCH 28/71] Adding the tasks outside the watch doesn't make it quicker. --- assets/markdown/remapURLs.js | 8 ++++---- tasks/register/default.js | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/assets/markdown/remapURLs.js b/assets/markdown/remapURLs.js index 38165a81..a6ee914b 100644 --- a/assets/markdown/remapURLs.js +++ b/assets/markdown/remapURLs.js @@ -1,6 +1,6 @@ module.exports = function (value) { - var userRegex = /()\/?\2(.*?)(<\/a>)/g; - var userReplaced = value.replace(userRegex, "$1https://www.reddit.com$2$3$2$5 ($1$2$3FlairHQ$5)"); - var subRegex = /()\/?\2(.*?)(<\/a>)/g; - return userReplaced.replace(subRegex, "$1https://www.reddit.com$2$3$2$5"); + var userRegex = /()\/?\2(.*?)(<\/a>)/g; + var userReplaced = value.replace(userRegex, "$1https://www.reddit.com/$2$3$2$5 ($1$2$3FlairHQ$5)"); + var subRegex = /()\/?\2(.*?)(<\/a>)/g; + return userReplaced.replace(subRegex, "$1https://www.reddit.com/$2$3$2$5"); }; \ No newline at end of file diff --git a/tasks/register/default.js b/tasks/register/default.js index 401377fb..6fdd3610 100644 --- a/tasks/register/default.js +++ b/tasks/register/default.js @@ -3,8 +3,6 @@ module.exports = function (grunt) { 'compileDev', 'sails-linker:devJs', 'sails-linker:devStyles', - 'browserify:dev', - 'eslint', 'watch:assets' ]); }; From 454b04905b3a503ec890528d06b35d0b9aa07e6d Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Mon, 23 Nov 2015 15:43:00 -0500 Subject: [PATCH 29/71] Fixed regex and unit tests --- assets/markdown/remapURLs.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/markdown/remapURLs.js b/assets/markdown/remapURLs.js index a6ee914b..6b84c566 100644 --- a/assets/markdown/remapURLs.js +++ b/assets/markdown/remapURLs.js @@ -1,6 +1,6 @@ module.exports = function (value) { - var userRegex = /()\/?\2(.*?)(<\/a>)/g; - var userReplaced = value.replace(userRegex, "$1https://www.reddit.com/$2$3$2$5 ($1$2$3FlairHQ$5)"); - var subRegex = /()\/?\2(.*?)(<\/a>)/g; - return userReplaced.replace(subRegex, "$1https://www.reddit.com/$2$3$2$5"); + var userRegex = /()(\/?\2)(<\/a>)/g; + var userReplaced = value.replace(userRegex, "$1https://www.reddit.com/$2$3$4$5 ($1/$2$3FlairHQ$5)"); + var subRegex = /()(\/?\2)(<\/a>)/g; + return userReplaced.replace(subRegex, "$1https://www.reddit.com/$2$3$4$5"); }; \ No newline at end of file From d24f77889e370730676c5010ca4dc4163e5ff0cb Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Mon, 23 Nov 2015 02:53:12 -0500 Subject: [PATCH 30/71] Some search display improvements --- api/services/Reddit.js | 4 +++- assets/search/log/result.ejs | 4 ++-- assets/search/modmail/result.ejs | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/api/services/Reddit.js b/api/services/Reddit.js index 39a1faa2..4a242bee 100644 --- a/api/services/Reddit.js +++ b/api/services/Reddit.js @@ -38,6 +38,8 @@ var makeRequest = async function (refreshToken, requestType, url, data, rateLimi if (left < rateLimitRemainingThreshold && moment().before(resetTime)) { throw {statusCode: 504, error: "Rate limited"}; } + // Prevent Reddit from sanitizing '> < &' to '> < &' in the response + url += (url.indexOf('?') === -1 ? '?' : '&') + 'raw_json=1'; var headers = {"User-Agent": sails.config.reddit.userAgent}; if (url.indexOf("oauth.reddit.com") !== -1) { headers.Authorization = "bearer " + await exports.refreshToken(refreshToken); @@ -118,7 +120,7 @@ exports.banUser = function (refreshToken, username, ban_message, note, subreddit }; exports.getWikiPage = async function (refreshToken, subreddit, page) { - var url = 'https://oauth.reddit.com/r/' + subreddit + '/wiki/' + page + '?raw_json=1'; + var url = 'https://oauth.reddit.com/r/' + subreddit + '/wiki/' + page; //Return a Promise for content of the page instead of all the other data let res = await makeRequest(refreshToken, 'GET', url, undefined, 5); return res.data.content_md; diff --git a/assets/search/log/result.ejs b/assets/search/log/result.ejs index 25a5705e..5489a280 100644 --- a/assets/search/log/result.ejs +++ b/assets/search/log/result.ejs @@ -1,7 +1,7 @@

- /u/{{result.user}} - {{result.createdAt | date:'yyyy-MM-dd HH:mm:ss'}} UTC + /u/{{result.user}} - {{result.createdAt | date:"yyyy-MM-dd HH:mm:ss' GMT'Z"}}

- {{result.content}}. + {{result.content}}

\ No newline at end of file diff --git a/assets/search/modmail/result.ejs b/assets/search/modmail/result.ejs index 809dd016..4571ab71 100644 --- a/assets/search/modmail/result.ejs +++ b/assets/search/modmail/result.ejs @@ -2,5 +2,6 @@ {{result.subject}} by /u/{{result.author}}

- {{result.created_utc * 1000 | date:'yyyy-MM-dd HH:mm:ss'}} {{timezoneOffset}}
{{result.body}} + {{result.created_utc * 1000 | date:"yyyy-MM-dd HH:mm:ss' GMT'Z"}}

+ From c26a73ee7572db3a3909b029971b2e811215139d Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Tue, 24 Nov 2015 20:14:58 +0000 Subject: [PATCH 31/71] Add grunt-focus and have different watches for less and js. --- package.json | 1 + tasks/config/watch.js | 34 +++++++++++++++++++++------------- tasks/register/default.js | 2 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 832ee6ac..af10227c 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "grunt-contrib-uglify": "~0.9.2", "grunt-contrib-watch": "~0.6.1", "grunt-eslint": "^17.3.1", + "grunt-focus": "^0.1.1", "grunt-mocha-test": "^0.12.7", "grunt-requirejs": "~0.4.2", "grunt-sails-linker": "~0.10.1", diff --git a/tasks/config/watch.js b/tasks/config/watch.js index b2d30052..d5f485df 100644 --- a/tasks/config/watch.js +++ b/tasks/config/watch.js @@ -16,28 +16,29 @@ module.exports = function (grunt) { grunt.config.set('watch', { api: { - - // API files to watch: files: ['api/**/*'] }, - assets: { - - // Assets to watch: + less: { files: [ - 'assets/**/*', - 'tasks/pipeline.js' + 'assets/styles/**/*' + ], + tasks: [ + 'less:dev' + ], + options: { + livereload: true, + livereloadOnError: false + } + }, + js: { + files: [ + 'assets/**/*.js' ], - - // When assets are changed: tasks: [ - 'less:dev', 'copy:dev', - 'sails-linker:devJs', - 'sails-linker:devStyles', 'eslint', 'browserify:dev' ], - options: { livereload: true, livereloadOnError: false @@ -45,5 +46,12 @@ module.exports = function (grunt) { } }); + grunt.config.set('focus', { + dev: { + include: ['less', 'js'] + } + }); + + grunt.loadNpmTasks('grunt-focus'); grunt.loadNpmTasks('grunt-contrib-watch'); }; diff --git a/tasks/register/default.js b/tasks/register/default.js index 6fdd3610..b9b43888 100644 --- a/tasks/register/default.js +++ b/tasks/register/default.js @@ -3,6 +3,6 @@ module.exports = function (grunt) { 'compileDev', 'sails-linker:devJs', 'sails-linker:devStyles', - 'watch:assets' + 'focus:dev' ]); }; From e9022c2f9543c08d28924f3f7753f6cbf12ba953 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Tue, 24 Nov 2015 20:19:33 +0000 Subject: [PATCH 32/71] Use watchify rather than browserify. --- tasks/config/browserify.js | 3 ++- tasks/config/watch.js | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tasks/config/browserify.js b/tasks/config/browserify.js index ad9ebcdf..6f4e7859 100644 --- a/tasks/config/browserify.js +++ b/tasks/config/browserify.js @@ -6,7 +6,8 @@ module.exports = function (grunt) { ".tmp/public/js/app.js": "assets/app.js" }, options: { - transform: [["babelify"]] + transform: [["babelify"]], + watch: true } } }); diff --git a/tasks/config/watch.js b/tasks/config/watch.js index d5f485df..32d8d5ce 100644 --- a/tasks/config/watch.js +++ b/tasks/config/watch.js @@ -36,8 +36,7 @@ module.exports = function (grunt) { ], tasks: [ 'copy:dev', - 'eslint', - 'browserify:dev' + 'eslint' ], options: { livereload: true, From 7d909d015feb27f79e017445b757d834535b9ea0 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Tue, 24 Nov 2015 20:33:47 +0000 Subject: [PATCH 33/71] Add the rest of the user stuff to ejs rather than angular in header --- assets/views/home/header.ejs | 8 +++++--- config/views.js | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/assets/views/home/header.ejs b/assets/views/home/header.ejs index 035e8fe9..15a13703 100644 --- a/assets/views/home/header.ejs +++ b/assets/views/home/header.ejs @@ -25,7 +25,8 @@ <% if (user) {%>
- <% }%> - - <% if (!user) {%> + <% } else {%> <% }%> diff --git a/assets/views/home/index.ejs b/assets/views/home/index.ejs index f8e7aea7..6bc23168 100644 --- a/assets/views/home/index.ejs +++ b/assets/views/home/index.ejs @@ -25,7 +25,7 @@
- + {{thingamy.refUrl}}{{thingamy.type}} @@ -53,37 +53,37 @@ - + - + - + - + - + - + - +
From 058ee459c39d7744cc02ee3a41d36f3328624475 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Tue, 24 Nov 2015 23:16:45 -0500 Subject: [PATCH 40/71] Added CSRF protection --- assets/userCtrl.js | 12 ++++++++++++ config/csrf.js | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/assets/userCtrl.js b/assets/userCtrl.js index c79046f5..3974748e 100644 --- a/assets/userCtrl.js +++ b/assets/userCtrl.js @@ -480,4 +480,16 @@ module.exports = function ($scope, $filter, $location, UserFactory) { }; $scope.getFlairs(); + + // Get a CSRF token from the server. + io.socket.get('/csrfToken', function (token) { + $scope._csrf = token._csrf; + }); + + /* Include the csrf token in all POST requests. + * (As far as I can tell, there's no better way to do this aside from manually adding the token parameter to each request.)*/ + io.socket.post = function (url, data, callback) { + data._csrf = $scope._csrf; + io.socket.request({method: 'post', url: url, params: data}, callback); + }; }; diff --git a/config/csrf.js b/config/csrf.js index 22e419a7..c728e60b 100644 --- a/config/csrf.js +++ b/config/csrf.js @@ -48,7 +48,7 @@ * * ****************************************************************************/ -// module.exports.csrf = false; +module.exports.csrf = true; /**************************************************************************** * * From addd266f1734ec866a9af7cbce00eeb1cd813676 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Fri, 27 Nov 2015 03:24:57 -0500 Subject: [PATCH 41/71] Fix #440 --- assets/views/home/header.ejs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/assets/views/home/header.ejs b/assets/views/home/header.ejs index 4241f896..7f87174c 100644 --- a/assets/views/home/header.ejs +++ b/assets/views/home/header.ejs @@ -22,7 +22,7 @@ From 39154596ded25b6a70486b3b0cc1b821cb2e0172 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Fri, 27 Nov 2015 05:40:12 -0500 Subject: [PATCH 42/71] Moved CSRF token to angular service --- assets/adminCtrl.js | 4 +--- assets/app.js | 22 +++++++++++++++++----- assets/banCtrl.js | 5 +---- assets/indexCtrl.js | 4 +--- assets/refCtrl.js | 4 +--- assets/userCtrl.js | 16 +--------------- assets/views/layout.ejs | 2 +- 7 files changed, 23 insertions(+), 34 deletions(-) diff --git a/assets/adminCtrl.js b/assets/adminCtrl.js index 30a03134..6a682175 100644 --- a/assets/adminCtrl.js +++ b/assets/adminCtrl.js @@ -1,8 +1,6 @@ -var socket = require("socket.io-client"); -var io = require("sails.io.js")(socket); var sharedService = require("./sharedClientFunctions.js"); -module.exports = function ($scope) { +module.exports = function ($scope, io) { $scope.users = []; $scope.flairApps = []; $scope.flairAppError = ""; diff --git a/assets/app.js b/assets/app.js index 693644ee..946670f7 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1,5 +1,6 @@ var ng = require('angular'); var $ = require('jquery'); +var _csrf = $('#app').attr('_csrf'); var refCtrl = require('./refCtrl'); var indexCtrl = require('./indexCtrl'); @@ -40,12 +41,23 @@ fapp.factory('UserFactory', function () { }; }); +fapp.service('io', function () { + var socket = require('socket.io-client'); + var io = require('sails.io.js')(socket); + io.socket.post = function (url, data, callback) { + data._csrf = _csrf; + console.log(data.csrf); + io.socket.request({method: 'post', url: url, params: data}, callback); + }; + return io; +}); + // Define controllers, and their angular dependencies -fapp.controller("referenceCtrl", ['$scope', '$filter', refCtrl]); -fapp.controller("indexCtrl", ['$scope', indexCtrl]); -fapp.controller("userCtrl", ['$scope', '$filter', '$location', 'UserFactory', userCtrl]); -fapp.controller("adminCtrl", ['$scope', adminCtrl]); -fapp.controller("banCtrl", ['$scope', banCtrl]); +fapp.controller("referenceCtrl", ['$scope', '$filter', 'io', refCtrl]); +fapp.controller("indexCtrl", ['$scope', 'io', indexCtrl]); +fapp.controller("userCtrl", ['$scope', '$filter', '$location', 'UserFactory', 'io', userCtrl]); +fapp.controller("adminCtrl", ['$scope', 'io', adminCtrl]); +fapp.controller("banCtrl", ['$scope', 'io', banCtrl]); // Bug fix for iOS safari $(function () { diff --git a/assets/banCtrl.js b/assets/banCtrl.js index 0a82a297..2600f4ba 100644 --- a/assets/banCtrl.js +++ b/assets/banCtrl.js @@ -1,7 +1,4 @@ -var socket = require("socket.io-client"); -var io = require("sails.io.js")(socket); - -module.exports = function ($scope) { +module.exports = function ($scope, io) { $scope.banInfo = { username: $scope.query.username || '', diff --git a/assets/indexCtrl.js b/assets/indexCtrl.js index fc29856c..f0cd44d5 100644 --- a/assets/indexCtrl.js +++ b/assets/indexCtrl.js @@ -1,9 +1,7 @@ -var socket = require("socket.io-client"); -var io = require("sails.io.js")(socket); var $ = require('jquery'); var sharedService = require('./sharedClientFunctions.js'); -module.exports = function ($scope) { +module.exports = function ($scope, io) { $scope.addInfo = { refUrl: $scope.query.refUrl || '', type: $scope.query.type || '', diff --git a/assets/refCtrl.js b/assets/refCtrl.js index a302fdf0..fd2aa9da 100644 --- a/assets/refCtrl.js +++ b/assets/refCtrl.js @@ -1,9 +1,7 @@ -var socket = require("socket.io-client"); -var io = require("sails.io.js")(socket); var $ = require('jquery'); var sharedService = require('./sharedClientFunctions.js'); -module.exports = function ($scope, $filter) { +module.exports = function ($scope, $filter, io) { $scope.newStuff = { newComment: "" }; diff --git a/assets/userCtrl.js b/assets/userCtrl.js index 3974748e..467717ac 100644 --- a/assets/userCtrl.js +++ b/assets/userCtrl.js @@ -1,12 +1,10 @@ -var socket = require("socket.io-client"); -var io = require("sails.io.js")(socket); var regex = require("regex"); var _ = require("lodash"); var $ = require("jquery"); var deparam = require('node-jquery-deparam'); var sharedService = require("./sharedClientFunctions.js"); -module.exports = function ($scope, $filter, $location, UserFactory) { +module.exports = function ($scope, $filter, $location, UserFactory, io) { $scope.regex = regex; $scope.scope = $scope; $scope.user = undefined; @@ -480,16 +478,4 @@ module.exports = function ($scope, $filter, $location, UserFactory) { }; $scope.getFlairs(); - - // Get a CSRF token from the server. - io.socket.get('/csrfToken', function (token) { - $scope._csrf = token._csrf; - }); - - /* Include the csrf token in all POST requests. - * (As far as I can tell, there's no better way to do this aside from manually adding the token parameter to each request.)*/ - io.socket.post = function (url, data, callback) { - data._csrf = $scope._csrf; - io.socket.request({method: 'post', url: url, params: data}, callback); - }; }; diff --git a/assets/views/layout.ejs b/assets/views/layout.ejs index 3d084589..d6330767 100644 --- a/assets/views/layout.ejs +++ b/assets/views/layout.ejs @@ -62,7 +62,7 @@
- + <% if(sails.config.environment == 'development' ){ %> <% } %> From a08e0a293ed15ddfd3380440b46b405d656d1c58 Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Fri, 27 Nov 2015 13:20:51 -0500 Subject: [PATCH 43/71] Fixed #432, #443, and a search UI annoyance --- api/controllers/UserController.js | 1 + assets/search/main.ejs | 8 ++++---- assets/userCtrl.js | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/api/controllers/UserController.js b/api/controllers/UserController.js index ae87329f..9e0e3654 100644 --- a/api/controllers/UserController.js +++ b/api/controllers/UserController.js @@ -96,6 +96,7 @@ module.exports = { return res.serverError(err); } req.user.apps = app; + req.user.redToken = undefined; res.ok(req.user); }); }); diff --git a/assets/search/main.ejs b/assets/search/main.ejs index 3a18c99c..58c07116 100644 --- a/assets/search/main.ejs +++ b/assets/search/main.ejs @@ -16,16 +16,16 @@
- <%- partial('references.ejs', {theUser: "user", refPage: false}) %> + <%- partial('references.ejs', {theUser: "refUser", refPage: false}) %> <%- partial('editreference.ejs') %> diff --git a/assets/views/home/reference.ejs b/assets/views/home/reference.ejs index c98e0633..6dbebb05 100644 --- a/assets/views/home/reference.ejs +++ b/assets/views/home/reference.ejs @@ -5,17 +5,17 @@

- {{refUser.name}} + <%= refUser.name %>

- {{refUser.flair.ptrades.flair_text}} + <%= refUser.flair.ptrades.flair_text %>
- {{refUser.flair.svex.flair_text}} + <%= refUser.flair.svex.flair_text %>
diff --git a/assets/views/layout.ejs b/assets/views/layout.ejs index d6330767..73dc82f1 100644 --- a/assets/views/layout.ejs +++ b/assets/views/layout.ejs @@ -25,7 +25,7 @@ - +
<%- partial ('home/header.ejs') %>
@@ -42,7 +42,7 @@ We are currently working hard to improve this so that you won't need JavaScript to use the site. -
@@ -50,7 +50,7 @@
- @@ -62,7 +62,15 @@
- + <% if(sails.config.environment == 'development' ){ %> <% } %> diff --git a/config/express.js b/config/express.js index 4fb63b81..3f904a1c 100644 --- a/config/express.js +++ b/config/express.js @@ -7,7 +7,7 @@ var verifyHandler = function (adminToken, token, tokenSecret, profile, done) { Reddit.getBothFlairs(adminToken, profile.name).then(function (flairs) { if (user) { if (user.banned) { - return done("You are banned from FAPP", user); + return done("banned", user); } user.redToken = tokenSecret; user.flair = {ptrades: flairs[0], svex: flairs[1]}; From a5327b723ad84275cb4fbb038ac4b189c0d3be4c Mon Sep 17 00:00:00 2001 From: not-an-aardvark Date: Wed, 2 Dec 2015 21:13:01 -0500 Subject: [PATCH 52/71] Refactored a bunch of clientside things, made some pages load much faster --- api/controllers/FlairController.js | 12 +- api/controllers/HomeController.js | 15 +- api/controllers/ReferenceController.js | 36 ++-- api/controllers/UserController.js | 36 +--- api/policies/bearerAuth.js | 7 - api/policies/passport.js | 21 +- api/policies/sessionAuth.js | 20 +- api/services/Flairs.js | 11 + api/services/References.js | 3 + api/services/Users.js | 55 +++++ assets/adminCtrl.js | 24 +-- assets/indexCtrl.js | 6 +- assets/refCtrl.js | 1 - assets/search/header.ejs | 1 - assets/search/search.controller.js | 6 +- assets/userCtrl.js | 43 +--- assets/views/home/banlist.ejs | 6 +- assets/views/home/references.ejs | 273 +++---------------------- assets/views/home/row.ejs | 40 ++++ assets/views/layout.ejs | 21 +- config/blueprints.js | 6 +- config/routes.js | 28 ++- package.json | 1 - 23 files changed, 238 insertions(+), 434 deletions(-) delete mode 100644 api/policies/bearerAuth.js create mode 100644 api/services/Users.js create mode 100644 assets/views/home/row.ejs diff --git a/api/controllers/FlairController.js b/api/controllers/FlairController.js index 06b30041..23ee5735 100644 --- a/api/controllers/FlairController.js +++ b/api/controllers/FlairController.js @@ -174,7 +174,7 @@ module.exports = { var message = 'The user /u/' + req.user.name + ' set the following flairs:\n\n' + flairs.ptrades + '\n\n' + flairs.svex + '\n\n'; if (identical_banned_fcs.length) { message += 'This flair contains a banned friend code: ' + identical_banned_fcs + '\n\n'; - } else if (flagged && similar_banned_fcs.length) { + } else if (flagged.length && similar_banned_fcs.length) { message += 'This flair contains a friend code similar to the following banned friend code' + (similar_banned_fcs.length > 1 ? 's: ' : ': ') + similar_banned_fcs.join(', ') + '\n\n'; } @@ -203,10 +203,12 @@ module.exports = { }); }, - getApps: function (req, res) { - Application.find().exec(function (err, apps) { - return res.ok(apps); - }); + getApps: async function (req, res) { + try { + return res.ok(await Flairs.getApps()); + } catch (err) { + return res.serverError(err); + } } }; diff --git a/api/controllers/HomeController.js b/api/controllers/HomeController.js index f94d353b..fefadf0b 100644 --- a/api/controllers/HomeController.js +++ b/api/controllers/HomeController.js @@ -33,18 +33,21 @@ module.exports = { reference: async function(req, res) { try { - res.view({refUser: await Users.get(req.user, req.params.user)}); + return res.view({refUser: await Users.get(req.user, req.params.user)}); } catch (err) { if (err.statusCode === 404) { - res.view('404', {data: {user: req.params.user, error: "User not found"}}); - } else { - res.serverError(); + return res.view('404', {data: {user: req.params.user, error: "User not found"}}); } + return res.serverError(err); } }, - banlist: function (req, res) { - res.view(); + banlist: async function (req, res) { + try { + return res.view({bannedUsers: await Users.getBannedUsers()}); + } catch (err) { + return res.serverError(err); + } }, banuser: function (req, res) { diff --git a/api/controllers/ReferenceController.js b/api/controllers/ReferenceController.js index 03bbbb0f..af7363dd 100644 --- a/api/controllers/ReferenceController.js +++ b/api/controllers/ReferenceController.js @@ -44,7 +44,6 @@ module.exports = { return res.ok(results); }); }); - }, add: function (req, res) { @@ -60,7 +59,7 @@ module.exports = { return res.serverError(err); } if (ref && (ref.type !== "egg" || req.params.type !== "egg")) { - return res.badRequest(); + return res.status(400).json({err: 'Already added that URL.'}); } Reference.create( { @@ -195,18 +194,16 @@ module.exports = { }); }, - approve: function (req, res) { - Reference.findOne(req.allParams().id, function (err, ref) { - if (!ref || err) { - return res.notFound(err); + approve: async function (req, res) { + try { + var ref = await Reference.findOne(req.allParams().id); + if (!References.isApprovable(ref)) { + return res.badRequest(); } - References.approve(ref, req.allParams().approve).then(function (result) { - return res.ok(result); - }, function (error) { - console.log(error); - return res.serverError(error); - }); - }); + return await References.approve(ref, req.allParams().approve); + } catch (err) { + return res.serverError(err); + } }, approveAll: function (req, res) { @@ -249,13 +246,12 @@ module.exports = { }); }, - getFlairs: function (req, res) { - Flair.find().exec(function (err, flairs) { - if (err) { - return res.serverError(err); - } - return res.ok(flairs); - }); + getFlairs: async function (req, res) { + try { + return res.ok(await Flairs.getFlairs()); + } catch (err) { + return res.serverError(err); + } } }; diff --git a/api/controllers/UserController.js b/api/controllers/UserController.js index 357d1aab..9219deb6 100644 --- a/api/controllers/UserController.js +++ b/api/controllers/UserController.js @@ -81,27 +81,6 @@ module.exports = { }); }, - mine: function (req, res) { - Game.find() - .where({user: req.user.name}) - .exec(function (err, games) { - req.user.games = games; - - var appData = { - user: req.user.name - }; - - Application.find(appData).exec(function (err, app) { - if (err) { - return res.serverError(err); - } - req.user.apps = app; - req.user.redToken = undefined; - res.ok(req.user); - }); - }); - }, - get: async function (req, res) { try { return res.ok(await Users.get(req.user, req.params.name)); @@ -109,7 +88,7 @@ module.exports = { if (err.statusCode === 404) { return res.notFound(); } - return res.serverError(); + return res.serverError(err); } }, @@ -274,13 +253,12 @@ module.exports = { }); }, - bannedUsers: function (req, res) { - User.find({banned: true}).exec(function (err, users) { - if (err) { - return res.serverError(err); - } - return res.ok(users); - }); + bannedUsers: async function (req, res) { + try { + return res.ok(await Users.getBannedUsers()); + } catch (err) { + return res.serverError(err); + } }, clearSession: function (req, res) { diff --git a/api/policies/bearerAuth.js b/api/policies/bearerAuth.js deleted file mode 100644 index 44660dd9..00000000 --- a/api/policies/bearerAuth.js +++ /dev/null @@ -1,7 +0,0 @@ -var passport = require('passport'); - -module.exports = function(req, res, next) { - - return passport.authenticate('reddit', { session: false })(req, res, next); - -}; \ No newline at end of file diff --git a/api/policies/passport.js b/api/policies/passport.js index adab86c1..8006efaa 100644 --- a/api/policies/passport.js +++ b/api/policies/passport.js @@ -4,14 +4,21 @@ module.exports = function (req, res, next) { // Initialize Passport passport.initialize()(req, res, function () { // Use the built-in sessions - passport.session()(req, res, function () { - // Make the user and query available throughout the frontend - res.locals.user = req.user; - if (res.locals.user) { - res.locals.user.redToken = undefined; + passport.session()(req, res, async function () { + try { + res.locals.user = req.user; + if (res.locals.user) { + res.locals.user.redToken = undefined; + } + res.locals.query = req.query; + res.locals.flairs = await Flairs.getFlairs(); + if (req.user && req.user.isMod) { + res.locals.flairApps = await Flairs.getApps(); + } + next(); + } catch (err) { + return res.serverError(err); } - res.locals.query = req.query; - next(); }); }); }; \ No newline at end of file diff --git a/api/policies/sessionAuth.js b/api/policies/sessionAuth.js index 07b10622..b4fe2493 100644 --- a/api/policies/sessionAuth.js +++ b/api/policies/sessionAuth.js @@ -8,27 +8,15 @@ * */ module.exports = function(req, res, next) { - - // User is banned, log them out. - if (req.user && req.user.banned) { - req.logout(); - if (req.isSocket) { - return res.forbidden("You have been banned from FAPP"); + if (req.user) { + if (req.user.banned) { + req.logout(); + return res.view(403, {error: "You have been banned from FlairHQ"}); } - return res.forbidden("You have been banned from FAPP"); - } - - // User is allowed, proceed to the next policy, - // or if this is the last policy, the controller - if (req.user || (req.isAuthenticated && req.isAuthenticated())) { return next(); } - - // User is not allowed - // (default res.forbidden() behavior can be overridden in `config/403.js`) if (req.isSocket) { return res.status(403).json({status: 403, redirectTo: "/login"}); } - return res.redirect('/login' + (req.url !== '/' ? '?redirect=' + encodeURIComponent(req.url) : '')); }; diff --git a/api/services/Flairs.js b/api/services/Flairs.js index 0dd50577..37e6ff4e 100644 --- a/api/services/Flairs.js +++ b/api/services/Flairs.js @@ -251,3 +251,14 @@ exports.getSimilarBannedFCs = function (fc) { }); }); }; + +// Returns a promise of all flair apps for a particular username. If username is undefined, returns flair apps for all users. +exports.getApps = function (username) { + var query = username ? {user: username} : {}; + return Application.find(query); +}; + +// Returns a promise for all flairs +exports.getFlairs = function () { + return Flair.find({}); +}; diff --git a/api/services/References.js b/api/services/References.js index db2e9ad7..b08e1e24 100644 --- a/api/services/References.js +++ b/api/services/References.js @@ -72,6 +72,9 @@ exports.isEggCheck = function (el) { exports.isMisc = function (el) { return el.type === "misc"; }; +exports.isApprovable = function (el) { + return ['event', 'shiny', 'casual', 'egg', 'giveaway', 'involvement', 'eggcheck'].indexOf(el.type) !== -1; +}; exports.isNotNormalTrade = function (type) { return type === 'egg' || type === 'giveaway' || type === 'misc' || type === 'eggcheck' || type === 'involvement'; }; diff --git a/api/services/Users.js b/api/services/Users.js new file mode 100644 index 00000000..b8c58352 --- /dev/null +++ b/api/services/Users.js @@ -0,0 +1,55 @@ +var removeSecretInformation = function (user) { + user.redToken = undefined; + user.loggedFriendCodes = undefined; + return user; +}; + +exports.get = async function (requester, username) { + var user = await User.findOne(username); + if (!user) { + throw {statusCode: 404}; + } + var promises = []; + + promises.push(Game.find({user: user.name}).sort({createdAt: 'desc'}).then(function (result) { + user.games = result; + })); + + promises.push(Comment.find({user: user.name}).sort({createdAt: 'desc'}).then(function (result) { + user.comments = result; + })); + + if (requester && requester.isMod) { + promises.push(ModNote.find({refUser: user.name}).sort({createdAt: 'desc'}).then(function (result) { + user.modNotes = result; + })); + } + + if (requester && requester.name === user.name) { + promises.push(Application.find({user: user.name}).then(function (result) { + user.apps = result; + })); + } + + promises.push(Reference.find({user: user.name}).sort({type: 'asc', createdAt: 'desc'}).then(function (result) { + result.forEach(function (ref) { + if (!requester || requester.name !== user.name) { + ref.privatenotes = undefined; + } + if (!requester || !requester.isMod) { + ref.approved = undefined; + ref.verified = undefined; + } + }); + user.references = result; + })); + await* promises; + return removeSecretInformation(user); +}; + +// Returns a promise for all banned users +exports.getBannedUsers = function () { + return User.find({banned: true}).then(function (results) { + return results.map(removeSecretInformation); + }); +}; diff --git a/assets/adminCtrl.js b/assets/adminCtrl.js index c0a04da9..276a6fcb 100644 --- a/assets/adminCtrl.js +++ b/assets/adminCtrl.js @@ -1,6 +1,5 @@ module.exports = function ($scope, io) { $scope.users = []; - $scope.flairApps = []; $scope.flairAppError = ""; $scope.adminok = { appFlair: {} @@ -9,24 +8,6 @@ module.exports = function ($scope, io) { appFlair: {} }; - $scope.getFlairApps = function () { - io.socket.get("/flair/apps/all", function (data, res) { - if (res.statusCode === 200) { - $scope.flairApps = data; - $scope.$apply(); - } - }); - }; - - $scope.getBannedUsers = function () { - io.socket.get("/user/banned", function (data, res) { - if (res.statusCode === 200) { - $scope.users = data; - $scope.$apply(); - } - }); - }; - $scope.denyApp = function (id, $index) { var url = "/flair/app/deny"; $scope.flairAppError = ""; @@ -61,7 +42,4 @@ module.exports = function ($scope, io) { $scope.$apply(); }); }; - - $scope.getBannedUsers(); - $scope.getFlairApps(); -}; \ No newline at end of file +}; diff --git a/assets/indexCtrl.js b/assets/indexCtrl.js index e004b144..bccfaac7 100644 --- a/assets/indexCtrl.js +++ b/assets/indexCtrl.js @@ -1,6 +1,6 @@ var $ = require('jquery'); -module.exports = function ($scope, user, io) { +module.exports = function ($scope, io) { $scope.addInfo = { refUrl: $scope.query.refUrl || '', type: $scope.query.type || '', @@ -69,7 +69,7 @@ module.exports = function ($scope, user, io) { $scope.addInfo.notes = ""; $scope.addInfo.privatenotes = ""; $scope.addInfo.number = ""; - $scope.user.references.push(data); + $scope.refUser.references.push(data); if (data.type === "redemption") { $('#collapseevents').prev().children().animate({ @@ -107,7 +107,7 @@ module.exports = function ($scope, user, io) { if (data && data.err) { $scope.addRefError = data.err; } else { - $scope.addRefError = "Already added that URL."; + $scope.addRefError = "There was an error."; } $scope.$apply(); } diff --git a/assets/refCtrl.js b/assets/refCtrl.js index 1f5f3d99..e2fd505b 100644 --- a/assets/refCtrl.js +++ b/assets/refCtrl.js @@ -16,7 +16,6 @@ module.exports = function ($scope, io) { $scope.referenceToRevert = {}; $scope.indexOk = {}; $scope.indexSpin = {}; - $scope.loaded = true; $scope.editReference = function (ref) { $scope.selectedRef = ref; diff --git a/assets/search/header.ejs b/assets/search/header.ejs index 4061d5a1..74766736 100644 --- a/assets/search/header.ejs +++ b/assets/search/header.ejs @@ -1,6 +1,5 @@
diff --git a/assets/views/home/references.ejs b/assets/views/home/references.ejs index 3040ea7a..fe0fc222 100644 --- a/assets/views/home/references.ejs +++ b/assets/views/home/references.ejs @@ -3,7 +3,7 @@

Trades ({{numberOfTrades()}})

-

Approved

+

Approved

@@ -11,7 +11,7 @@ [+] Events ({{<%= theUser %>.references.filter(isEvent).length}}) - {{<%= theUser %>.references.filter(isEvent).filter(isApproved).length}} + {{<%= theUser %>.references.filter(isEvent).filter(isApproved).length}} - {{$index + 1}}. - {{reference.gave}} for {{reference.got}} - - - {{getRedditUser(reference.user2)}} - - - - - * - - - - - - - - - - - - - + <%- partial('row.ejs') %> @@ -59,7 +33,7 @@ [+] Shinies ({{<%= theUser %>.references.filter(isShiny).length}}) - {{<%= theUser %>.references.filter(isShiny).filter(isApproved).length}} + {{<%= theUser %>.references.filter(isShiny).filter(isApproved).length}} - {{$index + 1}}. - {{reference.gave}} for {{reference.got}} - - - {{getRedditUser(reference.user2)}} - - - - - * - - - - - - - - - - - - - + <%- partial('row.ejs') %> @@ -108,7 +56,7 @@ [+] Competitive / Casual ({{<%= theUser %>.references.filter(isCasual).length}}) - {{<%= theUser %>.references.filter(isCasual).filter(isApproved).length}} + {{<%= theUser %>.references.filter(isCasual).filter(isApproved).length}} - {{$index + 1}}. - {{reference.gave}} for {{reference.got}} - - - {{getRedditUser(reference.user2)}} - - - - - * - - - - - - - - - - - - - + <%- partial('row.ejs') %> @@ -160,28 +82,7 @@ - {{$index + 1}}. - {{reference.gave}} for {{reference.got}} - - - {{getRedditUser(reference.user2)}} - - - - - - - - - - - - - - + <%- partial('row.ejs') %> @@ -189,7 +90,7 @@ [+]

Egg Hatches ({{<%= theUser %>.references.filter(isEgg).length}})

- {{<%= theUser %>.references.filter(isEgg).filter(isApproved).length}} + {{<%= theUser %>.references.filter(isEgg).filter(isApproved).length}} - {{$index + 1}}. - {{reference.description || reference.descrip}} - - - {{getRedditUser(reference.user2)}} - - - - - * - - - - - - - - - - - - + <%- partial('row.ejs') %> @@ -237,7 +113,7 @@ [+]

Giveaways/Contests ({{numberOfPokemonGivenAway(<%= theUser %>)}} Pokémon given, {{numberOfEggsGivenAway(<%= theUser %>)}} eggs given)

- {{numberOfApprovedEggChecks(<%= theUser %>)}} + {{numberOfApprovedEggChecks(<%= theUser %>)}} - {{$index + 1}}. - - {{reference.description || reference.descrip}} - {{reference.number ? "(" + reference.number + " checked)" : ""}} - - - - {{reference.description || reference.descrip}} - {{reference.number ? "(" + reference.number + " checked)" : ""}} - - - - * - - - - - - - - - - - - - - + <%- partial('row.ejs') %> @@ -340,7 +158,7 @@ [+]

Free Tradeback/Free Redemption ({{<%= theUser %>.references.filter(isInvolvement).length}})

- {{<%= theUser %>.references.filter(isInvolvement).filter(isApproved).length}} + {{<%= theUser %>.references.filter(isInvolvement).filter(isApproved).length}} - {{$index + 1}}. - {{reference.description || reference.descrip}} - - - {{getRedditUser(reference.user2)}} - - - - - * - - - - - - - - - - - - - - + <%- partial('row.ejs') %> @@ -390,7 +181,7 @@ [+]

Misc ({{<%= theUser %>.references.filter(isMisc).length}})

+ ng-really-click="denyApp(app.id)">Deny diff --git a/assets/views/home/flairApply.ejs b/assets/views/home/flairApply.ejs index bfa42f4c..074ed87c 100644 --- a/assets/views/home/flairApply.ejs +++ b/assets/views/home/flairApply.ejs @@ -17,7 +17,7 @@ + href="#" ng-click="setSelectedFlair(flair.name, canUserApply(flair))" title={{formattedRequirements(flair.name)}}> {{formattedName(flair.name)}} (Pending) @@ -26,7 +26,7 @@ + href="#" ng-click="setSelectedFlair(flair.name, canUserApply(flair))" title={{formattedRequirements(flair.name)}}> {{formattedName(flair.name)}} (Pending) @@ -38,7 +38,7 @@ + href="#" ng-click="setSelectedFlair(flair.name, canUserApply(flair))" title={{formattedRequirements(flair.name)}}> {{formattedName(flair.name)}} (Pending) @@ -47,7 +47,7 @@ + href="#" ng-click="setSelectedFlair(flair.name, canUserApply(flair))" title={{formattedRequirements(flair.name)}}> {{formattedName(flair.name)}} (Pending) @@ -62,9 +62,12 @@ - <%- partial('references.ejs', {theUser: "refUser", refPage: false}) %> + <%- partial('references.ejs', {refPage: false}) %> <%- partial('editreference.ejs') %> diff --git a/assets/views/home/reference.ejs b/assets/views/home/reference.ejs index 6dbebb05..7b1dda69 100644 --- a/assets/views/home/reference.ejs +++ b/assets/views/home/reference.ejs @@ -35,7 +35,7 @@
- <%- partial('references.ejs', {theUser: "refUser", refPage: true}) %> + <%- partial('references.ejs', {refPage: true}) %>
diff --git a/assets/views/home/references.ejs b/assets/views/home/references.ejs index fe0fc222..853495d7 100644 --- a/assets/views/home/references.ejs +++ b/assets/views/home/references.ejs @@ -8,7 +8,7 @@ - [+] Events ({{<%= theUser %>.references.filter(isEvent).length}}) + [+] Events ({{refUser.references.filter(isEvent).length}})