Refactor: move src to folder (#63)

* move all source code to src/

* replace express response patch with middleware

* fix readdir not looking in src

* fix stray redirect

* add base to manifest

* remove secret.txt from src/config/secret

* add src/config/secret/secret.txt to gitignore
This commit is contained in:
Wingy 2022-12-10 12:04:07 -05:00 committed by GitHub
parent 363146c331
commit 0d7d73744c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1569 changed files with 25 additions and 1159 deletions

View file

@ -0,0 +1,234 @@
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
7 // days
module.exports = ({ db, ensurePfp }) => {
const router = express.Router()
router.get('/', verifyAuth(), (req, res) => {
if (!req.user.admin) return res.redirect('/')
db.allDocs({ include_docs: true })
.then(docs => {
res.render('adminSettings', { title: _CC.lang('ADMIN_SETTINGS_HEADER'), users: docs.rows })
})
.catch(err => { throw err })
})
router.post('/add', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/')
const username = req.body.newUserUsername.trim()
if (!username) {
return db
.allDocs({ include_docs: true })
.then((docs) => {
res.render("adminSettings", {
add_user_error: _CC.lang(
"ADMIN_SETTINGS_USERS_ADD_ERROR_USERNAME_EMPTY"
),
title: _CC.lang("ADMIN_SETTINGS_HEADER"),
users: docs.rows,
});
})
.catch((err) => {
throw err;
});
}
await db.put({
_id: username,
admin: false,
wishlist: [],
signupToken: nanoid(SECRET_TOKEN_LENGTH),
expiry: new Date().getTime() + SECRET_TOKEN_LIFETIME
})
await ensurePfp(username)
res.redirect(`/admin-settings/edit/${req.body.newUserUsername.trim()}`)
})
router.get('/edit/:userToEdit', verifyAuth(), async (req, res) => {
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 })
})
router.post('/edit/refresh-signup-token/:userToEdit', verifyAuth(), async (req, res) => {
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('/')
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('/')
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('/')
if (!req.body.newUsername) {
req.flash('error', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_NO_USERNAME_PROVIDED'))
return res.redirect(`/admin-settings/edit/${req.params.userToRename}`)
}
if (req.body.newUsername === req.params.userToRename) {
req.flash('error', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_SAME_NAME'))
return res.redirect(`/admin-settings/edit/${req.params.userToRename}`)
}
const oldName = req.params.userToRename
const newName = req.body.newUsername
const userDoc = await db.get(oldName)
userDoc._id = newName
delete userDoc._rev
try {
await db.put(userDoc)
try {
const usersBulk = []
const users = (await db.allDocs({ include_docs: true })).rows
for (const { doc: user } of users) {
for (const item of user.wishlist) {
if (item.pledgedBy === oldName) item.pledgedBy = newName
if (item.addedBy === oldName) item.addedBy = newName
}
usersBulk.push(user)
}
await db.bulkDocs(usersBulk)
await db.remove(await db.get(oldName))
req.flash('success', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_RENAMED_USER'))
return res.redirect(`/wishlist/${newName}`)
} catch (error) {
console.log(error, error.stack)
await db.remove(await db.get(newName))
throw error
}
} catch (error) {
req.flash('error', error.message)
return res.redirect(`/admin-settings/edit/${oldName}`)
}
})
router.post('/edit/impersonate/:userToEdit', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/')
req.login({ _id: req.params.userToEdit }, err => {
if (err) {
req.flash('error', err.message)
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
}
req.flash('success', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_IMPERSONATE_SUCCESS', req.params.userToEdit))
res.redirect('/')
})
})
router.post('/edit/promote/:userToPromote', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/')
const user = await db.get(req.params.userToPromote)
if (!user) {
req.flash('error', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_PROMOTE_DEMOTE_NOT_FOUND'))
return res.redirect(`/admin-settings/edit/${req.params.userToPromote}`)
}
if (user.admin) {
req.flash('error', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_PROMOTE_ALREADY_ADMIN'))
return res.redirect(`/admin-settings/edit/${req.params.userToPromote}`)
}
user.admin = true
await db.put(user)
req.flash('success', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_PROMOTE_SUCCESS', user._id))
return res.redirect(`/admin-settings/edit/${req.params.userToPromote}`)
})
router.post('/edit/demote/:userToDemote', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/')
if (req.user._id === req.params.userToDemote) {
req.flash('error', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_DEMOTE_SELF'))
return res.redirect(`/admin-settings/edit/${req.params.userToDemote}`)
}
const user = await db.get(req.params.userToDemote)
if (!user) {
req.flash('error', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_PROMOTE_DEMOTE_NOT_FOUND'))
return res.redirect(`/admin-settings/edit/${req.params.userToDemote}`)
}
if (!user.admin) {
req.flash('error', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_DEMOTE_NOT_ADMIN'))
return res.redirect(`/admin-settings/edit/${req.params.userToDemote}`)
}
user.admin = false
await db.put(user)
req.flash('success', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_DEMOTE_SUCCESS', user._id))
return res.redirect(`/admin-settings/edit/${req.params.userToDemote}`)
})
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 (doc.admin) {
req.flash('error', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_DELETE_FAIL_ADMIN'))
return res.redirect('/admin-settings')
}
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', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_DELETE_SUCCESS', req.params.userToRemove))
res.redirect('/admin-settings')
})
router.get('/clear-wishlists', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/')
res.render('admin-clear-wishlists')
})
router.post('/clear-wishlists', verifyAuth(), async (req, res) => {
if (!req.user.admin) return res.redirect('/')
const { rows } = await db.allDocs({ include_docs: true })
for (const row of rows) {
row.doc.wishlist = []
await db.put(row.doc)
}
req.flash('success', _CC.lang('ADMIN_SETTINGS_CLEARDB_SUCCESS'))
res.redirect('/admin-settings')
})
return router
}

18
src/routes/api/index.js Normal file
View file

@ -0,0 +1,18 @@
const verifyAuth = require('../../middlewares/verifyAuth')
const express = require('express')
module.exports = ({ db, config }) => {
const router = express.Router()
router.use(verifyAuth())
router.get('/', (req, res) => {
res.send({
api: true
})
})
router.use('/wishlist', require('./wishlist')({ db }))
return router
}

View file

@ -0,0 +1,40 @@
const express = require('express')
module.exports = ({ db }) => {
const router = express.Router()
router.get('/', (req, res) => {
res.send({
route: 'wishlist'
})
})
router.post('/:user/:id/move/:direction', async (req, res) => {
try {
if (req.user._id !== req.params.user) return res.json({ error: 'Not correct user' })
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.id) moveFromIndex = wishlist.indexOf(wish)
})
const moveToIndex = wishlist.findIndex(wish => {
return (wishlist.indexOf(wish) > moveFromIndex && wish.addedBy === req.user._id)
})
if (moveToIndex < 0 || moveToIndex > wishlist.length) return res.send({ error: 'Invalid move ' })
const original = wishlist[moveToIndex]
wishlist[moveToIndex] = wishlist[moveFromIndex]
wishlist[moveFromIndex] = original
if (req.params.direction === 'up') wishlist.reverse()
doc.wishlist = wishlist
await db.put(doc)
res.send({ error: false })
} catch (error) {
console.error(error)
res.send({ error: error.message })
}
})
return router
}

View file

@ -0,0 +1,44 @@
const bcrypt = require('bcrypt-nodejs')
const express = require('express')
module.exports = (db) => {
const router = express.Router()
router.get('/:code', async (req, res) => {
const row = (await db.allDocs({ include_docs: true }))
.rows
.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 }))
.rows
.find(({ doc }) => doc.signupToken === req.params.code)
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
doc.password = passwordHash
delete doc.signupToken
delete doc.expiry
await db.put(doc)
req.login({ _id: doc._id }, err => {
if (err) {
console.log(err)
req.flash('error', err.message)
return res.redirect('/')
}
req.flash('success', _CC.lang('CONFIRM_ACCOUNT_SUCCESS'))
res.redirect('/')
})
})
})
return router
}

68
src/routes/index.js Normal file
View file

@ -0,0 +1,68 @@
const publicRoute = require('../middlewares/publicRoute')
const express = require('express')
const path = require('path')
const fs = require('fs/promises')
module.exports = ({ db, config }) => {
async function ensurePfp (username) {
if (!config.pfp) return
const user = await db.get(username)
if (user.pfp) return
const { rows } = await db.allDocs({ include_docs: true })
const unfilteredPool = await fs.readdir('src/static/img/default-pfps')
const filteredPool = unfilteredPool.filter(file => !rows.find(row => row.doc.pfp === `${_CC.config.base}img/default-pfps/${file}`))
const pool = filteredPool.length ? filteredPool : unfilteredPool
user.pfp = `${_CC.config.base}img/default-pfps/${_CC._.sample(pool)}`
await db.put(user)
}
;(async () => {
const { rows } = await db.allDocs({ include_docs: true })
for (const row of rows) {
await ensurePfp(row.id)
}
})()
const router = express.Router()
router.use('/', express.static(path.join(__dirname, '../static')))
router.use(require('cookie-parser')())
router.get('/',
async (req, res, next) => {
const dbInfo = await db.info()
if (dbInfo.doc_count === 0) {
res.redirect('/setup')
} else {
next()
}
},
publicRoute(),
(req, res) => {
res.redirect('/wishlist')
}
)
router.use('/api', require('./api')({ db }))
router.use('/setup', require('./setup')({ db, ensurePfp }))
router.use('/login', require('./login')({ ensurePfp }))
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('/supported-sites', require('./supported-sites')())
router.use('/profile', require('./profile')({ db, config, ensurePfp }))
router.use('/admin-settings', require('./adminSettings')({ db, ensurePfp }))
router.use('/manifest.json', require('./manifest.json')({ config }))
return router
}

29
src/routes/login/index.js Normal file
View file

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

View file

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

View file

@ -0,0 +1,25 @@
const express = require('express')
module.exports = ({ config }) => {
const router = express.Router()
router.get('/', (req, res) => {
res.send({
name: config.siteTitle,
short_name: config.shortTitle,
background_color: 'white',
description: 'Sleek Wishlist App',
theme_color: '#dc5878',
start_url: '/',
display: 'standalone',
icons: [
{
sizes: '1280x1280',
src: `${_CC.config.base}img/logo.png`
}
]
})
})
return router
}

View file

@ -0,0 +1,63 @@
const verifyAuth = require('../../middlewares/verifyAuth')
const bcrypt = require('bcrypt-nodejs')
const express = require('express')
module.exports = ({ db, config, ensurePfp }) => {
const router = express.Router()
router.get('/', verifyAuth(), async (req, res) => {
await ensurePfp(req.user._id)
res.render('profile', { title: _CC.lang('PROFILE_TITLE', req.user._id) })
})
router.post('/pfp', verifyAuth(), async (req, res) => {
if (config.pfp) {
req.user.pfp = req.body.image
await db.put(req.user)
if (!req.user.pfp) await ensurePfp(req.user._id)
req.flash('success', _CC.lang('PROFILE_SAVE_PFP_SUCCESS'))
} else {
req.flash('error', _CC.lang('PROFILE_SAVE_PFP_DISABLED'))
}
res.redirect('/profile')
})
router.get('/password', verifyAuth(), async (req, res) => {
await ensurePfp(req.user._id)
res.render('profile-password', { title: _CC.lang('PROFILE_PASSWORD_TITLE', req.user._id) })
})
router.post('/password', verifyAuth(), (req, res) => {
if (!req.body.oldPassword) {
req.flash('error', _CC.lang('PROFILE_PASSWORD_REQUIRED_OLD'))
return res.redirect('/profile/password')
}
if (!req.body.newPassword) {
req.flash('error', _CC.lang('PROFILE_PASSWORD_REQUIRED_NEW'))
return res.redirect('/profile/password')
}
bcrypt.compare(req.body.oldPassword, req.user.password, (err, correct) => {
if (err) throw err
if (correct) {
bcrypt.hash(req.body.newPassword, null, null, (err, hash) => {
if (err) throw err
db.get(req.user._id)
.then(doc => {
doc.password = hash
db.put(doc)
.then(() => {
req.flash('success', _CC.lang('PROFILE_PASSWORD_SUCCESS'))
res.redirect('/profile/password')
})
.catch(err => { throw err })
})
.catch(err => { throw err })
})
} else {
req.flash('error', _CC.lang('PROFILE_PASSWORD_OLD_MISMATCH'))
res.redirect('/profile/password')
}
})
})
return router
}

View file

@ -0,0 +1,44 @@
const bcrypt = require('bcrypt-nodejs')
const express = require('express')
module.exports = (db) => {
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 }))
.rows
.find(({ doc }) => doc.pwToken === req.params.code)
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
doc.password = passwordHash
delete doc.pwToken
delete doc.pwExpiry
await db.put(doc)
req.login({ _id: doc._id }, err => {
if (err) {
console.log(err)
req.flash('error', err.message)
return res.redirect('/')
}
req.flash('success', _CC.lang('RESET_PASSWORD_SUCCESS'))
res.redirect('/')
})
})
})
return router
}

43
src/routes/setup/index.js Normal file
View file

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

View file

@ -0,0 +1,11 @@
const express = require('express')
module.exports = () => {
const router = express.Router()
router.get('/', async (req, res) => {
res.render('supported-sites', { title: _CC.lang('SUPPORTED_SITES_HEADER') })
})
return router
}

View file

@ -0,0 +1,297 @@
const createDOMPurify = require('dompurify')
const express = require('express')
const getProductName = require('get-product-name')
const { JSDOM } = require('jsdom')
const marked = require('marked')
const u64 = require('u64')
const config = require('../../config')
const publicRoute = require('../../middlewares/publicRoute')
const verifyAuth = require('../../middlewares/verifyAuth')
const window = new JSDOM('').window
const DOMPurify = createDOMPurify(window)
const totals = wishlist => {
let unpledged = 0
let pledged = 0
wishlist.forEach(wishItem => {
if (wishItem.pledgedBy) pledged += 1
else unpledged += 1
})
return { unpledged, pledged }
}
const ValidURL = (string) => { // Ty SO
try {
const url = new URL(string)
if (global._CC.config.wishlist.smile) {
if (url.hostname === 'www.amazon.com') url.hostname = 'smile.amazon.com'
}
if (url) return url
} catch (_) {
return false
}
}
module.exports = (db) => {
const router = express.Router()
router.get('/', publicRoute(), async (req, res) => {
const docs = await db.allDocs({ include_docs: true })
if (global._CC.config.wishlist.singleList) {
for (const row of docs.rows) {
if (row.doc.admin) return res.redirect(`/wishlist/${row.doc._id}`)
}
}
res.render('wishlists', { title: _CC.lang('WISHLISTS_TITLE'), users: docs.rows, totals })
})
router.get('/:user', publicRoute(), async (req, res) => {
try {
const dbUser = await db.get(req.params.user)
if (global._CC.config.wishlist.singleList) {
if (!dbUser.admin) {
const docs = await db.allDocs({ include_docs: true })
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)
for (const item of dbUser.wishlist) {
if (global._CC.config.wishlist.note.markdown) item.note = DOMPurify.sanitize(marked(item.note))
}
res.render('wishlist', {
title: _CC.lang('WISHLIST_TITLE', dbUser._id),
name: dbUser._id,
wishlist: [
...dbUser.wishlist.filter(item => item.addedBy === req.params.user),
...dbUser.wishlist.filter(item => item.addedBy !== req.params.user)
],
firstCanSee,
lastCanSee
})
} catch (error) {
req.flash('error', error)
return res.redirect('/wishlist')
}
})
router.post('/:user', verifyAuth(), async (req, res) => {
if (!req.body.itemUrlOrName) {
req.flash('error', _CC.lang('WISHLIST_URL_REQUIRED'))
return res.redirect(`/wishlist/${req.params.user}`)
}
const potentialUrl = req.body.itemUrlOrName.split(' ').pop()
const url = ValidURL(potentialUrl)
const item = {}
let productData
try {
if (url) productData = await getProductName(url, config.proxyServer)
} catch (err) {
req.flash('error', err.toString())
}
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 || req.body.suggest ? 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)
try {
await db.put(doc)
} catch {
req.flash('error', _CC.lang('WISHLIST_CONFLICT'))
return res.redirect(`/wishlist/${req.params.user}`)
}
req.flash(
'success',
(
req.user._id === req.params.user
? 'Added item to wishlist'
: `Pleged item for ${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 })
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', _CC.lang('WISHLIST_PLEDGE_DUPLICATE'))
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', _CC.lang('WISHLIST_PLEDGE_SUCCESS'))
return res.redirect(`/wishlist/${req.params.user}`)
}
}
}
})
router.post('/:user/unpledge/:itemId', verifyAuth(), async (req, res) => {
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', _CC.lang('WISHLIST_UNPLEDGE_GUARD'))
return res.redirect(`/wishlist/${req.params.user}`)
}
docs.rows[i].doc.wishlist[j].pledgedBy = undefined
await db.put(docs.rows[i].doc)
req.flash('success', _CC.lang('WISHLIST_UNPLEDGE_SUCCESS'))
return res.redirect(`/wishlist/${req.params.user}`)
}
}
}
req.flash('error', _CC.lang('WISHLIST_UNPLEDGE_MISSING'))
return res.redirect(`/wishlist/${req.params.user}`)
})
router.post('/:user/remove/:itemId', verifyAuth(), async (req, res) => {
const doc = await db.get(req.params.user)
for (let i = 0; i < doc.wishlist.length; i++) {
if (doc.wishlist[i].id === req.params.itemId) {
if (req.user._id !== req.params.user && doc.wishlist[i].addedBy !== req.user._id) {
req.flash('error', _CC.lang('WISHLIST_REMOVE_GUARD'))
return res.redirect(`/wishlist/${req.params.user}`)
}
doc.wishlist.splice(i, 1)
await db.put(doc)
req.flash('success', _CC.lang('WISHLIST_REMOVE_SUCCESS'))
return res.redirect(`/wishlist/${req.params.user}`)
}
}
req.flash('error', _CC.lang('WISHLIST_REMOVE_MISSING'))
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', _CC.lang('WISHLIST_MOVE_GUARD'))
return res.redirect(`/wishlist/${req.params.user}`)
}
const doc = await db.get(req.user._id)
let wishlist = doc.wishlist
if (req.params.direction === 'top') {
const item = wishlist.find(item => item.id === req.params.itemId)
wishlist = wishlist.filter(item => item.id !== req.params.itemId)
wishlist.unshift(item)
} else {
if (req.params.direction === 'up') wishlist.reverse()
let moveFromIndex
wishlist.forEach(wish => {
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', _CC.lang('WISHLIST_MOVE_INVALID'))
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', _CC.lang('WISHLIST_MOVE_SUCCESS'))
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 item = doc.wishlist.find(item => item.id === req.params.id)
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++) {
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', _CC.lang('NOTE_GUARD'))
return res.redirect(`/wishlist/${req.params.user}`)
}
for (const type of [
'name', 'note', 'url', 'price', 'image'
]) {
if (!Object.prototype.hasOwnProperty.call(req.body, type)) {
req.flash('error', _CC.lang('NOTE_MISSING_PROP', type))
return res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`)
}
wishlistItem[type] = req.body[type]
}
wishlist[i] = wishlistItem
}
doc.wishlist = wishlist
await db.put(doc)
req.flash('success', _CC.lang('NOTE_SUCCESS'))
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++) {
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', _CC.lang('WISHLIST_REFRESH_GUARD'))
return res.redirect(`/wishlist/${req.params.user}`)
}
if (!wishlistItem.url) {
req.flash('error', _CC.lang('WISHLIST_REFRESH_NO_URL'))
return res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`)
}
const productData = await getProductName(wishlistItem.url)
for (const field of ['name', 'price', 'image']) {
if (productData[field]) wishlistItem[field] = productData[field]
}
wishlist[i] = wishlistItem
}
doc.wishlist = wishlist
await db.put(doc)
req.flash('success', _CC.lang('WISHLIST_REFRESH_SUCCESS'))
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++) {
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', _CC.lang('NOTE_REMOVE_GUARD'))
return res.redirect(`/wishlist/${req.params.user}`)
}
if (wishlistItem.note) {
wishlistItem.note = undefined
wishlist[i] = wishlistItem
} else {
req.flash('error', _CC.lang('NOTE_REMOVE_MISSING'))
return res.redirect(`/wishlist/${req.params.user}`)
}
}
doc.wishlist = wishlist
await db.put(doc)
req.flash('success', _CC.lang('NOTE_REMOVE_SUCCESS'))
return res.redirect(`/wishlist/${req.params.user}`)
})
return router
}