add eslint, fix bugs found

This commit is contained in:
Wingy 2020-11-08 16:54:08 -05:00
parent fc2c3a7114
commit 12558d3384
28 changed files with 1774 additions and 745 deletions

2
.eslintignore Normal file
View file

@ -0,0 +1,2 @@
static/libraries
patched-express-response.js

18
.eslintrc.js Normal file
View file

@ -0,0 +1,18 @@
module.exports = {
env: {
commonjs: true,
es2021: true,
node: true
},
extends: [
'standard'
],
parserOptions: {
ecmaVersion: 12
},
globals: {
_CC: 'readonly'
},
rules: {
}
}

View file

@ -1,6 +1,6 @@
require('dotenv').config();
require('dotenv').config()
const yesNo = require('yes-no');
const yesNo = require('yes-no')
module.exports = {
dbPrefix: process.env.DB_PREFIX || 'dbs/',
@ -16,4 +16,4 @@ module.exports = {
wishlist: require('./wishlist'),
base: (process.env.ROOT_PATH || '/').endsWith('/') ? (process.env.ROOT_PATH || '/') : `${process.env.ROOT_PATH}/`,
trustProxy: process.env.TRUST_PROXY === 'true' ? true : process.env.TRUST_PROXY || 'loopback'
};
}

View file

@ -1,13 +1,13 @@
const { nanoid } = require('nanoid')
const path = require('path');
const fs = require('fs');
const path = require('path')
const fs = require('fs')
const secretFilePath = path.join((process.env.SECRET_DIRNAME ? process.env.SECRET_DIRNAME : __dirname), 'secret.txt');
const secretFilePath = path.join((process.env.SECRET_DIRNAME ? process.env.SECRET_DIRNAME : __dirname), 'secret.txt')
try {
module.exports = fs.readFileSync(secretFilePath).toString();
module.exports = fs.readFileSync(secretFilePath).toString()
} catch (_) {
const secret = nanoid(128);
fs.writeFileSync(secretFilePath, secret);
module.exports = secret;
const secret = nanoid(128)
fs.writeFileSync(secretFilePath, secret)
module.exports = secret
}

View file

@ -1,14 +1,13 @@
global._CC = { require }
const PouchSession = require('session-pouchdb-store');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');
const bcrypt = require('bcrypt-nodejs');
const flash = require('connect-flash');
const passport = require('passport');
const express = require('express');
const level = require('level');
const PouchSession = require('session-pouchdb-store')
const LocalStrategy = require('passport-local').Strategy
const session = require('express-session')
const bcrypt = require('bcrypt-nodejs')
const flash = require('connect-flash')
const passport = require('passport')
const express = require('express')
const config = require('./config');
const config = require('./config')
_CC.config = config
if (!config.dbPrefix.startsWith('http')) {
@ -16,44 +15,43 @@ if (!config.dbPrefix.startsWith('http')) {
mkdirp(config.dbPrefix)
}
const PouchDB = require('pouchdb').defaults({ prefix: config.dbPrefix });
const PouchDB = require('pouchdb').defaults({ prefix: config.dbPrefix })
const logger = require('./logger');
const logger = require('./logger')
const app = express();
const app = express()
app.set('base', config.base)
app.set('trust proxy', config.trustProxy)
const db = new PouchDB('users');
const db = new PouchDB('users')
passport.use('local', new LocalStrategy(
(username, password, done) => {
username = username.trim();
username = username.trim()
db.get(username)
.then(doc => {
bcrypt.compare(password, doc.password, (err, correct) => {
if (err) return done(err);
if (!correct) return done(null, false, { message: 'Incorrect password' });
if (correct) return done(null, doc);
});
if (err) return done(err)
if (!correct) return done(null, false, { message: 'Incorrect password' })
if (correct) return done(null, doc)
})
})
.catch(err => {
if (err.message === 'missing') return done(null, false, { message: 'Incorrect username.' });
return done(err);
});
if (err.message === 'missing') return done(null, false, { message: 'Incorrect username.' })
return done(err)
})
}
));
))
passport.serializeUser((user, callback) => callback(null, user._id));
passport.serializeUser((user, callback) => callback(null, user._id))
passport.deserializeUser((user, callback) => {
db.get(user)
.then(dbUser => callback(null, dbUser))
.catch(() => callback(null, null));
});
.catch(() => callback(null, null))
})
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('body-parser').urlencoded({ extended: true }))
app.use(session({
secret: config.secret,
resave: false,
@ -63,26 +61,26 @@ app.use(session({
maxAge: config.sessionMaxAge
},
name: 'christmas_community.connect.sid'
}));
app.use(flash());
app.use(passport.initialize());
app.use(passport.session());
}))
app.use(flash())
app.use(passport.initialize())
app.use(passport.session())
app.use(require('./middlewares/locals'));
app.use(require('./middlewares/locals'))
app.use((req, res, next) => {
logger.log('express', `${req.ip} - ${req.method} ${req.originalUrl}`);
next();
});
logger.log('express', `${req.ip} - ${req.method} ${req.originalUrl}`)
next()
})
app.set('view engine', 'pug');
app.use(config.base, require('./routes')({ db, config }));
app.set('view engine', 'pug')
app.use(config.base, require('./routes')({ db, config }))
app.listen(config.port, () => logger.success('express', `Express server started on port ${config.port}!`))
;(() => {
if (!config.dbExposePort) return
const dbExposeApp = express()
dbExposeApp.use('/', require('express-pouchdb')(PouchDB, { inMemoryConfig: true }));
dbExposeApp.use('/', require('express-pouchdb')(PouchDB, { inMemoryConfig: true }))
dbExposeApp.listen(config.dbExposePort, () => logger.success('db expose', `DB has been exposed on port ${config.dbExposePort}`))
})()

View file

@ -1,12 +1,9 @@
const chalk = require('chalk');
const config = require('./config');
const colors = {log: 'blue', success: 'green', error: 'red', warn: 'yellow'};
const chalk = require('chalk')
const colors = { log: 'blue', success: 'green', error: 'red', warn: 'yellow' }
// rewrite to use Object.keys()
for (let property in colors) {
if (colors.hasOwnProperty(property)) {
module.exports[property] = (type, msg) => {
console.log(chalk.keyword(colors[property])(`[ ${type.toUpperCase()} ] ${msg}`));
};
}
}
Object.keys(colors).forEach(
method => // eslint-disable-line no-return-assign
module.exports[method] =
(type, msg) =>
console.log(chalk.keyword(colors[method])(`[ ${type.toUpperCase()} ] ${msg}`))
)

View file

@ -3,14 +3,14 @@ const { spawn } = require('child_process')
const PACKAGENAME = 'get-product-name'
async function isOutdated() {
async function isOutdated () {
const command = `npm outdated ${PACKAGENAME} --json`
const npm = await exec(command)
const data = JSON.parse(npm.stdout)
return data[PACKAGENAME]?.current !== data[PACKAGENAME]?.wanted
}
async function updateGPD() {
async function updateGPD () {
// https://blog.cloud66.com/using-node-with-docker/
const command = `mv ./node_modules ./node_modules.tmp && mv ./node_modules.tmp ./node_modules && npm update ${PACKAGENAME}`
await exec(command)
@ -18,15 +18,15 @@ async function updateGPD() {
;(async () => {
let cc = null
function spawnCC() {
cc = spawn('node', [ 'index.js' ], { env: process.env })
function spawnCC () {
cc = spawn('node', ['index.js'], { env: process.env })
cc.on('exit', spawnCC)
cc.stdout.pipe(process.stdout)
cc.stderr.pipe(process.stderr)
}
if (process.env.UPDATE_GPD !== 'false') {
async function update() {
async function update () {
if (await isOutdated()) {
try {
await updateGPD()

View file

@ -1,6 +1,6 @@
const config = require('../config');
const config = require('../config')
module.exports = (req, res, next) => {
res.locals.config = config;
res.locals.req = req;
next();
};
res.locals.config = config
res.locals.req = req
next()
}

View file

@ -1,7 +1,7 @@
const config = require('../config');
const config = require('../config')
module.exports = options => {
return (req, res, next) => {
options = options ? options : {};
options = options || {}
let authed = false
try {
authed = req.isAuthenticated()
@ -10,5 +10,5 @@ module.exports = options => {
}
if (authed) return next()
res.redirect(options.failureRedirect || config.defaultFailureRedirect)
};
}
}

View file

@ -36,5 +36,12 @@
"session-pouchdb-store": "^0.4.1",
"u64": "^1.0.1",
"yes-no": "^0.0.1"
},
"devDependencies": {
"eslint": "^7.13.0",
"eslint-config-standard": "^16.0.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1"
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,31 +1,30 @@
const verifyAuth = require('../../middlewares/verifyAuth');
const bcrypt = require('bcrypt-nodejs');
const express = require('express');
const verifyAuth = require('../../middlewares/verifyAuth')
const express = require('express')
const { nanoid } = require('nanoid')
const SECRET_TOKEN_LENGTH = 32
const SECRET_TOKEN_LIFETIME =
// One week, approximately. Doesn't need to be perfect.
1000 // milliseconds
* 60 // seconds
* 60 // minutes
* 24 // hours
* 07 // days
1000 * // milliseconds
60 * // seconds
60 * // minutes
24 * // hours
7 // days
module.exports = (db) => {
const router = express.Router();
const router = express.Router()
router.get('/', verifyAuth(), (req, res) => {
if (!req.user.admin) return res.redirect('/');
if (!req.user.admin) return res.redirect('/')
db.allDocs({ include_docs: true })
.then(docs => {
res.render('adminSettings', { title: 'Admin Settings', users: docs.rows })
})
.catch(err => { throw err; });
});
.catch(err => { throw err })
})
router.post('/add', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/');
if (!req.user.admin) return res.redirect('/')
await db.put({
_id: req.body.newUserUsername.trim(),
admin: false,
@ -34,43 +33,43 @@ module.exports = (db) => {
signupToken: nanoid(SECRET_TOKEN_LENGTH),
expiry: new Date().getTime() + SECRET_TOKEN_LIFETIME
});
})
res.redirect(`/admin-settings/edit/${req.body.newUserUsername.trim()}`)
});
})
router.get('/edit/:userToEdit', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/');
if (!req.user.admin) return res.redirect('/')
const doc = await db.get(req.params.userToEdit)
delete doc.password
res.render('admin-user-edit', { user: doc });
});
res.render('admin-user-edit', { user: doc })
})
router.post('/edit/refresh-signup-token/:userToEdit', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/');
if (!req.user.admin) return res.redirect('/')
const doc = await db.get(req.params.userToEdit)
doc.signupToken = nanoid(SECRET_TOKEN_LENGTH)
doc.expiry = new Date().getTime() + SECRET_TOKEN_LIFETIME
await db.put(doc)
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
});
})
router.post('/edit/resetpw/:userToEdit', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/');
if (!req.user.admin) return res.redirect('/')
const doc = await db.get(req.params.userToEdit)
doc.pwToken = nanoid(SECRET_TOKEN_LENGTH)
doc.pwExpiry = new Date().getTime() + SECRET_TOKEN_LIFETIME
await db.put(doc)
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
});
})
router.post('/edit/cancelresetpw/:userToEdit', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/');
if (!req.user.admin) return res.redirect('/')
const doc = await db.get(req.params.userToEdit)
delete doc.pwToken
delete doc.pwExpiry
await db.put(doc)
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
});
})
router.post('/edit/rename/:userToRename', verifyAuth(), async (req, res) => {
if (!req.user.admin && req.user._id !== req.params.userToRename) return res.redirect('/')
@ -119,7 +118,7 @@ module.exports = (db) => {
})
router.post('/edit/impersonate/:userToEdit', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/');
if (!req.user.admin) return res.redirect('/')
req.login({ _id: req.params.userToEdit }, err => {
if (err) {
req.flash('error', err.message)
@ -128,29 +127,29 @@ module.exports = (db) => {
req.flash('success', `You are now ${req.params.userToEdit}.`)
res.redirect('/')
})
});
})
router.post('/edit/remove/:userToRemove', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/');
const doc = await db.get(req.params.userToRemove);
if (!req.user.admin) return res.redirect('/')
const doc = await db.get(req.params.userToRemove)
if (doc.admin) {
req.flash('error', 'Failed to remove: user is admin.');
return res.redirect('/admin-settings');
req.flash('error', 'Failed to remove: user is admin.')
return res.redirect('/admin-settings')
}
await db.remove(doc);
const docs = await db.allDocs({ include_docs: true });
for (let i = 0; i < docs.length; i++) {
for (let j = 0; j < docs[i].doc.wishlist.length; j++) {
if (docs[i].doc.wishlist[j].pledgedBy === req.params.userToRemove) {
docs[i].doc.wishlist[j].pledgedBy === undefined;
if (docs[i].doc.wishlist[j].addedBy === req.params.userToRemove) await db.remove(doc);
else await db.put(docs[i].doc);
await db.remove(doc)
const { rows } = await db.allDocs({ include_docs: true })
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < rows[i].doc.wishlist.length; j++) {
if (rows[i].doc.wishlist[j].pledgedBy === req.params.userToRemove) {
rows[i].doc.wishlist[j].pledgedBy = undefined
if (rows[i].doc.wishlist[j].addedBy === req.params.userToRemove) rows[i].doc.wishlist.splice(j, 1)
await db.put(rows[i].doc)
}
}
}
req.flash('success', `Successfully removed user ${req.params.userToRemove}`);
req.flash('success', `Successfully removed user ${req.params.userToRemove}`)
res.redirect('/admin-settings')
});
})
return router;
};
return router
}

View file

@ -1,9 +1,8 @@
const verifyAuth = require('../../middlewares/verifyAuth');
const express = require('express');
const path = require('path');
const verifyAuth = require('../../middlewares/verifyAuth')
const express = require('express')
module.exports = ({ db, config }) => {
const router = express.Router();
const router = express.Router()
router.use(verifyAuth())
@ -13,7 +12,7 @@ module.exports = ({ db, config }) => {
})
})
router.use('/wishlist', require('./wishlist')({ db }));
router.use('/wishlist', require('./wishlist')({ db }))
return router;
return router
}

View file

@ -1,8 +1,6 @@
const verifyAuth = require('../../../middlewares/verifyAuth')
const express = require('express')
const path = require('path')
module.exports = ({ db, config }) => {
module.exports = ({ db }) => {
const router = express.Router()
router.get('/', (req, res) => {
@ -19,12 +17,12 @@ module.exports = ({ db, config }) => {
if (req.params.direction === 'up') wishlist.reverse()
let moveFromIndex
wishlist.forEach(wish => {
if (wish.id === req.params.id) return moveFromIndex = wishlist.indexOf(wish)
if (wish.id === req.params.id) moveFromIndex = wishlist.indexOf(wish)
})
const moveToIndex = wishlist.findIndex(wish => {
return ( wishlist.indexOf(wish) > moveFromIndex && wish.addedBy === req.user._id )
return (wishlist.indexOf(wish) > moveFromIndex && wish.addedBy === req.user._id)
})
if (moveToIndex < 0 || moveToIndex > wishlist.length) return res.send({ error: 'Invalid move '})
if (moveToIndex < 0 || moveToIndex > wishlist.length) return res.send({ error: 'Invalid move ' })
const original = wishlist[moveToIndex]
wishlist[moveToIndex] = wishlist[moveFromIndex]
wishlist[moveFromIndex] = original

View file

@ -1,8 +1,8 @@
const bcrypt = require('bcrypt-nodejs');
const express = require('express');
const bcrypt = require('bcrypt-nodejs')
const express = require('express')
module.exports = (db) => {
const router = express.Router();
const router = express.Router()
router.get('/:code', async (req, res) => {
const row = (await db.allDocs({ include_docs: true }))
@ -10,7 +10,7 @@ module.exports = (db) => {
.find(({ doc }) => doc.signupToken === req.params.code)
res.render('confirm-account', { doc: row ? row.doc : undefined })
});
})
router.post('/:code', async (req, res) => {
const { doc } = (await db.allDocs({ include_docs: true }))
@ -20,7 +20,7 @@ module.exports = (db) => {
if (doc.expiry < new Date().getTime()) return res.redirect(`/confirm-account/${req.params.code}`)
bcrypt.hash(req.body.password, null, null, async (err, passwordHash) => {
if (err) throw err;
if (err) throw err
doc.password = passwordHash
delete doc.signupToken
@ -34,11 +34,11 @@ module.exports = (db) => {
req.flash('error', err.message)
return res.redirect('/')
}
req.flash('success', `Welcome to ${_CC.config.siteTitle}!`);
res.redirect('/');
req.flash('success', `Welcome to ${_CC.config.siteTitle}!`)
res.redirect('/')
})
});
});
})
})
return router;
};
return router
}

View file

@ -1,44 +1,44 @@
const verifyAuth = require('../middlewares/verifyAuth');
const express = require('express');
const path = require('path');
const verifyAuth = require('../middlewares/verifyAuth')
const express = require('express')
const path = require('path')
module.exports = ({ db, config }) => {
const router = express.Router();
const router = express.Router()
router.use('/', express.static(path.join(__dirname, '../static')));
router.use('/', express.static(path.join(__dirname, '../static')))
router.get('/',
async (req, res, next) => {
dbInfo = await db.info();
const dbInfo = await db.info()
if (dbInfo.doc_count === 0) {
res.redirect('/setup');
res.redirect('/setup')
} else {
next();
next()
}
},
verifyAuth(),
(req, res) => {
res.redirect('/wishlist');
res.redirect('/wishlist')
}
);
)
router.use('/api', require('./api')({ db }))
router.use('/setup', require('./setup')(db));
router.use('/setup', require('./setup')(db))
router.use('/login', require('./login')());
router.use('/logout', require('./logout')());
router.use('/resetpw', require('./resetpw')(db));
router.use('/confirm-account', require('./confirm-account')(db));
router.use('/login', require('./login')())
router.use('/logout', require('./logout')())
router.use('/resetpw', require('./resetpw')(db))
router.use('/confirm-account', require('./confirm-account')(db))
router.use('/wishlist', require('./wishlist')(db));
router.use('/wishlist', require('./wishlist')(db))
router.use('/supported-sites', require('./supported-sites')())
router.use('/profile', require('./profile')(db));
router.use('/profile', require('./profile')(db))
router.use('/admin-settings', require('./adminSettings')(db));
router.use('/admin-settings', require('./adminSettings')(db))
router.use('/manifest.json', require('./manifest.json')({ config }))
return router;
return router
}

View file

@ -1,29 +1,29 @@
const passport = require('passport');
const express = require('express');
const passport = require('passport')
const express = require('express')
module.exports = () => {
const router = express.Router();
const router = express.Router()
router.get('/',
(req, res) => {
if (req.isAuthenticated()) {
res.redirect('/');
res.redirect('/')
} else {
res.render('login');
res.render('login')
}
}
);
)
router.post(
'/',
(req, res, next) => {
next();
next()
},
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash: 'Invalid username or password'
})
);
return router;
};
)
return router
}

View file

@ -1,14 +1,14 @@
const verifyAuth = require('../../middlewares/verifyAuth');
const express = require('express');
const verifyAuth = require('../../middlewares/verifyAuth')
const express = require('express')
module.exports = () => {
const router = express.Router();
const router = express.Router()
router.get('/', verifyAuth(), (req, res) => res.render('logout'));
router.get('/', verifyAuth(), (req, res) => res.render('logout'))
router.post('/', (req, res) => {
req.logout();
res.redirect('/');
});
req.logout()
res.redirect('/')
})
return router;
};
return router
}

View file

@ -1,7 +1,7 @@
const express = require('express');
const express = require('express')
module.exports = ({ config }) => {
const router = express.Router();
const router = express.Router()
router.get('/', (req, res) => {
res.send({
@ -19,7 +19,7 @@ module.exports = ({ config }) => {
}
]
})
});
})
return router;
};
return router
}

View file

@ -1,39 +1,39 @@
const verifyAuth = require('../../middlewares/verifyAuth');
const bcrypt = require('bcrypt-nodejs');
const express = require('express');
const verifyAuth = require('../../middlewares/verifyAuth')
const bcrypt = require('bcrypt-nodejs')
const express = require('express')
module.exports = (db) => {
const router = express.Router();
const router = express.Router()
router.get('/', verifyAuth(), (req, res) => res.render('profile', { title: `Profile Settings - ${req.user._id}`}));
router.get('/', verifyAuth(), (req, res) => res.render('profile', { title: `Profile Settings - ${req.user._id}` }))
router.post('/', verifyAuth(), (req, res) => {
if (req.body.oldPassword && req.body.newPassword) {
bcrypt.compare(req.body.oldPassword, req.user.password, (err, correct) => {
if (err) throw err;
if (err) throw err
if (correct) {
bcrypt.hash(req.body.newPassword, null, null, (err, hash) => {
if (err) throw err;
if (err) throw err
db.get(req.user._id)
.then(doc => {
doc.password = hash;
doc.password = hash
db.put(doc)
.then(() => {
req.flash('success', 'Changes saved successfully!');
res.redirect('/profile');
req.flash('success', 'Changes saved successfully!')
res.redirect('/profile')
})
.catch(err => { throw err; });
.catch(err => { throw err })
})
.catch(err => { throw err; });
});
.catch(err => { throw err })
})
} else {
req.flash('error', 'Incorrect old password');
res.redirect('/profile');
req.flash('error', 'Incorrect old password')
res.redirect('/profile')
}
});
})
} else {
res.redirect('/profile');
res.redirect('/profile')
}
});
})
return router;
};
return router
}

View file

@ -1,17 +1,16 @@
const bcrypt = require('bcrypt-nodejs');
const express = require('express');
const bcrypt = require('bcrypt-nodejs')
const express = require('express')
module.exports = (db) => {
const router = express.Router();
const router = express.Router()
router.get('/:code', async (req, res) => {
const row = (await db.allDocs({ include_docs: true }))
.rows
.find(({ doc }) => doc.pwToken === req.params.code)
res.render('resetpw', { doc: row ? row.doc : undefined })
});
})
router.post('/:code', async (req, res) => {
const { doc } = (await db.allDocs({ include_docs: true }))
@ -21,7 +20,7 @@ module.exports = (db) => {
if (doc.expiry < new Date().getTime()) return res.redirect(`/resetpw/${req.params.code}`)
bcrypt.hash(req.body.password, null, null, async (err, passwordHash) => {
if (err) throw err;
if (err) throw err
doc.password = passwordHash
delete doc.pwToken
@ -35,11 +34,11 @@ module.exports = (db) => {
req.flash('error', err.message)
return res.redirect('/')
}
req.flash('success', `Welcome to ${_CC.config.siteTitle}!`);
res.redirect('/');
req.flash('success', `Welcome to ${_CC.config.siteTitle}!`)
res.redirect('/')
})
});
});
})
})
return router;
};
return router
}

View file

@ -1,39 +1,39 @@
const bcrypt = require('bcrypt-nodejs')
const express = require('express');
const express = require('express')
module.exports = (db) => {
const router = express.Router();
const router = express.Router()
router.get('/',
async (req, res) => {
const dbInfo = await db.info();
const dbInfo = await db.info()
if (dbInfo.doc_count === 0) {
res.render('setup', { title: 'Setup' });
res.render('setup', { title: 'Setup' })
} else {
res.redirect('/');
res.redirect('/')
}
}
);
)
router.post('/',
async (req, res) => {
const dbInfo = await db.info();
const dbInfo = await db.info()
if (dbInfo.doc_count === 0) {
bcrypt.hash(req.body.adminPassword, null, null, (err, adminPasswordHash) => {
if (err) throw err;
if (err) throw err
db.put({
_id: req.body.adminUsername.trim(),
password: adminPasswordHash,
admin: true,
wishlist: []
})
res.redirect('/');
});
res.redirect('/')
})
} else {
res.redirect('/');
res.redirect('/')
}
}
);
)
return router;
return router
}

View file

@ -1,18 +1,18 @@
const verifyAuth = require('../../middlewares/verifyAuth');
const getProductName = require('get-product-name');
const express = require('express');
const config = require('../../config');
const verifyAuth = require('../../middlewares/verifyAuth')
const getProductName = require('get-product-name')
const express = require('express')
const config = require('../../config')
const u64 = require('u64')
const totals = wishlist => {
let unpledged = 0;
let pledged = 0;
let unpledged = 0
let pledged = 0
wishlist.forEach(wishItem => {
if (wishItem.pledgedBy) pledged += 1;
else unpledged += 1;
});
return { unpledged, pledged };
};
if (wishItem.pledgedBy) pledged += 1
else unpledged += 1
})
return { unpledged, pledged }
}
const ValidURL = (string) => { // Ty SO
try {
@ -20,40 +20,40 @@ const ValidURL = (string) => { // Ty SO
if (process.env.SMILE !== 'false') {
if (url.hostname === 'www.amazon.com') url.hostname = 'smile.amazon.com'
}
if (url) return url;
if (url) return url
} catch (_) {
return false;
return false
}
}
module.exports = (db) => {
const router = express.Router();
const router = express.Router()
router.get('/', verifyAuth(), async (req, res) => {
const docs = await db.allDocs({ include_docs: true })
if (process.env.SINGLE_LIST === 'true') {
for (row of docs.rows) {
for (const row of docs.rows) {
if (row.doc.admin) return res.redirect(`/wishlist/${row.doc._id}`)
}
}
res.render('wishlists', { title: 'Wishlists', users: docs.rows, totals})
});
res.render('wishlists', { title: 'Wishlists', users: docs.rows, totals })
})
router.get('/:user', verifyAuth(), async (req, res) => {
try {
const dbUser = await db.get(req.params.user);
const dbUser = await db.get(req.params.user)
if (process.env.SINGLE_LIST === 'true') {
if (!dbUser.admin) {
const docs = await db.allDocs({ include_docs: true })
for (row of docs.rows) {
for (const row of docs.rows) {
if (row.doc.admin) return res.redirect(`/wishlist/${row.doc._id}`)
}
}
}
const firstCanSee = dbUser.wishlist.findIndex(element => (element.addedBy === req.params.user));
const wishlistReverse = [...dbUser.wishlist].reverse();
const lastCanSeeValue = wishlistReverse.find(element => (element.addedBy === req.params.user));
const lastCanSee = dbUser.wishlist.indexOf(lastCanSeeValue);
const firstCanSee = dbUser.wishlist.findIndex(element => (element.addedBy === req.params.user))
const wishlistReverse = [...dbUser.wishlist].reverse()
const lastCanSeeValue = wishlistReverse.find(element => (element.addedBy === req.params.user))
const lastCanSee = dbUser.wishlist.indexOf(lastCanSeeValue)
res.render('wishlist', {
title: `Wishlist - ${dbUser._id}`,
wishlist: [
@ -62,40 +62,40 @@ module.exports = (db) => {
],
firstCanSee,
lastCanSee
});
})
} catch (error) {
req.flash('error', error);
return res.redirect('/wishlist');
req.flash('error', error)
return res.redirect('/wishlist')
}
});
})
router.post('/:user', verifyAuth(), async (req, res) => {
if (!req.body.itemUrlOrName) {
req.flash('error', 'Item URL or Name is required')
return res.redirect(`/wishlist/${req.params.user}`)
}
const potentialUrl = req.body.itemUrlOrName.split(' ').pop();
const url = ValidURL(potentialUrl);
const item = {};
let productData;
const potentialUrl = req.body.itemUrlOrName.split(' ').pop()
const url = ValidURL(potentialUrl)
const item = {}
let productData
try {
if (url) productData = await getProductName(url, config.proxyServer);
if (url) productData = await getProductName(url, config.proxyServer)
} catch (err) {
req.flash('error', err.toString());
req.flash('error', err.toString())
}
item.name = (productData ? productData.name : '');
item.name = (productData ? productData.name : '')
item.price = productData?.price
item.image = productData?.image
item.addedBy = req.user._id;
item.pledgedBy = (req.user._id === req.params.user ? undefined : req.user._id);
item.note = req.body.note;
if (url) item.url = url;
item.addedBy = req.user._id
item.pledgedBy = (req.user._id === req.params.user ? undefined : req.user._id)
item.note = req.body.note
if (url) item.url = url
if (!url) item.name = req.body.itemUrlOrName
item.id = u64.encode(new Date().getTime().toString());
const doc = await db.get(req.params.user);
doc.wishlist.push(item);
item.id = u64.encode(new Date().getTime().toString())
const doc = await db.get(req.params.user)
doc.wishlist.push(item)
try {
await db.put(doc);
await db.put(doc)
} catch {
req.flash('error', 'Items are being added too quickly. Please try again.')
return res.redirect(`/wishlist/${req.params.user}`)
@ -104,134 +104,134 @@ module.exports = (db) => {
'success',
(
req.user._id === req.params.user
? 'Added item to wishlist'
: `Pleged item for ${req.params.user}`
? 'Added item to wishlist'
: `Pleged item for ${req.params.user}`
)
);
res.redirect(`/wishlist/${req.params.user}`);
});
)
res.redirect(`/wishlist/${req.params.user}`)
})
router.post('/:user/pledge/:itemId', verifyAuth(), async (req, res) => {
const docs = await db.allDocs({ include_docs: true });
const docs = await db.allDocs({ include_docs: true })
for (let i = 0; i < docs.rows.length; i++) {
for (let j = 0; j < docs.rows[i].doc.wishlist.length; j++) {
if (docs.rows[i].doc.wishlist[j].id === req.params.itemId) {
if (docs.rows[i].doc.wishlist[j].pledgedBy !== undefined) {
req.flash('error', 'Item already pledged for');
return res.redirect(`/wishlist/${req.params.user}`);
req.flash('error', 'Item already pledged for')
return res.redirect(`/wishlist/${req.params.user}`)
}
docs.rows[i].doc.wishlist[j].pledgedBy = req.user._id;
await db.put(docs.rows[i].doc);
req.flash('success', 'Successfully pledged for item!');
return res.redirect(`/wishlist/${req.params.user}`);
docs.rows[i].doc.wishlist[j].pledgedBy = req.user._id
await db.put(docs.rows[i].doc)
req.flash('success', 'Successfully pledged for item!')
return res.redirect(`/wishlist/${req.params.user}`)
}
}
}
});
})
router.post('/:user/unpledge/:itemId', verifyAuth(), async (req, res) => {
const docs = await db.allDocs({ include_docs: true });
const docs = await db.allDocs({ include_docs: true })
for (let i = 0; i < docs.rows.length; i++) {
for (let j = 0; j < docs.rows[i].doc.wishlist.length; j++) {
if (docs.rows[i].doc.wishlist[j].id === req.params.itemId) {
if (docs.rows[i].doc.wishlist[j].pledgedBy !== req.user._id) {
req.flash('error', 'You did not pledge for this');
return res.redirect(`/wishlist/${req.params.user}`);
req.flash('error', 'You did not pledge for this')
return res.redirect(`/wishlist/${req.params.user}`)
}
docs.rows[i].doc.wishlist[j].pledgedBy = undefined;
if (docs.rows[i].doc.wishlist[j].addedBy === req.user._id) docs.rows[i].doc.wishlist.splice(j, 1);
await db.put(docs.rows[i].doc);
req.flash('success', 'Successfully unpledged for item');
return res.redirect(`/wishlist/${req.params.user}`);
docs.rows[i].doc.wishlist[j].pledgedBy = undefined
if (docs.rows[i].doc.wishlist[j].addedBy === req.user._id) docs.rows[i].doc.wishlist.splice(j, 1)
await db.put(docs.rows[i].doc)
req.flash('success', 'Successfully unpledged for item')
return res.redirect(`/wishlist/${req.params.user}`)
}
}
}
req.flash('error', 'Failed to find item');
return res.redirect(`/wishlist/${req.params.user}`);
});
req.flash('error', 'Failed to find item')
return res.redirect(`/wishlist/${req.params.user}`)
})
router.post('/:user/remove/:itemId', verifyAuth(), async (req, res) => {
if (req.user._id !== req.params.user) {
req.flash('error', 'Not correct user');
return res.redirect(`/wishlist/${req.params.user}`);
req.flash('error', 'Not correct user')
return res.redirect(`/wishlist/${req.params.user}`)
}
const doc = await db.get(req.user._id);
const doc = await db.get(req.user._id)
for (let i = 0; i < doc.wishlist.length; i++) {
if (doc.wishlist[i].id === req.params.itemId) {
doc.wishlist.splice(i, 1);
await db.put(doc);
req.flash('success', 'Successfully removed from wishlist');
return res.redirect(`/wishlist/${req.params.user}`);
doc.wishlist.splice(i, 1)
await db.put(doc)
req.flash('success', 'Successfully removed from wishlist')
return res.redirect(`/wishlist/${req.params.user}`)
}
}
req.flash('error', 'Failed to find item');
return res.redirect(`/wishlist/${req.params.user}`);
});
req.flash('error', 'Failed to find item')
return res.redirect(`/wishlist/${req.params.user}`)
})
router.post('/:user/move/:direction/:itemId', verifyAuth(), async (req, res) => {
if (req.user._id !== req.params.user) {
req.flash('error', 'Not correct user');
return res.redirect(`/wishlist/${req.params.user}`);
req.flash('error', 'Not correct user')
return res.redirect(`/wishlist/${req.params.user}`)
}
const doc = await db.get(req.user._id);
const wishlist = doc.wishlist;
if (req.params.direction === 'up') wishlist.reverse();
let moveFromIndex;
const doc = await db.get(req.user._id)
const wishlist = doc.wishlist
if (req.params.direction === 'up') wishlist.reverse()
let moveFromIndex
wishlist.forEach(wish => {
if (wish.id === req.params.itemId) return moveFromIndex = wishlist.indexOf(wish);
});
const moveToIndex = wishlist.findIndex(wish => ( wishlist.indexOf(wish) > moveFromIndex && wish.addedBy === req.user._id ));
if (wish.id === req.params.itemId) moveFromIndex = wishlist.indexOf(wish)
})
const moveToIndex = wishlist.findIndex(wish => (wishlist.indexOf(wish) > moveFromIndex && wish.addedBy === req.user._id))
if (moveToIndex < 0 || moveToIndex > wishlist.length) {
req.flash('error', 'Invalid move');
return res.redirect(`/wishlist/${req.params.user}`);
req.flash('error', 'Invalid move')
return res.redirect(`/wishlist/${req.params.user}`)
}
[ wishlist[moveFromIndex], wishlist[moveToIndex] ] = [ wishlist[moveToIndex], wishlist[moveFromIndex] ];
if (req.params.direction === 'up') wishlist.reverse();
doc.wishlist = wishlist;
await db.put(doc);
req.flash('success', 'Successfully moved item!');
return res.redirect(`/wishlist/${req.params.user}`);
});
[wishlist[moveFromIndex], wishlist[moveToIndex]] = [wishlist[moveToIndex], wishlist[moveFromIndex]]
if (req.params.direction === 'up') wishlist.reverse()
doc.wishlist = wishlist
await db.put(doc)
req.flash('success', 'Successfully moved item!')
return res.redirect(`/wishlist/${req.params.user}`)
})
router.get('/:user/note/:id', verifyAuth(), async (req, res) => {
const doc = await db.get(req.params.user);
const doc = await db.get(req.params.user)
const item = doc.wishlist.find(item => item.id === req.params.id)
res.render('note', { item });
});
res.render('note', { item })
})
router.post('/:user/note/:id', verifyAuth(), async (req, res) => {
const doc = await db.get(req.params.user);
const wishlist = doc.wishlist;
for (let i=0; i < wishlist.length; i++) {
wishlistItem = wishlist[i];
if (wishlistItem.id !== req.params.id) continue;
const doc = await db.get(req.params.user)
const wishlist = doc.wishlist
for (let i = 0; i < wishlist.length; i++) {
const wishlistItem = wishlist[i]
if (wishlistItem.id !== req.params.id) continue
if (req.user._id !== req.params.user && req.user._id !== wishlistItem.addedBy) {
req.flash('error', 'Invalid user');
return res.redirect(`/wishlist/${req.params.user}`);
req.flash('error', 'Invalid user')
return res.redirect(`/wishlist/${req.params.user}`)
}
for (const type of [
'name', 'note', 'url', 'price', 'image'
]) {
if (!req.body.hasOwnProperty(type)) {
if (!Object.prototype.hasOwnProperty.call(req.body, type)) {
req.flash('error', `Missing property ${type}`)
return res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`)
}
wishlistItem[type] = req.body[type]
}
wishlist[i] = wishlistItem;
wishlist[i] = wishlistItem
}
doc.wishlist = wishlist;
await db.put(doc);
req.flash('success', `Successfully saved note!`);
return res.redirect(`/wishlist/${req.params.user}`);
});
doc.wishlist = wishlist
await db.put(doc)
req.flash('success', 'Successfully saved note!')
return res.redirect(`/wishlist/${req.params.user}`)
})
router.post('/:user/refresh/:id', verifyAuth(), async (req, res) => {
const doc = await db.get(req.params.user);
const wishlist = doc.wishlist;
for (let i=0; i < wishlist.length; i++) {
wishlistItem = wishlist[i];
if (wishlistItem.id !== req.params.id) continue;
const doc = await db.get(req.params.user)
const wishlist = doc.wishlist
for (let i = 0; i < wishlist.length; i++) {
const wishlistItem = wishlist[i]
if (wishlistItem.id !== req.params.id) continue
if (req.user._id !== req.params.user && req.user._id !== wishlistItem.addedBy) {
req.flash('error', 'Invalid user');
return res.redirect(`/wishlist/${req.params.user}`);
req.flash('error', 'Invalid user')
return res.redirect(`/wishlist/${req.params.user}`)
}
if (!wishlistItem.url) {
@ -240,38 +240,39 @@ module.exports = (db) => {
}
const productData = await getProductName(wishlistItem.url)
for (field of [ 'name', 'price', 'image' ]) {
for (const field of ['name', 'price', 'image']) {
if (productData[field]) wishlistItem[field] = productData[field]
}
wishlist[i] = wishlistItem;
wishlist[i] = wishlistItem
}
doc.wishlist = wishlist;
await db.put(doc);
req.flash('success', `Successfully refreshed data!`);
return res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`);
});
doc.wishlist = wishlist
await db.put(doc)
req.flash('success', 'Successfully refreshed data!')
return res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`)
})
router.post('/:user/note/remove/:id', verifyAuth(), async (req, res) => {
const doc = await db.get(req.params.user);
const wishlist = doc.wishlist;
for (let i=0; i < wishlist.length; i++) {
wishlistItem = wishlist[i];
if (wishlistItem.id !== req.params.id) continue;
const doc = await db.get(req.params.user)
const wishlist = doc.wishlist
for (let i = 0; i < wishlist.length; i++) {
const wishlistItem = wishlist[i]
if (wishlistItem.id !== req.params.id) continue
if (req.user._id !== req.params.user && req.user._id !== wishlistItem.addedBy) {
req.flash('error', 'Invalid user');
return res.redirect(`/wishlist/${req.params.user}`);
req.flash('error', 'Invalid user')
return res.redirect(`/wishlist/${req.params.user}`)
}
if (wishlistItem.note) {
wishlistItem.note = undefined;
wishlist[i] = wishlistItem;
wishlistItem.note = undefined
wishlist[i] = wishlistItem
} else {
req.flash('error', 'Has no note');
return res.redirect(`/wishlist/${req.params.user}`); }
req.flash('error', 'Has no note')
return res.redirect(`/wishlist/${req.params.user}`)
}
}
doc.wishlist = wishlist;
await db.put(doc);
req.flash('success', 'Successfully removed note');
return res.redirect(`/wishlist/${req.params.user}`);
doc.wishlist = wishlist
await db.put(doc)
req.flash('success', 'Successfully removed note')
return res.redirect(`/wishlist/${req.params.user}`)
})
return router;
};
return router
}

View file

@ -1,8 +1,8 @@
window.onload = () => {
const burger = document.getElementById('navBarBurger');
const navBarMenu = document.getElementById('navBarMenu');
const burger = document.getElementById('navBarBurger')
const navBarMenu = document.getElementById('navBarMenu')
burger.addEventListener('click', () => {
burger.classList.toggle('is-active');
navBarMenu.classList.toggle('is-active');
});
};
burger.classList.toggle('is-active')
navBarMenu.classList.toggle('is-active')
})
}

View file

@ -1,12 +1,13 @@
function animateCSS(node, animationName) {
/* eslint-env browser */
function animateCSS (node, animationName) {
return new Promise(resolve => {
node.classList.add('animated', animationName)
function handleAnimationEnd() {
node.classList.remove('animated', animationName)
node.removeEventListener('animationend', handleAnimationEnd)
function handleAnimationEnd () {
node.classList.remove('animated', animationName)
node.removeEventListener('animationend', handleAnimationEnd)
resolve()
resolve()
}
node.addEventListener('animationend', handleAnimationEnd)
@ -15,16 +16,14 @@ function animateCSS(node, animationName) {
// These move function are stolen from
// https://stackoverflow.com/a/34914096
function moveUp(element) {
if(element.previousElementSibling)
element.parentNode.insertBefore(element, element.previousElementSibling);
function moveUp (element) {
if (element.previousElementSibling) { element.parentNode.insertBefore(element, element.previousElementSibling) }
}
function moveDown(element) {
if(element.nextElementSibling)
element.parentNode.insertBefore(element.nextElementSibling, element);
function moveDown (element) {
if (element.nextElementSibling) { element.parentNode.insertBefore(element.nextElementSibling, element) }
}
function listen(element, upOrDown) {
function listen (element, upOrDown) {
element.addEventListener('submit', async event => {
try {
event.preventDefault()
@ -66,8 +65,8 @@ function listen(element, upOrDown) {
return false
} catch (error) {
alert(error.message)
throw error
location.reload()
throw error // probably useless but just in case reload doesn't do anything
}
})
}

1048
yarn.lock

File diff suppressed because it is too large Load diff