0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-08 03:12:40 +00:00
This commit is contained in:
Trevor Buckner
2024-09-04 01:06:56 -04:00
parent 4f39222724
commit d7d690a9d1
5 changed files with 132 additions and 133 deletions

View File

@@ -5,11 +5,11 @@ const Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function (props) { module.exports = function (props) {
return ( return (
<Nav.item <Nav.item
color="purple" color='purple'
icon="fas fa-dungeon" icon='fas fa-dungeon'
href="/vault" href='/vault'
newTab={false} newTab={false}
rel="noopener noreferrer" rel='noopener noreferrer'
> >
Vault Vault
</Nav.item> </Nav.item>

View File

@@ -141,7 +141,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?.map((author, index) => ( <i className='fas fa-user'/> {brew.authors?.map((author, index)=>(
<React.Fragment key={index}> <React.Fragment key={index}>
{author === 'hidden' {author === 'hidden'
? <span title="Username contained an email address; hidden to protect user's privacy">{author}</span> ? <span title="Username contained an email address; hidden to protect user's privacy">{author}</span>

View File

@@ -174,7 +174,7 @@ const errorIndex = (props)=>{
'90' : dedent` An unexpected error occurred while looking for these brews. '90' : dedent` An unexpected error occurred while looking for these brews.
Try again in a few minutes.`, Try again in a few minutes.`,
'91' : dedent` An unexpected error occurred while trying to get the total of brews.`, '91' : dedent` An unexpected error occurred while trying to get the total of brews.`,
}; };
}; };

View File

@@ -15,7 +15,7 @@ const ErrorIndex = require('../errorPage/errors/errorIndex.js');
const request = require('../../utils/request-middleware.js'); const request = require('../../utils/request-middleware.js');
const VaultPage = (props) => { const VaultPage = (props)=>{
const [pageState, setPageState] = useState(parseInt(props.query.page) || 1); const [pageState, setPageState] = useState(parseInt(props.query.page) || 1);
//Response state //Response state
@@ -32,33 +32,33 @@ const VaultPage = (props) => {
const legacyRef = useRef(null); const legacyRef = useRef(null);
const submitButtonRef = useRef(null); const submitButtonRef = useRef(null);
useEffect(() => { useEffect(()=>{
disableSubmitIfFormInvalid(); disableSubmitIfFormInvalid();
loadPage(pageState, true); loadPage(pageState, true);
}, []); }, []);
const updateStateWithBrews = (brews, page) => { const updateStateWithBrews = (brews, page)=>{
setBrewCollection(brews || null); setBrewCollection(brews || null);
setPageState(parseInt(page) || 1); setPageState(parseInt(page) || 1);
setSearching(false); setSearching(false);
}; };
const updateUrl = (titleValue, authorValue, countValue, v3Value, legacyValue, page) => { const updateUrl = (titleValue, authorValue, countValue, v3Value, legacyValue, page)=>{
const url = new URL(window.location.href); const url = new URL(window.location.href);
const urlParams = new URLSearchParams(url.search); const urlParams = new URLSearchParams(url.search);
urlParams.set('title', titleValue); urlParams.set('title', titleValue);
urlParams.set('author', authorValue); urlParams.set('author', authorValue);
urlParams.set('count', countValue); urlParams.set('count', countValue);
urlParams.set('v3', v3Value); urlParams.set('v3', v3Value);
urlParams.set('legacy', legacyValue); urlParams.set('legacy', legacyValue);
urlParams.set('page', page); urlParams.set('page', page);
url.search = urlParams.toString(); url.search = urlParams.toString();
window.history.replaceState(null, '', url.toString()); window.history.replaceState(null, '', url.toString());
}; };
const performSearch = async (title, author, count, v3, legacy, page) => { const performSearch = async (title, author, count, v3, legacy, page)=>{
updateUrl(title, author, count, v3, legacy, page); updateUrl(title, author, count, v3, legacy, page);
const response = await request.get( const response = await request.get(
@@ -71,14 +71,14 @@ const VaultPage = (props) => {
); );
updateStateWithBrews([], 1); updateStateWithBrews([], 1);
}); });
if (response.ok) if(response.ok)
updateStateWithBrews(response.body.brews, page); updateStateWithBrews(response.body.brews, page);
}; };
const loadTotal = async (title, author, v3, legacy) => { const loadTotal = async (title, author, v3, legacy)=>{
setTotalBrews(null); setTotalBrews(null);
const response = await request.get( const response = await request.get(
`/api/vault/total?title=${title}&author=${author}&v3=${v3}&legacy=${legacy}` `/api/vault/total?title=${title}&author=${author}&v3=${v3}&legacy=${legacy}`
).catch((error)=>{ ).catch((error)=>{
@@ -89,13 +89,13 @@ const VaultPage = (props) => {
); );
updateStateWithBrews([], 1); updateStateWithBrews([], 1);
}); });
if (response.ok) if(response.ok)
setTotalBrews(response.body.totalBrews); setTotalBrews(response.body.totalBrews);
}; };
const loadPage = async (page, updateTotal) => { const loadPage = async (page, updateTotal)=>{
if (!validateForm()) if(!validateForm())
return; return;
setSearching(true); setSearching(true);
@@ -109,14 +109,14 @@ const VaultPage = (props) => {
performSearch(title, author, count, v3, legacy, page); performSearch(title, author, count, v3, legacy, page);
if (updateTotal) if(updateTotal)
loadTotal(title, author, v3, legacy); loadTotal(title, author, v3, legacy);
}; };
const renderNavItems = () => ( const renderNavItems = ()=>(
<Navbar> <Navbar>
<Nav.section> <Nav.section>
<Nav.item className="brewTitle"> <Nav.item className='brewTitle'>
Vault: Search for brews Vault: Search for brews
</Nav.item> </Nav.item>
</Nav.section> </Nav.section>
@@ -129,7 +129,7 @@ const VaultPage = (props) => {
</Navbar> </Navbar>
); );
const validateForm = () => { const validateForm = ()=>{
//form validity: title or author must be written, and at least one renderer set //form validity: title or author must be written, and at least one renderer set
const isTitleValid = titleRef.current.validity.valid && titleRef.current.value; const isTitleValid = titleRef.current.validity.valid && titleRef.current.value;
const isAuthorValid = authorRef.current.validity.valid && authorRef.current.value; const isAuthorValid = authorRef.current.validity.valid && authorRef.current.value;
@@ -140,29 +140,29 @@ const VaultPage = (props) => {
return isFormValid; return isFormValid;
}; };
const disableSubmitIfFormInvalid = () => { const disableSubmitIfFormInvalid = ()=>{
submitButtonRef.current.disabled = !validateForm(); submitButtonRef.current.disabled = !validateForm();
}; };
const renderForm = () => ( const renderForm = ()=>(
<div className="brewLookup"> <div className='brewLookup'>
<h2 className="formTitle">Brew Lookup</h2> <h2 className='formTitle'>Brew Lookup</h2>
<div className="formContents"> <div className='formContents'>
<label> <label>
Title of the brew Title of the brew
<input <input
ref={titleRef} ref={titleRef}
type="text" type='text'
name="title" name='title'
defaultValue={props.query.title || ''} defaultValue={props.query.title || ''}
onKeyUp={disableSubmitIfFormInvalid} onKeyUp={disableSubmitIfFormInvalid}
pattern=".{3,}" pattern='.{3,}'
title="At least 3 characters" title='At least 3 characters'
onKeyDown={(e) => { onKeyDown={(e)=>{
if (e.key === 'Enter' && !submitButtonRef.current.disabled) if(e.key === 'Enter' && !submitButtonRef.current.disabled)
loadPage(1, true); loadPage(1, true);
}} }}
placeholder="v3 Reference Document" placeholder='v3 Reference Document'
/> />
</label> </label>
@@ -170,34 +170,34 @@ const VaultPage = (props) => {
Author of the brew Author of the brew
<input <input
ref={authorRef} ref={authorRef}
type="text" type='text'
name="author" name='author'
pattern=".{1,}" pattern='.{1,}'
defaultValue={props.query.author || ''} defaultValue={props.query.author || ''}
onKeyUp={disableSubmitIfFormInvalid} onKeyUp={disableSubmitIfFormInvalid}
onKeyDown={(e) => { onKeyDown={(e)=>{
if (e.key === 'Enter' && !submitButtonRef.current.disabled) if(e.key === 'Enter' && !submitButtonRef.current.disabled)
loadPage(1, true); loadPage(1, true);
}} }}
placeholder="Username" placeholder='Username'
/> />
</label> </label>
<label> <label>
Results per page Results per page
<select ref={countRef} name="count" defaultValue={props.query.count || 20}> <select ref={countRef} name='count' defaultValue={props.query.count || 20}>
<option value="10">10</option> <option value='10'>10</option>
<option value="20">20</option> <option value='20'>20</option>
<option value="40">40</option> <option value='40'>40</option>
<option value="60">60</option> <option value='60'>60</option>
</select> </select>
</label> </label>
<label> <label>
<input <input
className="renderer" className='renderer'
ref={v3Ref} ref={v3Ref}
type="checkbox" type='checkbox'
defaultChecked={props.query.v3 !== 'false'} defaultChecked={props.query.v3 !== 'false'}
onChange={disableSubmitIfFormInvalid} onChange={disableSubmitIfFormInvalid}
/> />
@@ -206,9 +206,9 @@ const VaultPage = (props) => {
<label> <label>
<input <input
className="renderer" className='renderer'
ref={legacyRef} ref={legacyRef}
type="checkbox" type='checkbox'
defaultChecked={props.query.legacy !== 'false'} defaultChecked={props.query.legacy !== 'false'}
onChange={disableSubmitIfFormInvalid} onChange={disableSubmitIfFormInvalid}
/> />
@@ -216,9 +216,9 @@ const VaultPage = (props) => {
</label> </label>
<button <button
id="searchButton" id='searchButton'
ref={submitButtonRef} ref={submitButtonRef}
onClick={() => { onClick={()=>{
loadPage(1, true); loadPage(1, true);
}} }}
> >
@@ -244,7 +244,7 @@ const VaultPage = (props) => {
<li> <li>
Some common words like "a", "after", "through", "itself", "here", etc., Some common words like "a", "after", "through", "itself", "here", etc.,
are ignored in searches. The full list can be found &nbsp; are ignored in searches. The full list can be found &nbsp;
<a href="https://github.com/mongodb/mongo/blob/0e3b3ca8480ddddf5d0105d11a94bd4698335312/src/mongo/db/fts/stop_words_english.txt"> <a href='https://github.com/mongodb/mongo/blob/0e3b3ca8480ddddf5d0105d11a94bd4698335312/src/mongo/db/fts/stop_words_english.txt'>
here here
</a> </a>
</li> </li>
@@ -253,17 +253,17 @@ const VaultPage = (props) => {
</div> </div>
); );
const renderPaginationControls = () => { const renderPaginationControls = ()=>{
if (!totalBrews) return null; if(!totalBrews) return null;
const countInt = parseInt(props.query.count || 20); const countInt = parseInt(props.query.count || 20);
const totalPages = Math.ceil(totalBrews / countInt); const totalPages = Math.ceil(totalBrews / countInt);
let startPage, endPage; let startPage, endPage;
if (pageState <= 6) { if(pageState <= 6) {
startPage = 1; startPage = 1;
endPage = Math.min(totalPages, 10); endPage = Math.min(totalPages, 10);
} else if (pageState + 4 >= totalPages) { } else if(pageState + 4 >= totalPages) {
startPage = Math.max(1, totalPages - 9); startPage = Math.max(1, totalPages - 9);
endPage = totalPages; endPage = totalPages;
} else { } else {
@@ -273,32 +273,32 @@ const VaultPage = (props) => {
const pagesAroundCurrent = new Array(endPage - startPage + 1) const pagesAroundCurrent = new Array(endPage - startPage + 1)
.fill() .fill()
.map((_, index) => ( .map((_, index)=>(
<a <a
key={startPage + index} key={startPage + index}
className={`pageNumber ${ className={`pageNumber ${
pageState === startPage + index ? 'currentPage' : '' pageState === startPage + index ? 'currentPage' : ''
}`} }`}
onClick={() => loadPage(startPage + index, false)} onClick={()=>loadPage(startPage + index, false)}
> >
{startPage + index} {startPage + index}
</a> </a>
)); ));
return ( return (
<div className="paginationControls"> <div className='paginationControls'>
<button <button
className="previousPage" className='previousPage'
onClick={() => loadPage(pageState - 1, false)} onClick={()=>loadPage(pageState - 1, false)}
disabled={pageState === startPage} disabled={pageState === startPage}
> >
<i className="fa-solid fa-chevron-left"></i> <i className='fa-solid fa-chevron-left'></i>
</button> </button>
<ol className="pages"> <ol className='pages'>
{startPage > 1 && ( {startPage > 1 && (
<a <a
className="pageNumber firstPage" className='pageNumber firstPage'
onClick={() => loadPage(1, false)} onClick={()=>loadPage(1, false)}
> >
1 ... 1 ...
</a> </a>
@@ -306,90 +306,90 @@ const VaultPage = (props) => {
{pagesAroundCurrent} {pagesAroundCurrent}
{endPage < totalPages && ( {endPage < totalPages && (
<a <a
className="pageNumber lastPage" className='pageNumber lastPage'
onClick={() => loadPage(totalPages, false)} onClick={()=>loadPage(totalPages, false)}
> >
... {totalPages} ... {totalPages}
</a> </a>
)} )}
</ol> </ol>
<button <button
className="nextPage" className='nextPage'
onClick={() => loadPage(pageState + 1, false)} onClick={()=>loadPage(pageState + 1, false)}
disabled={pageState === totalPages} disabled={pageState === totalPages}
> >
<i className="fa-solid fa-chevron-right"></i> <i className='fa-solid fa-chevron-right'></i>
</button> </button>
</div> </div>
); );
}; };
const renderFoundBrews = () => { const renderFoundBrews = ()=>{
if (searching) { if(searching) {
return ( return (
<div className="foundBrews searching"> <div className='foundBrews searching'>
<h3 className="searchAnim">Searching</h3> <h3 className='searchAnim'>Searching</h3>
</div> </div>
); );
} }
if (error) { if(error) {
const errorText = ErrorIndex({ brew })[brew.HBErrorCode.toString()] || ''; const errorText = ErrorIndex({ brew })[brew.HBErrorCode.toString()] || '';
console.log('render Error: ', error); console.log('render Error: ', error);
return ( return (
<div className="foundBrews noBrews"> <div className='foundBrews noBrews'>
<h3>Error: {errorText}</h3> <h3>Error: {errorText}</h3>
</div> </div>
); );
} }
if (!brewCollection) { if(!brewCollection) {
return ( return (
<div className="foundBrews noBrews"> <div className='foundBrews noBrews'>
<h3>No search yet</h3> <h3>No search yet</h3>
</div> </div>
); );
} }
if (brewCollection.length === 0) { if(brewCollection.length === 0) {
return ( return (
<div className="foundBrews noBrews"> <div className='foundBrews noBrews'>
<h3>No brews found</h3> <h3>No brews found</h3>
</div> </div>
); );
} }
return ( return (
<div className="foundBrews"> <div className='foundBrews'>
<span className="totalBrews"> <span className='totalBrews'>
{`Brews found: `} {`Brews found: `}
<span>{totalBrews}</span> <span>{totalBrews}</span>
</span> </span>
{brewCollection.map((brew, index) => { {brewCollection.map((brew, index)=>{
return ( return (
<BrewItem <BrewItem
brew={{...brew}} brew={{ ...brew }}
key={index} key={index}
reportError={props.reportError} reportError={props.reportError}
/> />
); );
})} })}
{renderPaginationControls()} {renderPaginationControls()}
</div> </div>
); );
}; };
return ( return (
<div className="vaultPage"> <div className='vaultPage'>
<link href="/themes/V3/Blank/style.css" rel="stylesheet" /> <link href='/themes/V3/Blank/style.css' rel='stylesheet' />
<link href="/themes/V3/5ePHB/style.css" rel="stylesheet" /> <link href='/themes/V3/5ePHB/style.css' rel='stylesheet' />
{renderNavItems()} {renderNavItems()}
<div className="content"> <div className='content'>
<SplitPane hideMoveArrows> <SplitPane hideMoveArrows>
<div className="form dataGroup">{renderForm()}</div> <div className='form dataGroup'>{renderForm()}</div>
<div className="resultsContainer dataGroup"> <div className='resultsContainer dataGroup'>
{renderFoundBrews()} {renderFoundBrews()}
</div> </div>
</SplitPane> </SplitPane>

View File

@@ -4,32 +4,32 @@ const HomebrewModel = require('./homebrew.model.js').model;
const router = express.Router(); const router = express.Router();
const titleConditions = (title) => { const titleConditions = (title)=>{
if (!title) return {}; if(!title) return {};
return { return {
$text: { $text : {
$search: title, $search : title,
$caseSensitive: false, $caseSensitive : false,
}, },
}; };
}; };
const authorConditions = (author) => { const authorConditions = (author)=>{
if (!author) return {}; if(!author) return {};
return { authors: author }; return { authors: author };
}; };
const rendererConditions = (legacy, v3) => { const rendererConditions = (legacy, v3)=>{
if (legacy === 'true' && v3 !== 'true') if(legacy === 'true' && v3 !== 'true')
return { renderer: 'legacy'}; return { renderer: 'legacy' };
if (v3 === 'true' && legacy !== 'true') if(v3 === 'true' && legacy !== 'true')
return { renderer: 'V3'}; return { renderer: 'V3' };
return {}; // If all renderers selected, renderer field not needed in query for speed return {}; // If all renderers selected, renderer field not needed in query for speed
}; };
const findBrews = async (req, res) => { const findBrews = async (req, res)=>{
const title = req.query.title || ''; const title = req.query.title || '';
const author = req.query.author || ''; const author = req.query.author || '';
const page = Math.max(parseInt(req.query.page) || 1, 1); const page = Math.max(parseInt(req.query.page) || 1, 1);
@@ -37,7 +37,7 @@ const findBrews = async (req, res) => {
const skip = (page - 1) * count; const skip = (page - 1) * count;
const combinedQuery = { const combinedQuery = {
$and: [ $and : [
{ published: true }, { published: true },
rendererConditions(req.query.legacy, req.query.v3), rendererConditions(req.query.legacy, req.query.v3),
titleConditions(title), titleConditions(title),
@@ -58,28 +58,27 @@ const findBrews = async (req, res) => {
.limit(count) .limit(count)
.maxTimeMS(5000) .maxTimeMS(5000)
.exec() .exec()
.then((brews) => { .then((brews)=>{
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const processedBrews = brews.map((brew) => { const processedBrews = brews.map((brew)=>{
brew.authors = brew.authors.map(author => brew.authors = brew.authors.map((author)=>emailRegex.test(author) ? 'hidden' : author
emailRegex.test(author) ? 'hidden' : author
); );
return brew; return brew;
}); });
res.json({ brews: processedBrews, page }); res.json({ brews: processedBrews, page });
}) })
.catch((error) => { .catch((error)=>{
throw {...error, message: "Error finding brews in Vault search", HBErrorCode: 90}; throw { ...error, message: 'Error finding brews in Vault search', HBErrorCode: 90 };
}); });
}; };
const findTotal = async (req, res) => { const findTotal = async (req, res)=>{
const title = req.query.title || ''; const title = req.query.title || '';
const author = req.query.author || ''; const author = req.query.author || '';
const combinedQuery = { const combinedQuery = {
$and: [ $and : [
{ published: true }, { published: true },
rendererConditions(req.query.legacy, req.query.v3), rendererConditions(req.query.legacy, req.query.v3),
titleConditions(title), titleConditions(title),
@@ -88,12 +87,12 @@ const findTotal = async (req, res) => {
}; };
await HomebrewModel.countDocuments(combinedQuery) await HomebrewModel.countDocuments(combinedQuery)
.then((totalBrews) => { .then((totalBrews)=>{
console.log(`when returning, the total of brews is ${totalBrews} for the query ${JSON.stringify(combinedQuery)}`); console.log(`when returning, the total of brews is ${totalBrews} for the query ${JSON.stringify(combinedQuery)}`);
res.json({ totalBrews }); res.json({ totalBrews });
}) })
.catch((error) => { .catch((error)=>{
throw {...error, message: "Error finding brews in Vault search findTotal function", HBErrorCode: 91}; throw { ...error, message: 'Error finding brews in Vault search findTotal function', HBErrorCode: 91 };
}); });
}; };