add eslint, fix bugs found
This commit is contained in:
parent
fc2c3a7114
commit
12558d3384
28 changed files with 1774 additions and 745 deletions
2
.eslintignore
Normal file
2
.eslintignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
static/libraries
|
||||||
|
patched-express-response.js
|
18
.eslintrc.js
Normal file
18
.eslintrc.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
commonjs: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'standard'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 12
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
_CC: 'readonly'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
require('dotenv').config();
|
require('dotenv').config()
|
||||||
|
|
||||||
const yesNo = require('yes-no');
|
const yesNo = require('yes-no')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
dbPrefix: process.env.DB_PREFIX || 'dbs/',
|
dbPrefix: process.env.DB_PREFIX || 'dbs/',
|
||||||
|
@ -16,4 +16,4 @@ module.exports = {
|
||||||
wishlist: require('./wishlist'),
|
wishlist: require('./wishlist'),
|
||||||
base: (process.env.ROOT_PATH || '/').endsWith('/') ? (process.env.ROOT_PATH || '/') : `${process.env.ROOT_PATH}/`,
|
base: (process.env.ROOT_PATH || '/').endsWith('/') ? (process.env.ROOT_PATH || '/') : `${process.env.ROOT_PATH}/`,
|
||||||
trustProxy: process.env.TRUST_PROXY === 'true' ? true : process.env.TRUST_PROXY || 'loopback'
|
trustProxy: process.env.TRUST_PROXY === 'true' ? true : process.env.TRUST_PROXY || 'loopback'
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
const { nanoid } = require('nanoid')
|
const { nanoid } = require('nanoid')
|
||||||
const path = require('path');
|
const path = require('path')
|
||||||
const fs = require('fs');
|
const fs = require('fs')
|
||||||
|
|
||||||
const secretFilePath = path.join((process.env.SECRET_DIRNAME ? process.env.SECRET_DIRNAME : __dirname), 'secret.txt');
|
const secretFilePath = path.join((process.env.SECRET_DIRNAME ? process.env.SECRET_DIRNAME : __dirname), 'secret.txt')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
module.exports = fs.readFileSync(secretFilePath).toString();
|
module.exports = fs.readFileSync(secretFilePath).toString()
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
const secret = nanoid(128);
|
const secret = nanoid(128)
|
||||||
fs.writeFileSync(secretFilePath, secret);
|
fs.writeFileSync(secretFilePath, secret)
|
||||||
module.exports = secret;
|
module.exports = secret
|
||||||
}
|
}
|
74
index.js
74
index.js
|
@ -1,14 +1,13 @@
|
||||||
global._CC = { require }
|
global._CC = { require }
|
||||||
const PouchSession = require('session-pouchdb-store');
|
const PouchSession = require('session-pouchdb-store')
|
||||||
const LocalStrategy = require('passport-local').Strategy;
|
const LocalStrategy = require('passport-local').Strategy
|
||||||
const session = require('express-session');
|
const session = require('express-session')
|
||||||
const bcrypt = require('bcrypt-nodejs');
|
const bcrypt = require('bcrypt-nodejs')
|
||||||
const flash = require('connect-flash');
|
const flash = require('connect-flash')
|
||||||
const passport = require('passport');
|
const passport = require('passport')
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
const level = require('level');
|
|
||||||
|
|
||||||
const config = require('./config');
|
const config = require('./config')
|
||||||
_CC.config = config
|
_CC.config = config
|
||||||
|
|
||||||
if (!config.dbPrefix.startsWith('http')) {
|
if (!config.dbPrefix.startsWith('http')) {
|
||||||
|
@ -16,44 +15,43 @@ if (!config.dbPrefix.startsWith('http')) {
|
||||||
mkdirp(config.dbPrefix)
|
mkdirp(config.dbPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
const PouchDB = require('pouchdb').defaults({ prefix: config.dbPrefix });
|
const PouchDB = require('pouchdb').defaults({ prefix: config.dbPrefix })
|
||||||
|
|
||||||
const logger = require('./logger');
|
const logger = require('./logger')
|
||||||
|
|
||||||
const app = express();
|
const app = express()
|
||||||
app.set('base', config.base)
|
app.set('base', config.base)
|
||||||
app.set('trust proxy', config.trustProxy)
|
app.set('trust proxy', config.trustProxy)
|
||||||
|
|
||||||
const db = new PouchDB('users');
|
const db = new PouchDB('users')
|
||||||
|
|
||||||
passport.use('local', new LocalStrategy(
|
passport.use('local', new LocalStrategy(
|
||||||
(username, password, done) => {
|
(username, password, done) => {
|
||||||
username = username.trim();
|
username = username.trim()
|
||||||
db.get(username)
|
db.get(username)
|
||||||
.then(doc => {
|
.then(doc => {
|
||||||
bcrypt.compare(password, doc.password, (err, correct) => {
|
bcrypt.compare(password, doc.password, (err, correct) => {
|
||||||
if (err) return done(err);
|
if (err) return done(err)
|
||||||
if (!correct) return done(null, false, { message: 'Incorrect password' });
|
if (!correct) return done(null, false, { message: 'Incorrect password' })
|
||||||
if (correct) return done(null, doc);
|
if (correct) return done(null, doc)
|
||||||
});
|
})
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.message === 'missing') return done(null, false, { message: 'Incorrect username.' });
|
if (err.message === 'missing') return done(null, false, { message: 'Incorrect username.' })
|
||||||
return done(err);
|
return done(err)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
));
|
))
|
||||||
|
|
||||||
passport.serializeUser((user, callback) => callback(null, user._id));
|
passport.serializeUser((user, callback) => callback(null, user._id))
|
||||||
|
|
||||||
passport.deserializeUser((user, callback) => {
|
passport.deserializeUser((user, callback) => {
|
||||||
db.get(user)
|
db.get(user)
|
||||||
.then(dbUser => callback(null, dbUser))
|
.then(dbUser => callback(null, dbUser))
|
||||||
.catch(() => callback(null, null));
|
.catch(() => callback(null, null))
|
||||||
});
|
})
|
||||||
|
|
||||||
|
app.use(require('body-parser').urlencoded({ extended: true }))
|
||||||
app.use(require('body-parser').urlencoded({ extended: true }));
|
|
||||||
app.use(session({
|
app.use(session({
|
||||||
secret: config.secret,
|
secret: config.secret,
|
||||||
resave: false,
|
resave: false,
|
||||||
|
@ -63,26 +61,26 @@ app.use(session({
|
||||||
maxAge: config.sessionMaxAge
|
maxAge: config.sessionMaxAge
|
||||||
},
|
},
|
||||||
name: 'christmas_community.connect.sid'
|
name: 'christmas_community.connect.sid'
|
||||||
}));
|
}))
|
||||||
app.use(flash());
|
app.use(flash())
|
||||||
app.use(passport.initialize());
|
app.use(passport.initialize())
|
||||||
app.use(passport.session());
|
app.use(passport.session())
|
||||||
|
|
||||||
app.use(require('./middlewares/locals'));
|
app.use(require('./middlewares/locals'))
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
logger.log('express', `${req.ip} - ${req.method} ${req.originalUrl}`);
|
logger.log('express', `${req.ip} - ${req.method} ${req.originalUrl}`)
|
||||||
next();
|
next()
|
||||||
});
|
})
|
||||||
|
|
||||||
app.set('view engine', 'pug');
|
app.set('view engine', 'pug')
|
||||||
app.use(config.base, require('./routes')({ db, config }));
|
app.use(config.base, require('./routes')({ db, config }))
|
||||||
|
|
||||||
app.listen(config.port, () => logger.success('express', `Express server started on port ${config.port}!`))
|
app.listen(config.port, () => logger.success('express', `Express server started on port ${config.port}!`))
|
||||||
|
|
||||||
;(() => {
|
;(() => {
|
||||||
if (!config.dbExposePort) return
|
if (!config.dbExposePort) return
|
||||||
const dbExposeApp = express()
|
const dbExposeApp = express()
|
||||||
dbExposeApp.use('/', require('express-pouchdb')(PouchDB, { inMemoryConfig: true }));
|
dbExposeApp.use('/', require('express-pouchdb')(PouchDB, { inMemoryConfig: true }))
|
||||||
dbExposeApp.listen(config.dbExposePort, () => logger.success('db expose', `DB has been exposed on port ${config.dbExposePort}`))
|
dbExposeApp.listen(config.dbExposePort, () => logger.success('db expose', `DB has been exposed on port ${config.dbExposePort}`))
|
||||||
})()
|
})()
|
||||||
|
|
19
logger.js
19
logger.js
|
@ -1,12 +1,9 @@
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk')
|
||||||
const config = require('./config');
|
const colors = { log: 'blue', success: 'green', error: 'red', warn: 'yellow' }
|
||||||
const colors = {log: 'blue', success: 'green', error: 'red', warn: 'yellow'};
|
|
||||||
|
|
||||||
// rewrite to use Object.keys()
|
Object.keys(colors).forEach(
|
||||||
for (let property in colors) {
|
method => // eslint-disable-line no-return-assign
|
||||||
if (colors.hasOwnProperty(property)) {
|
module.exports[method] =
|
||||||
module.exports[property] = (type, msg) => {
|
(type, msg) =>
|
||||||
console.log(chalk.keyword(colors[property])(`[ ${type.toUpperCase()} ] ${msg}`));
|
console.log(chalk.keyword(colors[method])(`[ ${type.toUpperCase()} ] ${msg}`))
|
||||||
};
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
10
manager.js
10
manager.js
|
@ -3,14 +3,14 @@ const { spawn } = require('child_process')
|
||||||
|
|
||||||
const PACKAGENAME = 'get-product-name'
|
const PACKAGENAME = 'get-product-name'
|
||||||
|
|
||||||
async function isOutdated() {
|
async function isOutdated () {
|
||||||
const command = `npm outdated ${PACKAGENAME} --json`
|
const command = `npm outdated ${PACKAGENAME} --json`
|
||||||
const npm = await exec(command)
|
const npm = await exec(command)
|
||||||
const data = JSON.parse(npm.stdout)
|
const data = JSON.parse(npm.stdout)
|
||||||
return data[PACKAGENAME]?.current !== data[PACKAGENAME]?.wanted
|
return data[PACKAGENAME]?.current !== data[PACKAGENAME]?.wanted
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateGPD() {
|
async function updateGPD () {
|
||||||
// https://blog.cloud66.com/using-node-with-docker/
|
// https://blog.cloud66.com/using-node-with-docker/
|
||||||
const command = `mv ./node_modules ./node_modules.tmp && mv ./node_modules.tmp ./node_modules && npm update ${PACKAGENAME}`
|
const command = `mv ./node_modules ./node_modules.tmp && mv ./node_modules.tmp ./node_modules && npm update ${PACKAGENAME}`
|
||||||
await exec(command)
|
await exec(command)
|
||||||
|
@ -18,15 +18,15 @@ async function updateGPD() {
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
let cc = null
|
let cc = null
|
||||||
function spawnCC() {
|
function spawnCC () {
|
||||||
cc = spawn('node', [ 'index.js' ], { env: process.env })
|
cc = spawn('node', ['index.js'], { env: process.env })
|
||||||
cc.on('exit', spawnCC)
|
cc.on('exit', spawnCC)
|
||||||
cc.stdout.pipe(process.stdout)
|
cc.stdout.pipe(process.stdout)
|
||||||
cc.stderr.pipe(process.stderr)
|
cc.stderr.pipe(process.stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.UPDATE_GPD !== 'false') {
|
if (process.env.UPDATE_GPD !== 'false') {
|
||||||
async function update() {
|
async function update () {
|
||||||
if (await isOutdated()) {
|
if (await isOutdated()) {
|
||||||
try {
|
try {
|
||||||
await updateGPD()
|
await updateGPD()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const config = require('../config');
|
const config = require('../config')
|
||||||
module.exports = (req, res, next) => {
|
module.exports = (req, res, next) => {
|
||||||
res.locals.config = config;
|
res.locals.config = config
|
||||||
res.locals.req = req;
|
res.locals.req = req
|
||||||
next();
|
next()
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const config = require('../config');
|
const config = require('../config')
|
||||||
module.exports = options => {
|
module.exports = options => {
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
options = options ? options : {};
|
options = options || {}
|
||||||
let authed = false
|
let authed = false
|
||||||
try {
|
try {
|
||||||
authed = req.isAuthenticated()
|
authed = req.isAuthenticated()
|
||||||
|
@ -10,5 +10,5 @@ module.exports = options => {
|
||||||
}
|
}
|
||||||
if (authed) return next()
|
if (authed) return next()
|
||||||
res.redirect(options.failureRedirect || config.defaultFailureRedirect)
|
res.redirect(options.failureRedirect || config.defaultFailureRedirect)
|
||||||
};
|
}
|
||||||
}
|
}
|
|
@ -36,5 +36,12 @@
|
||||||
"session-pouchdb-store": "^0.4.1",
|
"session-pouchdb-store": "^0.4.1",
|
||||||
"u64": "^1.0.1",
|
"u64": "^1.0.1",
|
||||||
"yes-no": "^0.0.1"
|
"yes-no": "^0.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^7.13.0",
|
||||||
|
"eslint-config-standard": "^16.0.1",
|
||||||
|
"eslint-plugin-import": "^2.22.1",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-promise": "^4.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,31 +1,30 @@
|
||||||
const verifyAuth = require('../../middlewares/verifyAuth');
|
const verifyAuth = require('../../middlewares/verifyAuth')
|
||||||
const bcrypt = require('bcrypt-nodejs');
|
const express = require('express')
|
||||||
const express = require('express');
|
|
||||||
const { nanoid } = require('nanoid')
|
const { nanoid } = require('nanoid')
|
||||||
|
|
||||||
const SECRET_TOKEN_LENGTH = 32
|
const SECRET_TOKEN_LENGTH = 32
|
||||||
const SECRET_TOKEN_LIFETIME =
|
const SECRET_TOKEN_LIFETIME =
|
||||||
// One week, approximately. Doesn't need to be perfect.
|
// One week, approximately. Doesn't need to be perfect.
|
||||||
1000 // milliseconds
|
1000 * // milliseconds
|
||||||
* 60 // seconds
|
60 * // seconds
|
||||||
* 60 // minutes
|
60 * // minutes
|
||||||
* 24 // hours
|
24 * // hours
|
||||||
* 07 // days
|
7 // days
|
||||||
|
|
||||||
module.exports = (db) => {
|
module.exports = (db) => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/', verifyAuth(), (req, res) => {
|
router.get('/', verifyAuth(), (req, res) => {
|
||||||
if (!req.user.admin) return res.redirect('/');
|
if (!req.user.admin) return res.redirect('/')
|
||||||
db.allDocs({ include_docs: true })
|
db.allDocs({ include_docs: true })
|
||||||
.then(docs => {
|
.then(docs => {
|
||||||
res.render('adminSettings', { title: 'Admin Settings', users: docs.rows })
|
res.render('adminSettings', { title: 'Admin Settings', users: docs.rows })
|
||||||
})
|
})
|
||||||
.catch(err => { throw err; });
|
.catch(err => { throw err })
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/add', verifyAuth(), async (req, res) => {
|
router.post('/add', verifyAuth(), async (req, res) => {
|
||||||
if (!req.user.admin) return res.redirect('/');
|
if (!req.user.admin) return res.redirect('/')
|
||||||
await db.put({
|
await db.put({
|
||||||
_id: req.body.newUserUsername.trim(),
|
_id: req.body.newUserUsername.trim(),
|
||||||
admin: false,
|
admin: false,
|
||||||
|
@ -34,43 +33,43 @@ module.exports = (db) => {
|
||||||
signupToken: nanoid(SECRET_TOKEN_LENGTH),
|
signupToken: nanoid(SECRET_TOKEN_LENGTH),
|
||||||
expiry: new Date().getTime() + SECRET_TOKEN_LIFETIME
|
expiry: new Date().getTime() + SECRET_TOKEN_LIFETIME
|
||||||
|
|
||||||
});
|
})
|
||||||
res.redirect(`/admin-settings/edit/${req.body.newUserUsername.trim()}`)
|
res.redirect(`/admin-settings/edit/${req.body.newUserUsername.trim()}`)
|
||||||
});
|
})
|
||||||
|
|
||||||
router.get('/edit/:userToEdit', verifyAuth(), async (req, res) => {
|
router.get('/edit/:userToEdit', verifyAuth(), async (req, res) => {
|
||||||
if (!req.user.admin) return res.redirect('/');
|
if (!req.user.admin) return res.redirect('/')
|
||||||
const doc = await db.get(req.params.userToEdit)
|
const doc = await db.get(req.params.userToEdit)
|
||||||
delete doc.password
|
delete doc.password
|
||||||
res.render('admin-user-edit', { user: doc });
|
res.render('admin-user-edit', { user: doc })
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/edit/refresh-signup-token/:userToEdit', verifyAuth(), async (req, res) => {
|
router.post('/edit/refresh-signup-token/:userToEdit', verifyAuth(), async (req, res) => {
|
||||||
if (!req.user.admin) return res.redirect('/');
|
if (!req.user.admin) return res.redirect('/')
|
||||||
const doc = await db.get(req.params.userToEdit)
|
const doc = await db.get(req.params.userToEdit)
|
||||||
doc.signupToken = nanoid(SECRET_TOKEN_LENGTH)
|
doc.signupToken = nanoid(SECRET_TOKEN_LENGTH)
|
||||||
doc.expiry = new Date().getTime() + SECRET_TOKEN_LIFETIME
|
doc.expiry = new Date().getTime() + SECRET_TOKEN_LIFETIME
|
||||||
await db.put(doc)
|
await db.put(doc)
|
||||||
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
|
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/edit/resetpw/:userToEdit', verifyAuth(), async (req, res) => {
|
router.post('/edit/resetpw/:userToEdit', verifyAuth(), async (req, res) => {
|
||||||
if (!req.user.admin) return res.redirect('/');
|
if (!req.user.admin) return res.redirect('/')
|
||||||
const doc = await db.get(req.params.userToEdit)
|
const doc = await db.get(req.params.userToEdit)
|
||||||
doc.pwToken = nanoid(SECRET_TOKEN_LENGTH)
|
doc.pwToken = nanoid(SECRET_TOKEN_LENGTH)
|
||||||
doc.pwExpiry = new Date().getTime() + SECRET_TOKEN_LIFETIME
|
doc.pwExpiry = new Date().getTime() + SECRET_TOKEN_LIFETIME
|
||||||
await db.put(doc)
|
await db.put(doc)
|
||||||
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
|
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/edit/cancelresetpw/:userToEdit', verifyAuth(), async (req, res) => {
|
router.post('/edit/cancelresetpw/:userToEdit', verifyAuth(), async (req, res) => {
|
||||||
if (!req.user.admin) return res.redirect('/');
|
if (!req.user.admin) return res.redirect('/')
|
||||||
const doc = await db.get(req.params.userToEdit)
|
const doc = await db.get(req.params.userToEdit)
|
||||||
delete doc.pwToken
|
delete doc.pwToken
|
||||||
delete doc.pwExpiry
|
delete doc.pwExpiry
|
||||||
await db.put(doc)
|
await db.put(doc)
|
||||||
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
|
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/edit/rename/:userToRename', verifyAuth(), async (req, res) => {
|
router.post('/edit/rename/:userToRename', verifyAuth(), async (req, res) => {
|
||||||
if (!req.user.admin && req.user._id !== req.params.userToRename) return res.redirect('/')
|
if (!req.user.admin && req.user._id !== req.params.userToRename) return res.redirect('/')
|
||||||
|
@ -119,7 +118,7 @@ module.exports = (db) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/edit/impersonate/:userToEdit', verifyAuth(), async (req, res) => {
|
router.post('/edit/impersonate/:userToEdit', verifyAuth(), async (req, res) => {
|
||||||
if (!req.user.admin) return res.redirect('/');
|
if (!req.user.admin) return res.redirect('/')
|
||||||
req.login({ _id: req.params.userToEdit }, err => {
|
req.login({ _id: req.params.userToEdit }, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
req.flash('error', err.message)
|
req.flash('error', err.message)
|
||||||
|
@ -128,29 +127,29 @@ module.exports = (db) => {
|
||||||
req.flash('success', `You are now ${req.params.userToEdit}.`)
|
req.flash('success', `You are now ${req.params.userToEdit}.`)
|
||||||
res.redirect('/')
|
res.redirect('/')
|
||||||
})
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/edit/remove/:userToRemove', verifyAuth(), async (req, res) => {
|
router.post('/edit/remove/:userToRemove', verifyAuth(), async (req, res) => {
|
||||||
if (!req.user.admin) return res.redirect('/');
|
if (!req.user.admin) return res.redirect('/')
|
||||||
const doc = await db.get(req.params.userToRemove);
|
const doc = await db.get(req.params.userToRemove)
|
||||||
if (doc.admin) {
|
if (doc.admin) {
|
||||||
req.flash('error', 'Failed to remove: user is admin.');
|
req.flash('error', 'Failed to remove: user is admin.')
|
||||||
return res.redirect('/admin-settings');
|
return res.redirect('/admin-settings')
|
||||||
}
|
}
|
||||||
await db.remove(doc);
|
await db.remove(doc)
|
||||||
const docs = await db.allDocs({ include_docs: true });
|
const { rows } = await db.allDocs({ include_docs: true })
|
||||||
for (let i = 0; i < docs.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
for (let j = 0; j < docs[i].doc.wishlist.length; j++) {
|
for (let j = 0; j < rows[i].doc.wishlist.length; j++) {
|
||||||
if (docs[i].doc.wishlist[j].pledgedBy === req.params.userToRemove) {
|
if (rows[i].doc.wishlist[j].pledgedBy === req.params.userToRemove) {
|
||||||
docs[i].doc.wishlist[j].pledgedBy === undefined;
|
rows[i].doc.wishlist[j].pledgedBy = undefined
|
||||||
if (docs[i].doc.wishlist[j].addedBy === req.params.userToRemove) await db.remove(doc);
|
if (rows[i].doc.wishlist[j].addedBy === req.params.userToRemove) rows[i].doc.wishlist.splice(j, 1)
|
||||||
else await db.put(docs[i].doc);
|
await db.put(rows[i].doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req.flash('success', `Successfully removed user ${req.params.userToRemove}`);
|
req.flash('success', `Successfully removed user ${req.params.userToRemove}`)
|
||||||
res.redirect('/admin-settings')
|
res.redirect('/admin-settings')
|
||||||
});
|
})
|
||||||
|
|
||||||
return router;
|
return router
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
const verifyAuth = require('../../middlewares/verifyAuth');
|
const verifyAuth = require('../../middlewares/verifyAuth')
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
module.exports = ({ db, config }) => {
|
module.exports = ({ db, config }) => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.use(verifyAuth())
|
router.use(verifyAuth())
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ module.exports = ({ db, config }) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.use('/wishlist', require('./wishlist')({ db }));
|
router.use('/wishlist', require('./wishlist')({ db }))
|
||||||
|
|
||||||
return router;
|
return router
|
||||||
}
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
const verifyAuth = require('../../../middlewares/verifyAuth')
|
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
module.exports = ({ db, config }) => {
|
module.exports = ({ db }) => {
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
|
@ -19,12 +17,12 @@ module.exports = ({ db, config }) => {
|
||||||
if (req.params.direction === 'up') wishlist.reverse()
|
if (req.params.direction === 'up') wishlist.reverse()
|
||||||
let moveFromIndex
|
let moveFromIndex
|
||||||
wishlist.forEach(wish => {
|
wishlist.forEach(wish => {
|
||||||
if (wish.id === req.params.id) return moveFromIndex = wishlist.indexOf(wish)
|
if (wish.id === req.params.id) moveFromIndex = wishlist.indexOf(wish)
|
||||||
})
|
})
|
||||||
const moveToIndex = wishlist.findIndex(wish => {
|
const moveToIndex = wishlist.findIndex(wish => {
|
||||||
return ( wishlist.indexOf(wish) > moveFromIndex && wish.addedBy === req.user._id )
|
return (wishlist.indexOf(wish) > moveFromIndex && wish.addedBy === req.user._id)
|
||||||
})
|
})
|
||||||
if (moveToIndex < 0 || moveToIndex > wishlist.length) return res.send({ error: 'Invalid move '})
|
if (moveToIndex < 0 || moveToIndex > wishlist.length) return res.send({ error: 'Invalid move ' })
|
||||||
const original = wishlist[moveToIndex]
|
const original = wishlist[moveToIndex]
|
||||||
wishlist[moveToIndex] = wishlist[moveFromIndex]
|
wishlist[moveToIndex] = wishlist[moveFromIndex]
|
||||||
wishlist[moveFromIndex] = original
|
wishlist[moveFromIndex] = original
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
const bcrypt = require('bcrypt-nodejs');
|
const bcrypt = require('bcrypt-nodejs')
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
|
|
||||||
module.exports = (db) => {
|
module.exports = (db) => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/:code', async (req, res) => {
|
router.get('/:code', async (req, res) => {
|
||||||
const row = (await db.allDocs({ include_docs: true }))
|
const row = (await db.allDocs({ include_docs: true }))
|
||||||
|
@ -10,7 +10,7 @@ module.exports = (db) => {
|
||||||
.find(({ doc }) => doc.signupToken === req.params.code)
|
.find(({ doc }) => doc.signupToken === req.params.code)
|
||||||
|
|
||||||
res.render('confirm-account', { doc: row ? row.doc : undefined })
|
res.render('confirm-account', { doc: row ? row.doc : undefined })
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/:code', async (req, res) => {
|
router.post('/:code', async (req, res) => {
|
||||||
const { doc } = (await db.allDocs({ include_docs: true }))
|
const { doc } = (await db.allDocs({ include_docs: true }))
|
||||||
|
@ -20,7 +20,7 @@ module.exports = (db) => {
|
||||||
if (doc.expiry < new Date().getTime()) return res.redirect(`/confirm-account/${req.params.code}`)
|
if (doc.expiry < new Date().getTime()) return res.redirect(`/confirm-account/${req.params.code}`)
|
||||||
|
|
||||||
bcrypt.hash(req.body.password, null, null, async (err, passwordHash) => {
|
bcrypt.hash(req.body.password, null, null, async (err, passwordHash) => {
|
||||||
if (err) throw err;
|
if (err) throw err
|
||||||
|
|
||||||
doc.password = passwordHash
|
doc.password = passwordHash
|
||||||
delete doc.signupToken
|
delete doc.signupToken
|
||||||
|
@ -34,11 +34,11 @@ module.exports = (db) => {
|
||||||
req.flash('error', err.message)
|
req.flash('error', err.message)
|
||||||
return res.redirect('/')
|
return res.redirect('/')
|
||||||
}
|
}
|
||||||
req.flash('success', `Welcome to ${_CC.config.siteTitle}!`);
|
req.flash('success', `Welcome to ${_CC.config.siteTitle}!`)
|
||||||
res.redirect('/');
|
res.redirect('/')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return router;
|
return router
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,44 +1,44 @@
|
||||||
const verifyAuth = require('../middlewares/verifyAuth');
|
const verifyAuth = require('../middlewares/verifyAuth')
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
const path = require('path');
|
const path = require('path')
|
||||||
|
|
||||||
module.exports = ({ db, config }) => {
|
module.exports = ({ db, config }) => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.use('/', express.static(path.join(__dirname, '../static')));
|
router.use('/', express.static(path.join(__dirname, '../static')))
|
||||||
|
|
||||||
router.get('/',
|
router.get('/',
|
||||||
async (req, res, next) => {
|
async (req, res, next) => {
|
||||||
dbInfo = await db.info();
|
const dbInfo = await db.info()
|
||||||
if (dbInfo.doc_count === 0) {
|
if (dbInfo.doc_count === 0) {
|
||||||
res.redirect('/setup');
|
res.redirect('/setup')
|
||||||
} else {
|
} else {
|
||||||
next();
|
next()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
verifyAuth(),
|
verifyAuth(),
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
res.redirect('/wishlist');
|
res.redirect('/wishlist')
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
router.use('/api', require('./api')({ db }))
|
router.use('/api', require('./api')({ db }))
|
||||||
|
|
||||||
router.use('/setup', require('./setup')(db));
|
router.use('/setup', require('./setup')(db))
|
||||||
|
|
||||||
router.use('/login', require('./login')());
|
router.use('/login', require('./login')())
|
||||||
router.use('/logout', require('./logout')());
|
router.use('/logout', require('./logout')())
|
||||||
router.use('/resetpw', require('./resetpw')(db));
|
router.use('/resetpw', require('./resetpw')(db))
|
||||||
router.use('/confirm-account', require('./confirm-account')(db));
|
router.use('/confirm-account', require('./confirm-account')(db))
|
||||||
|
|
||||||
router.use('/wishlist', require('./wishlist')(db));
|
router.use('/wishlist', require('./wishlist')(db))
|
||||||
router.use('/supported-sites', require('./supported-sites')())
|
router.use('/supported-sites', require('./supported-sites')())
|
||||||
|
|
||||||
router.use('/profile', require('./profile')(db));
|
router.use('/profile', require('./profile')(db))
|
||||||
|
|
||||||
router.use('/admin-settings', require('./adminSettings')(db));
|
router.use('/admin-settings', require('./adminSettings')(db))
|
||||||
|
|
||||||
router.use('/manifest.json', require('./manifest.json')({ config }))
|
router.use('/manifest.json', require('./manifest.json')({ config }))
|
||||||
|
|
||||||
return router;
|
return router
|
||||||
}
|
}
|
|
@ -1,29 +1,29 @@
|
||||||
const passport = require('passport');
|
const passport = require('passport')
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
|
|
||||||
module.exports = () => {
|
module.exports = () => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/',
|
router.get('/',
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
res.redirect('/');
|
res.redirect('/')
|
||||||
} else {
|
} else {
|
||||||
res.render('login');
|
res.render('login')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
(req, res, next) => {
|
(req, res, next) => {
|
||||||
next();
|
next()
|
||||||
},
|
},
|
||||||
passport.authenticate('local', {
|
passport.authenticate('local', {
|
||||||
successRedirect: '/',
|
successRedirect: '/',
|
||||||
failureRedirect: '/login',
|
failureRedirect: '/login',
|
||||||
failureFlash: 'Invalid username or password'
|
failureFlash: 'Invalid username or password'
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
return router;
|
return router
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
const verifyAuth = require('../../middlewares/verifyAuth');
|
const verifyAuth = require('../../middlewares/verifyAuth')
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
|
|
||||||
module.exports = () => {
|
module.exports = () => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/', verifyAuth(), (req, res) => res.render('logout'));
|
router.get('/', verifyAuth(), (req, res) => res.render('logout'))
|
||||||
router.post('/', (req, res) => {
|
router.post('/', (req, res) => {
|
||||||
req.logout();
|
req.logout()
|
||||||
res.redirect('/');
|
res.redirect('/')
|
||||||
});
|
})
|
||||||
|
|
||||||
return router;
|
return router
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
|
|
||||||
module.exports = ({ config }) => {
|
module.exports = ({ config }) => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
res.send({
|
res.send({
|
||||||
|
@ -19,7 +19,7 @@ module.exports = ({ config }) => {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
return router;
|
return router
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
const verifyAuth = require('../../middlewares/verifyAuth');
|
const verifyAuth = require('../../middlewares/verifyAuth')
|
||||||
const bcrypt = require('bcrypt-nodejs');
|
const bcrypt = require('bcrypt-nodejs')
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
|
|
||||||
module.exports = (db) => {
|
module.exports = (db) => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/', verifyAuth(), (req, res) => res.render('profile', { title: `Profile Settings - ${req.user._id}`}));
|
router.get('/', verifyAuth(), (req, res) => res.render('profile', { title: `Profile Settings - ${req.user._id}` }))
|
||||||
router.post('/', verifyAuth(), (req, res) => {
|
router.post('/', verifyAuth(), (req, res) => {
|
||||||
if (req.body.oldPassword && req.body.newPassword) {
|
if (req.body.oldPassword && req.body.newPassword) {
|
||||||
bcrypt.compare(req.body.oldPassword, req.user.password, (err, correct) => {
|
bcrypt.compare(req.body.oldPassword, req.user.password, (err, correct) => {
|
||||||
if (err) throw err;
|
if (err) throw err
|
||||||
if (correct) {
|
if (correct) {
|
||||||
bcrypt.hash(req.body.newPassword, null, null, (err, hash) => {
|
bcrypt.hash(req.body.newPassword, null, null, (err, hash) => {
|
||||||
if (err) throw err;
|
if (err) throw err
|
||||||
db.get(req.user._id)
|
db.get(req.user._id)
|
||||||
.then(doc => {
|
.then(doc => {
|
||||||
doc.password = hash;
|
doc.password = hash
|
||||||
db.put(doc)
|
db.put(doc)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
req.flash('success', 'Changes saved successfully!');
|
req.flash('success', 'Changes saved successfully!')
|
||||||
res.redirect('/profile');
|
res.redirect('/profile')
|
||||||
})
|
})
|
||||||
.catch(err => { throw err; });
|
.catch(err => { throw err })
|
||||||
|
})
|
||||||
|
.catch(err => { throw err })
|
||||||
})
|
})
|
||||||
.catch(err => { throw err; });
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
req.flash('error', 'Incorrect old password');
|
req.flash('error', 'Incorrect old password')
|
||||||
res.redirect('/profile');
|
res.redirect('/profile')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
res.redirect('/profile');
|
res.redirect('/profile')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
return router;
|
return router
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
const bcrypt = require('bcrypt-nodejs');
|
const bcrypt = require('bcrypt-nodejs')
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
|
|
||||||
module.exports = (db) => {
|
module.exports = (db) => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/:code', async (req, res) => {
|
router.get('/:code', async (req, res) => {
|
||||||
const row = (await db.allDocs({ include_docs: true }))
|
const row = (await db.allDocs({ include_docs: true }))
|
||||||
.rows
|
.rows
|
||||||
.find(({ doc }) => doc.pwToken === req.params.code)
|
.find(({ doc }) => doc.pwToken === req.params.code)
|
||||||
|
|
||||||
|
|
||||||
res.render('resetpw', { doc: row ? row.doc : undefined })
|
res.render('resetpw', { doc: row ? row.doc : undefined })
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/:code', async (req, res) => {
|
router.post('/:code', async (req, res) => {
|
||||||
const { doc } = (await db.allDocs({ include_docs: true }))
|
const { doc } = (await db.allDocs({ include_docs: true }))
|
||||||
|
@ -21,7 +20,7 @@ module.exports = (db) => {
|
||||||
if (doc.expiry < new Date().getTime()) return res.redirect(`/resetpw/${req.params.code}`)
|
if (doc.expiry < new Date().getTime()) return res.redirect(`/resetpw/${req.params.code}`)
|
||||||
|
|
||||||
bcrypt.hash(req.body.password, null, null, async (err, passwordHash) => {
|
bcrypt.hash(req.body.password, null, null, async (err, passwordHash) => {
|
||||||
if (err) throw err;
|
if (err) throw err
|
||||||
|
|
||||||
doc.password = passwordHash
|
doc.password = passwordHash
|
||||||
delete doc.pwToken
|
delete doc.pwToken
|
||||||
|
@ -35,11 +34,11 @@ module.exports = (db) => {
|
||||||
req.flash('error', err.message)
|
req.flash('error', err.message)
|
||||||
return res.redirect('/')
|
return res.redirect('/')
|
||||||
}
|
}
|
||||||
req.flash('success', `Welcome to ${_CC.config.siteTitle}!`);
|
req.flash('success', `Welcome to ${_CC.config.siteTitle}!`)
|
||||||
res.redirect('/');
|
res.redirect('/')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return router;
|
return router
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
const bcrypt = require('bcrypt-nodejs')
|
const bcrypt = require('bcrypt-nodejs')
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
|
|
||||||
module.exports = (db) => {
|
module.exports = (db) => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/',
|
router.get('/',
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const dbInfo = await db.info();
|
const dbInfo = await db.info()
|
||||||
if (dbInfo.doc_count === 0) {
|
if (dbInfo.doc_count === 0) {
|
||||||
res.render('setup', { title: 'Setup' });
|
res.render('setup', { title: 'Setup' })
|
||||||
} else {
|
} else {
|
||||||
res.redirect('/');
|
res.redirect('/')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
router.post('/',
|
router.post('/',
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const dbInfo = await db.info();
|
const dbInfo = await db.info()
|
||||||
if (dbInfo.doc_count === 0) {
|
if (dbInfo.doc_count === 0) {
|
||||||
bcrypt.hash(req.body.adminPassword, null, null, (err, adminPasswordHash) => {
|
bcrypt.hash(req.body.adminPassword, null, null, (err, adminPasswordHash) => {
|
||||||
if (err) throw err;
|
if (err) throw err
|
||||||
db.put({
|
db.put({
|
||||||
_id: req.body.adminUsername.trim(),
|
_id: req.body.adminUsername.trim(),
|
||||||
password: adminPasswordHash,
|
password: adminPasswordHash,
|
||||||
admin: true,
|
admin: true,
|
||||||
wishlist: []
|
wishlist: []
|
||||||
})
|
})
|
||||||
res.redirect('/');
|
res.redirect('/')
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
res.redirect('/');
|
res.redirect('/')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
return router;
|
return router
|
||||||
}
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
const verifyAuth = require('../../middlewares/verifyAuth');
|
const verifyAuth = require('../../middlewares/verifyAuth')
|
||||||
const getProductName = require('get-product-name');
|
const getProductName = require('get-product-name')
|
||||||
const express = require('express');
|
const express = require('express')
|
||||||
const config = require('../../config');
|
const config = require('../../config')
|
||||||
const u64 = require('u64')
|
const u64 = require('u64')
|
||||||
|
|
||||||
const totals = wishlist => {
|
const totals = wishlist => {
|
||||||
let unpledged = 0;
|
let unpledged = 0
|
||||||
let pledged = 0;
|
let pledged = 0
|
||||||
wishlist.forEach(wishItem => {
|
wishlist.forEach(wishItem => {
|
||||||
if (wishItem.pledgedBy) pledged += 1;
|
if (wishItem.pledgedBy) pledged += 1
|
||||||
else unpledged += 1;
|
else unpledged += 1
|
||||||
});
|
})
|
||||||
return { unpledged, pledged };
|
return { unpledged, pledged }
|
||||||
};
|
}
|
||||||
|
|
||||||
const ValidURL = (string) => { // Ty SO
|
const ValidURL = (string) => { // Ty SO
|
||||||
try {
|
try {
|
||||||
|
@ -20,40 +20,40 @@ const ValidURL = (string) => { // Ty SO
|
||||||
if (process.env.SMILE !== 'false') {
|
if (process.env.SMILE !== 'false') {
|
||||||
if (url.hostname === 'www.amazon.com') url.hostname = 'smile.amazon.com'
|
if (url.hostname === 'www.amazon.com') url.hostname = 'smile.amazon.com'
|
||||||
}
|
}
|
||||||
if (url) return url;
|
if (url) return url
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = (db) => {
|
module.exports = (db) => {
|
||||||
const router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
router.get('/', verifyAuth(), async (req, res) => {
|
router.get('/', verifyAuth(), async (req, res) => {
|
||||||
const docs = await db.allDocs({ include_docs: true })
|
const docs = await db.allDocs({ include_docs: true })
|
||||||
if (process.env.SINGLE_LIST === 'true') {
|
if (process.env.SINGLE_LIST === 'true') {
|
||||||
for (row of docs.rows) {
|
for (const row of docs.rows) {
|
||||||
if (row.doc.admin) return res.redirect(`/wishlist/${row.doc._id}`)
|
if (row.doc.admin) return res.redirect(`/wishlist/${row.doc._id}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.render('wishlists', { title: 'Wishlists', users: docs.rows, totals})
|
res.render('wishlists', { title: 'Wishlists', users: docs.rows, totals })
|
||||||
});
|
})
|
||||||
|
|
||||||
router.get('/:user', verifyAuth(), async (req, res) => {
|
router.get('/:user', verifyAuth(), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const dbUser = await db.get(req.params.user);
|
const dbUser = await db.get(req.params.user)
|
||||||
if (process.env.SINGLE_LIST === 'true') {
|
if (process.env.SINGLE_LIST === 'true') {
|
||||||
if (!dbUser.admin) {
|
if (!dbUser.admin) {
|
||||||
const docs = await db.allDocs({ include_docs: true })
|
const docs = await db.allDocs({ include_docs: true })
|
||||||
for (row of docs.rows) {
|
for (const row of docs.rows) {
|
||||||
if (row.doc.admin) return res.redirect(`/wishlist/${row.doc._id}`)
|
if (row.doc.admin) return res.redirect(`/wishlist/${row.doc._id}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const firstCanSee = dbUser.wishlist.findIndex(element => (element.addedBy === req.params.user));
|
const firstCanSee = dbUser.wishlist.findIndex(element => (element.addedBy === req.params.user))
|
||||||
const wishlistReverse = [...dbUser.wishlist].reverse();
|
const wishlistReverse = [...dbUser.wishlist].reverse()
|
||||||
const lastCanSeeValue = wishlistReverse.find(element => (element.addedBy === req.params.user));
|
const lastCanSeeValue = wishlistReverse.find(element => (element.addedBy === req.params.user))
|
||||||
const lastCanSee = dbUser.wishlist.indexOf(lastCanSeeValue);
|
const lastCanSee = dbUser.wishlist.indexOf(lastCanSeeValue)
|
||||||
res.render('wishlist', {
|
res.render('wishlist', {
|
||||||
title: `Wishlist - ${dbUser._id}`,
|
title: `Wishlist - ${dbUser._id}`,
|
||||||
wishlist: [
|
wishlist: [
|
||||||
|
@ -62,40 +62,40 @@ module.exports = (db) => {
|
||||||
],
|
],
|
||||||
firstCanSee,
|
firstCanSee,
|
||||||
lastCanSee
|
lastCanSee
|
||||||
});
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
req.flash('error', error);
|
req.flash('error', error)
|
||||||
return res.redirect('/wishlist');
|
return res.redirect('/wishlist')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/:user', verifyAuth(), async (req, res) => {
|
router.post('/:user', verifyAuth(), async (req, res) => {
|
||||||
if (!req.body.itemUrlOrName) {
|
if (!req.body.itemUrlOrName) {
|
||||||
req.flash('error', 'Item URL or Name is required')
|
req.flash('error', 'Item URL or Name is required')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`)
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
const potentialUrl = req.body.itemUrlOrName.split(' ').pop();
|
const potentialUrl = req.body.itemUrlOrName.split(' ').pop()
|
||||||
const url = ValidURL(potentialUrl);
|
const url = ValidURL(potentialUrl)
|
||||||
const item = {};
|
const item = {}
|
||||||
let productData;
|
let productData
|
||||||
try {
|
try {
|
||||||
if (url) productData = await getProductName(url, config.proxyServer);
|
if (url) productData = await getProductName(url, config.proxyServer)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
req.flash('error', err.toString());
|
req.flash('error', err.toString())
|
||||||
}
|
}
|
||||||
item.name = (productData ? productData.name : '');
|
item.name = (productData ? productData.name : '')
|
||||||
item.price = productData?.price
|
item.price = productData?.price
|
||||||
item.image = productData?.image
|
item.image = productData?.image
|
||||||
item.addedBy = req.user._id;
|
item.addedBy = req.user._id
|
||||||
item.pledgedBy = (req.user._id === req.params.user ? undefined : req.user._id);
|
item.pledgedBy = (req.user._id === req.params.user ? undefined : req.user._id)
|
||||||
item.note = req.body.note;
|
item.note = req.body.note
|
||||||
if (url) item.url = url;
|
if (url) item.url = url
|
||||||
if (!url) item.name = req.body.itemUrlOrName
|
if (!url) item.name = req.body.itemUrlOrName
|
||||||
item.id = u64.encode(new Date().getTime().toString());
|
item.id = u64.encode(new Date().getTime().toString())
|
||||||
const doc = await db.get(req.params.user);
|
const doc = await db.get(req.params.user)
|
||||||
doc.wishlist.push(item);
|
doc.wishlist.push(item)
|
||||||
try {
|
try {
|
||||||
await db.put(doc);
|
await db.put(doc)
|
||||||
} catch {
|
} catch {
|
||||||
req.flash('error', 'Items are being added too quickly. Please try again.')
|
req.flash('error', 'Items are being added too quickly. Please try again.')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`)
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
|
@ -107,131 +107,131 @@ module.exports = (db) => {
|
||||||
? 'Added item to wishlist'
|
? 'Added item to wishlist'
|
||||||
: `Pleged item for ${req.params.user}`
|
: `Pleged item for ${req.params.user}`
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
res.redirect(`/wishlist/${req.params.user}`);
|
res.redirect(`/wishlist/${req.params.user}`)
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/:user/pledge/:itemId', verifyAuth(), async (req, res) => {
|
router.post('/:user/pledge/:itemId', verifyAuth(), async (req, res) => {
|
||||||
const docs = await db.allDocs({ include_docs: true });
|
const docs = await db.allDocs({ include_docs: true })
|
||||||
for (let i = 0; i < docs.rows.length; i++) {
|
for (let i = 0; i < docs.rows.length; i++) {
|
||||||
for (let j = 0; j < docs.rows[i].doc.wishlist.length; j++) {
|
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].id === req.params.itemId) {
|
||||||
if (docs.rows[i].doc.wishlist[j].pledgedBy !== undefined) {
|
if (docs.rows[i].doc.wishlist[j].pledgedBy !== undefined) {
|
||||||
req.flash('error', 'Item already pledged for');
|
req.flash('error', 'Item already pledged for')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
docs.rows[i].doc.wishlist[j].pledgedBy = req.user._id;
|
docs.rows[i].doc.wishlist[j].pledgedBy = req.user._id
|
||||||
await db.put(docs.rows[i].doc);
|
await db.put(docs.rows[i].doc)
|
||||||
req.flash('success', 'Successfully pledged for item!');
|
req.flash('success', 'Successfully pledged for item!')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
router.post('/:user/unpledge/:itemId', verifyAuth(), async (req, res) => {
|
router.post('/:user/unpledge/:itemId', verifyAuth(), async (req, res) => {
|
||||||
const docs = await db.allDocs({ include_docs: true });
|
const docs = await db.allDocs({ include_docs: true })
|
||||||
for (let i = 0; i < docs.rows.length; i++) {
|
for (let i = 0; i < docs.rows.length; i++) {
|
||||||
for (let j = 0; j < docs.rows[i].doc.wishlist.length; j++) {
|
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].id === req.params.itemId) {
|
||||||
if (docs.rows[i].doc.wishlist[j].pledgedBy !== req.user._id) {
|
if (docs.rows[i].doc.wishlist[j].pledgedBy !== req.user._id) {
|
||||||
req.flash('error', 'You did not pledge for this');
|
req.flash('error', 'You did not pledge for this')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
docs.rows[i].doc.wishlist[j].pledgedBy = undefined;
|
docs.rows[i].doc.wishlist[j].pledgedBy = undefined
|
||||||
if (docs.rows[i].doc.wishlist[j].addedBy === req.user._id) docs.rows[i].doc.wishlist.splice(j, 1);
|
if (docs.rows[i].doc.wishlist[j].addedBy === req.user._id) docs.rows[i].doc.wishlist.splice(j, 1)
|
||||||
await db.put(docs.rows[i].doc);
|
await db.put(docs.rows[i].doc)
|
||||||
req.flash('success', 'Successfully unpledged for item');
|
req.flash('success', 'Successfully unpledged for item')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req.flash('error', 'Failed to find item');
|
req.flash('error', 'Failed to find item')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/:user/remove/:itemId', verifyAuth(), async (req, res) => {
|
router.post('/:user/remove/:itemId', verifyAuth(), async (req, res) => {
|
||||||
if (req.user._id !== req.params.user) {
|
if (req.user._id !== req.params.user) {
|
||||||
req.flash('error', 'Not correct user');
|
req.flash('error', 'Not correct user')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
const doc = await db.get(req.user._id);
|
const doc = await db.get(req.user._id)
|
||||||
for (let i = 0; i < doc.wishlist.length; i++) {
|
for (let i = 0; i < doc.wishlist.length; i++) {
|
||||||
if (doc.wishlist[i].id === req.params.itemId) {
|
if (doc.wishlist[i].id === req.params.itemId) {
|
||||||
doc.wishlist.splice(i, 1);
|
doc.wishlist.splice(i, 1)
|
||||||
await db.put(doc);
|
await db.put(doc)
|
||||||
req.flash('success', 'Successfully removed from wishlist');
|
req.flash('success', 'Successfully removed from wishlist')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req.flash('error', 'Failed to find item');
|
req.flash('error', 'Failed to find item')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/:user/move/:direction/:itemId', verifyAuth(), async (req, res) => {
|
router.post('/:user/move/:direction/:itemId', verifyAuth(), async (req, res) => {
|
||||||
if (req.user._id !== req.params.user) {
|
if (req.user._id !== req.params.user) {
|
||||||
req.flash('error', 'Not correct user');
|
req.flash('error', 'Not correct user')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
const doc = await db.get(req.user._id);
|
const doc = await db.get(req.user._id)
|
||||||
const wishlist = doc.wishlist;
|
const wishlist = doc.wishlist
|
||||||
if (req.params.direction === 'up') wishlist.reverse();
|
if (req.params.direction === 'up') wishlist.reverse()
|
||||||
let moveFromIndex;
|
let moveFromIndex
|
||||||
wishlist.forEach(wish => {
|
wishlist.forEach(wish => {
|
||||||
if (wish.id === req.params.itemId) return moveFromIndex = wishlist.indexOf(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 ));
|
const moveToIndex = wishlist.findIndex(wish => (wishlist.indexOf(wish) > moveFromIndex && wish.addedBy === req.user._id))
|
||||||
if (moveToIndex < 0 || moveToIndex > wishlist.length) {
|
if (moveToIndex < 0 || moveToIndex > wishlist.length) {
|
||||||
req.flash('error', 'Invalid move');
|
req.flash('error', 'Invalid move')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
[ wishlist[moveFromIndex], wishlist[moveToIndex] ] = [ wishlist[moveToIndex], wishlist[moveFromIndex] ];
|
[wishlist[moveFromIndex], wishlist[moveToIndex]] = [wishlist[moveToIndex], wishlist[moveFromIndex]]
|
||||||
if (req.params.direction === 'up') wishlist.reverse();
|
if (req.params.direction === 'up') wishlist.reverse()
|
||||||
doc.wishlist = wishlist;
|
doc.wishlist = wishlist
|
||||||
await db.put(doc);
|
await db.put(doc)
|
||||||
req.flash('success', 'Successfully moved item!');
|
req.flash('success', 'Successfully moved item!')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
});
|
})
|
||||||
|
|
||||||
router.get('/:user/note/:id', verifyAuth(), async (req, res) => {
|
router.get('/:user/note/:id', verifyAuth(), async (req, res) => {
|
||||||
const doc = await db.get(req.params.user);
|
const doc = await db.get(req.params.user)
|
||||||
const item = doc.wishlist.find(item => item.id === req.params.id)
|
const item = doc.wishlist.find(item => item.id === req.params.id)
|
||||||
res.render('note', { item });
|
res.render('note', { item })
|
||||||
});
|
})
|
||||||
router.post('/:user/note/:id', verifyAuth(), async (req, res) => {
|
router.post('/:user/note/:id', verifyAuth(), async (req, res) => {
|
||||||
const doc = await db.get(req.params.user);
|
const doc = await db.get(req.params.user)
|
||||||
const wishlist = doc.wishlist;
|
const wishlist = doc.wishlist
|
||||||
for (let i=0; i < wishlist.length; i++) {
|
for (let i = 0; i < wishlist.length; i++) {
|
||||||
wishlistItem = wishlist[i];
|
const wishlistItem = wishlist[i]
|
||||||
if (wishlistItem.id !== req.params.id) continue;
|
if (wishlistItem.id !== req.params.id) continue
|
||||||
if (req.user._id !== req.params.user && req.user._id !== wishlistItem.addedBy) {
|
if (req.user._id !== req.params.user && req.user._id !== wishlistItem.addedBy) {
|
||||||
req.flash('error', 'Invalid user');
|
req.flash('error', 'Invalid user')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
for (const type of [
|
for (const type of [
|
||||||
'name', 'note', 'url', 'price', 'image'
|
'name', 'note', 'url', 'price', 'image'
|
||||||
]) {
|
]) {
|
||||||
if (!req.body.hasOwnProperty(type)) {
|
if (!Object.prototype.hasOwnProperty.call(req.body, type)) {
|
||||||
req.flash('error', `Missing property ${type}`)
|
req.flash('error', `Missing property ${type}`)
|
||||||
return res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`)
|
return res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`)
|
||||||
}
|
}
|
||||||
wishlistItem[type] = req.body[type]
|
wishlistItem[type] = req.body[type]
|
||||||
}
|
}
|
||||||
wishlist[i] = wishlistItem;
|
wishlist[i] = wishlistItem
|
||||||
}
|
}
|
||||||
doc.wishlist = wishlist;
|
doc.wishlist = wishlist
|
||||||
await db.put(doc);
|
await db.put(doc)
|
||||||
req.flash('success', `Successfully saved note!`);
|
req.flash('success', 'Successfully saved note!')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
});
|
})
|
||||||
router.post('/:user/refresh/:id', verifyAuth(), async (req, res) => {
|
router.post('/:user/refresh/:id', verifyAuth(), async (req, res) => {
|
||||||
const doc = await db.get(req.params.user);
|
const doc = await db.get(req.params.user)
|
||||||
const wishlist = doc.wishlist;
|
const wishlist = doc.wishlist
|
||||||
for (let i=0; i < wishlist.length; i++) {
|
for (let i = 0; i < wishlist.length; i++) {
|
||||||
wishlistItem = wishlist[i];
|
const wishlistItem = wishlist[i]
|
||||||
if (wishlistItem.id !== req.params.id) continue;
|
if (wishlistItem.id !== req.params.id) continue
|
||||||
if (req.user._id !== req.params.user && req.user._id !== wishlistItem.addedBy) {
|
if (req.user._id !== req.params.user && req.user._id !== wishlistItem.addedBy) {
|
||||||
req.flash('error', 'Invalid user');
|
req.flash('error', 'Invalid user')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wishlistItem.url) {
|
if (!wishlistItem.url) {
|
||||||
|
@ -240,38 +240,39 @@ module.exports = (db) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const productData = await getProductName(wishlistItem.url)
|
const productData = await getProductName(wishlistItem.url)
|
||||||
for (field of [ 'name', 'price', 'image' ]) {
|
for (const field of ['name', 'price', 'image']) {
|
||||||
if (productData[field]) wishlistItem[field] = productData[field]
|
if (productData[field]) wishlistItem[field] = productData[field]
|
||||||
}
|
}
|
||||||
|
|
||||||
wishlist[i] = wishlistItem;
|
wishlist[i] = wishlistItem
|
||||||
}
|
}
|
||||||
doc.wishlist = wishlist;
|
doc.wishlist = wishlist
|
||||||
await db.put(doc);
|
await db.put(doc)
|
||||||
req.flash('success', `Successfully refreshed data!`);
|
req.flash('success', 'Successfully refreshed data!')
|
||||||
return res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`);
|
return res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`)
|
||||||
});
|
})
|
||||||
router.post('/:user/note/remove/:id', verifyAuth(), async (req, res) => {
|
router.post('/:user/note/remove/:id', verifyAuth(), async (req, res) => {
|
||||||
const doc = await db.get(req.params.user);
|
const doc = await db.get(req.params.user)
|
||||||
const wishlist = doc.wishlist;
|
const wishlist = doc.wishlist
|
||||||
for (let i=0; i < wishlist.length; i++) {
|
for (let i = 0; i < wishlist.length; i++) {
|
||||||
wishlistItem = wishlist[i];
|
const wishlistItem = wishlist[i]
|
||||||
if (wishlistItem.id !== req.params.id) continue;
|
if (wishlistItem.id !== req.params.id) continue
|
||||||
if (req.user._id !== req.params.user && req.user._id !== wishlistItem.addedBy) {
|
if (req.user._id !== req.params.user && req.user._id !== wishlistItem.addedBy) {
|
||||||
req.flash('error', 'Invalid user');
|
req.flash('error', 'Invalid user')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
if (wishlistItem.note) {
|
if (wishlistItem.note) {
|
||||||
wishlistItem.note = undefined;
|
wishlistItem.note = undefined
|
||||||
wishlist[i] = wishlistItem;
|
wishlist[i] = wishlistItem
|
||||||
} else {
|
} else {
|
||||||
req.flash('error', 'Has no note');
|
req.flash('error', 'Has no note')
|
||||||
return res.redirect(`/wishlist/${req.params.user}`); }
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
}
|
}
|
||||||
doc.wishlist = wishlist;
|
}
|
||||||
await db.put(doc);
|
doc.wishlist = wishlist
|
||||||
req.flash('success', 'Successfully removed note');
|
await db.put(doc)
|
||||||
return res.redirect(`/wishlist/${req.params.user}`);
|
req.flash('success', 'Successfully removed note')
|
||||||
|
return res.redirect(`/wishlist/${req.params.user}`)
|
||||||
})
|
})
|
||||||
return router;
|
return router
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
const burger = document.getElementById('navBarBurger');
|
const burger = document.getElementById('navBarBurger')
|
||||||
const navBarMenu = document.getElementById('navBarMenu');
|
const navBarMenu = document.getElementById('navBarMenu')
|
||||||
burger.addEventListener('click', () => {
|
burger.addEventListener('click', () => {
|
||||||
burger.classList.toggle('is-active');
|
burger.classList.toggle('is-active')
|
||||||
navBarMenu.classList.toggle('is-active');
|
navBarMenu.classList.toggle('is-active')
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
function animateCSS(node, animationName) {
|
/* eslint-env browser */
|
||||||
|
function animateCSS (node, animationName) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
node.classList.add('animated', animationName)
|
node.classList.add('animated', animationName)
|
||||||
|
|
||||||
function handleAnimationEnd() {
|
function handleAnimationEnd () {
|
||||||
node.classList.remove('animated', animationName)
|
node.classList.remove('animated', animationName)
|
||||||
node.removeEventListener('animationend', handleAnimationEnd)
|
node.removeEventListener('animationend', handleAnimationEnd)
|
||||||
|
|
||||||
|
@ -15,16 +16,14 @@ function animateCSS(node, animationName) {
|
||||||
|
|
||||||
// These move function are stolen from
|
// These move function are stolen from
|
||||||
// https://stackoverflow.com/a/34914096
|
// https://stackoverflow.com/a/34914096
|
||||||
function moveUp(element) {
|
function moveUp (element) {
|
||||||
if(element.previousElementSibling)
|
if (element.previousElementSibling) { element.parentNode.insertBefore(element, element.previousElementSibling) }
|
||||||
element.parentNode.insertBefore(element, element.previousElementSibling);
|
|
||||||
}
|
}
|
||||||
function moveDown(element) {
|
function moveDown (element) {
|
||||||
if(element.nextElementSibling)
|
if (element.nextElementSibling) { element.parentNode.insertBefore(element.nextElementSibling, element) }
|
||||||
element.parentNode.insertBefore(element.nextElementSibling, element);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function listen(element, upOrDown) {
|
function listen (element, upOrDown) {
|
||||||
element.addEventListener('submit', async event => {
|
element.addEventListener('submit', async event => {
|
||||||
try {
|
try {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
@ -66,8 +65,8 @@ function listen(element, upOrDown) {
|
||||||
return false
|
return false
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(error.message)
|
alert(error.message)
|
||||||
throw error
|
|
||||||
location.reload()
|
location.reload()
|
||||||
|
throw error // probably useless but just in case reload doesn't do anything
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue