mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-05-07 16:38:38 +00:00
Merge branch 'master' into HTMLDownload
This commit is contained in:
+245
-227
@@ -4,64 +4,69 @@ import { model as NotificationModel } from './notifications.model.js';
|
||||
import express from 'express';
|
||||
import Moment from 'moment';
|
||||
import zlib from 'zlib';
|
||||
import templateFn from '../client/template.js';
|
||||
import config from './config.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
const nodeEnv = config.get('node_env');
|
||||
const isProd = nodeEnv === 'production';
|
||||
|
||||
import HomebrewAPI from './homebrew.api.js';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import { splitTextStyleAndMetadata } from '../shared/helpers.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
||||
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
|
||||
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password3';
|
||||
|
||||
const mw = {
|
||||
adminOnly : (req, res, next)=>{
|
||||
if(!req.get('authorization')){
|
||||
return res
|
||||
export default function createAdminApi(vite) {
|
||||
const router = express.Router();
|
||||
|
||||
const mw = {
|
||||
adminOnly : (req, res, next)=>{
|
||||
if(!req.get('authorization')){
|
||||
return res
|
||||
.set('WWW-Authenticate', 'Basic realm="Authorization Required"')
|
||||
.status(401)
|
||||
.send('Authorization Required');
|
||||
}
|
||||
const [username, password] = Buffer.from(req.get('authorization').split(' ').pop(), 'base64')
|
||||
}
|
||||
const [username, password] = Buffer.from(req.get('authorization').split(' ').pop(), 'base64')
|
||||
.toString('ascii')
|
||||
.split(':');
|
||||
if(process.env.ADMIN_USER === username && process.env.ADMIN_PASS === password){
|
||||
return next();
|
||||
if(process.env.ADMIN_USER === username && process.env.ADMIN_PASS === password){
|
||||
return next();
|
||||
}
|
||||
throw { HBErrorCode: '52', code: 401, message: 'Access denied' };
|
||||
}
|
||||
throw { HBErrorCode: '52', code: 401, message: 'Access denied' };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const junkBrewPipeline = [
|
||||
{ $match : {
|
||||
updatedAt : { $lt: Moment().subtract(30, 'days').toDate() },
|
||||
lastViewed : { $lt: Moment().subtract(30, 'days').toDate() }
|
||||
} },
|
||||
{ $project: { textBinSize: { $binarySize: '$textBin' } } },
|
||||
{ $match: { textBinSize: { $lt: 140 } } },
|
||||
{ $limit: 100 }
|
||||
];
|
||||
const junkBrewPipeline = [
|
||||
{ $match : {
|
||||
updatedAt : { $lt: Moment().subtract(30, 'days').toDate() },
|
||||
lastViewed : { $lt: Moment().subtract(30, 'days').toDate() }
|
||||
} },
|
||||
{ $project: { textBinSize: { $binarySize: '$textBin' } } },
|
||||
{ $match: { textBinSize: { $lt: 140 } } },
|
||||
{ $limit: 100 }
|
||||
];
|
||||
|
||||
/* Search for brews that aren't compressed (missing the compressed text field) */
|
||||
const uncompressedBrewQuery = HomebrewModel.find({
|
||||
'text' : { '$exists': true }
|
||||
}).lean().limit(10000).select('_id');
|
||||
/* Search for brews that aren't compressed (missing the compressed text field) */
|
||||
const uncompressedBrewQuery = HomebrewModel.find({
|
||||
'text' : { '$exists': true }
|
||||
}).lean().limit(10000).select('_id');
|
||||
|
||||
// Search for up to 100 brews that have not been viewed or updated in 30 days and are shorter than 140 bytes
|
||||
router.get('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
||||
HomebrewModel.aggregate(junkBrewPipeline).option({ maxTimeMS: 60000 })
|
||||
// Search for up to 100 brews that have not been viewed or updated in 30 days and are shorter than 140 bytes
|
||||
router.get('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
||||
HomebrewModel.aggregate(junkBrewPipeline).option({ maxTimeMS: 60000 })
|
||||
.then((objs)=>res.json({ count: objs.length }))
|
||||
.catch((error)=>{
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Delete up to 100 brews that have not been viewed or updated in 30 days and are shorter than 140 bytes
|
||||
router.post('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
||||
HomebrewModel.aggregate(junkBrewPipeline).option({ maxTimeMS: 60000 })
|
||||
// Delete up to 100 brews that have not been viewed or updated in 30 days and are shorter than 140 bytes
|
||||
router.post('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
||||
HomebrewModel.aggregate(junkBrewPipeline).option({ maxTimeMS: 60000 })
|
||||
.then((docs)=>{
|
||||
const ids = docs.map((doc)=>doc._id);
|
||||
return HomebrewModel.deleteMany({ _id: { $in: ids } });
|
||||
@@ -71,18 +76,18 @@ router.post('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* Searches for matching edit or share id, also attempts to partial match */
|
||||
router.get('/admin/lookup/:id', mw.adminOnly, asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res, next)=>{
|
||||
return res.json(req.brew);
|
||||
});
|
||||
/* Searches for matching edit or share id, also attempts to partial match */
|
||||
router.get('/admin/lookup/:id', mw.adminOnly, asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res, next)=>{
|
||||
return res.json(req.brew);
|
||||
});
|
||||
|
||||
/* Find 50 brews that aren't compressed yet */
|
||||
router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
|
||||
const query = uncompressedBrewQuery.clone();
|
||||
/* Find 50 brews that aren't compressed yet */
|
||||
router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
|
||||
const query = uncompressedBrewQuery.clone();
|
||||
|
||||
query.exec()
|
||||
query.exec()
|
||||
.then((objs)=>{
|
||||
const ids = objs.map((obj)=>obj._id);
|
||||
res.json({ count: ids.length, ids });
|
||||
@@ -91,46 +96,46 @@ router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
|
||||
console.error(err);
|
||||
res.status(500).send(err.message || 'Internal Server Error');
|
||||
});
|
||||
});
|
||||
|
||||
/* Cleans `<script` and `</script>` from the "text" field of a brew */
|
||||
router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res)=>{
|
||||
console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Cleaning script tags from ShareID ${req.params.id}`);
|
||||
|
||||
function cleanText(text){return text.replaceAll(/(<\/?s)cript/gi, '');};
|
||||
|
||||
const brew = req.brew;
|
||||
|
||||
const properties = ['text', 'description', 'title'];
|
||||
properties.forEach((property)=>{
|
||||
brew[property] = cleanText(brew[property]);
|
||||
});
|
||||
|
||||
splitTextStyleAndMetadata(brew);
|
||||
/* Cleans `<script` and `</script>` from the "text" field of a brew */
|
||||
router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res)=>{
|
||||
console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Cleaning script tags from ShareID ${req.params.id}`);
|
||||
|
||||
req.body = brew;
|
||||
function cleanText(text){return text.replaceAll(/(<\/?s)cript/gi, '');};
|
||||
|
||||
// Remove Account from request to prevent Admin user from being added to brew as an Author
|
||||
req.account = undefined;
|
||||
const brew = req.brew;
|
||||
|
||||
return await HomebrewAPI.updateBrew(req, res);
|
||||
});
|
||||
const properties = ['text', 'description', 'title'];
|
||||
properties.forEach((property)=>{
|
||||
brew[property] = cleanText(brew[property]);
|
||||
});
|
||||
|
||||
/* Get list of a user's documents */
|
||||
router.get('/admin/user/list/:user', mw.adminOnly, async (req, res)=>{
|
||||
const username = req.params.user;
|
||||
const fields = { _id: 0, text: 0, textBin: 0 }; // Remove unnecessary fields from document lists
|
||||
splitTextStyleAndMetadata(brew);
|
||||
|
||||
console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Get brew list for ${username}`);
|
||||
req.body = brew;
|
||||
|
||||
const brews = await HomebrewModel.getByUser(username, true, fields);
|
||||
// Remove Account from request to prevent Admin user from being added to brew as an Author
|
||||
req.account = undefined;
|
||||
|
||||
return res.json(brews);
|
||||
});
|
||||
return await HomebrewAPI.updateBrew(req, res);
|
||||
});
|
||||
|
||||
/* Compresses the "text" field of a brew to binary */
|
||||
router.put('/admin/compress/:id', (req, res)=>{
|
||||
HomebrewModel.findOne({ _id: req.params.id })
|
||||
/* Get list of a user's documents */
|
||||
router.get('/admin/user/list/:user', mw.adminOnly, async (req, res)=>{
|
||||
const username = req.params.user;
|
||||
const fields = { _id: 0, text: 0, textBin: 0 }; // Remove unnecessary fields from document lists
|
||||
|
||||
console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Get brew list for ${username}`);
|
||||
|
||||
const brews = await HomebrewModel.getByUser(username, true, fields);
|
||||
|
||||
return res.json(brews);
|
||||
});
|
||||
|
||||
/* Compresses the "text" field of a brew to binary */
|
||||
router.put('/admin/compress/:id', (req, res)=>{
|
||||
HomebrewModel.findOne({ _id: req.params.id })
|
||||
.then((brew)=>{
|
||||
if(!brew)
|
||||
return res.status(404).send('Brew not found');
|
||||
@@ -147,239 +152,252 @@ router.put('/admin/compress/:id', (req, res)=>{
|
||||
console.error(err);
|
||||
res.status(500).send('Error while saving');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/admin/stats', mw.adminOnly, async (req, res)=>{
|
||||
try {
|
||||
const totalBrewsCount = await HomebrewModel.countDocuments({});
|
||||
const publishedBrewsCount = await HomebrewModel.countDocuments({ published: true });
|
||||
router.get('/admin/stats', mw.adminOnly, async (req, res)=>{
|
||||
try {
|
||||
const totalBrewsCount = await HomebrewModel.countDocuments({});
|
||||
const publishedBrewsCount = await HomebrewModel.countDocuments({ published: true });
|
||||
|
||||
return res.json({
|
||||
totalBrews : totalBrewsCount,
|
||||
totalPublishedBrews : publishedBrewsCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
return res.json({
|
||||
totalBrews : totalBrewsCount,
|
||||
totalPublishedBrews : publishedBrewsCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
// ####################### LOCKS
|
||||
// ####################### LOCKS
|
||||
|
||||
router.get('/api/lock/count', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
router.get('/api/lock/count', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
|
||||
const countLocksQuery = {
|
||||
lock : { $exists: true }
|
||||
};
|
||||
const count = await HomebrewModel.countDocuments(countLocksQuery)
|
||||
const countLocksQuery = {
|
||||
lock : { $exists: true }
|
||||
};
|
||||
const count = await HomebrewModel.countDocuments(countLocksQuery)
|
||||
.catch((error)=>{
|
||||
throw { name: 'Lock Count Error', message: 'Unable to get lock count', status: 500, HBErrorCode: '61', error };
|
||||
});
|
||||
|
||||
return res.json({ count });
|
||||
return res.json({ count });
|
||||
|
||||
}));
|
||||
}));
|
||||
|
||||
router.get('/api/locks', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
const countLocksPipeline = [
|
||||
{
|
||||
router.get('/api/locks', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
const countLocksPipeline = [
|
||||
{
|
||||
$match :
|
||||
{
|
||||
'lock' : { '$exists': 1 }
|
||||
},
|
||||
},
|
||||
{
|
||||
$project : {
|
||||
shareId : 1,
|
||||
editId : 1,
|
||||
title : 1,
|
||||
lock : 1
|
||||
},
|
||||
{
|
||||
$project : {
|
||||
shareId : 1,
|
||||
editId : 1,
|
||||
title : 1,
|
||||
lock : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
const lockedDocuments = await HomebrewModel.aggregate(countLocksPipeline)
|
||||
];
|
||||
const lockedDocuments = await HomebrewModel.aggregate(countLocksPipeline)
|
||||
.catch((error)=>{
|
||||
throw { name: 'Can Not Get Locked Brews', message: 'Unable to get locked brew collection', status: 500, HBErrorCode: '68', error };
|
||||
});
|
||||
return res.json({
|
||||
lockedDocuments
|
||||
});
|
||||
return res.json({
|
||||
lockedDocuments
|
||||
});
|
||||
|
||||
}));
|
||||
}));
|
||||
|
||||
router.post('/api/lock/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
router.post('/api/lock/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
|
||||
const lock = req.body;
|
||||
const lock = req.body;
|
||||
|
||||
lock.applied = new Date;
|
||||
lock.applied = new Date;
|
||||
|
||||
const filter = {
|
||||
shareId : req.params.id
|
||||
};
|
||||
const filter = {
|
||||
shareId : req.params.id
|
||||
};
|
||||
|
||||
const brew = await HomebrewModel.findOne(filter);
|
||||
const brew = await HomebrewModel.findOne(filter);
|
||||
|
||||
if(!brew) throw { name: 'Brew Not Found', message: 'Cannot find brew to lock', shareId: req.params.id, status: 500, HBErrorCode: '63' };
|
||||
if(!brew) throw { name: 'Brew Not Found', message: 'Cannot find brew to lock', shareId: req.params.id, status: 500, HBErrorCode: '63' };
|
||||
|
||||
if(brew.lock && !lock.overwrite) {
|
||||
throw { name: 'Already Locked', message: 'Lock already exists on brew', shareId: req.params.id, title: brew.title, status: 500, HBErrorCode: '64' };
|
||||
}
|
||||
if(brew.lock && !lock.overwrite) {
|
||||
throw { name: 'Already Locked', message: 'Lock already exists on brew', shareId: req.params.id, title: brew.title, status: 500, HBErrorCode: '64' };
|
||||
}
|
||||
|
||||
lock.overwrite = undefined;
|
||||
lock.overwrite = undefined;
|
||||
|
||||
brew.lock = lock;
|
||||
brew.markModified('lock');
|
||||
brew.lock = lock;
|
||||
brew.markModified('lock');
|
||||
|
||||
await brew.save()
|
||||
await brew.save()
|
||||
.catch((error)=>{
|
||||
throw { name: 'Lock Error', message: 'Unable to set lock', shareId: req.params.id, status: 500, HBErrorCode: '62', error };
|
||||
});
|
||||
|
||||
return res.json({ name: 'LOCKED', message: `Lock applied to brew ID ${brew.shareId} - ${brew.title}`, ...lock });
|
||||
return res.json({ name: 'LOCKED', message: `Lock applied to brew ID ${brew.shareId} - ${brew.title}`, ...lock });
|
||||
|
||||
}));
|
||||
}));
|
||||
|
||||
router.put('/api/unlock/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
router.put('/api/unlock/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
|
||||
const filter = {
|
||||
shareId : req.params.id
|
||||
};
|
||||
const filter = {
|
||||
shareId : req.params.id
|
||||
};
|
||||
|
||||
const brew = await HomebrewModel.findOne(filter);
|
||||
const brew = await HomebrewModel.findOne(filter);
|
||||
|
||||
if(!brew) throw { name: 'Brew Not Found', message: 'Cannot find brew to unlock', shareId: req.params.id, status: 500, HBErrorCode: '66' };
|
||||
if(!brew) throw { name: 'Brew Not Found', message: 'Cannot find brew to unlock', shareId: req.params.id, status: 500, HBErrorCode: '66' };
|
||||
|
||||
if(!brew.lock) throw { name: 'Not Locked', message: 'Cannot unlock as brew is not locked', shareId: req.params.id, status: 500, HBErrorCode: '67' };
|
||||
if(!brew.lock) throw { name: 'Not Locked', message: 'Cannot unlock as brew is not locked', shareId: req.params.id, status: 500, HBErrorCode: '67' };
|
||||
|
||||
brew.lock = undefined;
|
||||
brew.markModified('lock');
|
||||
brew.lock = undefined;
|
||||
brew.markModified('lock');
|
||||
|
||||
await brew.save()
|
||||
await brew.save()
|
||||
.catch((error)=>{
|
||||
throw { name: 'Cannot Unlock', message: 'Unable to clear lock', shareId: req.params.id, status: 500, HBErrorCode: '65', error };
|
||||
});
|
||||
|
||||
return res.json({ name: 'Unlocked', message: `Lock removed from brew ID ${req.params.id}` });
|
||||
}));
|
||||
return res.json({ name: 'Unlocked', message: `Lock removed from brew ID ${req.params.id}` });
|
||||
}));
|
||||
|
||||
router.get('/api/lock/reviews', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
const countReviewsPipeline = [
|
||||
{
|
||||
router.get('/api/lock/reviews', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
const countReviewsPipeline = [
|
||||
{
|
||||
$match :
|
||||
{
|
||||
'lock.reviewRequested' : { '$exists': 1 }
|
||||
},
|
||||
},
|
||||
{
|
||||
$project : {
|
||||
shareId : 1,
|
||||
editId : 1,
|
||||
title : 1,
|
||||
lock : 1
|
||||
},
|
||||
{
|
||||
$project : {
|
||||
shareId : 1,
|
||||
editId : 1,
|
||||
title : 1,
|
||||
lock : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
const reviewDocuments = await HomebrewModel.aggregate(countReviewsPipeline)
|
||||
];
|
||||
const reviewDocuments = await HomebrewModel.aggregate(countReviewsPipeline)
|
||||
.catch((error)=>{
|
||||
throw { name: 'Can Not Get Reviews', message: 'Unable to get review collection', status: 500, HBErrorCode: '68', error };
|
||||
});
|
||||
return res.json({
|
||||
reviewDocuments
|
||||
});
|
||||
return res.json({
|
||||
reviewDocuments
|
||||
});
|
||||
|
||||
}));
|
||||
}));
|
||||
|
||||
router.put('/api/lock/review/request/:id', asyncHandler(async (req, res)=>{
|
||||
router.put('/api/lock/review/request/:id', asyncHandler(async (req, res)=>{
|
||||
// === This route is NOT Admin only ===
|
||||
// Any user can request a review of their document
|
||||
const filter = {
|
||||
shareId : req.params.id,
|
||||
lock : { $exists: 1 }
|
||||
};
|
||||
const filter = {
|
||||
shareId : req.params.id,
|
||||
lock : { $exists: 1 }
|
||||
};
|
||||
|
||||
const brew = await HomebrewModel.findOne(filter);
|
||||
if(!brew) { throw { name: 'Brew Not Found', message: `Cannot find a locked brew with ID ${req.params.id}`, code: 500, HBErrorCode: '70' }; };
|
||||
const brew = await HomebrewModel.findOne(filter);
|
||||
if(!brew) { throw { name: 'Brew Not Found', message: `Cannot find a locked brew with ID ${req.params.id}`, code: 500, HBErrorCode: '70' }; };
|
||||
|
||||
if(brew.lock.reviewRequested){
|
||||
throw { name: 'Review Already Requested', message: `Review already requested for brew ${brew.shareId} - ${brew.title}`, code: 500, HBErrorCode: '71' };
|
||||
};
|
||||
if(brew.lock.reviewRequested){
|
||||
throw { name: 'Review Already Requested', message: `Review already requested for brew ${brew.shareId} - ${brew.title}`, code: 500, HBErrorCode: '71' };
|
||||
};
|
||||
|
||||
brew.lock.reviewRequested = new Date();
|
||||
brew.markModified('lock');
|
||||
brew.lock.reviewRequested = new Date();
|
||||
brew.markModified('lock');
|
||||
|
||||
await brew.save()
|
||||
await brew.save()
|
||||
.catch((error)=>{
|
||||
throw { name: 'Can Not Set Review Request', message: `Unable to set request for review on brew ID ${req.params.id}`, code: 500, HBErrorCode: '69', error };
|
||||
});
|
||||
|
||||
return res.json({ name: 'Review Requested', message: `Review requested on brew ID ${brew.shareId} - ${brew.title}` });
|
||||
return res.json({ name: 'Review Requested', message: `Review requested on brew ID ${brew.shareId} - ${brew.title}` });
|
||||
|
||||
}));
|
||||
}));
|
||||
|
||||
router.put('/api/lock/review/remove/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
router.put('/api/lock/review/remove/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
|
||||
const filter = {
|
||||
shareId : req.params.id,
|
||||
'lock.reviewRequested' : { $exists: 1 }
|
||||
};
|
||||
const filter = {
|
||||
shareId : req.params.id,
|
||||
'lock.reviewRequested' : { $exists: 1 }
|
||||
};
|
||||
|
||||
const brew = await HomebrewModel.findOne(filter);
|
||||
if(!brew) { throw { name: 'Can Not Clear Review Request', message: `Brew ID ${req.params.id} does not have a review pending!`, HBErrorCode: '73' }; };
|
||||
const brew = await HomebrewModel.findOne(filter);
|
||||
if(!brew) { throw { name: 'Can Not Clear Review Request', message: `Brew ID ${req.params.id} does not have a review pending!`, HBErrorCode: '73' }; };
|
||||
|
||||
brew.lock.reviewRequested = undefined;
|
||||
brew.markModified('lock');
|
||||
brew.lock.reviewRequested = undefined;
|
||||
brew.markModified('lock');
|
||||
|
||||
await brew.save()
|
||||
await brew.save()
|
||||
.catch((error)=>{
|
||||
throw { name: 'Can Not Clear Review Request', message: `Unable to remove request for review on brew ID ${req.params.id}`, HBErrorCode: '72', error };
|
||||
});
|
||||
|
||||
return res.json({ name: 'Review Request Cleared', message: `Review request removed for brew ID ${brew.shareId} - ${brew.title}` });
|
||||
return res.json({ name: 'Review Request Cleared', message: `Review request removed for brew ID ${brew.shareId} - ${brew.title}` });
|
||||
|
||||
}));
|
||||
}));
|
||||
|
||||
// ####################### NOTIFICATIONS
|
||||
// ####################### NOTIFICATIONS
|
||||
|
||||
router.get('/admin/notification/all', async (req, res, next)=>{
|
||||
try {
|
||||
const notifications = await NotificationModel.getAll();
|
||||
return res.json(notifications);
|
||||
router.get('/admin/notification/all', async (req, res, next)=>{
|
||||
try {
|
||||
const notifications = await NotificationModel.getAll();
|
||||
return res.json(notifications);
|
||||
|
||||
} catch (error) {
|
||||
console.log('Error getting all notifications: ', error.message);
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/admin/notification/add', mw.adminOnly, async (req, res, next)=>{
|
||||
try {
|
||||
const notification = await NotificationModel.addNotification(req.body);
|
||||
return res.status(201).json(notification);
|
||||
} catch (error) {
|
||||
console.log('Error adding notification: ', error.message);
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/admin/notification/delete/:id', mw.adminOnly, async (req, res, next)=>{
|
||||
try {
|
||||
const notification = await NotificationModel.deleteNotification(req.params.id);
|
||||
return res.json(notification);
|
||||
} catch (error) {
|
||||
console.error('Error deleting notification: { key: ', req.params.id, ' error: ', error.message, ' }');
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/admin', mw.adminOnly, (req, res)=>{
|
||||
templateFn('admin', {
|
||||
url : req.originalUrl
|
||||
})
|
||||
.then((page)=>res.send(page))
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
res.sendStatus(500);
|
||||
} catch (error) {
|
||||
console.log('Error getting all notifications: ', error.message);
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
router.post('/admin/notification/add', mw.adminOnly, async (req, res, next)=>{
|
||||
try {
|
||||
const notification = await NotificationModel.addNotification(req.body);
|
||||
return res.status(201).json(notification);
|
||||
} catch (error) {
|
||||
console.log('Error adding notification: ', error.message);
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/admin/notification/delete/:id', mw.adminOnly, async (req, res, next)=>{
|
||||
try {
|
||||
const notification = await NotificationModel.deleteNotification(req.params.id);
|
||||
return res.json(notification);
|
||||
} catch (error) {
|
||||
console.error('Error deleting notification: { key: ', req.params.id, ' error: ', error.message, ' }');
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/admin', mw.adminOnly, asyncHandler(async (req, res)=>{
|
||||
const props = {
|
||||
url : req.originalUrl
|
||||
};
|
||||
|
||||
const htmlPath = isProd
|
||||
? path.resolve('build', 'index.html')
|
||||
: path.resolve('index.html');
|
||||
|
||||
let html = fs.readFileSync(htmlPath, 'utf-8');
|
||||
|
||||
if(!isProd && vite?.transformIndexHtml) {
|
||||
html = await vite.transformIndexHtml(req.originalUrl, html);
|
||||
}
|
||||
|
||||
res.send(html.replace(
|
||||
'<head>',
|
||||
`<head>\n<script id="props">window.__INITIAL_PROPS__ = ${JSON.stringify(props)}</script>`
|
||||
));
|
||||
}));
|
||||
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
|
||||
+160
-228
@@ -1,42 +1,45 @@
|
||||
/*eslint max-lines: ["warn", {"max": 1000, "skipBlankLines": true, "skipComments": true}]*/
|
||||
import mongoose from 'mongoose';
|
||||
import supertest from 'supertest';
|
||||
import HBApp from './app.js';
|
||||
import createApp from './app.js';
|
||||
import { model as NotificationModel } from './notifications.model.js';
|
||||
import { model as HomebrewModel } from './homebrew.model.js';
|
||||
|
||||
|
||||
// Mimic https responses to avoid being redirected all the time
|
||||
const app = supertest.agent(HBApp).set('X-Forwarded-Proto', 'https');
|
||||
|
||||
let app;
|
||||
let request;
|
||||
let dbState;
|
||||
|
||||
beforeAll(async ()=>{
|
||||
app = await createApp();
|
||||
request = supertest.agent(app).set('X-Forwarded-Proto', 'https');
|
||||
});
|
||||
|
||||
describe('Tests for admin api', ()=>{
|
||||
beforeEach(()=>{
|
||||
// Mock DB ready (for dbCheck middleware)
|
||||
dbState = mongoose.connection.readyState;
|
||||
mongoose.connection.readyState = 1;
|
||||
});
|
||||
|
||||
afterEach(()=>{
|
||||
// Restore DB ready state
|
||||
mongoose.connection.readyState = dbState;
|
||||
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
afterAll(async ()=>{
|
||||
await mongoose.connection.close();
|
||||
});
|
||||
|
||||
describe('Notifications', ()=>{
|
||||
it('should return list of all notifications', async ()=>{
|
||||
const testNotifications = ['a', 'b'];
|
||||
|
||||
jest.spyOn(NotificationModel, 'find')
|
||||
.mockImplementationOnce(()=>{
|
||||
jest.spyOn(NotificationModel, 'find').mockImplementationOnce(()=>{
|
||||
return { exec: jest.fn().mockResolvedValue(testNotifications) };
|
||||
});
|
||||
|
||||
const response = await app
|
||||
.get('/admin/notification/all')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
const response = await request
|
||||
.get('/admin/notification/all')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual(testNotifications);
|
||||
@@ -56,18 +59,17 @@ describe('Tests for admin api', ()=>{
|
||||
_id : expect.any(String),
|
||||
createdAt : expect.any(String),
|
||||
startAt : inputNotification.startAt,
|
||||
stopAt : inputNotification.stopAt,
|
||||
stopAt : inputNotification.stopAt
|
||||
};
|
||||
|
||||
jest.spyOn(NotificationModel.prototype, 'save')
|
||||
.mockImplementationOnce(function() {
|
||||
return Promise.resolve(this);
|
||||
});
|
||||
jest.spyOn(NotificationModel.prototype, 'save').mockImplementationOnce(function () {
|
||||
return Promise.resolve(this);
|
||||
});
|
||||
|
||||
const response = await app
|
||||
.post('/admin/notification/add')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.send(inputNotification);
|
||||
const response = await request
|
||||
.post('/admin/notification/add')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.send(inputNotification);
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(response.body).toEqual(savedNotification);
|
||||
@@ -81,16 +83,14 @@ describe('Tests for admin api', ()=>{
|
||||
stopAt : new Date().toISOString()
|
||||
};
|
||||
|
||||
//Change 'save' function to just return itself instead of actually interacting with the database
|
||||
jest.spyOn(NotificationModel.prototype, 'save')
|
||||
.mockImplementationOnce(function() {
|
||||
return Promise.resolve(this);
|
||||
});
|
||||
jest.spyOn(NotificationModel.prototype, 'save').mockImplementationOnce(function () {
|
||||
return Promise.resolve(this);
|
||||
});
|
||||
|
||||
const response = await app
|
||||
.post('/admin/notification/add')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.send(inputNotification);
|
||||
const response = await request
|
||||
.post('/admin/notification/add')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.send(inputNotification);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ message: 'Dismiss key is required!' });
|
||||
@@ -99,15 +99,15 @@ describe('Tests for admin api', ()=>{
|
||||
it('should delete a notification based on its dismiss key', async ()=>{
|
||||
const dismissKey = 'testKey';
|
||||
|
||||
jest.spyOn(NotificationModel, 'findOneAndDelete')
|
||||
.mockImplementationOnce((key)=>{
|
||||
return { exec: jest.fn().mockResolvedValue(key) };
|
||||
});
|
||||
const response = await app
|
||||
.delete(`/admin/notification/delete/${dismissKey}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
jest.spyOn(NotificationModel, 'findOneAndDelete').mockImplementationOnce((key)=>{
|
||||
return { exec: jest.fn().mockResolvedValue(key) };
|
||||
});
|
||||
|
||||
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ 'dismissKey': 'testKey' });
|
||||
const response = await request
|
||||
.delete(`/admin/notification/delete/${dismissKey}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ dismissKey: 'testKey' });
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({ dismissKey: 'testKey' });
|
||||
});
|
||||
@@ -115,15 +115,15 @@ describe('Tests for admin api', ()=>{
|
||||
it('should handle error deleting a notification that doesnt exist', async ()=>{
|
||||
const dismissKey = 'testKey';
|
||||
|
||||
jest.spyOn(NotificationModel, 'findOneAndDelete')
|
||||
.mockImplementationOnce(()=>{
|
||||
return { exec: jest.fn().mockResolvedValue() };
|
||||
});
|
||||
const response = await app
|
||||
.delete(`/admin/notification/delete/${dismissKey}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
jest.spyOn(NotificationModel, 'findOneAndDelete').mockImplementationOnce(()=>{
|
||||
return { exec: jest.fn().mockResolvedValue() };
|
||||
});
|
||||
|
||||
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ 'dismissKey': 'testKey' });
|
||||
const response = await request
|
||||
.delete(`/admin/notification/delete/${dismissKey}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ dismissKey: 'testKey' });
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ message: 'Notification not found' });
|
||||
});
|
||||
@@ -132,30 +132,24 @@ describe('Tests for admin api', ()=>{
|
||||
describe('Locks', ()=>{
|
||||
describe('Count', ()=>{
|
||||
it('Count of all locked documents', async ()=>{
|
||||
const testNumber = 16777216; // 8^8, because why not
|
||||
const testNumber = 16777216;
|
||||
|
||||
jest.spyOn(HomebrewModel, 'countDocuments')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testNumber);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'countDocuments').mockImplementationOnce(()=>Promise.resolve(testNumber));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.get('/api/lock/count');
|
||||
const response = await request
|
||||
.get('/api/lock/count')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({ count: testNumber });
|
||||
});
|
||||
|
||||
it('Handle error while fetching count of locked documents', async ()=>{
|
||||
jest.spyOn(HomebrewModel, 'countDocuments')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.reject();
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'countDocuments').mockImplementationOnce(()=>Promise.reject());
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.get('/api/lock/count');
|
||||
const response = await request
|
||||
.get('/api/lock/count')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
@@ -163,7 +157,7 @@ describe('Tests for admin api', ()=>{
|
||||
message : 'Unable to get lock count',
|
||||
name : 'Lock Count Error',
|
||||
originalUrl : '/api/lock/count',
|
||||
status : 500,
|
||||
status : 500
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -172,28 +166,22 @@ describe('Tests for admin api', ()=>{
|
||||
it('Get list of all locked documents', async ()=>{
|
||||
const testLocks = ['a', 'b'];
|
||||
|
||||
jest.spyOn(HomebrewModel, 'aggregate')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testLocks);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'aggregate').mockImplementationOnce(()=>Promise.resolve(testLocks));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.get('/api/locks');
|
||||
const response = await request
|
||||
.get('/api/locks')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({ lockedDocuments: testLocks });
|
||||
});
|
||||
|
||||
it('Handle error while fetching list of all locked documents', async ()=>{
|
||||
jest.spyOn(HomebrewModel, 'aggregate')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.reject();
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'aggregate').mockImplementationOnce(()=>Promise.reject());
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.get('/api/locks');
|
||||
const response = await request
|
||||
.get('/api/locks')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
@@ -208,28 +196,22 @@ describe('Tests for admin api', ()=>{
|
||||
it('Get list of all locked documents with pending review requests', async ()=>{
|
||||
const testLocks = ['a', 'b'];
|
||||
|
||||
jest.spyOn(HomebrewModel, 'aggregate')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testLocks);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'aggregate').mockImplementationOnce(()=>Promise.resolve(testLocks));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.get('/api/lock/reviews');
|
||||
const response = await request
|
||||
.get('/api/lock/reviews')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({ reviewDocuments: testLocks });
|
||||
});
|
||||
|
||||
it('Handle error while fetching list of all locked documents with pending review requests', async ()=>{
|
||||
jest.spyOn(HomebrewModel, 'aggregate')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.reject();
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'aggregate').mockImplementationOnce(()=>Promise.reject());
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.get('/api/lock/reviews');
|
||||
const response = await request
|
||||
.get('/api/lock/reviews')
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
@@ -247,8 +229,8 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.resolve(); }
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.resolve()
|
||||
};
|
||||
|
||||
const testLock = {
|
||||
@@ -257,15 +239,12 @@ describe('Tests for admin api', ()=>{
|
||||
shareMessage : 'share'
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.post(`/api/lock/${testBrew.shareId}`)
|
||||
.send(testLock);
|
||||
const response = await request
|
||||
.post(`/api/lock/${testBrew.shareId}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.send(testLock);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toMatchObject({
|
||||
@@ -289,24 +268,21 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.resolve(); },
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.resolve(),
|
||||
lock : {
|
||||
code : 1,
|
||||
editMessage : 'oldEdit',
|
||||
shareMessage : 'oldShare',
|
||||
shareMessage : 'oldShare'
|
||||
}
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.post(`/api/lock/${testBrew.shareId}`)
|
||||
.send(testLock);
|
||||
const response = await request
|
||||
.post(`/api/lock/${testBrew.shareId}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.send(testLock);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toMatchObject({
|
||||
@@ -329,24 +305,21 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.resolve(); },
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.resolve(),
|
||||
lock : {
|
||||
code : 1,
|
||||
editMessage : 'oldEdit',
|
||||
shareMessage : 'oldShare',
|
||||
shareMessage : 'oldShare'
|
||||
}
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.post(`/api/lock/${testBrew.shareId}`)
|
||||
.send(testLock);
|
||||
const response = await request
|
||||
.post(`/api/lock/${testBrew.shareId}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.send(testLock);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
@@ -364,8 +337,8 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.reject(); }
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.reject()
|
||||
};
|
||||
|
||||
const testLock = {
|
||||
@@ -374,15 +347,12 @@ describe('Tests for admin api', ()=>{
|
||||
shareMessage : 'share'
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.post(`/api/lock/${testBrew.shareId}`)
|
||||
.send(testLock);
|
||||
const response = await request
|
||||
.post(`/api/lock/${testBrew.shareId}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.send(testLock);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
@@ -408,19 +378,17 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.resolve(); },
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.resolve(),
|
||||
lock : testLock
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.put(`/api/unlock/${testBrew.shareId}`);
|
||||
const response = await request.put(`/api/unlock/${testBrew.shareId}`).set(
|
||||
'Authorization',
|
||||
`Basic ${Buffer.from('admin:password3').toString('base64')}`
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({
|
||||
@@ -433,18 +401,16 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.resolve(); },
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.resolve()
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.put(`/api/unlock/${testBrew.shareId}`);
|
||||
const response = await request.put(`/api/unlock/${testBrew.shareId}`).set(
|
||||
'Authorization',
|
||||
`Basic ${Buffer.from('admin:password3').toString('base64')}`
|
||||
);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
@@ -453,7 +419,7 @@ describe('Tests for admin api', ()=>{
|
||||
name : 'Not Locked',
|
||||
originalUrl : `/api/unlock/${testBrew.shareId}`,
|
||||
shareId : testBrew.shareId,
|
||||
status : 500,
|
||||
status : 500
|
||||
});
|
||||
});
|
||||
|
||||
@@ -468,19 +434,17 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.reject(); },
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.reject(),
|
||||
lock : testLock
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.put(`/api/unlock/${testBrew.shareId}`);
|
||||
const response = await request.put(`/api/unlock/${testBrew.shareId}`).set(
|
||||
'Authorization',
|
||||
`Basic ${Buffer.from('admin:password3').toString('base64')}`
|
||||
);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
@@ -506,40 +470,28 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.resolve(); },
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.resolve(),
|
||||
lock : testLock
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.put(`/api/lock/review/request/${testBrew.shareId}`);
|
||||
const response = await request.put(`/api/lock/review/request/${testBrew.shareId}`);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({
|
||||
message : `Review requested on brew ID ${testBrew.shareId} - ${testBrew.title}`,
|
||||
name : 'Review Requested',
|
||||
name : 'Review Requested'
|
||||
});
|
||||
});
|
||||
|
||||
it('Error when cannot find a locked brew', async ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId'
|
||||
};
|
||||
const testBrew = { shareId: 'shareId' };
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(false);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(false));
|
||||
|
||||
|
||||
const response = await app
|
||||
.put(`/api/lock/review/request/${testBrew.shareId}`)
|
||||
.catch((err)=>{return err;});
|
||||
const response = await request.put(`/api/lock/review/request/${testBrew.shareId}`);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
@@ -569,25 +521,20 @@ describe('Tests for admin api', ()=>{
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(false);
|
||||
});
|
||||
.mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
|
||||
const response = await app
|
||||
.put(`/api/lock/review/request/${testBrew.shareId}`)
|
||||
.catch((err)=>{return err;});
|
||||
const response = await request
|
||||
.put(`/api/lock/review/request/${testBrew.shareId}`);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
HBErrorCode : '70',
|
||||
HBErrorCode : '71',
|
||||
code : 500,
|
||||
message : `Cannot find a locked brew with ID ${testBrew.shareId}`,
|
||||
name : 'Brew Not Found',
|
||||
message : `Review already requested for brew ${testBrew.shareId} - ${testBrew.title}`,
|
||||
name : 'Review Already Requested',
|
||||
originalUrl : `/api/lock/review/request/${testBrew.shareId}`
|
||||
});
|
||||
});
|
||||
|
||||
it('Handle error while adding review request to a locked brew', async ()=>{
|
||||
const testLock = {
|
||||
applied : 'YES',
|
||||
@@ -599,18 +546,14 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.reject(); },
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.reject(),
|
||||
lock : testLock
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.put(`/api/lock/review/request/${testBrew.shareId}`);
|
||||
const response = await request.put(`/api/lock/review/request/${testBrew.shareId}`);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
@@ -634,19 +577,16 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.resolve(); },
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.resolve(),
|
||||
lock : testLock
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.put(`/api/lock/review/remove/${testBrew.shareId}`);
|
||||
const response = await request
|
||||
.put(`/api/lock/review/remove/${testBrew.shareId}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({
|
||||
@@ -656,18 +596,13 @@ describe('Tests for admin api', ()=>{
|
||||
});
|
||||
|
||||
it('Error when clearing review request from a brew with no review request', async ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
};
|
||||
const testBrew = { shareId: 'shareId' };
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(false);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(false));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.put(`/api/lock/review/remove/${testBrew.shareId}`);
|
||||
const response = await request
|
||||
.put(`/api/lock/review/remove/${testBrew.shareId}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
@@ -690,19 +625,16 @@ describe('Tests for admin api', ()=>{
|
||||
const testBrew = {
|
||||
shareId : 'shareId',
|
||||
title : 'title',
|
||||
markModified : ()=>{ return true; },
|
||||
save : ()=>{ return Promise.reject(); },
|
||||
markModified : ()=>true,
|
||||
save : ()=>Promise.reject(),
|
||||
lock : testLock
|
||||
};
|
||||
|
||||
jest.spyOn(HomebrewModel, 'findOne')
|
||||
.mockImplementationOnce(()=>{
|
||||
return Promise.resolve(testBrew);
|
||||
});
|
||||
jest.spyOn(HomebrewModel, 'findOne').mockImplementationOnce(()=>Promise.resolve(testBrew));
|
||||
|
||||
const response = await app
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
|
||||
.put(`/api/lock/review/remove/${testBrew.shareId}`);
|
||||
const response = await request
|
||||
.put(`/api/lock/review/remove/${testBrew.shareId}`)
|
||||
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({
|
||||
|
||||
+565
-537
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,6 @@ const DEFAULT_BREW = {
|
||||
theme : '5ePHB',
|
||||
authors : [],
|
||||
tags : [],
|
||||
systems : [],
|
||||
lang : 'en',
|
||||
thumbnail : '',
|
||||
views : 0,
|
||||
|
||||
@@ -151,7 +151,6 @@ const GoogleActions = {
|
||||
description : file.description,
|
||||
views : parseInt(file.properties.views),
|
||||
published : file.properties.published ? file.properties.published == 'true' : false,
|
||||
systems : [],
|
||||
lang : file.properties.lang,
|
||||
thumbnail : file.properties.thumbnail,
|
||||
webViewLink : file.webViewLink
|
||||
@@ -298,7 +297,6 @@ const GoogleActions = {
|
||||
text : file.data,
|
||||
|
||||
description : obj.data.description,
|
||||
systems : obj.data.properties.systems ? obj.data.properties.systems.split(',') : [],
|
||||
authors : [],
|
||||
lang : obj.data.properties.lang,
|
||||
published : obj.data.properties.published ? obj.data.properties.published == 'true' : false,
|
||||
|
||||
+30
-3
@@ -31,6 +31,27 @@ const isStaticTheme = (renderer, themeName)=>{
|
||||
// });
|
||||
// };
|
||||
|
||||
|
||||
const migrateSystemsToTags = (brew) => {
|
||||
if (!('systems' in brew)) return brew;
|
||||
|
||||
if (!Array.isArray(brew.systems) || brew.systems.length === 0) {
|
||||
brew.systems = undefined;
|
||||
return brew;
|
||||
}
|
||||
const systemMap = {
|
||||
'5e': 'system:D&D 5e',
|
||||
'4e': 'system:D&D 4e',
|
||||
'3.5e': 'system:D&D 3.5e',
|
||||
'Pathfinder': 'system:Pathfinder 2e'
|
||||
};
|
||||
const systemTags = brew.systems.map(s => systemMap[s]);
|
||||
brew.tags = _.uniq([...(brew.tags || []), ...systemTags]);
|
||||
|
||||
brew.systems = undefined;
|
||||
return brew;
|
||||
};
|
||||
|
||||
const MAX_TITLE_LENGTH = 100;
|
||||
|
||||
const api = {
|
||||
@@ -167,7 +188,10 @@ const api = {
|
||||
stub.renderer = stub.renderer || undefined; // Clear empty strings
|
||||
stub = _.defaults(stub, DEFAULT_BREW_LOAD); // Fill in blank fields
|
||||
|
||||
req.brew = stub;
|
||||
|
||||
|
||||
const fixedStub = migrateSystemsToTags(stub);
|
||||
req.brew = fixedStub;
|
||||
next();
|
||||
};
|
||||
},
|
||||
@@ -193,7 +217,7 @@ const api = {
|
||||
`\`\`\`\n\n` +
|
||||
`${text}`;
|
||||
}
|
||||
const metadata = _.pick(brew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme']);
|
||||
const metadata = _.pick(brew, ['title', 'description', 'tags', 'renderer', 'theme']);
|
||||
const snippetsArray = brewSnippetsToJSON('brew_snippets', brew.snippets, null, false).snippets;
|
||||
metadata.snippets = snippetsArray.length > 0 ? snippetsArray : undefined;
|
||||
text = `\`\`\`metadata\n` +
|
||||
@@ -392,6 +416,9 @@ const api = {
|
||||
}
|
||||
|
||||
let brew = _.assign(brewFromServer, brewFromClient);
|
||||
|
||||
migrateSystemsToTags(brew);
|
||||
|
||||
brew.title = brew.title.trim();
|
||||
brew.description = brew.description.trim() || '';
|
||||
brew.text = api.mergeBrewText(brew);
|
||||
@@ -481,7 +508,7 @@ const api = {
|
||||
await HomebrewModel.deleteOne({ editId: id });
|
||||
return next();
|
||||
}
|
||||
throw(err);
|
||||
throw (err);
|
||||
}
|
||||
|
||||
let brew = req.brew;
|
||||
|
||||
@@ -63,7 +63,6 @@ describe('Tests for api', ()=>{
|
||||
title : 'some title',
|
||||
description : 'this is a description',
|
||||
tags : ['something', 'fun'],
|
||||
systems : ['D&D 5e'],
|
||||
lang : 'en',
|
||||
renderer : 'v3',
|
||||
theme : 'phb',
|
||||
@@ -351,7 +350,6 @@ describe('Tests for api', ()=>{
|
||||
renderer : 'legacy',
|
||||
lang : 'en',
|
||||
shareId : undefined,
|
||||
systems : [],
|
||||
tags : [],
|
||||
theme : '5ePHB',
|
||||
thumbnail : '',
|
||||
@@ -390,7 +388,6 @@ describe('Tests for api', ()=>{
|
||||
title : 'some title',
|
||||
description : 'this is a description',
|
||||
tags : ['something', 'fun'],
|
||||
systems : ['D&D 5e'],
|
||||
renderer : 'v3',
|
||||
theme : 'phb',
|
||||
googleId : '12345'
|
||||
@@ -402,8 +399,6 @@ description: this is a description
|
||||
tags:
|
||||
- something
|
||||
- fun
|
||||
systems:
|
||||
- D&D 5e
|
||||
renderer: v3
|
||||
theme: phb
|
||||
|
||||
@@ -419,7 +414,6 @@ brew`);
|
||||
title : 'some title',
|
||||
description : 'this is a description',
|
||||
tags : ['something', 'fun'],
|
||||
systems : ['D&D 5e'],
|
||||
renderer : 'v3',
|
||||
theme : 'phb',
|
||||
googleId : '12345'
|
||||
@@ -431,8 +425,6 @@ description: this is a description
|
||||
tags:
|
||||
- something
|
||||
- fun
|
||||
systems:
|
||||
- D&D 5e
|
||||
renderer: v3
|
||||
theme: phb
|
||||
|
||||
@@ -463,7 +455,6 @@ brew`);
|
||||
|
||||
expect(sent).toEqual(googleBrew);
|
||||
expect(result.tags).toBeUndefined();
|
||||
expect(result.systems).toBeUndefined();
|
||||
expect(result.published).toBeUndefined();
|
||||
expect(result.authors).toBeUndefined();
|
||||
expect(result.owner).toBeUndefined();
|
||||
@@ -558,7 +549,6 @@ brew`);
|
||||
lang : 'en',
|
||||
shareId : expect.any(String),
|
||||
style : undefined,
|
||||
systems : [],
|
||||
tags : [],
|
||||
text : undefined,
|
||||
textBin : expect.objectContaining({}),
|
||||
@@ -618,7 +608,6 @@ brew`);
|
||||
shareId : expect.any(String),
|
||||
googleId : expect.any(String),
|
||||
style : undefined,
|
||||
systems : [],
|
||||
tags : [],
|
||||
text : undefined,
|
||||
textBin : undefined,
|
||||
@@ -1076,7 +1065,6 @@ brew`);
|
||||
'title: title\n' +
|
||||
'description: description\n' +
|
||||
'tags: [ \'tag a\' , \'tag b\' ]\n' +
|
||||
'systems: [ test system ]\n' +
|
||||
'renderer: legacy\n' +
|
||||
'theme: 5ePHB\n' +
|
||||
'lang: en\n' +
|
||||
@@ -1097,8 +1085,6 @@ brew`);
|
||||
// Metadata
|
||||
expect(testBrew.title).toEqual('title');
|
||||
expect(testBrew.description).toEqual('description');
|
||||
expect(testBrew.tags).toEqual(['tag a', 'tag b']);
|
||||
expect(testBrew.systems).toEqual(['test system']);
|
||||
expect(testBrew.renderer).toEqual('legacy');
|
||||
expect(testBrew.theme).toEqual('5ePHB');
|
||||
expect(testBrew.lang).toEqual('en');
|
||||
@@ -1107,19 +1093,6 @@ brew`);
|
||||
// Text
|
||||
expect(testBrew.text).toEqual('text\n');
|
||||
});
|
||||
|
||||
it('convert tags string to array', async ()=>{
|
||||
const testBrew = {
|
||||
text : '```metadata\n' +
|
||||
'tags: tag a\n' +
|
||||
'```\n\n'
|
||||
};
|
||||
|
||||
splitTextStyleAndMetadata(testBrew);
|
||||
|
||||
// Metadata
|
||||
expect(testBrew.tags).toEqual(['tag a']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateBrew', ()=>{
|
||||
|
||||
@@ -15,7 +15,7 @@ const HomebrewSchema = mongoose.Schema({
|
||||
|
||||
description : { type: String, default: '' },
|
||||
tags : { type: [String], index: true },
|
||||
systems : [String],
|
||||
systems : { type: [String], default: undefined },
|
||||
lang : { type: String, default: 'en', index: true },
|
||||
renderer : { type: String, default: '', index: true },
|
||||
authors : { type: [String], index: true },
|
||||
|
||||
Reference in New Issue
Block a user