diff --git a/client/admin/admin.less b/client/admin/admin.less index 432f92e8b..e66ead5e3 100644 --- a/client/admin/admin.less +++ b/client/admin/admin.less @@ -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; } } diff --git a/client/admin/brewUtils/brewCleanup/brewCleanup.jsx b/client/admin/brewUtils/brewCleanup/brewCleanup.jsx index 721ec1e00..7d6c34f55 100644 --- a/client/admin/brewUtils/brewCleanup/brewCleanup.jsx +++ b/client/admin/brewUtils/brewCleanup/brewCleanup.jsx @@ -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
No Matching Brews found.
; + 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 <> +

{`Results - No brews found` }

+ + + + + + + + + + + + + + +
TitleLast Updatelast viewedStorage
"No brews found"
+ ; + } + console.log(type); + console.log(brewList); + return <> +

{`Results - ${brewList.length} brews` }

+ + + + + + + + + + + {brewList + .sort((a, b)=>{ // Sort brews from most recently updated + if(a.lastViewed > b.lastViewed) return -1; + return 1; + }) + .map((brew, idx)=>{ + return + + + + + + })} + +
TitleLast Updatelast viewedStorage
{brew.title || 'No Title'}{Moment(brew.updatedAt).fromNow()}{brew.lastViewed ? Moment(brew.lastViewed).fromNow() : 'No last viewed date'}{brew.googleId ? 'Google' : 'Homebrewery'}
+ ; + }; + const renderFound = (type)=>{ + const deleteButton = !(type === 'junk' && junkBrewCollection.length === 0 || type === 'lost' && lostBrewCollection.length === 0); return
- - Found {count} Brews that could be removed. + } + {renderBrewList(type)} +
; + }; + const renderJunkBrewCleanup = ()=>{ + return
+

Junk brews

+

Queries unauthored brews that have not been viewed or
updated in 30 days and are shorter than 140 bytes (up to 300)

+ + + {renderFound('junk')} + + {error &&
{error.toString()}
} +
; + }; + const renderLostBrewCleanup = ()=>{ + return
+

Lost brews

+

Queries unauthored brews that have not been
updated or viewed for 2 years (up to 500)

+ + + {renderFound('lost')} + + {error &&
{error.toString()}
}
; }; return

Brew Cleanup

-

Removes very short brews to tidy up the database

+ {renderJunkBrewCleanup()} +
+
+ {renderLostBrewCleanup()} - - {renderPrimed()} - - {error &&
{error.toString()}
}
; }; diff --git a/server/admin.api.js b/server/admin.api.js index f55eefdf2..d0676f47f 100644 --- a/server/admin.api.js +++ b/server/admin.api.js @@ -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 } });