0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-07 01:12:44 +00:00

Merge branch 'master' into Issue_1958

This commit is contained in:
David Bolack
2024-02-07 20:10:04 -06:00
committed by GitHub
6 changed files with 15115 additions and 15071 deletions

View File

@@ -125,7 +125,7 @@ const BrewItem = createClass({
<div className='info'> <div className='info'>
{brew.tags?.length ? <> {brew.tags?.length ? <>
<div className='brewTags' title={`Tags:\n${brew.tags.join('\n')}`}> <div className='brewTags' title={`${brew.tags.length} tags:\n${brew.tags.join('\n')}`}>
<i className='fas fa-tags'/> <i className='fas fa-tags'/>
{brew.tags.map((tag, idx)=>{ {brew.tags.map((tag, idx)=>{
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/); const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
@@ -135,7 +135,7 @@ const BrewItem = createClass({
</> : <></> </> : <></>
} }
<span title={`Authors:\n${brew.authors?.join('\n')}`}> <span title={`Authors:\n${brew.authors?.join('\n')}`}>
<i className='fas fa-user'/> {brew.authors?.join(', ')} <i className='fas fa-user'/> {brew.authors.map((item) => <a href={`/user/${item}`}>{item}</a>)}
</span> </span>
<br /> <br />
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}> <span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>

View File

@@ -48,6 +48,10 @@
&>span{ &>span{
margin-right : 12px; margin-right : 12px;
line-height : 1.5em; line-height : 1.5em;
a {
color:inherit;
}
} }
} }
.brewTags span { .brewTags span {

30032
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -79,11 +79,11 @@
] ]
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.23.7", "@babel/core": "^7.23.9",
"@babel/plugin-transform-runtime": "^7.23.7", "@babel/plugin-transform-runtime": "^7.23.9",
"@babel/preset-env": "^7.23.8", "@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3", "@babel/preset-react": "^7.23.3",
"@googleapis/drive": "^8.6.0", "@googleapis/drive": "^8.7.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"codemirror": "^5.65.6", "codemirror": "^5.65.6",
@@ -98,19 +98,19 @@
"jwt-simple": "^0.5.6", "jwt-simple": "^0.5.6",
"less": "^3.13.1", "less": "^3.13.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"marked": "11.1.1", "marked": "11.2.0",
"marked-extended-tables": "^1.0.8", "marked-extended-tables": "^1.0.8",
"marked-gfm-heading-id": "^3.1.2", "marked-gfm-heading-id": "^3.1.3",
"marked-smartypants-lite": "^1.0.2", "marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19", "markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1", "moment": "^2.30.1",
"mongoose": "^8.1.0", "mongoose": "^8.1.1",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"nconf": "^0.12.1", "nconf": "^0.12.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-frame-component": "^4.1.3", "react-frame-component": "^4.1.3",
"react-router-dom": "6.21.3", "react-router-dom": "6.22.0",
"sanitize-filename": "1.6.3", "sanitize-filename": "1.6.3",
"superagent": "^8.1.2", "superagent": "^8.1.2",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git" "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
@@ -123,7 +123,7 @@
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"stylelint": "^15.11.0", "stylelint": "^15.11.0",
"stylelint-config-recess-order": "^4.4.0", "stylelint-config-recess-order": "^4.5.0",
"stylelint-config-recommended": "^13.0.0", "stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3", "stylelint-stylistic": "^0.4.3",
"supertest": "^6.3.4" "supertest": "^6.3.4"

View File

@@ -26,85 +26,124 @@ const mw = {
} }
}; };
const junkBrewPipeline = [
/* Search for brews that are older than 3 days and that are shorter than a tweet */ { $match : {
const junkBrewQuery = HomebrewModel.find({ updatedAt : { $lt: Moment().subtract(30, 'days').toDate() },
'$where' : 'this.text.length < 140', lastViewed : { $lt: Moment().subtract(30, 'days').toDate() }
createdAt : { }},
$lt : Moment().subtract(30, 'days').toDate() { $project: { textBinSize: { $binarySize: '$textBin' } } },
} { $match: { textBinSize: { $lt: 140 } } },
}).limit(100).maxTime(60000); { $limit: 100 }
];
/* Search for brews that aren't compressed (missing the compressed text field) */ /* Search for brews that aren't compressed (missing the compressed text field) */
const uncompressedBrewQuery = HomebrewModel.find({ const uncompressedBrewQuery = HomebrewModel.find({
'text' : { '$exists': true } 'text' : { '$exists': true }
}).lean().limit(10000).select('_id'); }).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)=>{ router.get('/admin/cleanup', mw.adminOnly, (req, res)=>{
junkBrewQuery.exec((err, objs)=>{ HomebrewModel.aggregate(junkBrewPipeline).option({ maxTimeMS: 60000 })
if(err) return res.status(500).send(err); .then((objs)=>res.json({ count: objs.length }))
return res.json({ count: objs.length }); .catch((error)=>{
}); console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
});
}); });
/* Removes all empty brews that are older than 3 days and that are shorter than a tweet */
// 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)=>{ router.post('/admin/cleanup', mw.adminOnly, (req, res)=>{
junkBrewQuery.remove().exec((err, objs)=>{ HomebrewModel.aggregate(junkBrewPipeline).option({ maxTimeMS: 60000 })
if(err) return res.status(500).send(err); .then((docs)=>{
return res.json({ count: objs.length }); const ids = docs.map((doc)=>doc._id);
}); return HomebrewModel.deleteMany({ _id: { $in: ids } });
}).then((result)=>{
res.json({ count: result.deletedCount });
}).catch((error)=>{
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
});
}); });
/* Searches for matching edit or share id, also attempts to partial match */ /* Searches for matching edit or share id, also attempts to partial match */
router.get('/admin/lookup/:id', mw.adminOnly, (req, res, next)=>{ router.get('/admin/lookup/:id', mw.adminOnly, async (req, res, next)=>{
HomebrewModel.findOne({ $or : [ HomebrewModel.findOne({
{ editId: { '$regex': req.params.id, '$options': 'i' } }, $or : [
{ shareId: { '$regex': req.params.id, '$options': 'i' } }, { editId: { $regex: req.params.id, $options: 'i' } },
] }).exec((err, brew)=>{ { shareId: { $regex: req.params.id, $options: 'i' } },
return res.json(brew); ]
}).exec()
.then((brew)=>{
if(!brew) // No document found
return res.status(404).json({ error: 'Document not found' });
else
return res.json(brew);
})
.catch((err)=>{
console.error(err);
return res.status(500).json({ error: 'Internal Server Error' });
}); });
}); });
/* Find 50 brews that aren't compressed yet */ /* Find 50 brews that aren't compressed yet */
router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{ router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
uncompressedBrewQuery.exec((err, objs)=>{ const query = uncompressedBrewQuery.clone();
if(err) return res.status(500).send(err);
objs = objs.map((obj)=>{return obj._id;}); query.exec()
return res.json({ count: objs.length, ids: objs }); .then((objs)=>{
}); const ids = objs.map((obj)=>obj._id);
res.json({ count: ids.length, ids });
})
.catch((err)=>{
console.error(err);
res.status(500).send(err.message || 'Internal Server Error');
});
}); });
/* Compresses the "text" field of a brew to binary */ /* Compresses the "text" field of a brew to binary */
router.put('/admin/compress/:id', (req, res)=>{ router.put('/admin/compress/:id', (req, res)=>{
HomebrewModel.get({ _id: req.params.id }) HomebrewModel.findOne({ _id: req.params.id })
.then((brew)=>{ .then((brew)=>{
brew.textBin = zlib.deflateRawSync(brew.text); // Compress brew text to binary before saving if(!brew)
brew.text = undefined; // Delete the non-binary text field since it's not needed anymore return res.status(404).send('Brew not found');
brew.save((err, obj)=>{ if(brew.text) {
if(err) throw err; brew.textBin = brew.textBin || zlib.deflateRawSync(brew.text); //Don't overwrite textBin if exists
return res.status(200).send(obj); brew.text = undefined;
}); }
return brew.save();
}) })
.then((obj)=>res.status(200).send(obj))
.catch((err)=>{ .catch((err)=>{
console.log(err); console.error(err);
return res.status(500).send('Error while saving'); res.status(500).send('Error while saving');
}); });
}); });
router.get('/admin/stats', mw.adminOnly, (req, res)=>{
HomebrewModel.count({}, (err, count)=>{ 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({ return res.json({
totalBrews : count totalBrews : totalBrewsCount,
totalPublishedBrews : publishedBrewsCount
}); });
}); } catch (error) {
console.error(error);
return res.status(500).json({ error: 'Internal Server Error' });
}
}); });
router.get('/admin', mw.adminOnly, (req, res)=>{ router.get('/admin', mw.adminOnly, (req, res)=>{
templateFn('admin', { templateFn('admin', {
url : req.originalUrl url : req.originalUrl
}) })
.then((page)=>res.send(page)) .then((page)=>res.send(page))
.catch((err)=>res.sendStatus(500)); .catch((err)=>res.sendStatus(500));
}); });
module.exports = router; module.exports = router;

View File

@@ -305,7 +305,8 @@ app.get('/new/:id', asyncHandler(getBrew('share')), (req, res, next)=>{
text : req.brew.text, text : req.brew.text,
style : req.brew.style, style : req.brew.style,
renderer : req.brew.renderer, renderer : req.brew.renderer,
theme : req.brew.theme theme : req.brew.theme,
tags : req.brew.tags
}; };
req.brew = _.defaults(brew, DEFAULT_BREW); req.brew = _.defaults(brew, DEFAULT_BREW);