From c5461f2ea2b6a703d4fb9717211ea3b7802bbbf8 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Tue, 13 Oct 2026 12:16:26 -0500 Subject: [PATCH 01/29] Rebuild BR Issue_5048 Moves calls to compress/decompress the text/textBin document members to the hombrewery model code. Compression is done in a save().pre setup. Decompression was already handled in the static for loading a brew but the textBin member was not emptied on decompression. --- package-lock.json | 147 +++++++++++++++++------------------- server/homebrew.api.js | 15 +--- server/homebrew.api.spec.js | 15 +++- server/homebrew.model.js | 8 ++ 4 files changed, 88 insertions(+), 97 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e4277d3a..d17c0ace2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2360,6 +2360,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2371,6 +2372,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2381,6 +2383,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -3646,6 +3649,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -3782,6 +3786,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3802,6 +3807,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3822,6 +3828,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3842,6 +3849,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3862,9 +3870,7 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3885,9 +3891,7 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3908,9 +3912,7 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3931,9 +3933,7 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3954,9 +3954,7 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3977,9 +3975,7 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4000,6 +3996,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4020,6 +4017,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4037,6 +4035,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -4165,9 +4164,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4184,9 +4180,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4203,9 +4196,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4222,9 +4212,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4241,9 +4228,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4260,9 +4244,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4404,6 +4385,7 @@ "version": "0.10.4", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4913,6 +4895,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4926,6 +4909,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4939,6 +4923,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4952,6 +4937,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4965,6 +4951,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4978,6 +4965,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4991,6 +4979,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5004,9 +4993,7 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5020,9 +5007,7 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5036,9 +5021,7 @@ "cpu": [ "loong64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5052,9 +5035,7 @@ "cpu": [ "loong64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5068,9 +5049,7 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5084,9 +5063,7 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5100,9 +5077,7 @@ "cpu": [ "riscv64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5116,9 +5091,7 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5132,9 +5105,7 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5148,9 +5119,7 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5164,6 +5133,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5177,6 +5147,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -5195,6 +5166,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5208,6 +5180,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5221,6 +5194,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11278,9 +11252,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11301,9 +11272,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11324,9 +11292,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11347,9 +11312,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11907,6 +11869,33 @@ "url": "https://opencollective.com/mongoose" } }, + "node_modules/mongoose/node_modules/gcp-metadata": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", + "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongoose/node_modules/google-logging-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.2.0.tgz", + "integrity": "sha512-WE9av4wKDZgRjBwgVUabocx8T6/7o3Ca1Fat46FXDhXVAFibzNadedcOXrdgd1Kzmk8tsk/9ZH89Wyf/SqeZ3A==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/mongoose/node_modules/mongodb": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.7.0.tgz", @@ -14725,7 +14714,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "devOptional": true, + "dev": true, "license": "0BSD" }, "node_modules/type-check": { diff --git a/server/homebrew.api.js b/server/homebrew.api.js index aa1f1f84a..efea261b3 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -2,7 +2,6 @@ import _ from 'lodash'; import { model as HomebrewModel } from './homebrew.model.js'; import express from 'express'; -import zlib from 'zlib'; import GoogleActions from './googleActions.js'; import { hbfm } from 'marked-hbfm'; import * as yaml from 'js-yaml'; @@ -303,13 +302,8 @@ const api = { if(!googleId) return; api.excludeStubProps(newHomebrew); newHomebrew.googleId = googleId; - } else { - // Compress brew text to binary before saving - newHomebrew.textBin = zlib.deflateRawSync(newHomebrew.text); - // Delete the non-binary text field since it's not needed anymore - newHomebrew.text = undefined; } - + saved = await newHomebrew.save() .catch((err)=>{ console.error(err, err.toString(), err.stack); @@ -464,11 +458,6 @@ const api = { if(brew.googleId) { // If the google id exists after all those actions, exclude the props that are stored in google and aren't needed for rendering the brew items api.excludeStubProps(brew); - } else { - // Compress brew text to binary before saving - brew.textBin = zlib.deflateRawSync(brew.text); - // Delete the non-binary text field since it's not needed anymore - brew.text = undefined; } brew.updatedAt = new Date(); brew.version = (brew.version || 1) + 1; @@ -549,8 +538,6 @@ const api = { if(shouldDeleteGoogleBrew) { // When there are still authors remaining, we delete the google brew but store the full brew in the Homebrewery database brew.googleId = undefined; - brew.textBin = zlib.deflateRawSync(brew.text); - brew.text = undefined; } brew.markModified('authors'); //Mongo will not properly update arrays without markModified() await brew.save() diff --git a/server/homebrew.api.spec.js b/server/homebrew.api.spec.js index 8bda5407b..68aeec616 100644 --- a/server/homebrew.api.spec.js +++ b/server/homebrew.api.spec.js @@ -700,8 +700,16 @@ brew`); shareId : expect.any(String), style : undefined, tags : [], - text : undefined, - textBin : expect.objectContaining({}), + text : '```metadata\n' + + 'title: asdf\n' + + 'bleedSize: {}\n' + + 'safetySpace: {}\n' + + 'trimSize: {}\n' + + '\n' + + '```\n' + + '\n' + + 'asdf', + textBin : undefined, theme : '5ePHB', thumbnail : '', title : 'asdf', @@ -1233,8 +1241,7 @@ brew`); expect(saveFunc).toHaveBeenCalled(); expect(saved.authors).toEqual(['test2']); expect(saved.googleId).toEqual(undefined); - expect(saved.text).toEqual(undefined); - expect(saved.textBin).not.toEqual(undefined); + expect(saved.text).not.toEqual(undefined); }); it('should retain google brew and update stub when multiple authors and extra author requests deletion', async ()=>{ diff --git a/server/homebrew.model.js b/server/homebrew.model.js index e923ac928..b78ece631 100644 --- a/server/homebrew.model.js +++ b/server/homebrew.model.js @@ -32,6 +32,13 @@ const HomebrewSchema = mongoose.Schema({ lock : { type: Object, index: true } }, { versionKey: false }); +HomebrewSchema.pre('save', ()=>{ + if(!this.googleId) { + this.textBin = zlib.deflateRawSync(this.text); + this.text = undefined; + } +}); + HomebrewSchema.statics.increaseView = async function(query) { const brew = await Homebrew.findOne(query).exec(); brew.lastViewed = new Date(); @@ -51,6 +58,7 @@ HomebrewSchema.statics.get = async function(query, fields=null){ if(!_.isNil(brew.textBin)) { // Uncompress zipped text field const unzipped = zlib.inflateRawSync(brew.textBin); brew.text = unzipped.toString(); + brew.textBin = undefined; } return brew; }; From 8cb852263564effa6cdf08d975ec0b5e5011c5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Mon, 19 Oct 2026 13:17:08 +0200 Subject: [PATCH 02/29] refactor to functional --- .../editor/metadataEditor/metadataEditor.jsx | 304 +++++++++--------- 1 file changed, 149 insertions(+), 155 deletions(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index a14eb3f09..af357f4c0 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ import '../uiEditor.less'; -import React from 'react'; +import React, {useState, useEffect} from 'react'; import createReactClass from 'create-react-class'; import _ from 'lodash'; import request from '../../utils/request-middleware.js'; @@ -18,48 +18,37 @@ const callIfExists = (val, fn, ...args)=>{ } }; -const MetadataEditor = createReactClass({ - displayName : 'MetadataEditor', - getDefaultProps : function() { - return { - metadata : { - editId : null, - shareId : null, - title : '', - description : '', - thumbnail : '', - tags : [], - published : false, - authors : [], - renderer : 'legacy', - theme : '5ePHB', - lang : 'en' - }, - - onChange : ()=>{}, - reportError : ()=>{} - }; +const MetadataEditor = ({ + metadata = { + editId: null, + shareId: null, + title: '', + description: '', + thumbnail: '', + tags: [], + published: false, + authors: [], + renderer: 'legacy', + theme: '5ePHB', + lang: 'en', }, - getInitialState : function(){ - return { - isOwner : global.account?.username && global.account?.username === this.props.metadata?.authors[0], - showThumbnail : true - }; - }, + onChange = () => {}, + reportError = () => {}, + +}, ...props) => { + const [isOwner, setIsOwner] = useState(global.account?.username && global.account?.username === metadata?.authors[0]); + const [showThumbnail, setShowThumbnail] = useState(true); - toggleThumbnailDisplay : function(){ - this.setState({ - showThumbnail : !this.state.showThumbnail - }); - }, - renderThumbnail : function(){ - if(!this.state.showThumbnail) return; - return ; - }, - - handleFieldChange : function(name, e){ + const toggleThumbnailDisplay = ()=>{ + setShowThumbnail(!showThumbnail); + }; + const renderThumbnail = ()=>{ + if(!showThumbnail) return; + return ; + }; + const handleFieldChange = (name, e)=>{ // load validation rules, and check input value against them const inputRules = validations[name] ?? []; const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean); @@ -72,8 +61,8 @@ const MetadataEditor = createReactClass({ // if no validation rules, save to props if(validationErr.length === 0){ callIfExists(e.target, 'setCustomValidity', ''); - this.props.onChange({ - ...this.props.metadata, + onChange({ + ...metadata, [name] : e.target.value }); return true; @@ -86,45 +75,39 @@ const MetadataEditor = createReactClass({ debouncedReportValidity(e.target, errMessage); return false; } - }, - - handleRenderer : function(renderer, e){ + }; + const handleRenderer = (renderer, e)=>{ if(e.target.checked){ - this.props.metadata.renderer = renderer; + metadata.renderer = renderer; if(renderer == 'legacy') - this.props.metadata.theme = '5ePHB'; + metadata.theme = '5ePHB'; } - this.props.onChange(this.props.metadata, 'renderer'); - }, - - handlePublish : function(val){ - this.props.onChange({ - ...this.props.metadata, + onChange(metadata, 'renderer'); + }; + const handlePublish = (val)=>{ + onChange({ + ...metadata, published : val }); - }, + }; + const handleTheme = (theme)=>{ + metadata.renderer = theme.renderer; + metadata.theme = theme.path; - handleTheme : function(theme){ - this.props.metadata.renderer = theme.renderer; - this.props.metadata.theme = theme.path; - - this.props.onChange(this.props.metadata, 'theme'); - }, - - handleThemeWritein : function(e) { + onChange(metadata, 'theme'); + }; + const handleThemeWritein = (e) =>{ const shareId = e.target.value.split('/').pop(); //Extract just the ID if a URL was pasted in - this.props.metadata.theme = shareId; + metadata.theme = shareId; - this.props.onChange(this.props.metadata, 'theme'); - }, - - handleLanguage : function(languageCode){ - this.props.metadata.lang = languageCode; - this.props.onChange(this.props.metadata); - }, - - handleDelete : function(){ - if(this.props.metadata.authors && this.props.metadata.authors.length <= 1){ + onChange(metadata, 'theme'); + }; + const handleLanguage = (languageCode)=>{ + metadata.lang = languageCode; + onChange(metadata); + }; + const handleDelete = ()=>{ + if(metadata.authors && metadata.authors.length <= 1){ if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return; if(!confirm('Are you REALLY sure? You will not be able to recover the document.')) return; } else { @@ -132,54 +115,50 @@ const MetadataEditor = createReactClass({ if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return; } - request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`) + request.delete(`/api/${metadata.googleId ?? ''}${metadata.editId}`) .send() .end((err, res)=>{ if(err) { - this.props.reportError(err); + reportError(err); } else { window.location.href = '/'; } }); - }, - - handleDeleteAuthor : function(author){ + }; + const handleDeleteAuthor = (author)=>{ if(!confirm('Are you sure you want to remove this author? They will lose all edit access to this brew, and it will dissapear from their userpage.')) return; - if(!this.props.metadata.authors.includes(author)) return; - this.props.onChange({ - ...this.props.metadata, - authors : this.props.metadata.authors.filter((a)=>a !== author) + if(!metadata.authors.includes(author)) return; + onChange({ + ...metadata, + authors : metadata.authors.filter((a)=>a !== author) }); - }, - - renderPublish : function(){ - if(this.props.metadata.published){ - return