0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-23 14:23:21 +00:00

Compare commits

...

203 Commits

Author SHA1 Message Date
Trevor Buckner
c8f9e5bcf2 Update Version to v3.6.0 2023-01-23 16:01:32 -05:00
Trevor Buckner
0a9a5909d1 Merge pull request #2589 from jeddai/client-server-version-mismatch-middleware
adjust frontend error handling, add client/server mismatch middleware
2023-01-23 15:40:48 -05:00
Charlie Humphreys
8f75ea4728 fix error issue 2023-01-23 14:22:18 -06:00
Trevor Buckner
91ad46b202 Merge pull request #2627 from naturalcrit/dependabot/npm_and_yarn/cookiejar-2.1.4
Bump cookiejar from 2.1.3 to 2.1.4
2023-01-23 14:59:58 -05:00
Trevor Buckner
752806c0ef Merge pull request #2616 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.7.0
Bump react-router-dom from 6.6.1 to 6.7.0
2023-01-23 14:59:41 -05:00
dependabot[bot]
c39bb67bf3 Bump cookiejar from 2.1.3 to 2.1.4
Bumps [cookiejar](https://github.com/bmeck/node-cookiejar) from 2.1.3 to 2.1.4.
- [Release notes](https://github.com/bmeck/node-cookiejar/releases)
- [Commits](https://github.com/bmeck/node-cookiejar/commits)

---
updated-dependencies:
- dependency-name: cookiejar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-23 19:58:54 +00:00
Trevor Buckner
fe6097e0d9 Merge pull request #2607 from naturalcrit/dependabot/npm_and_yarn/eslint-8.32.0
Bump eslint from 8.31.0 to 8.32.0
2023-01-23 14:58:12 -05:00
Charlie Humphreys
6ddf0bb889 update metadata editor to include error handling 2023-01-23 09:35:19 -06:00
Trevor Buckner
8dcbad97df Merge pull request #2623 from 5e-Cleric/Cloning-views-fix
Cloning views fix
2023-01-23 10:14:23 -05:00
Trevor Buckner
64b3955bc9 Update Dependencies 2023-01-23 10:13:59 -05:00
Victor Losada Hernandez
0f88a13635 Merge branch 'master' of hb into Cloning-views-fix 2023-01-22 22:11:23 +01:00
Victor Losada Hernandez
e8c7d38608 setting view count to 0 2023-01-22 21:57:14 +01:00
Trevor Buckner
6020657529 Ensure getBrew returns an object if undefined 2023-01-22 21:57:14 +01:00
Trevor Buckner
a8c35f3967 Remove extra console log for getGoogleBrew
App.js already logs the thrown error, so we were getting double logs (and Google errors are long....). This should make the logs a bit easier to sift through.
2023-01-22 21:57:13 +01:00
Trevor Buckner
62fa8f511a Only fix stub if we actually found one. 2023-01-22 21:57:13 +01:00
Trevor Buckner
18cf202ac0 If no stub exists, start stub as {}, not undefined
Recent changes to handle bad brew properties (tags as strings, no renderer, etc.) didn't account for the case where an old Google brew is first being stubbed; since no stub exists, there is no object to perform the corrections on, and we crash.
2023-01-22 21:57:13 +01:00
Trevor Buckner
98d9018e13 Fix saving issue when tags do not exist
Rare case where tags don't exist at all; gets confused in the step of converting tag strings to arrays
2023-01-22 21:57:13 +01:00
Trevor Buckner
a9e14f6165 Enable brew version mismatch checking on server
Was commented out previously to allow users time to update client code so the proper error displays
2023-01-22 21:57:13 +01:00
dependabot[bot]
7005e9f760 Bump mongoose from 6.8.2 to 6.8.3
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.8.2 to 6.8.3.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.8.2...6.8.3)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-22 21:57:13 +01:00
dependabot[bot]
163bc4e0b3 Bump @babel/core from 7.20.7 to 7.20.12
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.20.7 to 7.20.12.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.20.12/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-22 21:57:13 +01:00
dependabot[bot]
3420886192 Bump eslint from 8.31.0 to 8.32.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.31.0 to 8.32.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.31.0...v8.32.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-22 17:31:41 +00:00
Trevor Buckner
1704ef6557 Merge pull request #2610 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-react-7.32.1
Bump eslint-plugin-react from 7.31.11 to 7.32.1
2023-01-22 12:30:28 -05:00
Trevor Buckner
637b3311d6 Merge pull request #2612 from naturalcrit/dependabot/npm_and_yarn/mongoose-6.8.4
Bump mongoose from 6.8.3 to 6.8.4
2023-01-22 12:30:09 -05:00
Trevor Buckner
4b8c34bcbd Merge pull request #2608 from naturalcrit/dependabot/npm_and_yarn/marked-4.2.12
Bump marked from 4.2.5 to 4.2.12
2023-01-22 12:29:50 -05:00
Charlie Humphreys
d5a34eedb9 Merge branch 'master' into client-server-version-mismatch-middleware 2023-01-21 15:00:54 -06:00
Trevor Buckner
c997908a49 Merge pull request #2621 from jeddai/adjust-coverage-thresholds
lower coverage thresholds for api
2023-01-21 11:11:31 -05:00
Charlie Humphreys
bd594fa214 lower coverage thresholds for api 2023-01-21 09:22:58 -06:00
Charlie Humphreys
6bae21a578 Merge branch 'master' into client-server-version-mismatch-middleware 2023-01-21 00:26:18 -06:00
Charlie Humphreys
79db97efdf create error navitem and use it in all necessary use cases 2023-01-21 00:25:35 -06:00
Charlie Humphreys
7755affa1e update based on feedback 2023-01-20 23:07:24 -06:00
Trevor Buckner
ea04069fe5 Merge pull request #2618 from naturalcrit/FixGoogleBrewsDuplicatingOnStub
Fix Google Drive brews duplicating
2023-01-20 17:25:47 -05:00
Trevor Buckner
2c3a302c85 Merge branch 'master' into FixGoogleBrewsDuplicatingOnStub 2023-01-20 17:25:07 -05:00
Trevor Buckner
89d9bfe1f1 Merge pull request #2606 from G-Ambatte/addThemesToWatchList-#2605
[LOCAL] Add Themes directory to watch list
2023-01-20 17:21:15 -05:00
Trevor Buckner
c9241e2162 Fix Google Drive brews duplicating 2023-01-20 17:19:02 -05:00
dependabot[bot]
99f7668901 Bump react-router-dom from 6.6.1 to 6.7.0
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.6.1 to 6.7.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.7.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-19 03:01:37 +00:00
dependabot[bot]
187738ee11 Bump mongoose from 6.8.3 to 6.8.4
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.8.3 to 6.8.4.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.8.3...6.8.4)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-18 03:01:47 +00:00
dependabot[bot]
68d3724e50 Bump eslint-plugin-react from 7.31.11 to 7.32.1
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.31.11 to 7.32.1.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.31.11...v7.32.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-17 03:00:45 +00:00
dependabot[bot]
8526faa041 Bump marked from 4.2.5 to 4.2.12
Bumps [marked](https://github.com/markedjs/marked) from 4.2.5 to 4.2.12.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v4.2.5...v4.2.12)

---
updated-dependencies:
- dependency-name: marked
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-16 03:01:52 +00:00
G.Ambatte
ac9fbb1f08 Move changes from 3.6.1 to 3.6.0 2023-01-15 18:24:23 +13:00
G.Ambatte
e82da3a6c1 Add buildDev to NPM scripts 2023-01-15 18:19:23 +13:00
G.Ambatte
40e36fd875 Fix minor version number 2023-01-15 17:26:56 +13:00
G.Ambatte
3c86984cf1 Add change to changelog 2023-01-15 14:13:25 +13:00
G.Ambatte
50d172bbd5 Add Themes directory to watch list 2023-01-15 14:11:33 +13:00
Trevor Buckner
da676c6ec1 Ensure getBrew returns an object if undefined 2023-01-12 13:33:55 -05:00
Trevor Buckner
901615d99e Remove extra console log for getGoogleBrew
App.js already logs the thrown error, so we were getting double logs (and Google errors are long....). This should make the logs a bit easier to sift through.
2023-01-10 21:08:21 -05:00
Trevor Buckner
6ed52f37cc Only fix stub if we actually found one. 2023-01-09 17:30:49 -05:00
Trevor Buckner
15e5a31767 If no stub exists, start stub as {}, not undefined
Recent changes to handle bad brew properties (tags as strings, no renderer, etc.) didn't account for the case where an old Google brew is first being stubbed; since no stub exists, there is no object to perform the corrections on, and we crash.
2023-01-09 14:53:35 -05:00
Trevor Buckner
8cbab4d4ce Fix saving issue when tags do not exist
Rare case where tags don't exist at all; gets confused in the step of converting tag strings to arrays
2023-01-09 14:12:57 -05:00
Trevor Buckner
a9c28e84d0 Enable brew version mismatch checking on server
Was commented out previously to allow users time to update client code so the proper error displays
2023-01-09 10:30:07 -05:00
Trevor Buckner
bafabb84b4 Merge pull request #2591 from naturalcrit/dependabot/npm_and_yarn/mongoose-6.8.3
Bump mongoose from 6.8.2 to 6.8.3
2023-01-09 10:26:36 -05:00
dependabot[bot]
f5d592a291 Bump mongoose from 6.8.2 to 6.8.3
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.8.2 to 6.8.3.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.8.2...6.8.3)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-09 03:00:50 +00:00
Charlie Humphreys
385bee964d adjust frontend error handling, add client/server mismatch middleware 2023-01-06 21:29:21 -06:00
Trevor Buckner
18f277182a Merge pull request #2588 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.20.12
Bump @babel/core from 7.20.7 to 7.20.12
2023-01-06 13:08:12 -05:00
Trevor Buckner
344a9d8334 Merge pull request #2548 from jeddai/api-tests
Add api tests
2023-01-06 13:07:54 -05:00
Charlie Humphreys
67a76f9d86 adjust circleci command for API unit tests 2023-01-06 11:53:38 -06:00
Charlie Humphreys
93d6d1ac6a add unit test command 2023-01-06 11:48:00 -06:00
Charlie Humphreys
4b3edf053f add coverage thresholds and coverage command 2023-01-05 23:30:20 -06:00
Charlie Humphreys
0720ac6a15 update changelog 2023-01-05 23:29:58 -06:00
Charlie Humphreys
c0b9cd951e update circleci test config to run test coverage 2023-01-05 23:29:43 -06:00
Charlie Humphreys
a54ebabf53 fix tests after merge from master 2023-01-05 23:10:25 -06:00
Charlie Humphreys
676acb9e5a Merge branch 'master' into api-tests 2023-01-05 23:02:43 -06:00
Charlie Humphreys
387c468d84 add tests for deletion and address PR feedback 2023-01-05 22:56:54 -06:00
dependabot[bot]
64e7fe3422 Bump @babel/core from 7.20.7 to 7.20.12
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.20.7 to 7.20.12.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.20.12/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-05 03:01:25 +00:00
Trevor Buckner
2b1466c51b Merge pull request #2587 from naturalcrit/FixThemesOnPrint
Fix Themes on /print
2023-01-04 21:41:15 -05:00
Trevor Buckner
332bcde4b2 Themes weren't being picked up for rendering 2023-01-04 21:39:51 -05:00
Trevor Buckner
db783179ce Merge pull request #2429 from G-Ambatte/fixThemesDefault
Add default brew on load and before save
2023-01-04 21:36:37 -05:00
Trevor Buckner
c78d687387 Fix all brews being forced to V3 on load 2023-01-04 21:32:16 -05:00
Trevor Buckner
b717059a39 Fix /new storing string "undefined" in Style tab 2023-01-04 20:58:29 -05:00
Trevor Buckner
e02bde5eed Cleanup; added more fields to DEFAULT_BREW 2023-01-04 17:33:23 -05:00
Trevor Buckner
e1765cad41 Merge branch 'fixThemesDefault' of https://github.com/G-Ambatte/homebrewery into pr/2429 2023-01-03 15:13:41 -05:00
Trevor Buckner
6dab5f836e Merge branch 'master' into pr/2429 2023-01-03 15:13:19 -05:00
Charlie Humphreys
e47f81698c Merge branch 'master' into api-tests 2023-01-03 13:47:09 -06:00
Trevor Buckner
2b824839c3 Merge pull request #2581 from naturalcrit/dependabot/npm_and_yarn/eslint-8.31.0
Bump eslint from 8.30.0 to 8.31.0
2023-01-03 14:05:28 -05:00
Trevor Buckner
26f009a295 Merge pull request #2577 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.6.1
Bump react-router-dom from 6.6.0 to 6.6.1
2023-01-03 14:05:18 -05:00
Trevor Buckner
9b4c997b57 Merge pull request #2578 from naturalcrit/dependabot/npm_and_yarn/mongoose-6.8.2
Bump mongoose from 6.8.1 to 6.8.2
2023-01-03 14:05:10 -05:00
Trevor Buckner
39143295b1 Merge pull request #2576 from naturalcrit/dependabot/npm_and_yarn/marked-4.2.5
Bump marked from 4.2.4 to 4.2.5
2023-01-03 14:05:01 -05:00
Trevor Buckner
6eaf2feb40 Log version mismatch errors
To gauge how frequently they are occurring for future update.
2023-01-03 12:47:17 -05:00
Trevor Buckner
4fe0f1a7af Comment out server-side brew version checking
Requires client-side update to apply brew version correctly; temporarily disabling server-side checking for a few days until more users have updated their clients.
2023-01-03 12:38:00 -05:00
Trevor Buckner
7aac377ce8 Merge pull request #2575 from jeddai/google-brew-version-comparison-fix
add check for truthy version, and make sure the version exists when incrementing it
2023-01-03 11:22:37 -05:00
dependabot[bot]
77542c5f06 Bump eslint from 8.30.0 to 8.31.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.30.0 to 8.31.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.30.0...v8.31.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-02 03:01:18 +00:00
dependabot[bot]
3d365339e4 Bump mongoose from 6.8.1 to 6.8.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.8.1 to 6.8.2.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.8.1...6.8.2)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-29 03:01:18 +00:00
dependabot[bot]
66be400f5f Bump react-router-dom from 6.6.0 to 6.6.1
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.6.0 to 6.6.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.6.1/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-26 03:01:12 +00:00
dependabot[bot]
9880792c4d Bump marked from 4.2.4 to 4.2.5
Bumps [marked](https://github.com/markedjs/marked) from 4.2.4 to 4.2.5.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v4.2.4...v4.2.5)

---
updated-dependencies:
- dependency-name: marked
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-26 03:00:50 +00:00
Charlie Humphreys
ca03a473ec add check for truthy version, and make sure the version exists when incrementing it 2022-12-23 14:31:58 -06:00
Trevor Buckner
c364856942 Merge branch 'master' into fixThemesDefault 2022-12-23 13:40:12 -05:00
Trevor Buckner
4e7a96b67c Merge pull request #2571 from naturalcrit/v3.5.0
v3.5.0
2022-12-23 13:25:00 -05:00
Trevor Buckner
39bfc818a6 Merge pull request #2572 from jeddai/new-props-authors-fix
add elvis to fix invited authors editor
2022-12-23 13:24:48 -05:00
Charlie Humphreys
6d9982f735 add elvis to fix invited authors editor 2022-12-23 12:21:15 -06:00
Trevor Buckner
4f6ba7a388 v3.5.0 2022-12-23 12:07:31 -05:00
Trevor Buckner
0274fb214c Merge pull request #2566 from naturalcrit/dependabot/npm_and_yarn/mongoose-6.8.1
Bump mongoose from 6.8.0 to 6.8.1
2022-12-23 01:57:27 -05:00
Trevor Buckner
15519f142d Merge pull request #2569 from naturalcrit/dependabot/npm_and_yarn/googleapis-110.0.0
Bump googleapis from 109.0.1 to 110.0.0
2022-12-23 01:53:09 -05:00
Trevor Buckner
6b258886a4 Merge pull request #2570 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.20.7
Bump @babel/core from 7.20.5 to 7.20.7
2022-12-23 01:52:49 -05:00
Trevor Buckner
d23a88c997 Merge pull request #2568 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.6.0
Bump react-router-dom from 6.4.5 to 6.6.0
2022-12-23 01:52:42 -05:00
Trevor Buckner
096e17ab5a Merge pull request #2564 from naturalcrit/dependabot/npm_and_yarn/eslint-8.30.0
Bump eslint from 8.29.0 to 8.30.0
2022-12-23 01:52:32 -05:00
Charlie Humphreys
da9e20e96f add more tests and merge from main 2022-12-23 00:11:46 -06:00
dependabot[bot]
53d5f9f6e0 Bump @babel/core from 7.20.5 to 7.20.7
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.20.5 to 7.20.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.20.7/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-23 03:01:08 +00:00
dependabot[bot]
43b4fe75e2 Bump googleapis from 109.0.1 to 110.0.0
Bumps [googleapis](https://github.com/googleapis/google-api-nodejs-client) from 109.0.1 to 110.0.0.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/googleapis-v109.0.1...googleapis-v110.0.0)

---
updated-dependencies:
- dependency-name: googleapis
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-23 03:00:52 +00:00
Charlie Humphreys
925db96b08 Merge branch 'fixThemesDefault' into api-tests 2022-12-22 20:09:49 -06:00
G.Ambatte
efdb8e07b0 Merge branch 'master' into fixThemesDefault 2022-12-23 13:03:31 +13:00
Trevor Buckner
816860dc4f Merge pull request #2561 from jeddai/add-ability-to-invite-authors
Add ability to invite authors + version comparison check
2022-12-22 17:03:31 -05:00
Charlie
314f758d62 Update server/homebrew.api.js
Co-authored-by: Trevor Buckner <calculuschild@gmail.com>
2022-12-22 15:58:33 -06:00
dependabot[bot]
c799aaa7cb Bump react-router-dom from 6.4.5 to 6.6.0
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.4.5 to 6.6.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.6.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-22 03:00:35 +00:00
Charlie Humphreys
2f5bc8db54 remove redundant boolean comparison logic 2022-12-21 16:19:41 -06:00
Charlie Humphreys
7c61a27084 update authorship edit check and error 2022-12-21 15:59:00 -06:00
dependabot[bot]
ad3e83da22 Bump mongoose from 6.8.0 to 6.8.1
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.8.0 to 6.8.1.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.8.0...6.8.1)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-20 03:00:35 +00:00
dependabot[bot]
8888704b58 Bump eslint from 8.29.0 to 8.30.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.29.0 to 8.30.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.29.0...v8.30.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-19 03:00:45 +00:00
Charlie Humphreys
a3dc5e78fd remove console log 2022-12-14 10:04:43 -06:00
G.Ambatte
32506860dd Add additional fields to default 2022-12-14 21:26:57 +13:00
G.Ambatte
a89b20584f Apply default to EditPage 2022-12-14 21:26:40 +13:00
G.Ambatte
8e42c09721 Apply default to HomePage 2022-12-14 21:26:31 +13:00
G.Ambatte
bb5978dfea Apply default to SharePage 2022-12-14 21:26:20 +13:00
G.Ambatte
0dc491adfc Apply default to NewPage 2022-12-14 21:26:11 +13:00
G.Ambatte
f470a6185a Move default brew to app.js 2022-12-14 19:23:16 +13:00
Charlie Humphreys
e5febc1fef update ui to include invitedAuthors array 2022-12-13 21:06:47 -06:00
Charlie Humphreys
354d01e980 set the version in the ui from the server response on save 2022-12-13 21:05:45 -06:00
Charlie Humphreys
63e043593a add invitedAuthors key and move invited author to authors on save 2022-12-13 21:05:00 -06:00
Charlie Humphreys
770d0c141d add 409 return when server version is greater than updating version
This also moves the version back onto the stub
2022-12-13 21:03:51 -06:00
G.Ambatte
0a885c8581 Merge branch 'master' into fixThemesDefault 2022-12-14 12:30:28 +13:00
G.Ambatte
ec9c704e71 Merge branch 'master' into fixThemesDefault 2022-12-14 11:57:37 +13:00
Trevor Buckner
02c0176070 Merge pull request #2509 from jeddai/disable-changes-from-non-authors
Disable changes from non authors
2022-12-12 12:15:23 -05:00
Charlie Humphreys
f847de852b Merge branch 'master' into disable-changes-from-non-authors 2022-12-12 10:39:08 -06:00
Trevor Buckner
86413b5767 Merge pull request #2560 from naturalcrit/fixGoogleToMongoTransfer
Change findOneAndUpdate to FindOne and Save
2022-12-12 10:22:49 -05:00
Trevor Buckner
747c976a14 typo 2022-12-12 10:19:49 -05:00
Trevor Buckner
326c28a11d Change findOneAndUpdate to FindOne and Save
Setting an object property to `undefined` should tell Mongoose to remove that property (for example, remove the googleId from a brew). That doesn't seem to work with `findOneAndUpdate` however; the `undefined` property remains after the update.

Switching back to `save()` to make this work again.
2022-12-12 09:59:04 -05:00
Trevor Buckner
263471bcbb Fix changelog typo 2022-12-10 18:03:16 -05:00
Trevor Buckner
9478454063 Merge pull request #2558 from naturalcrit/v3.4.2
v3.4.2
2022-12-10 14:18:16 -05:00
Trevor Buckner
a9a9804517 v3.4.2 2022-12-10 14:15:09 -05:00
Trevor Buckner
0bde44ec2f Merge pull request #2470 from jeddai/remove-google-get-during-update
update getBrew usages to not fetch google brew during updates
2022-12-10 13:54:58 -05:00
Trevor Buckner
13ad179a1b Merge pull request #2523 from G-Ambatte/showGoogleAuthStatus-#2520
Add link to instructions to refresh Google creds
2022-12-10 13:34:17 -05:00
Trevor Buckner
b72acd9e59 cleanup 2022-12-10 13:31:57 -05:00
G.Ambatte
d0a1ef9571 Change to aggregate query, rename variables 2022-12-09 18:35:17 +13:00
G.Ambatte
d1f049871f Merge branch 'master' into showGoogleAuthStatus-#2520 2022-12-09 07:56:25 +13:00
Trevor Buckner
070184b309 Merge pull request #2553 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.4.5
Bump react-router-dom from 6.4.4 to 6.4.5
2022-12-07 22:49:39 -05:00
Trevor Buckner
cbb41676e0 Merge pull request #2551 from naturalcrit/dependabot/npm_and_yarn/marked-4.2.4
Bump marked from 4.2.3 to 4.2.4
2022-12-07 22:49:09 -05:00
Trevor Buckner
81130dd514 Merge pull request #2550 from naturalcrit/dependabot/npm_and_yarn/supertest-6.3.3
Bump supertest from 6.3.2 to 6.3.3
2022-12-07 22:48:59 -05:00
dependabot[bot]
61d3edca17 Bump react-router-dom from 6.4.4 to 6.4.5
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.4.4 to 6.4.5.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.4.5/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-08 03:01:48 +00:00
dependabot[bot]
248b56a706 Bump marked from 4.2.3 to 4.2.4
Bumps [marked](https://github.com/markedjs/marked) from 4.2.3 to 4.2.4.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v4.2.3...v4.2.4)

---
updated-dependencies:
- dependency-name: marked
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-08 03:01:13 +00:00
dependabot[bot]
90f8d1d6da Bump supertest from 6.3.2 to 6.3.3
Bumps [supertest](https://github.com/visionmedia/supertest) from 6.3.2 to 6.3.3.
- [Release notes](https://github.com/visionmedia/supertest/releases)
- [Commits](https://github.com/visionmedia/supertest/compare/v6.3.2...v6.3.3)

---
updated-dependencies:
- dependency-name: supertest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-08 03:00:58 +00:00
Trevor Buckner
8f08b71475 Merge pull request #2547 from jeddai/tags-validation-fix
Fix tags validation issues
2022-12-07 12:54:22 -05:00
Trevor Buckner
cc6527029c Merge pull request #2546 from naturalcrit/dependabot/npm_and_yarn/mongoose-6.8.0
Bump mongoose from 6.7.5 to 6.8.0
2022-12-07 11:32:27 -05:00
Charlie Humphreys
8a110567fc remove call to persist 2022-12-07 07:32:22 -06:00
Charlie Humphreys
a451e562fb add initial set of tests for api 2022-12-06 00:01:38 -06:00
Charlie Humphreys
4e2f6b1d26 move callIfExists to base file scope 2022-12-05 22:39:38 -06:00
Charlie Humphreys
6b8db74a2b add authors length check and account elvis operator 2022-12-05 22:31:56 -06:00
Charlie Humphreys
4c629772cc add a check for the accessType when editing a document 2022-12-05 22:11:24 -06:00
Charlie Humphreys
208593d203 add callIfExists, which will call a method only if it exists 2022-12-05 22:06:06 -06:00
Charlie Humphreys
99019be152 switch updateOne to findOneAndUpdate and return the saved instead of the brew passed in 2022-12-05 21:00:44 -06:00
dependabot[bot]
fa73e1707d Bump mongoose from 6.7.5 to 6.8.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.7.5 to 6.8.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.7.5...6.8.0)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-06 03:00:40 +00:00
Trevor Buckner
d7de2e3d21 Merge pull request #2545 from naturalcrit/v3.4.1
Up version to v3.4.1
2022-12-05 21:38:11 -05:00
Trevor Buckner
903ff4fd09 Merge pull request #2542 from naturalcrit/dependabot/npm_and_yarn/supertest-6.3.2
Bump supertest from 6.3.1 to 6.3.2
2022-12-05 21:37:55 -05:00
Trevor Buckner
feaabacc94 Merge pull request #2544 from naturalcrit/dependabot/npm_and_yarn/eslint-8.29.0
Bump eslint from 8.28.0 to 8.29.0
2022-12-05 21:37:40 -05:00
Trevor Buckner
a5827f66c9 Up version to v3.4.1 2022-12-05 21:36:01 -05:00
Trevor Buckner
bc0846c190 Merge pull request #2537 from Gazook89/Update-Injector-RegEx
change injector regex to work with safari
2022-12-05 21:18:24 -05:00
Gazook89
ecdcaadfa9 remove | from regexp 2022-12-05 11:25:24 -06:00
dependabot[bot]
db2478f73d Bump eslint from 8.28.0 to 8.29.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.28.0 to 8.29.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.28.0...v8.29.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-05 03:01:41 +00:00
dependabot[bot]
5d6a7e692f Bump supertest from 6.3.1 to 6.3.2
Bumps [supertest](https://github.com/visionmedia/supertest) from 6.3.1 to 6.3.2.
- [Release notes](https://github.com/visionmedia/supertest/releases)
- [Commits](https://github.com/visionmedia/supertest/compare/v6.3.1...v6.3.2)

---
updated-dependencies:
- dependency-name: supertest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-05 03:00:45 +00:00
Gazook89
0fbeca1536 Merge branch 'master' into Update-Injector-RegEx 2022-12-04 20:17:46 -06:00
Trevor Buckner
f5e1869a9b Merge pull request #2529 from naturalcrit/dependabot/npm_and_yarn/fs-extra-11.1.0
Bump fs-extra from 10.1.0 to 11.1.0
2022-12-02 21:39:08 -05:00
Trevor Buckner
10c8414ab0 Merge pull request #2536 from Gazook89/Prevent-Crash-with-Non-Latin-Characters
Encode Filename in Content-Disposition header
2022-12-02 21:31:46 -05:00
Trevor Buckner
6832e68ada Merge pull request #2528 from Gazook89/GoogleActions-error
Change error message about googleActions on local startup.
2022-12-02 21:29:39 -05:00
Trevor Buckner
24477327aa message tweaks 2022-12-02 21:28:57 -05:00
dependabot[bot]
52b11d9b38 Bump fs-extra from 10.1.0 to 11.1.0
Bumps [fs-extra](https://github.com/jprichardson/node-fs-extra) from 10.1.0 to 11.1.0.
- [Release notes](https://github.com/jprichardson/node-fs-extra/releases)
- [Changelog](https://github.com/jprichardson/node-fs-extra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jprichardson/node-fs-extra/compare/10.1.0...11.1.0)

---
updated-dependencies:
- dependency-name: fs-extra
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-03 01:58:32 +00:00
Trevor Buckner
c7b239f362 Merge pull request #2539 from naturalcrit/react17
update to react 17
2022-12-02 20:57:17 -05:00
Trevor Buckner
f1b67ad9d6 Update package-lock.json 2022-12-02 20:55:38 -05:00
Trevor Buckner
6279a85d2e Merge branch 'master' into react17 2022-12-02 17:14:45 -05:00
Trevor Buckner
7a4b5cca04 Merge pull request #2538 from naturalcrit/dependabot/npm_and_yarn/decode-uri-component-0.2.2
Bump decode-uri-component from 0.2.0 to 0.2.2
2022-12-02 17:13:55 -05:00
Trevor Buckner
c4eff2478a update dpendencies 2022-12-02 15:31:06 -05:00
Trevor Buckner
15c918b5d4 Merge pull request #2533 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.4.4
Bump react-router-dom from 6.4.3 to 6.4.4
2022-12-02 10:14:23 -05:00
Gazook89
0a5bfe2939 check if keys exist before trying for auth. 2022-12-01 19:51:36 -06:00
dependabot[bot]
05b9bbdf59 Bump decode-uri-component from 0.2.0 to 0.2.2
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-02 01:23:25 +00:00
Trevor Buckner
b88c89d61b Merge pull request #2535 from naturalcrit/dependabot/npm_and_yarn/mongoose-6.7.5
Bump mongoose from 6.7.4 to 6.7.5
2022-12-01 16:46:00 -05:00
Gazook89
31d58f9075 change regex to work with safari. 2022-12-01 11:55:37 -06:00
Trevor Buckner
743d0fa689 Merge pull request #2527 from Gazook89/OG-Meta-tag-fix
Fix og:image and og:site_name
2022-12-01 12:49:36 -05:00
Gazook89
3b6d3963f1 point to defaultMetatags.image 2022-12-01 11:01:40 -06:00
Gazook89
3a4c2d8f13 change image property to reference default image property
Co-authored-by: Trevor Buckner <calculuschild@gmail.com>
2022-12-01 10:58:27 -06:00
Gazook89
7d86a40261 encode filename string to allow non-latin characters 2022-12-01 10:52:30 -06:00
dependabot[bot]
5527aa7990 Bump mongoose from 6.7.4 to 6.7.5
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.7.4 to 6.7.5.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.7.4...6.7.5)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-01 03:02:05 +00:00
dependabot[bot]
f48484520a Bump react-router-dom from 6.4.3 to 6.4.4
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.4.3 to 6.4.4.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.4.4/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-01 03:00:49 +00:00
Trevor Buckner
5957ddd01c Merge pull request #2526 from naturalcrit/dependabot/npm_and_yarn/mongoose-6.7.4
Bump mongoose from 6.7.3 to 6.7.4
2022-11-30 14:21:53 -05:00
Trevor Buckner
994b1584b7 Merge pull request #2524 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.20.5
Bump @babel/core from 7.20.2 to 7.20.5
2022-11-30 14:21:43 -05:00
Gazook89
9647fbcc74 change default image on a few pages, and default description. 2022-11-29 19:12:54 -06:00
Gazook89
ca6f8d085a changing messaging about googleActions 2022-11-29 12:57:27 -06:00
Gazook89
c2637a7576 fix og:image and og:site_name 2022-11-29 11:27:15 -06:00
dependabot[bot]
4fd1cdd7e8 Bump mongoose from 6.7.3 to 6.7.4
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.7.3 to 6.7.4.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.7.3...6.7.4)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-29 03:01:22 +00:00
dependabot[bot]
ad20ff514a Bump @babel/core from 7.20.2 to 7.20.5
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.20.2 to 7.20.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.20.5/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-29 03:00:35 +00:00
Trevor Buckner
5cd50f7138 Merge pull request #2522 from G-Ambatte/fixAccountLoginTime-#2521
[AccountPage] Fix last login time
2022-11-28 17:19:31 -05:00
G.Ambatte
89e6bada56 Add link to instructions to refresh Google creds 2022-11-29 10:26:44 +13:00
G.Ambatte
420d703f9d Add updateTokens param to GoogleActions/authCheck 2022-11-29 10:00:23 +13:00
Trevor Buckner
090da33f33 Merge pull request #2519 from naturalcrit/v3.4.0
V3.4.0
2022-11-28 13:43:43 -05:00
Charlie Humphreys
fec1766e26 switch fetchGoogle to stubOnly 2022-11-21 16:21:36 -06:00
Charlie Humphreys
f26e3d6cd1 remove tags from google brew fetch 2022-11-18 17:53:47 -06:00
Charlie Humphreys
2e6fcafc68 Merge branch 'remove-google-get-during-update' into disable-changes-from-non-authors 2022-11-16 23:15:55 -06:00
Charlie Humphreys
13b43e8902 Merge branch 'master' into remove-google-get-during-update 2022-11-16 23:15:11 -06:00
Charlie Humphreys
837708fc0c prevent changes to brews from non-authors 2022-11-16 22:39:06 -06:00
Charlie Humphreys
2e305d5636 remove authorship piece from this PR 2022-11-16 22:37:59 -06:00
Charlie Humphreys
f9711de634 add elvis for the possibility that the save failed 2022-11-16 22:32:50 -06:00
Charlie Humphreys
2c6779bb1c adjust getBrew to check authorship, change update method to not perform a get 2022-11-16 22:28:00 -06:00
Charlie Humphreys
0867b142da update getBrew usages to not fetch google brew during updates 2022-10-27 21:44:26 -05:00
G.Ambatte
52a79b4f75 Add default loading properties using custom assign 2022-10-20 19:11:31 +13:00
G.Ambatte
9ccf9d0a83 Merge branch 'master' into fixThemesDefault 2022-10-20 08:03:43 +13:00
G.Ambatte
9ad915c14a Remove unnecessary & incorrect default setting 2022-10-09 21:50:31 +13:00
G.Ambatte
9fd5fea50c Remove obsolete code in app.js 2022-10-09 21:50:11 +13:00
G.Ambatte
41fa0f2c77 Apply defaults on load and before saving 2022-10-09 21:49:25 +13:00
31 changed files with 2816 additions and 1886 deletions

View File

@@ -55,9 +55,15 @@ jobs:
at: .
# run tests!
- run:
name: Test - API Unit Tests
command: npm run test:api-unit
- run:
name: Test - Basic
command: npm run test:basic
- run:
name: Test - Coverage
command: npm run test:coverage
- run:
name: Test - Mustache Spans
command: npm run test:mustache-span

View File

@@ -1,13 +1,17 @@
```css
h5 {
font-size: .35cm !important;
margin-top: 0.3cm;
}
.page ul ul {
margin-left: 0px;
}
.page .taskList {
display:block;
break-inside:auto;
}
.taskList li input {
list-style-type : none;
margin-left : -0.52cm;
@@ -36,15 +40,101 @@ pre {
margin-top : 0.1cm;
}
.page ul + h5 {
margin-top: 0.25cm;
}
.page p + h5 {
margin-top: 0.25cm;
}
.page .openSans {
font-family: 'Open Sans';
font-size: 0.9em;
}
.page {
padding-bottom: 1.5cm;
}
```
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Friday 23/01/2023 - v3.6.0
{{taskList
##### calculuschild
* [x] Fix Google Drive brews sometimes duplicating
Fixes issues [#2603](https://github.com/naturalcrit/homebrewery/issues/2603)
##### Jeddai
* [x] Add unit tests with full coverage for the Homebrewery API
* [x] Add message to refresh the browser if the user is missing an update to the Homebrewery
Fixes issues [#2583](https://github.com/naturalcrit/homebrewery/issues/2583)
##### G-Ambatte
* [x] Auto-compile Themes CSS on development server
##### 5e-Cleric
* [x] Fix cloned brews inheriting the parent view count
}}
### Friday 23/12/2022 - v3.5.0
{{taskList
##### Jeddai
* [x] Only brew owners or invited authors can edit a brew
- Visiting an `/edit` page of a brew that does not list you as an author will result in an error page. Authors can be added to any brew by opening its {{fa,fa-info-circle}} **Properties** menu and typing the author's username (case-sensitive) into the **Invited Authors** bubble.
- Warn user if a newer brew version has been saved on another device
Fixes issues [#1987](https://github.com/naturalcrit/homebrewery/issues/1987)
}}
\page
### Saturday 10/12/2022 - v3.4.2
{{taskList
##### Jeddai
* [x] Fix broken tags editor
* [x] Reduce server load to fix some saving issues
Fixes issues [#2322](https://github.com/naturalcrit/homebrewery/issues/2322)
##### G-Ambatte
* [x] Account page help link for Google Drive errors
Fixes issues [#2520](https://github.com/naturalcrit/homebrewery/issues/2520)
}}
### Monday 05/12/2022 - v3.4.1
{{taskList
##### G-Ambatte
* [x] Fix Account page incorrect last login time
Fixes issues [#2521](https://github.com/naturalcrit/homebrewery/issues/2521)
##### Gazook
* [x] Fix crashing on iOS and Safari browsers
Fixes issues [#2531](https://github.com/naturalcrit/homebrewery/issues/2531)
}}
### Monday 28/11/2022 - v3.4.0
{{taskList
@@ -126,12 +216,7 @@ Fixes issues [#2135](https://github.com/naturalcrit/homebrewery/issues/2135)
* [x] Fix brew settings being lost on first save
Fixes issues [#2427](https://github.com/naturalcrit/homebrewery/issues/2427)
}}
\column
{{taskList
##### Gazook:
* [x] Several updates to bug reporting and error popups
@@ -181,6 +266,10 @@ Fixes issues [#2317](https://github.com/naturalcrit/homebrewery/issues/2317), [
Fixes issues: [#1797](https://github.com/naturalcrit/homebrewery/issues/1797), [#2315](https://github.com/naturalcrit/homebrewery/issues/2315), [#2326](https://github.com/naturalcrit/homebrewery/issues/2326), [#2328](https://github.com/naturalcrit/homebrewery/issues/2328)
}}
\page
### Wednesday 31/08/2022 - v3.2.1
{{taskList
@@ -207,8 +296,6 @@ Fixes issues [#2317](https://github.com/naturalcrit/homebrewery/issues/2317), [
Fixes issues: [#2301](https://github.com/naturalcrit/homebrewery/issues/2301), [#2303](https://github.com/naturalcrit/homebrewery/issues/2303), [#2121](https://github.com/naturalcrit/homebrewery/issues/2121)
}}
\page
### Saturday 27/08/2022 - v3.2.0
{{taskList

View File

@@ -32,6 +32,7 @@ const Editor = createClass({
onTextChange : ()=>{},
onStyleChange : ()=>{},
onMetaChange : ()=>{},
reportError : ()=>{},
renderer : 'legacy'
};
@@ -139,10 +140,10 @@ const Editor = createClass({
// Highlight injectors {style}
if(line.includes('{') && line.includes('}')){
const regex = /(?<!{){(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/g;
const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm;
let match;
while ((match = regex.exec(line)) != null) {
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'injection' });
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' });
}
}
// Highlight inline spans {{content}}
@@ -291,7 +292,8 @@ const Editor = createClass({
rerenderParent={this.rerenderParent} />
<MetadataEditor
metadata={this.props.brew}
onChange={this.props.onMetaChange} />
onChange={this.props.onMetaChange}
reportError={this.props.reportError}/>
</>;
}
},

View File

@@ -4,17 +4,23 @@ const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const request = require('superagent');
const request = require('../../utils/request-middleware.js');
const Nav = require('naturalcrit/nav/nav.jsx');
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
const Themes = require('themes/themes.json');
const validations = require('./validations.js')
const validations = require('./validations.js');
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
const homebreweryThumbnail = require('../../thumbnail.png');
const callIfExists = (val, fn, ...args)=>{
if(val[fn]) {
val[fn](...args);
}
};
const MetadataEditor = createClass({
displayName : 'MetadataEditor',
getDefaultProps : function() {
@@ -31,7 +37,8 @@ const MetadataEditor = createClass({
renderer : 'legacy',
theme : '5ePHB'
},
onChange : ()=>{}
onChange : ()=>{},
reportError : ()=>{}
};
},
@@ -53,28 +60,25 @@ const MetadataEditor = createClass({
},
handleFieldChange : function(name, e){
e.persist();
// load validation rules, and check input value against them
const inputRules = validations[name] ?? [];
const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean);
// if no validation rules, save to props
if(validationErr.length === 0){
e.target.setCustomValidity('');
callIfExists(e.target, 'setCustomValidity', '');
this.props.onChange({
...this.props.metadata,
[name] : e.target.value
});
} else {
// if validation issues, display built-in browser error popup with each error.
console.log(validationErr);
const errMessage = validationErr.map((err)=>{
return `- ${err}`;
}).join('\n');
e.target.setCustomValidity(errMessage);
e.target.reportValidity();
};
callIfExists(e.target, 'setCustomValidity', errMessage);
callIfExists(e.target, 'reportValidity');
}
},
handleSystem : function(system, e){
@@ -118,8 +122,12 @@ const MetadataEditor = createClass({
request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`)
.send()
.end(function(err, res){
window.location.href = '/';
.end((err, res)=>{
if(err) {
this.props.reportError(err);
} else {
window.location.href = '/';
}
});
},
@@ -247,6 +255,8 @@ const MetadataEditor = createClass({
render : function(){
return <div className='metadataEditor'>
<h1 className='sectionHead'>Brew</h1>
<div className='field title'>
<label>title</label>
<input type='text' className='value'
@@ -280,8 +290,6 @@ const MetadataEditor = createClass({
values={this.props.metadata.tags}
onChange={(e)=>this.handleFieldChange('tags', e)}/>
{this.renderAuthors()}
<div className='field systems'>
<label>systems</label>
<div className='value'>
@@ -293,6 +301,23 @@ const MetadataEditor = createClass({
{this.renderRenderOptions()}
<hr/>
<h1 className='sectionHead'>Authors</h1>
{this.renderAuthors()}
<StringArrayEditor label='invited authors' valuePatterns={[/.+/]}
validators={[(v)=>!this.props.metadata.authors?.includes(v)]}
placeholder='invite author' unique={true}
values={this.props.metadata.invitedAuthors}
notes={['Invited authors 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)}/>
<hr/>
<h1 className='sectionHead'>Privacy</h1>
<div className='field publish'>
<label>publish</label>
<div className='value'>

View File

@@ -10,6 +10,15 @@
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
overflow-y : auto;
.sectionHead {
font-weight: 1000;
margin: 20px 0;
&:first-of-type {
margin-top: 0;
}
}
& > div {
margin-bottom: 10px;
}
@@ -30,6 +39,7 @@
}
.field{
display : flex;
flex-wrap : wrap;
width : 100%;
min-width : 200px;
&>label{
@@ -78,6 +88,11 @@
font-size : 0.8em;
}
}
small {
font-size : 0.6em;
font-style : italic;
}
}
@@ -128,10 +143,6 @@
button.unpublish{
.button(@silver);
}
small{
font-size : 0.6em;
font-style : italic;
}
}
.delete.field .value{
@@ -196,6 +207,7 @@
}
.field .list {
display: flex;
flex: 1 0;
flex-wrap: wrap;
> * {

View File

@@ -9,7 +9,9 @@ const StringArrayEditor = createClass({
label : '',
values : [],
valuePatterns : null,
validators : [],
placeholder : '',
notes : [],
unique : false,
cannotEdit : [],
onChange : ()=>{}
@@ -83,7 +85,8 @@ const StringArrayEditor = createClass({
}
const matchesPatterns = !this.props.valuePatterns || this.props.valuePatterns.some((pattern)=>!!(value || '').match(pattern));
const uniqueIfSet = !this.props.unique || !values.includes(value);
return matchesPatterns && uniqueIfSet;
const passesValidators = !this.props.validators || this.props.validators.every((validator)=>validator(value));
return matchesPatterns && uniqueIfSet && passesValidators;
},
handleValueInputKeyDown : function(event, index) {
@@ -123,17 +126,21 @@ const StringArrayEditor = createClass({
</div>
);
return <div className='field values'>
return <div className='field'>
<label>{this.props.label}</label>
<div className='list'>
{valueElements}
<div className='input-group'>
<input type='text' className={`value ${this.valueIsValid(this.state.temporaryValue) ? '' : 'invalid'}`} placeholder={this.props.placeholder}
value={this.state.temporaryValue}
onKeyDown={(e)=>this.handleValueInputKeyDown(e)}
onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
{this.valueIsValid(this.state.temporaryValue) ? <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}><i className='fa fa-check fa-fw'/></div> : null}
<div style={{ flex: '1 0' }}>
<div className='list'>
{valueElements}
<div className='input-group'>
<input type='text' className={`value ${this.valueIsValid(this.state.temporaryValue) ? '' : 'invalid'}`} placeholder={this.props.placeholder}
value={this.state.temporaryValue}
onKeyDown={(e)=>this.handleValueInputKeyDown(e)}
onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
{this.valueIsValid(this.state.temporaryValue) ? <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}><i className='fa fa-check fa-fw'/></div> : null}
</div>
</div>
{this.props.notes ? this.props.notes.map((n)=><p><small>{n}</small></p>) : null}
</div>
</div>;
}

View File

@@ -0,0 +1,85 @@
require('./error-navitem.less');
const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx');
const createClass = require('create-react-class');
const ErrorNavItem = createClass({
getDefaultProps : function() {
return {
error : '',
parent : null
};
},
render : function() {
const clearError = ()=>{
const state = {
error : null
};
if(this.props.parent.state.isSaving) {
state.isSaving = false;
}
this.props.parent.setState(state);
};
const error = this.props.error;
const response = error.response;
const status = response.status;
const message = response.body?.message;
let errMsg = '';
try {
errMsg += `${error.toString()}\n\n`;
errMsg += `\`\`\`\n${error.stack}\n`;
errMsg += `${JSON.stringify(response.error, null, ' ')}\n\`\`\``;
console.log(errMsg);
} catch (e){}
if(status === 409) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Conflict: please refresh to get latest changes'}
</div>
</Nav.item>;
} else if(status === 412) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Your client is out of date. Please save your changes elsewhere and refresh.'}
</div>
</Nav.item>;
}
if(response.req.url.match(/^\/api.*Google.*$/m)){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like your Google credentials have
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer' href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
here
</a>.
</div>
</Nav.item>;
}
});
module.exports = ErrorNavItem;

View File

@@ -0,0 +1,77 @@
.navItem {
&.error {
position : relative;
background-color : @red;
}
.errorContainer{
animation-name: glideDown;
animation-duration: 0.4s;
position : absolute;
top : 100%;
left : 50%;
z-index : 1000;
width : 140px;
padding : 3px;
color : white;
background-color : #333;
border : 3px solid #444;
border-radius : 5px;
transform : translate(-50% + 3px, 10px);
text-align : center;
font-size : 10px;
font-weight : 800;
text-transform : uppercase;
a{
color : @teal;
}
&:before {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #444;
left: 53px;
top: -23px;
}
&:after {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #333;
left: 53px;
top: -19px;
}
.deny {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
border-left : 1px solid #666;
.animate(background-color);
&:hover{
background-color : red;
}
}
.confirm {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
color : white;
.animate(background-color);
&:hover{
background-color : teal;
}
}
}
}

View File

@@ -42,7 +42,6 @@ const AccountPage = createClass({
},
renderUiItems : function() {
// console.log(this.props.uiItems);
return <>
<div className='dataGroup'>
<h1>Account Information <i className='fas fa-user'></i></h1>
@@ -51,12 +50,16 @@ const AccountPage = createClass({
</div>
<div className='dataGroup'>
<h3>Homebrewery Information <NaturalCritIcon /></h3>
<p><strong>Brews on Homebrewery: </strong> {this.props.uiItems.mongoCount || '-'}</p>
<p><strong>Brews on Homebrewery: </strong> {this.props.uiItems.mongoCount}</p>
</div>
<div className='dataGroup'>
<h3>Google Information <i className='fab fa-google-drive'></i></h3>
<p><strong>Linked to Google: </strong> {this.props.uiItems.googleId ? 'YES' : 'NO'}</p>
{this.props.uiItems.googleId ? <p><strong>Brews on Google Drive: </strong> {this.props.uiItems.fileCount || '-'}</p> : '' }
{this.props.uiItems.googleId &&
<p>
<strong>Brews on Google Drive: </strong> {this.props.uiItems.googleCount ?? <>Unable to retrieve files - <a href='https://github.com/naturalcrit/homebrewery/discussions/1580'>follow these steps to renew your Google credentials.</a></>}
</p>
}
</div>
</>;
},

View File

@@ -4,7 +4,7 @@ const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const moment = require('moment');
const request = require('superagent');
const request = require('../../../../utils/request-middleware.js');
const googleDriveIcon = require('../../../../googleDrive.png');
const dedent = require('dedent-tabs').default;
@@ -18,7 +18,8 @@ const BrewItem = createClass({
description : '',
authors : [],
stubbed : true
}
},
reportError : ()=>{}
};
},
@@ -33,8 +34,12 @@ const BrewItem = createClass({
request.delete(`/api/${this.props.brew.googleId ?? ''}${this.props.brew.editId}`)
.send()
.end(function(err, res){
location.reload();
.end((err, res)=>{
if(err) {
this.props.reportError(err);
} else {
location.reload();
}
});
},

View File

@@ -23,7 +23,8 @@ const ListPage = createClass({
brews : []
}
],
navItems : <></>
navItems : <></>,
reportError : null
};
},
getInitialState : function() {
@@ -81,7 +82,7 @@ const ListPage = createClass({
if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>;
return _.map(brews, (brew, idx)=>{
return <BrewItem brew={brew} key={idx}/>;
return <BrewItem brew={brew} key={idx} reportError={this.props.reportError}/>;
});
},

View File

@@ -3,7 +3,7 @@ require('./editPage.less');
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const request = require('superagent');
const request = require('../../utils/request-middleware.js');
const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx');
@@ -12,6 +12,7 @@ const Navbar = require('../../navbar/navbar.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const PrintLink = require('../../navbar/print.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
@@ -21,6 +22,8 @@ const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const Markdown = require('naturalcrit/markdown.js');
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
const googleDriveActive = require('../../googleDrive.png');
const googleDriveInactive = require('../../googleDriveMono.png');
@@ -30,24 +33,7 @@ const EditPage = createClass({
displayName : 'EditPage',
getDefaultProps : function() {
return {
brew : {
text : '',
style : '',
shareId : null,
editId : null,
createdAt : null,
updatedAt : null,
gDrive : false,
trashed : false,
title : '',
description : '',
tags : '',
published : false,
authors : [],
systems : [],
renderer : 'legacy'
}
brew : DEFAULT_BREW_LOAD
};
},
@@ -60,7 +46,7 @@ const EditPage = createClass({
alertLoginToTransfer : false,
saveGoogle : this.props.brew.googleId ? true : false,
confirmGoogleTransfer : false,
errors : null,
error : null,
htmlErrors : Markdown.validate(this.props.brew.text),
url : '',
autoSave : true,
@@ -75,7 +61,6 @@ const EditPage = createClass({
url : window.location.href
});
this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy
this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{
@@ -172,7 +157,10 @@ const EditPage = createClass({
this.setState((prevState)=>({
confirmGoogleTransfer : !prevState.confirmGoogleTransfer
}));
this.clearErrors();
this.setState({
error : null,
isSaving : false
});
},
closeAlerts : function(event){
@@ -188,24 +176,16 @@ const EditPage = createClass({
this.setState((prevState)=>({
saveGoogle : !prevState.saveGoogle,
isSaving : false,
errors : null
error : null
}), ()=>this.save());
},
clearErrors : function(){
this.setState({
errors : null,
isSaving : false
});
},
save : async function(){
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
this.setState((prevState)=>({
isSaving : true,
errors : null,
error : null,
htmlErrors : Markdown.validate(prevState.brew.text)
}));
@@ -220,8 +200,9 @@ const EditPage = createClass({
.send(brew)
.catch((err)=>{
console.log('Error Updating Local Brew');
this.setState({ errors: err });
this.setState({ error: err });
});
if(!res) return;
this.savedBrew = res.body;
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
@@ -230,7 +211,8 @@ const EditPage = createClass({
brew : { ...prevState.brew,
googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null,
editId : this.savedBrew.editId,
shareId : this.savedBrew.shareId
shareId : this.savedBrew.shareId,
version : this.savedBrew.version
},
isPending : false,
isSaving : false,
@@ -280,67 +262,6 @@ const EditPage = createClass({
},
renderSaveButton : function(){
if(this.state.errors){
let errMsg = '';
try {
errMsg += `${this.state.errors.toString()}\n\n`;
errMsg += `\`\`\`\n${this.state.errors.stack}\n`;
errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
console.log(errMsg);
} catch (e){}
// if(this.state.errors.status == '401'){
// return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
// Oops!
// <div className='errorContainer' onClick={this.clearErrors}>
// You must be signed in to a Google account
// to save this to<br />Google Drive!<br />
// <a target='_blank' rel='noopener noreferrer'
// href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
// <div className='confirm'>
// Sign In
// </div>
// </a>
// <div className='deny'>
// Not Now
// </div>
// </div>
// </Nav.item>;
// }
if(this.state.errors.response.req.url.match(/^\/api.*Google.*$/m)){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={this.clearErrors}>
Looks like your Google credentials have
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer'
href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
here
</a>.
</div>
</Nav.item>;
}
if(this.state.autoSaveWarning && this.hasChanges()){
this.setAutosaveWarning();
const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
@@ -384,6 +305,12 @@ const EditPage = createClass({
this.warningTimer;
},
errorReported : function(error) {
this.setState({
error
});
},
renderAutoSaveButton : function(){
return <Nav.item onClick={this.handleAutoSave}>
Autosave <i className={this.state.autoSave ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
@@ -428,10 +355,13 @@ const EditPage = createClass({
<Nav.section>
{this.renderGoogleDriveIcon()}
<Nav.dropdown className='save-menu'>
{this.renderSaveButton()}
{this.renderAutoSaveButton()}
</Nav.dropdown>
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
<Nav.dropdown className='save-menu'>
{this.renderSaveButton()}
{this.renderAutoSaveButton()}
</Nav.dropdown>
}
<NewBrew />
<HelpNavItem/>
<Nav.dropdown>
@@ -469,6 +399,7 @@ const EditPage = createClass({
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
reportError={this.errorReported}
renderer={this.state.brew.renderer}
/>
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} theme={this.state.brew.theme} errors={this.state.htmlErrors} />

View File

@@ -13,10 +13,6 @@
cursor : initial;
color : #666;
}
&.error{
position : relative;
background-color : @red;
}
}
.googleDriveStorage {
position : relative;
@@ -26,74 +22,4 @@
padding : 0px;
margin : -5px;
}
.errorContainer{
animation-name: glideDown;
animation-duration: 0.4s;
position : absolute;
top : 100%;
left : 50%;
z-index : 500;
width : 140px;
padding : 3px;
color : white;
background-color : #333;
border : 3px solid #444;
border-radius : 5px;
transform : translate(-50% + 3px, 10px);
text-align : center;
font-size : 10px;
font-weight : 800;
text-transform : uppercase;
a{
color : @teal;
}
&:before {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #444;
left: 53px;
top: -23px;
}
&:after {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #333;
left: 53px;
top: -19px;
}
.deny {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
border-left : 1px solid #666;
.animate(background-color);
&:hover{
background-color : red;
}
}
.confirm {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
color : white;
.animate(background-color);
&:hover{
background-color : teal;
}
}
}
}

View File

@@ -3,7 +3,7 @@ const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const request = require('superagent');
const request = require('../../utils/request-middleware.js');
const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx');
@@ -12,35 +12,38 @@ const NewBrewItem = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.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 SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
const HomePage = createClass({
displayName : 'HomePage',
getDefaultProps : function() {
return {
brew : {
text : '',
},
ver : '0.0.0'
brew : DEFAULT_BREW,
ver : '0.0.0'
};
},
getInitialState : function() {
return {
brew : this.props.brew,
welcomeText : this.props.brew.text
welcomeText : this.props.brew.text,
error : undefined
};
},
handleSave : function(){
request.post('/api')
.send(this.state.brew)
.end((err, res)=>{
if(err) return;
if(err) {
this.setState({ error: err });
return;
}
const brew = res.body;
window.location = `/edit/${brew.editId}`;
});
@@ -56,6 +59,10 @@ const HomePage = createClass({
renderNavbar : function(){
return <Navbar ver={this.props.ver}>
<Nav.section>
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
null
}
<NewBrewItem />
<HelpNavItem />
<RecentNavItem />

View File

@@ -40,4 +40,11 @@
right : 350px;
}
}
.navItem.save{
background-color: @orange;
&:hover{
background-color: @green;
}
}
}

View File

@@ -3,13 +3,14 @@ require('./newPage.less');
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const request = require('superagent');
const request = require('../../utils/request-middleware.js');
const Markdown = require('naturalcrit/markdown.js');
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const AccountNavItem = require('../../navbar/account.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const HelpNavItem = require('../../navbar/help.navitem.jsx');
@@ -17,6 +18,8 @@ const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
const METAKEY = 'homebrewery-new-meta';
@@ -26,36 +29,18 @@ const NewPage = createClass({
displayName : 'NewPage',
getDefaultProps : function() {
return {
brew : {
text : '',
style : undefined,
title : '',
description : '',
renderer : 'V3',
theme : '5ePHB'
}
brew : DEFAULT_BREW
};
},
getInitialState : function() {
let brew = this.props.brew;
if(this.props.brew.shareId) {
brew = {
text : brew.text ?? '',
style : brew.style ?? undefined,
title : brew.title ?? '',
description : brew.description ?? '',
renderer : brew.renderer ?? 'legacy',
theme : brew.theme ?? '5ePHB'
};
}
const brew = this.props.brew;
return {
brew : brew,
isSaving : false,
saveGoogle : (global.account && global.account.googleId ? true : false),
errors : null,
error : null,
htmlErrors : Markdown.validate(brew.text)
};
},
@@ -83,7 +68,8 @@ const NewPage = createClass({
}
localStorage.setItem(BREWKEY, brew.text);
localStorage.setItem(STYLEKEY, brew.style);
if(brew.style)
localStorage.setItem(STYLEKEY, brew.style);
localStorage.setItem(METAKEY, JSON.stringify({ 'renderer': brew.renderer, 'theme': brew.theme }));
},
componentWillUnmount : function() {
@@ -137,14 +123,6 @@ const NewPage = createClass({
}));
},
clearErrors : function(){
this.setState({
errors : null,
isSaving : false
});
},
save : async function(){
this.setState({
isSaving : true
@@ -167,7 +145,7 @@ const NewPage = createClass({
.send(brew)
.catch((err)=>{
console.log(err);
this.setState({ isSaving: false, errors: err });
this.setState({ isSaving: false, error: err });
});
if(!res) return;
@@ -179,67 +157,6 @@ const NewPage = createClass({
},
renderSaveButton : function(){
if(this.state.errors){
let errMsg = '';
try {
errMsg += `${this.state.errors.toString()}\n\n`;
errMsg += `\`\`\`\n${this.state.errors.stack}\n`;
errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
console.log(errMsg);
} catch (e){}
// if(this.state.errors.status == '401'){
// return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
// Oops!
// <div className='errorContainer' onClick={this.clearErrors}>
// You must be signed in to a Google account
// to save this to<br />Google Drive!<br />
// <a target='_blank' rel='noopener noreferrer'
// href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
// <div className='confirm'>
// Sign In
// </div>
// </a>
// <div className='deny'>
// Not Now
// </div>
// </div>
// </Nav.item>;
// }
if(this.state.errors.response.req.url.match(/^\/api.*Google.*$/m)){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={this.clearErrors}>
Looks like your Google credentials have
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer'
href={`https://github.com/naturalcrit/homebrewery/issues/new?body=${encodeURIComponent(errMsg)}`}>
here
</a>.
</div>
</Nav.item>;
}
if(this.state.isSaving){
return <Nav.item icon='fas fa-spinner fa-spin' className='save'>
save...
@@ -269,7 +186,10 @@ const NewPage = createClass({
</Nav.section>
<Nav.section>
{this.renderSaveButton()}
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
this.renderSaveButton()
}
{this.renderLocalPrintButton()}
<HelpNavItem />
<RecentNavItem />

View File

@@ -4,79 +4,5 @@
&:hover{
background-color: @green;
}
&.error{
position : relative;
background-color : @red;
}
}
.errorContainer{
animation-name: glideDown;
animation-duration: 0.4s;
position : absolute;
top : 100%;
left : 50%;
z-index : 100000;
width : 140px;
padding : 3px;
color : white;
background-color : #333;
border : 3px solid #444;
border-radius : 5px;
transform : translate(-50% + 3px, 10px);
text-align : center;
font-size : 10px;
font-weight : 800;
text-transform : uppercase;
a{
color : @teal;
}
&:before {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #444;
left: 53px;
top: -23px;
}
&:after {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #333;
left: 53px;
top: -19px;
}
.deny {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
border-left : 1px solid #666;
.animate(background-color);
&:hover{
background-color : red;
}
}
.confirm {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
color : white;
.animate(background-color);
&:hover{
background-color : teal;
}
}
}
}

View File

@@ -29,9 +29,10 @@ const PrintPage = createClass({
getInitialState : function() {
return {
brew : {
text : this.props.brew.text || '',
style : this.props.brew.style || undefined,
renderer : this.props.brew.renderer || 'legacy'
text : this.props.brew.text || '',
style : this.props.brew.style || undefined,
renderer : this.props.brew.renderer || 'legacy',
theme : this.props.brew.theme || '5ePHB'
}
};
},
@@ -48,7 +49,7 @@ const PrintPage = createClass({
text : brewStorage,
style : styleStorage,
renderer : metaStorage?.renderer || 'legacy',
theme : metaStorage?.theme || '5ePHB'
theme : metaStorage?.theme || '5ePHB'
}
};
});

View File

@@ -12,21 +12,13 @@ const Account = require('../../navbar/account.navitem.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
const SharePage = createClass({
displayName : 'SharePage',
getDefaultProps : function() {
return {
brew : {
title : '',
text : '',
style : '',
shareId : null,
createdAt : null,
updatedAt : null,
views : 0,
renderer : ''
}
brew : DEFAULT_BREW_LOAD
};
},

View File

@@ -12,6 +12,7 @@ const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const Account = require('../../navbar/account.navitem.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const UserPage = createClass({
displayName : 'UserPage',
@@ -19,7 +20,8 @@ const UserPage = createClass({
return {
username : '',
brews : [],
query : ''
query : '',
error : null
};
},
getInitialState : function() {
@@ -50,10 +52,19 @@ const UserPage = createClass({
brewCollection : brewCollection
};
},
errorReported : function(error) {
this.setState({
error
});
},
navItems : function() {
return <Navbar>
<Nav.section>
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
null
}
<NewBrew />
<HelpNavItem />
<RecentNavItem />
@@ -63,7 +74,7 @@ const UserPage = createClass({
},
render : function(){
return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()} query={this.props.query}></ListPage>;
return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()} query={this.props.query} reportError={this.errorReported}></ListPage>;
}
});

View File

@@ -0,0 +1,12 @@
const request = require('superagent');
const addHeader = (request)=>request.set('Homebrewery-Version', global.version);
const requestMiddleware = {
get : (path)=>addHeader(request.get(path)),
put : (path)=>addHeader(request.put(path)),
post : (path)=>addHeader(request.post(path)),
delete : (path)=>addHeader(request.delete(path)),
};
module.exports = requestMiddleware;

2265
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.4.0",
"version": "3.6.0",
"engines": {
"node": "16.11.x"
},
@@ -14,11 +14,14 @@
"quick": "node scripts/quick.js",
"build": "node scripts/buildHomebrew.js",
"buildall": "node scripts/buildHomebrew.js && node scripts/buildAdmin.js",
"builddev": "node scripts/buildHomebrew.js --dev",
"lint": "eslint --fix **/*.{js,jsx}",
"lint:dry": "eslint **/*.{js,jsx}",
"circleci": "npm test && eslint **/*.{js,jsx} --max-warnings=0",
"verify": "npm run lint && npm test",
"test": "jest",
"test:api-unit": "jest server/*.spec.js --verbose",
"test:coverage": "jest --coverage --silent",
"test:dev": "jest --verbose --watch",
"test:basic": "jest tests/markdown/basic.test.js --verbose",
"test:mustache-span": "jest tests/markdown/mustache-span.test.js --verbose",
@@ -39,7 +42,21 @@
"mode_modules",
"shared",
"server"
]
],
"coverageThreshold" : {
"global" : {
"statements" : 25,
"branches" : 10,
"functions" : 22,
"lines" : 25
},
"server/homebrew.api.js" : {
"statements" : 65,
"branches" : 50,
"functions" : 60,
"lines" : 70
}
}
},
"babel": {
"presets": [
@@ -51,7 +68,7 @@
]
},
"dependencies": {
"@babel/core": "^7.19.6",
"@babel/core": "^7.20.12",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.19.4",
"@babel/preset-react": "^7.18.6",
@@ -64,32 +81,32 @@
"express": "^4.18.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.7",
"fs-extra": "10.1.0",
"googleapis": "109.0.1",
"fs-extra": "11.1.0",
"googleapis": "110.0.0",
"js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "4.2.3",
"marked": "4.2.12",
"marked-extended-tables": "^1.0.5",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.4",
"mongoose": "^6.7.3",
"mongoose": "^6.8.4",
"nanoid": "3.3.4",
"nconf": "^0.12.0",
"npm": "^8.10.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-frame-component": "4.1.3",
"react-router-dom": "6.4.3",
"react-router-dom": "6.7.0",
"sanitize-filename": "1.6.3",
"superagent": "^6.1.0",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
},
"devDependencies": {
"eslint": "^8.28.0",
"eslint-plugin-react": "^7.31.11",
"eslint": "^8.32.0",
"eslint-plugin-react": "^7.32.1",
"jest": "^29.2.2",
"supertest": "^6.3.1"
"supertest": "^6.3.3"
}
}

View File

@@ -136,6 +136,6 @@ fs.emptyDirSync('./build');
if(isDev){
livereload('./build');
watchFile('./server.js', {
watch : ['./client', './server'] // Watch additional folders if you want
watch : ['./client', './server', './themes'] // Watch additional folders if you want
});
}

View File

@@ -15,6 +15,8 @@ const serveCompressedStaticAssets = require('./static-assets.mv.js');
const sanitizeFilename = require('sanitize-filename');
const asyncHandler = require('express-async-handler');
const { DEFAULT_BREW } = require('./brewDefaults.js');
const splitTextStyleAndMetadata = (brew)=>{
brew.text = brew.text.replaceAll('\r\n', '\n');
if(brew.text.startsWith('```metadata')) {
@@ -29,7 +31,6 @@ const splitTextStyleAndMetadata = (brew)=>{
brew.style = brew.text.slice(7, index - 1);
brew.text = brew.text.slice(index + 5);
}
_.defaults(brew, { 'renderer': 'legacy', 'theme': '5ePHB' });
};
const sanitizeBrew = (brew, accessType)=>{
@@ -78,10 +79,10 @@ const faqText = require('fs').readFileSync('faq.md', 'utf8');
String.prototype.replaceAll = function(s, r){return this.split(s).join(r);};
const defaultMetaTags = {
siteName : 'The Homebrewery - Make your Homebrew content look legit!',
site_name : 'The Homebrewery - Make your Homebrew content look legit!',
title : 'The Homebrewery',
description : 'A NaturalCrit Tool for Homebrews',
thumbnail : `${config.get('publicUrl')}/thumbnail.png`,
description : 'A NaturalCrit Tool for creating authentic Homebrews using Markdown.',
image : `${config.get('publicUrl')}/thumbnail.png`,
type : 'website'
};
@@ -148,8 +149,7 @@ app.get('/changelog', async (req, res, next)=>{
req.ogMeta = { ...defaultMetaTags,
title : 'Changelog',
description : 'Development changelog.',
thumbnail : null
description : 'Development changelog.'
};
splitTextStyleAndMetadata(req.brew);
@@ -192,12 +192,19 @@ app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{
sanitizeBrew(brew, 'share');
const prefix = 'HB - ';
const encodeRFC3986ValueChars = (str)=>{
return (
encodeURIComponent(str)
.replace(/[!'()*]/g, (char)=>{`%${char.charCodeAt(0).toString(16).toUpperCase()}`;})
);
};
let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');
if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; };
res.set({
'Cache-Control' : 'no-cache',
'Content-Type' : 'text/plain',
'Content-Disposition' : `attachment; filename="${fileName}.txt"`
'Content-Disposition' : `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.txt`
});
res.status(200).send(brew.text);
});
@@ -208,8 +215,7 @@ app.get('/user/:username', async (req, res, next)=>{
req.ogMeta = { ...defaultMetaTags,
title : `${req.params.username}'s Collection`,
description : 'View my collection of homebrew on the Homebrewery.',
image : null
description : 'View my collection of homebrew on the Homebrewery.'
// type : could be 'profile'?
};
@@ -274,7 +280,7 @@ app.get('/edit/:id', asyncHandler(getBrew('edit')), (req, res, next)=>{
req.ogMeta = { ...defaultMetaTags,
title : req.brew.title || 'Untitled Brew',
description : req.brew.description || 'No description.',
image : req.brew.thumbnail || null,
image : req.brew.thumbnail || defaultMetaTags.image,
type : 'article'
};
@@ -288,12 +294,12 @@ app.get('/edit/:id', asyncHandler(getBrew('edit')), (req, res, next)=>{
app.get('/new/:id', asyncHandler(getBrew('share')), (req, res, next)=>{
sanitizeBrew(req.brew, 'share');
splitTextStyleAndMetadata(req.brew);
req.brew.views = 0;
req.brew.title = `CLONE - ${req.brew.title}`;
req.ogMeta = { ...defaultMetaTags,
title : 'New',
description : 'Start crafting your homebrew on the Homebrewery!',
image : null
description : 'Start crafting your homebrew on the Homebrewery!'
};
return next();
@@ -306,7 +312,7 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r
req.ogMeta = { ...defaultMetaTags,
title : req.brew.title || 'Untitled Brew',
description : req.brew.description || 'No description.',
image : req.brew.thumbnail || null,
image : req.brew.thumbnail || defaultMetaTags.image,
type : 'article'
};
@@ -336,11 +342,11 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
data.title = 'Account Information Page';
let auth;
let files;
let googleCount = [];
if(req.account) {
if(req.account.googleId) {
try {
auth = await GoogleActions.authCheck(req.account, res);
auth = await GoogleActions.authCheck(req.account, res, false);
} catch (e) {
auth = undefined;
console.log('Google auth check failed!');
@@ -348,9 +354,9 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
}
if(auth.credentials.access_token) {
try {
files = await GoogleActions.listGoogleBrews(auth);
googleCount = await GoogleActions.listGoogleBrews(auth);
} catch (e) {
files = undefined;
googleCount = undefined;
console.log('List Google files failed!');
console.log(e);
}
@@ -358,18 +364,19 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
}
const query = { authors: req.account.username, googleId: { $exists: false } };
const brews = await HomebrewModel.find(query, 'id')
const mongoCount = await HomebrewModel.countDocuments(query)
.catch((err)=>{
mongoCount = 0;
console.log(err);
});
data.uiItems = {
username : req.account.username,
issued : req.account.issued,
mongoCount : brews.length,
googleId : Boolean(req.account.googleId),
authCheck : Boolean(req.account.googleId && auth.credentials.access_token),
fileCount : files?.length || '-'
username : req.account.username,
issued : req.account.issued,
googleId : Boolean(req.account.googleId),
authCheck : Boolean(req.account.googleId && auth.credentials.access_token),
mongoCount : mongoCount,
googleCount : googleCount?.length
};
}
@@ -377,8 +384,7 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
req.ogMeta = { ...defaultMetaTags,
title : `Account Page`,
description : null,
image : null
description : null
};
return next();
@@ -402,6 +408,7 @@ if(isLocalEnvironment){
//Render the page
const templateFn = require('./../client/template.js');
app.use(asyncHandler(async (req, res, next)=>{
// Create configuration object
const configuration = {
local : isLocalEnvironment,

36
server/brewDefaults.js Normal file
View File

@@ -0,0 +1,36 @@
const _ = require('lodash');
// Default properties for newly-created brews
const DEFAULT_BREW = {
title : '',
text : '',
style : undefined,
description : '',
editId : undefined,
shareId : undefined,
createdAt : undefined,
updatedAt : undefined,
renderer : 'V3',
theme : '5ePHB',
authors : [],
tags : [],
systems : [],
thumbnail : '',
published : false,
pageCount : 1,
gDrive : false,
trashed : false
};
// Default values for older brews with missing properties
// e.g., missing "renderer" is assumed to be "legacy"
const DEFAULT_BREW_LOAD = _.defaults(
{
renderer : 'legacy',
},
DEFAULT_BREW);
module.exports = {
DEFAULT_BREW,
DEFAULT_BREW_LOAD
};

View File

@@ -5,24 +5,28 @@ const { nanoid } = require('nanoid');
const token = require('./token.js');
const config = require('./config.js');
const keys = typeof(config.get('service_account')) == 'string' ?
JSON.parse(config.get('service_account')) :
config.get('service_account');
let serviceAuth;
try {
serviceAuth = google.auth.fromJSON(keys);
serviceAuth.scopes = [
'https://www.googleapis.com/auth/drive'
];
} catch (err) {
console.warn(err);
console.log('Please make sure that a Google Service Account is set up properly in your config files.');
if(!config.get('service_account')){
console.log('No Google Service Account in config files - Google Drive integration will not be available.');
} else {
const keys = typeof(config.get('service_account')) == 'string' ?
JSON.parse(config.get('service_account')) :
config.get('service_account');
try {
serviceAuth = google.auth.fromJSON(keys);
serviceAuth.scopes = ['https://www.googleapis.com/auth/drive'];
} catch (err) {
console.warn(err);
console.log('Please make sure the Google Service Account is set up properly in your config files.');
}
}
google.options({ auth: serviceAuth || config.get('google_api_key') });
const GoogleActions = {
authCheck : (account, res)=>{
authCheck : (account, res, updateTokens=true)=>{
if(!account || !account.googleId){ // If not signed into Google
const err = new Error('Not Signed In');
err.status = 401;
@@ -40,7 +44,7 @@ const GoogleActions = {
refresh_token : account.googleRefreshToken
});
oAuth2Client.on('tokens', (tokens)=>{
updateTokens && oAuth2Client.on('tokens', (tokens)=>{
if(tokens.refresh_token) {
account.googleRefreshToken = tokens.refresh_token;
}
@@ -249,7 +253,6 @@ const GoogleActions = {
text : file.data,
description : obj.data.description,
tags : obj.data.properties.tags ? obj.data.properties.tags : '',
systems : obj.data.properties.systems ? obj.data.properties.systems.split(',') : [],
authors : [],
published : obj.data.properties.published ? obj.data.properties.published == 'true' : false,

View File

@@ -9,330 +9,340 @@ const yaml = require('js-yaml');
const asyncHandler = require('express-async-handler');
const { nanoid } = require('nanoid');
const { DEFAULT_BREW, DEFAULT_BREW_LOAD } = require('./brewDefaults.js');
// const getTopBrews = (cb) => {
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
// cb(brews);
// });
// };
const getId = (req)=>{
// Set the id and initial potential google id, where the google id is present on the existing brew.
let id = req.params.id, googleId = req.body?.googleId;
// If the id is longer than 12, then it's a google id + the edit id. This splits the longer id up.
if(id.length > 12) {
googleId = id.slice(0, -12);
id = id.slice(-12);
}
return { id, googleId };
};
const getBrew = (accessType)=>{
// Create middleware with the accessType passed in as part of the scope
return async (req, res, next)=>{
// Get relevant IDs for the brew
const { id, googleId } = getId(req);
// Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine.
let stub = await HomebrewModel.get(accessType === 'edit' ? { editId: id } : { shareId: id })
.catch((err)=>{
if(googleId) {
console.warn(`Unable to find document stub for ${accessType}Id ${id}`);
} else {
console.warn(err);
}
});
stub = stub?.toObject();
// If there is a google id, try to find the google brew
if(googleId || stub?.googleId) {
let googleError;
const googleBrew = await GoogleActions.getGoogleBrew(googleId || stub?.googleId, id, accessType)
.catch((err)=>{
console.warn(err);
googleError = err;
});
// If we can't find the google brew and there is a google id for the brew, throw an error.
if(!googleBrew) throw googleError;
// Combine the Homebrewery stub with the google brew, or if the stub doesn't exist just use the google brew
stub = stub ? _.assign({ ...excludeStubProps(stub), stubbed: true }, excludeGoogleProps(googleBrew)) : googleBrew;
}
// If after all of that we still don't have a brew, throw an exception
if(!stub) {
throw 'Brew not found in Homebrewery database or Google Drive';
}
if(typeof stub.tags === 'string') {
stub.tags = [];
}
req.brew = stub;
next();
};
};
const mergeBrewText = (brew)=>{
let text = brew.text;
if(brew.style !== undefined) {
text = `\`\`\`css\n` +
`${brew.style || ''}\n` +
`\`\`\`\n\n` +
`${text}`;
}
const metadata = _.pick(brew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme']);
text = `\`\`\`metadata\n` +
`${yaml.dump(metadata)}\n` +
`\`\`\`\n\n` +
`${text}`;
return text;
};
const MAX_TITLE_LENGTH = 100;
const getGoodBrewTitle = (text)=>{
const tokens = Markdown.marked.lexer(text);
return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title')
.slice(0, MAX_TITLE_LENGTH);
};
const api = {
homebrewApi : router,
getId : (req)=>{
// Set the id and initial potential google id, where the google id is present on the existing brew.
let id = req.params.id, googleId = req.body?.googleId;
const excludePropsFromUpdate = (brew)=>{
// Remove undesired properties
const modified = _.clone(brew);
const propsToExclude = ['_id', 'views', 'lastViewed', 'editId', 'shareId', 'googleId'];
for (const prop of propsToExclude) {
delete modified[prop];
}
return modified;
};
const excludeGoogleProps = (brew)=>{
const modified = _.clone(brew);
const propsToExclude = ['tags', 'systems', 'published', 'authors', 'owner', 'views', 'thumbnail'];
for (const prop of propsToExclude) {
delete modified[prop];
}
return modified;
};
const excludeStubProps = (brew)=>{
const propsToExclude = ['text', 'textBin', 'renderer', 'pageCount', 'version'];
for (const prop of propsToExclude) {
brew[prop] = undefined;
}
return brew;
};
const beforeNewSave = (account, brew)=>{
if(!brew.title) {
brew.title = getGoodBrewTitle(brew.text);
}
brew.authors = (account) ? [account.username] : [];
brew.text = mergeBrewText(brew);
};
const newGoogleBrew = async (account, brew, res)=>{
const oAuth2Client = GoogleActions.authCheck(account, res);
const newBrew = excludeGoogleProps(brew);
return await GoogleActions.newGoogleBrew(oAuth2Client, newBrew);
};
const newBrew = async (req, res)=>{
const brew = req.body;
const { saveToGoogle } = req.query;
delete brew.editId;
delete brew.shareId;
delete brew.googleId;
beforeNewSave(req.account, brew);
const newHomebrew = new HomebrewModel(brew);
newHomebrew.editId = nanoid(12);
newHomebrew.shareId = nanoid(12);
let googleId, saved;
if(saveToGoogle) {
googleId = await newGoogleBrew(req.account, newHomebrew, res)
.catch((err)=>{
console.error(err);
res.status(err?.status || err?.response?.status || 500).send(err?.message || err);
});
if(!googleId) return;
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);
throw `Error while creating new brew, ${err.toString()}`;
});
if(!saved) return;
saved = saved.toObject();
res.status(200).send(saved);
};
const updateBrew = async (req, res)=>{
// Initialize brew from request and body, destructure query params, set a constant for the google id, and set the initial value for the after-save method
let brew = _.assign(req.brew, excludePropsFromUpdate(req.body));
const { saveToGoogle, removeFromGoogle } = req.query;
const googleId = brew.googleId;
let afterSave = async ()=>true;
brew.text = mergeBrewText(brew);
if(brew.googleId && removeFromGoogle) {
// If the google id exists and we're removing it from google, set afterSave to delete the google brew and mark the brew's google id as undefined
afterSave = async ()=>{
return await deleteGoogleBrew(req.account, googleId, brew.editId, res)
.catch((err)=>{
console.error(err);
res.status(err?.status || err?.response?.status || 500).send(err.message || err);
});
};
brew.googleId = undefined;
} else if(!brew.googleId && saveToGoogle) {
// If we don't have a google id and the user wants to save to google, create the google brew and set the google id on the brew
brew.googleId = await newGoogleBrew(req.account, excludeGoogleProps(brew), res)
.catch((err)=>{
console.error(err);
res.status(err.status || err.response.status).send(err.message || err);
});
if(!brew.googleId) return;
} else if(brew.googleId) {
// If the google id exists and no other actions are being performed, update the google brew
const updated = await GoogleActions.updateGoogleBrew(excludeGoogleProps(brew))
.catch((err)=>{
console.error(err);
res.status(err?.response?.status || 500).send(err);
});
if(!updated) return;
}
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
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();
if(req.account) {
brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
}
// Fetch the brew from the database again (if it existed there to begin with), and assign the existing brew to it
brew = _.assign(await HomebrewModel.findOne({ _id: brew._id }), brew);
if(!brew.markModified) {
// If it wasn't in the database, create a new db brew
brew = new HomebrewModel(brew);
}
brew.markModified('authors');
brew.markModified('systems');
// Save the database brew
const saved = await brew.save()
.catch((err)=>{
console.error(err);
res.status(err.status || 500).send(err.message || 'Unable to save brew to Homebrewery database');
});
if(!saved) return;
// Call and wait for afterSave to complete
const after = await afterSave();
if(!after) return;
res.status(200).send(saved);
};
const deleteGoogleBrew = async (account, id, editId, res)=>{
const auth = await GoogleActions.authCheck(account, res);
await GoogleActions.deleteGoogleBrew(auth, id, editId);
return true;
};
const deleteBrew = async (req, res, next)=>{
// Delete an orphaned stub if its Google brew doesn't exist
try {
await getBrew('edit')(req, res, ()=>{});
} catch (err) {
const { id, googleId } = getId(req);
console.warn(`No google brew found for id ${googleId}, the stub with id ${id} will be deleted.`);
await HomebrewModel.deleteOne({ editId: id });
return next();
}
let brew = req.brew;
const { googleId, editId } = brew;
const account = req.account;
const isOwner = account && (brew.authors.length === 0 || brew.authors[0] === account.username);
// If the user is the owner and the file is saved to google, mark the google brew for deletion
const shouldDeleteGoogleBrew = googleId && isOwner;
if(brew._id) {
brew = _.assign(await HomebrewModel.findOne({ _id: brew._id }), brew);
if(account) {
// Remove current user as author
brew.authors = _.pull(brew.authors, account.username);
brew.markModified('authors');
// If the id is longer than 12, then it's a google id + the edit id. This splits the longer id up.
if(id.length > 12) {
googleId = id.slice(0, -12);
id = id.slice(-12);
}
return { id, googleId };
},
getBrew : (accessType, stubOnly = false)=>{
// Create middleware with the accessType passed in as part of the scope
return async (req, res, next)=>{
// Get relevant IDs for the brew
const { id, googleId } = api.getId(req);
if(brew.authors.length === 0) {
// Delete brew if there are no authors left
await brew.remove()
// Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine.
let stub = await HomebrewModel.get(accessType === 'edit' ? { editId: id } : { shareId: id })
.catch((err)=>{
console.error(err);
throw { status: 500, message: 'Error while removing' };
if(googleId) {
console.warn(`Unable to find document stub for ${accessType}Id ${id}`);
} else {
console.warn(err);
}
});
} else {
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;
stub = stub?.toObject();
// If there is a google id, try to find the google brew
if(!stubOnly && (googleId || stub?.googleId)) {
let googleError;
const googleBrew = await GoogleActions.getGoogleBrew(googleId || stub?.googleId, id, accessType)
.catch((err)=>{
googleError = err;
});
// Throw any error caught while attempting to retrieve Google brew.
if(googleError) throw googleError;
// Combine the Homebrewery stub with the google brew, or if the stub doesn't exist just use the google brew
stub = stub ? _.assign({ ...api.excludeStubProps(stub), stubbed: true }, api.excludeGoogleProps(googleBrew)) : googleBrew;
}
const authorsExist = stub?.authors?.length > 0;
const isAuthor = stub?.authors?.includes(req.account?.username);
const isInvited = stub?.invitedAuthors?.includes(req.account?.username);
if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) {
throw `The current logged in user does not have editor access to this brew.
If you believe you should have access to this brew, ask the file owner to invite you as an author by opening the brew, viewing the Properties tab, and adding your username to the "invited authors" list. You can then try to access this document again.`;
}
// Otherwise, save the brew with updated author list
await brew.save()
.catch((err)=>{
throw { status: 500, message: err };
});
// If after all of that we still don't have a brew, throw an exception
if(!stub && !stubOnly) {
throw 'Brew not found in Homebrewery database or Google Drive';
}
// Clean up brew: fill in missing fields with defaults / fix old invalid values
if(stub) {
stub.tags = stub.tags || undefined; // Clear empty strings
stub.renderer = stub.renderer || undefined; // Clear empty strings
stub = _.defaults(stub, DEFAULT_BREW_LOAD); // Fill in blank fields
}
req.brew = stub ?? {};
next();
};
},
mergeBrewText : (brew)=>{
let text = brew.text;
if(brew.style !== undefined) {
text = `\`\`\`css\n` +
`${brew.style || ''}\n` +
`\`\`\`\n\n` +
`${text}`;
}
}
if(shouldDeleteGoogleBrew) {
const deleted = await deleteGoogleBrew(account, googleId, editId, res)
const metadata = _.pick(brew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme']);
text = `\`\`\`metadata\n` +
`${yaml.dump(metadata)}\n` +
`\`\`\`\n\n` +
`${text}`;
return text;
},
getGoodBrewTitle : (text)=>{
const tokens = Markdown.marked.lexer(text);
return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title')
.slice(0, MAX_TITLE_LENGTH);
},
excludePropsFromUpdate : (brew)=>{
// Remove undesired properties
const modified = _.clone(brew);
const propsToExclude = ['_id', 'views', 'lastViewed'];
for (const prop of propsToExclude) {
delete modified[prop];
}
return modified;
},
excludeGoogleProps : (brew)=>{
const modified = _.clone(brew);
const propsToExclude = ['version', 'tags', 'systems', 'published', 'authors', 'owner', 'views', 'thumbnail'];
for (const prop of propsToExclude) {
delete modified[prop];
}
return modified;
},
excludeStubProps : (brew)=>{
const propsToExclude = ['text', 'textBin', 'renderer', 'pageCount'];
for (const prop of propsToExclude) {
brew[prop] = undefined;
}
return brew;
},
beforeNewSave : (account, brew)=>{
if(!brew.title) {
brew.title = api.getGoodBrewTitle(brew.text);
}
brew.authors = (account) ? [account.username] : [];
brew.text = api.mergeBrewText(brew);
_.defaults(brew, DEFAULT_BREW);
},
newGoogleBrew : async (account, brew, res)=>{
const oAuth2Client = GoogleActions.authCheck(account, res);
const newBrew = api.excludeGoogleProps(brew);
return await GoogleActions.newGoogleBrew(oAuth2Client, newBrew);
},
newBrew : async (req, res)=>{
const brew = req.body;
const { saveToGoogle } = req.query;
delete brew.editId;
delete brew.shareId;
delete brew.googleId;
api.beforeNewSave(req.account, brew);
const newHomebrew = new HomebrewModel(brew);
newHomebrew.editId = nanoid(12);
newHomebrew.shareId = nanoid(12);
let googleId, saved;
if(saveToGoogle) {
googleId = await api.newGoogleBrew(req.account, newHomebrew, res)
.catch((err)=>{
console.error(err);
res.status(err?.status || err?.response?.status || 500).send(err?.message || err);
});
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);
res.status(500).send(err);
console.error(err, err.toString(), err.stack);
throw `Error while creating new brew, ${err.toString()}`;
});
if(!deleted) return;
if(!saved) return;
saved = saved.toObject();
res.status(200).send(saved);
},
updateBrew : async (req, res)=>{
// Initialize brew from request and body, destructure query params, and set the initial value for the after-save method
const brewFromClient = api.excludePropsFromUpdate(req.body);
const brewFromServer = req.brew;
if(brewFromServer.version && brewFromClient.version && brewFromServer.version > brewFromClient.version) {
console.log(`Version mismatch on brew ${brewFromClient.editId}`);
res.setHeader('Content-Type', 'application/json');
return res.status(409).send(JSON.stringify({ message: `The brew has been changed on a different device. Please save your changes elsewhere, refresh, and try again.` }));
}
let brew = _.assign(brewFromServer, brewFromClient);
const googleId = brew.googleId;
const { saveToGoogle, removeFromGoogle } = req.query;
let afterSave = async ()=>true;
brew.text = api.mergeBrewText(brew);
if(brew.googleId && removeFromGoogle) {
// If the google id exists and we're removing it from google, set afterSave to delete the google brew and mark the brew's google id as undefined
afterSave = async ()=>{
return await api.deleteGoogleBrew(req.account, googleId, brew.editId, res)
.catch((err)=>{
console.error(err);
res.status(err?.status || err?.response?.status || 500).send(err.message || err);
});
};
brew.googleId = undefined;
} else if(!brew.googleId && saveToGoogle) {
// If we don't have a google id and the user wants to save to google, create the google brew and set the google id on the brew
brew.googleId = await api.newGoogleBrew(req.account, api.excludeGoogleProps(brew), res)
.catch((err)=>{
console.error(err);
res.status(err.status || err.response.status).send(err.message || err);
});
if(!brew.googleId) return;
} else if(brew.googleId) {
// If the google id exists and no other actions are being performed, update the google brew
const updated = await GoogleActions.updateGoogleBrew(api.excludeGoogleProps(brew))
.catch((err)=>{
console.error(err);
res.status(err?.response?.status || 500).send(err);
});
if(!updated) return;
}
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;
if(req.account) {
brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
brew.invitedAuthors = _.uniq(_.filter(brew.invitedAuthors, (a)=>req.account.username !== a));
}
// define a function to catch our save errors
const saveError = (err)=>{
console.error(err);
res.status(err.status || 500).send(err.message || 'Unable to save brew to Homebrewery database');
};
let saved;
if(!brew._id) {
// if the brew does not have a stub id, create and save it, then write the new value back to the brew.
saved = await new HomebrewModel(brew).save().catch(saveError);
} else {
// if the brew does have a stub id, update it using the stub id as the key.
brew = _.assign(await HomebrewModel.findOne({ _id: brew._id }), brew);
saved = await brew.save()
.catch(saveError);
}
if(!saved) return;
// Call and wait for afterSave to complete
const after = await afterSave();
if(!after) return;
res.status(200).send(saved);
},
deleteGoogleBrew : async (account, id, editId, res)=>{
const auth = await GoogleActions.authCheck(account, res);
await GoogleActions.deleteGoogleBrew(auth, id, editId);
return true;
},
deleteBrew : async (req, res, next)=>{
// Delete an orphaned stub if its Google brew doesn't exist
try {
await api.getBrew('edit')(req, res, ()=>{});
} catch (err) {
const { id, googleId } = api.getId(req);
console.warn(`No google brew found for id ${googleId}, the stub with id ${id} will be deleted.`);
await HomebrewModel.deleteOne({ editId: id });
return next();
}
let brew = req.brew;
const { googleId, editId } = brew;
const account = req.account;
const isOwner = account && (brew.authors.length === 0 || brew.authors[0] === account.username);
// If the user is the owner and the file is saved to google, mark the google brew for deletion
const shouldDeleteGoogleBrew = googleId && isOwner;
if(brew._id) {
brew = _.assign(await HomebrewModel.findOne({ _id: brew._id }), brew);
if(account) {
// Remove current user as author
brew.authors = _.pull(brew.authors, account.username);
}
if(brew.authors.length === 0) {
// Delete brew if there are no authors left
await brew.remove()
.catch((err)=>{
console.error(err);
throw { status: 500, message: 'Error while removing' };
});
} else {
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;
}
// Otherwise, save the brew with updated author list
await brew.save()
.catch((err)=>{
throw { status: 500, message: err };
});
}
}
if(shouldDeleteGoogleBrew) {
const deleted = await api.deleteGoogleBrew(account, googleId, editId, res)
.catch((err)=>{
console.error(err);
res.status(500).send(err);
});
if(!deleted) return;
}
res.status(204).send();
}
res.status(204).send();
};
router.post('/api', asyncHandler(newBrew));
router.put('/api/:id', asyncHandler(getBrew('edit')), asyncHandler(updateBrew));
router.put('/api/update/:id', asyncHandler(getBrew('edit')), asyncHandler(updateBrew));
router.delete('/api/:id', asyncHandler(deleteBrew));
router.get('/api/remove/:id', asyncHandler(deleteBrew));
router.use('/api', require('./middleware/check-client-version.js'));
router.post('/api', asyncHandler(api.newBrew));
router.put('/api/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
router.put('/api/update/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
router.delete('/api/:id', asyncHandler(api.deleteBrew));
router.get('/api/remove/:id', asyncHandler(api.deleteBrew));
module.exports = {
homebrewApi : router,
getBrew
};
module.exports = api;

748
server/homebrew.api.spec.js Normal file
View File

@@ -0,0 +1,748 @@
/* eslint-disable max-lines */
describe('Tests for api', ()=>{
let api;
let google;
let model;
let hbBrew;
let googleBrew;
let res;
let modelBrew;
let saveFunc;
let removeFunc;
let saved;
beforeEach(()=>{
saved = undefined;
saveFunc = jest.fn(async function() {
saved = { ...this, _id: '1' };
return saved;
});
removeFunc = jest.fn(async function() {});
modelBrew = (brew)=>({
...brew,
save : saveFunc,
remove : removeFunc,
toObject : function() {
delete this.save;
delete this.toObject;
delete this.remove;
return this;
}
});
google = require('./googleActions.js');
model = require('./homebrew.model.js').model;
jest.mock('./googleActions.js');
google.authCheck = jest.fn(()=>'client');
google.newGoogleBrew = jest.fn(()=>'id');
google.deleteGoogleBrew = jest.fn(()=>true);
jest.mock('./homebrew.model.js');
model.mockImplementation((brew)=>modelBrew(brew));
res = {
status : jest.fn(()=>res),
send : jest.fn(()=>{})
};
api = require('./homebrew.api');
hbBrew = {
text : `brew text`,
style : 'hello yes i am css',
title : 'some title',
description : 'this is a description',
tags : ['something', 'fun'],
systems : ['D&D 5e'],
renderer : 'v3',
theme : 'phb',
published : true,
authors : ['1', '2'],
owner : '1',
thumbnail : '',
_id : 'mongoid',
editId : 'abcdefg',
shareId : 'hijklmnop',
views : 1,
lastViewed : new Date(),
version : 1,
pageCount : 1,
textBin : ''
};
googleBrew = {
...hbBrew,
googleId : '12345'
};
});
afterEach(()=>{
jest.restoreAllMocks();
});
describe('getId', ()=>{
it('should return only id if google id is not present', ()=>{
const { id, googleId } = api.getId({
params : {
id : 'abcdefgh'
}
});
expect(id).toEqual('abcdefgh');
expect(googleId).toBeUndefined();
});
it('should return id and google id from request body', ()=>{
const { id, googleId } = api.getId({
params : {
id : 'abcdefgh'
},
body : {
googleId : '12345'
}
});
expect(id).toEqual('abcdefgh');
expect(googleId).toEqual('12345');
});
it('should return id and google id from params', ()=>{
const { id, googleId } = api.getId({
params : {
id : '123456789012abcdefghijkl'
}
});
expect(id).toEqual('abcdefghijkl');
expect(googleId).toEqual('123456789012');
});
});
describe('getBrew', ()=>{
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
const notFoundError = 'Brew not found in Homebrewery database or Google Drive';
it('returns middleware', ()=>{
const getFn = api.getBrew('share');
expect(getFn).toBeInstanceOf(Function);
});
it('should fetch from mongoose', async ()=>{
const testBrew = { title: 'test brew', authors: [] };
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise(testBrew));
const fn = api.getBrew('share', true);
const req = { brew: {} };
const next = jest.fn();
await fn(req, null, next);
expect(req.brew).toEqual(testBrew);
expect(next).toHaveBeenCalled();
expect(api.getId).toHaveBeenCalledWith(req);
expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
});
it('should handle mongoose error', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>new Promise((_, rej)=>rej('Unable to find brew')));
const fn = api.getBrew('share', false);
const req = { brew: {} };
const next = jest.fn();
let err;
try {
await fn(req, null, next);
} catch (e) {
err = e;
}
expect(err).toEqual(notFoundError);
expect(req.brew).toEqual({});
expect(next).not.toHaveBeenCalled();
expect(api.getId).toHaveBeenCalledWith(req);
expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
});
it('changes tags from string to array', async ()=>{
const testBrew = { title: 'test brew', authors: [], tags: '' };
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise(testBrew));
const fn = api.getBrew('share', true);
const req = { brew: {} };
const next = jest.fn();
await fn(req, null, next);
expect(req.brew.tags).toEqual([]);
expect(next).toHaveBeenCalled();
});
it('throws if invalid author', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: ['a'] }));
const fn = api.getBrew('edit', true);
const req = { brew: {} };
let err;
try {
await fn(req, null, null);
} catch (e) {
err = e;
}
expect(err).toEqual(`The current logged in user does not have editor access to this brew.
If you believe you should have access to this brew, ask the file owner to invite you as an author by opening the brew, viewing the Properties tab, and adding your username to the "invited authors" list. You can then try to access this document again.`);
});
it('does not throw if no authors', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: [] }));
const fn = api.getBrew('edit', true);
const req = { brew: {} };
const next = jest.fn();
await fn(req, null, next);
expect(next).toHaveBeenCalled();
expect(req.brew.title).toEqual('test brew');
expect(req.brew.authors).toEqual([]);
});
it('does not throw if valid author', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: ['a'] }));
const fn = api.getBrew('edit', true);
const req = { brew: {}, account: { username: 'a' } };
const next = jest.fn();
await fn(req, null, next);
expect(next).toHaveBeenCalled();
expect(req.brew.title).toEqual('test brew');
expect(req.brew.authors).toEqual(['a']);
});
it('fetches google brew if needed', async()=>{
const stubBrew = { title: 'test brew', authors: ['a'] };
const googleBrew = { title: 'test google brew', text: 'brew text' };
api.getId = jest.fn(()=>({ id: '1', googleId: '2' }));
model.get = jest.fn(()=>toBrewPromise(stubBrew));
google.getGoogleBrew = jest.fn(()=>new Promise((res)=>res(googleBrew)));
const fn = api.getBrew('share', false);
const req = { brew: {} };
const next = jest.fn();
await fn(req, null, next);
expect(req.brew).toEqual({
title : 'test google brew',
authors : ['a'],
text : 'brew text',
stubbed : true,
description : '',
editId : undefined,
pageCount : 1,
published : false,
renderer : 'legacy',
shareId : undefined,
systems : [],
tags : [],
theme : '5ePHB',
thumbnail : '',
textBin : undefined,
version : undefined,
createdAt : undefined,
gDrive : false,
style : undefined,
trashed : false,
updatedAt : undefined
});
expect(next).toHaveBeenCalled();
expect(api.getId).toHaveBeenCalledWith(req);
expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
expect(google.getGoogleBrew).toHaveBeenCalledWith('2', '1', 'share');
});
});
describe('mergeBrewText', ()=>{
it('should set metadata and no style if it is not present', ()=>{
const result = api.mergeBrewText({
text : `brew`,
title : 'some title',
description : 'this is a description',
tags : ['something', 'fun'],
systems : ['D&D 5e'],
renderer : 'v3',
theme : 'phb',
googleId : '12345'
});
expect(result).toEqual(`\`\`\`metadata
title: some title
description: this is a description
tags:
- something
- fun
systems:
- D&D 5e
renderer: v3
theme: phb
\`\`\`
brew`);
});
it('should set metadata and style', ()=>{
const result = api.mergeBrewText({
text : `brew`,
style : 'hello yes i am css',
title : 'some title',
description : 'this is a description',
tags : ['something', 'fun'],
systems : ['D&D 5e'],
renderer : 'v3',
theme : 'phb',
googleId : '12345'
});
expect(result).toEqual(`\`\`\`metadata
title: some title
description: this is a description
tags:
- something
- fun
systems:
- D&D 5e
renderer: v3
theme: phb
\`\`\`
\`\`\`css
hello yes i am css
\`\`\`
brew`);
});
});
describe('exclusion methods', ()=>{
it('excludePropsFromUpdate removes the correct keys', ()=>{
const sent = Object.assign({}, googleBrew);
const result = api.excludePropsFromUpdate(sent);
expect(sent).toEqual(googleBrew);
expect(result._id).toBeUndefined();
expect(result.views).toBeUndefined();
expect(result.lastViewed).toBeUndefined();
});
it('excludeGoogleProps removes the correct keys', ()=>{
const sent = Object.assign({}, googleBrew);
const result = api.excludeGoogleProps(sent);
expect(sent).toEqual(googleBrew);
expect(result.tags).toBeUndefined();
expect(result.systems).toBeUndefined();
expect(result.published).toBeUndefined();
expect(result.authors).toBeUndefined();
expect(result.owner).toBeUndefined();
expect(result.views).toBeUndefined();
expect(result.thumbnail).toBeUndefined();
expect(result.version).toBeUndefined();
});
it('excludeStubProps removes the correct keys from the original object', ()=>{
const sent = Object.assign({}, googleBrew);
const result = api.excludeStubProps(sent);
expect(sent).not.toEqual(googleBrew);
expect(result.text).toBeUndefined();
expect(result.textBin).toBeUndefined();
expect(result.renderer).toBeUndefined();
expect(result.pageCount).toBeUndefined();
});
});
describe('beforeNewSave', ()=>{
it('sets the title if none', ()=>{
const brew = {
...hbBrew,
title : undefined
};
api.beforeNewSave({}, brew);
expect(brew.title).toEqual('brew text');
});
it('does not override the title if present', ()=>{
const brew = {
...hbBrew,
title : 'test'
};
api.beforeNewSave({}, brew);
expect(brew.title).toEqual('test');
});
it('does not set authors if account missing username', ()=>{
api.beforeNewSave({}, hbBrew);
expect(hbBrew.authors).toEqual([]);
});
it('sets authors if account has username', ()=>{
api.beforeNewSave({ username: 'hi' }, hbBrew);
expect(hbBrew.authors).toEqual(['hi']);
});
it('merges brew text', ()=>{
api.mergeBrewText = jest.fn(()=>'merged');
api.beforeNewSave({}, hbBrew);
expect(api.mergeBrewText).toHaveBeenCalled();
expect(hbBrew.text).toEqual('merged');
});
});
describe('newGoogleBrew', ()=>{
it('should call the correct methods', ()=>{
api.excludeGoogleProps = jest.fn(()=>'newBrew');
const acct = { username: 'test' };
const brew = { title: 'test title' };
api.newGoogleBrew(acct, brew, res);
expect(google.authCheck).toHaveBeenCalledWith(acct, res);
expect(api.excludeGoogleProps).toHaveBeenCalledWith(brew);
expect(google.newGoogleBrew).toHaveBeenCalledWith('client', 'newBrew');
});
});
describe('newBrew', ()=>{
it('should set up a default brew via Homebrew model', async ()=>{
await api.newBrew({ body: { text: 'asdf' }, query: {}, account: { username: 'test user' } }, res);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({
_id : '1',
authors : ['test user'],
createdAt : undefined,
description : '',
editId : expect.any(String),
gDrive : false,
pageCount : 1,
published : false,
renderer : 'V3',
shareId : expect.any(String),
style : undefined,
systems : [],
tags : [],
text : undefined,
textBin : expect.objectContaining({}),
theme : '5ePHB',
thumbnail : '',
title : 'asdf',
trashed : false,
updatedAt : undefined
});
});
it('should remove edit/share/google ids', async ()=>{
await api.newBrew({ body: { editId: '1234', shareId: '1234', googleId: '1234', text: 'asdf', title: '' }, query: {} }, res);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalled();
const sent = res.send.mock.calls[0][0];
expect(sent.editId).not.toEqual('1234');
expect(sent.shareId).not.toEqual('1234');
expect(sent.googleId).toBeUndefined();
});
it('should handle mongo error', async ()=>{
saveFunc = jest.fn(async function() {
throw 'err';
});
model.mockImplementation((brew)=>modelBrew(brew));
let err;
try {
await api.newBrew({ body: { editId: '1234', shareId: '1234', googleId: '1234', text: 'asdf', title: '' }, query: {} }, res);
} catch (e) {
err = e;
}
expect(res.send).not.toHaveBeenCalled();
expect(err).not.toBeUndefined();
});
it('should save to google if requested', async()=>{
await api.newBrew({ body: { text: 'asdf', title: '' }, query: { saveToGoogle: true }, account: { username: 'test user' } }, res);
expect(google.newGoogleBrew).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({
_id : '1',
authors : ['test user'],
createdAt : undefined,
description : '',
editId : expect.any(String),
gDrive : false,
pageCount : undefined,
published : false,
renderer : undefined,
shareId : expect.any(String),
googleId : expect.any(String),
style : undefined,
systems : [],
tags : [],
text : undefined,
textBin : undefined,
theme : '5ePHB',
thumbnail : '',
title : 'asdf',
trashed : false,
updatedAt : undefined
});
});
it('should handle google error', async()=>{
google.newGoogleBrew = jest.fn(()=>{
throw 'err';
});
await api.newBrew({ body: { text: 'asdf', title: '' }, query: { saveToGoogle: true }, account: { username: 'test user' } }, res);
expect(res.status).toHaveBeenCalledWith(500);
expect(res.send).toHaveBeenCalledWith('err');
});
});
describe('deleteGoogleBrew', ()=>{
it('should check auth and delete brew', async ()=>{
const result = await api.deleteGoogleBrew({ username: 'test user' }, 'id', 'editId', res);
expect(result).toBe(true);
expect(google.authCheck).toHaveBeenCalledWith({ username: 'test user' }, expect.objectContaining({}));
expect(google.deleteGoogleBrew).toHaveBeenCalledWith('client', 'id', 'editId');
});
});
describe('deleteBrew', ()=>{
it('should handle case where fetching the brew returns an error', async ()=>{
api.getBrew = jest.fn(()=>async ()=>{ throw 'err'; });
api.getId = jest.fn(()=>({ id: '1', googleId: '2' }));
model.deleteOne = jest.fn(async ()=>{});
const next = jest.fn(()=>{});
await api.deleteBrew(null, null, next);
expect(next).toHaveBeenCalled();
expect(model.deleteOne).toHaveBeenCalledWith({ editId: '1' });
});
it('should delete if no authors', async ()=>{
const brew = {
...hbBrew,
authors : []
};
api.getBrew = jest.fn(()=>async (req)=>{
req.brew = brew;
});
model.findOne = jest.fn(async ()=>modelBrew(brew));
const req = {};
await api.deleteBrew(req, res);
expect(api.getBrew).toHaveBeenCalled();
expect(model.findOne).toHaveBeenCalled();
expect(removeFunc).toHaveBeenCalled();
});
it('should throw on delete error', async ()=>{
const brew = {
...hbBrew,
authors : []
};
api.getBrew = jest.fn(()=>async (req)=>{
req.brew = brew;
});
model.findOne = jest.fn(async ()=>modelBrew(brew));
removeFunc = jest.fn(async ()=>{ throw 'err'; });
const req = {};
let err;
try {
await api.deleteBrew(req, res);
} catch (e) {
err = e;
}
expect(err).not.toBeUndefined();
expect(api.getBrew).toHaveBeenCalled();
expect(model.findOne).toHaveBeenCalled();
expect(removeFunc).toHaveBeenCalled();
});
it('should delete when one author', async ()=>{
const brew = {
...hbBrew,
authors : ['test']
};
api.getBrew = jest.fn(()=>async (req)=>{
req.brew = brew;
});
model.findOne = jest.fn(async ()=>modelBrew(brew));
const req = { account: { username: 'test' } };
await api.deleteBrew(req, res);
expect(api.getBrew).toHaveBeenCalled();
expect(model.findOne).toHaveBeenCalled();
expect(removeFunc).toHaveBeenCalled();
});
it('should remove one author when multiple present', async ()=>{
const brew = {
...hbBrew,
authors : ['test', 'test2']
};
api.getBrew = jest.fn(()=>async (req)=>{
req.brew = brew;
});
model.findOne = jest.fn(async ()=>modelBrew(brew));
const req = { account: { username: 'test' } };
await api.deleteBrew(req, res);
expect(api.getBrew).toHaveBeenCalled();
expect(model.findOne).toHaveBeenCalled();
expect(removeFunc).not.toHaveBeenCalled();
expect(saveFunc).toHaveBeenCalled();
expect(saved.authors).toEqual(['test2']);
});
it('should handle save error', async ()=>{
const brew = {
...hbBrew,
authors : ['test', 'test2']
};
api.getBrew = jest.fn(()=>async (req)=>{
req.brew = brew;
});
model.findOne = jest.fn(async ()=>modelBrew(brew));
saveFunc = jest.fn(async ()=>{ throw 'err'; });
const req = { account: { username: 'test' } };
let err;
try {
await api.deleteBrew(req, res);
} catch (e) {
err = e;
}
expect(err).not.toBeUndefined();
expect(api.getBrew).toHaveBeenCalled();
expect(model.findOne).toHaveBeenCalled();
expect(removeFunc).not.toHaveBeenCalled();
expect(saveFunc).toHaveBeenCalled();
});
it('should delete google brew', async ()=>{
const brew = {
...googleBrew,
authors : ['test']
};
api.getBrew = jest.fn(()=>async (req)=>{
req.brew = brew;
});
model.findOne = jest.fn(async ()=>modelBrew(brew));
api.deleteGoogleBrew = jest.fn(async ()=>true);
const req = { account: { username: 'test' } };
await api.deleteBrew(req, res);
expect(api.getBrew).toHaveBeenCalled();
expect(model.findOne).toHaveBeenCalled();
expect(removeFunc).toHaveBeenCalled();
expect(api.deleteGoogleBrew).toHaveBeenCalled();
});
it('should handle google brew delete error', async ()=>{
const brew = {
...googleBrew,
authors : ['test']
};
api.getBrew = jest.fn(()=>async (req)=>{
req.brew = brew;
});
model.findOne = jest.fn(async ()=>modelBrew(brew));
api.deleteGoogleBrew = jest.fn(async ()=>{
throw 'err';
});
const req = { account: { username: 'test' } };
await api.deleteBrew(req, res);
expect(api.getBrew).toHaveBeenCalled();
expect(model.findOne).toHaveBeenCalled();
expect(removeFunc).toHaveBeenCalled();
expect(api.deleteGoogleBrew).toHaveBeenCalled();
});
it('should delete google brew and retain stub when multiple authors and owner request deletion', async ()=>{
const brew = {
...googleBrew,
authors : ['test', 'test2']
};
api.getBrew = jest.fn(()=>async (req)=>{
req.brew = brew;
});
model.findOne = jest.fn(async ()=>modelBrew(brew));
api.deleteGoogleBrew = jest.fn(async ()=>true);
const req = { account: { username: 'test' } };
await api.deleteBrew(req, res);
expect(api.getBrew).toHaveBeenCalled();
expect(model.findOne).toHaveBeenCalled();
expect(removeFunc).not.toHaveBeenCalled();
expect(api.deleteGoogleBrew).toHaveBeenCalled();
expect(saveFunc).toHaveBeenCalled();
expect(saved.authors).toEqual(['test2']);
expect(saved.googleId).toEqual(undefined);
expect(saved.text).toEqual(undefined);
expect(saved.textBin).not.toEqual(undefined);
});
it('should retain google brew and update stub when multiple authors and extra author requests deletion', async ()=>{
const brew = {
...googleBrew,
authors : ['test', 'test2']
};
api.getBrew = jest.fn(()=>async (req)=>{
req.brew = brew;
});
model.findOne = jest.fn(async ()=>modelBrew(brew));
api.deleteGoogleBrew = jest.fn(async ()=>true);
const req = { account: { username: 'test2' } };
await api.deleteBrew(req, res);
expect(api.getBrew).toHaveBeenCalled();
expect(model.findOne).toHaveBeenCalled();
expect(removeFunc).not.toHaveBeenCalled();
expect(api.deleteGoogleBrew).not.toHaveBeenCalled();
expect(saveFunc).toHaveBeenCalled();
expect(saved.authors).toEqual(['test']);
expect(saved.googleId).toEqual(brew.googleId);
});
});
});

View File

@@ -12,13 +12,14 @@ const HomebrewSchema = mongoose.Schema({
textBin : { type: Buffer },
pageCount : { type: Number, default: 1 },
description : { type: String, default: '' },
tags : [String],
systems : [String],
renderer : { type: String, default: '' },
authors : [String],
published : { type: Boolean, default: false },
thumbnail : { type: String, default: '' },
description : { type: String, default: '' },
tags : [String],
systems : [String],
renderer : { type: String, default: '' },
authors : [String],
invitedAuthors : [String],
published : { type: Boolean, default: false },
thumbnail : { type: String, default: '' },
createdAt : { type: Date, default: Date.now },
updatedAt : { type: Date, default: Date.now },
@@ -46,8 +47,6 @@ HomebrewSchema.statics.get = function(query, fields=null){
unzipped = zlib.inflateRawSync(brews[0].textBin);
brews[0].text = unzipped.toString();
}
if(!brews[0].renderer)
brews[0].renderer = 'legacy';
return resolve(brews[0]);
});
});

View File

@@ -0,0 +1,12 @@
module.exports = (req, res, next)=>{
const userVersion = req.get('Homebrewery-Version');
const version = require('../../package.json').version;
if(userVersion != version) {
return res.status(412).send({
message : `Client version ${userVersion} is out of date. Please save your changes elsewhere and refresh to pick up client version ${version}.`
});
}
next();
};