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:
parent
363146c331
commit
0d7d73744c
1569 changed files with 25 additions and 1159 deletions
234
src/routes/adminSettings/index.js
Normal file
234
src/routes/adminSettings/index.js
Normal 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
18
src/routes/api/index.js
Normal 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
|
||||
}
|
40
src/routes/api/wishlist/index.js
Normal file
40
src/routes/api/wishlist/index.js
Normal 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
|
||||
}
|
44
src/routes/confirm-account/index.js
Normal file
44
src/routes/confirm-account/index.js
Normal 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
68
src/routes/index.js
Normal 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
29
src/routes/login/index.js
Normal 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
|
||||
}
|
14
src/routes/logout/index.js
Normal file
14
src/routes/logout/index.js
Normal 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
|
||||
}
|
25
src/routes/manifest.json/index.js
Normal file
25
src/routes/manifest.json/index.js
Normal 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
|
||||
}
|
63
src/routes/profile/index.js
Normal file
63
src/routes/profile/index.js
Normal 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
|
||||
}
|
44
src/routes/resetpw/index.js
Normal file
44
src/routes/resetpw/index.js
Normal 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
43
src/routes/setup/index.js
Normal 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
|
||||
}
|
11
src/routes/supported-sites/index.js
Normal file
11
src/routes/supported-sites/index.js
Normal 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
|
||||
}
|
297
src/routes/wishlist/index.js
Normal file
297
src/routes/wishlist/index.js
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue