mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-06-21 22:28:42 +00:00
Merge pull request #4837 from 5e-Cleric/add-cleaning-tool-to-admin
[ADMIN]: Proper cleanup function
This commit is contained in:
@@ -111,6 +111,10 @@ body {
|
||||
vertical-align : middle;
|
||||
text-align : center;
|
||||
border-right : 1px solid;
|
||||
max-width:50ch;
|
||||
overflow:hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:last-child { border-right : none; }
|
||||
}
|
||||
|
||||
@@ -1,70 +1,174 @@
|
||||
import React, { useState } from 'react';
|
||||
import request from 'superagent';
|
||||
import Moment from 'moment';
|
||||
|
||||
const BrewCleanup = ({})=>{
|
||||
const [count, setCount] = useState(0);
|
||||
const [pending, setPending] = useState(false);
|
||||
const [primed, setPrimed] = useState(false);
|
||||
const [junkBrewCollection, setJunkBrewCollection] = useState([]);
|
||||
const [lostBrewCollection, setLostBrewCollection] = useState([]);
|
||||
const [pendingJunk, setPendingJunk] = useState(false);
|
||||
const [pendingLost, setPendingLost] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const prime = async ()=>{
|
||||
setPending(true);
|
||||
const find = async (type)=>{
|
||||
|
||||
|
||||
try {
|
||||
const res = await request.get('/admin/cleanup');
|
||||
if(type === 'junk') try {
|
||||
setPendingJunk(true);
|
||||
const res = await request.get('/admin/cleanupJunk');
|
||||
|
||||
setCount(res.body.count);
|
||||
setPrimed(true);
|
||||
setJunkBrewCollection(res.body.brewCollection);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setPending(false);
|
||||
setPendingJunk(false);
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = async ()=>{
|
||||
setPending(true);
|
||||
if(type === 'lost') try {
|
||||
setPendingLost(true);
|
||||
const res = await request.get('/admin/cleanupLost');
|
||||
|
||||
try {
|
||||
const res = await request.post('/admin/cleanup');
|
||||
|
||||
setCount(res.body.count);
|
||||
setLostBrewCollection(res.body.brewCollection);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setPending(false);
|
||||
setPrimed(false);
|
||||
setPendingLost(false);
|
||||
}
|
||||
};
|
||||
const renderPrimed = ()=>{
|
||||
if(!primed) return;
|
||||
|
||||
if(!count) return <div className='result noBrews'>No Matching Brews found.</div>;
|
||||
const cleanup = async (type)=>{
|
||||
|
||||
if(type === 'junk') try {
|
||||
setPendingJunk(true);
|
||||
console.log('deleting junk');
|
||||
const res = await request.post('/admin/cleanupJunk');
|
||||
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setPendingJunk(false);
|
||||
setJunkBrewCollection([]);
|
||||
}
|
||||
|
||||
if(type === 'lost') try {
|
||||
setPendingLost(true);
|
||||
const res = await request.post('/admin/cleanupLost');
|
||||
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setPendingLost(false);
|
||||
setLostBrewCollection([]);
|
||||
}
|
||||
};
|
||||
|
||||
const renderBrewList = (type)=>{
|
||||
const brewList = type === 'lost' ? lostBrewCollection : junkBrewCollection;
|
||||
|
||||
if(!brewList || brewList.length === 0) {
|
||||
return <>
|
||||
<h3>{`Results - No brews found` }</h3>
|
||||
<table className='resultsTable'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Last Update</th>
|
||||
<th>last viewed</th>
|
||||
<th>Storage</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={4}><strong>"No brews found"</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>;
|
||||
}
|
||||
console.log(type);
|
||||
console.log(brewList);
|
||||
return <>
|
||||
<h3>{`Results - ${brewList.length} brews` }</h3>
|
||||
<table className='resultsTable'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Last Update</th>
|
||||
<th>last viewed</th>
|
||||
<th>Storage</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{brewList
|
||||
.sort((a, b)=>{ // Sort brews from most recently updated
|
||||
if(a.lastViewed > b.lastViewed) return -1;
|
||||
return 1;
|
||||
})
|
||||
.map((brew, idx)=>{
|
||||
return <tr key={idx}>
|
||||
<td><strong>{brew.title || 'No Title'}</strong></td>
|
||||
<td style={{ width: '200px' }}>{Moment(brew.updatedAt).fromNow()}</td>
|
||||
<td>{brew.lastViewed ? Moment(brew.lastViewed).fromNow() : 'No last viewed date'}</td>
|
||||
<td>{brew.googleId ? 'Google' : 'Homebrewery'}</td>
|
||||
</tr>
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</>;
|
||||
};
|
||||
const renderFound = (type)=>{
|
||||
const deleteButton = !(type === 'junk' && junkBrewCollection.length === 0 || type === 'lost' && lostBrewCollection.length === 0);
|
||||
|
||||
return <div className='result'>
|
||||
<button onClick={()=>cleanup()} className='remove'>
|
||||
{pending
|
||||
{deleteButton && <button onClick={()=>cleanup(type)} className='remove'>
|
||||
{pendingLost && type === "lost" || pendingJunk && type === "junk"
|
||||
? <i className='fas fa-spin fa-spinner' />
|
||||
: <span><i className='fas fa-times' /> Remove</span>
|
||||
}
|
||||
</button>
|
||||
<span>Found {count} Brews that could be removed. </span>
|
||||
}
|
||||
{renderBrewList(type)}
|
||||
</div>;
|
||||
};
|
||||
const renderJunkBrewCleanup = ()=>{
|
||||
return <div className='junk'>
|
||||
<h3> Junk brews</h3>
|
||||
<p>Queries unauthored brews that have not been viewed or <br/>updated in 30 days and are shorter than 140 bytes (up to 300)</p>
|
||||
|
||||
<button onClick={()=>find('junk')} className='query'>
|
||||
{pendingJunk
|
||||
? <i className='fas fa-spin fa-spinner' />
|
||||
: 'Query Brews'
|
||||
}
|
||||
</button>
|
||||
{renderFound('junk')}
|
||||
|
||||
{error && <div className='error noBrews'>{error.toString()}</div>}
|
||||
</div>;
|
||||
};
|
||||
const renderLostBrewCleanup = ()=>{
|
||||
return <div className='lost'>
|
||||
<h3> Lost brews</h3>
|
||||
<p>Queries unauthored brews that have not been <br/>updated or viewed for 2 years (up to 500)</p>
|
||||
|
||||
<button onClick={()=>find('lost')} className='query'>
|
||||
{pendingLost
|
||||
? <i className='fas fa-spin fa-spinner' />
|
||||
: 'Query Brews'
|
||||
}
|
||||
</button>
|
||||
{renderFound('lost')}
|
||||
|
||||
{error && <div className='error noBrews'>{error.toString()}</div>}
|
||||
</div>;
|
||||
};
|
||||
|
||||
return <div className='brewUtil brewCleanup'>
|
||||
<h2> Brew Cleanup </h2>
|
||||
<p>Removes very short brews to tidy up the database</p>
|
||||
{renderJunkBrewCleanup()}
|
||||
<br/>
|
||||
<br/>
|
||||
{renderLostBrewCleanup()}
|
||||
|
||||
<button onClick={()=>prime()} className='query'>
|
||||
{pending
|
||||
? <i className='fas fa-spin fa-spinner' />
|
||||
: 'Query Brews'
|
||||
}
|
||||
</button>
|
||||
{renderPrimed()}
|
||||
|
||||
{error && <div className='error noBrews'>{error.toString()}</div>}
|
||||
</div>;
|
||||
|
||||
};
|
||||
|
||||
+48
-11
@@ -38,15 +38,30 @@ export default function createAdminApi(vite) {
|
||||
throw { HBErrorCode: '52', code: 401, message: 'Access denied' };
|
||||
}
|
||||
};
|
||||
|
||||
const junkBrewPipeline = [
|
||||
|
||||
// Search for up to 300 brews that have not been viewed or updated in 30 days and are shorter than 140 bytes
|
||||
const junkBrewsPipeline = [
|
||||
{ $match : {
|
||||
updatedAt : { $lt: Moment().subtract(30, 'days').toDate() },
|
||||
lastViewed : { $lt: Moment().subtract(30, 'days').toDate() }
|
||||
} },
|
||||
{ $project: { textBinSize: { $binarySize: '$textBin' } } },
|
||||
{ $project: { _id: 1, textBinSize: { $binarySize: '$textBin' }, updatedAt: 1, lastViewed: 1} },
|
||||
{ $match: { textBinSize: { $lt: 140 } } },
|
||||
{ $limit: 100 }
|
||||
{ $limit: 300 }
|
||||
];
|
||||
|
||||
// Search for up to 500 unauthored brews that have not been viewed or updated in two years
|
||||
const lostBrewsPipeline = [
|
||||
{
|
||||
$match: {
|
||||
authors: [],
|
||||
updatedAt: { $lt: Moment().subtract(2, 'years').toDate() },
|
||||
lastViewed: { $lt: Moment().subtract(2, 'years').toDate() }
|
||||
}
|
||||
},
|
||||
{
|
||||
$limit: 500
|
||||
}
|
||||
];
|
||||
|
||||
/* Search for brews that aren't compressed (missing the compressed text field) */
|
||||
@@ -54,19 +69,41 @@ export default function createAdminApi(vite) {
|
||||
'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 })
|
||||
.then((objs)=>res.json({ count: objs.length }))
|
||||
router.get('/admin/cleanupJunk', mw.adminOnly, (req, res)=>{
|
||||
HomebrewModel.aggregate(junkBrewsPipeline).option({ maxTimeMS: 60000 })
|
||||
.then((objs)=>res.json({ count: objs.length, brewCollection : objs }))
|
||||
.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 result of junkBrewsPipeline
|
||||
router.post('/admin/cleanupJunk', mw.adminOnly, (req, res)=>{
|
||||
HomebrewModel.aggregate(junkBrewsPipeline).option({ maxTimeMS: 60000 })
|
||||
.then((docs)=>{
|
||||
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' });
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/admin/cleanupLost', mw.adminOnly, (req, res)=>{
|
||||
HomebrewModel.aggregate(lostBrewsPipeline).option({ maxTimeMS: 60000 })
|
||||
.then((objs)=>res.json({ count: objs.length, brewCollection : objs }))
|
||||
.catch((error)=>{
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
});
|
||||
});
|
||||
|
||||
// Delete result of lostBrewsPipeline
|
||||
router.post('/admin/cleanupLost', mw.adminOnly, (req, res)=>{
|
||||
HomebrewModel.aggregate(lostBrewsPipeline).option({ maxTimeMS: 60000 })
|
||||
.then((docs)=>{
|
||||
const ids = docs.map((doc)=>doc._id);
|
||||
return HomebrewModel.deleteMany({ _id: { $in: ids } });
|
||||
|
||||
Reference in New Issue
Block a user