refactor wishlist manipulation code (#66)
This commit is contained in:
parent
0d7d73744c
commit
0b26ec0ce4
8 changed files with 521 additions and 361 deletions
|
@ -15,6 +15,7 @@ const path = require('path')
|
|||
_CC._ = require('lodash')
|
||||
_CC.moment = require('moment/min/moment-with-locales')
|
||||
|
||||
const { WishlistManager } = require('./structures/WishlistManager')
|
||||
const config = require('./config')
|
||||
_CC.config = config
|
||||
|
||||
|
@ -55,6 +56,9 @@ app.set('base', config.base)
|
|||
app.set('trust proxy', config.trustProxy)
|
||||
|
||||
const db = new PouchDB('users')
|
||||
_CC.usersDb = db
|
||||
|
||||
_CC.wishlistManager = new WishlistManager()
|
||||
|
||||
passport.use('local', new LocalStrategy(
|
||||
(username, password, done) => {
|
||||
|
|
|
@ -140,10 +140,13 @@ module.exports.strings = {
|
|||
WISHLIST_ADD: 'Add item to wishlist',
|
||||
WISHLIST_ADDED_BY_USER: addedBy => `Added by: ${addedBy}`,
|
||||
WISHLIST_ADDED_BY: 'Added By',
|
||||
WISHLIST_ADDED_ITEM_TO_OWN_WISHLIST: 'Added item to wishlist.',
|
||||
WISHLIST_CONFLICT: 'Items are being added too quickly. Please try again.',
|
||||
WISHLIST_DELETE: 'Delete',
|
||||
WISHLIST_EDIT_ITEM: 'Edit Item',
|
||||
WISHLIST_FETCH_FAIL: 'Failed to fetch the wishlist -- does the user exist?',
|
||||
WISHLIST_IMAGE: 'Image',
|
||||
WISHLIST_ITEM_MISSING: 'Failed to find item',
|
||||
WISHLIST_MOVE_DOWN: 'Move Down',
|
||||
WISHLIST_MOVE_GUARD: 'Not correct user',
|
||||
WISHLIST_MOVE_INVALID: 'Invalid move',
|
||||
|
@ -152,6 +155,7 @@ module.exports.strings = {
|
|||
WISHLIST_MOVE_ITEM_UP: 'Move Item Up',
|
||||
WISHLIST_MOVE_SUCCESS: 'Successfully moved item!',
|
||||
WISHLIST_MOVE_TOP: 'Move Top',
|
||||
WISHLIST_MOVE_UNKNOWN_DIRECTION: 'Unknown direction',
|
||||
WISHLIST_MOVE_UP: 'Move Up',
|
||||
WISHLIST_NAME: 'Name',
|
||||
WISHLIST_NOTE: 'Note',
|
||||
|
@ -162,17 +166,16 @@ module.exports.strings = {
|
|||
WISHLIST_PLEDGE: 'Pledge',
|
||||
WISHLIST_PLEDGED: pledgedBy => `Pledged for by ${pledgedBy}`,
|
||||
WISHLIST_PLEDGED_GUEST: 'Pledged for by a guest user',
|
||||
WISHLIST_PLEDGED_ITEM_FOR_USER: user => `Pledged item for ${user}.`,
|
||||
WISHLIST_PRICE: 'Price',
|
||||
WISHLIST_REFRESH_GUARD: 'Invalid user',
|
||||
WISHLIST_REFRESH_NO_URL: 'Item has no URL.',
|
||||
WISHLIST_REFRESH_SUCCESS: 'Successfully refreshed data!',
|
||||
WISHLIST_REMOVE_GUARD: 'Not correct user',
|
||||
WISHLIST_REMOVE_MISSING: 'Failed to find item',
|
||||
WISHLIST_REMOVE_SUCCESS: 'Successfully removed from wishlist',
|
||||
WISHLIST_SUGGEST: 'Suggest item',
|
||||
WISHLIST_TITLE: name => `${_CC.config.siteTitle} - Wishlist - ${name}`,
|
||||
WISHLIST_UNPLEDGE_GUARD: 'You did not pledge for this', // should never happen unless someone makes their own http requests
|
||||
WISHLIST_UNPLEDGE_MISSING: 'Failed to find item',
|
||||
WISHLIST_UNPLEDGE_SUCCESS: 'Successfully unpledged for item!',
|
||||
WISHLIST_UNPLEDGE: 'Unpledge',
|
||||
WISHLIST_URL_LABEL: 'Item URL or Name (<a href="/supported-sites">Supported Sites</a>)',
|
||||
|
|
|
@ -31,17 +31,17 @@ module.exports = ({ db, ensurePfp }) => {
|
|||
return db
|
||||
.allDocs({ include_docs: true })
|
||||
.then((docs) => {
|
||||
res.render("adminSettings", {
|
||||
res.render('adminSettings', {
|
||||
add_user_error: _CC.lang(
|
||||
"ADMIN_SETTINGS_USERS_ADD_ERROR_USERNAME_EMPTY"
|
||||
'ADMIN_SETTINGS_USERS_ADD_ERROR_USERNAME_EMPTY'
|
||||
),
|
||||
title: _CC.lang("ADMIN_SETTINGS_HEADER"),
|
||||
users: docs.rows,
|
||||
});
|
||||
title: _CC.lang('ADMIN_SETTINGS_HEADER'),
|
||||
users: docs.rows
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
await db.put({
|
||||
|
@ -123,6 +123,8 @@ module.exports = ({ db, ensurePfp }) => {
|
|||
await db.bulkDocs(usersBulk)
|
||||
await db.remove(await db.get(oldName))
|
||||
|
||||
await _CC.wishlistManager.clearCache()
|
||||
|
||||
req.flash('success', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_RENAMED_USER'))
|
||||
return res.redirect(`/wishlist/${newName}`)
|
||||
} catch (error) {
|
||||
|
@ -193,24 +195,33 @@ module.exports = ({ db, ensurePfp }) => {
|
|||
})
|
||||
|
||||
router.post('/edit/remove/:userToRemove', verifyAuth(), async (req, res) => {
|
||||
try {
|
||||
if (!req.user.admin) return res.redirect('/')
|
||||
const doc = await db.get(req.params.userToRemove)
|
||||
if (doc.admin) {
|
||||
|
||||
const userToRemove = await db.get(req.params.userToRemove)
|
||||
if (userToRemove.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)
|
||||
await db.remove(userToRemove)
|
||||
|
||||
const { rows } = await db.allDocs()
|
||||
for (const row of rows) {
|
||||
const wishlist = await _CC.wishlistManager.get(row.id)
|
||||
for (const item of wishlist.items) {
|
||||
if (item.addedBy === userToRemove._id) {
|
||||
await wishlist.remove(item.id)
|
||||
} else if (item.pledgedBy === userToRemove._id) {
|
||||
await wishlist.unpledge(item.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req.flash('success', _CC.lang('ADMIN_SETTINGS_USERS_EDIT_DELETE_SUCCESS', req.params.userToRemove))
|
||||
} catch (error) {
|
||||
req.flash('error', `${error}`)
|
||||
}
|
||||
|
||||
res.redirect('/admin-settings')
|
||||
})
|
||||
|
||||
|
@ -221,11 +232,17 @@ module.exports = ({ db, ensurePfp }) => {
|
|||
|
||||
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)
|
||||
|
||||
const usersBulk = []
|
||||
const { rows: users } = await db.allDocs({ include_docs: true })
|
||||
for (const { doc: user } of users) {
|
||||
user.wishlist = []
|
||||
usersBulk.push(user)
|
||||
}
|
||||
await db.bulkDocs(usersBulk)
|
||||
|
||||
await _CC.wishlistManager.clearCache()
|
||||
|
||||
req.flash('success', _CC.lang('ADMIN_SETTINGS_CLEARDB_SUCCESS'))
|
||||
res.redirect('/admin-settings')
|
||||
})
|
||||
|
|
|
@ -9,31 +9,27 @@ module.exports = ({ db }) => {
|
|||
})
|
||||
})
|
||||
|
||||
router.post('/:user/:id/move/:direction', async (req, res) => {
|
||||
router.post('/:user/:itemId/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 })
|
||||
if (req.user._id !== req.params.user) {
|
||||
throw new Error(_CC.lang('WISHLIST_MOVE_GUARD'))
|
||||
}
|
||||
|
||||
const wishlist = await _CC.wishlistManager.get(req.params.user)
|
||||
if (req.params.direction === 'top') {
|
||||
await wishlist.moveTop(req.params.itemId)
|
||||
} else if (req.params.direction === 'up') {
|
||||
await wishlist.move(req.params.itemId, -1)
|
||||
} else if (req.params.direction === 'down') {
|
||||
await wishlist.move(req.params.itemId, 1)
|
||||
} else {
|
||||
throw new Error(_CC.lang('WISHLIST_MOVE_UNKNOWN_DIRECTION'))
|
||||
}
|
||||
} catch (error) {
|
||||
return res.send({ error: error.message })
|
||||
}
|
||||
|
||||
res.send({ error: false })
|
||||
})
|
||||
|
||||
return router
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
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')
|
||||
|
||||
|
@ -22,21 +19,11 @@ const totals = wishlist => {
|
|||
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()
|
||||
|
||||
const wishlistManager = _CC.wishlistManager
|
||||
|
||||
router.get('/', publicRoute(), async (req, res) => {
|
||||
const docs = await db.allDocs({ include_docs: true })
|
||||
if (global._CC.config.wishlist.singleList) {
|
||||
|
@ -47,10 +34,9 @@ module.exports = (db) => {
|
|||
res.render('wishlists', { title: _CC.lang('WISHLISTS_TITLE'), users: docs.rows, totals })
|
||||
})
|
||||
|
||||
router.get('/:user', publicRoute(), async (req, res) => {
|
||||
try {
|
||||
async function redirectIfSingleUserMode (req, res, next) {
|
||||
const dbUser = await db.get(req.params.user)
|
||||
if (global._CC.config.wishlist.singleList) {
|
||||
if (_CC.config.wishlist.singleList) {
|
||||
if (!dbUser.admin) {
|
||||
const docs = await db.allDocs({ include_docs: true })
|
||||
for (const row of docs.rows) {
|
||||
|
@ -58,22 +44,21 @@ module.exports = (db) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
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))
|
||||
next()
|
||||
}
|
||||
|
||||
router.get('/:user', publicRoute(), redirectIfSingleUserMode, async (req, res) => {
|
||||
try {
|
||||
const wishlist = await wishlistManager.get(req.params.user)
|
||||
const items = await wishlist.itemsVisibleToUser(req.user._id)
|
||||
|
||||
for (const item of items) {
|
||||
if (_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
|
||||
title: _CC.lang('WISHLIST_TITLE', wishlist.username),
|
||||
name: wishlist.username,
|
||||
items
|
||||
})
|
||||
} catch (error) {
|
||||
req.flash('error', error)
|
||||
|
@ -82,216 +67,168 @@ module.exports = (db) => {
|
|||
})
|
||||
|
||||
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())
|
||||
const wishlist = await wishlistManager.get(req.params.user)
|
||||
|
||||
const { nonFatalErrors } = await wishlist.add({
|
||||
itemUrlOrName: req.body.itemUrlOrName,
|
||||
suggest: req.body.suggest,
|
||||
note: req.body.note,
|
||||
addedBy: req.user._id
|
||||
})
|
||||
|
||||
for (const error of nonFatalErrors) {
|
||||
req.flash('error', error)
|
||||
}
|
||||
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.flash('success',
|
||||
req.user._id === req.params.user
|
||||
? 'Added item to wishlist'
|
||||
: `Pleged item for ${req.params.user}`
|
||||
)
|
||||
? _CC.lang('WISHLIST_ADDED_ITEM_TO_OWN_WISHLIST')
|
||||
: _CC.lang('WISHLIST_PLEDGED_ITEM_FOR_USER', req.params.user)
|
||||
)
|
||||
} catch (error) {
|
||||
req.flash('error', `${error}`)
|
||||
}
|
||||
|
||||
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}`)
|
||||
}
|
||||
try {
|
||||
const wishlist = await wishlistManager.get(req.params.user)
|
||||
const item = await wishlist.get(req.params.itemId)
|
||||
|
||||
if (item.pledgedBy !== undefined) {
|
||||
throw new Error(_CC.lang('WISHLIST_PLEDGE_DUPLICATE'))
|
||||
}
|
||||
|
||||
await wishlist.pledge(item.id, req.user._id)
|
||||
} catch (error) {
|
||||
req.flash('error', `${error}`)
|
||||
}
|
||||
|
||||
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}`)
|
||||
try {
|
||||
const wishlist = await wishlistManager.get(req.params.user)
|
||||
const item = await wishlist.get(req.params.itemId)
|
||||
|
||||
const pledgedByUser = item.pledgedBy === req.user._id
|
||||
if (!pledgedByUser) {
|
||||
throw new Error(_CC.lang('WISHLIST_UNPLEDGE_GUARD'))
|
||||
}
|
||||
docs.rows[i].doc.wishlist[j].pledgedBy = undefined
|
||||
await db.put(docs.rows[i].doc)
|
||||
|
||||
await wishlist.unpledge(item.id)
|
||||
|
||||
req.flash('success', _CC.lang('WISHLIST_UNPLEDGE_SUCCESS'))
|
||||
return res.redirect(`/wishlist/${req.params.user}`)
|
||||
} catch (error) {
|
||||
req.flash('error', `${error}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
req.flash('error', _CC.lang('WISHLIST_UNPLEDGE_MISSING'))
|
||||
return res.redirect(`/wishlist/${req.params.user}`)
|
||||
|
||||
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}`)
|
||||
try {
|
||||
const wishlist = await wishlistManager.get(req.params.user)
|
||||
const item = wishlist.get(req.params.itemId)
|
||||
|
||||
const isOwnWishlist = req.user._id === wishlist.username
|
||||
const addedByUser = item.addedBy === req.user._id
|
||||
if (!isOwnWishlist && !addedByUser) {
|
||||
throw new Error(_CC.lang('WISHLIST_REMOVE_GUARD'))
|
||||
}
|
||||
|
||||
doc.wishlist.splice(i, 1)
|
||||
await db.put(doc)
|
||||
await wishlist.remove(item.id)
|
||||
|
||||
req.flash('success', _CC.lang('WISHLIST_REMOVE_SUCCESS'))
|
||||
return res.redirect(`/wishlist/${req.params.user}`)
|
||||
} catch (error) {
|
||||
req.flash('error', `${error}`)
|
||||
}
|
||||
}
|
||||
req.flash('error', _CC.lang('WISHLIST_REMOVE_MISSING'))
|
||||
return res.redirect(`/wishlist/${req.params.user}`)
|
||||
|
||||
res.redirect(`/wishlist/${req.params.user}`)
|
||||
})
|
||||
|
||||
router.post('/:user/move/:direction/:itemId', verifyAuth(), async (req, res) => {
|
||||
try {
|
||||
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()
|
||||
throw new Error(_CC.lang('WISHLIST_MOVE_GUARD'))
|
||||
}
|
||||
|
||||
const wishlist = await wishlistManager.get(req.params.user)
|
||||
|
||||
if (req.params.direction === 'top') {
|
||||
await wishlist.moveTop(req.params.itemId)
|
||||
} else if (req.params.direction === 'up') {
|
||||
await wishlist.move(req.params.itemId, -1)
|
||||
} else if (req.params.direction === 'down') {
|
||||
await wishlist.move(req.params.itemId, 1)
|
||||
} else {
|
||||
throw new Error(_CC.lang('WISHLIST_MOVE_UNKNOWN_DIRECTION'))
|
||||
}
|
||||
|
||||
doc.wishlist = wishlist
|
||||
await db.put(doc)
|
||||
req.flash('success', _CC.lang('WISHLIST_MOVE_SUCCESS'))
|
||||
return res.redirect(`/wishlist/${req.params.user}`)
|
||||
} catch (error) {
|
||||
req.flash('error', `${error}`)
|
||||
}
|
||||
|
||||
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)
|
||||
try {
|
||||
const wishlist = await wishlistManager.get(req.params.user)
|
||||
const item = await wishlist.get(req.params.id)
|
||||
res.render('note', { item })
|
||||
} catch (error) {
|
||||
req.flash('error', `${error}`)
|
||||
res.redirect(`/wishlist/${req.params.user}`)
|
||||
}
|
||||
})
|
||||
|
||||
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}`)
|
||||
try {
|
||||
const wishlist = await wishlistManager.get(req.params.user)
|
||||
const item = await wishlist.get(req.params.id)
|
||||
|
||||
const isOwnWishlist = req.user._id === req.params.user
|
||||
const addedByUser = req.user._id === item.addedBy
|
||||
if (!isOwnWishlist && !addedByUser) {
|
||||
throw new Error(_CC.lang('NOTE_GUARD'))
|
||||
}
|
||||
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)
|
||||
|
||||
await wishlist.setItemData(req.params.id, req.body)
|
||||
|
||||
req.flash('success', _CC.lang('NOTE_SUCCESS'))
|
||||
return res.redirect(`/wishlist/${req.params.user}`)
|
||||
res.redirect(`/wishlist/${req.params.user}`)
|
||||
} catch (error) {
|
||||
req.flash('error', `${error}`)
|
||||
res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`)
|
||||
}
|
||||
})
|
||||
|
||||
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}`)
|
||||
try {
|
||||
const wishlist = await wishlistManager.get(req.params.user)
|
||||
const item = await wishlist.get(req.params.id)
|
||||
|
||||
const isOwnWishlist = req.user._id === req.params.user
|
||||
const addedByUser = req.user._id === item.addedBy
|
||||
if (!isOwnWishlist && !addedByUser) {
|
||||
throw new Error(_CC.lang('WISHLIST_REFRESH_GUARD'))
|
||||
}
|
||||
|
||||
if (!wishlistItem.url) {
|
||||
req.flash('error', _CC.lang('WISHLIST_REFRESH_NO_URL'))
|
||||
return res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`)
|
||||
}
|
||||
await wishlist.refreshItemData(item.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}`)
|
||||
} catch (error) {
|
||||
req.flash('error', `${error}`)
|
||||
}
|
||||
|
||||
res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`)
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
|
182
src/structures/Wishlist.js
Normal file
182
src/structures/Wishlist.js
Normal file
|
@ -0,0 +1,182 @@
|
|||
const getProductData = require('get-product-name')
|
||||
const u64 = require('u64')
|
||||
|
||||
class Wishlist {
|
||||
static async new (username) {
|
||||
const instance = new this({ username })
|
||||
await instance.fetch()
|
||||
return instance
|
||||
}
|
||||
|
||||
constructor (opts) {
|
||||
this.username = opts.username
|
||||
}
|
||||
|
||||
async fetch () {
|
||||
try {
|
||||
this.doc = await _CC.usersDb.get(this.username)
|
||||
} catch {
|
||||
throw new Error(_CC.lang('WISHLIST_FETCH_FAIL'))
|
||||
}
|
||||
this.items = this.doc.wishlist
|
||||
}
|
||||
|
||||
async save () {
|
||||
try {
|
||||
const { rev } = await _CC.usersDb.put(this.doc)
|
||||
this.doc._rev = rev
|
||||
} catch {
|
||||
await this.fetch()
|
||||
throw new Error(_CC.lang('WISHLIST_CONFLICT'))
|
||||
}
|
||||
}
|
||||
|
||||
async get (id) {
|
||||
const item = this.items.find(item => item.id === id)
|
||||
if (!item) throw new Error(_CC.lang('WISHLIST_ITEM_MISSING'))
|
||||
return item
|
||||
}
|
||||
|
||||
async itemsVisibleToUser (username) {
|
||||
const addedBySelfAtTop = async (items) => {
|
||||
return [
|
||||
...items.filter(item => item.addedBy === this.username),
|
||||
...items.filter(item => item.addedBy !== this.username)
|
||||
]
|
||||
}
|
||||
|
||||
if (this.username === username) {
|
||||
return this.items
|
||||
.filter(item => item.addedBy === username)
|
||||
}
|
||||
|
||||
return addedBySelfAtTop(this.items)
|
||||
}
|
||||
|
||||
async add ({ itemUrlOrName, suggest, note, addedBy }) {
|
||||
if (!itemUrlOrName) {
|
||||
throw new Error(_CC.lang('WISHLIST_URL_REQUIRED'))
|
||||
}
|
||||
|
||||
const item = {}
|
||||
|
||||
const nonFatalErrors = []
|
||||
|
||||
const potentialUrl = itemUrlOrName.split(' ').pop()
|
||||
const url = parseURL(potentialUrl)
|
||||
let productData
|
||||
try {
|
||||
if (url) productData = await getProductData(url, _CC.config.proxyServer)
|
||||
} catch (err) {
|
||||
nonFatalErrors.push(err.toString())
|
||||
}
|
||||
|
||||
item.id = u64.encode(new Date().getTime().toString())
|
||||
item.name = (productData ? productData.name : '')
|
||||
item.price = productData?.price
|
||||
item.image = productData?.image
|
||||
item.addedBy = addedBy
|
||||
item.pledgedBy = (addedBy === this.username || suggest ? undefined : addedBy)
|
||||
item.note = note
|
||||
|
||||
if (url) item.url = url
|
||||
if (!url) item.name = itemUrlOrName
|
||||
|
||||
this.items.push(item)
|
||||
await this.save()
|
||||
|
||||
return { nonFatalErrors }
|
||||
}
|
||||
|
||||
async remove (id) {
|
||||
const index = this.items.findIndex(item => item.id === id)
|
||||
if (index === -1) throw new Error(_CC.lang('WISHLIST_ITEM_MISSING'))
|
||||
this.items.splice(index, 1)
|
||||
await this.save()
|
||||
}
|
||||
|
||||
async pledge (id, user) {
|
||||
const item = await this.get(id)
|
||||
item.pledgedBy = user
|
||||
await this.save()
|
||||
}
|
||||
|
||||
async unpledge (id) {
|
||||
const item = await this.get(id)
|
||||
item.pledgedBy = undefined
|
||||
await this.save()
|
||||
}
|
||||
|
||||
async move (id, places) {
|
||||
if (places === 0) throw new Error('places should never be 0')
|
||||
|
||||
const index = this.items.findIndex(item => item.id === id)
|
||||
if (index === -1) throw new Error(_CC.lang('WISHLIST_ITEM_MISSING'))
|
||||
|
||||
while (this.items[index + places] && this.items[index + places].addedBy !== this.username) {
|
||||
if (places < 0) {
|
||||
places--
|
||||
} else {
|
||||
places++
|
||||
}
|
||||
}
|
||||
if (index < 0 || index >= this.items.length || index + places < 0 || index + places >= this.items.length) {
|
||||
throw new Error(_CC.lang('WISHLIST_MOVE_INVALID'))
|
||||
}
|
||||
|
||||
const item = this.items.splice(index, 1)[0]
|
||||
this.items.splice(index + places, 0, item)
|
||||
await this.save()
|
||||
}
|
||||
|
||||
async moveTop (id) {
|
||||
const index = this.items.findIndex(item => item.id === id)
|
||||
if (index === -1) throw new Error(_CC.lang('WISHLIST_ITEM_MISSING'))
|
||||
|
||||
const item = this.items.splice(index, 1)[0]
|
||||
this.items.unshift(item)
|
||||
await this.save()
|
||||
}
|
||||
|
||||
async setItemData (id, data) {
|
||||
const item = await this.get(id)
|
||||
|
||||
for (const key of [
|
||||
'name', 'note', 'url', 'price', 'image'
|
||||
]) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
throw new Error(_CC.lang('NOTE_MISSING_PROP', key))
|
||||
}
|
||||
item[key] = data[key]
|
||||
}
|
||||
|
||||
await this.save()
|
||||
}
|
||||
|
||||
async refreshItemData (id) {
|
||||
const item = await this.get(id)
|
||||
|
||||
if (!item.url) {
|
||||
throw new Error(_CC.lang('WISHLIST_REFRESH_NO_URL'))
|
||||
}
|
||||
|
||||
const productData = await getProductData(item.url)
|
||||
for (const key of ['name', 'price', 'image']) {
|
||||
if (productData[key]) item[key] = productData[key]
|
||||
}
|
||||
|
||||
await this.save()
|
||||
}
|
||||
}
|
||||
|
||||
function parseURL (string) {
|
||||
try {
|
||||
const url = new URL(string)
|
||||
if (_CC.config.wishlist.smile) {
|
||||
if (url.hostname === 'www.amazon.com') url.hostname = 'smile.amazon.com'
|
||||
}
|
||||
if (url) return url
|
||||
} catch {}
|
||||
}
|
||||
|
||||
module.exports = { Wishlist }
|
22
src/structures/WishlistManager.js
Normal file
22
src/structures/WishlistManager.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
const { Wishlist } = require('./Wishlist')
|
||||
|
||||
class WishlistManager {
|
||||
constructor () {
|
||||
this.wishlistsCache = new Map()
|
||||
}
|
||||
|
||||
async get (username) {
|
||||
const cached = this.wishlistsCache.get(username)
|
||||
if (cached) return cached
|
||||
|
||||
const wishlist = await Wishlist.new(username)
|
||||
this.wishlistsCache.set(username, wishlist)
|
||||
return wishlist
|
||||
}
|
||||
|
||||
async clearCache () {
|
||||
this.wishlistsCache = new Map()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { WishlistManager }
|
|
@ -32,8 +32,7 @@ block content
|
|||
th= lang('WISHLIST_PLEDGE')
|
||||
th= lang('WISHLIST_DELETE')
|
||||
tbody
|
||||
each item, index in wishlist
|
||||
if req.user._id === item.addedBy || req.params.user !== req.user._id
|
||||
each item, index in items
|
||||
tr(id=item.id)
|
||||
td.rank= index + 1
|
||||
td
|
||||
|
@ -74,7 +73,7 @@ block content
|
|||
button.button.is-text(
|
||||
type='submit',
|
||||
style='text-decoration: none;',
|
||||
disabled=index === firstCanSee
|
||||
disabled=index === 0
|
||||
)
|
||||
span.icon
|
||||
i.fas.fa-angle-double-up
|
||||
|
@ -85,7 +84,7 @@ block content
|
|||
button.button.is-text(
|
||||
type='submit',
|
||||
style='text-decoration: none;',
|
||||
disabled=index === firstCanSee
|
||||
disabled=index === 0
|
||||
)
|
||||
span.icon
|
||||
i.fas.fa-arrow-up
|
||||
|
@ -96,7 +95,7 @@ block content
|
|||
button.button.is-text(
|
||||
type='submit',
|
||||
style='text-decoration: none;',
|
||||
disabled=index === lastCanSee
|
||||
disabled=index === items.length - 1
|
||||
)
|
||||
span.icon
|
||||
i.fas.fa-arrow-down
|
||||
|
@ -133,7 +132,7 @@ block content
|
|||
i.fas.fa-trash
|
||||
|
||||
else
|
||||
each item, index in wishlist
|
||||
each item, index in items
|
||||
if req.user._id === item.addedBy || req.params.user !== req.user._id
|
||||
.box
|
||||
if item.price
|
||||
|
@ -187,12 +186,12 @@ block content
|
|||
.field.inline
|
||||
.control.inline
|
||||
input.inline.button(type='submit', value=lang('WISHLIST_EDIT_ITEM'))
|
||||
if index !== firstCanSee && req.user._id === req.params.user
|
||||
if index !== 0 && req.user._id === req.params.user
|
||||
form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`)
|
||||
.field.inline
|
||||
.control.inline
|
||||
input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_UP'))
|
||||
if index !== lastCanSee && req.user._id === req.params.user
|
||||
if index !== items.length - 1 && req.user._id === req.params.user
|
||||
form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/down/${item.id}`)
|
||||
.field.inline
|
||||
.control.inline
|
||||
|
@ -226,7 +225,7 @@ block content
|
|||
block print
|
||||
h1 #{req.params.user}'s Wishlist
|
||||
.print-gifts
|
||||
each item, index in wishlist.filter(item => item.addedBy === req.params.user)
|
||||
each item, index in items.filter(item => item.addedBy === req.params.user)
|
||||
.print-gift(style='page-break-inside: avoid;')
|
||||
if index > 0
|
||||
hr(style='margin-top: .5em; margin-bottom: .5em; background-color: black;')
|
||||
|
|
Loading…
Reference in a new issue