0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-27 18:13:08 +00:00

Compare commits

..

15 Commits

Author SHA1 Message Date
Trevor Buckner
2bbff081ed Merge branch 'master' into delete-route-for-account-deletion 2025-07-15 16:05:34 -04:00
Víctor Losada Hernández
c3281038bd Merge branch 'master' of https://github.com/naturalcrit/homebrewery into delete-route-for-account-deletion 2025-05-25 15:31:49 +02:00
Víctor Losada Hernández
cc7e2ebc40 Merge branch 'delete-route-for-account-deletion' of https://github.com/naturalcrit/homebrewery into delete-route-for-account-deletion 2025-05-09 18:23:12 +02:00
Víctor Losada Hernández
22be3916b3 remove logs that break tests 2025-05-09 18:23:10 +02:00
Víctor Losada Hernández
49831f4a17 Merge branch 'master' into delete-route-for-account-deletion 2025-05-09 18:16:38 +02:00
Víctor Losada Hernández
19d6ef23a5 Merge branch 'master' into delete-route-for-account-deletion 2025-05-09 12:24:57 +02:00
Víctor Losada Hernández
3da6822017 removing logs 2025-04-19 20:48:23 +02:00
Víctor Losada Hernández
cd676d2509 remove test, fix issues 2025-04-19 20:43:39 +02:00
Víctor Losada Hernández
7c100e608d testing 2025-04-19 20:30:47 +02:00
Víctor Losada Hernández
a1b8a85583 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into delete-route-for-account-deletion 2025-04-19 19:20:36 +02:00
Trevor Buckner
d253023134 Merge branch 'master' into delete-route-for-account-deletion 2025-04-14 14:27:32 -04:00
Víctor Losada Hernández
abee6b98c0 comment for myself 2025-01-11 23:46:15 +01:00
Víctor Losada Hernández
e0d4eafe17 fix deletion 2025-01-11 23:03:26 +01:00
Víctor Losada Hernández
2d3ebfd6bf Merge branch 'master' of https://github.com/naturalcrit/homebrewery into delete-route-for-account-deletion 2025-01-10 11:39:46 +01:00
Víctor Losada Hernández
2d2f1dc84c This initial commit should work 2024-12-26 21:39:35 +01:00
12 changed files with 541 additions and 771 deletions

View File

@@ -1,37 +0,0 @@
require('./editPage.less');
const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../../navbar/navbar.jsx');
const NewBrewItem = require('../../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../../navbar/help.navitem.jsx');
const PrintNavItem = require('../../../navbar/print.navitem.jsx');
const ErrorNavItem = require('../../../navbar/error-navitem.jsx');
const AccountNavItem = require('../../../navbar/account.navitem.jsx');
const RecentNavItem = require('../../../navbar/recent.navitem.jsx').both;
const VaultNavItem = require('../../../navbar/vault.navitem.jsx');
const BaseEditPage = (props)=>{
return (
<div className={`sitePage ${props.className || ''}`}>
<Navbar>
<Nav.section>
<Nav.item className='brewTitle'>{props.brew.title}</Nav.item>
</Nav.section>
<Nav.section>
{props.navButtons}
<PrintNavItem />
<NewBrewItem />
<HelpNavItem />
<VaultNavItem />
<RecentNavItem brew={props.brew} storageKey={props.recentStorageKey} />
<AccountNavItem />
</Nav.section>
</Navbar>
{props.children}
</div>
);
};
module.exports = BaseEditPage;

View File

@@ -11,11 +11,17 @@ import request from '../../utils/request-middleware.js';
const { Meta } = require('vitreum/headtags'); const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const PrintNavItem = require('../../navbar/print.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx'); const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const VaultNavItem = require('../../navbar/vault.navitem.jsx');
const BaseEditPage = require('../basePages/editPage/editPage.jsx'); const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const SplitPane = require('client/components/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx'); const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
@@ -260,7 +266,7 @@ const EditPage = createClass({
brew.text = brew.text.normalize('NFC'); brew.text = brew.text.normalize('NFC');
this.savedBrew.text = this.savedBrew.text.normalize('NFC'); this.savedBrew.text = this.savedBrew.text.normalize('NFC');
brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
brew.patches = stringifyPatches(makePatches(encodeURI(this.savedBrew.text), encodeURI(brew.text))); brew.patches = stringifyPatches(makePatches(this.savedBrew.text, brew.text));
brew.hash = await md5(this.savedBrew.text); brew.hash = await md5(this.savedBrew.text);
//brew.text = undefined; - Temporary parallel path //brew.text = undefined; - Temporary parallel path
brew.textBin = undefined; brew.textBin = undefined;
@@ -435,7 +441,11 @@ const EditPage = createClass({
renderNavbar : function(){ renderNavbar : function(){
const shareLink = this.processShareId(); const shareLink = this.processShareId();
return <> return <Navbar>
<Nav.section>
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
</Nav.section>
<Nav.section> <Nav.section>
{this.renderGoogleDriveIcon()} {this.renderGoogleDriveIcon()}
{this.state.error ? {this.state.error ?
@@ -445,6 +455,8 @@ const EditPage = createClass({
{this.renderAutoSaveButton()} {this.renderAutoSaveButton()}
</Nav.dropdown> </Nav.dropdown>
} }
<NewBrew />
<HelpNavItem/>
<Nav.dropdown> <Nav.dropdown>
<Nav.item color='teal' icon='fas fa-share-alt'> <Nav.item color='teal' icon='fas fa-share-alt'>
share share
@@ -459,19 +471,20 @@ const EditPage = createClass({
post to reddit post to reddit
</Nav.item> </Nav.item>
</Nav.dropdown> </Nav.dropdown>
<PrintNavItem />
<VaultNavItem />
<RecentNavItem brew={this.state.brew} storageKey='edit' />
<Account />
</Nav.section> </Nav.section>
</>;
</Navbar>;
}, },
render : function(){ render : function(){
return <BaseEditPage return <div className='editPage sitePage'>
className="editPage"
errorState={this.state.error}
parent={this}
brew={this.state.brew}
navButtons={this.renderNavbar()}
recentStorageKey='edit'>
<Meta name='robots' content='noindex, nofollow' /> <Meta name='robots' content='noindex, nofollow' />
{this.renderNavbar()}
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} reviewRequested={this.props.brew.lock.reviewRequested} />} {this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} reviewRequested={this.props.brew.lock.reviewRequested} />}
<div className='content'> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}> <SplitPane onDragFinish={this.handleSplitMove}>
@@ -509,7 +522,7 @@ const EditPage = createClass({
/> />
</SplitPane> </SplitPane>
</div> </div>
</BaseEditPage>; </div>;
} }
}); });

View File

@@ -6,11 +6,16 @@ import request from '../../utils/request-middleware.js';
const { Meta } = require('vitreum/headtags'); const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const NewBrewItem = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const VaultNavItem = require('../../navbar/vault.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const AccountNavItem = require('../../navbar/account.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx'); const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const { fetchThemeBundle } = require('../../../../shared/helpers.js'); const { fetchThemeBundle } = require('../../../../shared/helpers.js');
const BaseEditPage = require('../basePages/editPage/editPage.jsx'); const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const SplitPane = require('client/components/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx'); const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
@@ -20,13 +25,13 @@ const HomePage = createClass({
displayName : 'HomePage', displayName : 'HomePage',
getDefaultProps : function() { getDefaultProps : function() {
return { return {
brew : DEFAULT_BREW brew : DEFAULT_BREW,
ver : '0.0.0'
}; };
}, },
getInitialState : function() { getInitialState : function() {
return { return {
brew : this.props.brew, brew : this.props.brew,
isSaving : false,
welcomeText : this.props.brew.text, welcomeText : this.props.brew.text,
error : undefined, error : undefined,
currentEditorViewPageNum : 1, currentEditorViewPageNum : 1,
@@ -42,11 +47,7 @@ const HomePage = createClass({
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme); fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
}, },
save : function(){ handleSave : function(){
this.setState({
isSaving : true
});
request.post('/api') request.post('/api')
.send(this.state.brew) .send(this.state.brew)
.end((err, res)=>{ .end((err, res)=>{
@@ -56,9 +57,6 @@ const HomePage = createClass({
} }
const brew = res.body; const brew = res.body;
window.location = `/edit/${brew.editId}`; window.location = `/edit/${brew.editId}`;
})
.catch((err)=>{
this.setState({ isSaving: false, error: err });
}); });
}, },
handleSplitMove : function(){ handleSplitMove : function(){
@@ -82,38 +80,26 @@ const HomePage = createClass({
brew : { ...prevState.brew, text: text }, brew : { ...prevState.brew, text: text },
})); }));
}, },
renderSaveButton : function(){
if(this.state.isSaving){
return <Nav.item icon='fas fa-spinner fa-spin' className='save'>
save...
</Nav.item>;
} else {
return <Nav.item icon='fas fa-save' className='save' onClick={this.save}>
save
</Nav.item>;
}
},
renderNavbar : function(){ renderNavbar : function(){
return <> return <Navbar ver={this.props.ver}>
<Nav.section> <Nav.section>
{this.state.error ? {this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> : <ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
null null
} }
<NewBrewItem />
<HelpNavItem />
<VaultNavItem />
<RecentNavItem />
<AccountNavItem />
</Nav.section> </Nav.section>
</>; </Navbar>;
}, },
render : function(){ render : function(){
return <BaseEditPage return <div className='homePage sitePage'>
className="homePage"
errorState={this.state.error}
parent={this}
brew={this.state.brew}
navButtons={this.renderNavbar()}>
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' /> <Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
{this.renderNavbar()}
<div className='content'> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}> <SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
@@ -141,14 +127,14 @@ const HomePage = createClass({
/> />
</SplitPane> </SplitPane>
</div> </div>
<div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.save}> <div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.handleSave}>
Save current <i className='fas fa-save' /> Save current <i className='fas fa-save' />
</div> </div>
<a href='/new' className='floatingNewButton'> <a href='/new' className='floatingNewButton'>
Create your own <i className='fas fa-magic' /> Create your own <i className='fas fa-magic' />
</a> </a>
</BaseEditPage> </div>;
} }
}); });

View File

@@ -7,10 +7,14 @@ import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js'; import Markdown from 'naturalcrit/markdown.js';
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const PrintNavItem = require('../../navbar/print.navitem.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const AccountNavItem = require('../../navbar/account.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx'); const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const BaseEditPage = require('../basePages/editPage/editPage.jsx'); const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const SplitPane = require('client/components/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx'); const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
@@ -208,23 +212,28 @@ const NewPage = createClass({
}, },
renderNavbar : function(){ renderNavbar : function(){
return <> return <Navbar>
<Nav.section>
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
</Nav.section>
<Nav.section> <Nav.section>
{this.state.error ? {this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> : <ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
this.renderSaveButton() this.renderSaveButton()
} }
<PrintNavItem />
<HelpNavItem />
<RecentNavItem />
<AccountNavItem />
</Nav.section> </Nav.section>
</>; </Navbar>;
}, },
render : function(){ render : function(){
return <BaseEditPage return <div className='newPage sitePage'>
className="newPage" {this.renderNavbar()}
errorState={this.state.error}
parent={this}
brew={this.state.brew}
navButtons={this.renderNavbar()}>
<div className='content'> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}> <SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
@@ -259,7 +268,7 @@ const NewPage = createClass({
/> />
</SplitPane> </SplitPane>
</div> </div>
</BaseEditPage>; </div>;
} }
}); });

View File

@@ -12,7 +12,7 @@ const Account = require('../../navbar/account.navitem.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx'); const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx'); const HelpNavItem = require('../../navbar/help.navitem.jsx');
const BrewItem = require('../basePages/listPage/brewItem/brewItem.jsx'); const BrewItem = require('../basePages/listPage/brewItem/brewItem.jsx');
const SplitPane = require('client/components/splitPane/splitPane.jsx'); const SplitPane = require('../../../../shared/naturalcrit/splitPane/splitPane.jsx');
const ErrorIndex = require('../errorPage/errors/errorIndex.js'); const ErrorIndex = require('../errorPage/errors/errorIndex.js');
import request from '../../utils/request-middleware.js'; import request from '../../utils/request-middleware.js';

1101
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -142,13 +142,13 @@
"eslint-plugin-jest": "^29.0.1", "eslint-plugin-jest": "^29.0.1",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"globals": "^16.3.0", "globals": "^16.3.0",
"jest": "^30.0.5", "jest": "^30.0.4",
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2", "jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"stylelint": "^16.22.0", "stylelint": "^16.21.1",
"stylelint-config-recess-order": "^7.1.0", "stylelint-config-recess-order": "^7.1.0",
"stylelint-config-recommended": "^16.0.0", "stylelint-config-recommended": "^16.0.0",
"supertest": "^7.1.4" "supertest": "^7.1.3"
} }
} }

View File

@@ -36,7 +36,6 @@ import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser'; import cookieParser from 'cookie-parser';
import forceSSL from './forcessl.mw.js'; import forceSSL from './forcessl.mw.js';
const sanitizeBrew = (brew, accessType)=>{ const sanitizeBrew = (brew, accessType)=>{
brew._id = undefined; brew._id = undefined;
brew.__v = undefined; brew.__v = undefined;
@@ -373,6 +372,34 @@ app.put('/api/user/rename', async (req, res)=>{
} }
}); });
//Delete brews based on author
app.delete('/api/user/delete', async (req, res)=>{
const { username } = req.body;
const ownAccount = req.account && (req.account.username == username);
if(!ownAccount) return res.status(403).json({ error: 'Must be logged in to delete your account' });
try {
const brews = await HomebrewModel.getByUser(username, true, ['_id', 'googleId', 'editId', 'authors']);
const deletePromises = brews.map((brew)=>{
req.brew = brew;
return new Promise((resolve, reject)=>{
api.deleteBrew(req, res, (err)=>err ? reject(err) : resolve());
});
});
await Promise.all(deletePromises);
return res.json({ success: true, message: `All brews for ${username} have been deleted.` });
} catch (error) {
console.error('Error deleting brews:', error);
if(!res.headersSent) {
return res.status(500).json({ error: 'Failed to delete the brews.' });
}
}
});
//Edit Page //Edit Page
app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, next)=>{ app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, next)=>{
req.brew = req.brew.toObject ? req.brew.toObject() : req.brew; req.brew = req.brew.toObject ? req.brew.toObject() : req.brew;
@@ -383,7 +410,6 @@ app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res,
title : req.brew.title || 'Untitled Brew', title : req.brew.title || 'Untitled Brew',
description : req.brew.description || 'No description.', description : req.brew.description || 'No description.',
image : req.brew.thumbnail || defaultMetaTags.image, image : req.brew.thumbnail || defaultMetaTags.image,
locale : req.brew.lang,
type : 'article' type : 'article'
}; };

View File

@@ -52,13 +52,13 @@ const api = {
// ID Validation Checks // ID Validation Checks
// Homebrewery ID // Homebrewery ID
// Typically 12 characters, but the DB shows a range of 7 to 14 characters // Typically 12 characters, but the DB shows a range of 7 to 14 characters
if(!id.match(/^[a-zA-Z0-9-_]{7,14}$/)){ if(!id.match(/^[A-Za-z0-9_-]{7,14}$/)){
throw { name: 'ID Error', message: 'Invalid ID', status: 404, HBErrorCode: '11', brewId: id }; throw { name: 'ID Error', message: 'Invalid ID', status: 404, HBErrorCode: '11', brewId: id };
} }
// Google ID // Google ID
// Typically 33 characters, old format is 44 - always starts with a 1 // Typically 33 characters, old format is 44 - always starts with a 1
// Managed by Google, may change outside of our control, so any length between 33 and 44 is acceptable // Managed by Google, may change outside of our control, so any length between 33 and 44 is acceptable
if(googleId && !googleId.match(/^1(?:[a-zA-Z0-9-_]{32,43})$/)){ if(googleId && !googleId.match(/^1(?:[A-Za-z0-9+\/]{32,43})$/)){
throw { name: 'Google ID Error', message: 'Invalid ID', status: 404, HBErrorCode: '12', brewId: id }; throw { name: 'Google ID Error', message: 'Invalid ID', status: 404, HBErrorCode: '12', brewId: id };
} }
@@ -375,14 +375,14 @@ const api = {
try { try {
const patches = parsePatch(brewFromClient.patches); const patches = parsePatch(brewFromClient.patches);
// Patch to a throwaway variable while parallelizing - we're more concerned with error/no error. // Patch to a throwaway variable while parallelizing - we're more concerned with error/no error.
const patchedResult = decodeURI(applyPatches(patches, encodeURI(brewFromServer.text))[0]); const patchedResult = applyPatches(patches, brewFromServer.text, { allowExceedingIndices: true })[0];
if(patchedResult != brewFromClient.text) if(patchedResult != brewFromClient.text)
throw("Patches did not apply cleanly, text mismatch detected"); throw("Patches did not apply cleanly, text mismatch detected");
// brew.text = applyPatches(patches, brewFromServer.text)[0]; // brew.text = applyPatches(patches, brewFromServer.text)[0];
} catch (err) { } catch (err) {
//debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`); //debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`);
console.error('Failed to apply patches:', { console.error('Failed to apply patches:', {
//patches : brewFromClient.patches, patches : brewFromClient.patches,
brewId : brewFromClient.editId || 'unknown', brewId : brewFromClient.editId || 'unknown',
error : err error : err
}); });