diff --git a/app/emailer.js b/app/emailer.js index 192f7bbb..c7b28afc 100644 --- a/app/emailer.js +++ b/app/emailer.js @@ -1,10 +1,12 @@ +const fs = require('fs') const nodemailer = require('nodemailer') const nodemailerHbs = require('nodemailer-express-handlebars') const moment = require('moment-timezone') -const auth = require(global.appRoot + '/config/auth.js') +const auth = require('../config/auth.js') +const User = require('./models/user') // create reusable transporter object using the default SMTP transport -transporter = nodemailer.createTransport({ +const transporter = nodemailer.createTransport({ host: 'box.raphaelkabo.com', port: 587, secure: false, // true for 465, false for other ports @@ -23,7 +25,7 @@ transporter.verify(function (error, success) { } }) -nodemailerHbsOptions = { +const nodemailerHbsOptions = { viewEngine: { extName: '.handlebars', partialsDir: global.appRoot + '/views/emails', @@ -36,7 +38,7 @@ nodemailerHbsOptions = { transporter.use('compile', nodemailerHbs(nodemailerHbsOptions)) function emailLog (message) { - if (process.env.NODE_ENV == 'production') { + if (process.env.NODE_ENV === 'production') { console.log(message) } fs.appendFileSync('emailLog.txt', message + '\n') @@ -47,7 +49,7 @@ var logFormat = 'dddd, MMMM Do YYYY, h:mm a' // utility function. note that this transforms the input object "in place", rather than returning the changed version function putInUsersLocalTime (momentObject, user) { - if (user.settings.timezone == 'auto') { + if (user.settings.timezone === 'auto') { momentObject.tz(user.settings.autoDetectedTimeZone) } else { momentObject.utcOffset(user.settings.timezone) @@ -65,7 +67,7 @@ User.find({ } ] }).then(users => { - for (user of users) { + for (const user of users) { emailScheduler(user) } }) @@ -88,16 +90,16 @@ function emailScheduler (user, justSentOne = false) { }) // set usersLocalTime's day to that of the next email: - if (user.settings.digestEmailFrequency == 'daily') { + if (user.settings.digestEmailFrequency === 'daily') { // if we're not sending today's email (so, either we've just sent it or the time at which we're supposed to send the email today has past) - if (justSentOne || (usersLocalTime.hour() > emailTimeComps[0]) || (usersLocalTime.hour() == emailTimeComps[0] && usersLocalTime.minute() > emailTimeComps[1])) { + if (justSentOne || (usersLocalTime.hour() > emailTimeComps[0]) || (usersLocalTime.hour() === emailTimeComps[0] && usersLocalTime.minute() > emailTimeComps[1])) { usersLocalTime.add(1, 'd') // then make this moment take place tomorrow } } else { var weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] var emailDayIndex = weekdays.indexOf(user.settings.emailDay) // if we aren't sending this week's email (either we just sent one or the point at which we're supposed to send it this week has past) - if (justSentOne || (usersLocalTime.day() > emailDayIndex) || (usersLocalTime.day() == emailDayIndex && usersLocalTime.hour() > emailTimeComps[0]) || (usersLocalTime.day() == emailDayIndex && usersLocalTime.hour() == emailTimeComps[0] && usersLocalTime.minute() > emailTimeComps[1])) { + if (justSentOne || (usersLocalTime.day() > emailDayIndex) || (usersLocalTime.day() === emailDayIndex && usersLocalTime.hour() > emailTimeComps[0]) || (usersLocalTime.day() === emailDayIndex && usersLocalTime.hour() === emailTimeComps[0] && usersLocalTime.minute() > emailTimeComps[1])) { usersLocalTime.day(user.settings.emailDay) // set the day of the week usersLocalTime.add(7, 'd') // then make this moment take place next week } else { @@ -114,8 +116,8 @@ function emailScheduler (user, justSentOne = false) { emailLog('sendUpdateEmail ran for ' + user.username + ' on ' + emailSentTime.format(logFormat) + ' our time, our time zone being UTC' + emailSentTime.format('Z z')) putInUsersLocalTime(emailSentTime, user) emailLog('that is equivalent to ' + emailSentTime.format(logFormat) + ' their time!') - emailLog('their email time is: ' + (user.settings.digestEmailFrequency == 'weekly' ? user.settings.emailDay + ', ' : '') + user.settings.emailTime) - emailLog('their time zone is: ' + (user.settings.timezone == 'auto' ? user.settings.autoDetectedTimeZone : user.settings.timezone)) + emailLog('their email time is: ' + (user.settings.digestEmailFrequency === 'weekly' ? user.settings.emailDay + ', ' : '') + user.settings.emailTime) + emailLog('their time zone is: ' + (user.settings.timezone === 'auto' ? user.settings.autoDetectedTimeZone : user.settings.timezone)) emailLog('their email frequency preference is: ' + user.settings.digestEmailFrequency) emailLog('their email address is: ' + user.email) emailLog('\n') @@ -125,8 +127,8 @@ function emailScheduler (user, justSentOne = false) { emailLog('scheduled email for user ' + user.username + ' to be sent on ' + nextEmailTime.format(logFormat) + ' our time, our time zone being UTC' + nextEmailTime.format('Z z')) putInUsersLocalTime(nextEmailTime, user) emailLog('that is equivalent to ' + nextEmailTime.format(logFormat) + ' their time!') - emailLog('their email time is: ' + (user.settings.digestEmailFrequency == 'weekly' ? user.settings.emailDay + ', ' : '') + user.settings.emailTime) - emailLog('their time zone is: ' + (user.settings.timezone == 'auto' ? user.settings.autoDetectedTimeZone : user.settings.timezone)) + emailLog('their email time is: ' + (user.settings.digestEmailFrequency === 'weekly' ? user.settings.emailDay + ', ' : '') + user.settings.emailTime) + emailLog('their time zone is: ' + (user.settings.timezone === 'auto' ? user.settings.autoDetectedTimeZone : user.settings.timezone)) emailLog('their email frequency preference is: ' + user.settings.digestEmailFrequency) emailLog('their email address is: ' + user.email) emailLog('\n') @@ -134,17 +136,17 @@ function emailScheduler (user, justSentOne = false) { async function sendUpdateEmail (user) { try { - email = {} - if (user.settings.digestEmailFrequency == 'daily') { + const email = {} + if (user.settings.digestEmailFrequency === 'daily') { email.subject = 'sweet daily update 🍭' - } else if (user.settings.digestEmailFrequency == 'weekly') { + } else if (user.settings.digestEmailFrequency === 'weekly') { email.subject = 'sweet weekly update 🍭' } else { emailLog('\n' + 'sendUpdateEmail was called, but ' + user.username + ' does not appear to have their email preference set correctly?') return } - const unreadNotifications = user.notifications.filter(n => n.seen == false) - if (unreadNotifications && unreadNotifications.length != 0) { + const unreadNotifications = user.notifications.filter(n => !n.seen) + if (unreadNotifications && unreadNotifications.length !== 0) { // send mail with defined transport object const info = { from: '"sweet 🍬" ', // sender address @@ -205,17 +207,12 @@ function emailRescheduler (user) { clearTimeout(scheduledEmails[user._id.toString()]) emailLog('cancelled emails for ' + user.username + '!') } - if (user.settings.digestEmailFrequency != 'off') { + if (user.settings.digestEmailFrequency !== 'off') { emailScheduler(user) } } async function sendSingleNotificationEmail (user, notification, link) { - const singleNotification = [{ - url: link, - image: notification.image, - text: notification.emailText - }] const info = { from: '"sweet 🍬" ', to: user.email, diff --git a/app/inhabitingCommunities.js b/app/inhabitingCommunities.js index 9edbc276..1e3abab5 100644 --- a/app/inhabitingCommunities.js +++ b/app/inhabitingCommunities.js @@ -1,17 +1,29 @@ +const mongoose = require('mongoose') +const sharp = require('sharp') +const shortid = require('shortid') +const schedule = require('node-schedule') +const fs = require('fs') +const moment = require('moment') +const helper = require('./utilityFunctionsMostlyText') const CommunityPlaceholder = mongoose.model('Community Placeholder') +const Community = require('./models/community') +const Vote = require('./models/vote') +const User = require('./models/user') +const Post = require('./models/post') +const notifier = require('./notifier') // this is never read from but it could be someday i guess const expiryTimers = [] // set vote expiration timers when the server starts up Vote.find({}).then(votes => { - for (vote of votes) { + for (const vote of votes) { // account for votes that expired while server was down - if (vote.expiryTime.getTime() < new Date() && vote.status != 'expired') { + if (vote.expiryTime.getTime() < new Date() && vote.status !== 'expired') { vote.status = 'expired' vote.save() // account for currently active votes that need to be scheduled to expire - } else if (vote.status == 'active') { + } else if (vote.status === 'active') { var expireVote = schedule.scheduleJob(vote.expiryTime, function () { if (vote) { vote.status = 'expired' @@ -75,11 +87,7 @@ module.exports = function (app, passport) { app.get('/community/:slug', function (req, res) { const today = moment().clone().startOf('day') const thisyear = moment().clone().startOf('year') - if (req.isAuthenticated()) { - isLoggedIn = true - } else { - isLoggedIn = false - } + const isLoggedIn = req.isAuthenticated() let isMember = false let hasRequested = false let isBanned = false @@ -132,7 +140,7 @@ module.exports = function (app, passport) { vote.parsedTimestamp = moment(vote.timestamp).format('D MMM YYYY') } vote.parsedExpiry = moment(vote.expiryTime).locale('en-GB').fromNow() - if (vote.reference == 'userban' || vote.reference == 'userunban' || vote.reference == 'usermute' || vote.reference == 'userunmute') { + if (vote.reference === 'userban' || vote.reference === 'userunban' || vote.reference === 'usermute' || vote.reference === 'userunmute') { vote.group = 'uservotes' User.findOne({ _id: vote.proposedValue @@ -218,7 +226,6 @@ module.exports = function (app, passport) { return res.redirect('back') } else { let imageEnabled = false - const imageUrl = '' const communityUrl = shortid.generate() if (req.files.imageUpload) { if (req.files.imageUpload.data.length > 3145728) { @@ -297,7 +304,7 @@ module.exports = function (app, passport) { touchCommunity(req.params.communityid) } var user = await User.findOne({ _id: req.user._id }) - if (!user.communities.some(v => v.toString() == req.params.communityid)) { + if (!user.communities.some(v => v.toString() === req.params.communityid)) { user.communities.push(req.params.communityid) await user.save() } @@ -348,12 +355,14 @@ module.exports = function (app, passport) { }) .then(community => { const memberIds = community.members.map(a => a._id.toString()) + let isMember if (memberIds.includes(req.user._id.toString())) { isMember = true } - voteUrl = shortid.generate() - created = new Date() - expiryTime = moment(created).add((community.settings.voteLength ? community.settings.voteLength : 7), 'd') + const voteUrl = shortid.generate() + const created = new Date() + const expiryTime = moment(created).add((community.settings.voteLength ? community.settings.voteLength : 7), 'd') + let votesNumber if (community.members.length - community.mutedMembers.length === 1) { // if there is only one member with permissions, start out with 0 votes total so that at least someone has to click on the 'vote' button to make it pass votesNumber = 0 @@ -375,9 +384,8 @@ module.exports = function (app, passport) { voteThreshold: 50, expiryTime: expiryTime, votes: votesNumber, - voters: votesNumber == 1 ? [req.user._id] : [] + voters: votesNumber === 1 ? [req.user._id] : [] }) - voteId = vote._id vote.save() .then(vote => { var expireVote = schedule.scheduleJob(expiryTime, function () { @@ -407,12 +415,14 @@ module.exports = function (app, passport) { }) .then((community) => { const memberIds = community.members.map(a => a._id.toString()) + let isMember if (memberIds.includes(req.user._id.toString())) { isMember = true } - voteUrl = shortid.generate() - created = new Date() - expiryTime = moment(created).add((community.settings.voteLength ? community.settings.voteLength : 7), 'd') + const voteUrl = shortid.generate() + const created = new Date() + const expiryTime = moment(created).add((community.settings.voteLength ? community.settings.voteLength : 7), 'd') + let votesNumber if (community.members.length - community.mutedMembers.length === 1) { // if there is only one member with permissions, start out with 0 votes total so that at least someone has to click on the 'vote' button to make it pass votesNumber = 0 @@ -434,9 +444,8 @@ module.exports = function (app, passport) { voteThreshold: 50, expiryTime: expiryTime, votes: votesNumber, - voters: votesNumber == 1 ? [req.user._id] : [] + voters: votesNumber === 1 ? [req.user._id] : [] }) - voteId = vote._id vote.save() .then(vote => { var expireVote = schedule.scheduleJob(expiryTime, function () { @@ -466,12 +475,14 @@ module.exports = function (app, passport) { }) .then((community) => { const memberIds = community.members.map(a => a._id.toString()) + let isMember if (memberIds.includes(req.user._id.toString())) { isMember = true } - voteUrl = shortid.generate() - created = new Date() - expiryTime = moment(created).add((community.settings.voteLength ? community.settings.voteLength : 7), 'd') + const voteUrl = shortid.generate() + const created = new Date() + const expiryTime = moment(created).add((community.settings.voteLength ? community.settings.voteLength : 7), 'd') + let votesNumber if (community.members.length - community.mutedMembers.length === 1) { // if there is only one member with permissions, start out with 0 votes total so that someone has to at least click on the 'vote' button to make it pass votesNumber = 0 @@ -493,9 +504,8 @@ module.exports = function (app, passport) { voteThreshold: 50, expiryTime: expiryTime, votes: votesNumber, - voters: votesNumber == 1 ? [req.user._id] : [] + voters: votesNumber === 1 ? [req.user._id] : [] }) - voteId = vote._id vote.save() .then(vote => { var expireVote = schedule.scheduleJob(expiryTime, function () { @@ -525,12 +535,14 @@ module.exports = function (app, passport) { }) .then((community) => { const memberIds = community.members.map(a => a._id.toString()) + let isMember if (memberIds.includes(req.user._id.toString())) { isMember = true } - voteUrl = shortid.generate() - created = new Date() - expiryTime = moment(created).add((community.settings.voteLength ? community.settings.voteLength : 7), 'd') + const voteUrl = shortid.generate() + const created = new Date() + const expiryTime = moment(created).add((community.settings.voteLength ? community.settings.voteLength : 7), 'd') + let votesNumber if (community.members.length - community.mutedMembers.length === 1) { // if there is only one member with permissions, start out with 0 votes total so that at least someone has to click on the 'vote' button to make it pass votesNumber = 0 @@ -552,9 +564,8 @@ module.exports = function (app, passport) { voteThreshold: 50, expiryTime: expiryTime, votes: votesNumber, - voters: votesNumber == 1 ? [req.user._id] : [] + voters: votesNumber === 1 ? [req.user._id] : [] }) - voteId = vote._id vote.save() .then(vote => { var expireVote = schedule.scheduleJob(expiryTime, function () { @@ -584,6 +595,7 @@ module.exports = function (app, passport) { }) .then(community => { const memberIds = community.members.map(a => a._id.toString()) + let isMember if (memberIds.includes(req.user._id.toString())) { isMember = true } @@ -614,6 +626,7 @@ module.exports = function (app, passport) { }) .then(community => { const memberIds = community.members.map(a => a._id.toString()) + let isMember if (memberIds.includes(req.user._id.toString())) { isMember = true } @@ -638,7 +651,8 @@ module.exports = function (app, passport) { return res.sendStatus(403) } console.log(req.body) - if (req.body.reference == 'image') { + let imageUrl + if (req.body.reference === 'image') { imageUrl = shortid.generate() + '.jpg' if (req.files.proposedValue.data.length > 3145728) { console.error('Image too large!') @@ -689,34 +703,36 @@ module.exports = function (app, passport) { } const parsedReference = parsedReferences[req.body.reference] var allowedChange = true // is there a change? and is it allowed? - if (req.body.reference == 'description' || req.body.reference == 'rules') { + let proposedValue + let parsedProposedValue + if (req.body.reference === 'description' || req.body.reference === 'rules') { proposedValue = req.body.proposedValue parsedProposedValue = (await helper.parseText(req.body.proposedValue)).text - if (req.body.reference == 'description') { - allowedChange = (community.descriptionRaw != proposedValue) + if (req.body.reference === 'description') { + allowedChange = (community.descriptionRaw !== proposedValue) } else { - allowedChange = (community.rulesRaw != proposedValue) + allowedChange = (community.rulesRaw !== proposedValue) } - } else if (req.body.reference == 'joinType') { + } else if (req.body.reference === 'joinType') { proposedValue = req.body.proposedValue parsedProposedValue = parsedJoinType[req.body.proposedValue] - allowedChange = (parsedProposedValue && community.settings.joinType != proposedValue) // parsedProposedValue will be undefined if req.body.proposedValue wasn't one of the allowed values - } else if (req.body.reference == 'visibility') { + allowedChange = (parsedProposedValue && community.settings.joinType !== proposedValue) // parsedProposedValue will be undefined if req.body.proposedValue wasn't one of the allowed values + } else if (req.body.reference === 'visibility') { proposedValue = req.body.proposedValue parsedProposedValue = parsedVisibility[req.body.proposedValue] - allowedChange = (parsedProposedValue && community.settings.visibility != proposedValue) - } else if (req.body.reference == 'voteLength') { + allowedChange = (parsedProposedValue && community.settings.visibility !== proposedValue) + } else if (req.body.reference === 'voteLength') { proposedValue = req.body.proposedValue parsedProposedValue = parsedVoteLength[req.body.proposedValue] - allowedChange = (parsedProposedValue && community.settings.voteLength != parseInt(proposedValue)) - } else if (req.body.reference == 'image') { + allowedChange = (parsedProposedValue && community.settings.voteLength !== parseInt(proposedValue)) + } else if (req.body.reference === 'image') { proposedValue = imageUrl parsedProposedValue = imageUrl - } else if (req.body.reference == 'name') { // this is where it gets complicated + } else if (req.body.reference === 'name') { // this is where it gets complicated proposedValue = req.body.proposedValue parsedProposedValue = proposedValue var slug = helper.slugify(proposedValue) - if (!parsedProposedValue || community.name == proposedValue) { + if (!parsedProposedValue || community.name === proposedValue) { // not using allowedChange for this non-change bc by the time we get to the code that reacts to allowedChange it will have already returned a duplicate name complaint req.session.sessionFlash = { type: 'warning', @@ -760,9 +776,10 @@ module.exports = function (app, passport) { return res.redirect('back') } console.log(community) - voteUrl = shortid.generate() - created = new Date() - expiryTime = moment(created).add((community.settings.voteLength ? community.settings.voteLength : 7), 'd') + const voteUrl = shortid.generate() + const created = new Date() + const expiryTime = moment(created).add((community.settings.voteLength ? community.settings.voteLength : 7), 'd') + let votesNumber if (community.members.length - community.mutedMembers.length === 1) { // if there is only one member with permissions, start out with 0 votes total so that they have to at least click on the 'vote' button to make it pass votesNumber = 0 @@ -784,13 +801,12 @@ module.exports = function (app, passport) { voteThreshold: 50, expiryTime: expiryTime, votes: votesNumber, - voters: votesNumber == 1 ? [req.user._id] : [] + voters: votesNumber === 1 ? [req.user._id] : [] }) - voteId = vote._id console.log(vote) vote.save() .then(vote => { - var expireVote = schedule.scheduleJob(expiryTime, function () { + const expireVote = schedule.scheduleJob(expiryTime, function () { Vote.findOne({ _id: vote._id }) @@ -824,7 +840,7 @@ module.exports = function (app, passport) { }) app.post('/api/community/vote/cast/:communityid/:voteid', isLoggedIn, function (req, res) { - if (!req.user.communities.some(v => v.toString() == req.params.communityid)) { + if (!req.user.communities.some(v => v.toString() === req.params.communityid)) { return res.sendStatus(403) } Vote.findOne({ @@ -856,7 +872,7 @@ module.exports = function (app, passport) { if (vote.votes >= majorityMargin) { console.log('Vote passed!') - if (vote.reference == 'visibility') { + if (vote.reference === 'visibility') { community.settings[vote.reference] = vote.proposedValue Image.find({ context: 'community', @@ -869,23 +885,23 @@ module.exports = function (app, passport) { image.save() }) }) - } else if (vote.reference == 'joinType' || vote.reference == 'voteLength') { + } else if (vote.reference === 'joinType' || vote.reference === 'voteLength') { community.settings[vote.reference] = vote.proposedValue - } else if (vote.reference == 'description' || vote.reference == 'rules') { + } else if (vote.reference === 'description' || vote.reference === 'rules') { community[vote.reference + 'Raw'] = vote.proposedValue community[vote.reference + 'Parsed'] = vote.parsedProposedValue - } else if (vote.reference == 'image') { + } else if (vote.reference === 'image') { fs.rename(global.appRoot + '/public/images/communities/staging/' + vote.proposedValue, global.appRoot + '/public/images/communities/' + vote.proposedValue, function () { community[vote.reference] = vote.proposedValue community.imageEnabled = true community.save() }) - } else if (vote.reference == 'name') { + } else if (vote.reference === 'name') { var oldName = community.name community.name = vote.proposedValue CommunityPlaceholder.deleteOne({ name: vote.proposedValue }, function (err) { console.error(err) }) community.slug = helper.slugify(vote.proposedValue) // i guess i'm assuming the "slugify" function hasn't been modified since the vote was created - } else if (vote.reference == 'userban') { + } else if (vote.reference === 'userban') { community.members.pull(vote.proposedValue) community.bannedMembers.push(vote.proposedValue) User.findOne({ @@ -896,7 +912,7 @@ module.exports = function (app, passport) { user.bannedCommunities.push(req.params.communityid) user.save() }) - } else if (vote.reference == 'usermute') { + } else if (vote.reference === 'usermute') { community.mutedMembers.push(vote.proposedValue) User.findOne({ _id: vote.proposedValue @@ -905,7 +921,7 @@ module.exports = function (app, passport) { user.mutedCommunities.push(req.params.communityid) user.save() }) - } else if (vote.reference == 'userunban') { + } else if (vote.reference === 'userunban') { community.bannedMembers.pull(vote.proposedValue) User.findOne({ _id: vote.proposedValue @@ -914,7 +930,7 @@ module.exports = function (app, passport) { user.bannedCommunities.pull(req.params.communityid) user.save() }) - } else if (vote.reference == 'userunmute') { + } else if (vote.reference === 'userunmute') { community.mutedMembers.pull(vote.proposedValue) User.findOne({ _id: vote.proposedValue @@ -932,7 +948,7 @@ module.exports = function (app, passport) { .then(vote => { vote.status = 'passed' vote.save() - if (vote.reference == 'userban') { + if (vote.reference === 'userban') { User.findOne({ _id: vote.proposedValue }) @@ -955,12 +971,12 @@ module.exports = function (app, passport) { notifier.notify('community', 'management', member, vote.proposedValue, req.params.communityid, '/api/community/getbyid/' + req.params.communityid, 'banned') }) notifier.notify('community', 'managementResponse', vote.proposedValue, req.user._id, req.params.communityid, '/api/community/getbyid/' + req.params.communityid, 'banned') - } else if (vote.reference == 'userunban') { + } else if (vote.reference === 'userunban') { community.members.forEach(member => { notifier.notify('community', 'management', member, vote.proposedValue, req.params.communityid, '/api/community/getbyid/' + req.params.communityid, 'unbanned') }) notifier.notify('community', 'managementResponse', vote.proposedValue, req.user._id, req.params.communityid, '/api/community/getbyid/' + req.params.communityid, 'unbanned') - } else if (vote.reference == 'usermute') { + } else if (vote.reference === 'usermute') { console.log('User muted - sending notifications') community.members.forEach(member => { console.log('Notification sending to ' + member) @@ -970,7 +986,7 @@ module.exports = function (app, passport) { notifier.notify('community', 'management', member, vote.proposedValue, req.params.communityid, '/api/community/getbyid/' + req.params.communityid, 'muted') } }) - } else if (vote.reference == 'userunmute') { + } else if (vote.reference === 'userunmute') { community.members.forEach(member => { if (member.equals(vote.proposedValue)) { notifier.notify('community', 'managementResponse', vote.proposedValue, req.user._id, req.params.communityid, '/api/community/getbyid/' + req.params.communityid, 'unmuted') @@ -978,7 +994,7 @@ module.exports = function (app, passport) { notifier.notify('community', 'management', member, vote.proposedValue, req.params.communityid, '/api/community/getbyid/' + req.params.communityid, 'unmuted') } }) - } else if (vote.reference == 'name') { + } else if (vote.reference === 'name') { community.members.forEach(member => { if (!member.equals(req.user._id)) { notifier.notify('community', 'nameChange', member, req.user._id, req.params.communityid, '/api/community/getbyid/' + req.params.communityid, oldName) @@ -994,7 +1010,7 @@ module.exports = function (app, passport) { }) } touchCommunity(req.params.communityid) - if (vote.reference == 'name') { + if (vote.reference === 'name') { res.end('{"success" : "Updated Successfully", "status" : 302, "redirect": "/community/' + community.slug + '"}') } else { res.end('{"success" : "Updated Successfully", "status" : 200}') diff --git a/app/models/community.js b/app/models/community.js index d7171daf..21507fea 100644 --- a/app/models/community.js +++ b/app/models/community.js @@ -1,3 +1,6 @@ +const mongoose = require('mongoose') +const DBReference = mongoose.Schema.Types.ObjectId + // define the schema for our user model var communitySchema = new mongoose.Schema({ created: Date, @@ -37,10 +40,10 @@ communitySchema.pre('validate', function (next) { this.membersCount = this.members.length this.requestsCount = this.membershipRequests.length this.mutedMembersCount = this.mutedMembers.length - membersIds = this.members.map(String) - mutedMembersIds = this.mutedMembers.map(String) - mutedUsersWhoAreMembers = mutedMembersIds.filter(id => membersIds.includes(id)) - votingMembers = this.membersCount - mutedUsersWhoAreMembers.length + const membersIds = this.members.map(String) + const mutedMembersIds = this.mutedMembers.map(String) + const mutedUsersWhoAreMembers = mutedMembersIds.filter(id => membersIds.includes(id)) + const votingMembers = this.membersCount - mutedUsersWhoAreMembers.length this.votingMembersCount = votingMembers if (this.membersCount === 0) { this.settings.joinType = 'open' diff --git a/app/models/image.js b/app/models/image.js index f1dd5d3c..54b82841 100644 --- a/app/models/image.js +++ b/app/models/image.js @@ -1,3 +1,5 @@ +const mongoose = require('mongoose') + var imageSchema = new mongoose.Schema({ context: String, // either "user" or "community", corresponding to the post schema's type field's values "original" and "community" filename: { type: String, unique: true }, diff --git a/app/models/post.js b/app/models/post.js index 387688ac..2a604231 100644 --- a/app/models/post.js +++ b/app/models/post.js @@ -1,3 +1,6 @@ +const mongoose = require('mongoose') +const DBReference = mongoose.Schema.Types.ObjectId + // this is used by older posts instead of the below inlineElementSchema var embedSchema = new mongoose.Schema({ type: String, // "video" always diff --git a/app/models/relationship.js b/app/models/relationship.js index d17fccc1..0e724202 100644 --- a/app/models/relationship.js +++ b/app/models/relationship.js @@ -1,3 +1,6 @@ +const mongoose = require('mongoose') +const DBReference = mongoose.Schema.Types.ObjectId + // define the schema for our user model var relationshipSchema = new mongoose.Schema({ from: { diff --git a/app/models/tag.js b/app/models/tag.js index e9979e28..f8e07bc5 100644 --- a/app/models/tag.js +++ b/app/models/tag.js @@ -1,3 +1,5 @@ +const mongoose = require('mongoose') + var tagSchema = new mongoose.Schema({ name: String, posts: [String], diff --git a/app/models/user.js b/app/models/user.js index 9ffae4bc..74f72c0c 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -1,3 +1,7 @@ +const bcrypt = require('bcrypt-nodejs') +const mongoose = require('mongoose') +const DBReference = mongoose.Schema.Types.ObjectId + var notificationSchema = new mongoose.Schema({ category: String, sourceId: String, diff --git a/app/models/vote.js b/app/models/vote.js index b31d8069..8839667b 100644 --- a/app/models/vote.js +++ b/app/models/vote.js @@ -1,9 +1,5 @@ - -// var optionSchema = new mongoose.Schema({ -// title: String, -// votes: Number, -// voters: [{ type: DBReference, ref: 'User' }] -// }); +const mongoose = require('mongoose') +const DBReference = mongoose.Schema.Types.ObjectId var commentSchema = new mongoose.Schema({ authorEmail: String, diff --git a/app/notifier.js b/app/notifier.js index 4118ecf2..4e917ec4 100644 --- a/app/notifier.js +++ b/app/notifier.js @@ -1,10 +1,15 @@ +const webpush = require('web-push') +const User = require('./models/user') +const Community = require('./models/community') +const emailer = require('./emailer') + function markRead (userId, subjectId) { User.findOne({ _id: userId }, 'notifications') .then(user => { user.notifications.forEach(notification => { - if (notification.seen == false && notification.subjectId == subjectId) { + if (!notification.seen && notification.subjectId === subjectId) { notification.seen = true } }) @@ -77,7 +82,7 @@ function notify (type, cause, notifieeID, sourceId, subjectId, url, context) { .then(async response => { // send the user push notifications if they have a subscribed browser if (notifiedUser.pushNotifSubscriptions.length > 0) { - for (subbed of notifiedUser.pushNotifSubscriptions) { + for (const subbed of notifiedUser.pushNotifSubscriptions) { const pushSubscription = JSON.parse(subbed) const options = { gcmAPIKey: '' @@ -90,23 +95,31 @@ function notify (type, cause, notifieeID, sourceId, subjectId, url, context) { webpush.sendNotification(pushSubscription, payload, options).catch(async err => { console.log('push notification subscription not working, will be removed:') console.log(err) - notifiedUser.pushNotifSubscriptions = notifiedUser.pushNotifSubscriptions.filter(v => v != subbed) + notifiedUser.pushNotifSubscriptions = notifiedUser.pushNotifSubscriptions.filter(v => v !== subbed) notifiedUser = await notifiedUser.save() }) } } // send the user an email if it's a mention and they have emails for mentions enabled - if (notifiedUser.settings.sendMentionEmails == true && response.emailText) { + if (notifiedUser.settings.sendMentionEmails === true && response.emailText) { emailer.sendSingleNotificationEmail(notifiedUser, response, url) } // if the most recent notification is a trust or follow, and the current is also a trust or follow from the same user, combine the two var lastNotif = notifiedUser.notifications[notifiedUser.notifications.length - 1] - if (lastNotif && cause == 'relationship' && lastNotif.category == 'relationship' && lastNotif.url == url && - ((lastNotif.text.includes('follows you') && context == 'trust') || (lastNotif.text.includes('trusts you') && context == 'follow'))) { + let notification + if ( + lastNotif && + cause === 'relationship' && + lastNotif.category === 'relationship' && + lastNotif.url === url && + ( + (lastNotif.text.includes('follows you') && context === 'trust') || + (lastNotif.text.includes('trusts you') && context === 'follow')) + ) { // It's too late at night to work out a better way to get the username of the new notification that isn't regexing the old notification, so... this will break at some point - var username = lastNotif.text.match(/@[A-Za-z0-9-_]*/) + const username = lastNotif.text.match(/@[A-Za-z0-9-_]*/) console.log(username) - var notification = { + notification = { category: cause, sourceId: sourceId, subjectId: subjectId, @@ -117,7 +130,7 @@ function notify (type, cause, notifieeID, sourceId, subjectId, url, context) { notifiedUser.notifications[notifiedUser.notifications.length - 1] = notification notifiedUser.save().then(() => { console.log('notification sent to ' + notifiedUser.username) }) } else { - var notification = { + notification = { category: cause, sourceId: sourceId, subjectId: subjectId, diff --git a/app/personalAccountActions.js b/app/personalAccountActions.js index dd85955b..731fb821 100644 --- a/app/personalAccountActions.js +++ b/app/personalAccountActions.js @@ -1,8 +1,14 @@ +const Autolinker = require('autolinker') +const sharp = require('sharp') +const bcrypt = require('bcrypt-nodejs') +const Post = require('./models/post') +const User = require('./models/user') +const Relationship = require('./models/relationship') +const helper = require('./utilityFunctionsMostlyText') +const notifier = require('./notifier') const reservedUsernames = require('../config/reserved-usernames.js') - -// APIs - const sgMail = require('./mail') +const emailer = require('./emailer') module.exports = function (app, passport) { // Responds to a post request containing signup information. @@ -108,10 +114,11 @@ module.exports = function (app, passport) { // Output: a relationship document in the database or the removal of one, depending on type, and a notification if someone has followed someone. Or // isLoggedInOrRedirect redirects you. app.post('/useraction/:type/:action/:from/:to/:fromid/:toid/:fromusername', isLoggedInOrRedirect, function (req, res) { - if (req.params.from != req.user._id.toString()) { + if (req.params.from !== req.user._id.toString()) { res.status(400).send("action not permitted: following/unfollowing/flagging/unflagging/trusting/untrusting a user from an account you're not logged in to") return } + let from User.findOne({ _id: req.params.from }) @@ -124,11 +131,12 @@ module.exports = function (app, passport) { _id: req.params.to }) .then(toUser => { - to = toUser + const to = toUser console.log(to) + return to }) - .then(() => { - if (req.params.action == 'add') { + .then((to) => { + if (req.params.action === 'add') { const flag = new Relationship({ from: from.email, to: to.email, @@ -139,7 +147,7 @@ module.exports = function (app, passport) { flag.save() .then(() => { // Do not notify when users are flagged, muted, or blocked (blocking and muting not currently implemented) - if (req.params.type != 'block' && req.params.type != 'flag' && req.params.type != 'mute') { + if (req.params.type !== 'block' && req.params.type !== 'flag' && req.params.type !== 'mute') { notifier.notify('user', 'relationship', to._id, from._id, from._id, '/' + from.username, req.params.type) } }) @@ -150,7 +158,7 @@ module.exports = function (app, passport) { console.log('Database error.') console.log(err) }) - } else if (req.params.action = 'delete') { + } else if (req.params.action === 'delete') { Relationship.findOneAndRemove({ from: from.email, to: to.email, @@ -160,7 +168,7 @@ module.exports = function (app, passport) { }).then(() => { res.end('{"success" : "Updated Successfully", "status" : 200}') }) - .catch((err) => { + .catch(() => { console.log('Database error.') }) } @@ -174,7 +182,7 @@ module.exports = function (app, passport) { app.post('/updateprofile', isLoggedInOrRedirect, function (req, res) { let imageEnabled = req.user.imageEnabled let imageFilename = req.user.image - if (Object.keys(req.files).length != 0) { + if (Object.keys(req.files).length !== 0) { console.log(req.files.imageUpload.data.length) if (req.files.imageUpload.data.length > 3145728) { req.session.sessionFlash = { @@ -205,12 +213,12 @@ module.exports = function (app, passport) { }) .then((user) => { let parsedAbout = req.user.aboutParsed - if (req.body.about != req.user.aboutRaw) { + if (req.body.about !== req.user.aboutRaw) { // Parse about section const splitAbout = helper.escapeHTMLChars(req.body.about).substring(0, 500).split(/\r\n|\r|\n/gi) const parsedAboutArray = [] splitAbout.forEach(function (line) { - if (line != '') { + if (line !== '') { line = '

' + line + '

' line = Autolinker.link(line) var mentionRegex = /(^|[^@\w])@([\w-]{1,30})[\b-]*/g @@ -251,7 +259,7 @@ module.exports = function (app, passport) { _id: req.params.postid }) .then(async post => { - if (post.type == 'boost') { + if (post.type === 'boost') { post = await Post.findById(post.boostTarget) } post.subscribedUsers.pull(req.user._id) @@ -272,7 +280,7 @@ module.exports = function (app, passport) { _id: req.params.postid }) .then(async post => { - if (post.type == 'boost') { + if (post.type === 'boost') { post = await Post.findById(post.boostTarget) } post.unsubscribedUsers.pull(req.user._id) @@ -297,7 +305,7 @@ module.exports = function (app, passport) { const oldSets = req.user.settings var emailSetsChanged = false - if (newSets.digestEmailFrequency != oldSets.digestEmailFrequency || newSets.timezone != oldSets.timezone || newSets.autoDetectedTimeZone != oldSets.autoDetectedTimeZone || newSets.emailTime != oldSets.emailTime || newSets.emailDay != oldSets.emailDay) { + if (newSets.digestEmailFrequency !== oldSets.digestEmailFrequency || newSets.timezone !== oldSets.timezone || newSets.autoDetectedTimeZone !== oldSets.autoDetectedTimeZone || newSets.emailTime !== oldSets.emailTime || newSets.emailDay !== oldSets.emailDay) { emailSetsChanged = true } @@ -314,10 +322,10 @@ module.exports = function (app, passport) { 'settings.homeTagTimelineSorting': newSets.homeTagTimelineSorting, 'settings.userTimelineSorting': newSets.userTimelineSorting, 'settings.communityTimelineSorting': newSets.communityTimelineSorting, - 'settings.flashRecentComments': (newSets.flashRecentComments == 'on'), - 'settings.showRecommendations': (newSets.showRecommendations == 'on'), - 'settings.showHashtags': (newSets.showHashtags == 'on'), - 'settings.sendMentionEmails': (newSets.sendMentionEmails == 'on'), + 'settings.flashRecentComments': (newSets.flashRecentComments === 'on'), + 'settings.showRecommendations': (newSets.showRecommendations === 'on'), + 'settings.showHashtags': (newSets.showHashtags === 'on'), + 'settings.sendMentionEmails': (newSets.sendMentionEmails === 'on'), 'settings.emailTime': newSets.emailTime, 'settings.emailDay': newSets.emailDay } @@ -461,6 +469,7 @@ module.exports = function (app, passport) { email: req.body.email }) .then(user => { + let token if (!user) { // There is no user registered with this email. req.session.sessionFlash = { message: 'A new token has been sent to ' + req.body.email + '. Please check your spam or junk folder if it does not arrive in the next few minutes. You may now close this page.', @@ -474,7 +483,7 @@ module.exports = function (app, passport) { } res.redirect(301, '/resend-token') } else { // Actual success - require('crypto').randomBytes(20, function (err, buffer) { + require('crypto').randomBytes(20, function (_, buffer) { token = buffer.toString('hex') }) user.verificationToken = token @@ -482,7 +491,7 @@ module.exports = function (app, passport) { user.save() .then(user => { const msg = { - to: email, + to: req.body.email, from: 'support@sweet.sh', subject: 'sweet new user verification', text: 'Hi! You are receiving this because you have created a new account on sweet with this email.\n\n' + @@ -498,7 +507,7 @@ module.exports = function (app, passport) { res.redirect(301, '/resend-token') }) }) - .catch(err => { + .catch(() => { req.session.sessionFlash = { type: 'alert', message: 'There has been a problem sending your token. Please try again in a few minutes.', @@ -508,7 +517,7 @@ module.exports = function (app, passport) { }) } }) - .catch(err => { + .catch(() => { req.session.sessionFlash = { type: 'alert', message: 'There has been a problem sending your token. Please try again in a few minutes.', @@ -564,7 +573,8 @@ module.exports = function (app, passport) { // an email with a link to /reset-password with a token if the email is right and the token saved in // the database, or a redirect to /forgot-password with a warning message if the email or database apis return errors. app.post('/forgot-password', function (req, res) { - require('crypto').randomBytes(20, function (err, buffer) { + let token + require('crypto').randomBytes(20, function (_, buffer) { token = buffer.toString('hex') }) User.findOne({ @@ -702,7 +712,7 @@ module.exports = function (app, passport) { if (req.isAuthenticated()) { var user = await User.findById(req.user._id) if (!user.pushNotifSubscriptions.some(v => { // check to make sure there isn't already a subscription set up with this endpoint - return JSON.parse(v).endpoint == JSON.parse(req.body.subscription).endpoint + return JSON.parse(v).endpoint === JSON.parse(req.body.subscription).endpoint })) { user.pushNotifSubscriptions.push(req.body.subscription) user.save() @@ -715,7 +725,7 @@ module.exports = function (app, passport) { if (req.isAuthenticated()) { var user = await User.findById(req.user._id) user.pushNotifSubscriptions = user.pushNotifSubscriptions.filter(v => { // check to make sure there isn't already a subscription set up with this endpoint - return !(JSON.parse(v).endpoint == JSON.parse(req.body.subscription).endpoint) + return !(JSON.parse(v).endpoint === JSON.parse(req.body.subscription).endpoint) }) user.save() } @@ -727,9 +737,9 @@ module.exports = function (app, passport) { _id: req.user._id }) .then(user => { - if (req.params.type == 'user') { + if (req.params.type === 'user') { user.hiddenRecommendedUsers.push(req.params.id) - } else if (req.params.type == 'community') { + } else if (req.params.type === 'community') { user.hiddenRecommendedCommunities.push(req.params.id) } user.save() @@ -744,7 +754,7 @@ module.exports = function (app, passport) { function isLoggedInOrRedirect (req, res, next) { if (req.isAuthenticated()) { // A potentially expensive way to update a user's last logged in timestamp (currently only relevant to sorting search results) - currentTime = new Date() + const currentTime = new Date() if ((currentTime - req.user.lastUpdated) > 3600000) { // If the timestamp is older than an hour User.findOne({ _id: req.user._id diff --git a/app/postingToSweet.js b/app/postingToSweet.js index df347ae7..e28c60cb 100644 --- a/app/postingToSweet.js +++ b/app/postingToSweet.js @@ -1,3 +1,16 @@ +const sharp = require('sharp') +const shortid = require('shortid') +const mongoose = require('mongoose') +const moment = require('moment') +const fs = require('fs') +const Post = require('./models/post') +const Community = require('./models/community') +const User = require('./models/user') +const Tag = require('./models/tag') +const Relationship = require('./models/relationship') +const helper = require('./utilityFunctionsMostlyText') +const notifier = require('./notifier') + // APIs module.exports = function (app) { @@ -28,8 +41,8 @@ module.exports = function (app) { var imageQualitySettings = imageQualitySettingsArray[req.user.settings.imageQuality] if (req.files.image) { if (req.files.image.size <= 10485760) { - var sharpImage - var imageMeta + let sharpImage + let imageMeta try { sharpImage = sharp(req.files.image.data) imageMeta = await sharpImage.metadata() @@ -39,9 +52,9 @@ module.exports = function (app) { res.end(JSON.stringify({ error: 'filetype' })) return } - var imageFormat = imageMeta.format + const imageFormat = imageMeta.format const imageUrl = shortid.generate() - if (imageFormat == 'gif') { + if (imageFormat === 'gif') { if (req.files.image.size <= 5242880) { var imageData = req.files.image.data fs.writeFile('./cdn/images/temp/' + imageUrl + '.gif', imageData, 'base64', function (err) { // to temp @@ -55,26 +68,27 @@ module.exports = function (app) { res.setHeader('content-type', 'text/plain') res.end(JSON.stringify({ error: 'filesize' })) } - } else if (imageFormat == 'jpeg' || imageFormat == 'png') { + } else if (imageFormat === 'jpeg' || imageFormat === 'png') { sharpImage = sharpImage.resize({ width: imageQualitySettings.resize, withoutEnlargement: true }).rotate() - if (imageFormat == 'png' && req.user.settings.imageQuality == 'standard') { + if (imageFormat === 'png' && req.user.settings.imageQuality === 'standard') { sharpImage = sharpImage.flatten({ background: { r: 255, g: 255, b: 255 } }) } - if (imageFormat == 'jpeg' || req.user.settings.imageQuality == 'standard') { + let finalFormat + if (imageFormat === 'jpeg' || req.user.settings.imageQuality === 'standard') { sharpImage = sharpImage.jpeg({ quality: imageQualitySettings.jpegQuality }) - var finalFormat = 'jpeg' + finalFormat = 'jpeg' } else { sharpImage = sharpImage.png() - var finalFormat = 'png' + finalFormat = 'png' } // send the client a thumbnail bc a) maybe the image is being rotated according to exif data or is a png with transparency being removed, and b) bc using a really small thumbnail in the browser speeds subsequent front-end interactions way up, at least on my phone // IN THEORY we should just be able to .clone() sharpImage and operate on the result of that instead of making this new object for the thumbnail, but i'll be damned if i can get that to behave, i get cropped images somehow var thumbnail = sharp(req.files.image.data).resize({ height: 200, withoutEnlargement: true }) - thumbnail = await (finalFormat == 'jpeg' ? thumbnail.rotate().flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg() : thumbnail.rotate().png()).toBuffer() + thumbnail = await (finalFormat === 'jpeg' ? thumbnail.rotate().flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg() : thumbnail.rotate().png()).toBuffer() var response = { url: imageUrl + '.' + finalFormat, thumbnail: 'data:image/' + finalFormat + ';base64,' + thumbnail.toString('base64') } await sharpImage.toFile('./cdn/images/temp/' + imageUrl + '.' + finalFormat) // to temp @@ -117,7 +131,7 @@ module.exports = function (app) { // parseText calls to parse it. // Outputs: all that stuff is saved as a new post document (with the body of the post parsed to turn urls and tags and @s into links). Or, error response if not logged in. app.post('/createpost', isLoggedInOrErrorResponse, async function (req, res) { - var parsedResult = await helper.parseText(JSON.parse(req.body.postContent)) + const parsedResult = await helper.parseText(JSON.parse(req.body.postContent)) // don't save mentions or tags for draft posts, this means that notifications and tag adding will be deferred until the post is published and at that point // all of the mentions and tags will register as "new" and so the right actions will occur then @@ -135,7 +149,7 @@ module.exports = function (app) { } for (var inline of parsedResult.inlineElements) { - if (inline.type == 'image(s)') { + if (inline.type === 'image(s)') { // calling this function also moves the images out of temp storage and saves documents for them in the images collection in the database var horizOrVertics = await helper.finalizeImages(inline.images, (req.body.communityId ? 'community' : req.body.isDraft ? 'draft' : 'original'), req.body.communityId, req.user._id, imagePrivacy, req.user.settings.imageQuality) inline.imageIsHorizontal = horizOrVertics.imageIsHorizontal @@ -172,16 +186,16 @@ module.exports = function (app) { subscribedUsers: [req.user._id] }) - var newPostId = post._id + const newPostId = post._id - for (mention of parsedResult.mentions) { - if (mention != req.user.username) { + for (const mention of parsedResult.mentions) { + if (mention !== req.user.username) { User.findOne({ username: mention }).then(async mentioned => { if (isCommunityPost) { if (mentioned.communities.some(v => v.equals(post.community))) { notifier.notify('user', 'mention', mentioned._id, req.user._id, newPostId, '/' + req.user.username + '/' + newPostUrl, 'post') } - } else if (req.body.postPrivacy == 'private') { + } else if (req.body.postPrivacy === 'private') { if (await Relationship.findOne({ value: 'trust', fromUser: req.user._id, toUser: mentioned._id })) { notifier.notify('user', 'mention', mentioned._id, req.user._id, newPostId, '/' + req.user.username + '/' + newPostUrl, 'post') } @@ -192,8 +206,13 @@ module.exports = function (app) { } } - for (tag of parsedResult.tags) { - Tag.findOneAndUpdate({ name: tag }, { $push: { posts: newPostId.toString() }, $set: { lastUpdated: postCreationTime } }, { upsert: true, new: true }, function (error, result) { if (error) return }) + for (const tag of parsedResult.tags) { + Tag.findOneAndUpdate( + { name: tag }, + { $push: { posts: newPostId.toString() }, $set: { lastUpdated: postCreationTime } }, + { upsert: true, new: true }, + () => {} + ) } if (isCommunityPost) { @@ -218,7 +237,7 @@ module.exports = function (app) { function deleteImagesRecursive (postOrComment) { if (postOrComment.inlineElements && postOrComment.inlineElements.length) { for (const il of postOrComment.inlineElements) { - if (il.type == 'image(s)') { + if (il.type === 'image(s)') { for (const image of il.images) { fs.unlink(global.appRoot + '/cdn/images/' + image, (err) => { if (err) console.log('Image deletion error ' + err) @@ -264,7 +283,7 @@ module.exports = function (app) { } // Delete boosts - if (post.type == 'original' && post.boosts) { + if (post.type === 'original' && post.boosts) { post.boosts.forEach((boost) => { Post.deleteOne({ _id: boost }) .then((boost) => { @@ -295,11 +314,11 @@ module.exports = function (app) { // Outputs: makes the comment document (with the body parsed for urls, tags, and @mentions), embeds a comment document in its post document, // moves comment images out of temp. Also, notify the owner of the post, people subscribed to the post, and everyone who was mentioned. app.post('/createcomment/:postid/:commentid', isLoggedInOrErrorResponse, async function (req, res) { - commentTimestamp = new Date() - var commentId = mongoose.Types.ObjectId() + const commentTimestamp = new Date() + const commentId = mongoose.Types.ObjectId() - var rawContent = req.body.commentContent - var parsedResult = await helper.parseText(JSON.parse(rawContent)) + const rawContent = req.body.commentContent + const parsedResult = await helper.parseText(JSON.parse(rawContent)) if (!(parsedResult.inlineElements.length || parsedResult.text.trim())) { res.status(400).send('bad post op') @@ -329,7 +348,7 @@ module.exports = function (app) { } for (var inline of parsedResult.inlineElements) { - if (inline.type == 'image(s)') { + if (inline.type === 'image(s)') { // calling this function also moves the images out of temp storage and saves documents for them in the images collection in the database var horizOrVertics = await helper.finalizeImages(inline.images, postType, post.communityId, req.user._id, postPrivacy, req.user.settings.imageQuality) inline.imageIsHorizontal = horizOrVertics.imageIsHorizontal @@ -338,13 +357,13 @@ module.exports = function (app) { } comment.inlineElements = parsedResult.inlineElements - var contentHTML = await helper.renderHTMLContent(comment) + const contentHTML = await helper.renderHTMLContent(comment) comment.cachedHTML = { fullContentHTML: contentHTML } - numberOfComments = 0 - var depth = undefined - commentParent = false - if (req.params.commentid == 'undefined') { + let numberOfComments = 0 + let depth + const commentParent = false + if (req.params.commentid === 'undefined') { depth = 1 // This is a top level comment with no parent (identified by commentid) post.comments.push(comment) @@ -368,7 +387,7 @@ module.exports = function (app) { } else { // This is a child level comment so we have to drill through the comments // until we find it - commentParent = undefined + let commentParent function findNested (array, id, depthSoFar = 2) { var foundElement = false @@ -410,7 +429,7 @@ module.exports = function (app) { var postPrivacy = post.privacy post.lastUpdated = new Date() // Add user to subscribed users for post - if ((!post.author._id.equals(req.user._id) && post.subscribedUsers.includes(req.user._id.toString()) === false)) { // Don't subscribe to your own post, or to a post you're already subscribed to + if ((!post.author._id.equals(req.user._id) && !post.subscribedUsers.includes(req.user._id.toString()))) { // Don't subscribe to your own post, or to a post you're already subscribed to post.subscribedUsers.push(req.user._id.toString()) } post.save() @@ -425,15 +444,15 @@ module.exports = function (app) { // NOTIFY EVERYONE WHO IS MENTIONED // we're never going to notify the author of the comment about them mentioning themself - workingMentions = parsedResult.mentions.filter(m => m != req.user.username) + const workingMentions = parsedResult.mentions.filter(m => m !== req.user.username) - if (post.type == 'community') { + if (post.type === 'community') { workingMentions.forEach(function (mentionedUsername) { User.findOne({ username: mentionedUsername }).then((mentionedUser) => { // within communities: notify the mentioned user if this post's community is one they belong to - if (mentionedUser.communities.some(c => c.toString() == post.community.toString())) { + if (mentionedUser.communities.some(c => c.toString() === post.community.toString())) { notifier.notify('user', 'mention', mentionedUser._id, req.user._id, post._id, '/' + originalPoster.username + '/' + post.url, 'reply') } }).catch(err => { @@ -442,7 +461,7 @@ module.exports = function (app) { }) }) } else { - if (postPrivacy == 'private') { + if (postPrivacy === 'private') { workingMentions.forEach(mentionedUsername => { User.findOne({ username: mentionedUsername @@ -469,7 +488,7 @@ module.exports = function (app) { console.log(err) }) }) - } else if (postPrivacy == 'public') { + } else if (postPrivacy === 'public') { workingMentions.forEach(function (mention) { User.findOne({ username: mention @@ -493,14 +512,21 @@ module.exports = function (app) { } // NOTIFY THE PARENT COMMENT'S AUTHOR - // Author doesn't need to know about their own child comments, and about replies on your posts they're not subscribed to, and if they're @ed they already got a notification above, and if they're the post's author as well as the parent comment's author (they got a notification above for that too) + // Author doesn't need to know about their own child comments, + // and about replies on your posts they're not subscribed to, + // and if they're @ed they already got a notification above, + // and if they're the post's author as well as the parent + // comment's author (they got a notification above for that + // too) // First check if this comment even HAS a parent if (commentParent) { - parentCommentAuthor = commentParent.author - if (!parentCommentAuthor._id.equals(req.user._id) && - (post.unsubscribedUsers.includes(parentCommentAuthor._id.toString()) === false) && - (!parsedResult.mentions.includes(parentCommentAuthor.username)) && - (!originalPoster._id.equals(parentCommentAuthor._id))) { + const parentCommentAuthor = commentParent.author + if ( + !parentCommentAuthor._id.equals(req.user._id) && + (!post.unsubscribedUsers.includes(parentCommentAuthor._id.toString())) && + (!parsedResult.mentions.includes(parentCommentAuthor.username)) && + (!originalPoster._id.equals(parentCommentAuthor._id)) + ) { console.log('Notifying parent comment author of a reply') notifier.notify('user', 'commentReply', parentCommentAuthor._id, req.user._id, post._id, '/' + originalPoster.username + '/' + post.url + '#comment-' + commentParent._id, 'post') } @@ -544,7 +570,7 @@ module.exports = function (app) { // checks each subscriber for trustedness if this is a private post, notifies all of 'em otherwise function notifySubscribers (subscriberList) { - if (postPrivacy == 'private') { + if (postPrivacy === 'private') { subscriberList.forEach(subscriberID => { Relationship.findOne({ fromUser: originalPoster._id, @@ -566,17 +592,24 @@ module.exports = function (app) { } function notifySubscriber (subscriberID) { - if ((subscriberID != req.user._id.toString()) // Do not notify the comment's author about the comment - && - (subscriberID != originalPoster._id.toString()) // don't notify the post's author (because they get a different notification, above) - && - (post.unsubscribedUsers.includes(subscriberID) === false) // don't notify unsubscribed users - && - (commentParent ? subscriberID != parentCommentAuthor._id.toString() : true) // don't notify parent comment author, if it's a child comment (because they get a different notification, above) + if ( + // Do not notify the comment's author about the comment + (subscriberID !== req.user._id.toString()) && + // don't notify the post's author (because they get a + // different notification, above) + (subscriberID !== originalPoster._id.toString()) && + // don't notify unsubscribed users + (!post.unsubscribedUsers.includes(subscriberID)) && + // don't notify parent comment author, if it's a child + // comment (because they get a different notification, + // above) + (commentParent ? subscriberID !== parentCommentAuthor._id.toString() : true) ) { console.log('Notifying subscribed user') User.findById(subscriberID).then((subscriber) => { - if (!parsedResult.mentions.includes(subscriber.username)) { // don't notify people who are going to be notified anyway bc they're mentioned in the new comment + if (!parsedResult.mentions.includes(subscriber.username)) { + // don't notify people who are going to be notified + // anyway bc they're mentioned in the new comment if (post.mentions.includes(subscriber.username)) { notifier.notify('user', 'mentioningPostReply', subscriberID, req.user._id, post._id, '/' + originalPoster.username + '/' + post.url, 'post') } else { @@ -594,16 +627,10 @@ module.exports = function (app) { console.log(err) }) - if (req.user.imageEnabled) { - var image = req.user.image - } else { - var image = 'cake.svg' - } - if (req.user.displayName) { - var name = '' + req.user.displayName + '@' + req.user.username + '' - } else { - var name = '@' + req.user.username + '' - } + const image = req.user.imageEnabled ? req.user.image : 'cake.svg' + const name = req.user.displayName + ? '' + req.user.displayName + '@' + req.user.username + '' + : '@' + req.user.username + '' hbs.render('./views/partials/comment_dynamic.handlebars', { image: image, @@ -616,7 +643,7 @@ module.exports = function (app) { depth: depth }) .then(async html => { - var result = { + const result = { comment: html } res.contentType('json') @@ -637,17 +664,17 @@ module.exports = function (app) { app.post('/deletecomment/:postid/:commentid', isLoggedInOrRedirect, function (req, res) { Post.findOne({ _id: req.params.postid }) .then((post) => { - var commentsByUser = 0 - var latestTimestamp = 0 - var numberOfComments = 0 - var target = undefined + let commentsByUser = 0 + let latestTimestamp = 0 + let numberOfComments = 0 + let target function findNested (array, id, parent) { array.forEach((element) => { if (!element.deleted) { numberOfComments++ } - if ((element.author.toString() == req.user._id.toString()) && !element.deleted) { + if ((element.author.toString() === req.user._id.toString()) && !element.deleted) { commentsByUser++ } if (element.timestamp > latestTimestamp) { @@ -673,7 +700,7 @@ module.exports = function (app) { } // i'll be impressed if someone trips this one, comment ids aren't displayed for comments that the logged in user didn't make - if (!target.author.equals(req.user._id) && post.author.toString() != req.user._id.toString()) { + if (!target.author.equals(req.user._id) && post.author.toString() !== req.user._id.toString()) { res.status(400).send("you do not appear to be who you would like us to think that you are! this comment ain't got your brand on it") return } @@ -687,7 +714,7 @@ module.exports = function (app) { } } else if (target.inlineElements && target.inlineElements.length) { for (const ie of target.inlineElements) { - if (ie.type == 'image(s)') { + if (ie.type === 'image(s)') { for (const image of ie.images) { fs.unlink(global.appRoot + '/cdn/images/' + image, (err) => { if (err) console.log('Image deletion error ' + err) @@ -707,7 +734,7 @@ module.exports = function (app) { } else { // There are no children, the target can be destroyed target.remove() - if (target.numberOfSiblings == 0 && target.parent.deleted) { + if (target.numberOfSiblings === 0 && target.parent.deleted) { // There are also no siblings, and the element's parent // has been deleted, so we can even destroy that! target.parent.remove() @@ -718,25 +745,15 @@ module.exports = function (app) { .then((comment) => { post.lastUpdated = latestTimestamp // unsubscribe the author of the deleted comment from the post if they have no other comments on it - if (commentsByUser == 0) { + if (commentsByUser === 0) { post.subscribedUsers = post.subscribedUsers.filter((v, i, a) => { - return v != req.user._id.toString() + return v !== req.user._id.toString() }) post.save().catch(err => { console.error(err) }) } - // if (!target.some((v, i, a) => { - // return v.author.toString() == req.user._id.toString(); - // })) { - // post.subscribedUsers = post.subscribedUsers.filter((v, i, a) => { - // return v != req.user._id.toString(); - // }) - // post.save().catch(err => { - // console.error(err) - // }) - // } - result = { + const result = { numberOfComments: numberOfComments } res.contentType('json').send(JSON.stringify(result)) @@ -752,7 +769,7 @@ module.exports = function (app) { // Outputs: a new post of type boost, adds the id of that new post into the boosts field of the old post, sends a notification to the // user whose post was boosted. app.post('/createboost/:postid', isLoggedInOrRedirect, function (req, res) { - boostedTimestamp = new Date() + const boostedTimestamp = new Date() Post.findOne({ _id: req.params.postid }, { @@ -764,7 +781,7 @@ module.exports = function (app) { url: 1 }).populate('author') .then((boostedPost) => { - if (boostedPost.privacy != 'public' || boostedPost.type == 'community') { + if (boostedPost.privacy !== 'public' || boostedPost.type === 'community') { res.status(400).send('post is not public and therefore may not be boosted') return } @@ -831,13 +848,13 @@ module.exports = function (app) { .then(async post => { if (post.author.equals(req.user._id)) { // This post has been written by the logged in user - we good - var isCommunityPost = (post.type == 'community') + var isCommunityPost = post.type === 'community' var content = await helper.renderHTMLContent(post, true) hbs.render('./views/partials/posteditormodal.handlebars', { contentWarnings: post.contentWarnings, privacy: post.privacy, isCommunityPost: isCommunityPost, - isDraft: post.type == 'draft', + isDraft: post.type === 'draft', postID: post._id.toString() }) .then(async html => { @@ -894,7 +911,7 @@ module.exports = function (app) { }) } } else if (post.inlineElements && post.inlineElements.length) { - post.inlineElements.filter(element => element.type == 'image(s)').map(imagesElement => imagesElement.images.map( + post.inlineElements.filter(element => element.type === 'image(s)').map(imagesElement => imagesElement.images.map( (imageFilename, i) => { oldPostImages.push(imageFilename) if (imagesElement.imageIsHorizontal && imagesElement.imageIsVertical) { @@ -903,32 +920,35 @@ module.exports = function (app) { } })) } + let imagePrivacy + let imagePrivacyChanged if (post.community) { - var imagePrivacy = (await Community.findById(post.community)).settings.visibility - var imagePrivacyChanged = false + imagePrivacy = (await Community.findById(post.community)).settings.visibility + imagePrivacyChanged = false } else if (req.body.isDraft) { - var imagePrivacy = 'private' - var imagePrivacyChanged = false + imagePrivacy = 'private' + imagePrivacyChanged = false } else { - var imagePrivacy = req.body.postPrivacy - var imagePrivacyChanged = !(imagePrivacy == post.privacy) + imagePrivacy = req.body.postPrivacy + imagePrivacyChanged = !(imagePrivacy === post.privacy) } // finalize each new image with the helper function; retrieve the orientation of the already existing ones from the lookup table by their filename. // change the privacy of old image documents if the post's privacy changed. var currentPostImages = [] - for (e of parsedPost.inlineElements) { - if (e.type == 'image(s)') { + for (const e of parsedPost.inlineElements) { + if (e.type === 'image(s)') { e.imageIsVertical = [] e.imageIsHorizontal = [] for (var i = 0; i < e.images.length; i++) { currentPostImages.push(e.images[i]) + let horizOrVertic if (!oldPostImages.includes(e.images[i])) { - var horizOrVertic = await helper.finalizeImages([e.images[i]], post.type, post.community, req.user._id.toString(), imagePrivacy, req.user.settings.imageQuality) + horizOrVertic = await helper.finalizeImages([e.images[i]], post.type, post.community, req.user._id.toString(), imagePrivacy, req.user.settings.imageQuality) e.imageIsVertical.push(horizOrVertic.imageIsVertical[0]) e.imageIsHorizontal.push(horizOrVertic.imageIsHorizontal[0]) } else if (!post.imageVersion || post.imageVersion < 2) { // finalize images that were previously stored in /public/images/uploads so that there's only one url scheme that needs to be used with inlineElements. - var horizOrVertic = await helper.finalizeImages([e.images[i]], post.type, post.community, req.user._id.toString(), imagePrivacy, req.user.settings.imageQuality, global.appRoot + '/public/images/uploads/') + horizOrVertic = await helper.finalizeImages([e.images[i]], post.type, post.community, req.user._id.toString(), imagePrivacy, req.user.settings.imageQuality, global.appRoot + '/public/images/uploads/') e.imageIsVertical.push(horizOrVertic.imageIsVertical[0]) e.imageIsHorizontal.push(horizOrVertic.imageIsHorizontal[0]) } else { @@ -945,7 +965,7 @@ module.exports = function (app) { } var deletedImages = oldPostImages.filter(v => !currentPostImages.includes(v)) - for (image of deletedImages) { + for (const image of deletedImages) { Image.deleteOne({ filename: image }) fs.unlink(global.appRoot + ((!post.imageVersion || post.imageVersion < 2) ? '/public/images/uploads/' : '/cdn/images/') + image, (err) => { if (err) { console.error('could not delete unused image from edited post:\n' + err) } }) } @@ -959,14 +979,14 @@ module.exports = function (app) { post.embeds = undefined var newMentions = parsedPost.mentions.filter(v => !post.mentions.includes(v)) - for (mention of newMentions) { - if (mention != req.user.username) { + for (const mention of newMentions) { + if (mention !== req.user.username) { User.findOne({ username: mention }).then(async mentioned => { if (post.community) { if (mentioned.communities.some(v => v.equals(post.community))) { notifier.notify('user', 'mention', mentioned._id, req.user._id, newPostId, '/' + req.user.username + '/' + post.url, 'post') } - } else if (req.body.postPrivacy == 'private') { + } else if (req.body.postPrivacy === 'private') { if (await Relationship.findOne({ value: 'trust', fromUser: req.user._id, toUser: mentioned._id })) { notifier.notify('user', 'mention', mentioned._id, req.user._id, newPostId, '/' + req.user.username + '/' + post.url, 'post') } @@ -990,7 +1010,7 @@ module.exports = function (app) { // if the post is in a community, the community's last activity timestamp could be updated here, but maybe not, idk - var newHTML = await helper.renderHTMLContent(post) + let newHTML = await helper.renderHTMLContent(post) post.cachedHTML.fullContentHTML = newHTML post.contentWarnings = req.body.postContentWarnings @@ -1002,7 +1022,7 @@ module.exports = function (app) { '' } - if (post.type == 'draft' && !req.body.isDraft) { + if (post.type === 'draft' && !req.body.isDraft) { var timePublished = new Date() post.timestamp = timePublished post.lastUpdated = timePublished @@ -1011,11 +1031,11 @@ module.exports = function (app) { newHTML = '

This post was published!

' // the color is $sweet-red from _colors.scss } - if (post.type == 'original') { + if (post.type === 'original') { post.privacy = req.body.postPrivacy - } else if (post.type == 'draft') { + } else if (post.type === 'draft') { post.privacy = 'private' // the client should send is this in req.body.postPrivacy but, just to be sure - } else if (post.type == 'community') { + } else if (post.type === 'community') { post.privacy = 'public' } @@ -1032,10 +1052,10 @@ module.exports = function (app) { // images that are going to be picked up by this function are those uploaded by users whose device loses power, whose device loses internet connection and doesn't // regain it before they close the tab, and maybe those that are using a really weird browser or extensions. function cleanTempFolder () { - fs.readdir('./cdn/images/temp', function (err, files) { + fs.readdir('./cdn/images/temp', function (_, files) { files.forEach(file => { - if (file != '.gitkeep' && file != '') { - fs.stat('./cdn/images/temp/' + file, function (err, s) { + if (file !== '.gitkeep' && file !== '') { + fs.stat('./cdn/images/temp/' + file, function (_, s) { if (Date.now() - s.mtimeMs > 7 * 24 * 60 * 60 * 1000) { fs.unlink('./cdn/images/temp/' + file, function (e) { if (e) { @@ -1056,7 +1076,7 @@ setInterval(cleanTempFolder, 24 * 60 * 60 * 1000) function isLoggedInOrRedirect (req, res, next) { if (req.isAuthenticated()) { // A potentially expensive way to update a user's last logged in timestamp (currently only relevant to sorting search results) - currentTime = new Date() + const currentTime = new Date() if ((currentTime - req.user.lastUpdated) > 3600000) { // If the timestamp is older than an hour User.findOne({ _id: req.user._id diff --git a/app/statisticsTracker.js b/app/statisticsTracker.js index 6994f8d3..6a47a0a8 100644 --- a/app/statisticsTracker.js +++ b/app/statisticsTracker.js @@ -1,3 +1,9 @@ +const path = require('path') +const bcrypt = require('bcrypt-nodejs') +const fs = require('fs') +const Post = require('./models/post') +const User = require('./models/user') + module.exports = function (app, mongoose) { // Fun stats tracker. Non-interactive. var cameOnlineAt = new Date() @@ -6,11 +12,11 @@ module.exports = function (app, mongoose) { var uptime = new Date(currentTime - cameOnlineAt).toISOString().slice(11, -1) Post.countDocuments({}).then(numberOfPosts => { Image.countDocuments({}).then(numberOfImages => { - mongoose.connection.db.collection('sessions', (err, collection) => { - collection.find().toArray(function (err, activeSessions) { + mongoose.connection.db.collection('sessions', (_, collection) => { + collection.find().toArray(function (_, activeSessions) { var numberOfActiveSessions = activeSessions.length var activeusers = [] - for (sesh of activeSessions) { + for (const sesh of activeSessions) { var sessiondata = JSON.parse(sesh.session) if (sessiondata.passport) { if (!activeusers.includes(sessiondata.passport.user)) { @@ -115,7 +121,7 @@ module.exports = function (app, mongoose) { if (userTablePromise) { await userTablePromise } - var mostRecentDate + let mostRecentDate if (!fs.existsSync(postTableFileName)) { if (!postTablePromise) { postTablePromise = rebuildPostTable() @@ -150,7 +156,7 @@ module.exports = function (app, mongoose) { if (postTablePromise) { await postTablePromise } - var mostRecentDate + let mostRecentDate if (!fs.existsSync(userTableFileName)) { if (!userTablePromise) { userTablePromise = rebuildUserTable() @@ -177,9 +183,9 @@ module.exports = function (app, mongoose) { }) }) - var activeUsersTablePromise = undefined + let activeUsersTablePromise app.get('/admin/justactiveusersgraph', async function (req, res) { - var mostRecentDate + let mostRecentDate if (!fs.existsSync(activeUserTableFileName)) { if (!activeUsersTablePromise) { activeUsersTablePromise = rebuildActiveUsersTable() @@ -253,7 +259,7 @@ var activeUserTableFileName = 'activeUsersTimeline.csv' function tableNotUpToDate (tableFilename, dateInterval = 1) { var lastLine = '' fs.readFileSync(tableFilename, 'utf-8').split('\n').forEach(function (line) { - if (line && line != '\n') { + if (line && line !== '\n') { lastLine = line } }) @@ -417,16 +423,17 @@ async function parseTableForGraph (filename, collection, callbackForCurrentY, en } }; // add in a datapoint representing the current exact second, if we have a source to obtain it from - var now = new Date() + const now = new Date() + let numberOfDocs if (collection) { - var numberOfDocs = await collection.countDocuments() + numberOfDocs = await collection.countDocuments() } else if (callbackForCurrentY) { - var numberOfDocs = await callbackForCurrentY() + numberOfDocs = await callbackForCurrentY() } else { return jsonVersion } jsonVersion.push({ - label: numberOfDocs == 69 ? 'nice' : now.toLocaleString(), + label: numberOfDocs === 69 ? 'nice' : now.toLocaleString(), year: now.getFullYear(), month: now.getMonth(), date: now.getDate(), @@ -440,10 +447,10 @@ async function parseTableForGraph (filename, collection, callbackForCurrentY, en // this takes the datpoints type thing above and takes the derivative by subtracting the last date function getPerDayRate (datapoints) { - var newpoints = [] - var previousPoint = undefined + const newpoints = [] + let previousPoint for (const point of datapoints) { - var y = point.y + let y = point.y if (previousPoint) { y -= previousPoint } @@ -607,7 +614,7 @@ async function getActiveUsersForInterval (intervalStart, intervalEnd) { async function getActiveUsersSinceLastSave () { var lastLine = '' fs.readFileSync(activeUserTableFileName, 'utf-8').split('\n').forEach(function (line) { - if (line && line != '\n') { + if (line && line !== '\n') { lastLine = line } }) diff --git a/app/utilityFunctionsMostlyText.js b/app/utilityFunctionsMostlyText.js index 37092a38..538c6a95 100644 --- a/app/utilityFunctionsMostlyText.js +++ b/app/utilityFunctionsMostlyText.js @@ -1,3 +1,12 @@ +const Autolinker = require('autolinker') +const mongoose = require('mongoose') +const fs = require('fs') +const sharp = require('sharp') +const path = require('path') +const sanitizeHtml = require('sanitize-html') +// const hbs +const User = require('./models/user') + // these requires are not in server.js bc they're only used here const urlParser = require('url') const metascraper = require('metascraper')([ @@ -12,12 +21,13 @@ module.exports = { // Parses new post and new comment content. Input: a text string. Output: a parsed text string. parseText: async function (rawText, mentionsEnabled = true, hashtagsEnabled = true, urlsEnabled = true) { console.log('Parsing content') + let inlineElements if (typeof rawText !== 'string') { // it is an array of paragraphs and inline elements var parsedParagraphList = await this.parseParagraphList(rawText) rawText = parsedParagraphList.text - var inlineElements = parsedParagraphList.inlineElements + inlineElements = parsedParagraphList.inlineElements } else { - var inlineElements = [] + inlineElements = [] // this is also done by parseParagraphList, but if we're not using that we use this, here rawText = rawText.replace(/^(

(
|\s)*<\/p>)*/, '') // filter out blank lines from beginning rawText = rawText.replace(/(

(
|\s)<\/p>)*$/, '') // filter them out from the end @@ -75,13 +85,13 @@ module.exports = { for (var i = 0; i < pList.length; i++) { if (typeof pList[i] === 'string') { var isBlank = pList[i].match(/^

(
|\s)*<\/p>$/) - if (((i == 0 && !inlineElements.length) || lastWasBlank) && isBlank) { + if (((i === 0 && !inlineElements.length) || lastWasBlank) && isBlank) { pList.splice(i, 1) i-- } lastWasBlank = isBlank } else { - if (pList[i].type == 'link-preview') { + if (pList[i].type === 'link-preview') { try { pList[i] = await this.getLinkMetadata(pList[i].linkUrl) } catch (err) { @@ -112,7 +122,7 @@ module.exports = { transformTags: { a: function (tagName, attribs) { // if a link is not explicitly relative due to an initial / (like mention and hashtag links are) and doesn't already include a protocol: - if (attribs.href.substring(0, 1) != '/' && !attribs.href.includes('//')) { + if (attribs.href.substring(0, 1) !== '/' && !attribs.href.includes('//')) { // make the link explicitly non-relative (even though technically maybe it should be https browsers will generally figure that out) attribs.href = 'http://' + attribs.href } @@ -144,18 +154,19 @@ module.exports = { }, getLinkMetadata: async function (url) { // remove the protocol and the path if it's empty from the url bc that's how it's stored in the cache (that way it matches it with or without) - var parsedUrl = Object.assign(urlParser.parse(url), { protocol: '', slashes: false }) - if (parsedUrl.path == '/' && parsedUrl.pathname == '/') { + const parsedUrl = Object.assign(urlParser.parse(url), { protocol: '', slashes: false }) + if (parsedUrl.path === '/' && parsedUrl.pathname === '/') { parsedUrl.path = '' parsedUrl.pathname = '' } - var retrievalUrl = urlParser.format(parsedUrl) - var finalUrl // this will have the correct protocol, obtained either by the cache or the request package - var cache = mongoose.model('Cached Link Metadata') - var found = await cache.findOne({ retrievalUrl: retrievalUrl }) - var cacheHit = !!found + const retrievalUrl = urlParser.format(parsedUrl) + let finalUrl // this will have the correct protocol, obtained either by the cache or the request package + const cache = mongoose.model('Cached Link Metadata') + const found = await cache.findOne({ retrievalUrl: retrievalUrl }) + const cacheHit = !!found + let metadata if (!cacheHit) { - var urlreq = new Promise(function (resolve, reject) { + const urlreq = new Promise(function (resolve, reject) { request({ url: url.includes('//') ? url : ('http://' + url), gzip: true }, function (error, response, body) { // (faking a maybe-wrong protocol here just so the this thing will accept it) if (error) { reject(error) @@ -166,10 +177,10 @@ module.exports = { }) }) const html = await urlreq - var metadata = await metascraper({ html, url: finalUrl }) + metadata = await metascraper({ html, url: finalUrl }) } else { - var metadata = found - var finalUrl = metadata.linkUrl + metadata = found + finalUrl = metadata.linkUrl } var result = { type: 'link-preview', @@ -183,9 +194,9 @@ module.exports = { // taken from https://stackoverflow.com/questions/19377262/regex-for-youtube-url var youtubeUrlFindingRegex = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/ // taken from https://github.com/regexhq/vimeo-regex/blob/master/index.js - var vimeoUrlFindingRegex = /^(http|https)?:\/\/(www\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|)(\d+)(?:|\/\?)$/ - var parsed = undefined - var isEmbeddableVideo = false + const vimeoUrlFindingRegex = /^(http|https)?:\/\/(www\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|)(\d+)(?:|\/\?)$/ + let parsed + let isEmbeddableVideo = false if (parsed = youtubeUrlFindingRegex.exec(finalUrl)) { result.embedUrl = 'https://www.youtube.com/embed/' + parsed[5] + '?autoplay=1' // won't actually autoplay until link preview is clicked isEmbeddableVideo = true @@ -233,8 +244,8 @@ module.exports = { var metadata = await sharp('./cdn/images/' + imageFileName).metadata() var image = new Image({ // posts' types are either original or community; the image's contexts are either user or community, meaning the same things. - context: postType == 'community' ? 'community' : 'user', - community: postType == 'community' ? community : undefined, + context: postType === 'community' ? 'community' : 'user', + community: postType === 'community' ? community : undefined, filename: imageFileName, privacy: privacy, user: posterID, @@ -256,11 +267,12 @@ module.exports = { return { imageIsHorizontal: imageIsHorizontal, imageIsVertical: imageIsVertical } }, // returns a string with the html contents of the post or comment rendered based on parsedContent, inlineElements, embeds, and/or the image parallel arrays. also, some extra info in the inlineElements, but that won't get saved 'cause it's not in the inlineElement schema - renderHTMLContent: async function (postOrComment, forEditor = false) { + renderHTMLContent: async (postOrComment, forEditor = false) => { + let cleanedParsedContent if (forEditor) { // remove relative links (those that start with a / but not //) from content for the editor. this is so that @ mention links and // hashtag links don't get picked up and re-linked when the edit is parsed after submission. - var cleanedParsedContent = sanitizeHtml(postOrComment.parsedContent, { + cleanedParsedContent = sanitizeHtml(postOrComment.parsedContent, { transformTags: { a: function (tagName, attribs) { if (attribs.href.match(/^\/\w/)) { @@ -278,40 +290,43 @@ module.exports = { } }) } else { - var cleanedParsedContent = postOrComment.parsedContent + cleanedParsedContent = postOrComment.parsedContent } + let filenames + let html if (postOrComment.inlineElements && postOrComment.inlineElements.length) { var lines = [] // they're more like paragraphs, really const lineFinder = /(

.*?<\/p>)|(