0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-18 14:22:42 +00:00

Merge branch 'master' into addLockRoutes-#3326

This commit is contained in:
Trevor Buckner
2024-09-24 13:37:53 -04:00
committed by GitHub
8 changed files with 205 additions and 118 deletions

View File

@@ -304,17 +304,14 @@ const MetadataEditor = createClass({
onChange={(e)=>this.handleRenderer('V3', e)} /> onChange={(e)=>this.handleRenderer('V3', e)} />
V3 V3
</label> </label>
<small><a href='/legacy' target='_blank' rel='noopener noreferrer'>Click here to see the demo page for the old Legacy renderer!</a></small>
<a href='/legacy' target='_blank' rel='noopener noreferrer'>
Click here to see the demo page for the old Legacy renderer!
</a>
</div> </div>
</div>; </div>;
}, },
render : function(){ render : function(){
return <div className='metadataEditor'> return <div className='metadataEditor'>
<h1 className='sectionHead'>Brew</h1> <h1>Properties Editor</h1>
<div className='field title'> <div className='field title'>
<label>title</label> <label>title</label>
@@ -362,9 +359,7 @@ const MetadataEditor = createClass({
{this.renderRenderOptions()} {this.renderRenderOptions()}
<hr/> <h2>Authors</h2>
<h1 className='sectionHead'>Authors</h1>
{this.renderAuthors()} {this.renderAuthors()}
@@ -375,15 +370,13 @@ const MetadataEditor = createClass({
notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']} notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}/> onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}/>
<hr/> <h2>Privacy</h2>
<h1 className='sectionHead'>Privacy</h1>
<div className='field publish'> <div className='field publish'>
<label>publish</label> <label>publish</label>
<div className='value'> <div className='value'>
{this.renderPublish()} {this.renderPublish()}
<small>Published homebrews will be publicly viewable and searchable (eventually...)</small> <small>Published brews are searchable in <a href='/vault'>the Vault</a> and visible on your user page. Unpublished brews are not indexed in the Vault or visible on your user page, but can still be shared and indexed by search engines. You can unpublish a brew any time.</small>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,6 @@
@import 'naturalcrit/styles/colors.less'; @import 'naturalcrit/styles/colors.less';
.metadataEditor { .metadataEditor {
position : absolute; position : absolute;
z-index : 5; z-index : 5;
@@ -9,12 +10,19 @@
padding : 25px; padding : 25px;
overflow-y : auto; overflow-y : auto;
background-color : #999999; background-color : #999999;
font-size : 13px;
.sectionHead { h1 {
margin: 0 0 40px;
font-weight: bold;
text-transform: uppercase;
}
h2 {
margin : 20px 0; margin : 20px 0;
font-weight : 1000; font-weight : bold;
border-bottom: 2px solid gray;
&:first-of-type { margin-top : 0; } color: #555;
} }
& > div { margin-bottom : 10px; } & > div { margin-bottom : 10px; }
@@ -43,15 +51,21 @@
min-width : 200px; min-width : 200px;
& > label { & > label {
width : 80px; width : 80px;
font-size : 11px;
font-weight : 800; font-weight : 800;
line-height : 1.8em; line-height : 1.8em;
text-transform : uppercase; text-transform : uppercase;
font-size: .9em;
} }
& > .value { & > .value {
flex : 1 1 auto; flex : 1 1 auto;
width : 50px; width : 50px;
&:invalid { background : #FFB9B9; } &:invalid { background : #FFB9B9; }
small {
display : block;
font-size : 0.9em;
font-style : italic;
line-height : 1.4em;
}
} }
input[type='text'], textarea { input[type='text'], textarea {
border : 1px solid gray; border : 1px solid gray;
@@ -78,7 +92,6 @@
textarea.value { textarea.value {
height : auto; height : auto;
font-family : 'Open Sans', sans-serif; font-family : 'Open Sans', sans-serif;
font-size : 0.8em;
resize : none; resize : none;
} }
} }
@@ -87,12 +100,6 @@
z-index : 200; z-index : 200;
max-width : 150px; max-width : 150px;
} }
small {
display : inline-block;
font-size : 0.6em;
font-style : italic;
line-height : 1.4em;
}
} }
@@ -113,18 +120,13 @@
display : inline-flex; display : inline-flex;
align-items : center; align-items : center;
margin-right : 15px; margin-right : 15px;
font-size : 0.7em; font-size : 0.9em;
font-weight : 800; font-weight : 800;
white-space : nowrap; white-space : nowrap;
vertical-align : middle; vertical-align : middle;
cursor : pointer; cursor : pointer;
user-select : none; user-select : none;
} }
a {
display : inline-flex;
font-size : 0.7em;
font-weight : 800;
}
input { input {
margin : 3px; margin : 3px;
vertical-align : middle; vertical-align : middle;
@@ -149,12 +151,10 @@
} }
} }
.authors.field .value { .authors.field .value {
font-size : 0.8em;
line-height : 1.5em; line-height : 1.5em;
} }
.themes.field { .themes.field {
font-size : 13.33px;
.navDropdownContainer { .navDropdownContainer {
position : relative; position : relative;
z-index : 100; z-index : 100;
@@ -165,9 +165,9 @@
background-color : darkgray; background-color : darkgray;
} }
& > div:first-child { & > div:first-child {
padding : 6px 3px; padding : 3px 3px;
background-color : inherit; background-color : inherit;
border : 2px solid rgb(118,118,118); border : 1px solid gray;
i { float : right; } i { float : right; }
&:hover { &:hover {
color : white; color : white;
@@ -240,6 +240,7 @@
} }
} }
} }
.field .list { .field .list {
display : flex; display : flex;
flex : 1 0; flex : 1 0;
@@ -258,15 +259,15 @@
color : white; color : white;
text-align : center; text-align : center;
cursor : pointer; cursor : pointer;
i { i {
position : relative; position : relative;
top : 50%; top : 50%;
transform : translateY(-50%); transform : translateY(-50%);
} }
&:not(:last-child) { border-right : 1px solid black; } &:not(:last-child) { border-right : 1px solid black; }
&:last-child { border-radius : 0 0.5em 0.5em 0; } &:last-child { border-radius : 0 0.5em 0.5em 0; }
} }
@@ -277,8 +278,7 @@
background-color : #DDDDDD; background-color : #DDDDDD;
border-radius : 0.5em; border-radius : 0.5em;
.icon { .icon { #groupedIcon; }
#groupedIcon; }
} }
.input-group { .input-group {
@@ -294,17 +294,30 @@
height : 100%; height : 100%;
} }
.invalid:focus { background-color : pink; } .input-group {
height : ~'calc(.9em + 4px + .6em)';
.icon { input { border-radius : 0.5em 0 0 0.5em; }
#groupedIcon;
top : -0.54em;
right : 1px;
height : 97%;
font-size : 0.8em;
i { font-size : 1.125em; } input:last-child { border-radius : 0.5em; }
.value {
width : 7.5vw;
min-width : 75px;
height : 100%;
}
.invalid:focus { background-color : pink; }
.icon {
#groupedIcon;
top : -0.54em;
right : 1px;
height : 97%;
i { font-size : 1.125em; }
}
} }
} }
} }
} }

View File

@@ -128,7 +128,7 @@ const StringArrayEditor = createClass({
return <div className='field'> return <div className='field'>
<label>{this.props.label}</label> <label>{this.props.label}</label>
<div style={{ flex: '1 0' }}> <div style={{ flex: '1 0' }} className='value'>
<div className='list'> <div className='list'>
{valueElements} {valueElements}
<div className='input-group'> <div className='input-group'>

View File

@@ -1,3 +1,5 @@
/*eslint max-lines: ["warn", {"max": 400, "skipBlankLines": true, "skipComments": true}]*/
/*eslint max-params:["warn", { max: 10 }], */
require('./vaultPage.less'); require('./vaultPage.less');
const React = require('react'); const React = require('react');
@@ -18,13 +20,15 @@ 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);
const [sortState, setSort] = useState(props.query.sort || 'title');
const [dirState, setdir] = useState(props.query.dir || 'asc');
//Response state //Response state
const [brewCollection, setBrewCollection] = useState(null); const [brewCollection, setBrewCollection] = useState(null);
const [totalBrews, setTotalBrews] = useState(null); const [totalBrews, setTotalBrews] = useState(null);
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const titleRef = useRef(null); const titleRef = useRef(null);
const authorRef = useRef(null); const authorRef = useRef(null);
const countRef = useRef(null); const countRef = useRef(null);
@@ -34,7 +38,7 @@ const VaultPage = (props)=>{
useEffect(()=>{ useEffect(()=>{
disableSubmitIfFormInvalid(); disableSubmitIfFormInvalid();
loadPage(pageState, true); loadPage(pageState, true, props.query.sort, props.query.dir);
}, []); }, []);
const updateStateWithBrews = (brews, page)=>{ const updateStateWithBrews = (brews, page)=>{
@@ -43,7 +47,7 @@ const VaultPage = (props)=>{
setSearching(false); setSearching(false);
}; };
const updateUrl = (titleValue, authorValue, countValue, v3Value, legacyValue, page)=>{ const updateUrl = (titleValue, authorValue, countValue, v3Value, legacyValue, page, sort, dir)=>{
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);
@@ -53,21 +57,23 @@ const VaultPage = (props)=>{
urlParams.set('v3', v3Value); urlParams.set('v3', v3Value);
urlParams.set('legacy', legacyValue); urlParams.set('legacy', legacyValue);
urlParams.set('page', page); urlParams.set('page', page);
urlParams.set('sort', sort);
urlParams.set('dir', dir);
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, sort, dir)=>{
updateUrl(title, author, count, v3, legacy, page); updateUrl(title, author, count, v3, legacy, page, sort, dir);
const response = await request.get( const response = await request
`/api/vault?title=${title}&author=${author}&v3=${v3}&legacy=${legacy}&count=${count}&page=${page}` .get(`/api/vault?title=${title}&author=${author}&v3=${v3}&legacy=${legacy}&count=${count}&page=${page}&sort=${sort}&dir=${dir}`)
).catch((error)=>{ .catch((error)=>{
console.log('error at loadPage: ', error); console.log('error at loadPage: ', error);
setError(error); setError(error);
updateStateWithBrews([], 1); updateStateWithBrews([], 1);
}); });
if(response.ok) if(response.ok)
updateStateWithBrews(response.body.brews, page); updateStateWithBrews(response.body.brews, page);
@@ -76,9 +82,8 @@ const VaultPage = (props)=>{
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)=>{
console.log('error at loadTotal: ', error); console.log('error at loadTotal: ', error);
setError(error); setError(error);
updateStateWithBrews([], 1); updateStateWithBrews([], 1);
@@ -88,9 +93,8 @@ const VaultPage = (props)=>{
setTotalBrews(response.body.totalBrews); setTotalBrews(response.body.totalBrews);
}; };
const loadPage = async (page, updateTotal)=>{ const loadPage = async (page, updateTotal, sort, dir)=>{
if(!validateForm()) if(!validateForm()) return;
return;
setSearching(true); setSearching(true);
setError(null); setError(null);
@@ -100,8 +104,14 @@ const VaultPage = (props)=>{
const count = countRef.current.value || 10; const count = countRef.current.value || 10;
const v3 = v3Ref.current.checked != false; const v3 = v3Ref.current.checked != false;
const legacy = legacyRef.current.checked != false; const legacy = legacyRef.current.checked != false;
const sortOption = sort || 'title';
const dirOption = dir || 'asc';
const pageProp = page || 1;
performSearch(title, author, count, v3, legacy, page); setSort(sortOption);
setdir(dirOption);
performSearch(title, author, count, v3, legacy, pageProp, sortOption, dirOption);
if(updateTotal) if(updateTotal)
loadTotal(title, author, v3, legacy); loadTotal(title, author, v3, legacy);
@@ -248,6 +258,33 @@ const VaultPage = (props)=>{
</div> </div>
); );
const renderSortOption = (optionTitle, optionValue)=>{
const oppositeDir = dirState === 'asc' ? 'desc' : 'asc';
return (
<div className={`sort-option ${sortState === optionValue ? `active` : ''}`}>
<button onClick={()=>loadPage(1, false, optionValue, oppositeDir)}>
{optionTitle}
</button>
{sortState === optionValue && (
<i className={`sortDir fas ${dirState === 'asc' ? 'fa-sort-up' : 'fa-sort-down'}`} />
)}
</div>
);
};
const renderSortBar = ()=>{
return (
<div className='sort-container'>
{renderSortOption('Title', 'title', props.query.dir)}
{renderSortOption('Created Date', 'createdAt', props.query.dir)}
{renderSortOption('Updated Date', 'updatedAt', props.query.dir)}
{renderSortOption('Views', 'views', props.query.dir)}
</div>
);
};
const renderPaginationControls = ()=>{ const renderPaginationControls = ()=>{
if(!totalBrews) return null; if(!totalBrews) return null;
@@ -271,10 +308,8 @@ const VaultPage = (props)=>{
.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, sortState, dirState)}
}`}
onClick={()=>loadPage(startPage + index, false)}
> >
{startPage + index} {startPage + index}
</a> </a>
@@ -284,7 +319,7 @@ const VaultPage = (props)=>{
<div className='paginationControls'> <div className='paginationControls'>
<button <button
className='previousPage' className='previousPage'
onClick={()=>loadPage(pageState - 1, false)} onClick={()=>loadPage(pageState - 1, false, sortState, dirState)}
disabled={pageState === startPage} disabled={pageState === startPage}
> >
<i className='fa-solid fa-chevron-left'></i> <i className='fa-solid fa-chevron-left'></i>
@@ -293,7 +328,7 @@ const VaultPage = (props)=>{
{startPage > 1 && ( {startPage > 1 && (
<a <a
className='pageNumber firstPage' className='pageNumber firstPage'
onClick={()=>loadPage(1, false)} onClick={()=>loadPage(1, false, sortState, dirState)}
> >
1 ... 1 ...
</a> </a>
@@ -302,7 +337,7 @@ const VaultPage = (props)=>{
{endPage < totalPages && ( {endPage < totalPages && (
<a <a
className='pageNumber lastPage' className='pageNumber lastPage'
onClick={()=>loadPage(totalPages, false)} onClick={()=>loadPage(totalPages, false, sortState, dirState)}
> >
... {totalPages} ... {totalPages}
</a> </a>
@@ -310,7 +345,7 @@ const VaultPage = (props)=>{
</ol> </ol>
<button <button
className='nextPage' className='nextPage'
onClick={()=>loadPage(pageState + 1, false)} onClick={()=>loadPage(pageState + 1, false, sortState, dirState)}
disabled={pageState === totalPages} disabled={pageState === totalPages}
> >
<i className='fa-solid fa-chevron-right'></i> <i className='fa-solid fa-chevron-right'></i>
@@ -385,6 +420,7 @@ const VaultPage = (props)=>{
<div className='form dataGroup'>{renderForm()}</div> <div className='form dataGroup'>{renderForm()}</div>
<div className='resultsContainer dataGroup'> <div className='resultsContainer dataGroup'>
{renderSortBar()}
{renderFoundBrews()} {renderFoundBrews()}
</div> </div>
</SplitPane> </SplitPane>

View File

@@ -6,8 +6,8 @@
*:not(input) { user-select : none; } *:not(input) { user-select : none; }
.content { .content {
height : 100%;
background : #2C3E50; background : #2C3E50;
height: 100%;
.dataGroup { .dataGroup {
width : 100%; width : 100%;
@@ -27,9 +27,9 @@
code { code {
padding-inline : 5px; padding-inline : 5px;
font-family : monospace;
background : lightgrey; background : lightgrey;
border-radius : 5px; border-radius : 5px;
font-family : monospace;
} }
h1, h2, h3, h4 { h1, h2, h3, h4 {
@@ -165,6 +165,48 @@
color : white; color : white;
} }
.sort-container {
display : flex;
flex-wrap : wrap;
column-gap : 15px;
justify-content : center;
height : 30px;
color : white;
background-color : #555555;
border-top : 1px solid #666666;
border-bottom : 1px solid #666666;
.sort-option {
display : flex;
align-items : center;
padding : 0 8px;
&:hover { background-color : #444444; }
&.active {
background-color : #333333;
button {
font-weight : 800;
color : white;
& + .sortDir { padding-left : 5px; }
}
}
button {
padding : 0;
font-size : 11px;
font-weight : normal;
color : #CCCCCC;
text-transform : uppercase;
background-color : transparent;
&:hover { background : none; }
}
}
}
.foundBrews { .foundBrews {
position : relative; position : relative;
width : 100%; width : 100%;
@@ -236,15 +278,15 @@
width : 47%; width : 47%;
margin-right : 40px; margin-right : 40px;
color : black; color : black;
isolation:isolate; isolation : isolate;
&:after { &::after {
position:absolute; position : absolute;
inset:0; inset : 0;
display:block; z-index : -2;
content:''; display : block;
content : '';
background-image : url('/assets/parchmentBackground.jpg'); background-image : url('/assets/parchmentBackground.jpg');
z-index:-1;
} }
&:nth-child(even of .brewItem) { margin-right : 0; } &:nth-child(even of .brewItem) { margin-right : 0; }
@@ -257,28 +299,24 @@
color : var(--HB_Color_HeaderText); color : var(--HB_Color_HeaderText);
} }
.info { .info {
position : relative;
z-index : 2;
font-family : 'ScalySansRemake'; font-family : 'ScalySansRemake';
font-size : 1.2em; font-size : 1.2em;
position:relative;
z-index:2;
>span { >span {
margin-right : 12px; margin-right : 12px;
line-height : 1.5em; line-height : 1.5em;
} }
} }
.links { .links { z-index : 2; }
z-index:2;
}
hr { hr {
margin: 0px; margin : 0px;
visibility: hidden; visibility : hidden;
} }
.thumbnail { .thumbnail { z-index : -1; }
z-index:1;
}
} }
.paginationControls { .paginationControls {

40
package-lock.json generated
View File

@@ -38,7 +38,7 @@
"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.6.2", "mongoose": "^8.6.3",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"nconf": "^0.12.1", "nconf": "^0.12.1",
"react": "^18.3.1", "react": "^18.3.1",
@@ -51,7 +51,7 @@
}, },
"devDependencies": { "devDependencies": {
"@stylistic/stylelint-plugin": "^3.0.1", "@stylistic/stylelint-plugin": "^3.0.1",
"eslint": "^9.10.0", "eslint": "^9.11.0",
"eslint-plugin-jest": "^28.8.3", "eslint-plugin-jest": "^28.8.3",
"eslint-plugin-react": "^7.36.1", "eslint-plugin-react": "^7.36.1",
"globals": "^15.9.0", "globals": "^15.9.0",
@@ -59,7 +59,7 @@
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"stylelint": "^16.9.0", "stylelint": "^16.9.0",
"stylelint-config-recess-order": "^5.1.0", "stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended": "^14.0.1", "stylelint-config-recommended": "^14.0.1",
"supertest": "^7.0.0" "supertest": "^7.0.0"
}, },
@@ -2073,9 +2073,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.10.0", "version": "9.11.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.0.tgz",
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", "integrity": "sha512-LPkkenkDqyzTFauZLLAPhIb48fj6drrfMvRGSL9tS3AcZBSVTllemLSNyCvHNNL2t797S/6DJNSIwRwXgMO/eQ==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2091,9 +2091,9 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.1.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"levn": "^0.4.1" "levn": "^0.4.1"
@@ -5829,17 +5829,17 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.10.0", "version": "9.11.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.0.tgz",
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", "integrity": "sha512-yVS6XODx+tMFMDFcG4+Hlh+qG7RM6cCJXtQhCKLSsr3XkLvWggHjCqjfh0XsPPnt1c56oaT6PMgW9XWQQjdHXA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.11.0", "@eslint-community/regexpp": "^4.11.0",
"@eslint/config-array": "^0.18.0", "@eslint/config-array": "^0.18.0",
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
"@eslint/js": "9.10.0", "@eslint/js": "9.11.0",
"@eslint/plugin-kit": "^0.1.0", "@eslint/plugin-kit": "^0.2.0",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.3.0", "@humanwhocodes/retry": "^0.3.0",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
@@ -10801,9 +10801,9 @@
} }
}, },
"node_modules/mongoose": { "node_modules/mongoose": {
"version": "8.6.2", "version": "8.6.3",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.2.tgz", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.3.tgz",
"integrity": "sha512-ErbDVvuUzUfyQpXvJ6sXznmZDICD8r6wIsa0VKjJtB6/LZncqwUn5Um040G1BaNo6L3Jz+xItLSwT0wZmSmUaQ==", "integrity": "sha512-++yRmm7hjMbqVA/8WeiygTnEfrFbiy+OBjQi49GFJIvCQuSYE56myyQWo4j5hbpcHjhHQU8NukMNGTwAWFWjIw==",
"dependencies": { "dependencies": {
"bson": "^6.7.0", "bson": "^6.7.0",
"kareem": "2.6.3", "kareem": "2.6.3",
@@ -13626,9 +13626,9 @@
} }
}, },
"node_modules/stylelint-config-recess-order": { "node_modules/stylelint-config-recess-order": {
"version": "5.1.0", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-5.1.0.tgz", "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-5.1.1.tgz",
"integrity": "sha512-ddapCF6B/kEtQYIFhQFReQ0dvK1ZdgJDM/SGFtIyeooYDbqaJqcOlGkRRGaVErCQYJY/bPSPsLRS2LdQtLJUVQ==", "integrity": "sha512-eDAHWVBelzDbMbdMj15pSw0Ycykv5eLeriJdbGCp0zd44yvhgZLI+wyVHegzXp5NrstxTPSxl0fuOVKdMm0XLA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"stylelint-order": "^6.0.4" "stylelint-order": "^6.0.4"

View File

@@ -113,7 +113,7 @@
"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.6.2", "mongoose": "^8.6.3",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"nconf": "^0.12.1", "nconf": "^0.12.1",
"react": "^18.3.1", "react": "^18.3.1",
@@ -126,7 +126,7 @@
}, },
"devDependencies": { "devDependencies": {
"@stylistic/stylelint-plugin": "^3.0.1", "@stylistic/stylelint-plugin": "^3.0.1",
"eslint": "^9.10.0", "eslint": "^9.11.0",
"eslint-plugin-jest": "^28.8.3", "eslint-plugin-jest": "^28.8.3",
"eslint-plugin-react": "^7.36.1", "eslint-plugin-react": "^7.36.1",
"globals": "^15.9.0", "globals": "^15.9.0",
@@ -134,7 +134,7 @@
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"stylelint": "^16.9.0", "stylelint": "^16.9.0",
"stylelint-config-recess-order": "^5.1.0", "stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended": "^14.0.1", "stylelint-config-recommended": "^14.0.1",
"supertest": "^7.0.0" "supertest": "^7.0.0"
} }

View File

@@ -29,12 +29,18 @@ const rendererConditions = (legacy, 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 sortConditions = (sort, dir) => {
return { [sort]: dir === 'asc' ? 1 : -1 };
};
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);
const count = Math.max(parseInt(req.query.count) || 20, 10); const count = Math.max(parseInt(req.query.count) || 20, 10);
const skip = (page - 1) * count; const skip = (page - 1) * count;
const sort = req.query.sort || 'title';
const dir = req.query.dir || 'asc';
const combinedQuery = { const combinedQuery = {
$and : [ $and : [
@@ -54,6 +60,7 @@ const findBrews = async (req, res)=>{
}; };
await HomebrewModel.find(combinedQuery, projection) await HomebrewModel.find(combinedQuery, projection)
.sort(sortConditions(sort, dir))
.skip(skip) .skip(skip)
.limit(count) .limit(count)
.maxTimeMS(5000) .maxTimeMS(5000)