From a081f70e4305223a7a3ef70b300ce1ad6ea5cf89 Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Sun, 10 Jun 2018 16:10:31 +0200 Subject: [PATCH 01/16] Remove JavaScript --- .travis.yml | 12 - npm-shrinkwrap.json | 1748 ----------------- package.json | 20 - pom.xml | 104 +- .../components/comment-form/comment-form.html | 12 - .../components/comment-form/index.js | 89 - .../components/comment/comment.html | 50 - .../javascript/components/comment/index.js | 118 -- src/main/javascript/counts.js | 33 - src/main/javascript/list.html | 13 - src/main/javascript/list.js | 86 - src/main/javascript/platon.css | 87 - src/main/javascript/platon.js | 23 - .../javascript/services/comment-service.js | 117 -- src/main/javascript/services/text-service.js | 37 - .../javascript/services/user-info-service.js | 41 - src/main/javascript/utils/events.js | 24 - src/main/javascript/utils/find-by-id.js | 29 - .../utils/find-thread-url-elements.js | 42 - .../utils/remove-comment-from-list.js | 36 - .../utils/update-comment-in-list.js | 28 - .../de/vorb/platon/ui/CommentCountIT.java | 168 -- .../java/de/vorb/platon/ui/CommentListIT.java | 165 -- .../platon/ui/pages/CommentCountPage.java | 99 - .../vorb/platon/ui/pages/CommentListPage.java | 126 -- webpack.config.js | 36 - 26 files changed, 6 insertions(+), 3337 deletions(-) delete mode 100644 npm-shrinkwrap.json delete mode 100644 package.json delete mode 100644 src/main/javascript/components/comment-form/comment-form.html delete mode 100644 src/main/javascript/components/comment-form/index.js delete mode 100644 src/main/javascript/components/comment/comment.html delete mode 100644 src/main/javascript/components/comment/index.js delete mode 100644 src/main/javascript/counts.js delete mode 100644 src/main/javascript/list.html delete mode 100644 src/main/javascript/list.js delete mode 100644 src/main/javascript/platon.css delete mode 100644 src/main/javascript/platon.js delete mode 100644 src/main/javascript/services/comment-service.js delete mode 100644 src/main/javascript/services/text-service.js delete mode 100644 src/main/javascript/services/user-info-service.js delete mode 100644 src/main/javascript/utils/events.js delete mode 100644 src/main/javascript/utils/find-by-id.js delete mode 100644 src/main/javascript/utils/find-thread-url-elements.js delete mode 100644 src/main/javascript/utils/remove-comment-from-list.js delete mode 100644 src/main/javascript/utils/update-comment-in-list.js delete mode 100644 src/test/java/de/vorb/platon/ui/CommentCountIT.java delete mode 100644 src/test/java/de/vorb/platon/ui/CommentListIT.java delete mode 100644 src/test/java/de/vorb/platon/ui/pages/CommentCountPage.java delete mode 100644 src/test/java/de/vorb/platon/ui/pages/CommentListPage.java delete mode 100644 webpack.config.js diff --git a/.travis.yml b/.travis.yml index f2a2a50..b2098c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,18 +9,6 @@ addons: cache: directories: - $HOME/.m2 - - node - - node_modules - -before_install: - - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x24 +extension RANDR" - - export DISPLAY=:99 - - "wget https://github.com/mozilla/geckodriver/releases/download/v0.19.0/geckodriver-v0.19.0-linux64.tar.gz" - - mkdir geckodriver - - tar -xzf geckodriver-v0.19.0-linux64.tar.gz -C geckodriver - - "wget -q https://selenium-release.storage.googleapis.com/3.6/selenium-server-standalone-3.6.0.jar" - - "java -Dwebdriver.gecko.driver=$PWD/geckodriver/geckodriver -jar selenium-server-standalone-3.6.0.jar &" - - sleep 5 install: /bin/true diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json deleted file mode 100644 index 1ae0ac2..0000000 --- a/npm-shrinkwrap.json +++ /dev/null @@ -1,1748 +0,0 @@ -{ - "dependencies": { - "abab": { - "version": "1.0.3", - "from": "abab@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.3.tgz" - }, - "acorn": { - "version": "2.7.0", - "from": "acorn@>=2.4.0 <3.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz" - }, - "acorn-globals": { - "version": "1.0.9", - "from": "acorn-globals@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz" - }, - "align-text": { - "version": "0.1.4", - "from": "align-text@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz" - }, - "alphanum-sort": { - "version": "1.0.2", - "from": "alphanum-sort@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz" - }, - "amdefine": { - "version": "1.0.0", - "from": "amdefine@>=0.0.4", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz" - }, - "ansi-regex": { - "version": "2.0.0", - "from": "ansi-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" - }, - "ansi-styles": { - "version": "2.2.1", - "from": "ansi-styles@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" - }, - "anymatch": { - "version": "1.3.0", - "from": "anymatch@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz" - }, - "argparse": { - "version": "1.0.9", - "from": "argparse@>=1.0.7 <2.0.0", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz" - }, - "arr-diff": { - "version": "2.0.0", - "from": "arr-diff@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz" - }, - "arr-flatten": { - "version": "1.0.1", - "from": "arr-flatten@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.1.tgz" - }, - "array-equal": { - "version": "1.0.0", - "from": "array-equal@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz" - }, - "array-unique": { - "version": "0.2.1", - "from": "array-unique@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz" - }, - "arrify": { - "version": "1.0.1", - "from": "arrify@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" - }, - "asn1": { - "version": "0.2.3", - "from": "asn1@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" - }, - "assert": { - "version": "1.4.1", - "from": "assert@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz" - }, - "assert-plus": { - "version": "0.2.0", - "from": "assert-plus@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" - }, - "async": { - "version": "1.5.2", - "from": "async@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" - }, - "async-each": { - "version": "1.0.1", - "from": "async-each@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz" - }, - "asynckit": { - "version": "0.4.0", - "from": "asynckit@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" - }, - "autoprefixer": { - "version": "6.5.1", - "from": "autoprefixer@>=6.3.1 <7.0.0", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.5.1.tgz" - }, - "aws-sign2": { - "version": "0.6.0", - "from": "aws-sign2@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" - }, - "aws4": { - "version": "1.5.0", - "from": "aws4@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.5.0.tgz" - }, - "babel-code-frame": { - "version": "6.16.0", - "from": "babel-code-frame@>=6.11.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.16.0.tgz" - }, - "balanced-match": { - "version": "0.4.2", - "from": "balanced-match@>=0.4.2 <0.5.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" - }, - "Base64": { - "version": "0.2.1", - "from": "Base64@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz" - }, - "base64-js": { - "version": "1.2.0", - "from": "base64-js@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz" - }, - "bcrypt-pbkdf": { - "version": "1.0.0", - "from": "bcrypt-pbkdf@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz" - }, - "big.js": { - "version": "3.1.3", - "from": "big.js@>=3.1.3 <4.0.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz" - }, - "binary-extensions": { - "version": "1.7.0", - "from": "binary-extensions@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.7.0.tgz" - }, - "block-elements": { - "version": "1.1.0", - "from": "block-elements@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/block-elements/-/block-elements-1.1.0.tgz" - }, - "boom": { - "version": "2.10.1", - "from": "boom@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" - }, - "brace-expansion": { - "version": "1.1.6", - "from": "brace-expansion@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" - }, - "braces": { - "version": "1.8.5", - "from": "braces@>=1.8.2 <2.0.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz" - }, - "browserify-zlib": { - "version": "0.1.4", - "from": "browserify-zlib@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz" - }, - "browserslist": { - "version": "1.4.0", - "from": "browserslist@>=1.4.0 <1.5.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.4.0.tgz" - }, - "buffer": { - "version": "4.9.1", - "from": "buffer@>=4.9.0 <5.0.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz" - }, - "buffer-shims": { - "version": "1.0.0", - "from": "buffer-shims@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" - }, - "camelcase": { - "version": "1.2.1", - "from": "camelcase@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz" - }, - "caniuse-db": { - "version": "1.0.30000572", - "from": "caniuse-db@>=1.0.30000554 <2.0.0", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000572.tgz" - }, - "caseless": { - "version": "0.11.0", - "from": "caseless@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" - }, - "center-align": { - "version": "0.1.3", - "from": "center-align@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz" - }, - "chalk": { - "version": "1.1.3", - "from": "chalk@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" - }, - "chokidar": { - "version": "1.6.1", - "from": "chokidar@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.6.1.tgz" - }, - "clap": { - "version": "1.1.1", - "from": "clap@>=1.0.9 <2.0.0", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.1.1.tgz" - }, - "cliui": { - "version": "2.1.0", - "from": "cliui@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "from": "wordwrap@0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" - } - } - }, - "clone": { - "version": "1.0.2", - "from": "clone@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz" - }, - "coa": { - "version": "1.0.1", - "from": "coa@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.1.tgz" - }, - "collapse-whitespace": { - "version": "1.1.2", - "from": "collapse-whitespace@1.1.2", - "resolved": "https://registry.npmjs.org/collapse-whitespace/-/collapse-whitespace-1.1.2.tgz" - }, - "color": { - "version": "0.11.3", - "from": "color@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.3.tgz" - }, - "color-convert": { - "version": "1.6.0", - "from": "color-convert@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.6.0.tgz" - }, - "color-name": { - "version": "1.1.1", - "from": "color-name@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz" - }, - "color-string": { - "version": "0.3.0", - "from": "color-string@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz" - }, - "colormin": { - "version": "1.1.2", - "from": "colormin@>=1.0.5 <2.0.0", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz" - }, - "colors": { - "version": "1.1.2", - "from": "colors@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz" - }, - "combined-stream": { - "version": "1.0.5", - "from": "combined-stream@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" - }, - "commander": { - "version": "2.9.0", - "from": "commander@>=2.9.0 <3.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" - }, - "concat-map": { - "version": "0.0.1", - "from": "concat-map@0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - }, - "console-browserify": { - "version": "1.1.0", - "from": "console-browserify@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz" - }, - "constants-browserify": { - "version": "0.0.1", - "from": "constants-browserify@0.0.1", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-0.0.1.tgz" - }, - "content-type-parser": { - "version": "1.0.1", - "from": "content-type-parser@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.1.tgz" - }, - "core-util-is": { - "version": "1.0.2", - "from": "core-util-is@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - }, - "cryptiles": { - "version": "2.0.5", - "from": "cryptiles@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" - }, - "crypto-browserify": { - "version": "3.2.8", - "from": "crypto-browserify@>=3.2.6 <3.3.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.2.8.tgz" - }, - "css-color-names": { - "version": "0.0.4", - "from": "css-color-names@0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz" - }, - "css-loader": { - "version": "0.25.0", - "from": "css-loader@0.25.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.25.0.tgz" - }, - "css-selector-tokenizer": { - "version": "0.6.0", - "from": "css-selector-tokenizer@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.6.0.tgz" - }, - "cssesc": { - "version": "0.1.0", - "from": "cssesc@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz" - }, - "cssnano": { - "version": "3.8.0", - "from": "cssnano@>=2.6.1 <4.0.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.8.0.tgz" - }, - "csso": { - "version": "2.2.1", - "from": "csso@>=2.2.1 <2.3.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-2.2.1.tgz", - "dependencies": { - "source-map": { - "version": "0.5.6", - "from": "source-map@>=0.5.3 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" - } - } - }, - "cssom": { - "version": "0.3.1", - "from": "cssom@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.1.tgz" - }, - "cssstyle": { - "version": "0.2.37", - "from": "cssstyle@>=0.2.36 <0.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz" - }, - "dashdash": { - "version": "1.14.0", - "from": "dashdash@>=1.12.0 <2.0.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "date-now": { - "version": "0.1.4", - "from": "date-now@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz" - }, - "de-indent": { - "version": "1.0.2", - "from": "de-indent@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz" - }, - "decamelize": { - "version": "1.2.0", - "from": "decamelize@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" - }, - "deep-is": { - "version": "0.1.3", - "from": "deep-is@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz" - }, - "defined": { - "version": "1.0.0", - "from": "defined@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz" - }, - "delayed-stream": { - "version": "1.0.0", - "from": "delayed-stream@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - }, - "domain-browser": { - "version": "1.1.7", - "from": "domain-browser@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz" - }, - "ecc-jsbn": { - "version": "0.1.1", - "from": "ecc-jsbn@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" - }, - "emojis-list": { - "version": "2.1.0", - "from": "emojis-list@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz" - }, - "enhanced-resolve": { - "version": "0.9.1", - "from": "enhanced-resolve@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", - "dependencies": { - "memory-fs": { - "version": "0.2.0", - "from": "memory-fs@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz" - } - } - }, - "errno": { - "version": "0.1.4", - "from": "errno@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz" - }, - "escape-string-regexp": { - "version": "1.0.5", - "from": "escape-string-regexp@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - }, - "escodegen": { - "version": "1.8.1", - "from": "escodegen@>=1.6.1 <2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz" - }, - "esprima": { - "version": "2.7.3", - "from": "esprima@>=2.7.1 <3.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz" - }, - "estraverse": { - "version": "1.9.3", - "from": "estraverse@>=1.9.1 <2.0.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz" - }, - "esutils": { - "version": "2.0.2", - "from": "esutils@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz" - }, - "events": { - "version": "1.1.1", - "from": "events@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz" - }, - "expand-brackets": { - "version": "0.1.5", - "from": "expand-brackets@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz" - }, - "expand-range": { - "version": "1.8.2", - "from": "expand-range@>=1.8.1 <2.0.0", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz" - }, - "extend": { - "version": "3.0.0", - "from": "extend@>=3.0.0 <3.1.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" - }, - "extglob": { - "version": "0.3.2", - "from": "extglob@>=0.3.1 <0.4.0", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz" - }, - "extsprintf": { - "version": "1.0.2", - "from": "extsprintf@1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" - }, - "fast-levenshtein": { - "version": "2.0.5", - "from": "fast-levenshtein@>=2.0.4 <2.1.0", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.5.tgz" - }, - "fastparse": { - "version": "1.1.1", - "from": "fastparse@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz" - }, - "filename-regex": { - "version": "2.0.0", - "from": "filename-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.0.tgz" - }, - "fill-range": { - "version": "2.2.3", - "from": "fill-range@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz" - }, - "flatten": { - "version": "1.0.2", - "from": "flatten@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz" - }, - "for-in": { - "version": "0.1.6", - "from": "for-in@>=0.1.5 <0.2.0", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.6.tgz" - }, - "for-own": { - "version": "0.1.4", - "from": "for-own@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.4.tgz" - }, - "forever-agent": { - "version": "0.6.1", - "from": "forever-agent@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - }, - "form-data": { - "version": "2.1.1", - "from": "form-data@>=2.1.1 <2.2.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.1.tgz" - }, - "function-bind": { - "version": "1.1.0", - "from": "function-bind@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz" - }, - "generate-function": { - "version": "2.0.0", - "from": "generate-function@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" - }, - "generate-object-property": { - "version": "1.2.0", - "from": "generate-object-property@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" - }, - "getpass": { - "version": "0.1.6", - "from": "getpass@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "glob-base": { - "version": "0.3.0", - "from": "glob-base@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz" - }, - "glob-parent": { - "version": "2.0.0", - "from": "glob-parent@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" - }, - "graceful-fs": { - "version": "4.1.9", - "from": "graceful-fs@>=4.1.2 <5.0.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.9.tgz" - }, - "graceful-readlink": { - "version": "1.0.1", - "from": "graceful-readlink@>=1.0.0", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" - }, - "har-validator": { - "version": "2.0.6", - "from": "har-validator@>=2.0.6 <2.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" - }, - "has": { - "version": "1.0.1", - "from": "has@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz" - }, - "has-ansi": { - "version": "2.0.0", - "from": "has-ansi@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" - }, - "has-flag": { - "version": "1.0.0", - "from": "has-flag@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz" - }, - "hawk": { - "version": "3.1.3", - "from": "hawk@>=3.1.3 <3.2.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" - }, - "he": { - "version": "1.1.0", - "from": "he@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.0.tgz" - }, - "hoek": { - "version": "2.16.3", - "from": "hoek@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" - }, - "html-comment-regex": { - "version": "1.1.1", - "from": "html-comment-regex@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz" - }, - "html-encoding-sniffer": { - "version": "1.0.1", - "from": "html-encoding-sniffer@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz" - }, - "http-browserify": { - "version": "1.7.0", - "from": "http-browserify@>=1.3.2 <2.0.0", - "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz" - }, - "http-signature": { - "version": "1.1.1", - "from": "http-signature@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" - }, - "https-browserify": { - "version": "0.0.0", - "from": "https-browserify@0.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.0.tgz" - }, - "iconv-lite": { - "version": "0.4.13", - "from": "iconv-lite@>=0.4.13 <0.5.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz" - }, - "icss-replace-symbols": { - "version": "1.0.2", - "from": "icss-replace-symbols@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz" - }, - "ieee754": { - "version": "1.1.8", - "from": "ieee754@>=1.1.4 <2.0.0", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz" - }, - "indexes-of": { - "version": "1.0.1", - "from": "indexes-of@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz" - }, - "indexof": { - "version": "0.0.1", - "from": "indexof@0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz" - }, - "inherits": { - "version": "2.0.3", - "from": "inherits@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" - }, - "interpret": { - "version": "0.6.6", - "from": "interpret@>=0.6.4 <0.7.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-0.6.6.tgz" - }, - "is-absolute-url": { - "version": "2.0.0", - "from": "is-absolute-url@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.0.0.tgz" - }, - "is-binary-path": { - "version": "1.0.1", - "from": "is-binary-path@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz" - }, - "is-buffer": { - "version": "1.1.4", - "from": "is-buffer@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.4.tgz" - }, - "is-dotfile": { - "version": "1.0.2", - "from": "is-dotfile@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz" - }, - "is-equal-shallow": { - "version": "0.1.3", - "from": "is-equal-shallow@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz" - }, - "is-extendable": { - "version": "0.1.1", - "from": "is-extendable@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" - }, - "is-extglob": { - "version": "1.0.0", - "from": "is-extglob@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" - }, - "is-glob": { - "version": "2.0.1", - "from": "is-glob@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" - }, - "is-my-json-valid": { - "version": "2.15.0", - "from": "is-my-json-valid@>=2.12.4 <3.0.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz" - }, - "is-number": { - "version": "2.1.0", - "from": "is-number@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz" - }, - "is-plain-obj": { - "version": "1.1.0", - "from": "is-plain-obj@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" - }, - "is-posix-bracket": { - "version": "0.1.1", - "from": "is-posix-bracket@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz" - }, - "is-primitive": { - "version": "2.0.0", - "from": "is-primitive@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz" - }, - "is-property": { - "version": "1.0.2", - "from": "is-property@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" - }, - "is-svg": { - "version": "2.0.1", - "from": "is-svg@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.0.1.tgz" - }, - "is-typedarray": { - "version": "1.0.0", - "from": "is-typedarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - }, - "isobject": { - "version": "2.1.0", - "from": "isobject@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz" - }, - "isstream": { - "version": "0.1.2", - "from": "isstream@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" - }, - "jodid25519": { - "version": "1.0.2", - "from": "jodid25519@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" - }, - "js-base64": { - "version": "2.1.9", - "from": "js-base64@>=2.1.9 <3.0.0", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz" - }, - "js-tokens": { - "version": "2.0.0", - "from": "js-tokens@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz" - }, - "js-yaml": { - "version": "3.6.1", - "from": "js-yaml@>=3.6.1 <3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz" - }, - "jsbn": { - "version": "0.1.0", - "from": "jsbn@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" - }, - "jsdom": { - "version": "9.8.3", - "from": "jsdom@>=9.0.0 <10.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.8.3.tgz" - }, - "jsesc": { - "version": "0.5.0", - "from": "jsesc@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" - }, - "json-schema": { - "version": "0.2.3", - "from": "json-schema@0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" - }, - "json-stringify-safe": { - "version": "5.0.1", - "from": "json-stringify-safe@>=5.0.1 <5.1.0", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - }, - "json5": { - "version": "0.5.0", - "from": "json5@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.0.tgz" - }, - "jsonpointer": { - "version": "4.0.0", - "from": "jsonpointer@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.0.tgz" - }, - "jsprim": { - "version": "1.3.1", - "from": "jsprim@>=1.2.2 <2.0.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz" - }, - "kind-of": { - "version": "3.0.4", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.4.tgz" - }, - "lazy-cache": { - "version": "1.0.4", - "from": "lazy-cache@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz" - }, - "levn": { - "version": "0.3.0", - "from": "levn@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" - }, - "loader-utils": { - "version": "0.2.16", - "from": "loader-utils@>=0.2.2 <0.3.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.16.tgz" - }, - "lodash._createcompounder": { - "version": "3.0.0", - "from": "lodash._createcompounder@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash._createcompounder/-/lodash._createcompounder-3.0.0.tgz" - }, - "lodash._root": { - "version": "3.0.1", - "from": "lodash._root@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz" - }, - "lodash.camelcase": { - "version": "3.0.1", - "from": "lodash.camelcase@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-3.0.1.tgz" - }, - "lodash.deburr": { - "version": "3.2.0", - "from": "lodash.deburr@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-3.2.0.tgz" - }, - "lodash.indexof": { - "version": "4.0.5", - "from": "lodash.indexof@>=4.0.5 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.indexof/-/lodash.indexof-4.0.5.tgz" - }, - "lodash.words": { - "version": "3.2.0", - "from": "lodash.words@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash.words/-/lodash.words-3.2.0.tgz" - }, - "longest": { - "version": "1.0.1", - "from": "longest@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz" - }, - "macaddress": { - "version": "0.2.8", - "from": "macaddress@>=0.2.8 <0.3.0", - "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz" - }, - "marked": { - "version": "0.3.6", - "from": "marked@0.3.6", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz" - }, - "math-expression-evaluator": { - "version": "1.2.14", - "from": "math-expression-evaluator@>=1.2.14 <2.0.0", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.14.tgz" - }, - "memory-fs": { - "version": "0.3.0", - "from": "memory-fs@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.3.0.tgz" - }, - "micromatch": { - "version": "2.3.11", - "from": "micromatch@>=2.1.5 <3.0.0", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz" - }, - "mime-db": { - "version": "1.24.0", - "from": "mime-db@>=1.24.0 <1.25.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz" - }, - "mime-types": { - "version": "2.1.12", - "from": "mime-types@>=2.1.7 <2.2.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.12.tgz" - }, - "minimatch": { - "version": "3.0.3", - "from": "minimatch@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz" - }, - "minimist": { - "version": "0.0.8", - "from": "minimist@0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - }, - "mkdirp": { - "version": "0.5.1", - "from": "mkdirp@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" - }, - "node-libs-browser": { - "version": "0.6.0", - "from": "node-libs-browser@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-0.6.0.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.1.14", - "from": "readable-stream@>=1.1.13 <2.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" - } - } - }, - "node-uuid": { - "version": "1.4.7", - "from": "node-uuid@>=1.4.7 <1.5.0", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" - }, - "normalize-path": { - "version": "2.0.1", - "from": "normalize-path@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.0.1.tgz" - }, - "normalize-range": { - "version": "0.1.2", - "from": "normalize-range@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" - }, - "normalize-url": { - "version": "1.7.0", - "from": "normalize-url@>=1.4.0 <2.0.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.7.0.tgz" - }, - "num2fraction": { - "version": "1.2.2", - "from": "num2fraction@>=1.2.2 <2.0.0", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz" - }, - "nwmatcher": { - "version": "1.3.9", - "from": "nwmatcher@>=1.3.7 <2.0.0", - "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.3.9.tgz" - }, - "oauth-sign": { - "version": "0.8.2", - "from": "oauth-sign@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" - }, - "object-assign": { - "version": "4.1.0", - "from": "object-assign@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz" - }, - "object.omit": { - "version": "2.0.1", - "from": "object.omit@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz" - }, - "optimist": { - "version": "0.6.1", - "from": "optimist@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "from": "wordwrap@>=0.0.2 <0.1.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz" - } - } - }, - "optionator": { - "version": "0.8.2", - "from": "optionator@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz" - }, - "os-browserify": { - "version": "0.1.2", - "from": "os-browserify@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz" - }, - "pako": { - "version": "0.2.9", - "from": "pako@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz" - }, - "parse-glob": { - "version": "3.0.4", - "from": "parse-glob@>=3.0.4 <4.0.0", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz" - }, - "parse5": { - "version": "1.5.1", - "from": "parse5@>=1.5.1 <2.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz" - }, - "path-browserify": { - "version": "0.0.0", - "from": "path-browserify@0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz" - }, - "path-is-absolute": { - "version": "1.0.1", - "from": "path-is-absolute@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - }, - "pbkdf2-compat": { - "version": "2.0.1", - "from": "pbkdf2-compat@2.0.1", - "resolved": "https://registry.npmjs.org/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz" - }, - "pinkie": { - "version": "2.0.4", - "from": "pinkie@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" - }, - "pinkie-promise": { - "version": "2.0.1", - "from": "pinkie-promise@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" - }, - "postcss": { - "version": "5.2.5", - "from": "postcss@>=5.0.6 <6.0.0", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.5.tgz", - "dependencies": { - "source-map": { - "version": "0.5.6", - "from": "source-map@>=0.5.6 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" - }, - "supports-color": { - "version": "3.1.2", - "from": "supports-color@>=3.1.2 <4.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz" - } - } - }, - "postcss-calc": { - "version": "5.3.1", - "from": "postcss-calc@>=5.2.0 <6.0.0", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz" - }, - "postcss-colormin": { - "version": "2.2.1", - "from": "postcss-colormin@>=2.1.8 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.1.tgz" - }, - "postcss-convert-values": { - "version": "2.4.1", - "from": "postcss-convert-values@>=2.3.4 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.4.1.tgz" - }, - "postcss-discard-comments": { - "version": "2.0.4", - "from": "postcss-discard-comments@>=2.0.4 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz" - }, - "postcss-discard-duplicates": { - "version": "2.0.1", - "from": "postcss-discard-duplicates@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.0.1.tgz" - }, - "postcss-discard-empty": { - "version": "2.1.0", - "from": "postcss-discard-empty@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz" - }, - "postcss-discard-overridden": { - "version": "0.1.1", - "from": "postcss-discard-overridden@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz" - }, - "postcss-discard-unused": { - "version": "2.2.2", - "from": "postcss-discard-unused@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.2.tgz" - }, - "postcss-filter-plugins": { - "version": "2.0.2", - "from": "postcss-filter-plugins@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz" - }, - "postcss-merge-idents": { - "version": "2.1.7", - "from": "postcss-merge-idents@>=2.1.5 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz" - }, - "postcss-merge-longhand": { - "version": "2.0.1", - "from": "postcss-merge-longhand@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.1.tgz" - }, - "postcss-merge-rules": { - "version": "2.0.10", - "from": "postcss-merge-rules@>=2.0.3 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.0.10.tgz" - }, - "postcss-message-helpers": { - "version": "2.0.0", - "from": "postcss-message-helpers@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz" - }, - "postcss-minify-font-values": { - "version": "1.0.5", - "from": "postcss-minify-font-values@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz" - }, - "postcss-minify-gradients": { - "version": "1.0.5", - "from": "postcss-minify-gradients@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz" - }, - "postcss-minify-params": { - "version": "1.0.5", - "from": "postcss-minify-params@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.0.5.tgz" - }, - "postcss-minify-selectors": { - "version": "2.0.5", - "from": "postcss-minify-selectors@>=2.0.4 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.0.5.tgz" - }, - "postcss-modules-extract-imports": { - "version": "1.0.1", - "from": "postcss-modules-extract-imports@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.1.tgz" - }, - "postcss-modules-local-by-default": { - "version": "1.1.1", - "from": "postcss-modules-local-by-default@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.1.1.tgz" - }, - "postcss-modules-scope": { - "version": "1.0.2", - "from": "postcss-modules-scope@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.0.2.tgz" - }, - "postcss-modules-values": { - "version": "1.2.2", - "from": "postcss-modules-values@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.2.2.tgz" - }, - "postcss-normalize-charset": { - "version": "1.1.0", - "from": "postcss-normalize-charset@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.0.tgz" - }, - "postcss-normalize-url": { - "version": "3.0.7", - "from": "postcss-normalize-url@>=3.0.7 <4.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.7.tgz" - }, - "postcss-ordered-values": { - "version": "2.2.2", - "from": "postcss-ordered-values@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.2.tgz" - }, - "postcss-reduce-idents": { - "version": "2.3.1", - "from": "postcss-reduce-idents@>=2.2.2 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.3.1.tgz" - }, - "postcss-reduce-initial": { - "version": "1.0.0", - "from": "postcss-reduce-initial@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.0.tgz" - }, - "postcss-reduce-transforms": { - "version": "1.0.3", - "from": "postcss-reduce-transforms@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.3.tgz" - }, - "postcss-selector-parser": { - "version": "2.2.1", - "from": "postcss-selector-parser@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.1.tgz" - }, - "postcss-svgo": { - "version": "2.1.5", - "from": "postcss-svgo@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.5.tgz" - }, - "postcss-unique-selectors": { - "version": "2.0.2", - "from": "postcss-unique-selectors@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz" - }, - "postcss-value-parser": { - "version": "3.3.0", - "from": "postcss-value-parser@>=3.2.3 <4.0.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz" - }, - "postcss-zindex": { - "version": "2.1.1", - "from": "postcss-zindex@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.1.1.tgz" - }, - "prelude-ls": { - "version": "1.1.2", - "from": "prelude-ls@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" - }, - "prepend-http": { - "version": "1.0.4", - "from": "prepend-http@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz" - }, - "preserve": { - "version": "0.2.0", - "from": "preserve@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" - }, - "process": { - "version": "0.11.9", - "from": "process@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.9.tgz" - }, - "process-nextick-args": { - "version": "1.0.7", - "from": "process-nextick-args@>=1.0.6 <1.1.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" - }, - "prr": { - "version": "0.0.0", - "from": "prr@>=0.0.0 <0.1.0", - "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz" - }, - "punycode": { - "version": "1.4.1", - "from": "punycode@>=1.4.1 <2.0.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" - }, - "q": { - "version": "1.4.1", - "from": "q@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz" - }, - "qs": { - "version": "6.3.0", - "from": "qs@>=6.3.0 <6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.0.tgz" - }, - "query-string": { - "version": "4.2.3", - "from": "query-string@>=4.1.0 <5.0.0", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.2.3.tgz" - }, - "querystring": { - "version": "0.2.0", - "from": "querystring@0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" - }, - "querystring-es3": { - "version": "0.2.1", - "from": "querystring-es3@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz" - }, - "randomatic": { - "version": "1.1.5", - "from": "randomatic@>=1.1.3 <2.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.5.tgz" - }, - "readable-stream": { - "version": "2.1.5", - "from": "readable-stream@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz" - }, - "readdirp": { - "version": "2.1.0", - "from": "readdirp@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz" - }, - "reduce-css-calc": { - "version": "1.3.0", - "from": "reduce-css-calc@>=1.2.6 <2.0.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz" - }, - "reduce-function-call": { - "version": "1.0.1", - "from": "reduce-function-call@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.1.tgz", - "dependencies": { - "balanced-match": { - "version": "0.1.0", - "from": "balanced-match@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.1.0.tgz" - } - } - }, - "regenerate": { - "version": "1.3.1", - "from": "regenerate@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.1.tgz" - }, - "regex-cache": { - "version": "0.4.3", - "from": "regex-cache@>=0.4.2 <0.5.0", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz" - }, - "regexpu-core": { - "version": "1.0.0", - "from": "regexpu-core@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz" - }, - "regjsgen": { - "version": "0.2.0", - "from": "regjsgen@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz" - }, - "regjsparser": { - "version": "0.1.5", - "from": "regjsparser@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz" - }, - "repeat-element": { - "version": "1.1.2", - "from": "repeat-element@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz" - }, - "repeat-string": { - "version": "1.6.1", - "from": "repeat-string@>=1.5.2 <2.0.0", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" - }, - "request": { - "version": "2.76.0", - "from": "request@>=2.55.0 <3.0.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.76.0.tgz" - }, - "right-align": { - "version": "0.1.3", - "from": "right-align@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz" - }, - "ripemd160": { - "version": "0.2.0", - "from": "ripemd160@0.2.0", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-0.2.0.tgz" - }, - "sax": { - "version": "1.2.1", - "from": "sax@>=1.1.4 <2.0.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz" - }, - "set-immediate-shim": { - "version": "1.0.1", - "from": "set-immediate-shim@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz" - }, - "sha.js": { - "version": "2.2.6", - "from": "sha.js@2.2.6", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.2.6.tgz" - }, - "sntp": { - "version": "1.0.9", - "from": "sntp@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" - }, - "sort-keys": { - "version": "1.1.2", - "from": "sort-keys@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz" - }, - "source-list-map": { - "version": "0.1.6", - "from": "source-list-map@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.6.tgz" - }, - "source-map": { - "version": "0.2.0", - "from": "source-map@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz" - }, - "sprintf-js": { - "version": "1.0.3", - "from": "sprintf-js@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" - }, - "sshpk": { - "version": "1.10.1", - "from": "sshpk@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.1.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "stream-browserify": { - "version": "1.0.0", - "from": "stream-browserify@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-1.0.0.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.1.14", - "from": "readable-stream@>=1.0.27-1 <2.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" - } - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "from": "strict-uri-encode@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz" - }, - "string_decoder": { - "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - }, - "stringstream": { - "version": "0.0.5", - "from": "stringstream@>=0.0.4 <0.1.0", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" - }, - "strip-ansi": { - "version": "3.0.1", - "from": "strip-ansi@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" - }, - "style-loader": { - "version": "0.13.1", - "from": "style-loader@0.13.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.13.1.tgz" - }, - "supports-color": { - "version": "2.0.0", - "from": "supports-color@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" - }, - "svgo": { - "version": "0.7.1", - "from": "svgo@>=0.7.0 <0.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.1.tgz" - }, - "symbol-tree": { - "version": "3.1.4", - "from": "symbol-tree@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.1.4.tgz" - }, - "tapable": { - "version": "0.1.10", - "from": "tapable@>=0.1.8 <0.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz" - }, - "timers-browserify": { - "version": "1.4.2", - "from": "timers-browserify@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz" - }, - "to-markdown": { - "version": "3.0.2", - "from": "to-markdown@3.0.2", - "resolved": "https://registry.npmjs.org/to-markdown/-/to-markdown-3.0.2.tgz" - }, - "tough-cookie": { - "version": "2.3.2", - "from": "tough-cookie@>=2.3.1 <3.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz" - }, - "tr46": { - "version": "0.0.3", - "from": "tr46@>=0.0.3 <0.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" - }, - "tty-browserify": { - "version": "0.0.0", - "from": "tty-browserify@0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz" - }, - "tunnel-agent": { - "version": "0.4.3", - "from": "tunnel-agent@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" - }, - "tweetnacl": { - "version": "0.14.3", - "from": "tweetnacl@>=0.14.0 <0.15.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz" - }, - "type-check": { - "version": "0.3.2", - "from": "type-check@>=0.3.2 <0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" - }, - "uglify-js": { - "version": "2.6.4", - "from": "uglify-js@>=2.6.0 <2.7.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.4.tgz", - "dependencies": { - "async": { - "version": "0.2.10", - "from": "async@>=0.2.6 <0.3.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz" - }, - "source-map": { - "version": "0.5.6", - "from": "source-map@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "from": "uglify-to-browserify@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz" - }, - "uniq": { - "version": "1.0.1", - "from": "uniq@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz" - }, - "uniqid": { - "version": "4.1.0", - "from": "uniqid@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.0.tgz" - }, - "uniqs": { - "version": "2.0.0", - "from": "uniqs@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz" - }, - "url": { - "version": "0.10.3", - "from": "url@>=0.10.1 <0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "dependencies": { - "punycode": { - "version": "1.3.2", - "from": "punycode@1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" - } - } - }, - "util": { - "version": "0.10.3", - "from": "util@>=0.10.3 <0.11.0", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "dependencies": { - "inherits": { - "version": "2.0.1", - "from": "inherits@2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "from": "util-deprecate@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - }, - "vendors": { - "version": "1.0.1", - "from": "vendors@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz" - }, - "verror": { - "version": "1.3.6", - "from": "verror@1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" - }, - "vm-browserify": { - "version": "0.0.4", - "from": "vm-browserify@0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz" - }, - "void-elements": { - "version": "2.0.1", - "from": "void-elements@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz" - }, - "vue": { - "version": "2.0.3", - "from": "vue@2.0.3", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.0.3.tgz" - }, - "vue-resource": { - "version": "1.0.3", - "from": "vue-resource@1.0.3", - "resolved": "https://registry.npmjs.org/vue-resource/-/vue-resource-1.0.3.tgz" - }, - "vue-template-compiler": { - "version": "2.0.3", - "from": "vue-template-compiler@>=2.0.0-rc.6 <3.0.0", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.0.3.tgz" - }, - "vue-template-compiler-loader": { - "version": "1.0.3", - "from": "vue-template-compiler-loader@1.0.3", - "resolved": "https://registry.npmjs.org/vue-template-compiler-loader/-/vue-template-compiler-loader-1.0.3.tgz" - }, - "watchpack": { - "version": "0.2.9", - "from": "watchpack@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-0.2.9.tgz", - "dependencies": { - "async": { - "version": "0.9.2", - "from": "async@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz" - } - } - }, - "webidl-conversions": { - "version": "3.0.1", - "from": "webidl-conversions@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" - }, - "webpack": { - "version": "1.13.2", - "from": "webpack@1.13.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-1.13.2.tgz", - "dependencies": { - "acorn": { - "version": "3.3.0", - "from": "acorn@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz" - }, - "supports-color": { - "version": "3.1.2", - "from": "supports-color@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz" - } - } - }, - "webpack-core": { - "version": "0.6.8", - "from": "webpack-core@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.8.tgz", - "dependencies": { - "source-map": { - "version": "0.4.4", - "from": "source-map@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz" - } - } - }, - "whatwg-encoding": { - "version": "1.0.1", - "from": "whatwg-encoding@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz" - }, - "whatwg-url": { - "version": "3.0.0", - "from": "whatwg-url@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-3.0.0.tgz" - }, - "whet.extend": { - "version": "0.9.9", - "from": "whet.extend@>=0.9.9 <0.10.0", - "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz" - }, - "window-size": { - "version": "0.1.0", - "from": "window-size@0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" - }, - "wordwrap": { - "version": "1.0.0", - "from": "wordwrap@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" - }, - "xml-name-validator": { - "version": "2.0.1", - "from": "xml-name-validator@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz" - }, - "xtend": { - "version": "4.0.1", - "from": "xtend@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" - }, - "yargs": { - "version": "3.10.0", - "from": "yargs@>=3.10.0 <3.11.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz" - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index e0edf18..0000000 --- a/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "private": true, - "main": "./src/main/javascript/platon.js", - "scripts": { - "start": "webpack --debug --watch", - "build": "webpack -p" - }, - "dependencies": { - "marked": "0.3.6", - "to-markdown": "3.0.2", - "vue": "2.0.5", - "vue-resource": "1.0.3" - }, - "devDependencies": { - "css-loader": "0.25.0", - "style-loader": "0.13.1", - "vue-template-compiler-loader": "1.0.3", - "webpack": "1.13.2" - } -} diff --git a/pom.xml b/pom.xml index 995935c..ad5e45f 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ org.springframework.boot spring-boot-starter-parent - 1.5.7.RELEASE + 2.0.2.RELEASE @@ -45,24 +45,15 @@ 1.8 UTF-8 - - 1.16.18 - - 21.0 - 3.5 + 25.1-jre + 3.7 1.10 - 2.8.6 - 20160924.1 + 20180219.1 1.2 - 1.4.193 - 9.4.1212 - 4.1.0 - 3.8.0 0.8.4 - 3.3.1 false @@ -113,7 +104,7 @@ com.fasterxml.jackson.datatype jackson-datatype-jsr310 - ${jackson-jsr310.version} + ${jackson.version} com.googlecode.owasp-java-html-sanitizer @@ -157,18 +148,6 @@ ${openpojo.version} test - - org.seleniumhq.selenium - selenium-remote-driver - ${selenium.version} - test - - - org.seleniumhq.selenium - selenium-support - ${selenium.version} - test - @@ -198,35 +177,10 @@ - - org.codehaus.mojo - build-helper-maven-plugin - 1.12 - - - reserve-container-port - - reserve-network-port - - process-resources - - - server.port - - - - - org.apache.maven.plugins maven-failsafe-plugin - 2.18.1 - - - ${server.port} - ${selenium.version} - - + 2.21.0 maven-resources-plugin @@ -371,52 +325,6 @@ - - com.github.eirslett - frontend-maven-plugin - 1.1 - - - ${skipFrontend} - - - - - install node and npm - - install-node-and-npm - - generate-resources - - v6.8.1 - 3.10.8 - - - - npm install - - npm - - - install - - - - npm run build - - npm - - - run build - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.3 - diff --git a/src/main/javascript/components/comment-form/comment-form.html b/src/main/javascript/components/comment-form/comment-form.html deleted file mode 100644 index 8ae6c9f..0000000 --- a/src/main/javascript/components/comment-form/comment-form.html +++ /dev/null @@ -1,12 +0,0 @@ -
- -
- {{ showPreview ? 'Hide Preview' : 'Preview' }}
-
-
-
-
- -
diff --git a/src/main/javascript/components/comment-form/index.js b/src/main/javascript/components/comment-form/index.js deleted file mode 100644 index dd602ef..0000000 --- a/src/main/javascript/components/comment-form/index.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var UserInfoService = require('./../../services/user-info-service.js'); -var CommentService = require('../../services/comment-service.js'); -var TextService = require('../../services/text-service.js'); -var events = require('../../utils/events.js'); - -var template = require('./comment-form.html'); - -module.exports = { - props: { - parentId: { - type: Number, - required: false - } - }, - - render: template.render, - staticRenderFns: template.staticRenderFns, - - data: function () { - return { - comment: UserInfoService.getUserInfo(), - rememberUser: UserInfoService.getRememberUser(), - showPreview: false, - previewStyle: {}, - markdown: '' - }; - }, - - methods: { - togglePreview: function () { - this.showPreview = !this.showPreview; - if (this.showPreview) { - this.comment.text = TextService.markdownToHtml(this.markdown); - } - try { - this.previewStyle.height = this.$el.getElementsByClassName('platon-form-text')[0].offsetHeight; - } catch (e) { - console.error(e.message); - } - }, - postComment: function () { - var vm = this; - - if (vm.rememberUser) { - UserInfoService.storeUserInfo({ - author: vm.comment.author, - email: vm.comment.email, - url: vm.comment.url - }); - } else { - UserInfoService.removeUserInfo(); - } - - vm.comment.parentId = vm.parentId; - vm.comment.text = TextService.markdownToHtml(vm.markdown); - - CommentService.postComment(window.location.pathname, document.title, vm.comment) - .then(function (newComment) { - vm.$emit('posted', newComment); - }) - .catch(function (error) { - console.error('error', error); - }); - } - }, - - created: function () { - var vm = this; - events.bus.$on(events.types.clearForm, function() { - vm.markdown = ''; - }); - } -}; diff --git a/src/main/javascript/components/comment/comment.html b/src/main/javascript/components/comment/comment.html deleted file mode 100644 index 58f7116..0000000 --- a/src/main/javascript/components/comment/comment.html +++ /dev/null @@ -1,50 +0,0 @@ -
- -
- -
- -
- {{ comment.author || 'Anonymous' }} - {{ comment.author || 'Anonymous' }} - ~ - {{ creationDate }} - (Edited) -
-
- [deleted] - ~ - {{ creationDate }} -
- -
-
- -
-
-
-

[deleted]

-
- - - - - - - -
diff --git a/src/main/javascript/components/comment/index.js b/src/main/javascript/components/comment/index.js deleted file mode 100644 index e4348e4..0000000 --- a/src/main/javascript/components/comment/index.js +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var CommentService = require('../../services/comment-service.js'); -var TextService = require('../../services/text-service.js'); -var updateCommentInList = require('../../utils/update-comment-in-list.js'); -var removeCommentFromList = require('../../utils/remove-comment-from-list.js'); - -var template = require('./comment.html'); - -module.exports = { - name: 'platon-comment', - props: { - comment: { - type: Object, - required: true - } - }, - - render: template.render, - staticRenderFns: template.staticRenderFns, - - data: function () { - return { - showReplyForm: false, - showEditForm: false, - showPreview: false, - markdown: '', - editedComment: { - text: '' - } - } - }, - - computed: { - creationDate: function () { - return new Date(this.comment.creationDate).toLocaleString(); - }, - longCreationDate: function () { - return 'Created: ' + new Date(this.comment.creationDate).toISOString(); - }, - longModificationDate: function () { - return 'Last modified: ' + new Date(this.comment.lastModificationDate).toISOString(); - }, - canEdit: function () { - return CommentService.canEditComment(this.comment); - }, - canDelete: function () { - return CommentService.canDeleteComment(this.comment); - }, - permalinkId: function () { - return 'platon-comment-' + this.comment.id; - } - }, - - components: { - 'platon-comment-form': require('../comment-form') - }, - - methods: { - replyPosted: function (newComment) { - this.comment.replies.push(newComment); - this.showReplyForm = false; - }, - replyEdited: function (updatedReply) { - updateCommentInList(this.comment.replies, updatedReply); - }, - replyDeleted: function (replyToRemove) { - removeCommentFromList(this.comment.replies, replyToRemove); - }, - toggleEditPreview: function () { - this.showPreview = !this.showPreview; - - if (this.showPreview) { - this.editedComment.text = TextService.markdownToHtml(this.markdown); - } - }, - updateMarkdown: function () { - this.markdown = TextService.htmlToMarkdown(this.comment.text); - }, - saveEdit: function () { - var vm = this; - - var comment = JSON.parse(JSON.stringify(this.comment)); - comment.text = TextService.markdownToHtml(this.markdown); - - CommentService.updateComment(comment) - .then(function () { - vm.$emit('edited', comment); - vm.showEditForm = false; - }) - .catch(function () { - console.error('error', arguments); - }); - }, - deleteComment: function () { - if (confirm('Do you really want to delete this comment?')) { - var vm = this; - CommentService.deleteComment(vm.comment).then(function () { - vm.$emit('deleted', vm.comment); - }); - } - } - } -}; diff --git a/src/main/javascript/counts.js b/src/main/javascript/counts.js deleted file mode 100644 index 3ff7dd6..0000000 --- a/src/main/javascript/counts.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var CommentService = require('./services/comment-service.js'); - -var findGroupedPlatonThreadUrlElements = require('./utils/find-thread-url-elements.js'); - -var groupedThreadUrlElements = findGroupedPlatonThreadUrlElements(); -var threadUrls = Object.keys(groupedThreadUrlElements); - -CommentService.countComments(threadUrls).then(function(commentCounts) { - for (var threadUrl in commentCounts) { - if (commentCounts.hasOwnProperty(threadUrl)) { - groupedThreadUrlElements[threadUrl].forEach(function (element) { - var count = commentCounts[threadUrl]; - element.textContent = count + (count === 1 ? ' Comment' : ' Comments'); - }); - } - } -}); diff --git a/src/main/javascript/list.html b/src/main/javascript/list.html deleted file mode 100644 index 52dbf07..0000000 --- a/src/main/javascript/list.html +++ /dev/null @@ -1,13 +0,0 @@ -
- -
Loading...
-

{{ (totalCommentCount || 0) + (totalCommentCount == 1 ? ' Comment' : ' Comments') }}

- - - - -
diff --git a/src/main/javascript/list.js b/src/main/javascript/list.js deleted file mode 100644 index 61c3b81..0000000 --- a/src/main/javascript/list.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var Vue = require('vue'); -var CommentService = require('./services/comment-service.js'); -var updateCommentInList = require('./utils/update-comment-in-list.js'); -var removeCommentFromList = require('./utils/remove-comment-from-list.js'); -var events = require('./utils/events.js'); - -var template = require('./list.html'); - -var userHasScrolled = false; - -if (document.getElementById('platon-comment-thread') !== null) { - - new Vue({ - el: '#platon-comment-thread', - render: template.render, - staticRenderFns: template.staticRenderFns, - - data: { - loading: true, - comments: [] - }, - - components: { - 'platon-comment': require('./components/comment'), - 'platon-comment-form': require('./components/comment-form') - }, - - methods: { - commentPosted: function (newComment) { - this.comments.push(newComment); - events.bus.$emit(events.types.clearForm); - }, - commentEdited: function (updatedComment) { - updateCommentInList(this.comments, updatedComment); - }, - commentDeleted: function (commentToRemove) { - removeCommentFromList(this.comments, commentToRemove); - } - }, - - created: function () { - var vm = this; - CommentService.getComments(window.location.pathname) - .then(function updateModel(commentsListResult) { - vm.comments = commentsListResult.comments; - vm.totalCommentCount = commentsListResult.totalCommentCount; - vm.loading = false; - - if (window.location.hash && window.location.hash.indexOf('#platon-comment-') >= 0) { - Vue.nextTick(function () { - var commentElem = document.querySelector(window.location.hash); - if (commentElem != null && !userHasScrolled) { - commentElem.scrollIntoView(true); - } - }); - } - }) - .catch(function displayError(reason) { - alert(reason); - }); - } - }); - - window.addEventListener('scroll', scrollListener); -} - -function scrollListener() { - userHasScrolled = true; - window.removeEventListener('scroll', scrollListener); -} diff --git a/src/main/javascript/platon.css b/src/main/javascript/platon.css deleted file mode 100644 index b3be662..0000000 --- a/src/main/javascript/platon.css +++ /dev/null @@ -1,87 +0,0 @@ -.platon-comments { - max-width: 40rem; -} - -.platon-comment { - clear: left; - min-height: 48px; -} - -.platon-comment > * { - margin-left: 58px; -} - -.platon-comment > .platon-comment { - border-left: 1px solid #ccc; - padding-left: 10px; -} - -.platon-comment > .platon-avatar { - float: left; - margin: 0 10px 10px 0; - width: 48px; -} - -.platon-avatar img { - width: 48px; - height: 48px; -} - -.platon-comment + .platon-comment, .platon-actions + .platon-comment, .platon-actions + .platon-form { - margin-top: 1rem; -} - -.platon-author { - font-weight: bold; -} - -.platon-text p { - margin: 0.5rem 0; -} - -.platon-meta, .platon-actions { - font-size: 0.8rem; -} - -.platon-meta { - margin-bottom: 0.5rem; -} - -.platon-actions { - margin-top: 0.5rem; -} - -.platon-form { - clear: left; -} - -.platon-form input { - margin-bottom: 0.5rem; -} - -.platon-form-text { - display: block; - width: 100%; - max-width: 100%; - min-height: 7.5rem; - line-height: 1.5; -} - -.platon-form-preview { - border: 1px solid #ddd; - overflow-y: auto; - box-sizing: border-box; - padding: 5px; -} - -.platon-comment > div.platon-text { - padding-right: -1px; -} - -.platon-form-preview > * { - margin-top: 0; -} - -.platon-form-preview > *:last-child { - margin-bottom: 0; -} diff --git a/src/main/javascript/platon.js b/src/main/javascript/platon.js deleted file mode 100644 index ad3d968..0000000 --- a/src/main/javascript/platon.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -require('./platon.css'); - -var Vue = require('vue'); -Vue.use(require('vue-resource')); - -require('./list.js'); -require('./counts.js'); diff --git a/src/main/javascript/services/comment-service.js b/src/main/javascript/services/comment-service.js deleted file mode 100644 index 15bb62b..0000000 --- a/src/main/javascript/services/comment-service.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var Vue = require('vue'); - -module.exports = { - getComments: function getComments(threadUrl) { - return Vue.http.get('/api/comments', { - params: { - threadUrl: threadUrl - } - }).then(function handleSuccess(response) { - return response.json(); - }, function handleError(response) { - if (response.status === 404) { - return Promise.resolve([]); - } else { - return Promise.reject(response); - } - }); - }, - - postComment: function postComment(threadUrl, threadTitle, comment) { - return Vue.http.post('/api/comments', comment, { - params: { - threadUrl: threadUrl, - threadTitle: threadTitle - } - }).then(function handleSuccess(response) { - if (response.status === 201) { - var commentSignature = response.headers.get('X-Signature'); - return response.json().then(function (newComment) { - storeSignature(newComment, commentSignature); - return Promise.resolve(newComment); - }); - } - }); - }, - - updateComment: function getCommentSignature(comment) { - return Vue.http.put('/api/comments/' + comment.id, comment, { - headers: { - 'X-Signature': getSignature(comment) - } - }); - }, - - deleteComment: function deleteComment(comment) { - return Vue.http.delete('/api/comments/' + comment.id, { - headers: { - 'X-Signature': getSignature(comment) - } - }); - }, - - canEditComment: function canEditComment(comment) { - return !hasSignatureExpired(getSignature(comment)); - }, - - canDeleteComment: function canDeleteComment(comment) { - return !hasSignatureExpired(getSignature(comment)); - }, - - countComments: function countComments(threadUrls) { - return Vue.http.get('/api/comment-counts{?threadUrl*}', { - params: { - threadUrl: threadUrls - } - }).then(function handleSuccess(response) { - return response.json().then(function (json) { - return Promise.resolve(json.commentCounts); - }); - }); - } -}; - -function storeSignature(comment, signature) { - localStorage.setItem(getSignatureKey(comment), signature); -} - -function getSignature(comment) { - return localStorage.getItem(getSignatureKey(comment)); -} - -function getSignatureKey(comment) { - return 'platon-comment-' + comment.id; -} - -function hasSignatureExpired(signature) { - if (!signature) { - return true; - } - - var signatureComponents = signature.split('|'); - - if (signatureComponents.length == 3) { - var expirationDate = Date.parse(signatureComponents[1]); - var now = Date.now(); - - return now > expirationDate; - } - - return true; -} diff --git a/src/main/javascript/services/text-service.js b/src/main/javascript/services/text-service.js deleted file mode 100644 index 140d0f6..0000000 --- a/src/main/javascript/services/text-service.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var toMarkdown = require('to-markdown'); -var toMarkdownOptions = { - gfm: true -}; - -var marked = require('marked'); -marked.setOptions({ - gfm: true, - tables: false, - breaks: false, - sanitize: false, - smartLists: true, - smartypants: true -}); - -module.exports = { - markdownToHtml: marked, - htmlToMarkdown: function (html) { - return toMarkdown(html, toMarkdownOptions); - } -}; diff --git a/src/main/javascript/services/user-info-service.js b/src/main/javascript/services/user-info-service.js deleted file mode 100644 index 5990b79..0000000 --- a/src/main/javascript/services/user-info-service.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var infoKey = 'platon-user-info'; -var rememberUserKey = 'platon-remember-user-key'; - -module.exports = { - getUserInfo: function getUserInfo() { - var userInfo = localStorage.getItem(infoKey); - if (userInfo === null) { - return {}; - } else { - return JSON.parse(userInfo) - } - }, - storeUserInfo: function storeUserInfo(userInfo) { - localStorage.setItem(infoKey, JSON.stringify(userInfo)); - localStorage.setItem(rememberUserKey, true); - }, - removeUserInfo: function removeUserInfo() { - localStorage.removeItem(infoKey); - localStorage.setItem(rememberUserKey, false); - }, - getRememberUser: function getRememberUser() { - var rememberUser = localStorage.getItem(rememberUserKey); - return rememberUser === null || rememberUser === 'true'; - } -}; diff --git a/src/main/javascript/utils/events.js b/src/main/javascript/utils/events.js deleted file mode 100644 index acc5e47..0000000 --- a/src/main/javascript/utils/events.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var Vue = require('vue'); - -module.exports = { - bus: new Vue(), - types: { - clearForm: 'clear-form' - } -}; diff --git a/src/main/javascript/utils/find-by-id.js b/src/main/javascript/utils/find-by-id.js deleted file mode 100644 index 0057716..0000000 --- a/src/main/javascript/utils/find-by-id.js +++ /dev/null @@ -1,29 +0,0 @@ - - -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = function findById(array, id) { - - for (var i = 0; i < array.length; i++) { - var element = array[i]; - if (element.id === id) { - return i; - } - } - - return false; -}; diff --git a/src/main/javascript/utils/find-thread-url-elements.js b/src/main/javascript/utils/find-thread-url-elements.js deleted file mode 100644 index 5900357..0000000 --- a/src/main/javascript/utils/find-thread-url-elements.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = function findGroupedPlatonThreadUrlElements() { - - var groupedElements = {}; - - function addThreadUrlAndElement(threadUrl, elem) { - if (!(threadUrl in groupedElements)) { - groupedElements[threadUrl] = [elem]; - } else if (groupedElements[threadUrl].indexOf(threadUrl) < 0) { - groupedElements[threadUrl].push(elem); - } - } - - var linksToCommentThreads = document.querySelectorAll('a[href$="#platon-comment-thread"]'); - - Array.prototype.forEach.call(linksToCommentThreads, function (linkElem) { - addThreadUrlAndElement(linkElem.pathname, linkElem); - }); - - var elemsWithThreadIds = document.querySelectorAll('*[data-platon-thread-url]'); - - Array.prototype.forEach.call(elemsWithThreadIds, function (elem) { - addThreadUrlAndElement(elem.getAttribute('data-platon-thread-url'), elem); - }); - - return groupedElements; -}; diff --git a/src/main/javascript/utils/remove-comment-from-list.js b/src/main/javascript/utils/remove-comment-from-list.js deleted file mode 100644 index d34f049..0000000 --- a/src/main/javascript/utils/remove-comment-from-list.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var Vue = require('vue'); -var findById = require('./find-by-id.js'); - -module.exports = function removeCommentFromList(list, comment) { - - var index = findById(list, comment.id); - - if (index !== false) { - var originalReplies = list[index].replies; - - Vue.set(list, index, { - id: comment.id, - parentId: comment.parentId, - creationDate: comment.creationDate, - lastModificationDate: comment.lastModificationDate, - replies: originalReplies, - status: 'DELETED' - }); - } -}; diff --git a/src/main/javascript/utils/update-comment-in-list.js b/src/main/javascript/utils/update-comment-in-list.js deleted file mode 100644 index 5cf152f..0000000 --- a/src/main/javascript/utils/update-comment-in-list.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var Vue = require('vue'); -var findById = require('./find-by-id.js'); - -module.exports = function updateCommentInList(list, comment) { - - var index = findById(list, comment.id); - - if (index !== false) { - comment.lastModificationDate = new Date().toISOString(); - Vue.set(list, index, comment); - } -}; diff --git a/src/test/java/de/vorb/platon/ui/CommentCountIT.java b/src/test/java/de/vorb/platon/ui/CommentCountIT.java deleted file mode 100644 index 7582399..0000000 --- a/src/test/java/de/vorb/platon/ui/CommentCountIT.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.ui; - -import de.vorb.platon.SpringUiIntegrationTestConfig; -import de.vorb.platon.jooq.tables.records.CommentRecord; -import de.vorb.platon.jooq.tables.records.CommentThreadRecord; -import de.vorb.platon.model.CommentStatus; -import de.vorb.platon.ui.pages.CommentCountPage; - -import lombok.extern.slf4j.Slf4j; -import org.jooq.DSLContext; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.openqa.selenium.TimeoutException; -import org.openqa.selenium.UnsupportedCommandException; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.logging.LogEntry; -import org.openqa.selenium.logging.LogType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static de.vorb.platon.jooq.Tables.COMMENT; -import static de.vorb.platon.jooq.Tables.COMMENT_THREAD; -import static org.assertj.core.api.Assertions.assertThat; - -@RunWith(SpringRunner.class) -@ContextConfiguration(classes = SpringUiIntegrationTestConfig.class) -@Slf4j -public class CommentCountIT { - - @Autowired - private WebDriver webDriver; - - @Autowired - private DSLContext dslContext; - - @Value("http://localhost:${server.port}/comment-count.html") - private String testUrl; - - private static final String URL_THREAD_1 = "/comment-count-thread-1.html"; - private static final String URL_THREAD_2 = "/comment-count-thread-2.html"; - - private Long thread1Id; - private List commentsThread1 = new ArrayList<>(); - private List commentsThread2 = Collections.emptyList(); - - @Before - public void setUp() throws Exception { - CommentThreadRecord thread = new CommentThreadRecord() - .setUrl(URL_THREAD_1) - .setTitle("Thread Count 1"); - thread = dslContext.insertInto(COMMENT_THREAD).set(thread).returning(COMMENT_THREAD.ID).fetchOne(); - thread1Id = thread.getId(); - - CommentRecord comment = new CommentRecord() - .setThreadId(thread1Id) - .setCreationDate(Instant.now()) - .setLastModificationDate(Instant.now()) - .setStatus(CommentStatus.PUBLIC) - .setText("Comment 1"); - comment = dslContext.insertInto(COMMENT).set(comment).returning(COMMENT.ID, COMMENT.STATUS).fetchOne(); - commentsThread1.add(comment); - - comment = new CommentRecord() - .setThreadId(thread1Id) - .setCreationDate(Instant.now()) - .setLastModificationDate(Instant.now()) - .setStatus(CommentStatus.AWAITING_MODERATION) - .setText("Comment 2"); - comment = dslContext.insertInto(COMMENT).set(comment).returning(COMMENT.ID, COMMENT.STATUS).fetchOne(); - commentsThread1.add(comment); - - comment = new CommentRecord() - .setThreadId(thread1Id) - .setCreationDate(Instant.now()) - .setLastModificationDate(Instant.now()) - .setStatus(CommentStatus.DELETED) - .setText("Comment 1"); - comment = dslContext.insertInto(COMMENT).set(comment).returning(COMMENT.ID, COMMENT.STATUS).fetchOne(); - commentsThread1.add(comment); - - comment = new CommentRecord() - .setThreadId(thread1Id) - .setParentId(comment.getId()) - .setCreationDate(Instant.now()) - .setLastModificationDate(Instant.now()) - .setStatus(CommentStatus.PUBLIC) - .setText("Comment 1"); - comment = dslContext.insertInto(COMMENT).set(comment).returning(COMMENT.ID, COMMENT.STATUS).fetchOne(); - commentsThread1.add(comment); - } - - @After - public void tearDown() throws Exception { - dslContext.deleteFrom(COMMENT).execute(); - dslContext.deleteFrom(COMMENT_THREAD).execute(); - } - - @Test - public void loadCommentCounts() throws Exception { - - final CommentCountPage commentPage = new CommentCountPage(webDriver); - - webDriver.get(testUrl); - try { - commentPage.waitUntilCommentCountsLoaded(); - } catch (TimeoutException e) { - displayBrowserLogs(); - } - - final Map> commentCountsByThread = commentPage.getCommentCountsByThread(); - - commentCountsByThread.values().forEach(commentCounts -> assertThat(commentCounts).hasSize(1)); - - final long countThread1 = commentCountsByThread.get(URL_THREAD_1).iterator().next(); - final long expectedCountThread1 = commentsThread1.stream() - .filter(comment -> comment.getStatus() == CommentStatus.PUBLIC) - .count(); - - assertThat(countThread1).isEqualTo(expectedCountThread1); - - final long countThread2 = commentCountsByThread.get(URL_THREAD_2).iterator().next(); - final long expectedCountThread2 = commentsThread2.stream() - .filter(comment -> comment.getStatus() == CommentStatus.PUBLIC) - .count(); - - assertThat(countThread2).isEqualTo(expectedCountThread2); - } - - private void displayBrowserLogs() { - try { - webDriver.manage().logs().get(LogType.BROWSER).getAll().forEach(this::logBrowserLogEntry); - } catch (UnsupportedCommandException e) { - log.warn("Unable to collect logs from browser"); - } - } - - private void logBrowserLogEntry(LogEntry logEntry) { - log.info("Browser log: <{}> [{}] {}", logEntry.getLevel(), - Instant.ofEpochMilli(logEntry.getTimestamp()), logEntry.getMessage()); - } -} diff --git a/src/test/java/de/vorb/platon/ui/CommentListIT.java b/src/test/java/de/vorb/platon/ui/CommentListIT.java deleted file mode 100644 index d72af6e..0000000 --- a/src/test/java/de/vorb/platon/ui/CommentListIT.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.ui; - -import de.vorb.platon.SpringUiIntegrationTestConfig; -import de.vorb.platon.jooq.tables.records.CommentRecord; -import de.vorb.platon.jooq.tables.records.CommentThreadRecord; -import de.vorb.platon.model.CommentStatus; -import de.vorb.platon.ui.pages.CommentListPage; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.codec.digest.MessageDigestAlgorithms; -import org.jooq.DSLContext; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.openqa.selenium.TimeoutException; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.logging.LogType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.util.Base64; - -import static de.vorb.platon.jooq.Tables.COMMENT; -import static de.vorb.platon.jooq.Tables.COMMENT_THREAD; -import static org.assertj.core.api.Assertions.assertThat; - -@RunWith(SpringRunner.class) -@ContextConfiguration(classes = SpringUiIntegrationTestConfig.class) -@Slf4j -public class CommentListIT { - - @Autowired - private WebDriver webDriver; - - @Autowired - private DSLContext dslContext; - - private static final String THREAD_URL = "/comment-list.html"; - - @Value("http://localhost:${server.port}" + THREAD_URL) - private String testUrl; - - private CommentThreadRecord thread; - private CommentRecord topLevelComment; - private CommentRecord childComment; - private CommentRecord deletedComment; - - @Before - public void setUp() throws Exception { - thread = new CommentThreadRecord() - .setUrl(THREAD_URL) - .setTitle("Thread"); - thread = dslContext.insertInto(COMMENT_THREAD).set(thread).returning(COMMENT_THREAD.ID).fetchOne(); - - topLevelComment = createComment(null, CommentStatus.PUBLIC, "Sample text", "A", "a@example.org", - "http://example.org"); - deletedComment = createComment(topLevelComment.getId(), CommentStatus.DELETED, "Sample text 2", "B", - "b@example.com", "http://example.com"); - childComment = createComment(topLevelComment.getId(), CommentStatus.PUBLIC, "Child text", "C", "c@example.com", - "http://example.com"); - } - - @After - public void tearDown() throws Exception { - dslContext.deleteFrom(COMMENT).execute(); - dslContext.deleteFrom(COMMENT_THREAD).execute(); - } - - private CommentRecord createComment(Long parentId, CommentStatus status, String text, String author, String email, - String url) throws NoSuchAlgorithmException { - - CommentRecord comment = new CommentRecord() - .setThreadId(thread.getId()) - .setParentId(parentId) - .setCreationDate(Instant.now()) - .setLastModificationDate(Instant.now()) - .setStatus(status) - .setText(text) - .setAuthor(author); - - MessageDigest md5 = MessageDigest.getInstance(MessageDigestAlgorithms.MD5); - comment.setEmailHash(Base64.getEncoder().encodeToString(md5.digest(email.getBytes(StandardCharsets.UTF_8)))); - - comment.setUrl(url); - - comment = dslContext.insertInto(COMMENT) - .set(comment) - .returning(COMMENT.ID, COMMENT.STATUS) - .fetchOne(); - - return comment; - } - - @Test - public void loadCommentsAndDisplayComments() throws Exception { - - final CommentListPage commentPage = new CommentListPage(webDriver); - - webDriver.get(testUrl); - try { - commentPage.waitUntilCommentListLoaded(); - } catch (TimeoutException e) { - webDriver.manage().logs().get(LogType.BROWSER).getAll().forEach( - logEntry -> log.info("Browser log: <{}> [{}] {}", logEntry.getLevel(), - Instant.ofEpochMilli(logEntry.getTimestamp()), logEntry.getMessage())); - } - - assertThat(commentPage.isCommentFormVisible()).isTrue(); - - assertThat(commentPage.isCommentWithIdVisible(topLevelComment.getId())).isTrue(); - assertThat(commentPage.isCommentWithIdVisible(deletedComment.getId())).isTrue(); - assertThat(commentPage.isCommentWithIdVisible(childComment.getId())).isTrue(); - - assertThat(commentPage.isCommentWithIdDeleted(topLevelComment.getId())).isFalse(); - assertThat(commentPage.isCommentWithIdDeleted(deletedComment.getId())).isTrue(); - assertThat(commentPage.isCommentWithIdDeleted(childComment.getId())).isFalse(); - } - - @Test - public void postNewComment() throws Exception { - - final CommentListPage commentPage = new CommentListPage(webDriver); - - webDriver.get(testUrl); - try { - commentPage.waitUntilCommentListLoaded(); - } catch (TimeoutException e) { - webDriver.manage().logs().get(LogType.BROWSER).getAll().forEach( - logEntry -> log.info("Browser log: <{}> [{}] {}", logEntry.getLevel(), - Instant.ofEpochMilli(logEntry.getTimestamp()), logEntry.getMessage())); - } - - commentPage.replyToComment( - childComment.getId(), - "A newly created comment", - "Selenium", - "selenium@example.org", - "http://example.org/selenium"); - - assertThat(commentPage.commentWithIdHasReplies(childComment.getId())).isTrue(); - } -} diff --git a/src/test/java/de/vorb/platon/ui/pages/CommentCountPage.java b/src/test/java/de/vorb/platon/ui/pages/CommentCountPage.java deleted file mode 100644 index 5aeef3d..0000000 --- a/src/test/java/de/vorb/platon/ui/pages/CommentCountPage.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.ui.pages; - -import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.WebDriverWait; - -import java.net.URI; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import static org.openqa.selenium.support.ui.ExpectedConditions.textMatches; - -public class CommentCountPage { - - private static final String HREF_SELECTOR = "a[href$=\"#platon-comment-thread\"]"; - private static final String ATTR_SELECTOR = "*[data-platon-thread-url]"; - - private static Pattern COUNT_PATTERN = Pattern.compile(".*?(\\d++).*?"); - - private final WebDriver webDriver; - - public CommentCountPage(WebDriver webDriver) { - this.webDriver = webDriver; - } - - public void waitUntilCommentCountsLoaded() { - new WebDriverWait(webDriver, 15) - .until(textMatches(By.cssSelector(HREF_SELECTOR + ", " + ATTR_SELECTOR), COUNT_PATTERN)); - } - - public Map> getCommentCountsByThread() { - - final Map> commentCountsByThread = new HashMap<>(); - - final Map> linkCommentCounts = getCommentCounts( - By.cssSelector(HREF_SELECTOR), CommentCountPage::getThreadUrlFromHref); - - linkCommentCounts.forEach(commentCountsByThread::put); - - final Map> attrCommentCounts = getCommentCounts( - By.cssSelector(ATTR_SELECTOR), CommentCountPage::getThreadUrlFromDataAttribute); - - attrCommentCounts.forEach((threadUrl, commentCount) -> { - if (attrCommentCounts.containsKey(threadUrl)) { - attrCommentCounts.get(threadUrl).addAll(commentCount); - } else { - attrCommentCounts.put(threadUrl, commentCount); - } - }); - - return commentCountsByThread; - } - - private Map> getCommentCounts(By selector, Function getThreadId) { - return webDriver.findElements(selector).stream() - .collect(Collectors.groupingBy(getThreadId, - Collectors.mapping(CommentCountPage::parseCommentCount, Collectors.toSet()))); - } - - private static Long parseCommentCount(WebElement elem) { - final Matcher matcher = COUNT_PATTERN.matcher(elem.getText()); - if (matcher.matches()) { - return Long.parseUnsignedLong(matcher.group(1)); - } else { - throw new IllegalArgumentException( - "Comment count could not be parsed from element text: " + elem.getText()); - } - } - - private static String getThreadUrlFromHref(WebElement elem) { - return URI.create(elem.getAttribute("href")).getPath(); - } - - private static String getThreadUrlFromDataAttribute(WebElement elem) { - return elem.getAttribute("data-platon-thread-url"); - } -} diff --git a/src/test/java/de/vorb/platon/ui/pages/CommentListPage.java b/src/test/java/de/vorb/platon/ui/pages/CommentListPage.java deleted file mode 100644 index 281daf1..0000000 --- a/src/test/java/de/vorb/platon/ui/pages/CommentListPage.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.ui.pages; - -import org.assertj.core.util.Preconditions; -import org.openqa.selenium.By; -import org.openqa.selenium.UnsupportedCommandException; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.interactions.Actions; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; - -import java.util.List; - -public class CommentListPage { - - private final WebDriver webDriver; - - public CommentListPage(WebDriver webDriver) { - this.webDriver = webDriver; - } - - public void waitUntilCommentListLoaded() { - new WebDriverWait(webDriver, 15).until( - ExpectedConditions.presenceOfElementLocated(By.className("platon-comments"))); - } - - public boolean isCommentFormVisible() { - return webDriver.findElement(By.className("platon-form")).isDisplayed(); - } - - public boolean isCommentWithIdVisible(long id) { - final WebElement comment = findCommentById(id); - return comment.isDisplayed(); - } - - public boolean isCommentWithIdDeleted(long id) { - final WebElement comment = findCommentById(id); - final String author = comment.findElement(By.className("platon-author")).getText(); - final String text = comment.findElement(By.className("platon-text")).getText(); - return "[deleted]".equals(author) - && "[deleted]".equals(text); - } - - public void replyToComment(long id, String text, String author, String email, String url) { - final WebElement existingComment = findCommentById(id); - existingComment.findElement(By.linkText("Reply")).click(); - - Preconditions.checkNotNull(text); - final WebElement textArea = getFirstVisibleChildMatching(existingComment, By.className("platon-form-text")); - tryMovingToElement(textArea); - textArea.sendKeys(text); - - if (author != null) { - final WebElement authorTextField = getFirstVisibleChildMatching(existingComment, - By.className("platon-form-author")); - tryMovingToElement(authorTextField); - authorTextField.sendKeys(author); - } - - if (email != null) { - final WebElement emailTextField = getFirstVisibleChildMatching(existingComment, - By.className("platon-form-email")); - tryMovingToElement(emailTextField); - emailTextField.sendKeys(email); - } - - if (url != null) { - final WebElement urlTextField = getFirstVisibleChildMatching(existingComment, - By.className("platon-form-url")); - tryMovingToElement(urlTextField); - urlTextField.sendKeys(url); - } - - getFirstVisibleChildMatching(existingComment, By.cssSelector("form.platon-form")).submit(); - } - - public boolean commentWithIdHasReplies(long id) { - - new WebDriverWait(webDriver, 30).until( - ExpectedConditions.visibilityOfNestedElementsLocatedBy(findCommentById(id), - By.className("platon-comment"))); - - final List replies = findCommentById(id).findElements(By.className("platon-comment")); - - if (!replies.isEmpty()) { - tryMovingToElement(replies.get(0)); - return true; - } else { - return false; - } - } - - private void tryMovingToElement(WebElement element) { - try { - new Actions(webDriver).moveToElement(element).perform(); - } catch (UnsupportedCommandException ignored) { - } - } - - private WebElement findCommentById(long id) { - return webDriver.findElement(By.id("platon-comment-" + id)); - } - - private WebElement getFirstVisibleChildMatching(WebElement parent, By childLocator) { - return parent.findElements(childLocator).stream() - .filter(WebElement::isDisplayed) - .findFirst() - .orElseThrow(() -> new NullPointerException("No matching element found that is visible")); - } -} diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index b52b23a..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var path = require('path'); - -var jsDir = path.resolve(__dirname, 'src/main/javascript'); - -module.exports = { - entry: { - platon: path.resolve(jsDir, 'platon.js') - }, - output: { - path: path.resolve(__dirname, 'src/main/webapp/js'), - filename: '[name].js' - }, - module: { - loaders: [ - {test: /\.html$/, loader: 'vue-template-compiler'}, - {test: /\.css$/, loader: 'style!css'} - ] - }, - devtool: 'source-map' -}; From f6920beacf8b8afc6ae9a8351c69e65230183bdb Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Sun, 10 Jun 2018 16:50:19 +0200 Subject: [PATCH 02/16] Remove technical dept --- pom.xml | 26 ++++++++++ .../platon/security/SecurityException.java | 4 +- .../de/vorb/platon/web/api/ApiConfig.java | 4 +- .../common/PoweredByResponseInterceptor.java | 2 +- .../web/api/errors/RequestException.java | 4 +- .../vorb/platon/HtmlInputSanitizerTest.java | 6 +-- .../java/de/vorb/platon/PlatonAppTest.java | 2 +- src/test/java/de/vorb/platon/PojoTest.java | 4 +- .../platon/SpringUiIntegrationTestConfig.java | 51 ------------------- .../DatabaseSecretKeyProviderTest.java | 8 +-- .../HmacSignatureTokenValidatorTest.java | 8 +-- .../security/SignatureComponentsTest.java | 8 +-- .../platon/security/SignatureCreatorTest.java | 6 +-- .../platon/util/ByteArrayConverterTest.java | 6 +-- .../de/vorb/platon/web/api/ApiConfigIT.java | 2 +- .../de/vorb/platon/web/api/ApiConfigTest.java | 4 +- .../web/api/RequestExceptionVerifier.java | 2 +- .../web/api/common/CommentConverterTest.java | 13 +++-- .../web/api/common/CommentFiltersTest.java | 2 +- .../web/api/common/CommentSanitizerTest.java | 18 +++---- .../api/common/CommentUriResolverTest.java | 2 +- .../PoweredByResponseInterceptorTest.java | 6 +-- .../web/api/common/RequestValidatorTest.java | 8 +-- .../CommentControllerDeleteTest.java | 6 +-- .../CommentControllerFindByThreadUrlTest.java | 8 +-- .../CommentControllerGetByIdTest.java | 6 +-- .../api/controllers/CommentControllerIT.java | 4 +- .../CommentControllerPostTest.java | 18 +++---- .../controllers/CommentControllerTest.java | 10 ++-- .../CommentControllerUpdateTest.java | 14 ++--- .../errors/RequestExceptionHandlerTest.java | 2 +- .../web/api/errors/RequestExceptionTest.java | 18 +++---- .../web/api/json/ByteArraySerializerTest.java | 2 +- .../platon/web/mvc/IndexControllerTest.java | 2 +- 34 files changed, 130 insertions(+), 156 deletions(-) delete mode 100644 src/test/java/de/vorb/platon/SpringUiIntegrationTestConfig.java diff --git a/pom.xml b/pom.xml index ad5e45f..5e29ca5 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,7 @@ 0.8.4 + 2.18.3 false @@ -148,6 +149,12 @@ ${openpojo.version} test + + org.mockito + mockito-core + ${mockito-core.version} + test + @@ -177,6 +184,25 @@ + + org.codehaus.mojo + build-helper-maven-plugin + 1.12 + + + reserve-container-port + + reserve-network-port + + process-resources + + + server.port + + + + + org.apache.maven.plugins maven-failsafe-plugin diff --git a/src/main/java/de/vorb/platon/security/SecurityException.java b/src/main/java/de/vorb/platon/security/SecurityException.java index 462342b..7ba333a 100644 --- a/src/main/java/de/vorb/platon/security/SecurityException.java +++ b/src/main/java/de/vorb/platon/security/SecurityException.java @@ -16,9 +16,9 @@ package de.vorb.platon.security; -public class SecurityException extends RuntimeException { +class SecurityException extends RuntimeException { - public SecurityException(String message, Throwable cause) { + SecurityException(String message, Throwable cause) { super(message, cause); } diff --git a/src/main/java/de/vorb/platon/web/api/ApiConfig.java b/src/main/java/de/vorb/platon/web/api/ApiConfig.java index ed49b2d..dcce1fb 100644 --- a/src/main/java/de/vorb/platon/web/api/ApiConfig.java +++ b/src/main/java/de/vorb/platon/web/api/ApiConfig.java @@ -26,11 +26,11 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @ComponentScan(basePackageClasses = {PersistenceConfig.class, SecurityConfig.class}) -public class ApiConfig extends WebMvcConfigurerAdapter { +public class ApiConfig implements WebMvcConfigurer { @Bean public ObjectMapper objectMapper() { diff --git a/src/main/java/de/vorb/platon/web/api/common/PoweredByResponseInterceptor.java b/src/main/java/de/vorb/platon/web/api/common/PoweredByResponseInterceptor.java index 24b8e0c..027fcee 100644 --- a/src/main/java/de/vorb/platon/web/api/common/PoweredByResponseInterceptor.java +++ b/src/main/java/de/vorb/platon/web/api/common/PoweredByResponseInterceptor.java @@ -26,7 +26,7 @@ public class PoweredByResponseInterceptor extends HandlerInterceptorAdapter { @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, - ModelAndView modelAndView) throws Exception { + ModelAndView modelAndView) { response.addHeader("X-Powered-By", "Platon"); } } diff --git a/src/main/java/de/vorb/platon/web/api/errors/RequestException.java b/src/main/java/de/vorb/platon/web/api/errors/RequestException.java index 4af5df7..1d3eb18 100644 --- a/src/main/java/de/vorb/platon/web/api/errors/RequestException.java +++ b/src/main/java/de/vorb/platon/web/api/errors/RequestException.java @@ -80,7 +80,7 @@ public static Builder notFound() { return withStatus(HttpStatus.NOT_FOUND); } - public static Builder internalServerError() { + static Builder internalServerError() { return withStatus(HttpStatus.INTERNAL_SERVER_ERROR); } @@ -90,7 +90,7 @@ public static class Builder { private String message; private Throwable cause; - public Builder(int status) { + Builder(int status) { this.status = status; } diff --git a/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java b/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java index f93ca5c..1f56cba 100644 --- a/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java +++ b/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java @@ -28,12 +28,12 @@ public class HtmlInputSanitizerTest { private HtmlInputSanitizer htmlInputSanitizer; @Before - public void setUp() throws Exception { + public void setUp() { htmlInputSanitizer = new HtmlInputSanitizer("p,br"); } @Test - public void htmlWithScriptTag() throws Exception { + public void htmlWithScriptTag() { final String sanitizedHtml = htmlInputSanitizer.sanitize("

Text

"); @@ -44,7 +44,7 @@ public void htmlWithScriptTag() throws Exception { } @Test - public void worksWithMultipleTags() throws Exception { + public void worksWithMultipleTags() { final String sanitizedHtml = htmlInputSanitizer.sanitize("

First line
Second line

"); diff --git a/src/test/java/de/vorb/platon/PlatonAppTest.java b/src/test/java/de/vorb/platon/PlatonAppTest.java index 461937d..364a738 100644 --- a/src/test/java/de/vorb/platon/PlatonAppTest.java +++ b/src/test/java/de/vorb/platon/PlatonAppTest.java @@ -26,7 +26,7 @@ public class PlatonAppTest { @Test - public void configuredClockIsUtc() throws Exception { + public void configuredClockIsUtc() { final Clock configuredClock = new PlatonApp().clock(); final ZoneId utc = ZoneId.of("Z"); assertThat(configuredClock.getZone()).isEqualTo(utc); diff --git a/src/test/java/de/vorb/platon/PojoTest.java b/src/test/java/de/vorb/platon/PojoTest.java index 6a5abdd..07e0a13 100644 --- a/src/test/java/de/vorb/platon/PojoTest.java +++ b/src/test/java/de/vorb/platon/PojoTest.java @@ -70,7 +70,7 @@ public PojoTest(PojoClass pojoClass) { } @Before - public void setUp() throws Exception { + public void setUp() { validator = ValidatorBuilder.create() .with(new NoPrimitivesRule()) .with(new NoStaticExceptFinalRule()) @@ -83,7 +83,7 @@ public void setUp() throws Exception { } @Test - public void validate() throws Exception { + public void validate() { validator.validate(pojoClass); } diff --git a/src/test/java/de/vorb/platon/SpringUiIntegrationTestConfig.java b/src/test/java/de/vorb/platon/SpringUiIntegrationTestConfig.java deleted file mode 100644 index 66142ff..0000000 --- a/src/test/java/de/vorb/platon/SpringUiIntegrationTestConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon; - -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.remote.DesiredCapabilities; -import org.openqa.selenium.remote.RemoteWebDriver; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.concurrent.TimeUnit; - -@Configuration -@EnableAutoConfiguration -@PropertySource("classpath:config/application.properties") -public class SpringUiIntegrationTestConfig { - - @Bean - public WebDriver webDriver() throws MalformedURLException { - final WebDriver webDriver = new RemoteWebDriver(getRemoteUrl(), getDesiredCapabilities()); - webDriver.manage().timeouts().implicitlyWait(500, TimeUnit.MILLISECONDS); - return webDriver; - } - - private DesiredCapabilities getDesiredCapabilities() { - return DesiredCapabilities.firefox(); - } - - private URL getRemoteUrl() throws MalformedURLException { - return new URL("http://localhost:4444/wd/hub"); - } - -} diff --git a/src/test/java/de/vorb/platon/security/DatabaseSecretKeyProviderTest.java b/src/test/java/de/vorb/platon/security/DatabaseSecretKeyProviderTest.java index 50e5d68..a13642f 100644 --- a/src/test/java/de/vorb/platon/security/DatabaseSecretKeyProviderTest.java +++ b/src/test/java/de/vorb/platon/security/DatabaseSecretKeyProviderTest.java @@ -22,15 +22,15 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.util.Base64; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -48,7 +48,7 @@ public class DatabaseSecretKeyProviderTest { private PropertyRepository propertyRepository; @Test - public void noSecretKeyAvailable() throws Exception { + public void noSecretKeyAvailable() { final SecretKey secretKey = assertThatSecretKeyIsGenerated(); assertThatSecretKeyIsNotRecreated(secretKey); } diff --git a/src/test/java/de/vorb/platon/security/HmacSignatureTokenValidatorTest.java b/src/test/java/de/vorb/platon/security/HmacSignatureTokenValidatorTest.java index 42d0c33..21145b3 100644 --- a/src/test/java/de/vorb/platon/security/HmacSignatureTokenValidatorTest.java +++ b/src/test/java/de/vorb/platon/security/HmacSignatureTokenValidatorTest.java @@ -51,7 +51,7 @@ public void setUp() throws Exception { } @Test(expected = SecurityException.class) - public void testInvalidKey() throws Exception { + public void testInvalidKey() { Mockito.when(secretKeyProvider.getSecretKey()).thenReturn(null); @@ -61,7 +61,7 @@ public void testInvalidKey() throws Exception { @Test @Repeat(10) - public void testGetSignatureTokenIsRepeatable() throws Exception { + public void testGetSignatureTokenIsRepeatable() { final String identifier = "comment/1"; final Instant expirationTime = Instant.now(); @@ -73,13 +73,13 @@ public void testGetSignatureTokenIsRepeatable() throws Exception { } @Test - public void expiredTokenRequestIsInvalid() throws Exception { + public void expiredTokenRequestIsInvalid() { final Instant expirationTime = CURRENT_TIME.minusMillis(1); assertThatRequestIsInvalid(expirationTime, expirationTime); } @Test - public void testCannotFakeExpirationDate() throws Exception { + public void testCannotFakeExpirationDate() { assertThatRequestIsInvalid(CURRENT_TIME.minusMillis(1), CURRENT_TIME); } diff --git a/src/test/java/de/vorb/platon/security/SignatureComponentsTest.java b/src/test/java/de/vorb/platon/security/SignatureComponentsTest.java index 2cc829b..86c502d 100644 --- a/src/test/java/de/vorb/platon/security/SignatureComponentsTest.java +++ b/src/test/java/de/vorb/platon/security/SignatureComponentsTest.java @@ -40,7 +40,7 @@ public class SignatureComponentsTest { encodeSignatureToken(); @Test - public void fromStringParsesComponents() throws Exception { + public void fromStringParsesComponents() { final SignatureComponents signatureComponents = SignatureComponents.fromString(SAMPLE_SIGNATURE); @@ -50,7 +50,7 @@ public void fromStringParsesComponents() throws Exception { } @Test - public void toStringConcatenatesComponentsUsingPipe() throws Exception { + public void toStringConcatenatesComponentsUsingPipe() { final SignatureComponents signatureComponents = SignatureComponents.fromString(SAMPLE_SIGNATURE); @@ -60,7 +60,7 @@ public void toStringConcatenatesComponentsUsingPipe() throws Exception { } @Test - public void throwsIllegalArgumentExceptionWhenComponentCountInvalid() throws Exception { + public void throwsIllegalArgumentExceptionWhenComponentCountInvalid() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> SignatureComponents.fromString("a|b")); @@ -70,7 +70,7 @@ public void throwsIllegalArgumentExceptionWhenComponentCountInvalid() throws Exc } @Test - public void throwsDateExceptionWhenDateIsNotParsable() throws Exception { + public void throwsDateExceptionWhenDateIsNotParsable() { assertThatExceptionOfType(DateTimeParseException.class) .isThrownBy(() -> diff --git a/src/test/java/de/vorb/platon/security/SignatureCreatorTest.java b/src/test/java/de/vorb/platon/security/SignatureCreatorTest.java index 2e6be4a..41bb56f 100644 --- a/src/test/java/de/vorb/platon/security/SignatureCreatorTest.java +++ b/src/test/java/de/vorb/platon/security/SignatureCreatorTest.java @@ -20,13 +20,13 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.time.Instant; import static java.time.temporal.ChronoUnit.HOURS; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @@ -39,7 +39,7 @@ public class SignatureCreatorTest { private SignatureTokenValidator signatureTokenValidator; @Test - public void usesSignatureTokenToCreateSignatureComponents() throws Exception { + public void usesSignatureTokenToCreateSignatureComponents() { final String commentUri = "/api/comments/1234"; final Instant expirationTime = Instant.now().plus(3, HOURS); diff --git a/src/test/java/de/vorb/platon/util/ByteArrayConverterTest.java b/src/test/java/de/vorb/platon/util/ByteArrayConverterTest.java index fd77e33..3e409b4 100644 --- a/src/test/java/de/vorb/platon/util/ByteArrayConverterTest.java +++ b/src/test/java/de/vorb/platon/util/ByteArrayConverterTest.java @@ -28,17 +28,17 @@ public class ByteArrayConverterTest { private static final String HEX_STRING = "ff00a3"; @Test - public void shortBytesToHexString() throws Exception { + public void shortBytesToHexString() { assertThat(ByteArrayConverter.bytesToHexString(BYTES)).isEqualTo(HEX_STRING); } @Test - public void shortHexStringToBytes() throws Exception { + public void shortHexStringToBytes() { assertThat(ByteArrayConverter.hexStringToBytes(HEX_STRING)).isEqualTo(BYTES); } @Test(expected = IllegalArgumentException.class) - public void unevenLengthHexToBytes() throws Exception { + public void unevenLengthHexToBytes() { ByteArrayConverter.hexStringToBytes("ff00a"); } } diff --git a/src/test/java/de/vorb/platon/web/api/ApiConfigIT.java b/src/test/java/de/vorb/platon/web/api/ApiConfigIT.java index e718bac..cc8d8e7 100644 --- a/src/test/java/de/vorb/platon/web/api/ApiConfigIT.java +++ b/src/test/java/de/vorb/platon/web/api/ApiConfigIT.java @@ -37,7 +37,7 @@ public class ApiConfigIT { private ObjectMapper objectMapper; @Test - public void writeDatesAsTimestampsIsDisabled() throws Exception { + public void writeDatesAsTimestampsIsDisabled() { assertThatSerializationFeatureIsDisabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } diff --git a/src/test/java/de/vorb/platon/web/api/ApiConfigTest.java b/src/test/java/de/vorb/platon/web/api/ApiConfigTest.java index 3d20148..4a26ad0 100644 --- a/src/test/java/de/vorb/platon/web/api/ApiConfigTest.java +++ b/src/test/java/de/vorb/platon/web/api/ApiConfigTest.java @@ -24,7 +24,7 @@ import java.time.Instant; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -40,7 +40,7 @@ public void objectMapperWritesInstantsInIsoFormat() throws Exception { } @Test - public void addsPoweredByReponseInterceptor() throws Exception { + public void addsPoweredByResponseInterceptor() { final InterceptorRegistry interceptorRegistry = mock(InterceptorRegistry.class); apiConfig.addInterceptors(interceptorRegistry); verify(interceptorRegistry).addInterceptor(any(PoweredByResponseInterceptor.class)); diff --git a/src/test/java/de/vorb/platon/web/api/RequestExceptionVerifier.java b/src/test/java/de/vorb/platon/web/api/RequestExceptionVerifier.java index 10672ea..0ef8a13 100644 --- a/src/test/java/de/vorb/platon/web/api/RequestExceptionVerifier.java +++ b/src/test/java/de/vorb/platon/web/api/RequestExceptionVerifier.java @@ -23,7 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class RequestExceptionVerifier { +class RequestExceptionVerifier { public static void assertRequestExceptionWithStatus(HttpStatus expectedStatus, Runnable task) { try { diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java index ba0f549..9896ffc 100644 --- a/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java @@ -22,7 +22,6 @@ import org.junit.Test; -import java.sql.Timestamp; import java.time.Instant; import java.util.Base64; import java.util.Optional; @@ -34,7 +33,7 @@ public class CommentConverterTest { private final CommentConverter commentConverter = new CommentConverter(); @Test - public void convertsRegularPojoToEquivalentJson() throws Exception { + public void convertsRegularPojoToEquivalentJson() { final Comment comment = prepareComment() .setStatus(CommentStatus.PUBLIC); @@ -46,7 +45,7 @@ public void convertsRegularPojoToEquivalentJson() throws Exception { } @Test - public void convertsMinimalPojoToEquivalentJson() throws Exception { + public void convertsMinimalPojoToEquivalentJson() { final Comment comment = prepareComment() .setCreationDate(null) @@ -63,7 +62,7 @@ public void convertsMinimalPojoToEquivalentJson() throws Exception { } @Test - public void convertsDeletedPojoToJsonWithoutContent() throws Exception { + public void convertsDeletedPojoToJsonWithoutContent() { final Comment comment = prepareComment() .setStatus(CommentStatus.DELETED); @@ -126,7 +125,7 @@ private Comment prepareComment() { } @Test - public void convertsJsonToEquivalentPojo() throws Exception { + public void convertsJsonToEquivalentPojo() { final CommentJson json = prepareJson().build(); @@ -144,7 +143,7 @@ public void convertsJsonToEquivalentPojo() throws Exception { } @Test - public void convertsMinimalJsonToEquivalentPojo() throws Exception { + public void convertsMinimalJsonToEquivalentPojo() { final CommentJson json = prepareJson() .parentId(null) @@ -170,7 +169,7 @@ public void convertsMinimalJsonToEquivalentPojo() throws Exception { } @Test - public void acceptsJsonWithMissingEmailAddress() throws Exception { + public void acceptsJsonWithMissingEmailAddress() { final CommentJson json = prepareJson() .email(null) diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentFiltersTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentFiltersTest.java index 9d24bff..d0d9826 100644 --- a/src/test/java/de/vorb/platon/web/api/common/CommentFiltersTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/CommentFiltersTest.java @@ -31,7 +31,7 @@ public class CommentFiltersTest { private final CommentFilters commentFilters = new CommentFilters(); @Test - public void doesCommentCount() throws Exception { + public void doesCommentCount() { assertThat(commentFilters.doesCommentCount(commentWithStatus(DELETED))).isFalse(); assertThat(commentFilters.doesCommentCount(commentWithStatus(PUBLIC))).isTrue(); assertThat(commentFilters.doesCommentCount(commentWithStatus(AWAITING_MODERATION))).isFalse(); diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java index 64f8824..6172230 100644 --- a/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java @@ -22,10 +22,10 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,10 +38,10 @@ public class CommentSanitizerTest { @Mock private InputSanitizer inputSanitizer; - private Comment comment = new Comment(); + private final Comment comment = new Comment(); @Test - public void removesHtmlInAuthorName() throws Exception { + public void removesHtmlInAuthorName() { comment.setAuthor("Jane Doe "); @@ -51,7 +51,7 @@ public void removesHtmlInAuthorName() throws Exception { } @Test - public void sanitizesAndEncodesUrls() throws Exception { + public void sanitizesAndEncodesUrls() { assertThatUrlIsAccepted("http://example.org/article.html"); assertThatUrlIsAccepted("https://example.org/secure/profile.php"); @@ -74,20 +74,20 @@ private void assertThatUrlIsNotAccepted(String url) { } @Test - public void encodesSpecialCharactersInUrls() throws Exception { + public void encodesSpecialCharactersInUrls() { sanitizeCommentWithUrl("https://example.org/a url with spaces"); assertThat(comment.getUrl()).isEqualTo("https://example.org/a%20url%20with%20spaces"); } @Test - public void doesNotDoubleEncodeEscapedCharacters() throws Exception { + public void doesNotDoubleEncodeEscapedCharacters() { final String url = "https://example.org/a%20url%20with%20spaces"; sanitizeCommentWithUrl(url); assertThat(comment.getUrl()).isEqualTo(url); } @Test - public void prependsHttpsToUrlsWithMissingScheme() throws Exception { + public void prependsHttpsToUrlsWithMissingScheme() { sanitizeCommentWithUrl("www.example.org"); assertThat(comment.getUrl()).isEqualTo("https://www.example.org"); } @@ -98,7 +98,7 @@ private void sanitizeCommentWithUrl(String url) { } @Test - public void sanitizesCommentTextUsingInputSanitizer() throws Exception { + public void sanitizesCommentTextUsingInputSanitizer() { final String text = "Text"; final String sanitizedText = "Sanitized text"; diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentUriResolverTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentUriResolverTest.java index 29bd983..473e1c2 100644 --- a/src/test/java/de/vorb/platon/web/api/common/CommentUriResolverTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/CommentUriResolverTest.java @@ -29,7 +29,7 @@ import static java.util.Collections.enumeration; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/src/test/java/de/vorb/platon/web/api/common/PoweredByResponseInterceptorTest.java b/src/test/java/de/vorb/platon/web/api/common/PoweredByResponseInterceptorTest.java index a956667..c084f2d 100644 --- a/src/test/java/de/vorb/platon/web/api/common/PoweredByResponseInterceptorTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/PoweredByResponseInterceptorTest.java @@ -23,18 +23,18 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; public class PoweredByResponseInterceptorTest { - private PoweredByResponseInterceptor poweredByResponseInterceptor = new PoweredByResponseInterceptor(); + private final PoweredByResponseInterceptor poweredByResponseInterceptor = new PoweredByResponseInterceptor(); private HttpServletResponse response; @Before - public void setUp() throws Exception { + public void setUp() { response = Mockito.spy(HttpServletResponse.class); } diff --git a/src/test/java/de/vorb/platon/web/api/common/RequestValidatorTest.java b/src/test/java/de/vorb/platon/web/api/common/RequestValidatorTest.java index 27b4ecf..7f3b032 100644 --- a/src/test/java/de/vorb/platon/web/api/common/RequestValidatorTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/RequestValidatorTest.java @@ -24,7 +24,7 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.springframework.http.HttpStatus; import java.time.Instant; @@ -32,7 +32,7 @@ import static java.time.temporal.ChronoUnit.DAYS; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -46,7 +46,7 @@ public class RequestValidatorTest { private SignatureTokenValidator signatureTokenValidator; @Test - public void doesNotThrowIfRequestWasValid() throws Exception { + public void doesNotThrowIfRequestWasValid() { final String identifier = "/api/comments/1234"; final Instant expirationTime = Instant.now().plus(1, DAYS); @@ -61,7 +61,7 @@ public void doesNotThrowIfRequestWasValid() throws Exception { } @Test - public void throwsBadRequestIfRequestWasInvalid() throws Exception { + public void throwsBadRequestIfRequestWasInvalid() { assertThatExceptionOfType(RequestException.class) .isThrownBy(() -> requestValidator.verifyValidRequest("invalid signature", "/api/comments/1234")) diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerDeleteTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerDeleteTest.java index 920ddb8..dd81f36 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerDeleteTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerDeleteTest.java @@ -24,7 +24,7 @@ import static de.vorb.platon.model.CommentStatus.DELETED; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.springframework.http.HttpStatus.BAD_REQUEST; @@ -32,7 +32,7 @@ public class CommentControllerDeleteTest extends CommentControllerTest { @Test - public void setsCommentStatusToDeletedIfRequestIsValid() throws Exception { + public void setsCommentStatusToDeletedIfRequestIsValid() { final long commentId = 1234; final String signature = "signature"; @@ -44,7 +44,7 @@ public void setsCommentStatusToDeletedIfRequestIsValid() throws Exception { } @Test - public void throwsBadRequestIfErrorOccursDuringSaving() throws Exception { + public void throwsBadRequestIfErrorOccursDuringSaving() { final long commentId = 1234; final String signature = "signature"; diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerFindByThreadUrlTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerFindByThreadUrlTest.java index 971e482..0d7661b 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerFindByThreadUrlTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerFindByThreadUrlTest.java @@ -32,8 +32,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,7 +45,7 @@ public class CommentControllerFindByThreadUrlTest extends CommentControllerTest private CommentJson childCommentJson; @Test - public void returnsCommentsAsTree() throws Exception { + public void returnsCommentsAsTree() { final Comment comment = new Comment().setId(4711L); final Comment childComment = new Comment().setId(4712L).setParentId(comment.getId()); @@ -76,7 +76,7 @@ private void acceptAllComments() { } @Test - public void throwsNotFoundIfThreadIsEmpty() throws Exception { + public void throwsNotFoundIfThreadIsEmpty() { when(commentRepository.findByThreadUrl(any())).thenReturn(Collections.emptyList()); diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java index cd47633..1ee5993 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java @@ -30,7 +30,7 @@ import static de.vorb.platon.model.CommentStatus.PUBLIC; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; public class CommentControllerGetByIdTest extends CommentControllerTest { @@ -39,7 +39,7 @@ public class CommentControllerGetByIdTest extends CommentControllerTest { private CommentJson commentJson; @Test - public void returnsPublicComment() throws Exception { + public void returnsPublicComment() { final long commentId = 4711; final Comment publicComment = @@ -55,7 +55,7 @@ public void returnsPublicComment() throws Exception { } @Test - public void throwsNotFoundForDeletedComment() throws Exception { + public void throwsNotFoundForDeletedComment() { final long commentId = 1337; final Comment deletedComment = diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java index d959f37..8ce5ce3 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java @@ -34,7 +34,7 @@ import java.time.Instant; import java.util.Optional; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -67,7 +67,7 @@ public class CommentControllerIT { private CommentRepository commentRepository; @Before - public void setUp() throws Exception { + public void setUp() { when(commentRepository.findById(eq(SAMPLE_COMMENT.getId()))).thenReturn(Optional.of(SAMPLE_COMMENT)); } diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerPostTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerPostTest.java index f18d226..65564c9 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerPostTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerPostTest.java @@ -26,7 +26,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -37,15 +37,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.http.HttpStatus.BAD_REQUEST; -@RunWith(MockitoJUnitRunner.class) +@RunWith(MockitoJUnitRunner.Silent.class) public class CommentControllerPostTest extends CommentControllerTest { private static final AtomicLong COMMENT_ID_SEQUENCE = new AtomicLong(); @@ -59,7 +59,7 @@ public class CommentControllerPostTest extends CommentControllerTest { private Comment parentComment = new Comment(); @Test - public void createsNewThreadOnDemand() throws Exception { + public void createsNewThreadOnDemand() { insertCommentReturnsCommentWithNextId(); prepareMocksForPostRequest(); @@ -74,7 +74,7 @@ public void createsNewThreadOnDemand() throws Exception { } @Test - public void verifiesParentBelongsToSameThread() throws Exception { + public void verifiesParentBelongsToSameThread() { insertCommentReturnsCommentWithNextId(); prepareMocksForPostRequest(); @@ -101,7 +101,7 @@ public void verifiesParentBelongsToSameThread() throws Exception { } @Test - public void verifiesParentCommentExists() throws Exception { + public void verifiesParentCommentExists() { insertCommentReturnsCommentWithNextId(); prepareMocksForPostRequest(); @@ -137,7 +137,7 @@ private void assertThatLocationHeaderIsCorrect(URI location) { } @Test - public void postCommentWithIdThrowsBadRequestException() throws Exception { + public void postCommentWithIdThrowsBadRequestException() { when(commentJson.getId()).thenReturn(1337L); assertThatExceptionOfType(RequestException.class) @@ -149,7 +149,7 @@ public void postCommentWithIdThrowsBadRequestException() throws Exception { private void insertCommentReturnsCommentWithNextId() { when(commentRepository.insert(any())) .then(invocation -> { - final Comment insertedComment = invocation.getArgumentAt(0, Comment.class); + final Comment insertedComment = invocation.getArgument(0); final long nextCommentId = COMMENT_ID_SEQUENCE.incrementAndGet(); return insertedComment.setId(nextCommentId); }); diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java index c7e623d..ce24553 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java @@ -30,16 +30,16 @@ import org.junit.Before; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.net.URI; import java.time.Clock; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) +@RunWith(MockitoJUnitRunner.Silent.class) @SuppressWarnings("WeakerAccess") public abstract class CommentControllerTest { @@ -74,7 +74,7 @@ public void setUp() throws Exception { commentConverter, commentUriResolver, requestValidator, commentFilters, commentSanitizer); when(commentUriResolver.createRelativeCommentUriForId(anyLong())) - .thenAnswer(invocation -> new URI("/api/comments/" + invocation.getArgumentAt(0, long.class))); + .thenAnswer(invocation -> new URI("/api/comments/" + invocation.getArgument(0))); } protected void convertCommentToJson(Comment comment, CommentJson json) { diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java index 9420fa3..745249f 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java @@ -34,8 +34,8 @@ import java.util.Optional; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -72,7 +72,7 @@ public void setUp() throws Exception { } @Test - public void comparesCommentIds() throws Exception { + public void comparesCommentIds() { assertThatExceptionOfType(RequestException.class) .isThrownBy(() -> commentController.updateComment( @@ -82,7 +82,7 @@ public void comparesCommentIds() throws Exception { } @Test - public void updatesCommentIfAllChecksPass() throws Exception { + public void updatesCommentIfAllChecksPass() { when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.of(comment)); @@ -100,7 +100,7 @@ public void updatesCommentIfAllChecksPass() throws Exception { } @Test - public void throwsBadRequestIfCommentDoesNotExist() throws Exception { + public void throwsBadRequestIfCommentDoesNotExist() { when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.empty()); @@ -111,7 +111,7 @@ public void throwsBadRequestIfCommentDoesNotExist() throws Exception { } @Test - public void doesNotUpdateCommentIfRequestValidationFails() throws Exception { + public void doesNotUpdateCommentIfRequestValidationFails() { doThrow(RequestException.class) .when(requestValidator) @@ -125,7 +125,7 @@ public void doesNotUpdateCommentIfRequestValidationFails() throws Exception { } @Test - public void throwsConflictExceptionWhenUpdateFails() throws Exception { + public void throwsConflictExceptionWhenUpdateFails() { when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.of(comment)); doThrow(DataAccessException.class).when(commentRepository).update(any()); diff --git a/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionHandlerTest.java b/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionHandlerTest.java index cc7b069..ee7bcb0 100644 --- a/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionHandlerTest.java +++ b/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionHandlerTest.java @@ -25,7 +25,7 @@ public class RequestExceptionHandlerTest { private final RequestExceptionHandler requestExceptionHandler = new RequestExceptionHandler(); @Test - public void handlesRequestException() throws Exception { + public void handlesRequestException() { final RequestException requestException = RequestException.badRequest().build(); diff --git a/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionTest.java b/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionTest.java index 12beb47..cd2b1fb 100644 --- a/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionTest.java +++ b/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionTest.java @@ -28,7 +28,7 @@ public class RequestExceptionTest { @Test - public void acceptOnlyStatusCodesGreaterThan400() throws Exception { + public void acceptOnlyStatusCodesGreaterThan400() { assertThatIllegalArgumentException().isThrownBy(creatingResultException(100)); assertThatIllegalArgumentException().isThrownBy(creatingResultException(200)); @@ -42,12 +42,12 @@ public void acceptOnlyStatusCodesGreaterThan400() throws Exception { } @Test - public void returnsOriginalStatus() throws Exception { + public void returnsOriginalStatus() { assertThat(RequestException.withStatus(400).build().getStatus()).isEqualTo(400); } @Test - public void convertsToJson() throws Exception { + public void convertsToJson() { final String notFoundMessage = "Not Found"; final RequestException notFound = @@ -70,7 +70,7 @@ public void convertsToJson() throws Exception { } @Test - public void toResponseEntity() throws Exception { + public void toResponseEntity() { final RequestException requestException = RequestException.badRequest().build(); final ResponseEntity response = requestException.toResponseEntity(); @@ -80,27 +80,27 @@ public void toResponseEntity() throws Exception { } @Test - public void badRequest() throws Exception { + public void badRequest() { assertThat(RequestException.badRequest().build().getHttpStatus()).isEqualTo(HttpStatus.BAD_REQUEST); } @Test - public void unauthorized() throws Exception { + public void unauthorized() { assertThat(RequestException.unauthorized().build().getHttpStatus()).isEqualTo(HttpStatus.UNAUTHORIZED); } @Test - public void forbidden() throws Exception { + public void forbidden() { assertThat(RequestException.forbidden().build().getHttpStatus()).isEqualTo(HttpStatus.FORBIDDEN); } @Test - public void notFound() throws Exception { + public void notFound() { assertThat(RequestException.notFound().build().getHttpStatus()).isEqualTo(HttpStatus.NOT_FOUND); } @Test - public void internalServerError() throws Exception { + public void internalServerError() { assertThat(RequestException.internalServerError().build().getHttpStatus()) .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); } diff --git a/src/test/java/de/vorb/platon/web/api/json/ByteArraySerializerTest.java b/src/test/java/de/vorb/platon/web/api/json/ByteArraySerializerTest.java index 7d6543c..2bbf327 100644 --- a/src/test/java/de/vorb/platon/web/api/json/ByteArraySerializerTest.java +++ b/src/test/java/de/vorb/platon/web/api/json/ByteArraySerializerTest.java @@ -35,7 +35,7 @@ public class ByteArraySerializerTest { private SerializerProvider serializerProvider; @Before - public void setUp() throws Exception { + public void setUp() { MockitoAnnotations.initMocks(this); } diff --git a/src/test/java/de/vorb/platon/web/mvc/IndexControllerTest.java b/src/test/java/de/vorb/platon/web/mvc/IndexControllerTest.java index 68935da..695a4fd 100644 --- a/src/test/java/de/vorb/platon/web/mvc/IndexControllerTest.java +++ b/src/test/java/de/vorb/platon/web/mvc/IndexControllerTest.java @@ -25,7 +25,7 @@ public class IndexControllerTest { private final IndexController indexController = new IndexController(); @Test - public void getIndex() throws Exception { + public void getIndex() { assertThat(indexController.getIndex()).isEqualTo("index"); } From bbde5ed565ee81c81787589c666a8fd79cd050fc Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Tue, 26 Jun 2018 23:45:53 +0200 Subject: [PATCH 03/16] Throw away JavaScript --- .gitignore | 8 +- pom.xml | 82 +-- .../platon/persistence/CommentRepository.java | 4 +- .../platon/persistence/ThreadRepository.java | 11 +- .../conversion/InstantConverter.java | 46 -- .../impl/JooqCommentRepository.java | 18 +- .../impl/JooqPropertyRepository.java | 4 +- .../impl/JooqThreadRepository.java | 43 +- .../persistence/jooq/DefaultCatalog.java | 60 ++ .../vorb/platon/persistence/jooq/Indexes.java | 60 ++ .../de/vorb/platon/persistence/jooq/Keys.java | 77 +++ .../vorb/platon/persistence/jooq/Public.java | 100 +++ .../platon/persistence/jooq/Sequences.java | 35 ++ .../vorb/platon/persistence/jooq/Tables.java | 41 ++ .../persistence/jooq/tables/Comment.java | 219 +++++++ .../jooq/tables/CommentThread.java | 172 +++++ .../persistence/jooq/tables/Property.java | 158 +++++ .../jooq/tables/pojos/Comment.java | 188 ++++++ .../jooq/tables/pojos/CommentThread.java | 87 +++ .../jooq/tables/pojos/Property.java | 73 +++ .../jooq/tables/records/CommentRecord.java | 594 ++++++++++++++++++ .../tables/records/CommentThreadRecord.java | 241 +++++++ .../jooq/tables/records/PropertyRecord.java | 191 ++++++ .../security/DatabaseSecretKeyProvider.java | 2 + .../security/HmacSignatureTokenValidator.java | 4 - .../vorb/platon/security/SecurityConfig.java | 12 +- .../services/feedreader/FeedReader.java | 90 +++ .../feedreader/FeedReaderConfiguration.java | 46 ++ .../feedreader/FeedReaderProperties.java | 22 + .../web/api/common/CommentConverter.java | 2 +- .../platon/web/api/common/CommentFilters.java | 2 +- .../web/api/common/CommentSanitizer.java | 2 +- .../web/api/controllers/CommentAction.java | 8 + .../api/controllers/CommentController.java | 393 ++++++------ .../vorb/platon/web/api/errors/ErrorCode.java | 21 - .../web/api/errors/RequestException.java | 13 +- .../api/errors/RequestExceptionHandler.java | 10 +- .../web/api/errors/RequestExceptionJson.java | 30 - .../vorb/platon/web/api/json/CommentJson.java | 6 +- src/main/resources/application.yml | 33 + .../resources/config/application.properties | 11 - .../db/migration/V0.1.0__initial_schema.sql | 36 -- .../V0.2.0__persistent_secret_key.sql | 4 - .../V0.2.1__singular_table_names.sql | 3 - .../V0.2.2018.06.11__initial_schema.sql | 35 ++ src/main/resources/templates/comment-form.ftl | 77 +++ .../resources/templates/comments-flat.ftl | 28 + src/main/resources/templates/error.ftl | 22 + .../resources/templates/snippets/base.ftl | 58 ++ .../resources/templates/snippets/comment.ftl | 47 ++ .../web/api/common/CommentConverterTest.java | 12 +- .../web/api/common/CommentFiltersTest.java | 2 +- .../web/api/common/CommentSanitizerTest.java | 2 +- .../CommentControllerDeleteTest.java | 52 +- .../CommentControllerFindByThreadUrlTest.java | 63 +- .../CommentControllerGetByIdTest.java | 2 +- .../api/controllers/CommentControllerIT.java | 12 +- .../CommentControllerPostTest.java | 144 ++--- .../controllers/CommentControllerTest.java | 21 +- .../CommentControllerUpdateTest.java | 134 ++-- .../errors/RequestExceptionHandlerTest.java | 35 -- .../web/api/errors/RequestExceptionTest.java | 34 - 62 files changed, 3319 insertions(+), 723 deletions(-) delete mode 100644 src/main/java/de/vorb/platon/persistence/conversion/InstantConverter.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/DefaultCatalog.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/Indexes.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/Keys.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/Public.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/Sequences.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/Tables.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/tables/CommentThread.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/tables/Property.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/CommentThread.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Property.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentThreadRecord.java create mode 100644 src/main/java/de/vorb/platon/persistence/jooq/tables/records/PropertyRecord.java create mode 100644 src/main/java/de/vorb/platon/services/feedreader/FeedReader.java create mode 100644 src/main/java/de/vorb/platon/services/feedreader/FeedReaderConfiguration.java create mode 100644 src/main/java/de/vorb/platon/services/feedreader/FeedReaderProperties.java create mode 100644 src/main/java/de/vorb/platon/web/api/controllers/CommentAction.java delete mode 100644 src/main/java/de/vorb/platon/web/api/errors/ErrorCode.java delete mode 100644 src/main/java/de/vorb/platon/web/api/errors/RequestExceptionJson.java create mode 100644 src/main/resources/application.yml delete mode 100644 src/main/resources/config/application.properties delete mode 100644 src/main/resources/db/migration/V0.1.0__initial_schema.sql delete mode 100644 src/main/resources/db/migration/V0.2.0__persistent_secret_key.sql delete mode 100644 src/main/resources/db/migration/V0.2.1__singular_table_names.sql create mode 100644 src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql create mode 100644 src/main/resources/templates/comment-form.ftl create mode 100644 src/main/resources/templates/comments-flat.ftl create mode 100644 src/main/resources/templates/error.ftl create mode 100644 src/main/resources/templates/snippets/base.ftl create mode 100644 src/main/resources/templates/snippets/comment.ftl delete mode 100644 src/test/java/de/vorb/platon/web/api/errors/RequestExceptionHandlerTest.java diff --git a/.gitignore b/.gitignore index 55511af..0933604 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,7 @@ /target/ /.idea/ /*.iml -/*.db *.log -/node/ -/node_modules/ - -/src/main/webapp/js/ - -/application.properties +/config/ diff --git a/pom.xml b/pom.xml index 5e29ca5..256189d 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ org.springframework.boot spring-boot-starter-parent - 2.0.2.RELEASE + 2.0.3.RELEASE @@ -51,6 +51,8 @@ 1.10 20180219.1 1.2 + 1.10.0 + 4.1.1 0.8.4 @@ -62,8 +64,9 @@ false - jdbc:h2:file:./platon - sa + jdbc:postgresql://localhost:5432/platon_jooq_codegen + postgres + postgres @@ -86,6 +89,11 @@ org.springframework.boot spring-boot-starter-freemarker + + org.springframework.boot + spring-boot-configuration-processor + true + com.google.guava @@ -95,17 +103,19 @@ org.apache.commons commons-lang3 - ${commons-lang3.version} commons-codec commons-codec + + org.apache.httpcomponents + httpclient + com.fasterxml.jackson.datatype jackson-datatype-jsr310 - ${jackson.version} com.googlecode.owasp-java-html-sanitizer @@ -123,19 +133,29 @@ spring-boot-starter-jooq - com.h2database - h2 - ${h2.version} + org.flywaydb + flyway-core org.postgresql postgresql - ${postgresql.version} + - org.flywaydb - flyway-core - ${flyway.version} + com.rometools + rome + ${rome.version} + + + + io.micrometer + micrometer-registry-prometheus + + + + org.webjars + bootstrap + ${bootstrap.version} @@ -273,6 +293,7 @@ ${db.url} ${db.username} + ${db.password}
@@ -291,33 +312,29 @@ - com.h2database - h2 - ${h2.version} + org.postgresql + postgresql + ${postgresql.version} - org.h2.Driver + org.postgresql.Driver ${db.url} ${db.username} + ${db.password} - org.jooq.util.h2.H2Database + org.jooq.util.postgres.PostgresDatabase + public + public\.flyway_schema_history .* - schema_version - PUBLIC - - java.time.Instant - de.vorb.platon.persistence.conversion.InstantConverter - TIMESTAMP - de.vorb.platon.model.CommentStatus de.vorb.platon.persistence.conversion.CommentStatusConverter @@ -327,25 +344,14 @@ - de.vorb.platon.jooq - target/generated-sources/jooq + de.vorb.platon.persistence.jooq + ${project.build.sourceDirectory} - true - false - true - true true true - true - false - false - false - false - true - false - true + true true diff --git a/src/main/java/de/vorb/platon/persistence/CommentRepository.java b/src/main/java/de/vorb/platon/persistence/CommentRepository.java index d27c13d..203aece 100644 --- a/src/main/java/de/vorb/platon/persistence/CommentRepository.java +++ b/src/main/java/de/vorb/platon/persistence/CommentRepository.java @@ -16,7 +16,7 @@ package de.vorb.platon.persistence; -import de.vorb.platon.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.model.CommentStatus; import java.util.List; @@ -26,7 +26,7 @@ public interface CommentRepository { - List findByThreadUrl(String threadUrl); + List findByThreadId(long threadId); Optional findById(long id); diff --git a/src/main/java/de/vorb/platon/persistence/ThreadRepository.java b/src/main/java/de/vorb/platon/persistence/ThreadRepository.java index 0b15343..350079e 100644 --- a/src/main/java/de/vorb/platon/persistence/ThreadRepository.java +++ b/src/main/java/de/vorb/platon/persistence/ThreadRepository.java @@ -16,14 +16,23 @@ package de.vorb.platon.persistence; -import de.vorb.platon.jooq.tables.pojos.CommentThread; +import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; +import java.util.List; import java.util.Optional; public interface ThreadRepository { + CommentThread getById(long id); + Optional findThreadIdForUrl(String threadUrl); + Optional findThreadForUrl(String threadUrl); + + List findThreadsForUrlPrefix(String threadUrlPrefix); + CommentThread insert(CommentThread thread); + void updateThreadTitle(long id, String newTitle); + } diff --git a/src/main/java/de/vorb/platon/persistence/conversion/InstantConverter.java b/src/main/java/de/vorb/platon/persistence/conversion/InstantConverter.java deleted file mode 100644 index 4e663f8..0000000 --- a/src/main/java/de/vorb/platon/persistence/conversion/InstantConverter.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.persistence.conversion; - -import org.jooq.Converter; - -import java.sql.Timestamp; -import java.time.Instant; - -public class InstantConverter implements Converter { - - @Override - public Instant from(Timestamp databaseObject) { - return databaseObject == null ? null : databaseObject.toInstant(); - } - - @Override - public Timestamp to(Instant userObject) { - return userObject == null ? null : Timestamp.from(userObject); - } - - @Override - public Class fromType() { - return Timestamp.class; - } - - @Override - public Class toType() { - return Instant.class; - } - -} diff --git a/src/main/java/de/vorb/platon/persistence/impl/JooqCommentRepository.java b/src/main/java/de/vorb/platon/persistence/impl/JooqCommentRepository.java index 9a72217..d0d7fde 100644 --- a/src/main/java/de/vorb/platon/persistence/impl/JooqCommentRepository.java +++ b/src/main/java/de/vorb/platon/persistence/impl/JooqCommentRepository.java @@ -16,10 +16,10 @@ package de.vorb.platon.persistence.impl; -import de.vorb.platon.jooq.tables.pojos.Comment; -import de.vorb.platon.jooq.tables.records.CommentRecord; import de.vorb.platon.model.CommentStatus; import de.vorb.platon.persistence.CommentRepository; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.records.CommentRecord; import lombok.RequiredArgsConstructor; import org.jooq.DSLContext; @@ -31,8 +31,8 @@ import java.util.Optional; import java.util.Set; -import static de.vorb.platon.jooq.Tables.COMMENT; -import static de.vorb.platon.jooq.Tables.COMMENT_THREAD; +import static de.vorb.platon.persistence.jooq.Tables.COMMENT; +import static de.vorb.platon.persistence.jooq.Tables.COMMENT_THREAD; import static org.jooq.impl.DSL.count; @Repository @@ -42,11 +42,11 @@ public class JooqCommentRepository implements CommentRepository { private final DSLContext dslContext; @Override - public List findByThreadUrl(String threadUrl) { - return dslContext - .selectFrom(COMMENT - .join(COMMENT_THREAD).on(COMMENT.THREAD_ID.eq(COMMENT_THREAD.ID))) - .where(COMMENT_THREAD.URL.eq(threadUrl)) + public List findByThreadId(long threadId) { + return dslContext.select(COMMENT.fields()) + .from(COMMENT) + .where(COMMENT.THREAD_ID.eq(threadId)) + .orderBy(COMMENT.ID.asc()) .fetchInto(Comment.class); } diff --git a/src/main/java/de/vorb/platon/persistence/impl/JooqPropertyRepository.java b/src/main/java/de/vorb/platon/persistence/impl/JooqPropertyRepository.java index 074afd3..29f1c2c 100644 --- a/src/main/java/de/vorb/platon/persistence/impl/JooqPropertyRepository.java +++ b/src/main/java/de/vorb/platon/persistence/impl/JooqPropertyRepository.java @@ -16,14 +16,14 @@ package de.vorb.platon.persistence.impl; -import de.vorb.platon.jooq.tables.records.PropertyRecord; +import de.vorb.platon.persistence.jooq.tables.records.PropertyRecord; import de.vorb.platon.persistence.PropertyRepository; import lombok.RequiredArgsConstructor; import org.jooq.DSLContext; import org.springframework.stereotype.Repository; -import static de.vorb.platon.jooq.tables.Property.PROPERTY; +import static de.vorb.platon.persistence.jooq.tables.Property.PROPERTY; @Repository @RequiredArgsConstructor diff --git a/src/main/java/de/vorb/platon/persistence/impl/JooqThreadRepository.java b/src/main/java/de/vorb/platon/persistence/impl/JooqThreadRepository.java index d60b6c4..9c5a4c9 100644 --- a/src/main/java/de/vorb/platon/persistence/impl/JooqThreadRepository.java +++ b/src/main/java/de/vorb/platon/persistence/impl/JooqThreadRepository.java @@ -16,17 +16,18 @@ package de.vorb.platon.persistence.impl; -import de.vorb.platon.jooq.tables.pojos.CommentThread; -import de.vorb.platon.jooq.tables.records.CommentThreadRecord; import de.vorb.platon.persistence.ThreadRepository; +import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; +import de.vorb.platon.persistence.jooq.tables.records.CommentThreadRecord; import lombok.RequiredArgsConstructor; import org.jooq.DSLContext; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; -import static de.vorb.platon.jooq.Tables.COMMENT_THREAD; +import static de.vorb.platon.persistence.jooq.Tables.COMMENT_THREAD; @Repository @RequiredArgsConstructor @@ -34,12 +35,33 @@ public class JooqThreadRepository implements ThreadRepository { private final DSLContext dslContext; + @Override + public CommentThread getById(long id) { + return dslContext.selectFrom(COMMENT_THREAD) + .where(COMMENT_THREAD.ID.eq(id)) + .fetchSingleInto(CommentThread.class); + } + @Override public Optional findThreadIdForUrl(String threadUrl) { - return Optional.ofNullable( - dslContext.selectFrom(COMMENT_THREAD) - .where(COMMENT_THREAD.URL.eq(threadUrl)) - .fetchOne(COMMENT_THREAD.ID)); + return dslContext.selectFrom(COMMENT_THREAD) + .where(COMMENT_THREAD.URL.eq(threadUrl)) + .fetchOptional(COMMENT_THREAD.ID); + } + + @Override + public Optional findThreadForUrl(String threadUrl) { + return dslContext.selectFrom(COMMENT_THREAD) + .where(COMMENT_THREAD.URL.eq(threadUrl)) + .fetchOptionalInto(CommentThread.class); + } + + @Override + public List findThreadsForUrlPrefix(String threadUrlPrefix) { + return dslContext.selectFrom(COMMENT_THREAD) + .where(COMMENT_THREAD.URL.startsWith(threadUrlPrefix)) + .orderBy(COMMENT_THREAD.ID.desc()) + .fetchInto(CommentThread.class); } @Override @@ -55,4 +77,11 @@ private CommentThreadRecord convertPojoToRecord(CommentThread thread) { return dslContext.newRecord(COMMENT_THREAD, thread); } + @Override + public void updateThreadTitle(long id, String newTitle) { + dslContext.update(COMMENT_THREAD) + .set(COMMENT_THREAD.TITLE, newTitle) + .where(COMMENT_THREAD.ID.eq(id)); + } + } diff --git a/src/main/java/de/vorb/platon/persistence/jooq/DefaultCatalog.java b/src/main/java/de/vorb/platon/persistence/jooq/DefaultCatalog.java new file mode 100644 index 0000000..0132e92 --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/DefaultCatalog.java @@ -0,0 +1,60 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq; + + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Generated; + +import org.jooq.Schema; +import org.jooq.impl.CatalogImpl; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class DefaultCatalog extends CatalogImpl { + + private static final long serialVersionUID = -1545065016; + + /** + * The reference instance of + */ + public static final DefaultCatalog DEFAULT_CATALOG = new DefaultCatalog(); + + /** + * The schema public. + */ + public final Public PUBLIC = de.vorb.platon.persistence.jooq.Public.PUBLIC; + + /** + * No further instances allowed + */ + private DefaultCatalog() { + super(""); + } + + @Override + public final List getSchemas() { + List result = new ArrayList(); + result.addAll(getSchemas0()); + return result; + } + + private final List getSchemas0() { + return Arrays.asList( + Public.PUBLIC); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/Indexes.java b/src/main/java/de/vorb/platon/persistence/jooq/Indexes.java new file mode 100644 index 0000000..2eea302 --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/Indexes.java @@ -0,0 +1,60 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq; + + +import de.vorb.platon.persistence.jooq.tables.Comment; +import de.vorb.platon.persistence.jooq.tables.CommentThread; +import de.vorb.platon.persistence.jooq.tables.Property; + +import javax.annotation.Generated; + +import org.jooq.Index; +import org.jooq.OrderField; +import org.jooq.impl.Internal; + + +/** + * A class modelling indexes of tables of the public schema. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Indexes { + + // ------------------------------------------------------------------------- + // INDEX definitions + // ------------------------------------------------------------------------- + + public static final Index COMMENT__CREATION_DATE__IDX = Indexes0.COMMENT__CREATION_DATE__IDX; + public static final Index COMMENT__ID__IDX = Indexes0.COMMENT__ID__IDX; + public static final Index COMMENT__STATUS__IDX = Indexes0.COMMENT__STATUS__IDX; + public static final Index COMMENT__THREAD_ID__IDX = Indexes0.COMMENT__THREAD_ID__IDX; + public static final Index COMMENT_PKEY = Indexes0.COMMENT_PKEY; + public static final Index COMMENT_THREAD__ID__IDX = Indexes0.COMMENT_THREAD__ID__IDX; + public static final Index COMMENT_THREAD__URL__IDX = Indexes0.COMMENT_THREAD__URL__IDX; + public static final Index COMMENT_THREAD_PKEY = Indexes0.COMMENT_THREAD_PKEY; + public static final Index PROPERTY_PKEY = Indexes0.PROPERTY_PKEY; + + // ------------------------------------------------------------------------- + // [#1459] distribute members to avoid static initialisers > 64kb + // ------------------------------------------------------------------------- + + private static class Indexes0 { + public static Index COMMENT__CREATION_DATE__IDX = Internal.createIndex("comment__creation_date__idx", Comment.COMMENT, new OrderField[] { Comment.COMMENT.CREATION_DATE }, false); + public static Index COMMENT__ID__IDX = Internal.createIndex("comment__id__idx", Comment.COMMENT, new OrderField[] { Comment.COMMENT.ID }, false); + public static Index COMMENT__STATUS__IDX = Internal.createIndex("comment__status__idx", Comment.COMMENT, new OrderField[] { Comment.COMMENT.STATUS }, false); + public static Index COMMENT__THREAD_ID__IDX = Internal.createIndex("comment__thread_id__idx", Comment.COMMENT, new OrderField[] { Comment.COMMENT.THREAD_ID }, false); + public static Index COMMENT_PKEY = Internal.createIndex("comment_pkey", Comment.COMMENT, new OrderField[] { Comment.COMMENT.ID }, true); + public static Index COMMENT_THREAD__ID__IDX = Internal.createIndex("comment_thread__id__idx", CommentThread.COMMENT_THREAD, new OrderField[] { CommentThread.COMMENT_THREAD.ID }, false); + public static Index COMMENT_THREAD__URL__IDX = Internal.createIndex("comment_thread__url__idx", CommentThread.COMMENT_THREAD, new OrderField[] { CommentThread.COMMENT_THREAD.URL }, false); + public static Index COMMENT_THREAD_PKEY = Internal.createIndex("comment_thread_pkey", CommentThread.COMMENT_THREAD, new OrderField[] { CommentThread.COMMENT_THREAD.ID }, true); + public static Index PROPERTY_PKEY = Internal.createIndex("property_pkey", Property.PROPERTY, new OrderField[] { Property.PROPERTY.KEY }, true); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/Keys.java b/src/main/java/de/vorb/platon/persistence/jooq/Keys.java new file mode 100644 index 0000000..bdfe0b4 --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/Keys.java @@ -0,0 +1,77 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq; + + +import de.vorb.platon.persistence.jooq.tables.Comment; +import de.vorb.platon.persistence.jooq.tables.CommentThread; +import de.vorb.platon.persistence.jooq.tables.Property; +import de.vorb.platon.persistence.jooq.tables.records.CommentRecord; +import de.vorb.platon.persistence.jooq.tables.records.CommentThreadRecord; +import de.vorb.platon.persistence.jooq.tables.records.PropertyRecord; + +import javax.annotation.Generated; + +import org.jooq.ForeignKey; +import org.jooq.Identity; +import org.jooq.UniqueKey; +import org.jooq.impl.Internal; + + +/** + * A class modelling foreign key relationships and constraints of tables of + * the public schema. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Keys { + + // ------------------------------------------------------------------------- + // IDENTITY definitions + // ------------------------------------------------------------------------- + + public static final Identity IDENTITY_COMMENT = Identities0.IDENTITY_COMMENT; + public static final Identity IDENTITY_COMMENT_THREAD = Identities0.IDENTITY_COMMENT_THREAD; + + // ------------------------------------------------------------------------- + // UNIQUE and PRIMARY KEY definitions + // ------------------------------------------------------------------------- + + public static final UniqueKey COMMENT_PKEY = UniqueKeys0.COMMENT_PKEY; + public static final UniqueKey COMMENT_THREAD_PKEY = UniqueKeys0.COMMENT_THREAD_PKEY; + public static final UniqueKey PROPERTY_PKEY = UniqueKeys0.PROPERTY_PKEY; + + // ------------------------------------------------------------------------- + // FOREIGN KEY definitions + // ------------------------------------------------------------------------- + + public static final ForeignKey COMMENT__COMMENT_THREAD_ID_FKEY = ForeignKeys0.COMMENT__COMMENT_THREAD_ID_FKEY; + public static final ForeignKey COMMENT__COMMENT_PARENT_ID_FKEY = ForeignKeys0.COMMENT__COMMENT_PARENT_ID_FKEY; + + // ------------------------------------------------------------------------- + // [#1459] distribute members to avoid static initialisers > 64kb + // ------------------------------------------------------------------------- + + private static class Identities0 { + public static Identity IDENTITY_COMMENT = Internal.createIdentity(Comment.COMMENT, Comment.COMMENT.ID); + public static Identity IDENTITY_COMMENT_THREAD = Internal.createIdentity(CommentThread.COMMENT_THREAD, CommentThread.COMMENT_THREAD.ID); + } + + private static class UniqueKeys0 { + public static final UniqueKey COMMENT_PKEY = Internal.createUniqueKey(Comment.COMMENT, "comment_pkey", Comment.COMMENT.ID); + public static final UniqueKey COMMENT_THREAD_PKEY = Internal.createUniqueKey(CommentThread.COMMENT_THREAD, "comment_thread_pkey", CommentThread.COMMENT_THREAD.ID); + public static final UniqueKey PROPERTY_PKEY = Internal.createUniqueKey(Property.PROPERTY, "property_pkey", Property.PROPERTY.KEY); + } + + private static class ForeignKeys0 { + public static final ForeignKey COMMENT__COMMENT_THREAD_ID_FKEY = Internal.createForeignKey(de.vorb.platon.persistence.jooq.Keys.COMMENT_THREAD_PKEY, Comment.COMMENT, "comment__comment_thread_id_fkey", Comment.COMMENT.THREAD_ID); + public static final ForeignKey COMMENT__COMMENT_PARENT_ID_FKEY = Internal.createForeignKey(de.vorb.platon.persistence.jooq.Keys.COMMENT_PKEY, Comment.COMMENT, "comment__comment_parent_id_fkey", Comment.COMMENT.PARENT_ID); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/Public.java b/src/main/java/de/vorb/platon/persistence/jooq/Public.java new file mode 100644 index 0000000..092d621 --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/Public.java @@ -0,0 +1,100 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq; + + +import de.vorb.platon.persistence.jooq.tables.Comment; +import de.vorb.platon.persistence.jooq.tables.CommentThread; +import de.vorb.platon.persistence.jooq.tables.Property; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Generated; + +import org.jooq.Catalog; +import org.jooq.Sequence; +import org.jooq.Table; +import org.jooq.impl.SchemaImpl; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Public extends SchemaImpl { + + private static final long serialVersionUID = -2087980905; + + /** + * The reference instance of public + */ + public static final Public PUBLIC = new Public(); + + /** + * The table public.comment. + */ + public final Comment COMMENT = de.vorb.platon.persistence.jooq.tables.Comment.COMMENT; + + /** + * The table public.comment_thread. + */ + public final CommentThread COMMENT_THREAD = de.vorb.platon.persistence.jooq.tables.CommentThread.COMMENT_THREAD; + + /** + * The table public.property. + */ + public final Property PROPERTY = de.vorb.platon.persistence.jooq.tables.Property.PROPERTY; + + /** + * No further instances allowed + */ + private Public() { + super("public", null); + } + + + /** + * {@inheritDoc} + */ + @Override + public Catalog getCatalog() { + return DefaultCatalog.DEFAULT_CATALOG; + } + + @Override + public final List> getSequences() { + List result = new ArrayList(); + result.addAll(getSequences0()); + return result; + } + + private final List> getSequences0() { + return Arrays.>asList( + Sequences.COMMENT_ID_SEQ, + Sequences.COMMENT_THREAD_ID_SEQ); + } + + @Override + public final List> getTables() { + List result = new ArrayList(); + result.addAll(getTables0()); + return result; + } + + private final List> getTables0() { + return Arrays.>asList( + Comment.COMMENT, + CommentThread.COMMENT_THREAD, + Property.PROPERTY); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/Sequences.java b/src/main/java/de/vorb/platon/persistence/jooq/Sequences.java new file mode 100644 index 0000000..06aaab6 --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/Sequences.java @@ -0,0 +1,35 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq; + + +import javax.annotation.Generated; + +import org.jooq.Sequence; +import org.jooq.impl.SequenceImpl; + + +/** + * Convenience access to all sequences in public + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Sequences { + + /** + * The sequence public.comment_id_seq + */ + public static final Sequence COMMENT_ID_SEQ = new SequenceImpl("comment_id_seq", Public.PUBLIC, org.jooq.impl.SQLDataType.BIGINT.nullable(false)); + + /** + * The sequence public.comment_thread_id_seq + */ + public static final Sequence COMMENT_THREAD_ID_SEQ = new SequenceImpl("comment_thread_id_seq", Public.PUBLIC, org.jooq.impl.SQLDataType.BIGINT.nullable(false)); +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/Tables.java b/src/main/java/de/vorb/platon/persistence/jooq/Tables.java new file mode 100644 index 0000000..9fe2271 --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/Tables.java @@ -0,0 +1,41 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq; + + +import de.vorb.platon.persistence.jooq.tables.Comment; +import de.vorb.platon.persistence.jooq.tables.CommentThread; +import de.vorb.platon.persistence.jooq.tables.Property; + +import javax.annotation.Generated; + + +/** + * Convenience access to all tables in public + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Tables { + + /** + * The table public.comment. + */ + public static final Comment COMMENT = de.vorb.platon.persistence.jooq.tables.Comment.COMMENT; + + /** + * The table public.comment_thread. + */ + public static final CommentThread COMMENT_THREAD = de.vorb.platon.persistence.jooq.tables.CommentThread.COMMENT_THREAD; + + /** + * The table public.property. + */ + public static final Property PROPERTY = de.vorb.platon.persistence.jooq.tables.Property.PROPERTY; +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java new file mode 100644 index 0000000..ee448eb --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java @@ -0,0 +1,219 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq.tables; + + +import de.vorb.platon.model.CommentStatus; +import de.vorb.platon.persistence.conversion.CommentStatusConverter; +import de.vorb.platon.persistence.jooq.Indexes; +import de.vorb.platon.persistence.jooq.Keys; +import de.vorb.platon.persistence.jooq.Public; +import de.vorb.platon.persistence.jooq.tables.records.CommentRecord; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Generated; + +import org.jooq.Field; +import org.jooq.ForeignKey; +import org.jooq.Identity; +import org.jooq.Index; +import org.jooq.Name; +import org.jooq.Schema; +import org.jooq.Table; +import org.jooq.TableField; +import org.jooq.UniqueKey; +import org.jooq.impl.DSL; +import org.jooq.impl.TableImpl; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Comment extends TableImpl { + + private static final long serialVersionUID = -394233886; + + /** + * The reference instance of public.comment + */ + public static final Comment COMMENT = new Comment(); + + /** + * The class holding records for this type + */ + @Override + public Class getRecordType() { + return CommentRecord.class; + } + + /** + * The column public.comment.id. + */ + public final TableField ID = createField("id", org.jooq.impl.SQLDataType.BIGINT.nullable(false).defaultValue(org.jooq.impl.DSL.field("nextval('comment_id_seq'::regclass)", org.jooq.impl.SQLDataType.BIGINT)), this, ""); + + /** + * The column public.comment.thread_id. + */ + public final TableField THREAD_ID = createField("thread_id", org.jooq.impl.SQLDataType.BIGINT.nullable(false), this, ""); + + /** + * The column public.comment.parent_id. + */ + public final TableField PARENT_ID = createField("parent_id", org.jooq.impl.SQLDataType.BIGINT, this, ""); + + /** + * The column public.comment.creation_date. + */ + public final TableField CREATION_DATE = createField("creation_date", org.jooq.impl.SQLDataType.LOCALDATETIME.nullable(false), this, ""); + + /** + * The column public.comment.last_modification_date. + */ + public final TableField LAST_MODIFICATION_DATE = createField("last_modification_date", org.jooq.impl.SQLDataType.LOCALDATETIME.nullable(false), this, ""); + + /** + * The column public.comment.status. + */ + public final TableField STATUS = createField("status", org.jooq.impl.SQLDataType.VARCHAR(32).nullable(false), this, "", new CommentStatusConverter()); + + /** + * The column public.comment.text. + */ + public final TableField TEXT = createField("text", org.jooq.impl.SQLDataType.CLOB.nullable(false), this, ""); + + /** + * The column public.comment.author. + */ + public final TableField AUTHOR = createField("author", org.jooq.impl.SQLDataType.VARCHAR(128), this, ""); + + /** + * The column public.comment.email_hash. + */ + public final TableField EMAIL_HASH = createField("email_hash", org.jooq.impl.SQLDataType.CHAR(32), this, ""); + + /** + * The column public.comment.url. + */ + public final TableField URL = createField("url", org.jooq.impl.SQLDataType.VARCHAR(256), this, ""); + + /** + * Create a public.comment table reference + */ + public Comment() { + this(DSL.name("comment"), null); + } + + /** + * Create an aliased public.comment table reference + */ + public Comment(String alias) { + this(DSL.name(alias), COMMENT); + } + + /** + * Create an aliased public.comment table reference + */ + public Comment(Name alias) { + this(alias, COMMENT); + } + + private Comment(Name alias, Table aliased) { + this(alias, aliased, null); + } + + private Comment(Name alias, Table aliased, Field[] parameters) { + super(alias, null, aliased, parameters, ""); + } + + /** + * {@inheritDoc} + */ + @Override + public Schema getSchema() { + return Public.PUBLIC; + } + + /** + * {@inheritDoc} + */ + @Override + public List getIndexes() { + return Arrays.asList(Indexes.COMMENT__CREATION_DATE__IDX, Indexes.COMMENT__ID__IDX, Indexes.COMMENT__STATUS__IDX, Indexes.COMMENT__THREAD_ID__IDX, Indexes.COMMENT_PKEY); + } + + /** + * {@inheritDoc} + */ + @Override + public Identity getIdentity() { + return Keys.IDENTITY_COMMENT; + } + + /** + * {@inheritDoc} + */ + @Override + public UniqueKey getPrimaryKey() { + return Keys.COMMENT_PKEY; + } + + /** + * {@inheritDoc} + */ + @Override + public List> getKeys() { + return Arrays.>asList(Keys.COMMENT_PKEY); + } + + /** + * {@inheritDoc} + */ + @Override + public List> getReferences() { + return Arrays.>asList(Keys.COMMENT__COMMENT_THREAD_ID_FKEY, Keys.COMMENT__COMMENT_PARENT_ID_FKEY); + } + + /** + * {@inheritDoc} + */ + @Override + public Comment as(String alias) { + return new Comment(DSL.name(alias), this); + } + + /** + * {@inheritDoc} + */ + @Override + public Comment as(Name alias) { + return new Comment(alias, this); + } + + /** + * Rename this table + */ + @Override + public Comment rename(String name) { + return new Comment(DSL.name(name), null); + } + + /** + * Rename this table + */ + @Override + public Comment rename(Name name) { + return new Comment(name, null); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/CommentThread.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/CommentThread.java new file mode 100644 index 0000000..c5118e9 --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/CommentThread.java @@ -0,0 +1,172 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq.tables; + + +import de.vorb.platon.persistence.jooq.Indexes; +import de.vorb.platon.persistence.jooq.Keys; +import de.vorb.platon.persistence.jooq.Public; +import de.vorb.platon.persistence.jooq.tables.records.CommentThreadRecord; + +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Generated; + +import org.jooq.Field; +import org.jooq.Identity; +import org.jooq.Index; +import org.jooq.Name; +import org.jooq.Schema; +import org.jooq.Table; +import org.jooq.TableField; +import org.jooq.UniqueKey; +import org.jooq.impl.DSL; +import org.jooq.impl.TableImpl; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class CommentThread extends TableImpl { + + private static final long serialVersionUID = 22120208; + + /** + * The reference instance of public.comment_thread + */ + public static final CommentThread COMMENT_THREAD = new CommentThread(); + + /** + * The class holding records for this type + */ + @Override + public Class getRecordType() { + return CommentThreadRecord.class; + } + + /** + * The column public.comment_thread.id. + */ + public final TableField ID = createField("id", org.jooq.impl.SQLDataType.BIGINT.nullable(false).defaultValue(org.jooq.impl.DSL.field("nextval('comment_thread_id_seq'::regclass)", org.jooq.impl.SQLDataType.BIGINT)), this, ""); + + /** + * The column public.comment_thread.url. + */ + public final TableField URL = createField("url", org.jooq.impl.SQLDataType.VARCHAR(256).nullable(false), this, ""); + + /** + * The column public.comment_thread.title. + */ + public final TableField TITLE = createField("title", org.jooq.impl.SQLDataType.VARCHAR(512), this, ""); + + /** + * Create a public.comment_thread table reference + */ + public CommentThread() { + this(DSL.name("comment_thread"), null); + } + + /** + * Create an aliased public.comment_thread table reference + */ + public CommentThread(String alias) { + this(DSL.name(alias), COMMENT_THREAD); + } + + /** + * Create an aliased public.comment_thread table reference + */ + public CommentThread(Name alias) { + this(alias, COMMENT_THREAD); + } + + private CommentThread(Name alias, Table aliased) { + this(alias, aliased, null); + } + + private CommentThread(Name alias, Table aliased, Field[] parameters) { + super(alias, null, aliased, parameters, ""); + } + + /** + * {@inheritDoc} + */ + @Override + public Schema getSchema() { + return Public.PUBLIC; + } + + /** + * {@inheritDoc} + */ + @Override + public List getIndexes() { + return Arrays.asList(Indexes.COMMENT_THREAD__ID__IDX, Indexes.COMMENT_THREAD__URL__IDX, Indexes.COMMENT_THREAD_PKEY); + } + + /** + * {@inheritDoc} + */ + @Override + public Identity getIdentity() { + return Keys.IDENTITY_COMMENT_THREAD; + } + + /** + * {@inheritDoc} + */ + @Override + public UniqueKey getPrimaryKey() { + return Keys.COMMENT_THREAD_PKEY; + } + + /** + * {@inheritDoc} + */ + @Override + public List> getKeys() { + return Arrays.>asList(Keys.COMMENT_THREAD_PKEY); + } + + /** + * {@inheritDoc} + */ + @Override + public CommentThread as(String alias) { + return new CommentThread(DSL.name(alias), this); + } + + /** + * {@inheritDoc} + */ + @Override + public CommentThread as(Name alias) { + return new CommentThread(alias, this); + } + + /** + * Rename this table + */ + @Override + public CommentThread rename(String name) { + return new CommentThread(DSL.name(name), null); + } + + /** + * Rename this table + */ + @Override + public CommentThread rename(Name name) { + return new CommentThread(name, null); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/Property.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/Property.java new file mode 100644 index 0000000..fb27649 --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/Property.java @@ -0,0 +1,158 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq.tables; + + +import de.vorb.platon.persistence.jooq.Indexes; +import de.vorb.platon.persistence.jooq.Keys; +import de.vorb.platon.persistence.jooq.Public; +import de.vorb.platon.persistence.jooq.tables.records.PropertyRecord; + +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Generated; + +import org.jooq.Field; +import org.jooq.Index; +import org.jooq.Name; +import org.jooq.Schema; +import org.jooq.Table; +import org.jooq.TableField; +import org.jooq.UniqueKey; +import org.jooq.impl.DSL; +import org.jooq.impl.TableImpl; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Property extends TableImpl { + + private static final long serialVersionUID = -1455209178; + + /** + * The reference instance of public.property + */ + public static final Property PROPERTY = new Property(); + + /** + * The class holding records for this type + */ + @Override + public Class getRecordType() { + return PropertyRecord.class; + } + + /** + * The column public.property.key. + */ + public final TableField KEY = createField("key", org.jooq.impl.SQLDataType.VARCHAR(32).nullable(false), this, ""); + + /** + * The column public.property.value. + */ + public final TableField VALUE = createField("value", org.jooq.impl.SQLDataType.VARCHAR(256).nullable(false), this, ""); + + /** + * Create a public.property table reference + */ + public Property() { + this(DSL.name("property"), null); + } + + /** + * Create an aliased public.property table reference + */ + public Property(String alias) { + this(DSL.name(alias), PROPERTY); + } + + /** + * Create an aliased public.property table reference + */ + public Property(Name alias) { + this(alias, PROPERTY); + } + + private Property(Name alias, Table aliased) { + this(alias, aliased, null); + } + + private Property(Name alias, Table aliased, Field[] parameters) { + super(alias, null, aliased, parameters, ""); + } + + /** + * {@inheritDoc} + */ + @Override + public Schema getSchema() { + return Public.PUBLIC; + } + + /** + * {@inheritDoc} + */ + @Override + public List getIndexes() { + return Arrays.asList(Indexes.PROPERTY_PKEY); + } + + /** + * {@inheritDoc} + */ + @Override + public UniqueKey getPrimaryKey() { + return Keys.PROPERTY_PKEY; + } + + /** + * {@inheritDoc} + */ + @Override + public List> getKeys() { + return Arrays.>asList(Keys.PROPERTY_PKEY); + } + + /** + * {@inheritDoc} + */ + @Override + public Property as(String alias) { + return new Property(DSL.name(alias), this); + } + + /** + * {@inheritDoc} + */ + @Override + public Property as(Name alias) { + return new Property(alias, this); + } + + /** + * Rename this table + */ + @Override + public Property rename(String name) { + return new Property(DSL.name(name), null); + } + + /** + * Rename this table + */ + @Override + public Property rename(Name name) { + return new Property(name, null); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java new file mode 100644 index 0000000..dce7a8a --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java @@ -0,0 +1,188 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq.tables.pojos; + + +import de.vorb.platon.model.CommentStatus; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import javax.annotation.Generated; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Comment implements Serializable { + + private static final long serialVersionUID = 11505420; + + private Long id; + private Long threadId; + private Long parentId; + private LocalDateTime creationDate; + private LocalDateTime lastModificationDate; + private CommentStatus status; + private String text; + private String author; + private String emailHash; + private String url; + + public Comment() {} + + public Comment(Comment value) { + this.id = value.id; + this.threadId = value.threadId; + this.parentId = value.parentId; + this.creationDate = value.creationDate; + this.lastModificationDate = value.lastModificationDate; + this.status = value.status; + this.text = value.text; + this.author = value.author; + this.emailHash = value.emailHash; + this.url = value.url; + } + + public Comment( + Long id, + Long threadId, + Long parentId, + LocalDateTime creationDate, + LocalDateTime lastModificationDate, + CommentStatus status, + String text, + String author, + String emailHash, + String url + ) { + this.id = id; + this.threadId = threadId; + this.parentId = parentId; + this.creationDate = creationDate; + this.lastModificationDate = lastModificationDate; + this.status = status; + this.text = text; + this.author = author; + this.emailHash = emailHash; + this.url = url; + } + + public Long getId() { + return this.id; + } + + public Comment setId(Long id) { + this.id = id; + return this; + } + + public Long getThreadId() { + return this.threadId; + } + + public Comment setThreadId(Long threadId) { + this.threadId = threadId; + return this; + } + + public Long getParentId() { + return this.parentId; + } + + public Comment setParentId(Long parentId) { + this.parentId = parentId; + return this; + } + + public LocalDateTime getCreationDate() { + return this.creationDate; + } + + public Comment setCreationDate(LocalDateTime creationDate) { + this.creationDate = creationDate; + return this; + } + + public LocalDateTime getLastModificationDate() { + return this.lastModificationDate; + } + + public Comment setLastModificationDate(LocalDateTime lastModificationDate) { + this.lastModificationDate = lastModificationDate; + return this; + } + + public CommentStatus getStatus() { + return this.status; + } + + public Comment setStatus(CommentStatus status) { + this.status = status; + return this; + } + + public String getText() { + return this.text; + } + + public Comment setText(String text) { + this.text = text; + return this; + } + + public String getAuthor() { + return this.author; + } + + public Comment setAuthor(String author) { + this.author = author; + return this; + } + + public String getEmailHash() { + return this.emailHash; + } + + public Comment setEmailHash(String emailHash) { + this.emailHash = emailHash; + return this; + } + + public String getUrl() { + return this.url; + } + + public Comment setUrl(String url) { + this.url = url; + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Comment ("); + + sb.append(id); + sb.append(", ").append(threadId); + sb.append(", ").append(parentId); + sb.append(", ").append(creationDate); + sb.append(", ").append(lastModificationDate); + sb.append(", ").append(status); + sb.append(", ").append(text); + sb.append(", ").append(author); + sb.append(", ").append(emailHash); + sb.append(", ").append(url); + + sb.append(")"); + return sb.toString(); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/CommentThread.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/CommentThread.java new file mode 100644 index 0000000..f66c73f --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/CommentThread.java @@ -0,0 +1,87 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq.tables.pojos; + + +import java.io.Serializable; + +import javax.annotation.Generated; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class CommentThread implements Serializable { + + private static final long serialVersionUID = 1625604914; + + private Long id; + private String url; + private String title; + + public CommentThread() {} + + public CommentThread(CommentThread value) { + this.id = value.id; + this.url = value.url; + this.title = value.title; + } + + public CommentThread( + Long id, + String url, + String title + ) { + this.id = id; + this.url = url; + this.title = title; + } + + public Long getId() { + return this.id; + } + + public CommentThread setId(Long id) { + this.id = id; + return this; + } + + public String getUrl() { + return this.url; + } + + public CommentThread setUrl(String url) { + this.url = url; + return this; + } + + public String getTitle() { + return this.title; + } + + public CommentThread setTitle(String title) { + this.title = title; + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("CommentThread ("); + + sb.append(id); + sb.append(", ").append(url); + sb.append(", ").append(title); + + sb.append(")"); + return sb.toString(); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Property.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Property.java new file mode 100644 index 0000000..d4fa63f --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Property.java @@ -0,0 +1,73 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq.tables.pojos; + + +import java.io.Serializable; + +import javax.annotation.Generated; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Property implements Serializable { + + private static final long serialVersionUID = 160128780; + + private String key; + private String value; + + public Property() {} + + public Property(Property value) { + this.key = value.key; + this.value = value.value; + } + + public Property( + String key, + String value + ) { + this.key = key; + this.value = value; + } + + public String getKey() { + return this.key; + } + + public Property setKey(String key) { + this.key = key; + return this; + } + + public String getValue() { + return this.value; + } + + public Property setValue(String value) { + this.value = value; + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Property ("); + + sb.append(key); + sb.append(", ").append(value); + + sb.append(")"); + return sb.toString(); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java new file mode 100644 index 0000000..8f4d0ec --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java @@ -0,0 +1,594 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq.tables.records; + + +import de.vorb.platon.model.CommentStatus; +import de.vorb.platon.persistence.jooq.tables.Comment; + +import java.time.LocalDateTime; + +import javax.annotation.Generated; + +import org.jooq.Field; +import org.jooq.Record1; +import org.jooq.Record10; +import org.jooq.Row10; +import org.jooq.impl.UpdatableRecordImpl; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class CommentRecord extends UpdatableRecordImpl implements Record10 { + + private static final long serialVersionUID = 1375009388; + + /** + * Setter for public.comment.id. + */ + public CommentRecord setId(Long value) { + set(0, value); + return this; + } + + /** + * Getter for public.comment.id. + */ + public Long getId() { + return (Long) get(0); + } + + /** + * Setter for public.comment.thread_id. + */ + public CommentRecord setThreadId(Long value) { + set(1, value); + return this; + } + + /** + * Getter for public.comment.thread_id. + */ + public Long getThreadId() { + return (Long) get(1); + } + + /** + * Setter for public.comment.parent_id. + */ + public CommentRecord setParentId(Long value) { + set(2, value); + return this; + } + + /** + * Getter for public.comment.parent_id. + */ + public Long getParentId() { + return (Long) get(2); + } + + /** + * Setter for public.comment.creation_date. + */ + public CommentRecord setCreationDate(LocalDateTime value) { + set(3, value); + return this; + } + + /** + * Getter for public.comment.creation_date. + */ + public LocalDateTime getCreationDate() { + return (LocalDateTime) get(3); + } + + /** + * Setter for public.comment.last_modification_date. + */ + public CommentRecord setLastModificationDate(LocalDateTime value) { + set(4, value); + return this; + } + + /** + * Getter for public.comment.last_modification_date. + */ + public LocalDateTime getLastModificationDate() { + return (LocalDateTime) get(4); + } + + /** + * Setter for public.comment.status. + */ + public CommentRecord setStatus(CommentStatus value) { + set(5, value); + return this; + } + + /** + * Getter for public.comment.status. + */ + public CommentStatus getStatus() { + return (CommentStatus) get(5); + } + + /** + * Setter for public.comment.text. + */ + public CommentRecord setText(String value) { + set(6, value); + return this; + } + + /** + * Getter for public.comment.text. + */ + public String getText() { + return (String) get(6); + } + + /** + * Setter for public.comment.author. + */ + public CommentRecord setAuthor(String value) { + set(7, value); + return this; + } + + /** + * Getter for public.comment.author. + */ + public String getAuthor() { + return (String) get(7); + } + + /** + * Setter for public.comment.email_hash. + */ + public CommentRecord setEmailHash(String value) { + set(8, value); + return this; + } + + /** + * Getter for public.comment.email_hash. + */ + public String getEmailHash() { + return (String) get(8); + } + + /** + * Setter for public.comment.url. + */ + public CommentRecord setUrl(String value) { + set(9, value); + return this; + } + + /** + * Getter for public.comment.url. + */ + public String getUrl() { + return (String) get(9); + } + + // ------------------------------------------------------------------------- + // Primary key information + // ------------------------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Record1 key() { + return (Record1) super.key(); + } + + // ------------------------------------------------------------------------- + // Record10 type implementation + // ------------------------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Row10 fieldsRow() { + return (Row10) super.fieldsRow(); + } + + /** + * {@inheritDoc} + */ + @Override + public Row10 valuesRow() { + return (Row10) super.valuesRow(); + } + + /** + * {@inheritDoc} + */ + @Override + public Field field1() { + return Comment.COMMENT.ID; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field2() { + return Comment.COMMENT.THREAD_ID; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field3() { + return Comment.COMMENT.PARENT_ID; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field4() { + return Comment.COMMENT.CREATION_DATE; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field5() { + return Comment.COMMENT.LAST_MODIFICATION_DATE; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field6() { + return Comment.COMMENT.STATUS; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field7() { + return Comment.COMMENT.TEXT; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field8() { + return Comment.COMMENT.AUTHOR; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field9() { + return Comment.COMMENT.EMAIL_HASH; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field10() { + return Comment.COMMENT.URL; + } + + /** + * {@inheritDoc} + */ + @Override + public Long component1() { + return getId(); + } + + /** + * {@inheritDoc} + */ + @Override + public Long component2() { + return getThreadId(); + } + + /** + * {@inheritDoc} + */ + @Override + public Long component3() { + return getParentId(); + } + + /** + * {@inheritDoc} + */ + @Override + public LocalDateTime component4() { + return getCreationDate(); + } + + /** + * {@inheritDoc} + */ + @Override + public LocalDateTime component5() { + return getLastModificationDate(); + } + + /** + * {@inheritDoc} + */ + @Override + public CommentStatus component6() { + return getStatus(); + } + + /** + * {@inheritDoc} + */ + @Override + public String component7() { + return getText(); + } + + /** + * {@inheritDoc} + */ + @Override + public String component8() { + return getAuthor(); + } + + /** + * {@inheritDoc} + */ + @Override + public String component9() { + return getEmailHash(); + } + + /** + * {@inheritDoc} + */ + @Override + public String component10() { + return getUrl(); + } + + /** + * {@inheritDoc} + */ + @Override + public Long value1() { + return getId(); + } + + /** + * {@inheritDoc} + */ + @Override + public Long value2() { + return getThreadId(); + } + + /** + * {@inheritDoc} + */ + @Override + public Long value3() { + return getParentId(); + } + + /** + * {@inheritDoc} + */ + @Override + public LocalDateTime value4() { + return getCreationDate(); + } + + /** + * {@inheritDoc} + */ + @Override + public LocalDateTime value5() { + return getLastModificationDate(); + } + + /** + * {@inheritDoc} + */ + @Override + public CommentStatus value6() { + return getStatus(); + } + + /** + * {@inheritDoc} + */ + @Override + public String value7() { + return getText(); + } + + /** + * {@inheritDoc} + */ + @Override + public String value8() { + return getAuthor(); + } + + /** + * {@inheritDoc} + */ + @Override + public String value9() { + return getEmailHash(); + } + + /** + * {@inheritDoc} + */ + @Override + public String value10() { + return getUrl(); + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value1(Long value) { + setId(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value2(Long value) { + setThreadId(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value3(Long value) { + setParentId(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value4(LocalDateTime value) { + setCreationDate(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value5(LocalDateTime value) { + setLastModificationDate(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value6(CommentStatus value) { + setStatus(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value7(String value) { + setText(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value8(String value) { + setAuthor(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value9(String value) { + setEmailHash(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value10(String value) { + setUrl(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value4, LocalDateTime value5, CommentStatus value6, String value7, String value8, String value9, String value10) { + value1(value1); + value2(value2); + value3(value3); + value4(value4); + value5(value5); + value6(value6); + value7(value7); + value8(value8); + value9(value9); + value10(value10); + return this; + } + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + /** + * Create a detached CommentRecord + */ + public CommentRecord() { + super(Comment.COMMENT); + } + + /** + * Create a detached, initialised CommentRecord + */ + public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creationDate, LocalDateTime lastModificationDate, CommentStatus status, String text, String author, String emailHash, String url) { + super(Comment.COMMENT); + + set(0, id); + set(1, threadId); + set(2, parentId); + set(3, creationDate); + set(4, lastModificationDate); + set(5, status); + set(6, text); + set(7, author); + set(8, emailHash); + set(9, url); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentThreadRecord.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentThreadRecord.java new file mode 100644 index 0000000..928cacd --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentThreadRecord.java @@ -0,0 +1,241 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq.tables.records; + + +import de.vorb.platon.persistence.jooq.tables.CommentThread; + +import javax.annotation.Generated; + +import org.jooq.Field; +import org.jooq.Record1; +import org.jooq.Record3; +import org.jooq.Row3; +import org.jooq.impl.UpdatableRecordImpl; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class CommentThreadRecord extends UpdatableRecordImpl implements Record3 { + + private static final long serialVersionUID = 542597670; + + /** + * Setter for public.comment_thread.id. + */ + public CommentThreadRecord setId(Long value) { + set(0, value); + return this; + } + + /** + * Getter for public.comment_thread.id. + */ + public Long getId() { + return (Long) get(0); + } + + /** + * Setter for public.comment_thread.url. + */ + public CommentThreadRecord setUrl(String value) { + set(1, value); + return this; + } + + /** + * Getter for public.comment_thread.url. + */ + public String getUrl() { + return (String) get(1); + } + + /** + * Setter for public.comment_thread.title. + */ + public CommentThreadRecord setTitle(String value) { + set(2, value); + return this; + } + + /** + * Getter for public.comment_thread.title. + */ + public String getTitle() { + return (String) get(2); + } + + // ------------------------------------------------------------------------- + // Primary key information + // ------------------------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Record1 key() { + return (Record1) super.key(); + } + + // ------------------------------------------------------------------------- + // Record3 type implementation + // ------------------------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Row3 fieldsRow() { + return (Row3) super.fieldsRow(); + } + + /** + * {@inheritDoc} + */ + @Override + public Row3 valuesRow() { + return (Row3) super.valuesRow(); + } + + /** + * {@inheritDoc} + */ + @Override + public Field field1() { + return CommentThread.COMMENT_THREAD.ID; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field2() { + return CommentThread.COMMENT_THREAD.URL; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field3() { + return CommentThread.COMMENT_THREAD.TITLE; + } + + /** + * {@inheritDoc} + */ + @Override + public Long component1() { + return getId(); + } + + /** + * {@inheritDoc} + */ + @Override + public String component2() { + return getUrl(); + } + + /** + * {@inheritDoc} + */ + @Override + public String component3() { + return getTitle(); + } + + /** + * {@inheritDoc} + */ + @Override + public Long value1() { + return getId(); + } + + /** + * {@inheritDoc} + */ + @Override + public String value2() { + return getUrl(); + } + + /** + * {@inheritDoc} + */ + @Override + public String value3() { + return getTitle(); + } + + /** + * {@inheritDoc} + */ + @Override + public CommentThreadRecord value1(Long value) { + setId(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentThreadRecord value2(String value) { + setUrl(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentThreadRecord value3(String value) { + setTitle(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentThreadRecord values(Long value1, String value2, String value3) { + value1(value1); + value2(value2); + value3(value3); + return this; + } + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + /** + * Create a detached CommentThreadRecord + */ + public CommentThreadRecord() { + super(CommentThread.COMMENT_THREAD); + } + + /** + * Create a detached, initialised CommentThreadRecord + */ + public CommentThreadRecord(Long id, String url, String title) { + super(CommentThread.COMMENT_THREAD); + + set(0, id); + set(1, url); + set(2, title); + } +} diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/PropertyRecord.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/PropertyRecord.java new file mode 100644 index 0000000..4ccb622 --- /dev/null +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/PropertyRecord.java @@ -0,0 +1,191 @@ +/* + * This file is generated by jOOQ. +*/ +package de.vorb.platon.persistence.jooq.tables.records; + + +import de.vorb.platon.persistence.jooq.tables.Property; + +import javax.annotation.Generated; + +import org.jooq.Field; +import org.jooq.Record1; +import org.jooq.Record2; +import org.jooq.Row2; +import org.jooq.impl.UpdatableRecordImpl; + + +/** + * This class is generated by jOOQ. + */ +@Generated( + value = { + "http://www.jooq.org", + "jOOQ version:3.10.7" + }, + comments = "This class is generated by jOOQ" +) +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class PropertyRecord extends UpdatableRecordImpl implements Record2 { + + private static final long serialVersionUID = 310035801; + + /** + * Setter for public.property.key. + */ + public PropertyRecord setKey(String value) { + set(0, value); + return this; + } + + /** + * Getter for public.property.key. + */ + public String getKey() { + return (String) get(0); + } + + /** + * Setter for public.property.value. + */ + public PropertyRecord setValue(String value) { + set(1, value); + return this; + } + + /** + * Getter for public.property.value. + */ + public String getValue() { + return (String) get(1); + } + + // ------------------------------------------------------------------------- + // Primary key information + // ------------------------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Record1 key() { + return (Record1) super.key(); + } + + // ------------------------------------------------------------------------- + // Record2 type implementation + // ------------------------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Row2 fieldsRow() { + return (Row2) super.fieldsRow(); + } + + /** + * {@inheritDoc} + */ + @Override + public Row2 valuesRow() { + return (Row2) super.valuesRow(); + } + + /** + * {@inheritDoc} + */ + @Override + public Field field1() { + return Property.PROPERTY.KEY; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field2() { + return Property.PROPERTY.VALUE; + } + + /** + * {@inheritDoc} + */ + @Override + public String component1() { + return getKey(); + } + + /** + * {@inheritDoc} + */ + @Override + public String component2() { + return getValue(); + } + + /** + * {@inheritDoc} + */ + @Override + public String value1() { + return getKey(); + } + + /** + * {@inheritDoc} + */ + @Override + public String value2() { + return getValue(); + } + + /** + * {@inheritDoc} + */ + @Override + public PropertyRecord value1(String value) { + setKey(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public PropertyRecord value2(String value) { + setValue(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public PropertyRecord values(String value1, String value2) { + value1(value1); + value2(value2); + return this; + } + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + /** + * Create a detached PropertyRecord + */ + public PropertyRecord() { + super(Property.PROPERTY); + } + + /** + * Create a detached, initialised PropertyRecord + */ + public PropertyRecord(String key, String value) { + super(Property.PROPERTY); + + set(0, key); + set(1, value); + } +} diff --git a/src/main/java/de/vorb/platon/security/DatabaseSecretKeyProvider.java b/src/main/java/de/vorb/platon/security/DatabaseSecretKeyProvider.java index c66366c..09d43ea 100644 --- a/src/main/java/de/vorb/platon/security/DatabaseSecretKeyProvider.java +++ b/src/main/java/de/vorb/platon/security/DatabaseSecretKeyProvider.java @@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; import javax.crypto.KeyGenerator; @@ -29,6 +30,7 @@ import java.util.Base64; @Component +@DependsOn("flywayInitializer") @RequiredArgsConstructor @Slf4j public class DatabaseSecretKeyProvider implements SecretKeyProvider { diff --git a/src/main/java/de/vorb/platon/security/HmacSignatureTokenValidator.java b/src/main/java/de/vorb/platon/security/HmacSignatureTokenValidator.java index d0e9ba7..45998b6 100644 --- a/src/main/java/de/vorb/platon/security/HmacSignatureTokenValidator.java +++ b/src/main/java/de/vorb/platon/security/HmacSignatureTokenValidator.java @@ -17,8 +17,6 @@ package de.vorb.platon.security; import org.apache.commons.codec.digest.HmacAlgorithms; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import javax.crypto.Mac; import javax.crypto.SecretKey; @@ -31,7 +29,6 @@ import static de.vorb.platon.security.SignatureComponents.COMPONENT_SEPARATOR; import static java.nio.charset.StandardCharsets.UTF_8; -@Service public class HmacSignatureTokenValidator implements SignatureTokenValidator { static final HmacAlgorithms HMAC_ALGORITHM = HmacAlgorithms.HMAC_SHA_256; @@ -40,7 +37,6 @@ public class HmacSignatureTokenValidator implements SignatureTokenValidator { private final Mac mac; - @Autowired public HmacSignatureTokenValidator(SecretKeyProvider keyProvider, Clock clock) { this.clock = clock; diff --git a/src/main/java/de/vorb/platon/security/SecurityConfig.java b/src/main/java/de/vorb/platon/security/SecurityConfig.java index ee4901a..fab0f27 100644 --- a/src/main/java/de/vorb/platon/security/SecurityConfig.java +++ b/src/main/java/de/vorb/platon/security/SecurityConfig.java @@ -16,7 +16,17 @@ package de.vorb.platon.security; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.time.Clock; + @Configuration -public class SecurityConfig {} +public class SecurityConfig { + + @Bean + public SignatureTokenValidator signatureTokenValidator(SecretKeyProvider secretKeyProvider, Clock clock) { + return new HmacSignatureTokenValidator(secretKeyProvider, clock); + } + +} diff --git a/src/main/java/de/vorb/platon/services/feedreader/FeedReader.java b/src/main/java/de/vorb/platon/services/feedreader/FeedReader.java new file mode 100644 index 0000000..cd6d1ee --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feedreader/FeedReader.java @@ -0,0 +1,90 @@ +package de.vorb.platon.services.feedreader; + +import de.vorb.platon.persistence.ThreadRepository; +import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; + +import com.rometools.rome.feed.synd.SyndEntry; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.feed.synd.SyndLink; +import com.rometools.rome.io.FeedException; +import com.rometools.rome.io.SyndFeedInput; +import com.rometools.rome.io.XmlReader; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FeedReader { + + private static final String REL_ALTERNATE = "alternate"; + + private final ThreadRepository threadRepository; + + @Transactional + void importFeed(URI feedUrl) { + + try (final CloseableHttpClient client = HttpClients.createMinimal()) { + + final HttpUriRequest request = new HttpGet(feedUrl); + + try (final CloseableHttpResponse response = client.execute(request); + final InputStream content = response.getEntity().getContent()) { + + final SyndFeed feed = parseFeed(content); + + feed.getEntries().forEach(this::handleEntry); + + } catch (FeedException e) { + log.info("Feed at {} is in invalid format", feedUrl); + } + } catch (IOException e) { + log.debug("Could not GET feed at {}", feedUrl); + } + } + + private SyndFeed parseFeed(InputStream content) throws FeedException, IOException { + final SyndFeedInput syndFeedInput = new SyndFeedInput(); + return syndFeedInput.build(new XmlReader(content)); + } + + private void handleEntry(SyndEntry feedEntry) { + + final List entryLinks = feedEntry.getLinks(); + + if (entryLinks != null && !entryLinks.isEmpty()) { + + final Stream alternateLinks = entryLinks.stream() + .filter(link -> REL_ALTERNATE.equalsIgnoreCase(link.getRel())); + + alternateLinks.findFirst().ifPresent(entryLink -> { + final String threadUrl = entryLink.getHref(); + final Optional existingThread = threadRepository.findThreadForUrl(threadUrl); + if (!existingThread.isPresent()) { + threadRepository.insert(new CommentThread().setTitle(feedEntry.getTitle()).setUrl(threadUrl)); + log.debug("Inserted new thread for URL {}", threadUrl); + } else { + if (!existingThread.get().getTitle().equals(feedEntry.getTitle())) { + threadRepository.updateThreadTitle(existingThread.get().getId(), feedEntry.getTitle()); + } + } + }); + } + + } + +} diff --git a/src/main/java/de/vorb/platon/services/feedreader/FeedReaderConfiguration.java b/src/main/java/de/vorb/platon/services/feedreader/FeedReaderConfiguration.java new file mode 100644 index 0000000..a9a5014 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feedreader/FeedReaderConfiguration.java @@ -0,0 +1,46 @@ +package de.vorb.platon.services.feedreader; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Configuration +class FeedReaderConfiguration { + + private final FeedReaderProperties feedReaderProperties; + private final FeedReader feedReader; + private final ScheduledExecutorService scheduler; + + public FeedReaderConfiguration(FeedReaderProperties feedReaderProperties, FeedReader feedReader) { + this.feedReaderProperties = feedReaderProperties; + this.feedReader = feedReader; + this.scheduler = Executors.newSingleThreadScheduledExecutor(); + } + + @PostConstruct + void registerTriggers() { + feedReaderProperties.getImportRules().forEach(importRule -> { + try { + final URI feedUrl = importRule.getFeedUrl().toURI(); + final Runnable importFeedJob = () -> feedReader.importFeed(feedUrl); + scheduler.scheduleAtFixedRate(importFeedJob, 0, 10, TimeUnit.MINUTES); + } catch (URISyntaxException e) { + log.warn("URL '{}' cannot be converted to URI", importRule.getFeedUrl(), e); + } + }); + } + + @PreDestroy + void shutdownScheduler() { + scheduler.shutdownNow(); + } + +} diff --git a/src/main/java/de/vorb/platon/services/feedreader/FeedReaderProperties.java b/src/main/java/de/vorb/platon/services/feedreader/FeedReaderProperties.java new file mode 100644 index 0000000..3aaebc6 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feedreader/FeedReaderProperties.java @@ -0,0 +1,22 @@ +package de.vorb.platon.services.feedreader; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +@Data +@Component +@ConfigurationProperties("platon.feed-reader") +class FeedReaderProperties { + + private final List importRules = new ArrayList<>(); + + @Data + static class ImportRule { + private URL feedUrl; + } +} diff --git a/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java b/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java index 9a0d475..e213e3b 100644 --- a/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java +++ b/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java @@ -16,7 +16,7 @@ package de.vorb.platon.web.api.common; -import de.vorb.platon.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.model.CommentStatus; import de.vorb.platon.web.api.json.CommentJson; diff --git a/src/main/java/de/vorb/platon/web/api/common/CommentFilters.java b/src/main/java/de/vorb/platon/web/api/common/CommentFilters.java index 39b0dad..0617d3f 100644 --- a/src/main/java/de/vorb/platon/web/api/common/CommentFilters.java +++ b/src/main/java/de/vorb/platon/web/api/common/CommentFilters.java @@ -16,7 +16,7 @@ package de.vorb.platon.web.api.common; -import de.vorb.platon.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.model.CommentStatus; import org.springframework.stereotype.Component; diff --git a/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java b/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java index 9bc2884..ca67b6e 100644 --- a/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java +++ b/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java @@ -16,7 +16,7 @@ package de.vorb.platon.web.api.common; -import de.vorb.platon.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import com.google.common.collect.ImmutableSet; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/de/vorb/platon/web/api/controllers/CommentAction.java b/src/main/java/de/vorb/platon/web/api/controllers/CommentAction.java new file mode 100644 index 0000000..cb9d3bd --- /dev/null +++ b/src/main/java/de/vorb/platon/web/api/controllers/CommentAction.java @@ -0,0 +1,8 @@ +package de.vorb.platon.web.api.controllers; + +public enum CommentAction { + CREATE, + PREVIEW, + UPDATE, + DELETE +} diff --git a/src/main/java/de/vorb/platon/web/api/controllers/CommentController.java b/src/main/java/de/vorb/platon/web/api/controllers/CommentController.java index 4365dc0..7d71702 100644 --- a/src/main/java/de/vorb/platon/web/api/controllers/CommentController.java +++ b/src/main/java/de/vorb/platon/web/api/controllers/CommentController.java @@ -16,65 +16,60 @@ package de.vorb.platon.web.api.controllers; -import de.vorb.platon.jooq.tables.pojos.Comment; -import de.vorb.platon.jooq.tables.pojos.CommentThread; import de.vorb.platon.model.CommentStatus; import de.vorb.platon.persistence.CommentRepository; import de.vorb.platon.persistence.ThreadRepository; -import de.vorb.platon.security.SignatureComponents; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; import de.vorb.platon.security.SignatureCreator; -import de.vorb.platon.web.api.common.CommentConverter; import de.vorb.platon.web.api.common.CommentFilters; import de.vorb.platon.web.api.common.CommentSanitizer; import de.vorb.platon.web.api.common.CommentUriResolver; import de.vorb.platon.web.api.common.RequestValidator; import de.vorb.platon.web.api.errors.RequestException; -import de.vorb.platon.web.api.json.CommentJson; -import de.vorb.platon.web.api.json.CommentListResultJson; +import com.google.common.collect.ImmutableMap; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.jooq.exception.DataAccessException; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; -import java.net.URI; import java.time.Clock; -import java.time.Instant; -import java.util.ArrayList; +import java.time.ZoneId; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.stream.Collectors; -import static de.vorb.platon.model.CommentStatus.DELETED; import static de.vorb.platon.model.CommentStatus.PUBLIC; -import static java.time.temporal.ChronoUnit.HOURS; -import static org.springframework.http.HttpStatus.CONFLICT; -import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_HTML_VALUE; -@RestController +@Controller @RequiredArgsConstructor @Slf4j public class CommentController { - private static final String PATH_LIST = "/api/comments"; + private static final String PATH_LIST = "/comments"; public static final String PATH_VAR_COMMENT_ID = "commentId"; - public static final String PATH_SINGLE = PATH_LIST + "/{" + PATH_VAR_COMMENT_ID + "}"; + public static final String PATH_SINGLE = PATH_LIST + "/{" + PATH_VAR_COMMENT_ID + "}.html"; private static final String SIGNATURE_HEADER = "X-Signature"; private static final CommentStatus DEFAULT_STATUS = PUBLIC; + private static final ZoneId UTC = ZoneId.of("UTC"); + private final Clock clock; @@ -82,15 +77,14 @@ public class CommentController { private final CommentRepository commentRepository; private final SignatureCreator signatureCreator; - private final CommentConverter commentConverter; private final CommentUriResolver commentUriResolver; private final RequestValidator requestValidator; private final CommentFilters commentFilters; private final CommentSanitizer commentSanitizer; - @GetMapping(value = PATH_SINGLE, produces = APPLICATION_JSON_UTF8_VALUE) - public CommentJson getCommentById(@PathVariable(PATH_VAR_COMMENT_ID) long commentId) { + @GetMapping(value = PATH_SINGLE, produces = TEXT_HTML_VALUE) + public ModelAndView getCommentById(@PathVariable(PATH_VAR_COMMENT_ID) long commentId) { final Comment comment = commentRepository.findById(commentId) .filter(c -> c.getStatus() == PUBLIC) @@ -99,180 +93,211 @@ public CommentJson getCommentById(@PathVariable(PATH_VAR_COMMENT_ID) long commen .message("No comment found with ID = " + commentId) .build()); - return commentConverter.convertPojoToJson(comment); + return new ModelAndView("comment-single", Collections.singletonMap("comment", comment)); } - @GetMapping(value = PATH_LIST, produces = APPLICATION_JSON_UTF8_VALUE) - public CommentListResultJson findCommentsByThreadUrl(@RequestParam("threadUrl") String threadUrl) { + @GetMapping(value = PATH_LIST, produces = TEXT_HTML_VALUE) + public ModelAndView findCommentsByThreadUrl(@RequestParam("threadUrl") String threadUrl) { - final List comments = commentRepository.findByThreadUrl(threadUrl); + final CommentThread thread = threadRepository.findThreadForUrl(threadUrl) + .orElseThrow(() -> RequestException.withStatus(HttpStatus.NOT_FOUND) + .message("No thread exists for URL “" + threadUrl + '”').build()); + final List comments = commentRepository.findByThreadId(thread.getId()); if (comments.isEmpty()) { throw RequestException.notFound() - .message(String.format("No thread found with url = '%s'", threadUrl)) + .message("No thread found with url = '" + threadUrl + "'") .build(); } else { - final long totalCommentCount = comments.stream().filter(commentFilters::doesCommentCount).count(); - final List topLevelComments = transformFlatCommentListToTree(comments); - - return CommentListResultJson.builder() - .totalCommentCount(totalCommentCount) - .comments(topLevelComments) - .build(); - } - } - - private List transformFlatCommentListToTree(List comments) { - - final Map lookupMap = comments.stream() - .map(commentConverter::convertPojoToJson) - .collect(Collectors.toMap(CommentJson::getId, Function.identity())); + final long commentCount = comments.stream().filter(commentFilters::doesCommentCount).count(); - final List topLevelComments = new ArrayList<>(); - for (final Comment comment : comments) { - final List commentList; - if (comment.getParentId() == null) { - commentList = topLevelComments; - } else { - commentList = lookupMap.get(comment.getParentId()).getReplies(); - } - commentList.add(lookupMap.get(comment.getId())); - } - - return topLevelComments; - } - - @PostMapping(value = PATH_LIST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity postComment( - @RequestParam("threadUrl") String threadUrl, - @RequestParam("threadTitle") String threadTitle, - @RequestBody CommentJson commentJson) { + final Map commentsById = comments.stream() + .collect(Collectors.toMap(Comment::getId, Function.identity(), throwingMerger(), + LinkedHashMap::new)); - if (commentJson.getId() != null) { - throw RequestException.badRequest() - .message("Comment ID is not null") - .build(); + return new ModelAndView("comments-flat", + ImmutableMap.of("commentCount", commentCount, "comments", commentsById)); } - - final long threadId = threadRepository.findThreadIdForUrl(threadUrl) - .orElseGet(() -> { - final CommentThread thread = new CommentThread() - .setUrl(threadUrl) - .setTitle(threadTitle); - - final long newThreadId = threadRepository.insert(thread).getId(); - - log.info("Created new thread for url '{}'", threadUrl); - - return newThreadId; - }); - - commentJson.setStatus(DEFAULT_STATUS); - - Comment comment = commentConverter.convertJsonToPojo(commentJson); - - comment.setThreadId(threadId); - comment.setCreationDate(clock.instant()); - comment.setLastModificationDate(comment.getCreationDate()); - - assertParentBelongsToSameThread(comment); - - commentSanitizer.sanitizeComment(comment); - - comment = commentRepository.insert(comment); - - log.info("Posted new comment to thread '{}'", threadUrl); - - final URI commentUri = commentUriResolver.createRelativeCommentUriForId(comment.getId()); - final Instant expirationTime = comment.getCreationDate().plus(24, HOURS); - final SignatureComponents signatureComponents = - signatureCreator.createSignatureComponents(commentUri.toString(), expirationTime); - - return ResponseEntity.created(commentUri) - .header(SIGNATURE_HEADER, signatureComponents.toString()) - .body(commentConverter.convertPojoToJson(comment)); } - private void assertParentBelongsToSameThread(Comment comment) { - - final Long parentId = comment.getParentId(); - if (parentId == null) { - return; - } - - final Comment parentComment = commentRepository.findById(parentId) - .orElseThrow(() -> - RequestException.badRequest() - .message("Parent comment does not exist") - .build()); - - if (!comment.getThreadId().equals(parentComment.getThreadId())) { - throw RequestException.badRequest() - .message("Parent comment does not belong to same thread") - .build(); - } - + private static BinaryOperator throwingMerger() { + return (a, b) -> { + throw new IllegalStateException("Duplicate key " + a); + }; } - - @PutMapping(value = PATH_SINGLE, consumes = APPLICATION_JSON_VALUE) - public void updateComment( - @PathVariable(PATH_VAR_COMMENT_ID) Long commentId, - @RequestHeader(SIGNATURE_HEADER) String signature, - @RequestBody CommentJson commentJson) { - - if (!commentId.equals(commentJson.getId())) { - throw RequestException.badRequest() - .message(String.format("Comment IDs do not match (%d != %d)", commentJson.getId(), commentId)) - .build(); - } - - final String commentUri = commentUriResolver.createRelativeCommentUriForId(commentId).toString(); - requestValidator.verifyValidRequest(signature, commentUri); - - final Comment comment = commentRepository.findById(commentId) - .orElseThrow(() -> - RequestException.badRequest() - .message(String.format("Comment with ID = %d does not exist", commentId)) - .build()); - - comment.setText(commentJson.getText()); - comment.setAuthor(comment.getAuthor()); - comment.setUrl(comment.getUrl()); - - comment.setLastModificationDate(clock.instant()); - - commentSanitizer.sanitizeComment(comment); - - try { - commentRepository.update(comment); - } catch (DataAccessException e) { - throw RequestException.withStatus(CONFLICT) - .message(String.format("Conflict on update of comment with ID = %d", commentId)) - .cause(e) - .build(); - } + @GetMapping(value = "/reply", produces = TEXT_HTML_VALUE, params = "threadTitle") + public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, + @RequestParam("threadTitle") String threadTitle) { + return new ModelAndView("comment-form", ImmutableMap.of("threadUrl", threadUrl, "threadTitle", threadTitle)); } - @DeleteMapping(PATH_SINGLE) - public void deleteComment( - @PathVariable(PATH_VAR_COMMENT_ID) Long commentId, - @RequestHeader(SIGNATURE_HEADER) String signature) { - - final URI commentUri = commentUriResolver.createRelativeCommentUriForId(commentId); - - requestValidator.verifyValidRequest(signature, commentUri.toString()); - - try { - commentRepository.setStatus(commentId, DELETED); - - log.info("Marked comment with ID = {} as {}", commentId, DELETED); - } catch (DataAccessException e) { - throw RequestException.badRequest() - .message(String.format("Unable to delete comment with ID = %d. Does it exist?", commentId)) - .cause(e) - .build(); - } - } +// @PostMapping(value = PATH_LIST, produces = TEXT_HTML_VALUE) +// public ModelAndView postComment( +// @RequestBody MultiValueMap commentData) { +// +// final CommentAction action = Optional.ofNullable(commentData.getFirst("action")) +// .map(String::toUpperCase) +// .map(CommentAction::valueOf) +// .orElse(CommentAction.CREATE); +// +// final String commentText = commentData.getFirst("commentText"); +// final String commentAuthor = commentData.getFirst("commentAuthor"); +// final String commentUrl = commentData.getFirst("commentUrl"); +// +// final String threadUrl = commentData.getFirst("threadUrl"); +// final String commentTitle = commentData.getFirst("commentTitle"); +// +// final Long threadId = threadRepository.findThreadIdForUrl(threadUrl) +// .orElseGet(() -> { +// final CommentThread thread = new CommentThread() +// .setUrl(threadUrl) +// .setTitle(commentTitle); +// +// final long newThreadId = threadRepository.insert(thread).getId(); +// log.info("Created new thread for url '{}'", threadUrl); +// return newThreadId; +// }); +// +// if (action == CommentAction.CREATE) { +// +// } +// } +// +// @PostMapping(value = PATH_LIST, consumes = APPLICATION_FORM_URLENCODED_VALUE, produces = TEXT_HTML_VALUE) +// public ModelAndView postComment( +// @RequestParam("url") String threadUrl, +// @RequestParam("threadTitle") String threadTitle, +// @RequestParam(value = "action", defaultValue = "CREATE") CommentAction action, +// @RequestBody MultiValueMap commentData) { +// +// if (commentData.getId() != null) { +// throw RequestException.badRequest() +// .message("Comment ID is not null") +// .build(); +// } +// +// final long threadId = threadRepository.findThreadIdForUrl(threadUrl) +// .orElseGet(() -> { +// final CommentThread thread = new CommentThread() +// .setUrl(threadUrl) +// .setTitle(threadTitle); +// +// final long newThreadId = threadRepository.insert(thread).getId(); +// +// log.info("Created new thread for url '{}'", threadUrl); +// +// return newThreadId; +// }); +// +// commentData.setStatus(DEFAULT_STATUS); +// +// Comment comment = commentConverter.convertJsonToPojo(commentData); +// +// comment.setThreadId(threadId); +// comment.setCreationDate(LocalDateTime.ofInstant(clock.instant(), UTC)); +// comment.setLastModificationDate(comment.getCreationDate()); +// +// assertParentBelongsToSameThread(comment); +// +// commentSanitizer.sanitizeComment(comment); +// +// comment = commentRepository.insert(comment); +// +// log.info("Posted new comment to thread '{}'", threadUrl); +// +// final URI commentUri = commentUriResolver.createRelativeCommentUriForId(comment.getId()); +// final LocalDateTime expirationTime = comment.getCreationDate().plusDays(1); +// final SignatureComponents signatureComponents = +// signatureCreator.createSignatureComponents(commentUri.toString(), +// expirationTime.toInstant(ZoneOffset.UTC)); +// +// return ResponseEntity.created(commentUri) +// .header(SIGNATURE_HEADER, signatureComponents.toString()) +// .body(commentConverter.convertPojoToJson(comment)); +// } +// +// private void assertParentBelongsToSameThread(Comment comment) { +// +// final Long parentId = comment.getParentId(); +// if (parentId == null) { +// return; +// } +// +// final Comment parentComment = commentRepository.findById(parentId) +// .orElseThrow(() -> +// RequestException.badRequest() +// .message("Parent comment does not exist") +// .build()); +// +// if (!comment.getThreadId().equals(parentComment.getThreadId())) { +// throw RequestException.badRequest() +// .message("Parent comment does not belong to same thread") +// .build(); +// } +// +// } +// +// +// @PutMapping(value = PATH_SINGLE, consumes = APPLICATION_JSON_VALUE) +// public void updateComment( +// @PathVariable(PATH_VAR_COMMENT_ID) Long commentId, +// @RequestHeader(SIGNATURE_HEADER) String signature, +// @RequestBody CommentJson commentJson) { +// +// if (!commentId.equals(commentJson.getId())) { +// throw RequestException.badRequest() +// .message(String.format("Comment IDs do not match (%d != %d)", commentJson.getId(), commentId)) +// .build(); +// } +// +// final String commentUri = commentUriResolver.createRelativeCommentUriForId(commentId).toString(); +// requestValidator.verifyValidRequest(signature, commentUri); +// +// final Comment comment = commentRepository.findById(commentId) +// .orElseThrow(() -> +// RequestException.badRequest() +// .message(String.format("Comment with ID = %d does not exist", commentId)) +// .build()); +// +// comment.setText(commentJson.getText()); +// comment.setAuthor(comment.getAuthor()); +// comment.setUrl(comment.getUrl()); +// +// comment.setLastModificationDate(clock.instant()); +// +// commentSanitizer.sanitizeComment(comment); +// +// try { +// commentRepository.update(comment); +// } catch (DataAccessException e) { +// throw RequestException.withStatus(CONFLICT) +// .message(String.format("Conflict on update of comment with ID = %d", commentId)) +// .cause(e) +// .build(); +// } +// } +// +// +// @DeleteMapping(PATH_SINGLE) +// public void deleteComment( +// @PathVariable(PATH_VAR_COMMENT_ID) Long commentId, +// @RequestHeader(SIGNATURE_HEADER) String signature) { +// +// final URI commentUri = commentUriResolver.createRelativeCommentUriForId(commentId); +// +// requestValidator.verifyValidRequest(signature, commentUri.toString()); +// +// try { +// commentRepository.setStatus(commentId, DELETED); +// +// log.info("Marked comment with ID = {} as {}", commentId, DELETED); +// } catch (DataAccessException e) { +// throw RequestException.badRequest() +// .message(String.format("Unable to delete comment with ID = %d. Does it exist?", commentId)) +// .cause(e) +// .build(); +// } +// } } diff --git a/src/main/java/de/vorb/platon/web/api/errors/ErrorCode.java b/src/main/java/de/vorb/platon/web/api/errors/ErrorCode.java deleted file mode 100644 index 38053bb..0000000 --- a/src/main/java/de/vorb/platon/web/api/errors/ErrorCode.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.web.api.errors; - -public interface ErrorCode { - String getIdentifier(); -} diff --git a/src/main/java/de/vorb/platon/web/api/errors/RequestException.java b/src/main/java/de/vorb/platon/web/api/errors/RequestException.java index 1d3eb18..1a7150d 100644 --- a/src/main/java/de/vorb/platon/web/api/errors/RequestException.java +++ b/src/main/java/de/vorb/platon/web/api/errors/RequestException.java @@ -16,18 +16,16 @@ package de.vorb.platon.web.api.errors; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.base.Preconditions; import lombok.Getter; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; public class RequestException extends RuntimeException { @Getter private final int status; - RequestException(int status, String message, Throwable cause) { + protected RequestException(int status, String message, Throwable cause) { super(message, cause); Preconditions.checkArgument(isHttpErrorStatus(status), @@ -43,19 +41,10 @@ private static boolean isHttpErrorStatus(int status) { /** * @throws IllegalArgumentException if {@link HttpStatus} has no constant for the specified numeric withStatus code */ - @JsonIgnore public HttpStatus getHttpStatus() { return HttpStatus.valueOf(status); } - public RequestExceptionJson toJson() { - return new RequestExceptionJson(status, getMessage(), getCause() == null ? null : getCause().getMessage()); - } - - public ResponseEntity toResponseEntity() { - return ResponseEntity.status(status).body(toJson()); - } - public static Builder withStatus(int status) { return new Builder(status); } diff --git a/src/main/java/de/vorb/platon/web/api/errors/RequestExceptionHandler.java b/src/main/java/de/vorb/platon/web/api/errors/RequestExceptionHandler.java index a124e92..1570428 100644 --- a/src/main/java/de/vorb/platon/web/api/errors/RequestExceptionHandler.java +++ b/src/main/java/de/vorb/platon/web/api/errors/RequestExceptionHandler.java @@ -16,16 +16,20 @@ package de.vorb.platon.web.api.errors; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletResponse; +import java.util.Collections; @ControllerAdvice public class RequestExceptionHandler { @ExceptionHandler(RequestException.class) - public ResponseEntity handleRequestException(RequestException requestException) { - return requestException.toResponseEntity(); + public ModelAndView handleRequestException(RequestException requestException, HttpServletResponse response) { + response.setStatus(requestException.getStatus()); + return new ModelAndView("error", Collections.singletonMap("error", requestException)); } } diff --git a/src/main/java/de/vorb/platon/web/api/errors/RequestExceptionJson.java b/src/main/java/de/vorb/platon/web/api/errors/RequestExceptionJson.java deleted file mode 100644 index 2e90ae2..0000000 --- a/src/main/java/de/vorb/platon/web/api/errors/RequestExceptionJson.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.web.api.errors; - -import com.fasterxml.jackson.annotation.JsonInclude; -import lombok.Data; - -import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; - -@JsonInclude(NON_NULL) -@Data -public class RequestExceptionJson { - private final int status; - private final String message; - private final String cause; -} diff --git a/src/main/java/de/vorb/platon/web/api/json/CommentJson.java b/src/main/java/de/vorb/platon/web/api/json/CommentJson.java index c682e74..c4f50ff 100644 --- a/src/main/java/de/vorb/platon/web/api/json/CommentJson.java +++ b/src/main/java/de/vorb/platon/web/api/json/CommentJson.java @@ -28,7 +28,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -import java.time.Instant; +import java.time.LocalDateTime; import java.util.List; @Data @@ -40,8 +40,8 @@ public class CommentJson { private Long id; private Long parentId; - private Instant creationDate; - private Instant lastModificationDate; + private LocalDateTime creationDate; + private LocalDateTime lastModificationDate; @JsonFormat(shape = JsonFormat.Shape.STRING) private CommentStatus status; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..6c309cd --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,33 @@ +spring.datasource: + url: jdbc:postgresql://localhost:5432/platon + username: postgres + password: postgres +spring.jooq.sql-dialect: postgres + +platon.input.html_elements: > + h1, h2, h3, h4, h5, h6, + br, p, hr, div, span, + a, img, em, strong, + ol, ul, li, + blockquote, code, pre + +platon.feed-reader.import-rules: [] + # - feed-url: https://example.org/blog.atom + +management: + endpoint: + prometheus: + enabled: true + server: + port: 9090 + endpoints: + web: + exposure: + include: + - info + - health + - prometheus + +spring.freemarker.settings: + incompatible_improvements: 2.3.28 + api_builtin_enabled: true diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties deleted file mode 100644 index 099a848..0000000 --- a/src/main/resources/config/application.properties +++ /dev/null @@ -1,11 +0,0 @@ -# database -spring.datasource.url = jdbc:h2:file:./platon;AUTO_SERVER=TRUE -spring.datasource.username = sa -spring.jooq.sql-dialect = H2 - -# input sanitization -platon.input.html_elements = h1, h2, h3, h4, h5, h6,\ - br, p, hr, div, span,\ - a, img, em, strong,\ - ol, ul, li,\ - blockquote, code, pre diff --git a/src/main/resources/db/migration/V0.1.0__initial_schema.sql b/src/main/resources/db/migration/V0.1.0__initial_schema.sql deleted file mode 100644 index 4f38767..0000000 --- a/src/main/resources/db/migration/V0.1.0__initial_schema.sql +++ /dev/null @@ -1,36 +0,0 @@ --- tables -CREATE TABLE threads ( - id BIGINT PRIMARY KEY AUTO_INCREMENT, - url VARCHAR(256) NOT NULL, - title VARCHAR(512) NULL -); - -CREATE TABLE comments ( - id BIGINT PRIMARY KEY AUTO_INCREMENT, - thread_id BIGINT NOT NULL, - parent_id BIGINT NULL, - creation_date TIMESTAMP NOT NULL, - last_modification_date TIMESTAMP NOT NULL, - status VARCHAR(32) NOT NULL, - text TEXT NOT NULL, - author VARCHAR(128) NULL, - email_hash CHAR(32) NULL, - url VARCHAR(256) NULL, - FOREIGN KEY (thread_id) REFERENCES threads (id), - FOREIGN KEY (parent_id) REFERENCES comments (id) -); - --- indexes -CREATE INDEX thread_id_idx - ON threads (id); -CREATE INDEX thread_url_idx - ON threads (url); - -CREATE INDEX comment_id_idx - ON comments (id); -CREATE INDEX comment_thread_id_idx - ON comments (thread_id); -CREATE INDEX comment_creation_date_idx - ON comments (creation_date); -CREATE INDEX comment_status_idx - ON comments (status); diff --git a/src/main/resources/db/migration/V0.2.0__persistent_secret_key.sql b/src/main/resources/db/migration/V0.2.0__persistent_secret_key.sql deleted file mode 100644 index d58124d..0000000 --- a/src/main/resources/db/migration/V0.2.0__persistent_secret_key.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE properties ( - key VARCHAR(32) PRIMARY KEY, - value VARCHAR(256) NOT NULL -); diff --git a/src/main/resources/db/migration/V0.2.1__singular_table_names.sql b/src/main/resources/db/migration/V0.2.1__singular_table_names.sql deleted file mode 100644 index d5e519b..0000000 --- a/src/main/resources/db/migration/V0.2.1__singular_table_names.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE comments RENAME TO comment; -ALTER TABLE threads RENAME TO comment_thread; -ALTER TABLE properties RENAME TO property; diff --git a/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql b/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql new file mode 100644 index 0000000..4b60cba --- /dev/null +++ b/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql @@ -0,0 +1,35 @@ +-- tables +CREATE TABLE comment_thread ( + id BIGSERIAL PRIMARY KEY, + url VARCHAR(256) UNIQUE NOT NULL, + title VARCHAR(512) NULL +); + +CREATE TABLE comment ( + id BIGSERIAL PRIMARY KEY, + thread_id BIGINT NOT NULL, + parent_id BIGINT NULL, + creation_date TIMESTAMP NOT NULL, + last_modification_date TIMESTAMP NOT NULL, + status VARCHAR(32) NOT NULL, + text TEXT NOT NULL, + author VARCHAR(128) NULL, + email_hash CHAR(32) NULL, + url VARCHAR(256) NULL, + FOREIGN KEY (thread_id) REFERENCES comment_thread (id), + FOREIGN KEY (parent_id) REFERENCES comment (id) +); + +CREATE TABLE property ( + key VARCHAR(32) PRIMARY KEY, + value VARCHAR(256) NOT NULL +); + +-- indexes +CREATE INDEX idx__comment__thread_id + ON comment (thread_id); +CREATE INDEX idx__comment__creation_date + ON comment (creation_date); +CREATE INDEX idx__comment__status + ON comment (status); + diff --git a/src/main/resources/templates/comment-form.ftl b/src/main/resources/templates/comment-form.ftl new file mode 100644 index 0000000..0b40362 --- /dev/null +++ b/src/main/resources/templates/comment-form.ftl @@ -0,0 +1,77 @@ + + + + + + + + + Leave a comment | Platon + + +
+

+ Leave a comment for “${threadTitle}” +

+ +
+

Replying to comment

+
+ +
+
+ by Paul Vorbach on + +
+

This is the content of our first comment. Please handle with care. This side up!

+
+
+
+ +
+
+ + +
+
+ +
+
+
👤
+
+ +
+
+
+ +
+
+
🔗
+
+ +
+
+
+
+ + +
+
+
+ + +
+ + + +
+
+ + diff --git a/src/main/resources/templates/comments-flat.ftl b/src/main/resources/templates/comments-flat.ftl new file mode 100644 index 0000000..39cdaf4 --- /dev/null +++ b/src/main/resources/templates/comments-flat.ftl @@ -0,0 +1,28 @@ +<#ftl output_format="HTML"/> +<#include "snippets/base.ftl"/> +<#include "snippets/comment.ftl"/> + +<#macro page_title> + Comments + + +<#macro page_header> + Comments for “Fast Front End + Development Cycle with Spring Boot” + + +<#macro page_content> +
+
+ Leave a comment +
+ <#list comments as id, comment> + <@page_comment comments comment/> + + +
+ + +<@page/> diff --git a/src/main/resources/templates/error.ftl b/src/main/resources/templates/error.ftl new file mode 100644 index 0000000..0e2f992 --- /dev/null +++ b/src/main/resources/templates/error.ftl @@ -0,0 +1,22 @@ +<#ftl output_format="HTML"/> +<#include "snippets/base.ftl"/> + +<#macro page_title> + Error ${error.status} + + +<#macro page_header> + + +<#macro page_content> +
+

Error ${error.status}

+

${error.message}

+ <#if error.cause??> +

${error.cause.message}

+ + Go back to homepage +
+ + +<@page/> diff --git a/src/main/resources/templates/snippets/base.ftl b/src/main/resources/templates/snippets/base.ftl new file mode 100644 index 0000000..34fa5f7 --- /dev/null +++ b/src/main/resources/templates/snippets/base.ftl @@ -0,0 +1,58 @@ +<#ftl output_format="HTML"/> + +<#macro page_title> + + + +<#macro page_header> + + + +<#macro page_content> + + + +<#macro page_comment comments comment> + + + +<#macro page> + + + + + + + + + + <@page_title/> + + +
+
+

+ <@page_header/> +

+
+ + <@page_content/> +
+ + + diff --git a/src/main/resources/templates/snippets/comment.ftl b/src/main/resources/templates/snippets/comment.ftl new file mode 100644 index 0000000..72bfd30 --- /dev/null +++ b/src/main/resources/templates/snippets/comment.ftl @@ -0,0 +1,47 @@ +<#ftl output_format="HTML"/> + +<#macro page_comment comments comment> +
+ +
+
+ <#if comment.author?? && comment.url??> + by ${comment.author} + <#elseif comment.author??> + by ${comment.author} + <#elseif comment.url??> + by an anonymous user + <#else> + by an anonymous user + + on + + (Permalink) + + <#if comment.parentId??> + <#assign parentComment=comments?api.get(comment.parentId)/> + <#assign clippedParentText=parentComment.text?replace('<[^>]+>', '', 'r')[0..*30]/> + + +
+ +
+ ${comment.text?no_esc} +
+ + +
+
+ diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java index 9896ffc..78aeac3 100644 --- a/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java @@ -16,13 +16,13 @@ package de.vorb.platon.web.api.common; -import de.vorb.platon.jooq.tables.pojos.Comment; import de.vorb.platon.model.CommentStatus; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.web.api.json.CommentJson; import org.junit.Test; -import java.time.Instant; +import java.time.LocalDateTime; import java.util.Base64; import java.util.Optional; @@ -116,8 +116,8 @@ private Comment prepareComment() { return new Comment() .setId(15L) .setParentId(13L) - .setCreationDate(Instant.now().minusSeconds(53)) - .setLastModificationDate(Instant.now()) + .setCreationDate(LocalDateTime.now().minusSeconds(53)) + .setLastModificationDate(LocalDateTime.now()) .setText("Some text") .setAuthor("Jane Doe") .setEmailHash("DBe/ZuZJBwFncB0tPNcXEQ==") @@ -184,8 +184,8 @@ private CommentJson.CommentJsonBuilder prepareJson() { return CommentJson.builder() .id(15L) .parentId(13L) - .creationDate(Instant.now().minusSeconds(53)) - .lastModificationDate(Instant.now()) + .creationDate(LocalDateTime.now().minusSeconds(53)) + .lastModificationDate(LocalDateTime.now()) .status(CommentStatus.PUBLIC) .text("Some text") .author("Jane Doe") diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentFiltersTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentFiltersTest.java index d0d9826..b197704 100644 --- a/src/test/java/de/vorb/platon/web/api/common/CommentFiltersTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/CommentFiltersTest.java @@ -16,7 +16,7 @@ package de.vorb.platon.web.api.common; -import de.vorb.platon.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.model.CommentStatus; import org.junit.Test; diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java index 6172230..a1432fe 100644 --- a/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java @@ -16,7 +16,7 @@ package de.vorb.platon.web.api.common; -import de.vorb.platon.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerDeleteTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerDeleteTest.java index dd81f36..d0261ce 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerDeleteTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerDeleteTest.java @@ -31,30 +31,30 @@ public class CommentControllerDeleteTest extends CommentControllerTest { - @Test - public void setsCommentStatusToDeletedIfRequestIsValid() { - - final long commentId = 1234; - final String signature = "signature"; - assertThatCode(() -> commentController.deleteComment(commentId, signature)) - .doesNotThrowAnyException(); - - verify(requestValidator).verifyValidRequest(eq(signature), eq("/api/comments/" + commentId)); - verify(commentRepository).setStatus(eq(commentId), eq(DELETED)); - } - - @Test - public void throwsBadRequestIfErrorOccursDuringSaving() { - - final long commentId = 1234; - final String signature = "signature"; - - doThrow(DataAccessException.class) - .when(commentRepository).setStatus(eq(commentId), eq(DELETED)); - - assertThatExceptionOfType(RequestException.class) - .isThrownBy(() -> commentController.deleteComment(commentId, signature)) - .matches(requestException -> requestException.getHttpStatus() == BAD_REQUEST) - .withMessageContaining("Unable to delete comment"); - } +// @Test +// public void setsCommentStatusToDeletedIfRequestIsValid() { +// +// final long commentId = 1234; +// final String signature = "signature"; +// assertThatCode(() -> commentController.deleteComment(commentId, signature)) +// .doesNotThrowAnyException(); +// +// verify(requestValidator).verifyValidRequest(eq(signature), eq("/api/comments/" + commentId)); +// verify(commentRepository).setStatus(eq(commentId), eq(DELETED)); +// } + +// @Test +// public void throwsBadRequestIfErrorOccursDuringSaving() { +// +// final long commentId = 1234; +// final String signature = "signature"; +// +// doThrow(DataAccessException.class) +// .when(commentRepository).setStatus(eq(commentId), eq(DELETED)); +// +// assertThatExceptionOfType(RequestException.class) +// .isThrownBy(() -> commentController.deleteComment(commentId, signature)) +// .matches(requestException -> requestException.getHttpStatus() == BAD_REQUEST) +// .withMessageContaining("Unable to delete comment"); +// } } diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerFindByThreadUrlTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerFindByThreadUrlTest.java index 0d7661b..a7de5ba 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerFindByThreadUrlTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerFindByThreadUrlTest.java @@ -16,25 +16,17 @@ package de.vorb.platon.web.api.controllers; -import de.vorb.platon.jooq.tables.pojos.Comment; import de.vorb.platon.web.api.errors.RequestException; import de.vorb.platon.web.api.json.CommentJson; -import de.vorb.platon.web.api.json.CommentListResultJson; import org.junit.Test; import org.mockito.Mock; import org.springframework.http.HttpStatus; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class CommentControllerFindByThreadUrlTest extends CommentControllerTest { @@ -44,32 +36,33 @@ public class CommentControllerFindByThreadUrlTest extends CommentControllerTest @Mock private CommentJson childCommentJson; - @Test - public void returnsCommentsAsTree() { - - final Comment comment = new Comment().setId(4711L); - final Comment childComment = new Comment().setId(4712L).setParentId(comment.getId()); - - final List comments = Arrays.asList(comment, childComment); - - when(commentRepository.findByThreadUrl(eq(THREAD_URL))).thenReturn(comments); - acceptAllComments(); - - convertCommentToJson(comment, commentJson); - convertCommentToJson(childComment, childCommentJson); - - when(commentJson.getId()).thenReturn(comment.getId()); - when(commentJson.getReplies()).thenReturn(new ArrayList<>()); - when(childCommentJson.getId()).thenReturn(childComment.getId()); - - final CommentListResultJson resultJson = commentController.findCommentsByThreadUrl(THREAD_URL); - - assertThat(resultJson.getComments()).isEqualTo(Collections.singletonList(commentJson)); - assertThat(resultJson.getComments().get(0).getReplies()).isEqualTo(Collections.singletonList(childCommentJson)); - assertThat(resultJson.getTotalCommentCount()).isEqualTo(comments.size()); - - comments.forEach(c -> verify(commentFilters).doesCommentCount(eq(c))); - } +// @Test +// public void returnsCommentsAsTree() { +// +// final Comment comment = new Comment().setId(4711L); +// final Comment childComment = new Comment().setId(4712L).setParentId(comment.getId()); +// +// final List comments = Arrays.asList(comment, childComment); +// +// when(commentRepository.findByThreadId(eq(THREAD_URL))).thenReturn(comments); +// acceptAllComments(); +// +// convertCommentToJson(comment, commentJson); +// convertCommentToJson(childComment, childCommentJson); +// +// when(commentJson.getId()).thenReturn(comment.getId()); +// when(commentJson.getReplies()).thenReturn(new ArrayList<>()); +// when(childCommentJson.getId()).thenReturn(childComment.getId()); +// +// final CommentListResultJson resultJson = commentController.findCommentsByThreadUrl(THREAD_URL); +// +// assertThat(resultJson.getComments()).isEqualTo(Collections.singletonList(commentJson)); +// assertThat(resultJson.getComments().get(0).getReplies()).isEqualTo(Collections.singletonList +// (childCommentJson)); +// assertThat(resultJson.getTotalCommentCount()).isEqualTo(comments.size()); +// +// comments.forEach(c -> verify(commentFilters).doesCommentCount(eq(c))); +// } private void acceptAllComments() { when(commentFilters.doesCommentCount(any())).thenReturn(true); @@ -78,7 +71,7 @@ private void acceptAllComments() { @Test public void throwsNotFoundIfThreadIsEmpty() { - when(commentRepository.findByThreadUrl(any())).thenReturn(Collections.emptyList()); + when(commentRepository.findByThreadId(any())).thenReturn(Collections.emptyList()); assertThatExceptionOfType(RequestException.class) .isThrownBy(() -> commentController.findCommentsByThreadUrl(THREAD_URL)) diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java index 1ee5993..aa7c74f 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java @@ -16,7 +16,7 @@ package de.vorb.platon.web.api.controllers; -import de.vorb.platon.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.web.api.errors.RequestException; import de.vorb.platon.web.api.json.CommentJson; diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java index 8ce5ce3..0bfaafd 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java @@ -16,9 +16,9 @@ package de.vorb.platon.web.api.controllers; -import de.vorb.platon.jooq.tables.pojos.Comment; import de.vorb.platon.model.CommentStatus; import de.vorb.platon.persistence.CommentRepository; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import org.junit.Before; import org.junit.Test; @@ -31,7 +31,7 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; -import java.time.Instant; +import java.time.LocalDateTime; import java.util.Optional; import static org.mockito.ArgumentMatchers.eq; @@ -45,15 +45,15 @@ @AutoConfigureMockMvc public class CommentControllerIT { - private static final String SAMPLE_CREATION_DATE = "2017-10-06T19:45:23.751Z"; - private static final String SAMPLE_LAST_MODIFICATION_DATE = "2017-10-06T19:48:51.179Z"; + private static final String SAMPLE_CREATION_DATE = "2017-10-06 19:45:23.751"; + private static final String SAMPLE_LAST_MODIFICATION_DATE = "2017-10-06 19:48:51.179"; private static final Comment SAMPLE_COMMENT = new Comment() .setId(4711L) .setThreadId(25L) .setParentId(1336L) - .setCreationDate(Instant.parse(SAMPLE_CREATION_DATE)) - .setLastModificationDate(Instant.parse(SAMPLE_LAST_MODIFICATION_DATE)) + .setCreationDate(LocalDateTime.parse(SAMPLE_CREATION_DATE)) + .setLastModificationDate(LocalDateTime.parse(SAMPLE_LAST_MODIFICATION_DATE)) .setStatus(CommentStatus.PUBLIC) .setText("Sample text") .setAuthor("John Doe") diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerPostTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerPostTest.java index 65564c9..23448d3 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerPostTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerPostTest.java @@ -16,8 +16,8 @@ package de.vorb.platon.web.api.controllers; -import de.vorb.platon.jooq.tables.pojos.Comment; -import de.vorb.platon.jooq.tables.pojos.CommentThread; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; import de.vorb.platon.security.SignatureComponents; import de.vorb.platon.web.api.errors.RequestException; import de.vorb.platon.web.api.json.CommentJson; @@ -58,67 +58,67 @@ public class CommentControllerPostTest extends CommentControllerTest { @Spy private Comment parentComment = new Comment(); - @Test - public void createsNewThreadOnDemand() { - - insertCommentReturnsCommentWithNextId(); - prepareMocksForPostRequest(); - when(comment.getParentId()).thenReturn(null); - - final ResponseEntity response = - commentController.postComment(THREAD_URL, THREAD_TITLE, commentJson); - verify(threadRepository).insert(eq(new CommentThread(null, THREAD_URL, THREAD_TITLE))); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); - assertThatLocationHeaderIsCorrect(response.getHeaders().getLocation()); - } - - @Test - public void verifiesParentBelongsToSameThread() { - - insertCommentReturnsCommentWithNextId(); - prepareMocksForPostRequest(); - - final long parentId = 1L; - when(comment.getParentId()).thenReturn(parentId); - when(commentRepository.findById(eq(parentId))).thenReturn(Optional.of(parentComment)); - when(comment.getThreadId()).thenReturn(1L); - when(parentComment.getThreadId()).thenReturn(2L); - - assertThatExceptionOfType(RequestException.class) - .isThrownBy(() -> commentController.postComment(THREAD_URL, THREAD_TITLE, commentJson)) - .matches(requestException -> requestException.getHttpStatus() == BAD_REQUEST) - .withMessage("Parent comment does not belong to same thread"); - - verify(commentRepository, never()).insert(any()); - - when(parentComment.getThreadId()).thenReturn(1L); - - assertThatCode(() -> commentController.postComment(THREAD_URL, THREAD_TITLE, commentJson)) - .doesNotThrowAnyException(); - - verify(commentRepository).insert(eq(comment)); - } - - @Test - public void verifiesParentCommentExists() { - - insertCommentReturnsCommentWithNextId(); - prepareMocksForPostRequest(); - - final long parentId = 1L; - when(comment.getParentId()).thenReturn(parentId); - when(commentRepository.findById(eq(parentId))).thenReturn(Optional.empty()); - when(comment.getThreadId()).thenReturn(1L); - when(parentComment.getThreadId()).thenReturn(1L); - - assertThatExceptionOfType(RequestException.class) - .isThrownBy(() -> commentController.postComment(THREAD_URL, THREAD_TITLE, commentJson)) - .matches(requestException -> requestException.getHttpStatus() == BAD_REQUEST) - .withMessage("Parent comment does not exist"); - - verify(commentRepository, never()).insert(any()); - } +// @Test +// public void createsNewThreadOnDemand() { +// +// insertCommentReturnsCommentWithNextId(); +// prepareMocksForPostRequest(); +// when(comment.getParentId()).thenReturn(null); +// +// final ResponseEntity response = +// commentController.postComment(THREAD_URL, THREAD_TITLE, commentJson); +// verify(threadRepository).insert(eq(new CommentThread(null, THREAD_URL, THREAD_TITLE))); +// +// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); +// assertThatLocationHeaderIsCorrect(response.getHeaders().getLocation()); +// } + +// @Test +// public void verifiesParentBelongsToSameThread() { +// +// insertCommentReturnsCommentWithNextId(); +// prepareMocksForPostRequest(); +// +// final long parentId = 1L; +// when(comment.getParentId()).thenReturn(parentId); +// when(commentRepository.findById(eq(parentId))).thenReturn(Optional.of(parentComment)); +// when(comment.getThreadId()).thenReturn(1L); +// when(parentComment.getThreadId()).thenReturn(2L); +// +// assertThatExceptionOfType(RequestException.class) +// .isThrownBy(() -> commentController.postComment(THREAD_URL, THREAD_TITLE, commentJson)) +// .matches(requestException -> requestException.getHttpStatus() == BAD_REQUEST) +// .withMessage("Parent comment does not belong to same thread"); +// +// verify(commentRepository, never()).insert(any()); +// +// when(parentComment.getThreadId()).thenReturn(1L); +// +// assertThatCode(() -> commentController.postComment(THREAD_URL, THREAD_TITLE, commentJson)) +// .doesNotThrowAnyException(); +// +// verify(commentRepository).insert(eq(comment)); +// } + +// @Test +// public void verifiesParentCommentExists() { +// +// insertCommentReturnsCommentWithNextId(); +// prepareMocksForPostRequest(); +// +// final long parentId = 1L; +// when(comment.getParentId()).thenReturn(parentId); +// when(commentRepository.findById(eq(parentId))).thenReturn(Optional.empty()); +// when(comment.getThreadId()).thenReturn(1L); +// when(parentComment.getThreadId()).thenReturn(1L); +// +// assertThatExceptionOfType(RequestException.class) +// .isThrownBy(() -> commentController.postComment(THREAD_URL, THREAD_TITLE, commentJson)) +// .matches(requestException -> requestException.getHttpStatus() == BAD_REQUEST) +// .withMessage("Parent comment does not exist"); +// +// verify(commentRepository, never()).insert(any()); +// } private void prepareMocksForPostRequest() { when(commentJson.getId()).thenReturn(null); @@ -136,15 +136,15 @@ private void assertThatLocationHeaderIsCorrect(URI location) { assertThat(location).hasNoParameters(); } - @Test - public void postCommentWithIdThrowsBadRequestException() { - when(commentJson.getId()).thenReturn(1337L); - - assertThatExceptionOfType(RequestException.class) - .isThrownBy(() -> commentController.postComment(THREAD_URL, THREAD_TITLE, commentJson)) - .matches(exception -> exception.getHttpStatus() == BAD_REQUEST) - .withMessage("Comment ID is not null"); - } +// @Test +// public void postCommentWithIdThrowsBadRequestException() { +// when(commentJson.getId()).thenReturn(1337L); +// +// assertThatExceptionOfType(RequestException.class) +// .isThrownBy(() -> commentController.postComment(THREAD_URL, THREAD_TITLE, commentJson)) +// .matches(exception -> exception.getHttpStatus() == BAD_REQUEST) +// .withMessage("Comment ID is not null"); +// } private void insertCommentReturnsCommentWithNextId() { when(commentRepository.insert(any())) diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java index ce24553..ebb316f 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java @@ -16,7 +16,7 @@ package de.vorb.platon.web.api.controllers; -import de.vorb.platon.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.persistence.CommentRepository; import de.vorb.platon.persistence.ThreadRepository; import de.vorb.platon.security.SignatureCreator; @@ -27,15 +27,12 @@ import de.vorb.platon.web.api.common.RequestValidator; import de.vorb.platon.web.api.json.CommentJson; -import org.junit.Before; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import java.net.URI; import java.time.Clock; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @@ -68,14 +65,14 @@ public abstract class CommentControllerTest { @Mock protected CommentSanitizer commentSanitizer; - @Before - public void setUp() throws Exception { - commentController = new CommentController(clock, threadRepository, commentRepository, signatureCreator, - commentConverter, commentUriResolver, requestValidator, commentFilters, commentSanitizer); - - when(commentUriResolver.createRelativeCommentUriForId(anyLong())) - .thenAnswer(invocation -> new URI("/api/comments/" + invocation.getArgument(0))); - } +// @Before +// public void setUp() throws Exception { +// commentController = new CommentController(clock, threadRepository, commentRepository, signatureCreator, +// commentConverter, commentUriResolver, requestValidator, commentFilters, commentSanitizer); +// +// when(commentUriResolver.createRelativeCommentUriForId(anyLong())) +// .thenAnswer(invocation -> new URI("/api/comments/" + invocation.getArgument(0))); +// } protected void convertCommentToJson(Comment comment, CommentJson json) { when(commentConverter.convertPojoToJson(eq(comment))).thenReturn(json); diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java index 745249f..8b2dcac 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java @@ -16,7 +16,7 @@ package de.vorb.platon.web.api.controllers; -import de.vorb.platon.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.security.SignatureComponents; import de.vorb.platon.web.api.errors.RequestException; import de.vorb.platon.web.api.json.CommentJson; @@ -61,79 +61,79 @@ public class CommentControllerUpdateTest extends CommentControllerTest { @Spy private Comment comment = new Comment(); - @Override +// @Override @Before public void setUp() throws Exception { clock = Clock.fixed(SAMPLE_EXPIRATION_TIME.minusMillis(1), ZoneId.of("Z")); - super.setUp(); +// super.setUp(); when(commentJson.getId()).thenReturn(SAMPLE_ID); } - @Test - public void comparesCommentIds() { - - assertThatExceptionOfType(RequestException.class) - .isThrownBy(() -> commentController.updateComment( - SAMPLE_ID + 1, SAMPLE_SIGNATURE.toString(), commentJson)) - .matches(requestException -> requestException.getHttpStatus() == HttpStatus.BAD_REQUEST) - .withMessageStartingWith("Comment IDs do not match"); - } - - @Test - public void updatesCommentIfAllChecksPass() { - - when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.of(comment)); - - commentController.updateComment(SAMPLE_ID, SAMPLE_SIGNATURE.toString(), commentJson); - - verify(requestValidator).verifyValidRequest(eq(SAMPLE_SIGNATURE.toString()), eq(SAMPLE_IDENTIFIER)); - - verify(comment).setText(eq(commentJson.getText())); - verify(comment).setAuthor(eq(commentJson.getAuthor())); - verify(comment).setUrl(eq(commentJson.getUrl())); - - verify(comment).setLastModificationDate(eq(clock.instant())); - - verify(commentRepository).update(eq(comment)); - } - - @Test - public void throwsBadRequestIfCommentDoesNotExist() { - - when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.empty()); - - assertThatExceptionOfType(RequestException.class) - .isThrownBy(() -> commentController.updateComment(SAMPLE_ID, SAMPLE_SIGNATURE.toString(), commentJson)) - .matches(requestException -> requestException.getHttpStatus() == HttpStatus.BAD_REQUEST) - .withMessageMatching("Comment .* does not exist"); - } - - @Test - public void doesNotUpdateCommentIfRequestValidationFails() { - - doThrow(RequestException.class) - .when(requestValidator) - .verifyValidRequest(eq(SAMPLE_SIGNATURE.toString()), eq(SAMPLE_IDENTIFIER)); - when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.of(comment)); - - assertThatExceptionOfType(RequestException.class) - .isThrownBy(() -> commentController.updateComment(SAMPLE_ID, SAMPLE_SIGNATURE.toString(), commentJson)); - - verify(commentRepository, never()).update(any()); - } - - @Test - public void throwsConflictExceptionWhenUpdateFails() { - - when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.of(comment)); - doThrow(DataAccessException.class).when(commentRepository).update(any()); - - assertThatExceptionOfType(RequestException.class) - .isThrownBy(() -> commentController.updateComment(SAMPLE_ID, SAMPLE_SIGNATURE.toString(), commentJson)) - .matches(requestException -> requestException.getHttpStatus() == HttpStatus.CONFLICT) - .withMessageMatching("Conflict .* comment.*"); - } +// @Test +// public void comparesCommentIds() { +// +// assertThatExceptionOfType(RequestException.class) +// .isThrownBy(() -> commentController.updateComment( +// SAMPLE_ID + 1, SAMPLE_SIGNATURE.toString(), commentJson)) +// .matches(requestException -> requestException.getHttpStatus() == HttpStatus.BAD_REQUEST) +// .withMessageStartingWith("Comment IDs do not match"); +// } +// +// @Test +// public void updatesCommentIfAllChecksPass() { +// +// when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.of(comment)); +// +// commentController.updateComment(SAMPLE_ID, SAMPLE_SIGNATURE.toString(), commentJson); +// +// verify(requestValidator).verifyValidRequest(eq(SAMPLE_SIGNATURE.toString()), eq(SAMPLE_IDENTIFIER)); +// +// verify(comment).setText(eq(commentJson.getText())); +// verify(comment).setAuthor(eq(commentJson.getAuthor())); +// verify(comment).setUrl(eq(commentJson.getUrl())); +// +// verify(comment).setLastModificationDate(eq(clock.instant())); +// +// verify(commentRepository).update(eq(comment)); +// } +// +// @Test +// public void throwsBadRequestIfCommentDoesNotExist() { +// +// when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.empty()); +// +// assertThatExceptionOfType(RequestException.class) +// .isThrownBy(() -> commentController.updateComment(SAMPLE_ID, SAMPLE_SIGNATURE.toString(), commentJson)) +// .matches(requestException -> requestException.getHttpStatus() == HttpStatus.BAD_REQUEST) +// .withMessageMatching("Comment .* does not exist"); +// } +// +// @Test +// public void doesNotUpdateCommentIfRequestValidationFails() { +// +// doThrow(RequestException.class) +// .when(requestValidator) +// .verifyValidRequest(eq(SAMPLE_SIGNATURE.toString()), eq(SAMPLE_IDENTIFIER)); +// when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.of(comment)); +// +// assertThatExceptionOfType(RequestException.class) +// .isThrownBy(() -> commentController.updateComment(SAMPLE_ID, SAMPLE_SIGNATURE.toString(), commentJson)); +// +// verify(commentRepository, never()).update(any()); +// } +// +// @Test +// public void throwsConflictExceptionWhenUpdateFails() { +// +// when(commentRepository.findById(eq(SAMPLE_ID))).thenReturn(Optional.of(comment)); +// doThrow(DataAccessException.class).when(commentRepository).update(any()); +// +// assertThatExceptionOfType(RequestException.class) +// .isThrownBy(() -> commentController.updateComment(SAMPLE_ID, SAMPLE_SIGNATURE.toString(), commentJson)) +// .matches(requestException -> requestException.getHttpStatus() == HttpStatus.CONFLICT) +// .withMessageMatching("Conflict .* comment.*"); +// } } diff --git a/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionHandlerTest.java b/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionHandlerTest.java deleted file mode 100644 index ee7bcb0..0000000 --- a/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionHandlerTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.web.api.errors; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class RequestExceptionHandlerTest { - - private final RequestExceptionHandler requestExceptionHandler = new RequestExceptionHandler(); - - @Test - public void handlesRequestException() { - - final RequestException requestException = RequestException.badRequest().build(); - - assertThat(requestExceptionHandler.handleRequestException(requestException)) - .isEqualTo(requestException.toResponseEntity()); - } -} diff --git a/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionTest.java b/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionTest.java index cd2b1fb..c3ae9be 100644 --- a/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionTest.java +++ b/src/test/java/de/vorb/platon/web/api/errors/RequestExceptionTest.java @@ -19,7 +19,6 @@ import org.assertj.core.api.ThrowableAssert; import org.junit.Test; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -46,39 +45,6 @@ public void returnsOriginalStatus() { assertThat(RequestException.withStatus(400).build().getStatus()).isEqualTo(400); } - @Test - public void convertsToJson() { - - final String notFoundMessage = "Not Found"; - final RequestException notFound = - RequestException.notFound() - .message(notFoundMessage) - .build(); - assertThat(notFound.toJson().getStatus()).isEqualTo(notFound.getStatus()); - assertThat(notFound.toJson().getMessage()).isEqualTo(notFoundMessage); - assertThat(notFound.toJson().getCause()).isNull(); - - final IllegalStateException cause = new IllegalStateException("Unexpected state"); - final RequestException internalServerError = - RequestException.badRequest() - .message("Internal Server Error") - .cause(cause) - .build(); - assertThat(internalServerError.toJson().getStatus()).isEqualTo(internalServerError.getStatus()); - assertThat(internalServerError.toJson().getMessage()).isEqualTo(internalServerError.getMessage()); - assertThat(internalServerError.toJson().getCause()).isEqualTo(cause.getMessage()); - } - - @Test - public void toResponseEntity() { - - final RequestException requestException = RequestException.badRequest().build(); - final ResponseEntity response = requestException.toResponseEntity(); - - assertThat(response.getStatusCodeValue()).isEqualTo(requestException.getStatus()); - assertThat(response.getBody()).isEqualTo(requestException.toJson()); - } - @Test public void badRequest() { assertThat(RequestException.badRequest().build().getHttpStatus()).isEqualTo(HttpStatus.BAD_REQUEST); From e98037e44ff3f226069a200f2f98e69f6a4b07a2 Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Wed, 27 Jun 2018 22:43:52 +0200 Subject: [PATCH 04/16] Introduce new URL schema --- .../platon/persistence/CommentRepository.java | 4 +- .../platon/persistence/ThreadRepository.java | 10 +-- .../impl/JooqCommentRepository.java | 10 +++ .../impl/JooqPropertyRepository.java | 2 +- .../impl/JooqThreadRepository.java | 20 ++--- .../vorb/platon/persistence/jooq/Indexes.java | 20 ++--- .../de/vorb/platon/persistence/jooq/Keys.java | 2 + .../persistence/jooq/tables/Comment.java | 9 +-- .../jooq/tables/CommentThread.java | 6 +- .../jooq/tables/pojos/Comment.java | 16 +--- .../jooq/tables/records/CommentRecord.java | 78 ++++--------------- .../services/feedreader/FeedReader.java | 2 +- .../web/api/common/CommentConverter.java | 10 +-- .../platon/web/api/common/CommentFilters.java | 2 +- .../web/api/common/CommentUriResolver.java | 6 +- .../web/api/common/HtmlInputSanitizer.java | 2 +- .../web/api/common/RequestValidator.java | 2 +- .../comments}/CommentAction.java | 2 +- .../comments}/CommentController.java | 32 ++++---- .../comments}/CommentCountsController.java | 2 +- .../{api => mvc}/errors/RequestException.java | 2 +- .../errors/RequestExceptionHandler.java | 2 +- .../web/mvc/threads/CommentController.java | 60 ++++++++++++++ .../web/mvc/threads/ReplyFormController.java | 32 ++++++++ .../mvc/threads/ThreadRedirectController.java | 62 +++++++++++++++ src/main/resources/application.yml | 24 +++--- .../V0.2.2018.06.11__initial_schema.sql | 1 - src/main/resources/static/comment-count.html | 8 -- src/main/resources/static/comment-list.html | 2 - src/main/resources/static/test.html | 14 ---- src/main/resources/templates/comment-form.ftl | 8 +- .../resources/templates/comments-flat.ftl | 13 ++-- src/main/resources/templates/error.ftl | 9 +-- .../resources/templates/snippets/base.ftl | 17 ++-- .../resources/templates/snippets/comment.ftl | 13 ++-- .../web/api/RequestExceptionVerifier.java | 2 +- .../web/api/common/CommentConverterTest.java | 18 ++--- .../web/api/common/CommentFiltersTest.java | 2 +- .../web/api/common/RequestValidatorTest.java | 2 +- .../CommentControllerDeleteTest.java | 13 ---- .../CommentControllerFindByThreadUrlTest.java | 2 +- .../CommentControllerGetByIdTest.java | 2 +- .../api/controllers/CommentControllerIT.java | 2 +- .../CommentControllerPostTest.java | 12 +-- .../controllers/CommentControllerTest.java | 3 +- .../CommentControllerUpdateTest.java | 11 --- .../errors/RequestExceptionTest.java | 2 +- 47 files changed, 306 insertions(+), 269 deletions(-) rename src/main/java/de/vorb/platon/web/{api/controllers => mvc/comments}/CommentAction.java (64%) rename src/main/java/de/vorb/platon/web/{api/controllers => mvc/comments}/CommentController.java (91%) rename src/main/java/de/vorb/platon/web/{api/controllers => mvc/comments}/CommentCountsController.java (97%) rename src/main/java/de/vorb/platon/web/{api => mvc}/errors/RequestException.java (98%) rename src/main/java/de/vorb/platon/web/{api => mvc}/errors/RequestExceptionHandler.java (97%) create mode 100644 src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java create mode 100644 src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java create mode 100644 src/main/java/de/vorb/platon/web/mvc/threads/ThreadRedirectController.java delete mode 100644 src/main/resources/static/comment-count.html delete mode 100644 src/main/resources/static/comment-list.html delete mode 100644 src/main/resources/static/test.html rename src/test/java/de/vorb/platon/web/{api => mvc}/errors/RequestExceptionTest.java (98%) diff --git a/src/main/java/de/vorb/platon/persistence/CommentRepository.java b/src/main/java/de/vorb/platon/persistence/CommentRepository.java index 203aece..44786dd 100644 --- a/src/main/java/de/vorb/platon/persistence/CommentRepository.java +++ b/src/main/java/de/vorb/platon/persistence/CommentRepository.java @@ -16,8 +16,8 @@ package de.vorb.platon.persistence; -import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.model.CommentStatus; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import java.util.List; import java.util.Map; @@ -28,6 +28,8 @@ public interface CommentRepository { List findByThreadId(long threadId); + List findPublicByThreadId(long threadId); + Optional findById(long id); Comment insert(Comment comment); diff --git a/src/main/java/de/vorb/platon/persistence/ThreadRepository.java b/src/main/java/de/vorb/platon/persistence/ThreadRepository.java index 350079e..1a902d8 100644 --- a/src/main/java/de/vorb/platon/persistence/ThreadRepository.java +++ b/src/main/java/de/vorb/platon/persistence/ThreadRepository.java @@ -23,16 +23,16 @@ public interface ThreadRepository { - CommentThread getById(long id); + Optional findById(long id); - Optional findThreadIdForUrl(String threadUrl); + Optional findIdForUrl(String url); - Optional findThreadForUrl(String threadUrl); + Optional findThreadForUrl(String url); - List findThreadsForUrlPrefix(String threadUrlPrefix); + List findThreadsForUrlPrefix(String urlPrefix); CommentThread insert(CommentThread thread); - void updateThreadTitle(long id, String newTitle); + void updateTitle(long id, String title); } diff --git a/src/main/java/de/vorb/platon/persistence/impl/JooqCommentRepository.java b/src/main/java/de/vorb/platon/persistence/impl/JooqCommentRepository.java index d0d7fde..c75c05c 100644 --- a/src/main/java/de/vorb/platon/persistence/impl/JooqCommentRepository.java +++ b/src/main/java/de/vorb/platon/persistence/impl/JooqCommentRepository.java @@ -50,6 +50,16 @@ public List findByThreadId(long threadId) { .fetchInto(Comment.class); } + @Override + public List findPublicByThreadId(long threadId) { + return dslContext.select(COMMENT.fields()) + .from(COMMENT) + .where(COMMENT.THREAD_ID.eq(threadId)) + .and(COMMENT.STATUS.eq(CommentStatus.PUBLIC)) + .orderBy(COMMENT.ID.asc()) + .fetchInto(Comment.class); + } + @Override public Optional findById(long id) { return Optional.ofNullable( diff --git a/src/main/java/de/vorb/platon/persistence/impl/JooqPropertyRepository.java b/src/main/java/de/vorb/platon/persistence/impl/JooqPropertyRepository.java index 29f1c2c..85ecd5d 100644 --- a/src/main/java/de/vorb/platon/persistence/impl/JooqPropertyRepository.java +++ b/src/main/java/de/vorb/platon/persistence/impl/JooqPropertyRepository.java @@ -16,8 +16,8 @@ package de.vorb.platon.persistence.impl; -import de.vorb.platon.persistence.jooq.tables.records.PropertyRecord; import de.vorb.platon.persistence.PropertyRepository; +import de.vorb.platon.persistence.jooq.tables.records.PropertyRecord; import lombok.RequiredArgsConstructor; import org.jooq.DSLContext; diff --git a/src/main/java/de/vorb/platon/persistence/impl/JooqThreadRepository.java b/src/main/java/de/vorb/platon/persistence/impl/JooqThreadRepository.java index 9c5a4c9..3f794f8 100644 --- a/src/main/java/de/vorb/platon/persistence/impl/JooqThreadRepository.java +++ b/src/main/java/de/vorb/platon/persistence/impl/JooqThreadRepository.java @@ -36,30 +36,30 @@ public class JooqThreadRepository implements ThreadRepository { private final DSLContext dslContext; @Override - public CommentThread getById(long id) { + public Optional findById(long id) { return dslContext.selectFrom(COMMENT_THREAD) .where(COMMENT_THREAD.ID.eq(id)) - .fetchSingleInto(CommentThread.class); + .fetchOptionalInto(CommentThread.class); } @Override - public Optional findThreadIdForUrl(String threadUrl) { + public Optional findIdForUrl(String url) { return dslContext.selectFrom(COMMENT_THREAD) - .where(COMMENT_THREAD.URL.eq(threadUrl)) + .where(COMMENT_THREAD.URL.eq(url)) .fetchOptional(COMMENT_THREAD.ID); } @Override - public Optional findThreadForUrl(String threadUrl) { + public Optional findThreadForUrl(String url) { return dslContext.selectFrom(COMMENT_THREAD) - .where(COMMENT_THREAD.URL.eq(threadUrl)) + .where(COMMENT_THREAD.URL.eq(url)) .fetchOptionalInto(CommentThread.class); } @Override - public List findThreadsForUrlPrefix(String threadUrlPrefix) { + public List findThreadsForUrlPrefix(String urlPrefix) { return dslContext.selectFrom(COMMENT_THREAD) - .where(COMMENT_THREAD.URL.startsWith(threadUrlPrefix)) + .where(COMMENT_THREAD.URL.startsWith(urlPrefix)) .orderBy(COMMENT_THREAD.ID.desc()) .fetchInto(CommentThread.class); } @@ -78,9 +78,9 @@ private CommentThreadRecord convertPojoToRecord(CommentThread thread) { } @Override - public void updateThreadTitle(long id, String newTitle) { + public void updateTitle(long id, String title) { dslContext.update(COMMENT_THREAD) - .set(COMMENT_THREAD.TITLE, newTitle) + .set(COMMENT_THREAD.TITLE, title) .where(COMMENT_THREAD.ID.eq(id)); } diff --git a/src/main/java/de/vorb/platon/persistence/jooq/Indexes.java b/src/main/java/de/vorb/platon/persistence/jooq/Indexes.java index 2eea302..c35b1d6 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/Indexes.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/Indexes.java @@ -32,14 +32,12 @@ public class Indexes { // INDEX definitions // ------------------------------------------------------------------------- - public static final Index COMMENT__CREATION_DATE__IDX = Indexes0.COMMENT__CREATION_DATE__IDX; - public static final Index COMMENT__ID__IDX = Indexes0.COMMENT__ID__IDX; - public static final Index COMMENT__STATUS__IDX = Indexes0.COMMENT__STATUS__IDX; - public static final Index COMMENT__THREAD_ID__IDX = Indexes0.COMMENT__THREAD_ID__IDX; public static final Index COMMENT_PKEY = Indexes0.COMMENT_PKEY; - public static final Index COMMENT_THREAD__ID__IDX = Indexes0.COMMENT_THREAD__ID__IDX; - public static final Index COMMENT_THREAD__URL__IDX = Indexes0.COMMENT_THREAD__URL__IDX; + public static final Index IDX__COMMENT__CREATION_DATE = Indexes0.IDX__COMMENT__CREATION_DATE; + public static final Index IDX__COMMENT__STATUS = Indexes0.IDX__COMMENT__STATUS; + public static final Index IDX__COMMENT__THREAD_ID = Indexes0.IDX__COMMENT__THREAD_ID; public static final Index COMMENT_THREAD_PKEY = Indexes0.COMMENT_THREAD_PKEY; + public static final Index COMMENT_THREAD_URL_KEY = Indexes0.COMMENT_THREAD_URL_KEY; public static final Index PROPERTY_PKEY = Indexes0.PROPERTY_PKEY; // ------------------------------------------------------------------------- @@ -47,14 +45,12 @@ public class Indexes { // ------------------------------------------------------------------------- private static class Indexes0 { - public static Index COMMENT__CREATION_DATE__IDX = Internal.createIndex("comment__creation_date__idx", Comment.COMMENT, new OrderField[] { Comment.COMMENT.CREATION_DATE }, false); - public static Index COMMENT__ID__IDX = Internal.createIndex("comment__id__idx", Comment.COMMENT, new OrderField[] { Comment.COMMENT.ID }, false); - public static Index COMMENT__STATUS__IDX = Internal.createIndex("comment__status__idx", Comment.COMMENT, new OrderField[] { Comment.COMMENT.STATUS }, false); - public static Index COMMENT__THREAD_ID__IDX = Internal.createIndex("comment__thread_id__idx", Comment.COMMENT, new OrderField[] { Comment.COMMENT.THREAD_ID }, false); public static Index COMMENT_PKEY = Internal.createIndex("comment_pkey", Comment.COMMENT, new OrderField[] { Comment.COMMENT.ID }, true); - public static Index COMMENT_THREAD__ID__IDX = Internal.createIndex("comment_thread__id__idx", CommentThread.COMMENT_THREAD, new OrderField[] { CommentThread.COMMENT_THREAD.ID }, false); - public static Index COMMENT_THREAD__URL__IDX = Internal.createIndex("comment_thread__url__idx", CommentThread.COMMENT_THREAD, new OrderField[] { CommentThread.COMMENT_THREAD.URL }, false); + public static Index IDX__COMMENT__CREATION_DATE = Internal.createIndex("idx__comment__creation_date", Comment.COMMENT, new OrderField[] { Comment.COMMENT.CREATION_DATE }, false); + public static Index IDX__COMMENT__STATUS = Internal.createIndex("idx__comment__status", Comment.COMMENT, new OrderField[] { Comment.COMMENT.STATUS }, false); + public static Index IDX__COMMENT__THREAD_ID = Internal.createIndex("idx__comment__thread_id", Comment.COMMENT, new OrderField[] { Comment.COMMENT.THREAD_ID }, false); public static Index COMMENT_THREAD_PKEY = Internal.createIndex("comment_thread_pkey", CommentThread.COMMENT_THREAD, new OrderField[] { CommentThread.COMMENT_THREAD.ID }, true); + public static Index COMMENT_THREAD_URL_KEY = Internal.createIndex("comment_thread_url_key", CommentThread.COMMENT_THREAD, new OrderField[] { CommentThread.COMMENT_THREAD.URL }, true); public static Index PROPERTY_PKEY = Internal.createIndex("property_pkey", Property.PROPERTY, new OrderField[] { Property.PROPERTY.KEY }, true); } } diff --git a/src/main/java/de/vorb/platon/persistence/jooq/Keys.java b/src/main/java/de/vorb/platon/persistence/jooq/Keys.java index bdfe0b4..b16c71a 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/Keys.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/Keys.java @@ -46,6 +46,7 @@ public class Keys { public static final UniqueKey COMMENT_PKEY = UniqueKeys0.COMMENT_PKEY; public static final UniqueKey COMMENT_THREAD_PKEY = UniqueKeys0.COMMENT_THREAD_PKEY; + public static final UniqueKey COMMENT_THREAD_URL_KEY = UniqueKeys0.COMMENT_THREAD_URL_KEY; public static final UniqueKey PROPERTY_PKEY = UniqueKeys0.PROPERTY_PKEY; // ------------------------------------------------------------------------- @@ -67,6 +68,7 @@ private static class Identities0 { private static class UniqueKeys0 { public static final UniqueKey COMMENT_PKEY = Internal.createUniqueKey(Comment.COMMENT, "comment_pkey", Comment.COMMENT.ID); public static final UniqueKey COMMENT_THREAD_PKEY = Internal.createUniqueKey(CommentThread.COMMENT_THREAD, "comment_thread_pkey", CommentThread.COMMENT_THREAD.ID); + public static final UniqueKey COMMENT_THREAD_URL_KEY = Internal.createUniqueKey(CommentThread.COMMENT_THREAD, "comment_thread_url_key", CommentThread.COMMENT_THREAD.URL); public static final UniqueKey PROPERTY_PKEY = Internal.createUniqueKey(Property.PROPERTY, "property_pkey", Property.PROPERTY.KEY); } diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java index ee448eb..95f9ee1 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java @@ -43,7 +43,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Comment extends TableImpl { - private static final long serialVersionUID = -394233886; + private static final long serialVersionUID = 690764222; /** * The reference instance of public.comment @@ -98,11 +98,6 @@ public Class getRecordType() { */ public final TableField AUTHOR = createField("author", org.jooq.impl.SQLDataType.VARCHAR(128), this, ""); - /** - * The column public.comment.email_hash. - */ - public final TableField EMAIL_HASH = createField("email_hash", org.jooq.impl.SQLDataType.CHAR(32), this, ""); - /** * The column public.comment.url. */ @@ -150,7 +145,7 @@ public Schema getSchema() { */ @Override public List getIndexes() { - return Arrays.asList(Indexes.COMMENT__CREATION_DATE__IDX, Indexes.COMMENT__ID__IDX, Indexes.COMMENT__STATUS__IDX, Indexes.COMMENT__THREAD_ID__IDX, Indexes.COMMENT_PKEY); + return Arrays.asList(Indexes.COMMENT_PKEY, Indexes.IDX__COMMENT__CREATION_DATE, Indexes.IDX__COMMENT__STATUS, Indexes.IDX__COMMENT__THREAD_ID); } /** diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/CommentThread.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/CommentThread.java index c5118e9..ae1f69f 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/CommentThread.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/CommentThread.java @@ -39,7 +39,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class CommentThread extends TableImpl { - private static final long serialVersionUID = 22120208; + private static final long serialVersionUID = -537616766; /** * The reference instance of public.comment_thread @@ -111,7 +111,7 @@ public Schema getSchema() { */ @Override public List getIndexes() { - return Arrays.asList(Indexes.COMMENT_THREAD__ID__IDX, Indexes.COMMENT_THREAD__URL__IDX, Indexes.COMMENT_THREAD_PKEY); + return Arrays.asList(Indexes.COMMENT_THREAD_PKEY, Indexes.COMMENT_THREAD_URL_KEY); } /** @@ -135,7 +135,7 @@ public UniqueKey getPrimaryKey() { */ @Override public List> getKeys() { - return Arrays.>asList(Keys.COMMENT_THREAD_PKEY); + return Arrays.>asList(Keys.COMMENT_THREAD_PKEY, Keys.COMMENT_THREAD_URL_KEY); } /** diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java index dce7a8a..28ccb1b 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java @@ -25,7 +25,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Comment implements Serializable { - private static final long serialVersionUID = 11505420; + private static final long serialVersionUID = 2073570444; private Long id; private Long threadId; @@ -35,7 +35,6 @@ public class Comment implements Serializable { private CommentStatus status; private String text; private String author; - private String emailHash; private String url; public Comment() {} @@ -49,7 +48,6 @@ public Comment(Comment value) { this.status = value.status; this.text = value.text; this.author = value.author; - this.emailHash = value.emailHash; this.url = value.url; } @@ -62,7 +60,6 @@ public Comment( CommentStatus status, String text, String author, - String emailHash, String url ) { this.id = id; @@ -73,7 +70,6 @@ public Comment( this.status = status; this.text = text; this.author = author; - this.emailHash = emailHash; this.url = url; } @@ -149,15 +145,6 @@ public Comment setAuthor(String author) { return this; } - public String getEmailHash() { - return this.emailHash; - } - - public Comment setEmailHash(String emailHash) { - this.emailHash = emailHash; - return this; - } - public String getUrl() { return this.url; } @@ -179,7 +166,6 @@ public String toString() { sb.append(", ").append(status); sb.append(", ").append(text); sb.append(", ").append(author); - sb.append(", ").append(emailHash); sb.append(", ").append(url); sb.append(")"); diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java index 8f4d0ec..92ea3cc 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java @@ -13,8 +13,8 @@ import org.jooq.Field; import org.jooq.Record1; -import org.jooq.Record10; -import org.jooq.Row10; +import org.jooq.Record9; +import org.jooq.Row9; import org.jooq.impl.UpdatableRecordImpl; @@ -29,9 +29,9 @@ comments = "This class is generated by jOOQ" ) @SuppressWarnings({ "all", "unchecked", "rawtypes" }) -public class CommentRecord extends UpdatableRecordImpl implements Record10 { +public class CommentRecord extends UpdatableRecordImpl implements Record9 { - private static final long serialVersionUID = 1375009388; + private static final long serialVersionUID = -675336345; /** * Setter for public.comment.id. @@ -153,26 +153,11 @@ public String getAuthor() { return (String) get(7); } - /** - * Setter for public.comment.email_hash. - */ - public CommentRecord setEmailHash(String value) { - set(8, value); - return this; - } - - /** - * Getter for public.comment.email_hash. - */ - public String getEmailHash() { - return (String) get(8); - } - /** * Setter for public.comment.url. */ public CommentRecord setUrl(String value) { - set(9, value); + set(8, value); return this; } @@ -180,7 +165,7 @@ public CommentRecord setUrl(String value) { * Getter for public.comment.url. */ public String getUrl() { - return (String) get(9); + return (String) get(8); } // ------------------------------------------------------------------------- @@ -196,23 +181,23 @@ public Record1 key() { } // ------------------------------------------------------------------------- - // Record10 type implementation + // Record9 type implementation // ------------------------------------------------------------------------- /** * {@inheritDoc} */ @Override - public Row10 fieldsRow() { - return (Row10) super.fieldsRow(); + public Row9 fieldsRow() { + return (Row9) super.fieldsRow(); } /** * {@inheritDoc} */ @Override - public Row10 valuesRow() { - return (Row10) super.valuesRow(); + public Row9 valuesRow() { + return (Row9) super.valuesRow(); } /** @@ -284,14 +269,6 @@ public Field field8() { */ @Override public Field field9() { - return Comment.COMMENT.EMAIL_HASH; - } - - /** - * {@inheritDoc} - */ - @Override - public Field field10() { return Comment.COMMENT.URL; } @@ -364,14 +341,6 @@ public String component8() { */ @Override public String component9() { - return getEmailHash(); - } - - /** - * {@inheritDoc} - */ - @Override - public String component10() { return getUrl(); } @@ -444,14 +413,6 @@ public String value8() { */ @Override public String value9() { - return getEmailHash(); - } - - /** - * {@inheritDoc} - */ - @Override - public String value10() { return getUrl(); } @@ -532,15 +493,6 @@ public CommentRecord value8(String value) { */ @Override public CommentRecord value9(String value) { - setEmailHash(value); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public CommentRecord value10(String value) { setUrl(value); return this; } @@ -549,7 +501,7 @@ public CommentRecord value10(String value) { * {@inheritDoc} */ @Override - public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value4, LocalDateTime value5, CommentStatus value6, String value7, String value8, String value9, String value10) { + public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value4, LocalDateTime value5, CommentStatus value6, String value7, String value8, String value9) { value1(value1); value2(value2); value3(value3); @@ -559,7 +511,6 @@ public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value7(value7); value8(value8); value9(value9); - value10(value10); return this; } @@ -577,7 +528,7 @@ public CommentRecord() { /** * Create a detached, initialised CommentRecord */ - public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creationDate, LocalDateTime lastModificationDate, CommentStatus status, String text, String author, String emailHash, String url) { + public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creationDate, LocalDateTime lastModificationDate, CommentStatus status, String text, String author, String url) { super(Comment.COMMENT); set(0, id); @@ -588,7 +539,6 @@ public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creati set(5, status); set(6, text); set(7, author); - set(8, emailHash); - set(9, url); + set(8, url); } } diff --git a/src/main/java/de/vorb/platon/services/feedreader/FeedReader.java b/src/main/java/de/vorb/platon/services/feedreader/FeedReader.java index cd6d1ee..c9d7ff1 100644 --- a/src/main/java/de/vorb/platon/services/feedreader/FeedReader.java +++ b/src/main/java/de/vorb/platon/services/feedreader/FeedReader.java @@ -79,7 +79,7 @@ private void handleEntry(SyndEntry feedEntry) { log.debug("Inserted new thread for URL {}", threadUrl); } else { if (!existingThread.get().getTitle().equals(feedEntry.getTitle())) { - threadRepository.updateThreadTitle(existingThread.get().getId(), feedEntry.getTitle()); + threadRepository.updateTitle(existingThread.get().getId(), feedEntry.getTitle()); } } }); diff --git a/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java b/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java index e213e3b..702d71a 100644 --- a/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java +++ b/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java @@ -16,8 +16,8 @@ package de.vorb.platon.web.api.common; -import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.model.CommentStatus; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.web.api.json.CommentJson; import lombok.SneakyThrows; @@ -54,9 +54,9 @@ public CommentJson convertPojoToJson(Comment comment) { json.author(comment.getAuthor()); json.url(comment.getUrl()); - if (comment.getEmailHash() != null) { - json.emailHash(Base64.getDecoder().decode(comment.getEmailHash())); - } +// if (comment.getEmailHash() != null) { +// json.emailHash(Base64.getDecoder().decode(comment.getEmailHash())); +// } } return json.build(); @@ -71,7 +71,7 @@ public Comment convertJsonToPojo(CommentJson json) { .setStatus(json.getStatus()) .setText(json.getText()) .setAuthor(json.getAuthor()) - .setEmailHash(calculateEmailHash(json.getEmail())) +// .setEmailHash(calculateEmailHash(json.getEmail())) .setUrl(json.getUrl()); } diff --git a/src/main/java/de/vorb/platon/web/api/common/CommentFilters.java b/src/main/java/de/vorb/platon/web/api/common/CommentFilters.java index 0617d3f..333e00e 100644 --- a/src/main/java/de/vorb/platon/web/api/common/CommentFilters.java +++ b/src/main/java/de/vorb/platon/web/api/common/CommentFilters.java @@ -16,8 +16,8 @@ package de.vorb.platon.web.api.common; -import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.model.CommentStatus; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import org.springframework.stereotype.Component; diff --git a/src/main/java/de/vorb/platon/web/api/common/CommentUriResolver.java b/src/main/java/de/vorb/platon/web/api/common/CommentUriResolver.java index afe0ecd..6006323 100644 --- a/src/main/java/de/vorb/platon/web/api/common/CommentUriResolver.java +++ b/src/main/java/de/vorb/platon/web/api/common/CommentUriResolver.java @@ -23,8 +23,8 @@ import java.net.URI; import java.util.Collections; -import static de.vorb.platon.web.api.controllers.CommentController.PATH_SINGLE; -import static de.vorb.platon.web.api.controllers.CommentController.PATH_VAR_COMMENT_ID; +import static de.vorb.platon.web.mvc.comments.CommentController.PATH_SINGLE_COMMENT; +import static de.vorb.platon.web.mvc.comments.CommentController.PATH_VAR_COMMENT_ID; @Component public class CommentUriResolver { @@ -32,7 +32,7 @@ public class CommentUriResolver { @SneakyThrows public URI createRelativeCommentUriForId(long commentId) { return new URI(ServletUriComponentsBuilder.fromCurrentRequest() - .path(PATH_SINGLE) + .path(PATH_SINGLE_COMMENT) .replaceQuery(null) .buildAndExpand(Collections.singletonMap(PATH_VAR_COMMENT_ID, commentId)) .getPath()); diff --git a/src/main/java/de/vorb/platon/web/api/common/HtmlInputSanitizer.java b/src/main/java/de/vorb/platon/web/api/common/HtmlInputSanitizer.java index e5be719..453a003 100644 --- a/src/main/java/de/vorb/platon/web/api/common/HtmlInputSanitizer.java +++ b/src/main/java/de/vorb/platon/web/api/common/HtmlInputSanitizer.java @@ -27,7 +27,7 @@ public class HtmlInputSanitizer implements InputSanitizer { private final PolicyFactory htmlContentPolicy; public HtmlInputSanitizer( - @Value("${platon.input.html_elements}") String allowedHtmlElements) { + @Value("${platon.input.html-elements}") String allowedHtmlElements) { final HtmlPolicyBuilder htmlPolicyBuilder = new HtmlPolicyBuilder() .allowUrlProtocols("http", "https", "mailto") diff --git a/src/main/java/de/vorb/platon/web/api/common/RequestValidator.java b/src/main/java/de/vorb/platon/web/api/common/RequestValidator.java index 29f9457..1694a4e 100644 --- a/src/main/java/de/vorb/platon/web/api/common/RequestValidator.java +++ b/src/main/java/de/vorb/platon/web/api/common/RequestValidator.java @@ -18,7 +18,7 @@ import de.vorb.platon.security.SignatureComponents; import de.vorb.platon.security.SignatureTokenValidator; -import de.vorb.platon.web.api.errors.RequestException; +import de.vorb.platon.web.mvc.errors.RequestException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/de/vorb/platon/web/api/controllers/CommentAction.java b/src/main/java/de/vorb/platon/web/mvc/comments/CommentAction.java similarity index 64% rename from src/main/java/de/vorb/platon/web/api/controllers/CommentAction.java rename to src/main/java/de/vorb/platon/web/mvc/comments/CommentAction.java index cb9d3bd..ffec645 100644 --- a/src/main/java/de/vorb/platon/web/api/controllers/CommentAction.java +++ b/src/main/java/de/vorb/platon/web/mvc/comments/CommentAction.java @@ -1,4 +1,4 @@ -package de.vorb.platon.web.api.controllers; +package de.vorb.platon.web.mvc.comments; public enum CommentAction { CREATE, diff --git a/src/main/java/de/vorb/platon/web/api/controllers/CommentController.java b/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java similarity index 91% rename from src/main/java/de/vorb/platon/web/api/controllers/CommentController.java rename to src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java index 7d71702..94b62e9 100644 --- a/src/main/java/de/vorb/platon/web/api/controllers/CommentController.java +++ b/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package de.vorb.platon.web.api.controllers; +package de.vorb.platon.web.mvc.comments; import de.vorb.platon.model.CommentStatus; import de.vorb.platon.persistence.CommentRepository; @@ -26,18 +26,15 @@ import de.vorb.platon.web.api.common.CommentSanitizer; import de.vorb.platon.web.api.common.CommentUriResolver; import de.vorb.platon.web.api.common.RequestValidator; -import de.vorb.platon.web.api.errors.RequestException; +import de.vorb.platon.web.mvc.errors.RequestException; import com.google.common.collect.ImmutableMap; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; -import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; @@ -47,7 +44,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.stream.Collectors; @@ -56,14 +52,15 @@ import static org.springframework.http.MediaType.TEXT_HTML_VALUE; -@Controller +//@Controller @RequiredArgsConstructor @Slf4j public class CommentController { - private static final String PATH_LIST = "/comments"; + private static final String PATH_VAR_THREAD_ID = "threadId"; + private static final String PATH_LIST_COMMENTS = "/{" + PATH_VAR_THREAD_ID + "}/comments"; public static final String PATH_VAR_COMMENT_ID = "commentId"; - public static final String PATH_SINGLE = PATH_LIST + "/{" + PATH_VAR_COMMENT_ID + "}.html"; + public static final String PATH_SINGLE_COMMENT = PATH_LIST_COMMENTS + "/{" + PATH_VAR_COMMENT_ID + "}.html"; private static final String SIGNATURE_HEADER = "X-Signature"; private static final CommentStatus DEFAULT_STATUS = PUBLIC; @@ -83,7 +80,7 @@ public class CommentController { private final CommentSanitizer commentSanitizer; - @GetMapping(value = PATH_SINGLE, produces = TEXT_HTML_VALUE) + @GetMapping(value = PATH_SINGLE_COMMENT, produces = TEXT_HTML_VALUE) public ModelAndView getCommentById(@PathVariable(PATH_VAR_COMMENT_ID) long commentId) { final Comment comment = commentRepository.findById(commentId) @@ -96,8 +93,7 @@ public ModelAndView getCommentById(@PathVariable(PATH_VAR_COMMENT_ID) long comme return new ModelAndView("comment-single", Collections.singletonMap("comment", comment)); } - - @GetMapping(value = PATH_LIST, produces = TEXT_HTML_VALUE) + @GetMapping(value = PATH_LIST_COMMENTS, produces = TEXT_HTML_VALUE) public ModelAndView findCommentsByThreadUrl(@RequestParam("threadUrl") String threadUrl) { final CommentThread thread = threadRepository.findThreadForUrl(threadUrl) @@ -132,7 +128,7 @@ public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, return new ModelAndView("comment-form", ImmutableMap.of("threadUrl", threadUrl, "threadTitle", threadTitle)); } -// @PostMapping(value = PATH_LIST, produces = TEXT_HTML_VALUE) +// @PostMapping(value = PATH_LIST_COMMENTS, produces = TEXT_HTML_VALUE) // public ModelAndView postComment( // @RequestBody MultiValueMap commentData) { // @@ -148,7 +144,7 @@ public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, // final String threadUrl = commentData.getFirst("threadUrl"); // final String commentTitle = commentData.getFirst("commentTitle"); // -// final Long threadId = threadRepository.findThreadIdForUrl(threadUrl) +// final Long threadId = threadRepository.findIdForUrl(threadUrl) // .orElseGet(() -> { // final CommentThread thread = new CommentThread() // .setUrl(threadUrl) @@ -164,7 +160,7 @@ public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, // } // } // -// @PostMapping(value = PATH_LIST, consumes = APPLICATION_FORM_URLENCODED_VALUE, produces = TEXT_HTML_VALUE) +// @PostMapping(value = PATH_LIST_COMMENTS, consumes = APPLICATION_FORM_URLENCODED_VALUE, produces = TEXT_HTML_VALUE) // public ModelAndView postComment( // @RequestParam("url") String threadUrl, // @RequestParam("threadTitle") String threadTitle, @@ -177,7 +173,7 @@ public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, // .build(); // } // -// final long threadId = threadRepository.findThreadIdForUrl(threadUrl) +// final long threadId = threadRepository.findIdForUrl(threadUrl) // .orElseGet(() -> { // final CommentThread thread = new CommentThread() // .setUrl(threadUrl) @@ -239,7 +235,7 @@ public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, // } // // -// @PutMapping(value = PATH_SINGLE, consumes = APPLICATION_JSON_VALUE) +// @PutMapping(value = PATH_SINGLE_COMMENT, consumes = APPLICATION_JSON_VALUE) // public void updateComment( // @PathVariable(PATH_VAR_COMMENT_ID) Long commentId, // @RequestHeader(SIGNATURE_HEADER) String signature, @@ -279,7 +275,7 @@ public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, // } // // -// @DeleteMapping(PATH_SINGLE) +// @DeleteMapping(PATH_SINGLE_COMMENT) // public void deleteComment( // @PathVariable(PATH_VAR_COMMENT_ID) Long commentId, // @RequestHeader(SIGNATURE_HEADER) String signature) { diff --git a/src/main/java/de/vorb/platon/web/api/controllers/CommentCountsController.java b/src/main/java/de/vorb/platon/web/mvc/comments/CommentCountsController.java similarity index 97% rename from src/main/java/de/vorb/platon/web/api/controllers/CommentCountsController.java rename to src/main/java/de/vorb/platon/web/mvc/comments/CommentCountsController.java index 2bda157..f27b305 100644 --- a/src/main/java/de/vorb/platon/web/api/controllers/CommentCountsController.java +++ b/src/main/java/de/vorb/platon/web/mvc/comments/CommentCountsController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package de.vorb.platon.web.api.controllers; +package de.vorb.platon.web.mvc.comments; import de.vorb.platon.persistence.CommentRepository; diff --git a/src/main/java/de/vorb/platon/web/api/errors/RequestException.java b/src/main/java/de/vorb/platon/web/mvc/errors/RequestException.java similarity index 98% rename from src/main/java/de/vorb/platon/web/api/errors/RequestException.java rename to src/main/java/de/vorb/platon/web/mvc/errors/RequestException.java index 1a7150d..b78cf2c 100644 --- a/src/main/java/de/vorb/platon/web/api/errors/RequestException.java +++ b/src/main/java/de/vorb/platon/web/mvc/errors/RequestException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package de.vorb.platon.web.api.errors; +package de.vorb.platon.web.mvc.errors; import com.google.common.base.Preconditions; import lombok.Getter; diff --git a/src/main/java/de/vorb/platon/web/api/errors/RequestExceptionHandler.java b/src/main/java/de/vorb/platon/web/mvc/errors/RequestExceptionHandler.java similarity index 97% rename from src/main/java/de/vorb/platon/web/api/errors/RequestExceptionHandler.java rename to src/main/java/de/vorb/platon/web/mvc/errors/RequestExceptionHandler.java index 1570428..551b56b 100644 --- a/src/main/java/de/vorb/platon/web/api/errors/RequestExceptionHandler.java +++ b/src/main/java/de/vorb/platon/web/mvc/errors/RequestExceptionHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package de.vorb.platon.web.api.errors; +package de.vorb.platon.web.mvc.errors; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java b/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java new file mode 100644 index 0000000..8edc6df --- /dev/null +++ b/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java @@ -0,0 +1,60 @@ +package de.vorb.platon.web.mvc.threads; + +import de.vorb.platon.persistence.CommentRepository; +import de.vorb.platon.persistence.ThreadRepository; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; +import de.vorb.platon.web.mvc.errors.RequestException; + +import com.google.common.collect.ImmutableMap; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.servlet.ModelAndView; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.springframework.http.MediaType.TEXT_HTML_VALUE; + +@Slf4j +@Controller +@RequiredArgsConstructor +public class CommentController { + + static final String PATH_LIST_THREADS = "/threads"; + static final String PATH_VAR_THREAD_ID = "threadId"; + static final String PATH_SINGLE_THREAD = PATH_LIST_THREADS + "/{" + PATH_VAR_THREAD_ID + "}/comments"; + + private final ThreadRepository threadRepository; + private final CommentRepository commentRepository; + + @GetMapping(value = PATH_SINGLE_THREAD, produces = TEXT_HTML_VALUE) + public ModelAndView findCommentsByThreadUrl(@PathVariable(PATH_VAR_THREAD_ID) long threadId) { + + final CommentThread thread = threadRepository.findById(threadId) + .orElseThrow(() -> RequestException.withStatus(HttpStatus.NOT_FOUND) + .message("No thread exists for ID “" + threadId + '”').build()); + final List comments = commentRepository.findByThreadId(thread.getId()); + + final Map commentsById = comments.stream() + .collect(Collectors.toMap(Comment::getId, Function.identity(), throwingMerger(), + LinkedHashMap::new)); + + return new ModelAndView("comments-flat", + ImmutableMap.of("thread", thread, "commentCount", comments.size(), "comments", commentsById)); + } + + private static BinaryOperator throwingMerger() { + return (a, b) -> { + throw new IllegalStateException("Duplicate key " + a); + }; + } +} diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java new file mode 100644 index 0000000..850c367 --- /dev/null +++ b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java @@ -0,0 +1,32 @@ +package de.vorb.platon.web.mvc.threads; + +import de.vorb.platon.persistence.CommentRepository; +import de.vorb.platon.persistence.ThreadRepository; +import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; + +import com.google.common.collect.ImmutableMap; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.servlet.ModelAndView; + +@Slf4j +@Controller +@RequiredArgsConstructor +public class ReplyFormController { + + private final ThreadRepository threadRepository; + private final CommentRepository commentRepository; + + @GetMapping(value = "/threads/{threadId}/reply", produces = MediaType.TEXT_HTML_VALUE) + public ModelAndView showReplyForm(@PathVariable(CommentController.PATH_VAR_THREAD_ID) long threadId) { + + final CommentThread thread = threadRepository.findById(threadId).orElseThrow(RuntimeException::new); + + return new ModelAndView("comment-form", ImmutableMap.of("thread", thread)); + } + +} diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/ThreadRedirectController.java b/src/main/java/de/vorb/platon/web/mvc/threads/ThreadRedirectController.java new file mode 100644 index 0000000..ead9945 --- /dev/null +++ b/src/main/java/de/vorb/platon/web/mvc/threads/ThreadRedirectController.java @@ -0,0 +1,62 @@ +package de.vorb.platon.web.mvc.threads; + +import de.vorb.platon.persistence.ThreadRepository; +import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Slf4j +@Controller +@RequiredArgsConstructor +public class ThreadRedirectController { + + private static final String THREAD_ID_REPLACEMENT_TARGET = '{' + CommentController.PATH_VAR_THREAD_ID + '}'; + + private final ThreadRepository threadRepository; + + @GetMapping(value = CommentController.PATH_LIST_THREADS, params = {"url", "title"}) + @ResponseStatus(HttpStatus.MOVED_PERMANENTLY) + @Transactional + public String redirectToComments( + @RequestParam("url") String threadUrl, + @RequestParam("title") String threadTitle) { + + final CommentThread thread = threadRepository.findThreadForUrl(threadUrl) + .orElseGet(() -> createThread(threadUrl, threadTitle)); + + return getRedirectForThread(thread); + } + + @GetMapping(value = CommentController.PATH_LIST_THREADS + '/' + THREAD_ID_REPLACEMENT_TARGET) + @ResponseStatus(HttpStatus.TEMPORARY_REDIRECT) + public String redirectToComments(@PathVariable("threadId") long threadId) { + + CommentThread thread = threadRepository.findById(threadId).orElseThrow(IndexOutOfBoundsException::new); + + return getRedirectForThread(thread); + } + + private CommentThread createThread(String threadUrl, String threadTitle) { + + final CommentThread newThread = threadRepository.insert( + new CommentThread().setUrl(threadUrl).setTitle(threadTitle)); + + log.info("Created new thread: {}", newThread); + + return newThread; + } + + private String getRedirectForThread(CommentThread commentThread) { + return "redirect:" + CommentController.PATH_SINGLE_THREAD.replace(THREAD_ID_REPLACEMENT_TARGET, + String.valueOf(commentThread.getId())); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6c309cd..3601cce 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,15 +4,9 @@ spring.datasource: password: postgres spring.jooq.sql-dialect: postgres -platon.input.html_elements: > - h1, h2, h3, h4, h5, h6, - br, p, hr, div, span, - a, img, em, strong, - ol, ul, li, - blockquote, code, pre - -platon.feed-reader.import-rules: [] - # - feed-url: https://example.org/blog.atom +server: + compression: + enabled: true management: endpoint: @@ -31,3 +25,15 @@ management: spring.freemarker.settings: incompatible_improvements: 2.3.28 api_builtin_enabled: true + +platon: + input: + html-elements: > + h1, h2, h3, h4, h5, h6, + br, p, hr, div, span, + a, img, em, strong, + ol, ul, li, + blockquote, code, pre + feed-reader: + import-rules: [] + # - feed-url: https://example.org/blog.atom diff --git a/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql b/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql index 4b60cba..b12fa94 100644 --- a/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql +++ b/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql @@ -14,7 +14,6 @@ CREATE TABLE comment ( status VARCHAR(32) NOT NULL, text TEXT NOT NULL, author VARCHAR(128) NULL, - email_hash CHAR(32) NULL, url VARCHAR(256) NULL, FOREIGN KEY (thread_id) REFERENCES comment_thread (id), FOREIGN KEY (parent_id) REFERENCES comment (id) diff --git a/src/main/resources/static/comment-count.html b/src/main/resources/static/comment-count.html deleted file mode 100644 index 817191b..0000000 --- a/src/main/resources/static/comment-count.html +++ /dev/null @@ -1,8 +0,0 @@ -Comment Count Test -

There are [loading...] on thread - comment-count-thread-1.html.

-

[loading...]

-

There are [loading...] on thread - comment-count-thread-2.html.

-

[loading...]

- diff --git a/src/main/resources/static/comment-list.html b/src/main/resources/static/comment-list.html deleted file mode 100644 index 5d3d3f8..0000000 --- a/src/main/resources/static/comment-list.html +++ /dev/null @@ -1,2 +0,0 @@ -
- diff --git a/src/main/resources/static/test.html b/src/main/resources/static/test.html deleted file mode 100644 index 2bace84..0000000 --- a/src/main/resources/static/test.html +++ /dev/null @@ -1,14 +0,0 @@ -Comments Test - -

Page has … Comments

-

Comments

-
- diff --git a/src/main/resources/templates/comment-form.ftl b/src/main/resources/templates/comment-form.ftl index 0b40362..5900739 100644 --- a/src/main/resources/templates/comment-form.ftl +++ b/src/main/resources/templates/comment-form.ftl @@ -12,10 +12,11 @@

- Leave a comment for “${threadTitle}” + Leave a comment for “${thread.title}

-
+ <#if parentComment??> +

Replying to comment

@@ -28,8 +29,9 @@
+ -
+
-
-
- -
-
-
👤
-
- -
-
-
- -
-
-
🔗
-
- + +
+ + +
+
+ +
+
+
👤
+
-
-
- - +
+
+ +
+
+
🔗
+
-
- - +
+
+
+ +
+
+
+ + +
- - - -
- - + + <#if parentComment??> + + + + + +<@page/> diff --git a/src/main/resources/templates/comments-flat.ftl b/src/main/resources/templates/comments-flat.ftl index 541ea6e..1f2e2e3 100644 --- a/src/main/resources/templates/comments-flat.ftl +++ b/src/main/resources/templates/comments-flat.ftl @@ -1,27 +1,27 @@ <#ftl output_format="HTML"/> <#include "snippets/base.ftl"/> -<#include "snippets/comment.ftl"/> +<#include "snippets/page-comment.ftl"/> <#macro page_title> - Comments for “${thread.title}” | Platon + Comments for “${thread.title}” <#macro page_header> - Comments for “${thread.title}” + Comments for “${thread.title}” <#macro page_content>
<#if commentCount > 3>
- Leave a comment + Leave a comment
<#list comments as id, comment> <@page_comment thread comments comment/>
diff --git a/src/main/resources/templates/error.ftl b/src/main/resources/templates/error.ftl index bc8e7e8..b69fb31 100644 --- a/src/main/resources/templates/error.ftl +++ b/src/main/resources/templates/error.ftl @@ -2,7 +2,7 @@ <#include "snippets/base.ftl"/> <#macro page_title> - ${status} ${error} | Platon + ${status} ${error} <#macro page_header> diff --git a/src/main/resources/templates/snippets/comment.ftl b/src/main/resources/templates/snippets/page-comment.ftl similarity index 68% rename from src/main/resources/templates/snippets/comment.ftl rename to src/main/resources/templates/snippets/page-comment.ftl index cd9098a..9ae6f3a 100644 --- a/src/main/resources/templates/snippets/comment.ftl +++ b/src/main/resources/templates/snippets/page-comment.ftl @@ -1,6 +1,6 @@ <#ftl output_format="HTML"/> -<#macro page_comment thread comments comment> +<#macro page_comment thread comments comment in_form=false>
@@ -16,20 +16,20 @@ on + <#if !in_form> (Permalink) + <#if comment.parentId??> <#assign parentComment=comments?api.get(comment.parentId)/> <#assign clippedParentText=parentComment.text?replace('<[^>]+>', '', 'r')[0..*80]/> - + <#if clippedParentText?length < parentComment.text?length> +
(In reply to comment + “${clippedParentText}…”)
+ <#else> +
(In reply to comment + “${parentComment.text}”)
+ @@ -37,10 +37,12 @@ ${comment.text?no_esc}
+ <#if !in_form> +
From 0b2234a3923bc2266c10473d7ab3c178baad4c53 Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Fri, 29 Jun 2018 19:28:02 +0200 Subject: [PATCH 06/16] Introduce primitive avatar generation --- .../web/mvc/avatars/AvatarController.java | 81 +++++++++++++++++++ .../templates/snippets/page-comment.ftl | 8 +- 2 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 src/main/java/de/vorb/platon/web/mvc/avatars/AvatarController.java diff --git a/src/main/java/de/vorb/platon/web/mvc/avatars/AvatarController.java b/src/main/java/de/vorb/platon/web/mvc/avatars/AvatarController.java new file mode 100644 index 0000000..09b9fbe --- /dev/null +++ b/src/main/java/de/vorb/platon/web/mvc/avatars/AvatarController.java @@ -0,0 +1,81 @@ +package de.vorb.platon.web.mvc.avatars; + +import com.google.common.collect.ImmutableList; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletResponse; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.List; +import java.util.Random; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class AvatarController { + + private static final List BACKGROUND_COLORS = ImmutableList.of( + new Color(0x96858f), new Color(0x6d7993), new Color(0x9099a2), new Color(0xd5d5d5)); + private static final List COLORS = ImmutableList.of( + new Color(0xe24e42), new Color(0xe9b000), new Color(0xeb6e80), new Color(0x008f95)); + + @GetMapping(value = "/avatars/{hash}", produces = MediaType.IMAGE_PNG_VALUE) + + public void bufferedImage(@PathVariable("hash") String hash, HttpServletResponse httpServletResponse) + throws IOException { + + final Random random = new Random(hash.hashCode()); + + final BufferedImage image = new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB); + + final int colorIndex = random.nextInt(COLORS.size()); + final Color backgroundColor = COLORS.get(colorIndex); + final Color foregroundColor = COLORS.get((colorIndex + 1) % COLORS.size()); + + final Graphics2D g2d = image.createGraphics(); + g2d.setColor(backgroundColor); + g2d.fillRect(0, 0, 64, 64); + + g2d.setColor(foregroundColor); + if (random.nextBoolean()) { + g2d.fillRect(5, 5, 18, 18); + } + if (random.nextBoolean()) { + g2d.fillRect(23, 5, 18, 18); + } + if (random.nextBoolean()) { + g2d.fillRect(41, 5, 18, 18); + } + if (random.nextBoolean()) { + g2d.fillRect(5, 23, 18, 18); + } + if (random.nextBoolean()) { + g2d.fillRect(23, 23, 18, 18); + } + if (random.nextBoolean()) { + g2d.fillRect(41, 23, 18, 18); + } + if (random.nextBoolean()) { + g2d.fillRect(5, 41, 18, 18); + } + if (random.nextBoolean()) { + g2d.fillRect(23, 41, 18, 18); + } + if (random.nextBoolean()) { + g2d.fillRect(41, 41, 18, 18); + } + + g2d.dispose(); + + ImageIO.write(image, "PNG", httpServletResponse.getOutputStream()); + } + +} diff --git a/src/main/resources/templates/snippets/page-comment.ftl b/src/main/resources/templates/snippets/page-comment.ftl index 9ae6f3a..0dab7d5 100644 --- a/src/main/resources/templates/snippets/page-comment.ftl +++ b/src/main/resources/templates/snippets/page-comment.ftl @@ -2,7 +2,7 @@ <#macro page_comment thread comments comment in_form=false>
- +
<#if comment.author?? && comment.url??> @@ -25,10 +25,12 @@ <#assign clippedParentText=parentComment.text?replace('<[^>]+>', '', 'r')[0..*80]/> <#if clippedParentText?length < parentComment.text?length>
(In reply to comment - “${clippedParentText}…”)
+ “${clippedParentText}…”) +
<#else>
(In reply to comment - “${parentComment.text}”)
+ “${parentComment.text}”) +
From 12133db061f2e33206b463dee1c20c5c19b6cfb7 Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Sat, 30 Jun 2018 01:49:53 +0200 Subject: [PATCH 07/16] Handle comment previews --- pom.xml | 4 + .../persistence/jooq/tables/Comment.java | 9 +- .../jooq/tables/pojos/Comment.java | 18 +++- .../jooq/tables/records/CommentRecord.java | 72 ++++++++++++--- .../de/vorb/platon/view/Base64UrlMethod.java | 19 ++++ .../platon/web/mvc/WebSecurityConfig.java | 32 +++++++ .../web/mvc/avatars/AvatarController.java | 8 +- .../web/mvc/threads/CommentController.java | 10 ++- .../web/mvc/threads/ReplyFormController.java | 90 ++++++++++++++++--- .../V0.2.2018.06.11__initial_schema.sql | 3 +- src/main/resources/templates/comment-form.ftl | 34 ++++--- .../templates/snippets/page-comment.ftl | 4 +- 12 files changed, 254 insertions(+), 49 deletions(-) create mode 100644 src/main/java/de/vorb/platon/view/Base64UrlMethod.java create mode 100644 src/main/java/de/vorb/platon/web/mvc/WebSecurityConfig.java diff --git a/pom.xml b/pom.xml index 256189d..6aa3856 100644 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,10 @@ org.springframework.boot spring-boot-starter-actuator + + org.springframework.boot + spring-boot-starter-security + org.springframework.boot spring-boot-starter-freemarker diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java index 95f9ee1..0ee7e00 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java @@ -43,7 +43,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Comment extends TableImpl { - private static final long serialVersionUID = 690764222; + private static final long serialVersionUID = -804500236; /** * The reference instance of public.comment @@ -96,13 +96,18 @@ public Class getRecordType() { /** * The column public.comment.author. */ - public final TableField AUTHOR = createField("author", org.jooq.impl.SQLDataType.VARCHAR(128), this, ""); + public final TableField AUTHOR = createField("author", org.jooq.impl.SQLDataType.VARCHAR(128).nullable(false), this, ""); /** * The column public.comment.url. */ public final TableField URL = createField("url", org.jooq.impl.SQLDataType.VARCHAR(256), this, ""); + /** + * The column public.comment.author_hash. + */ + public final TableField AUTHOR_HASH = createField("author_hash", org.jooq.impl.SQLDataType.BLOB.nullable(false), this, ""); + /** * Create a public.comment table reference */ diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java index 28ccb1b..e822bef 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java @@ -25,7 +25,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Comment implements Serializable { - private static final long serialVersionUID = 2073570444; + private static final long serialVersionUID = -1369366847; private Long id; private Long threadId; @@ -36,6 +36,7 @@ public class Comment implements Serializable { private String text; private String author; private String url; + private byte[] authorHash; public Comment() {} @@ -49,6 +50,7 @@ public Comment(Comment value) { this.text = value.text; this.author = value.author; this.url = value.url; + this.authorHash = value.authorHash; } public Comment( @@ -60,7 +62,8 @@ public Comment( CommentStatus status, String text, String author, - String url + String url, + byte[] authorHash ) { this.id = id; this.threadId = threadId; @@ -71,6 +74,7 @@ public Comment( this.text = text; this.author = author; this.url = url; + this.authorHash = authorHash; } public Long getId() { @@ -154,6 +158,15 @@ public Comment setUrl(String url) { return this; } + public byte[] getAuthorHash() { + return this.authorHash; + } + + public Comment setAuthorHash(byte... authorHash) { + this.authorHash = authorHash; + return this; + } + @Override public String toString() { StringBuilder sb = new StringBuilder("Comment ("); @@ -167,6 +180,7 @@ public String toString() { sb.append(", ").append(text); sb.append(", ").append(author); sb.append(", ").append(url); + sb.append(", ").append("[binary...]"); sb.append(")"); return sb.toString(); diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java index 92ea3cc..b4a6b2c 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java @@ -13,8 +13,8 @@ import org.jooq.Field; import org.jooq.Record1; -import org.jooq.Record9; -import org.jooq.Row9; +import org.jooq.Record10; +import org.jooq.Row10; import org.jooq.impl.UpdatableRecordImpl; @@ -29,9 +29,9 @@ comments = "This class is generated by jOOQ" ) @SuppressWarnings({ "all", "unchecked", "rawtypes" }) -public class CommentRecord extends UpdatableRecordImpl implements Record9 { +public class CommentRecord extends UpdatableRecordImpl implements Record10 { - private static final long serialVersionUID = -675336345; + private static final long serialVersionUID = -643903914; /** * Setter for public.comment.id. @@ -168,6 +168,21 @@ public String getUrl() { return (String) get(8); } + /** + * Setter for public.comment.author_hash. + */ + public CommentRecord setAuthorHash(byte... value) { + set(9, value); + return this; + } + + /** + * Getter for public.comment.author_hash. + */ + public byte[] getAuthorHash() { + return (byte[]) get(9); + } + // ------------------------------------------------------------------------- // Primary key information // ------------------------------------------------------------------------- @@ -181,23 +196,23 @@ public Record1 key() { } // ------------------------------------------------------------------------- - // Record9 type implementation + // Record10 type implementation // ------------------------------------------------------------------------- /** * {@inheritDoc} */ @Override - public Row9 fieldsRow() { - return (Row9) super.fieldsRow(); + public Row10 fieldsRow() { + return (Row10) super.fieldsRow(); } /** * {@inheritDoc} */ @Override - public Row9 valuesRow() { - return (Row9) super.valuesRow(); + public Row10 valuesRow() { + return (Row10) super.valuesRow(); } /** @@ -272,6 +287,14 @@ public Field field9() { return Comment.COMMENT.URL; } + /** + * {@inheritDoc} + */ + @Override + public Field field10() { + return Comment.COMMENT.AUTHOR_HASH; + } + /** * {@inheritDoc} */ @@ -344,6 +367,14 @@ public String component9() { return getUrl(); } + /** + * {@inheritDoc} + */ + @Override + public byte[] component10() { + return getAuthorHash(); + } + /** * {@inheritDoc} */ @@ -416,6 +447,14 @@ public String value9() { return getUrl(); } + /** + * {@inheritDoc} + */ + @Override + public byte[] value10() { + return getAuthorHash(); + } + /** * {@inheritDoc} */ @@ -501,7 +540,16 @@ public CommentRecord value9(String value) { * {@inheritDoc} */ @Override - public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value4, LocalDateTime value5, CommentStatus value6, String value7, String value8, String value9) { + public CommentRecord value10(byte... value) { + setAuthorHash(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value4, LocalDateTime value5, CommentStatus value6, String value7, String value8, String value9, byte[] value10) { value1(value1); value2(value2); value3(value3); @@ -511,6 +559,7 @@ public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value7(value7); value8(value8); value9(value9); + value10(value10); return this; } @@ -528,7 +577,7 @@ public CommentRecord() { /** * Create a detached, initialised CommentRecord */ - public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creationDate, LocalDateTime lastModificationDate, CommentStatus status, String text, String author, String url) { + public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creationDate, LocalDateTime lastModificationDate, CommentStatus status, String text, String author, String url, byte[] authorHash) { super(Comment.COMMENT); set(0, id); @@ -540,5 +589,6 @@ public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creati set(6, text); set(7, author); set(8, url); + set(9, authorHash); } } diff --git a/src/main/java/de/vorb/platon/view/Base64UrlMethod.java b/src/main/java/de/vorb/platon/view/Base64UrlMethod.java new file mode 100644 index 0000000..29eea6f --- /dev/null +++ b/src/main/java/de/vorb/platon/view/Base64UrlMethod.java @@ -0,0 +1,19 @@ +package de.vorb.platon.view; + +import freemarker.template.DefaultArrayAdapter; +import freemarker.template.TemplateMethodModelEx; + +import java.util.Base64; +import java.util.List; + +public enum Base64UrlMethod implements TemplateMethodModelEx { + + INSTANCE; + + @Override + public Object exec(List arguments) { + final byte[] bytes = (byte[]) ((DefaultArrayAdapter) arguments.get(0)).getWrappedObject(); + return Base64.getUrlEncoder().encodeToString(bytes); + } + +} diff --git a/src/main/java/de/vorb/platon/web/mvc/WebSecurityConfig.java b/src/main/java/de/vorb/platon/web/mvc/WebSecurityConfig.java new file mode 100644 index 0000000..d342d83 --- /dev/null +++ b/src/main/java/de/vorb/platon/web/mvc/WebSecurityConfig.java @@ -0,0 +1,32 @@ +package de.vorb.platon.web.mvc; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .anonymous() + .and() + .authorizeRequests() + .anyRequest().permitAll() + .and() + .headers() + .xssProtection() + .and() + .and() + .formLogin() + .and() + .logout() + .permitAll(); + // @formatter:on + } + +} diff --git a/src/main/java/de/vorb/platon/web/mvc/avatars/AvatarController.java b/src/main/java/de/vorb/platon/web/mvc/avatars/AvatarController.java index 09b9fbe..651b500 100644 --- a/src/main/java/de/vorb/platon/web/mvc/avatars/AvatarController.java +++ b/src/main/java/de/vorb/platon/web/mvc/avatars/AvatarController.java @@ -22,15 +22,11 @@ @RequiredArgsConstructor public class AvatarController { - private static final List BACKGROUND_COLORS = ImmutableList.of( - new Color(0x96858f), new Color(0x6d7993), new Color(0x9099a2), new Color(0xd5d5d5)); private static final List COLORS = ImmutableList.of( new Color(0xe24e42), new Color(0xe9b000), new Color(0xeb6e80), new Color(0x008f95)); @GetMapping(value = "/avatars/{hash}", produces = MediaType.IMAGE_PNG_VALUE) - - public void bufferedImage(@PathVariable("hash") String hash, HttpServletResponse httpServletResponse) - throws IOException { + public void getAvatar(@PathVariable("hash") String hash, HttpServletResponse response) throws IOException { final Random random = new Random(hash.hashCode()); @@ -75,7 +71,7 @@ public void bufferedImage(@PathVariable("hash") String hash, HttpServletResponse g2d.dispose(); - ImageIO.write(image, "PNG", httpServletResponse.getOutputStream()); + ImageIO.write(image, "PNG", response.getOutputStream()); } } diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java b/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java index 8edc6df..6b1c369 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java @@ -4,6 +4,7 @@ import de.vorb.platon.persistence.ThreadRepository; import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; +import de.vorb.platon.view.Base64UrlMethod; import de.vorb.platon.web.mvc.errors.RequestException; import com.google.common.collect.ImmutableMap; @@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.servlet.ModelAndView; +import javax.servlet.http.HttpServletRequest; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -37,7 +39,10 @@ public class CommentController { private final CommentRepository commentRepository; @GetMapping(value = PATH_SINGLE_THREAD, produces = TEXT_HTML_VALUE) - public ModelAndView findCommentsByThreadUrl(@PathVariable(PATH_VAR_THREAD_ID) long threadId) { + public ModelAndView findCommentsByThreadUrl(@PathVariable(PATH_VAR_THREAD_ID) long threadId, + HttpServletRequest request) { + + log.info("session.id = {}", request.getSession(true).getId()); final CommentThread thread = threadRepository.findById(threadId) .orElseThrow(() -> RequestException.withStatus(HttpStatus.NOT_FOUND) @@ -49,7 +54,8 @@ public ModelAndView findCommentsByThreadUrl(@PathVariable(PATH_VAR_THREAD_ID) lo LinkedHashMap::new)); return new ModelAndView("comments-flat", - ImmutableMap.of("thread", thread, "commentCount", comments.size(), "comments", commentsById)); + ImmutableMap.of("thread", thread, "commentCount", comments.size(), "comments", commentsById, + "base64Url", Base64UrlMethod.INSTANCE)); } private static BinaryOperator throwingMerger() { diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java index cfbcbcf..98e3944 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java @@ -1,19 +1,31 @@ package de.vorb.platon.web.mvc.threads; +import de.vorb.platon.model.CommentStatus; import de.vorb.platon.persistence.CommentRepository; import de.vorb.platon.persistence.ThreadRepository; import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; +import de.vorb.platon.view.Base64UrlMethod; +import de.vorb.platon.web.mvc.comments.CommentAction; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.servlet.ModelAndView; -import java.util.Collections; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Clock; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -32,29 +44,85 @@ public ModelAndView showThreadReplyForm( @PathVariable(CommentController.PATH_VAR_THREAD_ID) long threadId, @PathVariable(value = "parentCommentId", required = false) Long parentCommentId) { + final Map model = getModelForCommentForm(threadId, parentCommentId, null); + + return new ModelAndView("comment-form", model); + } + + @PostMapping(value = {"/threads/{threadId}/reply", "/threads/{threadId}/comments/{parentCommentId}/reply"}, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.TEXT_HTML_VALUE) + public ModelAndView postComment(HttpServletRequest request, HttpServletResponse response, + @PathVariable(CommentController.PATH_VAR_THREAD_ID) long threadId, + @PathVariable(value = "parentCommentId", required = false) Long parentCommentId, + @RequestBody MultiValueMap formValues) { + + final CommentAction action = CommentAction.valueOf(formValues.getFirst("action").toUpperCase()); + final String text = formValues.getFirst("text"); + final String author = formValues.getFirst("author"); + final String url = formValues.getFirst("url"); + final boolean acceptCookie = "checked".equalsIgnoreCase(formValues.getFirst("acceptCookie")); + + byte[] authorHash = null; + if (acceptCookie) { + final String sessionId = request.getSession(true).getId(); + try { + final MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + authorHash = sha1.digest(sessionId.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + log.warn("SHA-1 not supported"); + } + } + if (authorHash == null) { + authorHash = new byte[1]; + } + + final Comment previewComment = new Comment() + .setParentId(parentCommentId) + .setCreationDate(LocalDateTime.now(Clock.systemUTC())) + .setLastModificationDate(LocalDateTime.now(Clock.systemUTC())) + .setText(text) + .setAuthor(author) + .setUrl(url) + .setAuthorHash(authorHash) + .setStatus(CommentStatus.AWAITING_MODERATION); + + final Map model = getModelForCommentForm(threadId, parentCommentId, previewComment); + model.put("previewComment", previewComment); + + return new ModelAndView("comment-form", model); + } + + private Map getModelForCommentForm(long threadId, Long parentCommentId, Comment previewComment) { final CommentThread thread = threadRepository.findById(threadId).orElseThrow(RuntimeException::new); final Optional parentComment = Optional.ofNullable(parentCommentId).flatMap(commentRepository::findById); final Map model = new HashMap<>(); model.put("thread", thread); - parentComment.ifPresent(parent -> addParent(model, parent)); + model.put("base64Url", Base64UrlMethod.INSTANCE); - return new ModelAndView("comment-form", model); - } + final Map comments = new HashMap<>(2); + + parentComment.ifPresent(parent -> { + model.put("parentComment", parent); + comments.put(parent.getId(), parent); + addParentOfParent(comments, parent); + }); + + if (previewComment != null) { + model.put("previewComment", previewComment); + } + + model.put("comments", comments); - private void addParent(Map model, Comment parentComment) { - model.put("parentComment", parentComment); - addParentOfParent(model, parentComment); + return model; } - private void addParentOfParent(Map model, Comment parentComment) { + private void addParentOfParent(Map comments, Comment parentComment) { if (parentComment.getParentId() != null) { final Comment parentOfParent = commentRepository.findById(parentComment.getParentId()).orElseThrow(RuntimeException::new); - model.put("comments", Collections.singletonMap(parentOfParent.getId(), parentOfParent)); - } else { - model.put("comments", Collections.emptyMap()); + comments.put(parentOfParent.getId(), parentOfParent); } } diff --git a/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql b/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql index b12fa94..9c5348e 100644 --- a/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql +++ b/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql @@ -13,8 +13,9 @@ CREATE TABLE comment ( last_modification_date TIMESTAMP NOT NULL, status VARCHAR(32) NOT NULL, text TEXT NOT NULL, - author VARCHAR(128) NULL, + author VARCHAR(128) NOT NULL, url VARCHAR(256) NULL, + author_hash BYTEA NOT NULL, FOREIGN KEY (thread_id) REFERENCES comment_thread (id), FOREIGN KEY (parent_id) REFERENCES comment (id) ); diff --git a/src/main/resources/templates/comment-form.ftl b/src/main/resources/templates/comment-form.ftl index 256d068..03f35e6 100644 --- a/src/main/resources/templates/comment-form.ftl +++ b/src/main/resources/templates/comment-form.ftl @@ -14,6 +14,7 @@ + <#if parentComment??>

Replying to comment

@@ -21,20 +22,27 @@
+ <#if previewComment??> +
+

Preview of your comment

+ <@page_comment thread comments previewComment true/> +
+ +
- +
- +
👤
- +
@@ -43,21 +51,22 @@
🔗
- +
- -
- +
@@ -65,6 +74,7 @@ <#if parentComment??> + diff --git a/src/main/resources/templates/snippets/page-comment.ftl b/src/main/resources/templates/snippets/page-comment.ftl index 0dab7d5..0c33916 100644 --- a/src/main/resources/templates/snippets/page-comment.ftl +++ b/src/main/resources/templates/snippets/page-comment.ftl @@ -1,8 +1,8 @@ <#ftl output_format="HTML"/> <#macro page_comment thread comments comment in_form=false> -
- +
+
<#if comment.author?? && comment.url??> From 6d40af481533cc8aae20893cc6131fedaab6ddf1 Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Sun, 1 Jul 2018 22:46:05 +0200 Subject: [PATCH 08/16] Support comment creation, preview and validation --- pom.xml | 6 + src/main/java/de/vorb/platon/PlatonApp.java | 2 +- .../persistence/jooq/tables/Comment.java | 11 +- .../jooq/tables/pojos/Comment.java | 34 +++-- .../jooq/tables/records/CommentRecord.java | 124 ++++++++++++------ .../platon/view/FreemarkerConfiguration.java | 19 +++ .../web/api/common/CommentConverter.java | 4 +- .../web/api/common/CommentSanitizer.java | 4 +- .../platon/web/mvc/WebMvcConfiguration.java | 25 ++++ .../web/mvc/comments/CommentController.java | 2 +- .../web/mvc/threads/CommentController.java | 10 +- .../web/mvc/threads/CommentFormData.java | 31 +++++ .../web/mvc/threads/ReplyFormController.java | 99 ++++++++------ .../V0.2.2018.06.11__initial_schema.sql | 3 +- src/main/resources/templates/comment-form.ftl | 39 ++++-- src/main/resources/templates/platon.ftl | 12 ++ .../templates/snippets/page-comment.ftl | 16 +-- .../java/de/vorb/platon/PlatonAppTest.java | 2 +- .../web/api/common/CommentConverterTest.java | 9 +- .../web/api/common/CommentSanitizerTest.java | 4 +- .../CommentControllerGetByIdTest.java | 4 +- .../api/controllers/CommentControllerIT.java | 4 +- .../CommentControllerUpdateTest.java | 2 +- 23 files changed, 326 insertions(+), 140 deletions(-) create mode 100644 src/main/java/de/vorb/platon/view/FreemarkerConfiguration.java create mode 100644 src/main/java/de/vorb/platon/web/mvc/WebMvcConfiguration.java create mode 100644 src/main/java/de/vorb/platon/web/mvc/threads/CommentFormData.java create mode 100644 src/main/resources/templates/platon.ftl diff --git a/pom.xml b/pom.xml index 6aa3856..1b6274b 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,7 @@ 1.10 20180219.1 1.2 + 0.11.0 1.10.0 4.1.1 @@ -131,6 +132,11 @@ encoder ${encoder.version} + + com.atlassian.commonmark + commonmark + ${commonmark.version} + org.springframework.boot diff --git a/src/main/java/de/vorb/platon/PlatonApp.java b/src/main/java/de/vorb/platon/PlatonApp.java index 817fa4e..4a30423 100644 --- a/src/main/java/de/vorb/platon/PlatonApp.java +++ b/src/main/java/de/vorb/platon/PlatonApp.java @@ -30,7 +30,7 @@ public static void main(String... args) { } @Bean - public Clock clock() { + public Clock systemClock() { return Clock.systemUTC(); } diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java index 0ee7e00..8e62838 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java @@ -43,7 +43,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Comment extends TableImpl { - private static final long serialVersionUID = -804500236; + private static final long serialVersionUID = 1931271830; /** * The reference instance of public.comment @@ -89,9 +89,14 @@ public Class getRecordType() { public final TableField STATUS = createField("status", org.jooq.impl.SQLDataType.VARCHAR(32).nullable(false), this, "", new CommentStatusConverter()); /** - * The column public.comment.text. + * The column public.comment.text_source. */ - public final TableField TEXT = createField("text", org.jooq.impl.SQLDataType.CLOB.nullable(false), this, ""); + public final TableField TEXT_SOURCE = createField("text_source", org.jooq.impl.SQLDataType.CLOB.nullable(false), this, ""); + + /** + * The column public.comment.text_html. + */ + public final TableField TEXT_HTML = createField("text_html", org.jooq.impl.SQLDataType.CLOB.nullable(false), this, ""); /** * The column public.comment.author. diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java index e822bef..69cd84a 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java @@ -25,7 +25,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Comment implements Serializable { - private static final long serialVersionUID = -1369366847; + private static final long serialVersionUID = -1470539752; private Long id; private Long threadId; @@ -33,7 +33,8 @@ public class Comment implements Serializable { private LocalDateTime creationDate; private LocalDateTime lastModificationDate; private CommentStatus status; - private String text; + private String textSource; + private String textHtml; private String author; private String url; private byte[] authorHash; @@ -47,7 +48,8 @@ public Comment(Comment value) { this.creationDate = value.creationDate; this.lastModificationDate = value.lastModificationDate; this.status = value.status; - this.text = value.text; + this.textSource = value.textSource; + this.textHtml = value.textHtml; this.author = value.author; this.url = value.url; this.authorHash = value.authorHash; @@ -60,7 +62,8 @@ public Comment( LocalDateTime creationDate, LocalDateTime lastModificationDate, CommentStatus status, - String text, + String textSource, + String textHtml, String author, String url, byte[] authorHash @@ -71,7 +74,8 @@ public Comment( this.creationDate = creationDate; this.lastModificationDate = lastModificationDate; this.status = status; - this.text = text; + this.textSource = textSource; + this.textHtml = textHtml; this.author = author; this.url = url; this.authorHash = authorHash; @@ -131,12 +135,21 @@ public Comment setStatus(CommentStatus status) { return this; } - public String getText() { - return this.text; + public String getTextSource() { + return this.textSource; } - public Comment setText(String text) { - this.text = text; + public Comment setTextSource(String textSource) { + this.textSource = textSource; + return this; + } + + public String getTextHtml() { + return this.textHtml; + } + + public Comment setTextHtml(String textHtml) { + this.textHtml = textHtml; return this; } @@ -177,7 +190,8 @@ public String toString() { sb.append(", ").append(creationDate); sb.append(", ").append(lastModificationDate); sb.append(", ").append(status); - sb.append(", ").append(text); + sb.append(", ").append(textSource); + sb.append(", ").append(textHtml); sb.append(", ").append(author); sb.append(", ").append(url); sb.append(", ").append("[binary...]"); diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java index b4a6b2c..8e36ad7 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java @@ -13,8 +13,8 @@ import org.jooq.Field; import org.jooq.Record1; -import org.jooq.Record10; -import org.jooq.Row10; +import org.jooq.Record11; +import org.jooq.Row11; import org.jooq.impl.UpdatableRecordImpl; @@ -29,9 +29,9 @@ comments = "This class is generated by jOOQ" ) @SuppressWarnings({ "all", "unchecked", "rawtypes" }) -public class CommentRecord extends UpdatableRecordImpl implements Record10 { +public class CommentRecord extends UpdatableRecordImpl implements Record11 { - private static final long serialVersionUID = -643903914; + private static final long serialVersionUID = -614973525; /** * Setter for public.comment.id. @@ -124,25 +124,40 @@ public CommentStatus getStatus() { } /** - * Setter for public.comment.text. + * Setter for public.comment.text_source. */ - public CommentRecord setText(String value) { + public CommentRecord setTextSource(String value) { set(6, value); return this; } /** - * Getter for public.comment.text. + * Getter for public.comment.text_source. */ - public String getText() { + public String getTextSource() { return (String) get(6); } + /** + * Setter for public.comment.text_html. + */ + public CommentRecord setTextHtml(String value) { + set(7, value); + return this; + } + + /** + * Getter for public.comment.text_html. + */ + public String getTextHtml() { + return (String) get(7); + } + /** * Setter for public.comment.author. */ public CommentRecord setAuthor(String value) { - set(7, value); + set(8, value); return this; } @@ -150,14 +165,14 @@ public CommentRecord setAuthor(String value) { * Getter for public.comment.author. */ public String getAuthor() { - return (String) get(7); + return (String) get(8); } /** * Setter for public.comment.url. */ public CommentRecord setUrl(String value) { - set(8, value); + set(9, value); return this; } @@ -165,14 +180,14 @@ public CommentRecord setUrl(String value) { * Getter for public.comment.url. */ public String getUrl() { - return (String) get(8); + return (String) get(9); } /** * Setter for public.comment.author_hash. */ public CommentRecord setAuthorHash(byte... value) { - set(9, value); + set(10, value); return this; } @@ -180,7 +195,7 @@ public CommentRecord setAuthorHash(byte... value) { * Getter for public.comment.author_hash. */ public byte[] getAuthorHash() { - return (byte[]) get(9); + return (byte[]) get(10); } // ------------------------------------------------------------------------- @@ -196,23 +211,23 @@ public Record1 key() { } // ------------------------------------------------------------------------- - // Record10 type implementation + // Record11 type implementation // ------------------------------------------------------------------------- /** * {@inheritDoc} */ @Override - public Row10 fieldsRow() { - return (Row10) super.fieldsRow(); + public Row11 fieldsRow() { + return (Row11) super.fieldsRow(); } /** * {@inheritDoc} */ @Override - public Row10 valuesRow() { - return (Row10) super.valuesRow(); + public Row11 valuesRow() { + return (Row11) super.valuesRow(); } /** @@ -268,7 +283,7 @@ public Field field6() { */ @Override public Field field7() { - return Comment.COMMENT.TEXT; + return Comment.COMMENT.TEXT_SOURCE; } /** @@ -276,7 +291,7 @@ public Field field7() { */ @Override public Field field8() { - return Comment.COMMENT.AUTHOR; + return Comment.COMMENT.TEXT_HTML; } /** @@ -284,6 +299,14 @@ public Field field8() { */ @Override public Field field9() { + return Comment.COMMENT.AUTHOR; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field10() { return Comment.COMMENT.URL; } @@ -291,7 +314,7 @@ public Field field9() { * {@inheritDoc} */ @Override - public Field field10() { + public Field field11() { return Comment.COMMENT.AUTHOR_HASH; } @@ -348,7 +371,7 @@ public CommentStatus component6() { */ @Override public String component7() { - return getText(); + return getTextSource(); } /** @@ -356,7 +379,7 @@ public String component7() { */ @Override public String component8() { - return getAuthor(); + return getTextHtml(); } /** @@ -364,6 +387,14 @@ public String component8() { */ @Override public String component9() { + return getAuthor(); + } + + /** + * {@inheritDoc} + */ + @Override + public String component10() { return getUrl(); } @@ -371,7 +402,7 @@ public String component9() { * {@inheritDoc} */ @Override - public byte[] component10() { + public byte[] component11() { return getAuthorHash(); } @@ -428,7 +459,7 @@ public CommentStatus value6() { */ @Override public String value7() { - return getText(); + return getTextSource(); } /** @@ -436,7 +467,7 @@ public String value7() { */ @Override public String value8() { - return getAuthor(); + return getTextHtml(); } /** @@ -444,6 +475,14 @@ public String value8() { */ @Override public String value9() { + return getAuthor(); + } + + /** + * {@inheritDoc} + */ + @Override + public String value10() { return getUrl(); } @@ -451,7 +490,7 @@ public String value9() { * {@inheritDoc} */ @Override - public byte[] value10() { + public byte[] value11() { return getAuthorHash(); } @@ -514,7 +553,7 @@ public CommentRecord value6(CommentStatus value) { */ @Override public CommentRecord value7(String value) { - setText(value); + setTextSource(value); return this; } @@ -523,7 +562,7 @@ public CommentRecord value7(String value) { */ @Override public CommentRecord value8(String value) { - setAuthor(value); + setTextHtml(value); return this; } @@ -532,6 +571,15 @@ public CommentRecord value8(String value) { */ @Override public CommentRecord value9(String value) { + setAuthor(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value10(String value) { setUrl(value); return this; } @@ -540,7 +588,7 @@ public CommentRecord value9(String value) { * {@inheritDoc} */ @Override - public CommentRecord value10(byte... value) { + public CommentRecord value11(byte... value) { setAuthorHash(value); return this; } @@ -549,7 +597,7 @@ public CommentRecord value10(byte... value) { * {@inheritDoc} */ @Override - public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value4, LocalDateTime value5, CommentStatus value6, String value7, String value8, String value9, byte[] value10) { + public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value4, LocalDateTime value5, CommentStatus value6, String value7, String value8, String value9, String value10, byte[] value11) { value1(value1); value2(value2); value3(value3); @@ -560,6 +608,7 @@ public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value8(value8); value9(value9); value10(value10); + value11(value11); return this; } @@ -577,7 +626,7 @@ public CommentRecord() { /** * Create a detached, initialised CommentRecord */ - public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creationDate, LocalDateTime lastModificationDate, CommentStatus status, String text, String author, String url, byte[] authorHash) { + public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creationDate, LocalDateTime lastModificationDate, CommentStatus status, String textSource, String textHtml, String author, String url, byte[] authorHash) { super(Comment.COMMENT); set(0, id); @@ -586,9 +635,10 @@ public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creati set(3, creationDate); set(4, lastModificationDate); set(5, status); - set(6, text); - set(7, author); - set(8, url); - set(9, authorHash); + set(6, textSource); + set(7, textHtml); + set(8, author); + set(9, url); + set(10, authorHash); } } diff --git a/src/main/java/de/vorb/platon/view/FreemarkerConfiguration.java b/src/main/java/de/vorb/platon/view/FreemarkerConfiguration.java new file mode 100644 index 0000000..4efa175 --- /dev/null +++ b/src/main/java/de/vorb/platon/view/FreemarkerConfiguration.java @@ -0,0 +1,19 @@ +package de.vorb.platon.view; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +@Configuration +@RequiredArgsConstructor +public class FreemarkerConfiguration { + + private final freemarker.template.Configuration configuration; + + @PostConstruct + private void addSharedVariables() { + configuration.setSharedVariable("base64Url", Base64UrlMethod.INSTANCE); + } + +} diff --git a/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java b/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java index 702d71a..7e12f9d 100644 --- a/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java +++ b/src/main/java/de/vorb/platon/web/api/common/CommentConverter.java @@ -50,7 +50,7 @@ public CommentJson convertPojoToJson(Comment comment) { if (comment.getStatus() != CommentStatus.DELETED) { - json.text(comment.getText()); + json.text(comment.getTextSource()); json.author(comment.getAuthor()); json.url(comment.getUrl()); @@ -69,7 +69,7 @@ public Comment convertJsonToPojo(CommentJson json) { .setCreationDate(json.getCreationDate()) .setLastModificationDate(json.getLastModificationDate()) .setStatus(json.getStatus()) - .setText(json.getText()) + .setTextHtml(json.getText()) .setAuthor(json.getAuthor()) // .setEmailHash(calculateEmailHash(json.getEmail())) .setUrl(json.getUrl()); diff --git a/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java b/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java index ca67b6e..79089db 100644 --- a/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java +++ b/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java @@ -63,9 +63,9 @@ public void sanitizeComment(Comment comment) { ); } - final String requestText = comment.getText(); + final String requestText = comment.getTextSource(); final String sanitizedText = inputSanitizer.sanitize(requestText); - comment.setText(sanitizedText); + comment.setTextHtml(sanitizedText); } private Optional validateUrl(String urlAsString) { diff --git a/src/main/java/de/vorb/platon/web/mvc/WebMvcConfiguration.java b/src/main/java/de/vorb/platon/web/mvc/WebMvcConfiguration.java new file mode 100644 index 0000000..76211c6 --- /dev/null +++ b/src/main/java/de/vorb/platon/web/mvc/WebMvcConfiguration.java @@ -0,0 +1,25 @@ +package de.vorb.platon.web.mvc; + +import com.google.common.collect.ImmutableList; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; + +import java.util.List; +import java.util.Locale; + +@Configuration +public class WebMvcConfiguration implements WebMvcConfigurer { + + private static final List SUPPORTED_LOCALES = ImmutableList.of(Locale.forLanguageTag("en-US")); + + @Bean + public LocaleResolver localeResolver() { + final AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); + localeResolver.setSupportedLocales(SUPPORTED_LOCALES); + return localeResolver; + } + +} diff --git a/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java b/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java index 94b62e9..9361cb2 100644 --- a/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java +++ b/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java @@ -256,7 +256,7 @@ public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, // .message(String.format("Comment with ID = %d does not exist", commentId)) // .build()); // -// comment.setText(commentJson.getText()); +// comment.setText(commentJson.getTextSource()); // comment.setAuthor(comment.getAuthor()); // comment.setUrl(comment.getUrl()); // diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java b/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java index 6b1c369..db8ba44 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java @@ -4,7 +4,6 @@ import de.vorb.platon.persistence.ThreadRepository; import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; -import de.vorb.platon.view.Base64UrlMethod; import de.vorb.platon.web.mvc.errors.RequestException; import com.google.common.collect.ImmutableMap; @@ -42,8 +41,6 @@ public class CommentController { public ModelAndView findCommentsByThreadUrl(@PathVariable(PATH_VAR_THREAD_ID) long threadId, HttpServletRequest request) { - log.info("session.id = {}", request.getSession(true).getId()); - final CommentThread thread = threadRepository.findById(threadId) .orElseThrow(() -> RequestException.withStatus(HttpStatus.NOT_FOUND) .message("No thread exists for ID “" + threadId + '”').build()); @@ -54,8 +51,7 @@ public ModelAndView findCommentsByThreadUrl(@PathVariable(PATH_VAR_THREAD_ID) lo LinkedHashMap::new)); return new ModelAndView("comments-flat", - ImmutableMap.of("thread", thread, "commentCount", comments.size(), "comments", commentsById, - "base64Url", Base64UrlMethod.INSTANCE)); + ImmutableMap.of("thread", thread, "commentCount", comments.size(), "comments", commentsById)); } private static BinaryOperator throwingMerger() { @@ -63,4 +59,8 @@ private static BinaryOperator throwingMerger() { throw new IllegalStateException("Duplicate key " + a); }; } + + static String pathSingleThread(long threadId) { + return PATH_SINGLE_THREAD.replace('{' + PATH_VAR_THREAD_ID + '}', String.valueOf(threadId)); + } } diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/CommentFormData.java b/src/main/java/de/vorb/platon/web/mvc/threads/CommentFormData.java new file mode 100644 index 0000000..e8f0f20 --- /dev/null +++ b/src/main/java/de/vorb/platon/web/mvc/threads/CommentFormData.java @@ -0,0 +1,31 @@ +package de.vorb.platon.web.mvc.threads; + +import de.vorb.platon.web.mvc.comments.CommentAction; + +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +@Data +class CommentFormData { + + @NotNull + private CommentAction action; + + @NotNull + @NotBlank + private String text; + + @NotNull + @NotBlank + private String author; + + @URL + private String url; + + private boolean acceptCookie; + +} diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java index 98e3944..fe291b8 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java @@ -5,22 +5,24 @@ import de.vorb.platon.persistence.ThreadRepository; import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; -import de.vorb.platon.view.Base64UrlMethod; import de.vorb.platon.web.mvc.comments.CommentAction; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; -import org.springframework.util.MultiValueMap; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -35,35 +37,54 @@ @RequiredArgsConstructor public class ReplyFormController { + private static final String VIEW_NAME = "comment-form"; + private final ThreadRepository threadRepository; private final CommentRepository commentRepository; + private final Clock clock; + private final Parser parser = Parser.builder().build(); + private final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build(); @GetMapping(value = {"/threads/{threadId}/reply", "/threads/{threadId}/comments/{parentCommentId}/reply"}, produces = MediaType.TEXT_HTML_VALUE) - public ModelAndView showThreadReplyForm( + public String showThreadReplyForm( @PathVariable(CommentController.PATH_VAR_THREAD_ID) long threadId, - @PathVariable(value = "parentCommentId", required = false) Long parentCommentId) { + @PathVariable(value = "parentCommentId", required = false) Long parentCommentId, + @ModelAttribute("comment") CommentFormData comment, Model model) { - final Map model = getModelForCommentForm(threadId, parentCommentId, null); + applyCommentFormModel(model, threadId, parentCommentId); - return new ModelAndView("comment-form", model); + return VIEW_NAME; } @PostMapping(value = {"/threads/{threadId}/reply", "/threads/{threadId}/comments/{parentCommentId}/reply"}, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.TEXT_HTML_VALUE) - public ModelAndView postComment(HttpServletRequest request, HttpServletResponse response, + public String postComment(HttpServletRequest request, @PathVariable(CommentController.PATH_VAR_THREAD_ID) long threadId, @PathVariable(value = "parentCommentId", required = false) Long parentCommentId, - @RequestBody MultiValueMap formValues) { + @Valid @ModelAttribute("comment") CommentFormData formData, BindingResult bindingResult, Model model) { + + applyCommentFormModel(model, threadId, parentCommentId); + + if (bindingResult.hasErrors()) { + return VIEW_NAME; + } else { + final Comment comment = createComment(request, threadId, parentCommentId, formData); - final CommentAction action = CommentAction.valueOf(formValues.getFirst("action").toUpperCase()); - final String text = formValues.getFirst("text"); - final String author = formValues.getFirst("author"); - final String url = formValues.getFirst("url"); - final boolean acceptCookie = "checked".equalsIgnoreCase(formValues.getFirst("acceptCookie")); + if (formData.getAction() == CommentAction.CREATE) { + final Comment storedComment = commentRepository.insert(comment); + return "redirect:" + CommentController.pathSingleThread(threadId) + "#comment-" + storedComment.getId(); + } else { + model.addAttribute("previewComment", comment); + return VIEW_NAME; + } + } + } + private Comment createComment(HttpServletRequest request, long threadId, Long parentCommentId, + CommentFormData comment) { byte[] authorHash = null; - if (acceptCookie) { + if (comment.isAcceptCookie()) { final String sessionId = request.getSession(true).getId(); try { final MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); @@ -73,49 +94,43 @@ public ModelAndView postComment(HttpServletRequest request, HttpServletResponse } } if (authorHash == null) { - authorHash = new byte[1]; + authorHash = new byte[0]; } - final Comment previewComment = new Comment() - .setParentId(parentCommentId) - .setCreationDate(LocalDateTime.now(Clock.systemUTC())) - .setLastModificationDate(LocalDateTime.now(Clock.systemUTC())) - .setText(text) - .setAuthor(author) - .setUrl(url) - .setAuthorHash(authorHash) - .setStatus(CommentStatus.AWAITING_MODERATION); + final LocalDateTime now = LocalDateTime.now(clock); - final Map model = getModelForCommentForm(threadId, parentCommentId, previewComment); - model.put("previewComment", previewComment); + final Node parsedMarkdown = parser.parse(comment.getText()); + final String textHtml = htmlRenderer.render(parsedMarkdown); - return new ModelAndView("comment-form", model); + return new Comment() + .setThreadId(threadId) + .setParentId(parentCommentId) + .setCreationDate(now) + .setLastModificationDate(now) + .setTextSource(comment.getText()) + .setTextHtml(textHtml) + .setAuthor(comment.getAuthor()) + .setUrl(comment.getUrl()) + .setAuthorHash(authorHash) + .setStatus(CommentStatus.PUBLIC); } - private Map getModelForCommentForm(long threadId, Long parentCommentId, Comment previewComment) { + private void applyCommentFormModel(Model model, long threadId, Long parentCommentId) { final CommentThread thread = threadRepository.findById(threadId).orElseThrow(RuntimeException::new); final Optional parentComment = Optional.ofNullable(parentCommentId).flatMap(commentRepository::findById); - final Map model = new HashMap<>(); - model.put("thread", thread); - model.put("base64Url", Base64UrlMethod.INSTANCE); + model.addAttribute("thread", thread); final Map comments = new HashMap<>(2); parentComment.ifPresent(parent -> { - model.put("parentComment", parent); + model.addAttribute("parentComment", parent); comments.put(parent.getId(), parent); addParentOfParent(comments, parent); }); - if (previewComment != null) { - model.put("previewComment", previewComment); - } - - model.put("comments", comments); - - return model; + model.addAttribute("comments", comments); } private void addParentOfParent(Map comments, Comment parentComment) { diff --git a/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql b/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql index 9c5348e..dcec4ab 100644 --- a/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql +++ b/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql @@ -12,7 +12,8 @@ CREATE TABLE comment ( creation_date TIMESTAMP NOT NULL, last_modification_date TIMESTAMP NOT NULL, status VARCHAR(32) NOT NULL, - text TEXT NOT NULL, + text_source TEXT NOT NULL, + text_html TEXT NOT NULL, author VARCHAR(128) NOT NULL, url VARCHAR(256) NULL, author_hash BYTEA NOT NULL, diff --git a/src/main/resources/templates/comment-form.ftl b/src/main/resources/templates/comment-form.ftl index 03f35e6..5a41e06 100644 --- a/src/main/resources/templates/comment-form.ftl +++ b/src/main/resources/templates/comment-form.ftl @@ -1,4 +1,6 @@ <#ftl output_format="HTML"/> +<#import "/spring.ftl" as spring/> +<#import "platon.ftl" as platon/> <#include "snippets/base.ftl"/> <#include "snippets/page-comment.ftl"/> @@ -31,18 +33,27 @@
- - + + <@platon.formValidationClass "comment" "text"/> + <@spring.formTextarea "comment.text" 'class="form-control ${platon.validationClass}" rows="6" placeholder="Your comment goes here"'/> +
+ <@spring.showErrors ", "/> +
+ + You can use Markdown to mark up your comment. +
- +
👤
- + <@platon.formValidationClass "comment" "author"/> + <@spring.formInput "comment.author" 'class="form-control ${platon.validationClass}" placeholder="Name"'/> +
+ <@spring.showErrors ", "/> +
@@ -51,23 +62,25 @@
🔗
- + <@platon.formValidationClass "comment" "url"/> + <@spring.formInput "comment.url" 'class="form-control ${platon.validationClass}" placeholder="URL (optional)"'/> +
+ <@spring.showErrors ", "/> +
- -
- - + +
diff --git a/src/main/resources/templates/platon.ftl b/src/main/resources/templates/platon.ftl new file mode 100644 index 0000000..20a9811 --- /dev/null +++ b/src/main/resources/templates/platon.ftl @@ -0,0 +1,12 @@ +<#ftl output_format="HTML" strip_whitespace=true> +<#import "/spring.ftl" as spring/> +<#macro formValidationClass path field> + <@spring.bind path/> + <#if !spring.status.errors.hasErrors()> + <#assign validationClass=""/> + <#elseif spring.status.errors.hasFieldErrors(field)> + <#assign validationClass="is-invalid"/> + <#else> + <#assign validationClass="is-valid"/> + + diff --git a/src/main/resources/templates/snippets/page-comment.ftl b/src/main/resources/templates/snippets/page-comment.ftl index 0c33916..4eec78f 100644 --- a/src/main/resources/templates/snippets/page-comment.ftl +++ b/src/main/resources/templates/snippets/page-comment.ftl @@ -5,14 +5,10 @@
- <#if comment.author?? && comment.url??> + <#if comment.url??> by ${comment.author} - <#elseif comment.author??> - by ${comment.author} - <#elseif comment.url??> - by an anonymous user <#else> - by an anonymous user + by ${comment.author} on @@ -22,21 +18,21 @@ <#if comment.parentId??> <#assign parentComment=comments?api.get(comment.parentId)/> - <#assign clippedParentText=parentComment.text?replace('<[^>]+>', '', 'r')[0..*80]/> - <#if clippedParentText?length < parentComment.text?length> + <#assign clippedParentText=parentComment.textHtml?replace('<[^>]+>', '', 'r')[0..*80]/> + <#if clippedParentText?length < parentComment.textHtml?length>
(In reply to comment “${clippedParentText}…”)
<#else>
- ${comment.text?no_esc} + ${comment.textHtml?no_esc}
<#if !in_form> diff --git a/src/test/java/de/vorb/platon/PlatonAppTest.java b/src/test/java/de/vorb/platon/PlatonAppTest.java index 364a738..4f6c7ce 100644 --- a/src/test/java/de/vorb/platon/PlatonAppTest.java +++ b/src/test/java/de/vorb/platon/PlatonAppTest.java @@ -27,7 +27,7 @@ public class PlatonAppTest { @Test public void configuredClockIsUtc() { - final Clock configuredClock = new PlatonApp().clock(); + final Clock configuredClock = new PlatonApp().systemClock(); final ZoneId utc = ZoneId.of("Z"); assertThat(configuredClock.getZone()).isEqualTo(utc); } diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java index 0a5b565..1b7d687 100644 --- a/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/CommentConverterTest.java @@ -23,7 +23,6 @@ import org.junit.Test; import java.time.LocalDateTime; -import java.util.Base64; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -93,7 +92,7 @@ private void assertThatMetaFieldsMatchPojo(CommentJson json, Comment comment) { } private void assertThatContentFieldsMatchPojo(CommentJson json, Comment comment) { - assertThat(json.getText()).isEqualTo(comment.getText()); + assertThat(json.getText()).isEqualTo(comment.getTextSource()); assertThat(json.getAuthor()).isEqualTo(comment.getAuthor()); // assertThat(json.getEmailHash()) @@ -118,7 +117,7 @@ private Comment prepareComment() { .setParentId(13L) .setCreationDate(LocalDateTime.now().minusSeconds(53)) .setLastModificationDate(LocalDateTime.now()) - .setText("Some text") + .setTextSource("Some text") .setAuthor("Jane Doe") // .setEmailHash("DBe/ZuZJBwFncB0tPNcXEQ==") .setUrl("https://example.org/~jane.html"); @@ -136,7 +135,7 @@ public void convertsJsonToEquivalentPojo() { assertThat(comment.getCreationDate()).isEqualTo(json.getCreationDate()); assertThat(comment.getLastModificationDate()).isEqualTo(json.getLastModificationDate()); assertThat(comment.getStatus()).isEqualTo(json.getStatus()); - assertThat(comment.getText()).isEqualTo(json.getText()); + assertThat(comment.getTextSource()).isEqualTo(json.getText()); assertThat(comment.getAuthor()).isEqualTo(json.getAuthor()); // assertThat(comment.getEmailHash()).isEqualTo("18385ac57d9b171dc3fe83a5a92b68d9"); assertThat(comment.getUrl()).isEqualTo(json.getUrl()); @@ -162,7 +161,7 @@ public void convertsMinimalJsonToEquivalentPojo() { assertThat(comment.getCreationDate()).isNull(); assertThat(comment.getLastModificationDate()).isNull(); assertThat(comment.getStatus()).isNull(); - assertThat(comment.getText()).isEqualTo(json.getText()); + assertThat(comment.getTextSource()).isEqualTo(json.getText()); assertThat(comment.getAuthor()).isNull(); // assertThat(comment.getEmailHash()).isNull(); assertThat(comment.getUrl()).isNull(); diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java index a1432fe..f14a723 100644 --- a/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java @@ -104,12 +104,12 @@ public void sanitizesCommentTextUsingInputSanitizer() { final String sanitizedText = "Sanitized text"; when(inputSanitizer.sanitize(eq(text))).thenReturn(sanitizedText); - comment.setText(text); + comment.setTextSource(text); commentSanitizer.sanitizeComment(comment); verify(inputSanitizer).sanitize(eq(text)); - assertThat(comment.getText()).isEqualTo(sanitizedText); + assertThat(comment.getTextSource()).isEqualTo(sanitizedText); } } diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java index 87952f5..42608e1 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerGetByIdTest.java @@ -45,7 +45,7 @@ public void returnsPublicComment() { final Comment publicComment = new Comment() .setId(commentId) - .setText("Text") + .setTextSource("Text") .setStatus(PUBLIC); when(commentRepository.findById(eq(commentId))).thenReturn(Optional.of(publicComment)); @@ -61,7 +61,7 @@ public void throwsNotFoundForDeletedComment() { final Comment deletedComment = new Comment() .setId(commentId) - .setText("Text") + .setTextSource("Text") .setStatus(DELETED); when(commentRepository.findById(eq(commentId))).thenReturn(Optional.of(deletedComment)); diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java index 760cd16..b995f3f 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerIT.java @@ -55,7 +55,7 @@ public class CommentControllerIT { .setCreationDate(LocalDateTime.parse(SAMPLE_CREATION_DATE)) .setLastModificationDate(LocalDateTime.parse(SAMPLE_LAST_MODIFICATION_DATE)) .setStatus(CommentStatus.PUBLIC) - .setText("Sample text") + .setTextSource("Sample text") .setAuthor("John Doe") // .setEmailHash("DBe/ZuZJBwFncB0tPNcXEQ==") .setUrl("https://example.org"); @@ -82,7 +82,7 @@ public void getCommentByIdReturnsSingleComment() throws Exception { .andExpect(jsonPath("$.creationDate").value(SAMPLE_CREATION_DATE)) .andExpect(jsonPath("$.lastModificationDate").value(SAMPLE_LAST_MODIFICATION_DATE)) .andExpect(jsonPath("$.status").value(SAMPLE_COMMENT.getStatus().toString())) - .andExpect(jsonPath("$.text").value(SAMPLE_COMMENT.getText())) + .andExpect(jsonPath("$.text").value(SAMPLE_COMMENT.getTextSource())) .andExpect(jsonPath("$.author").value(SAMPLE_COMMENT.getAuthor())) .andExpect(jsonPath("$.emailHash").value("0c17bf66e649070167701d2d3cd71711")) .andExpect(jsonPath("$.url").value(SAMPLE_COMMENT.getUrl())) diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java index 00c373c..f735ec1 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerUpdateTest.java @@ -79,7 +79,7 @@ public void setUp() throws Exception { // // verify(requestValidator).verifyValidRequest(eq(SAMPLE_SIGNATURE.toString()), eq(SAMPLE_IDENTIFIER)); // -// verify(comment).setText(eq(commentJson.getText())); +// verify(comment).setText(eq(commentJson.getTextSource())); // verify(comment).setAuthor(eq(commentJson.getAuthor())); // verify(comment).setUrl(eq(commentJson.getUrl())); // From dd2da5c66236e35bad854f579f950a1f85d1e811 Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Mon, 2 Jul 2018 01:18:57 +0200 Subject: [PATCH 09/16] Pre-render text reference of parent comment --- pom.xml | 20 ++++ .../persistence/jooq/tables/Comment.java | 7 +- .../jooq/tables/pojos/Comment.java | 16 ++- .../jooq/tables/records/CommentRecord.java | 106 +++++++++++++----- .../services/markdown/CommonmarkRenderer.java | 19 ++++ .../markdown/MarkdownConfiguration.java | 77 +++++++++++++ .../services/markdown/MarkdownRenderer.java | 7 ++ .../web/mvc/threads/ReplyFormController.java | 27 +++-- .../V0.2.2018.06.11__initial_schema.sql | 1 + src/main/resources/templates/comment-form.ftl | 2 +- .../templates/snippets/page-comment.ftl | 9 +- 11 files changed, 240 insertions(+), 51 deletions(-) create mode 100644 src/main/java/de/vorb/platon/services/markdown/CommonmarkRenderer.java create mode 100644 src/main/java/de/vorb/platon/services/markdown/MarkdownConfiguration.java create mode 100644 src/main/java/de/vorb/platon/services/markdown/MarkdownRenderer.java diff --git a/pom.xml b/pom.xml index 1b6274b..1608e75 100644 --- a/pom.xml +++ b/pom.xml @@ -137,6 +137,26 @@ commonmark ${commonmark.version} + + com.atlassian.commonmark + commonmark-ext-autolink + ${commonmark.version} + + + com.atlassian.commonmark + commonmark-ext-heading-anchor + ${commonmark.version} + + + com.atlassian.commonmark + commonmark-ext-gfm-strikethrough + ${commonmark.version} + + + com.atlassian.commonmark + commonmark-ext-gfm-tables + ${commonmark.version} + org.springframework.boot diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java index 8e62838..9ea2270 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/Comment.java @@ -43,7 +43,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Comment extends TableImpl { - private static final long serialVersionUID = 1931271830; + private static final long serialVersionUID = 1714596848; /** * The reference instance of public.comment @@ -98,6 +98,11 @@ public Class getRecordType() { */ public final TableField TEXT_HTML = createField("text_html", org.jooq.impl.SQLDataType.CLOB.nullable(false), this, ""); + /** + * The column public.comment.text_reference. + */ + public final TableField TEXT_REFERENCE = createField("text_reference", org.jooq.impl.SQLDataType.VARCHAR(80).nullable(false), this, ""); + /** * The column public.comment.author. */ diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java index 69cd84a..72b0ea4 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/pojos/Comment.java @@ -25,7 +25,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Comment implements Serializable { - private static final long serialVersionUID = -1470539752; + private static final long serialVersionUID = 552231698; private Long id; private Long threadId; @@ -35,6 +35,7 @@ public class Comment implements Serializable { private CommentStatus status; private String textSource; private String textHtml; + private String textReference; private String author; private String url; private byte[] authorHash; @@ -50,6 +51,7 @@ public Comment(Comment value) { this.status = value.status; this.textSource = value.textSource; this.textHtml = value.textHtml; + this.textReference = value.textReference; this.author = value.author; this.url = value.url; this.authorHash = value.authorHash; @@ -64,6 +66,7 @@ public Comment( CommentStatus status, String textSource, String textHtml, + String textReference, String author, String url, byte[] authorHash @@ -76,6 +79,7 @@ public Comment( this.status = status; this.textSource = textSource; this.textHtml = textHtml; + this.textReference = textReference; this.author = author; this.url = url; this.authorHash = authorHash; @@ -153,6 +157,15 @@ public Comment setTextHtml(String textHtml) { return this; } + public String getTextReference() { + return this.textReference; + } + + public Comment setTextReference(String textReference) { + this.textReference = textReference; + return this; + } + public String getAuthor() { return this.author; } @@ -192,6 +205,7 @@ public String toString() { sb.append(", ").append(status); sb.append(", ").append(textSource); sb.append(", ").append(textHtml); + sb.append(", ").append(textReference); sb.append(", ").append(author); sb.append(", ").append(url); sb.append(", ").append("[binary...]"); diff --git a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java index 8e36ad7..e65e651 100644 --- a/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java +++ b/src/main/java/de/vorb/platon/persistence/jooq/tables/records/CommentRecord.java @@ -13,8 +13,8 @@ import org.jooq.Field; import org.jooq.Record1; -import org.jooq.Record11; -import org.jooq.Row11; +import org.jooq.Record12; +import org.jooq.Row12; import org.jooq.impl.UpdatableRecordImpl; @@ -29,9 +29,9 @@ comments = "This class is generated by jOOQ" ) @SuppressWarnings({ "all", "unchecked", "rawtypes" }) -public class CommentRecord extends UpdatableRecordImpl implements Record11 { +public class CommentRecord extends UpdatableRecordImpl implements Record12 { - private static final long serialVersionUID = -614973525; + private static final long serialVersionUID = 678522085; /** * Setter for public.comment.id. @@ -153,11 +153,26 @@ public String getTextHtml() { return (String) get(7); } + /** + * Setter for public.comment.text_reference. + */ + public CommentRecord setTextReference(String value) { + set(8, value); + return this; + } + + /** + * Getter for public.comment.text_reference. + */ + public String getTextReference() { + return (String) get(8); + } + /** * Setter for public.comment.author. */ public CommentRecord setAuthor(String value) { - set(8, value); + set(9, value); return this; } @@ -165,14 +180,14 @@ public CommentRecord setAuthor(String value) { * Getter for public.comment.author. */ public String getAuthor() { - return (String) get(8); + return (String) get(9); } /** * Setter for public.comment.url. */ public CommentRecord setUrl(String value) { - set(9, value); + set(10, value); return this; } @@ -180,14 +195,14 @@ public CommentRecord setUrl(String value) { * Getter for public.comment.url. */ public String getUrl() { - return (String) get(9); + return (String) get(10); } /** * Setter for public.comment.author_hash. */ public CommentRecord setAuthorHash(byte... value) { - set(10, value); + set(11, value); return this; } @@ -195,7 +210,7 @@ public CommentRecord setAuthorHash(byte... value) { * Getter for public.comment.author_hash. */ public byte[] getAuthorHash() { - return (byte[]) get(10); + return (byte[]) get(11); } // ------------------------------------------------------------------------- @@ -211,23 +226,23 @@ public Record1 key() { } // ------------------------------------------------------------------------- - // Record11 type implementation + // Record12 type implementation // ------------------------------------------------------------------------- /** * {@inheritDoc} */ @Override - public Row11 fieldsRow() { - return (Row11) super.fieldsRow(); + public Row12 fieldsRow() { + return (Row12) super.fieldsRow(); } /** * {@inheritDoc} */ @Override - public Row11 valuesRow() { - return (Row11) super.valuesRow(); + public Row12 valuesRow() { + return (Row12) super.valuesRow(); } /** @@ -299,7 +314,7 @@ public Field field8() { */ @Override public Field field9() { - return Comment.COMMENT.AUTHOR; + return Comment.COMMENT.TEXT_REFERENCE; } /** @@ -307,6 +322,14 @@ public Field field9() { */ @Override public Field field10() { + return Comment.COMMENT.AUTHOR; + } + + /** + * {@inheritDoc} + */ + @Override + public Field field11() { return Comment.COMMENT.URL; } @@ -314,7 +337,7 @@ public Field field10() { * {@inheritDoc} */ @Override - public Field field11() { + public Field field12() { return Comment.COMMENT.AUTHOR_HASH; } @@ -387,7 +410,7 @@ public String component8() { */ @Override public String component9() { - return getAuthor(); + return getTextReference(); } /** @@ -395,6 +418,14 @@ public String component9() { */ @Override public String component10() { + return getAuthor(); + } + + /** + * {@inheritDoc} + */ + @Override + public String component11() { return getUrl(); } @@ -402,7 +433,7 @@ public String component10() { * {@inheritDoc} */ @Override - public byte[] component11() { + public byte[] component12() { return getAuthorHash(); } @@ -475,7 +506,7 @@ public String value8() { */ @Override public String value9() { - return getAuthor(); + return getTextReference(); } /** @@ -483,6 +514,14 @@ public String value9() { */ @Override public String value10() { + return getAuthor(); + } + + /** + * {@inheritDoc} + */ + @Override + public String value11() { return getUrl(); } @@ -490,7 +529,7 @@ public String value10() { * {@inheritDoc} */ @Override - public byte[] value11() { + public byte[] value12() { return getAuthorHash(); } @@ -571,7 +610,7 @@ public CommentRecord value8(String value) { */ @Override public CommentRecord value9(String value) { - setAuthor(value); + setTextReference(value); return this; } @@ -580,6 +619,15 @@ public CommentRecord value9(String value) { */ @Override public CommentRecord value10(String value) { + setAuthor(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public CommentRecord value11(String value) { setUrl(value); return this; } @@ -588,7 +636,7 @@ public CommentRecord value10(String value) { * {@inheritDoc} */ @Override - public CommentRecord value11(byte... value) { + public CommentRecord value12(byte... value) { setAuthorHash(value); return this; } @@ -597,7 +645,7 @@ public CommentRecord value11(byte... value) { * {@inheritDoc} */ @Override - public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value4, LocalDateTime value5, CommentStatus value6, String value7, String value8, String value9, String value10, byte[] value11) { + public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value4, LocalDateTime value5, CommentStatus value6, String value7, String value8, String value9, String value10, String value11, byte[] value12) { value1(value1); value2(value2); value3(value3); @@ -609,6 +657,7 @@ public CommentRecord values(Long value1, Long value2, Long value3, LocalDateTime value9(value9); value10(value10); value11(value11); + value12(value12); return this; } @@ -626,7 +675,7 @@ public CommentRecord() { /** * Create a detached, initialised CommentRecord */ - public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creationDate, LocalDateTime lastModificationDate, CommentStatus status, String textSource, String textHtml, String author, String url, byte[] authorHash) { + public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creationDate, LocalDateTime lastModificationDate, CommentStatus status, String textSource, String textHtml, String textReference, String author, String url, byte[] authorHash) { super(Comment.COMMENT); set(0, id); @@ -637,8 +686,9 @@ public CommentRecord(Long id, Long threadId, Long parentId, LocalDateTime creati set(5, status); set(6, textSource); set(7, textHtml); - set(8, author); - set(9, url); - set(10, authorHash); + set(8, textReference); + set(9, author); + set(10, url); + set(11, authorHash); } } diff --git a/src/main/java/de/vorb/platon/services/markdown/CommonmarkRenderer.java b/src/main/java/de/vorb/platon/services/markdown/CommonmarkRenderer.java new file mode 100644 index 0000000..a8844b7 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/markdown/CommonmarkRenderer.java @@ -0,0 +1,19 @@ +package de.vorb.platon.services.markdown; + +import lombok.RequiredArgsConstructor; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; + +@RequiredArgsConstructor +public class CommonmarkRenderer implements MarkdownRenderer { + + private final Parser parser; + private final HtmlRenderer htmlRenderer; + + @Override + public String renderToHtml(String markdown) { + final Node parsedMarkdown = parser.parse(markdown); + return htmlRenderer.render(parsedMarkdown); + } +} diff --git a/src/main/java/de/vorb/platon/services/markdown/MarkdownConfiguration.java b/src/main/java/de/vorb/platon/services/markdown/MarkdownConfiguration.java new file mode 100644 index 0000000..3aef19c --- /dev/null +++ b/src/main/java/de/vorb/platon/services/markdown/MarkdownConfiguration.java @@ -0,0 +1,77 @@ +package de.vorb.platon.services.markdown; + +import com.google.common.collect.ImmutableSet; +import org.commonmark.Extension; +import org.commonmark.ext.autolink.AutolinkExtension; +import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; +import org.commonmark.ext.gfm.tables.TableBlock; +import org.commonmark.ext.gfm.tables.TablesExtension; +import org.commonmark.ext.heading.anchor.HeadingAnchorExtension; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.FencedCodeBlock; +import org.commonmark.node.Heading; +import org.commonmark.node.IndentedCodeBlock; +import org.commonmark.node.ListBlock; +import org.commonmark.node.ThematicBreak; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +public class MarkdownConfiguration { + + @Bean + public Extension autolinkExtension() { + return AutolinkExtension.create(); + } + + @Bean + public Extension headingAnchorExtension() { + return HeadingAnchorExtension.builder() + .idPrefix("comment-text-") + .build(); + } + + @Bean + public Extension strikethroughExtension() { + return StrikethroughExtension.create(); + } + + @Bean + @ConditionalOnProperty("platon.markdown.gfm-tables.enabled") + public Extension tablesExtension() { + return TablesExtension.create(); + } + + @Bean + public Parser markdownParser(List commonmarkExtensions) { + return Parser.builder() + .enabledBlockTypes(ImmutableSet.of( + Heading.class, + ListBlock.class, + BlockQuote.class, + IndentedCodeBlock.class, + FencedCodeBlock.class, + ThematicBreak.class, + TableBlock.class)) + .extensions(commonmarkExtensions) + .build(); + } + + @Bean + public HtmlRenderer markdownHtmlRenderer(List commonmarkExtensions) { + return HtmlRenderer.builder() + .extensions(commonmarkExtensions) + .build(); + } + + @Bean + public MarkdownRenderer commonmarkRenderer(Parser parser, HtmlRenderer htmlRenderer) { + return new CommonmarkRenderer(parser, htmlRenderer); + } + +} diff --git a/src/main/java/de/vorb/platon/services/markdown/MarkdownRenderer.java b/src/main/java/de/vorb/platon/services/markdown/MarkdownRenderer.java new file mode 100644 index 0000000..d326824 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/markdown/MarkdownRenderer.java @@ -0,0 +1,7 @@ +package de.vorb.platon.services.markdown; + +public interface MarkdownRenderer { + + String renderToHtml(String markdown); + +} diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java index fe291b8..781b189 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java @@ -5,13 +5,12 @@ import de.vorb.platon.persistence.ThreadRepository; import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; +import de.vorb.platon.services.markdown.MarkdownRenderer; import de.vorb.platon.web.mvc.comments.CommentAction; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.HtmlRenderer; +import org.apache.commons.lang3.StringUtils; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -31,6 +30,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.regex.Pattern; @Slf4j @Controller @@ -39,11 +39,12 @@ public class ReplyFormController { private static final String VIEW_NAME = "comment-form"; + private static final Pattern HTML_TAG_PATTERN = Pattern.compile("<[^>]+>"); + private final ThreadRepository threadRepository; private final CommentRepository commentRepository; + private final MarkdownRenderer markdownRenderer; private final Clock clock; - private final Parser parser = Parser.builder().build(); - private final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build(); @GetMapping(value = {"/threads/{threadId}/reply", "/threads/{threadId}/comments/{parentCommentId}/reply"}, produces = MediaType.TEXT_HTML_VALUE) @@ -82,9 +83,9 @@ public String postComment(HttpServletRequest request, } private Comment createComment(HttpServletRequest request, long threadId, Long parentCommentId, - CommentFormData comment) { + CommentFormData formData) { byte[] authorHash = null; - if (comment.isAcceptCookie()) { + if (formData.isAcceptCookie()) { final String sessionId = request.getSession(true).getId(); try { final MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); @@ -99,18 +100,20 @@ private Comment createComment(HttpServletRequest request, long threadId, Long pa final LocalDateTime now = LocalDateTime.now(clock); - final Node parsedMarkdown = parser.parse(comment.getText()); - final String textHtml = htmlRenderer.render(parsedMarkdown); + final String textHtml = markdownRenderer.renderToHtml(formData.getText()); + final String textWithoutTags = HTML_TAG_PATTERN.matcher(formData.getText()).replaceAll("").trim(); + final String textReference = StringUtils.abbreviate(textWithoutTags, "…", 80); return new Comment() .setThreadId(threadId) .setParentId(parentCommentId) .setCreationDate(now) .setLastModificationDate(now) - .setTextSource(comment.getText()) + .setTextSource(formData.getText()) .setTextHtml(textHtml) - .setAuthor(comment.getAuthor()) - .setUrl(comment.getUrl()) + .setTextReference(textReference) + .setAuthor(formData.getAuthor()) + .setUrl(formData.getUrl()) .setAuthorHash(authorHash) .setStatus(CommentStatus.PUBLIC); } diff --git a/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql b/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql index dcec4ab..aa2a801 100644 --- a/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql +++ b/src/main/resources/db/migration/V0.2.2018.06.11__initial_schema.sql @@ -14,6 +14,7 @@ CREATE TABLE comment ( status VARCHAR(32) NOT NULL, text_source TEXT NOT NULL, text_html TEXT NOT NULL, + text_reference VARCHAR(80) NOT NULL, author VARCHAR(128) NOT NULL, url VARCHAR(256) NULL, author_hash BYTEA NOT NULL, diff --git a/src/main/resources/templates/comment-form.ftl b/src/main/resources/templates/comment-form.ftl index 5a41e06..2de960f 100644 --- a/src/main/resources/templates/comment-form.ftl +++ b/src/main/resources/templates/comment-form.ftl @@ -31,7 +31,7 @@
- +
<@platon.formValidationClass "comment" "text"/> diff --git a/src/main/resources/templates/snippets/page-comment.ftl b/src/main/resources/templates/snippets/page-comment.ftl index 4eec78f..48af8cd 100644 --- a/src/main/resources/templates/snippets/page-comment.ftl +++ b/src/main/resources/templates/snippets/page-comment.ftl @@ -18,16 +18,9 @@ <#if comment.parentId??> <#assign parentComment=comments?api.get(comment.parentId)/> - <#assign clippedParentText=parentComment.textHtml?replace('<[^>]+>', '', 'r')[0..*80]/> - <#if clippedParentText?length < parentComment.textHtml?length> - <#else> -
(In reply to comment - “${clippedParentText}”) -
- From b995ae46855c3b29295a7f976fe5a7e02729dea2 Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Mon, 2 Jul 2018 01:40:42 +0200 Subject: [PATCH 10/16] Remove support for gfm tables --- pom.xml | 5 ----- .../services/markdown/MarkdownConfiguration.java | 12 +----------- .../platon/web/mvc/threads/ReplyFormController.java | 5 ++++- src/main/resources/application.yml | 7 ------- 4 files changed, 5 insertions(+), 24 deletions(-) diff --git a/pom.xml b/pom.xml index 1608e75..81bc343 100644 --- a/pom.xml +++ b/pom.xml @@ -152,11 +152,6 @@ commonmark-ext-gfm-strikethrough ${commonmark.version} - - com.atlassian.commonmark - commonmark-ext-gfm-tables - ${commonmark.version} - org.springframework.boot diff --git a/src/main/java/de/vorb/platon/services/markdown/MarkdownConfiguration.java b/src/main/java/de/vorb/platon/services/markdown/MarkdownConfiguration.java index 3aef19c..bb0f450 100644 --- a/src/main/java/de/vorb/platon/services/markdown/MarkdownConfiguration.java +++ b/src/main/java/de/vorb/platon/services/markdown/MarkdownConfiguration.java @@ -4,8 +4,6 @@ import org.commonmark.Extension; import org.commonmark.ext.autolink.AutolinkExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; -import org.commonmark.ext.gfm.tables.TableBlock; -import org.commonmark.ext.gfm.tables.TablesExtension; import org.commonmark.ext.heading.anchor.HeadingAnchorExtension; import org.commonmark.node.BlockQuote; import org.commonmark.node.FencedCodeBlock; @@ -15,7 +13,6 @@ import org.commonmark.node.ThematicBreak; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -41,12 +38,6 @@ public Extension strikethroughExtension() { return StrikethroughExtension.create(); } - @Bean - @ConditionalOnProperty("platon.markdown.gfm-tables.enabled") - public Extension tablesExtension() { - return TablesExtension.create(); - } - @Bean public Parser markdownParser(List commonmarkExtensions) { return Parser.builder() @@ -56,8 +47,7 @@ public Parser markdownParser(List commonmarkExtensions) { BlockQuote.class, IndentedCodeBlock.class, FencedCodeBlock.class, - ThematicBreak.class, - TableBlock.class)) + ThematicBreak.class)) .extensions(commonmarkExtensions) .build(); } diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java index 781b189..0386906 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java @@ -3,6 +3,7 @@ import de.vorb.platon.model.CommentStatus; import de.vorb.platon.persistence.CommentRepository; import de.vorb.platon.persistence.ThreadRepository; +import de.vorb.platon.persistence.jooq.Tables; import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; import de.vorb.platon.services.markdown.MarkdownRenderer; @@ -40,6 +41,7 @@ public class ReplyFormController { private static final String VIEW_NAME = "comment-form"; private static final Pattern HTML_TAG_PATTERN = Pattern.compile("<[^>]+>"); + private static final int MAX_TEXT_REFERENCE_LENGTH = Tables.COMMENT.TEXT_REFERENCE.getDataType().length(); private final ThreadRepository threadRepository; private final CommentRepository commentRepository; @@ -102,7 +104,8 @@ private Comment createComment(HttpServletRequest request, long threadId, Long pa final String textHtml = markdownRenderer.renderToHtml(formData.getText()); final String textWithoutTags = HTML_TAG_PATTERN.matcher(formData.getText()).replaceAll("").trim(); - final String textReference = StringUtils.abbreviate(textWithoutTags, "…", 80); + final String textReference = StringUtils.abbreviate(textWithoutTags, "…", + MAX_TEXT_REFERENCE_LENGTH); return new Comment() .setThreadId(threadId) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3601cce..4b5b2e3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -27,13 +27,6 @@ spring.freemarker.settings: api_builtin_enabled: true platon: - input: - html-elements: > - h1, h2, h3, h4, h5, h6, - br, p, hr, div, span, - a, img, em, strong, - ol, ul, li, - blockquote, code, pre feed-reader: import-rules: [] # - feed-url: https://example.org/blog.atom From b5206dcbb5ceaf714ad1ac59992fbc07a7d8991d Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Tue, 3 Jul 2018 00:44:33 +0200 Subject: [PATCH 11/16] Remove input element restrictions --- .../web/api/common/CommentSanitizer.java | 6 --- .../web/api/common/CommentUriResolver.java | 41 ---------------- .../web/api/common/HtmlInputSanitizer.java | 47 ------------------- .../platon/web/api/common/InputSanitizer.java | 24 ---------- .../web/mvc/comments/CommentController.java | 2 - .../web/mvc/threads/ReplyFormController.java | 14 +++++- .../vorb/platon/HtmlInputSanitizerTest.java | 2 - .../controllers/CommentControllerTest.java | 1 - 8 files changed, 13 insertions(+), 124 deletions(-) delete mode 100644 src/main/java/de/vorb/platon/web/api/common/CommentUriResolver.java delete mode 100644 src/main/java/de/vorb/platon/web/api/common/HtmlInputSanitizer.java delete mode 100644 src/main/java/de/vorb/platon/web/api/common/InputSanitizer.java diff --git a/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java b/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java index 79089db..62f38a2 100644 --- a/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java +++ b/src/main/java/de/vorb/platon/web/api/common/CommentSanitizer.java @@ -42,8 +42,6 @@ public class CommentSanitizer { private static final PolicyFactory NO_HTML_POLICY = new HtmlPolicyBuilder().toFactory(); - private final InputSanitizer inputSanitizer; - public void sanitizeComment(Comment comment) { if (comment.getAuthor() != null) { @@ -62,10 +60,6 @@ public void sanitizeComment(Comment comment) { .orElse(null) ); } - - final String requestText = comment.getTextSource(); - final String sanitizedText = inputSanitizer.sanitize(requestText); - comment.setTextHtml(sanitizedText); } private Optional validateUrl(String urlAsString) { diff --git a/src/main/java/de/vorb/platon/web/api/common/CommentUriResolver.java b/src/main/java/de/vorb/platon/web/api/common/CommentUriResolver.java deleted file mode 100644 index 6006323..0000000 --- a/src/main/java/de/vorb/platon/web/api/common/CommentUriResolver.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.web.api.common; - -import lombok.SneakyThrows; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; - -import java.net.URI; -import java.util.Collections; - -import static de.vorb.platon.web.mvc.comments.CommentController.PATH_SINGLE_COMMENT; -import static de.vorb.platon.web.mvc.comments.CommentController.PATH_VAR_COMMENT_ID; - -@Component -public class CommentUriResolver { - - @SneakyThrows - public URI createRelativeCommentUriForId(long commentId) { - return new URI(ServletUriComponentsBuilder.fromCurrentRequest() - .path(PATH_SINGLE_COMMENT) - .replaceQuery(null) - .buildAndExpand(Collections.singletonMap(PATH_VAR_COMMENT_ID, commentId)) - .getPath()); - } - -} diff --git a/src/main/java/de/vorb/platon/web/api/common/HtmlInputSanitizer.java b/src/main/java/de/vorb/platon/web/api/common/HtmlInputSanitizer.java deleted file mode 100644 index 453a003..0000000 --- a/src/main/java/de/vorb/platon/web/api/common/HtmlInputSanitizer.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.web.api.common; - -import org.owasp.html.HtmlPolicyBuilder; -import org.owasp.html.PolicyFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Component -public class HtmlInputSanitizer implements InputSanitizer { - - private final PolicyFactory htmlContentPolicy; - - public HtmlInputSanitizer( - @Value("${platon.input.html-elements}") String allowedHtmlElements) { - - final HtmlPolicyBuilder htmlPolicyBuilder = new HtmlPolicyBuilder() - .allowUrlProtocols("http", "https", "mailto") - .allowAttributes("href").onElements("a") - .allowAttributes("src", "width", "height", "alt").onElements("img") - .allowAttributes("class").onElements("div", "span"); - - htmlPolicyBuilder.allowElements(allowedHtmlElements.trim().split("\\s*,\\s*")); - - this.htmlContentPolicy = htmlPolicyBuilder.toFactory(); - } - - @Override - public String sanitize(String input) { - return htmlContentPolicy.sanitize(input); - } -} diff --git a/src/main/java/de/vorb/platon/web/api/common/InputSanitizer.java b/src/main/java/de/vorb/platon/web/api/common/InputSanitizer.java deleted file mode 100644 index ae7f3c8..0000000 --- a/src/main/java/de/vorb/platon/web/api/common/InputSanitizer.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.web.api.common; - -@FunctionalInterface -public interface InputSanitizer { - - String sanitize(String input); - -} diff --git a/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java b/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java index 9361cb2..adebe47 100644 --- a/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java +++ b/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java @@ -24,7 +24,6 @@ import de.vorb.platon.security.SignatureCreator; import de.vorb.platon.web.api.common.CommentFilters; import de.vorb.platon.web.api.common.CommentSanitizer; -import de.vorb.platon.web.api.common.CommentUriResolver; import de.vorb.platon.web.api.common.RequestValidator; import de.vorb.platon.web.mvc.errors.RequestException; @@ -32,7 +31,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java index 0386906..197de52 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java @@ -38,6 +38,18 @@ @RequiredArgsConstructor public class ReplyFormController { + private static final byte[] EMPTY_STRING_HASH = new byte[20]; + + static { + try { + final MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + final byte[] hash = sha1.digest("".getBytes(StandardCharsets.UTF_8)); + System.arraycopy(hash, 0, EMPTY_STRING_HASH, 0, hash.length); + } catch (NoSuchAlgorithmException e) { + log.warn("SHA-1 not supported"); + } + } + private static final String VIEW_NAME = "comment-form"; private static final Pattern HTML_TAG_PATTERN = Pattern.compile("<[^>]+>"); @@ -97,7 +109,7 @@ private Comment createComment(HttpServletRequest request, long threadId, Long pa } } if (authorHash == null) { - authorHash = new byte[0]; + authorHash = EMPTY_STRING_HASH; } final LocalDateTime now = LocalDateTime.now(clock); diff --git a/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java b/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java index 1f56cba..fbea92e 100644 --- a/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java +++ b/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java @@ -16,8 +16,6 @@ package de.vorb.platon; -import de.vorb.platon.web.api.common.HtmlInputSanitizer; - import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java index 6560992..20706be 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java @@ -23,7 +23,6 @@ import de.vorb.platon.web.api.common.CommentConverter; import de.vorb.platon.web.api.common.CommentFilters; import de.vorb.platon.web.api.common.CommentSanitizer; -import de.vorb.platon.web.api.common.CommentUriResolver; import de.vorb.platon.web.api.common.RequestValidator; import de.vorb.platon.web.api.json.CommentJson; import de.vorb.platon.web.mvc.comments.CommentController; From 7dd36ad5d6ea4e06063eb99440bf439ec8ead2b9 Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Tue, 3 Jul 2018 01:19:03 +0200 Subject: [PATCH 12/16] Start implementing Atom feeds --- .../platon/web/mvc/AtomFeedController.java | 84 +++++++++++++++++++ .../web/mvc/comments/CommentController.java | 2 +- .../vorb/platon/HtmlInputSanitizerTest.java | 60 ++++++------- .../web/api/common/CommentSanitizerTest.java | 32 +++---- .../api/common/CommentUriResolverTest.java | 74 ---------------- .../controllers/CommentControllerTest.java | 4 +- 6 files changed, 133 insertions(+), 123 deletions(-) create mode 100644 src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java delete mode 100644 src/test/java/de/vorb/platon/web/api/common/CommentUriResolverTest.java diff --git a/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java b/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java new file mode 100644 index 0000000..d880b46 --- /dev/null +++ b/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java @@ -0,0 +1,84 @@ +package de.vorb.platon.web.mvc; + +import de.vorb.platon.persistence.CommentRepository; +import de.vorb.platon.persistence.ThreadRepository; +import de.vorb.platon.persistence.jooq.tables.pojos.Comment; +import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; + +import com.rometools.rome.feed.synd.SyndContent; +import com.rometools.rome.feed.synd.SyndContentImpl; +import com.rometools.rome.feed.synd.SyndEntry; +import com.rometools.rome.feed.synd.SyndEntryImpl; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.feed.synd.SyndFeedImpl; +import com.rometools.rome.io.FeedException; +import com.rometools.rome.io.SyndFeedOutput; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.sql.Date; +import java.time.Clock; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequiredArgsConstructor +public class AtomFeedController { + + private static final String ATOM_1_0 = "atom_1.0"; + + private final ThreadRepository threadRepository; + private final CommentRepository commentRepository; + + @GetMapping(value = "/threads/{threadId}/feed") + public void getAtomFeedForThread(@PathVariable("threadId") long threadId, HttpServletResponse response) + throws IOException, FeedException { + + response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); + + final SyndFeed atomFeed = new SyndFeedImpl(); + atomFeed.setFeedType(ATOM_1_0); + + final CommentThread thread = threadRepository.findById(threadId).orElseThrow(RuntimeException::new); + final List comments = commentRepository.findPublicByThreadId(threadId); + + atomFeed.setTitle(thread.getTitle()); + atomFeed.setLink(thread.getUrl()); + comments.stream().map(Comment::getLastModificationDate).max(LocalDateTime::compareTo).ifPresent(maxDateTime -> { + atomFeed.setPublishedDate(Date.from(maxDateTime.toInstant(ZoneOffset.UTC))); + }); + + final List entries = comments.stream() + .map(comment -> { + final SyndEntry entry = new SyndEntryImpl(); + entry.setTitle(comment.getTextReference()); + final SyndContent content = new SyndContentImpl(); + content.setType("text/html"); + content.setValue(comment.getTextHtml()); + entry.setContents(Collections.singletonList(content)); + entry.setAuthor(comment.getAuthor()); + entry.setLink("/threads/" + threadId + "/comments#comment-" + comment.getId()); + entry.setPublishedDate(Date.from(comment.getLastModificationDate().toInstant(ZoneOffset.UTC))); + return entry; + }) + .collect(Collectors.toList()); + + atomFeed.setEntries(entries); + + final SyndFeedOutput output = new SyndFeedOutput(); + try (final OutputStreamWriter outputWriter = + new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8)) { + output.output(atomFeed, outputWriter); + } + } +} diff --git a/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java b/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java index adebe47..ac69122 100644 --- a/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java +++ b/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java @@ -72,7 +72,7 @@ public class CommentController { private final CommentRepository commentRepository; private final SignatureCreator signatureCreator; - private final CommentUriResolver commentUriResolver; + // private final CommentUriResolver commentUriResolver; private final RequestValidator requestValidator; private final CommentFilters commentFilters; private final CommentSanitizer commentSanitizer; diff --git a/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java b/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java index fbea92e..64a4c84 100644 --- a/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java +++ b/src/test/java/de/vorb/platon/HtmlInputSanitizerTest.java @@ -21,33 +21,33 @@ import static org.assertj.core.api.Assertions.assertThat; -public class HtmlInputSanitizerTest { - - private HtmlInputSanitizer htmlInputSanitizer; - - @Before - public void setUp() { - htmlInputSanitizer = new HtmlInputSanitizer("p,br"); - } - - @Test - public void htmlWithScriptTag() { - - final String sanitizedHtml = htmlInputSanitizer.sanitize("

Text

"); - - assertThat(sanitizedHtml).doesNotContain(""); - assertThat(sanitizedHtml).endsWith("

"); - } - - @Test - public void worksWithMultipleTags() { - - final String sanitizedHtml = htmlInputSanitizer.sanitize("

First line
Second line

"); - - assertThat(sanitizedHtml).contains("

"); - assertThat(sanitizedHtml).contains("
"); - assertThat(sanitizedHtml).contains("

"); - } -} +//public class HtmlInputSanitizerTest { +// +// private HtmlInputSanitizer htmlInputSanitizer; +// +// @Before +// public void setUp() { +// htmlInputSanitizer = new HtmlInputSanitizer("p,br"); +// } +// +// @Test +// public void htmlWithScriptTag() { +// +// final String sanitizedHtml = htmlInputSanitizer.sanitize("

Text

"); +// +// assertThat(sanitizedHtml).doesNotContain(""); +// assertThat(sanitizedHtml).endsWith("

"); +// } +// +// @Test +// public void worksWithMultipleTags() { +// +// final String sanitizedHtml = htmlInputSanitizer.sanitize("

First line
Second line

"); +// +// assertThat(sanitizedHtml).contains("

"); +// assertThat(sanitizedHtml).contains("
"); +// assertThat(sanitizedHtml).contains("

"); +// } +//} diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java index f14a723..373d5fd 100644 --- a/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java +++ b/src/test/java/de/vorb/platon/web/api/common/CommentSanitizerTest.java @@ -35,8 +35,8 @@ public class CommentSanitizerTest { @InjectMocks private CommentSanitizer commentSanitizer; - @Mock - private InputSanitizer inputSanitizer; +// @Mock +// private InputSanitizer inputSanitizer; private final Comment comment = new Comment(); @@ -97,19 +97,19 @@ private void sanitizeCommentWithUrl(String url) { commentSanitizer.sanitizeComment(comment); } - @Test - public void sanitizesCommentTextUsingInputSanitizer() { - - final String text = "Text"; - final String sanitizedText = "Sanitized text"; - - when(inputSanitizer.sanitize(eq(text))).thenReturn(sanitizedText); - comment.setTextSource(text); - - commentSanitizer.sanitizeComment(comment); - - verify(inputSanitizer).sanitize(eq(text)); - assertThat(comment.getTextSource()).isEqualTo(sanitizedText); - } +// @Test +// public void sanitizesCommentTextUsingInputSanitizer() { +// +// final String text = "Text"; +// final String sanitizedText = "Sanitized text"; +// +// when(inputSanitizer.sanitize(eq(text))).thenReturn(sanitizedText); +// comment.setTextSource(text); +// +// commentSanitizer.sanitizeComment(comment); +// +// verify(inputSanitizer).sanitize(eq(text)); +// assertThat(comment.getTextSource()).isEqualTo(sanitizedText); +// } } diff --git a/src/test/java/de/vorb/platon/web/api/common/CommentUriResolverTest.java b/src/test/java/de/vorb/platon/web/api/common/CommentUriResolverTest.java deleted file mode 100644 index 473e1c2..0000000 --- a/src/test/java/de/vorb/platon/web/api/common/CommentUriResolverTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.vorb.platon.web.api.common; - -import org.junit.Test; -import org.springframework.http.HttpHeaders; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import javax.servlet.http.HttpServletRequest; -import java.net.URI; - -import static java.util.Collections.enumeration; -import static java.util.Collections.singleton; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class CommentUriResolverTest { - - private final CommentUriResolver commentUriResolver = new CommentUriResolver(); - private final HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); - - @Test - public void resolvesCommentUriThroughServletRequest() throws Exception { - - mockPostOrUpdateCommentRequest(); - - final URI commentUri = commentUriResolver.createRelativeCommentUriForId(1234); - - assertThat(commentUri).isEqualTo(new URI("/api/comments/1234")); - } - - private void mockPostOrUpdateCommentRequest() { - - final UriComponents uriComponents = UriComponentsBuilder.newInstance() - .scheme("https") - .host("example.org") - .path("api/comments") - .queryParam("threadUrl", "/article-1.html") - .queryParam("threadTitle", "Article") - .build(); - - final String requestUrl = uriComponents.toUriString(); - final String requestQueryString = uriComponents.getQuery(); - - when(httpServletRequest.getRequestURL()) - .thenReturn(new StringBuffer(requestUrl)); - when(httpServletRequest.getQueryString()) - .thenReturn(requestQueryString); - when(httpServletRequest.getHeaderNames()) - .thenReturn(enumeration(singleton(HttpHeaders.CONTENT_TYPE))); - when(httpServletRequest.getHeaders(eq(HttpHeaders.CONTENT_TYPE))) - .thenReturn(enumeration(singleton("application/json"))); - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(httpServletRequest)); - } -} diff --git a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java index 20706be..ed09078 100644 --- a/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java +++ b/src/test/java/de/vorb/platon/web/api/controllers/CommentControllerTest.java @@ -56,8 +56,8 @@ public abstract class CommentControllerTest { @Mock protected CommentConverter commentConverter; - @Mock - protected CommentUriResolver commentUriResolver; +// @Mock +// protected CommentUriResolver commentUriResolver; @Mock protected RequestValidator requestValidator; @Mock From 9b8670005e9c8bdc0b91459ce7e9d3d06dd31dec Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Wed, 4 Jul 2018 01:16:14 +0200 Subject: [PATCH 13/16] Start implementing Atom feeds using JAXB --- pom.xml | 10 ++ .../services/feeds/atom/AtomCategory.java | 32 +++++ .../platon/services/feeds/atom/AtomEntry.java | 32 +++++ .../platon/services/feeds/atom/AtomFeed.java | 54 ++++++++ .../platon/services/feeds/atom/AtomLink.java | 40 ++++++ .../services/feeds/atom/AtomPerson.java | 32 +++++ .../services/feeds/atom/InstantAdapter.java | 18 +++ .../services/feeds/atom/package-info.java | 8 ++ .../platon/web/mvc/AtomFeedController.java | 130 +++++++++++------- src/main/resources/application.yml | 1 + 10 files changed, 309 insertions(+), 48 deletions(-) create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomCategory.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomEntry.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomFeed.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomLink.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomPerson.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/InstantAdapter.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/package-info.java diff --git a/pom.xml b/pom.xml index 81bc343..8d49d94 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,7 @@ 1.2 0.11.0 1.10.0 + 2.3.0 4.1.1 @@ -94,6 +95,10 @@ org.springframework.boot spring-boot-starter-freemarker
+ + org.springframework + spring-oxm + org.springframework.boot spring-boot-configuration-processor @@ -171,6 +176,11 @@ rome ${rome.version} + + javax.xml.bind + jaxb-api + ${jaxb-api.version} + io.micrometer diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomCategory.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomCategory.java new file mode 100644 index 0000000..4c43933 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomCategory.java @@ -0,0 +1,32 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlType; +import java.net.URI; + +@XmlType(propOrder = {"term"}) +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomCategory { + + @XmlAttribute(name = "term", required = true) + private String term; + + @XmlAttribute(name = "scheme") + private URI scheme; + + @XmlAttribute(name = "label") + private String label; + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomEntry.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomEntry.java new file mode 100644 index 0000000..e675522 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomEntry.java @@ -0,0 +1,32 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.List; + +@XmlType +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomEntry { + + @XmlElement(name = "id") + private String id; + + @XmlElement(name = "author") + private List authors; + + @XmlElement(name = "content") + private String content; + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomFeed.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomFeed.java new file mode 100644 index 0000000..6f84995 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomFeed.java @@ -0,0 +1,54 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; +import java.util.List; + +@XmlRootElement(name = "feed") +@XmlType +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomFeed { + static final String NS_ATOM = "http://www.w3.org/2005/Atom"; + + @XmlAttribute(name = "lang") + private String language; + + @XmlElement(name = "id", required = true) + private String id; + + @XmlElement(name = "link") + private List links; + + @XmlElement(name = "category") + private List categories; + + @XmlElement(name = "title") + private String title; + + @XmlElement(name = "subtitle") + private String subtitle; + + @XmlElement(name = "updated") + @XmlJavaTypeAdapter(InstantAdapter.class) + private Instant updated; + + @XmlElement(name = "entry") + private List entries; + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomLink.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomLink.java new file mode 100644 index 0000000..f12457f --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomLink.java @@ -0,0 +1,40 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlType; + +@XmlType(propOrder = {"href", "rel", "type", "hreflang", "title", "length"}) +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomLink { + + @XmlAttribute(name = "href", required = true) + private String href; + + @XmlAttribute(name = "rel") + private String rel; + + @XmlAttribute(name = "type") + private String type; + + @XmlAttribute(name = "hreflang") + private String hreflang; + + @XmlAttribute(name = "name") + private String title; + + @XmlAttribute(name = "length") + private String length; + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomPerson.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomPerson.java new file mode 100644 index 0000000..3144f70 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomPerson.java @@ -0,0 +1,32 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.net.URI; + +@XmlType +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomPerson { + + @XmlElement(name = "name", required = true) + private String name; + + @XmlElement(name = "uri") + private URI uri; + + @XmlElement(name = "email") + private String email; + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/InstantAdapter.java b/src/main/java/de/vorb/platon/services/feeds/atom/InstantAdapter.java new file mode 100644 index 0000000..bbc73cb --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/InstantAdapter.java @@ -0,0 +1,18 @@ +package de.vorb.platon.services.feeds.atom; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.time.Instant; + +public class InstantAdapter extends XmlAdapter { + + @Override + public Instant unmarshal(String value) { + return Instant.parse(value); + } + + @Override + public String marshal(Instant value) { + return value.toString(); + } + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/package-info.java b/src/main/java/de/vorb/platon/services/feeds/atom/package-info.java new file mode 100644 index 0000000..0047d01 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/package-info.java @@ -0,0 +1,8 @@ +@XmlSchema(namespace = AtomFeed.NS_ATOM, elementFormDefault = XmlNsForm.QUALIFIED, xmlns = { + @XmlNs(namespaceURI = AtomFeed.NS_ATOM, prefix = "atom") +}) +package de.vorb.platon.services.feeds.atom; + +import javax.xml.bind.annotation.XmlNs; +import javax.xml.bind.annotation.XmlNsForm; +import javax.xml.bind.annotation.XmlSchema; diff --git a/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java b/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java index d880b46..cb7742f 100644 --- a/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java +++ b/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java @@ -4,29 +4,21 @@ import de.vorb.platon.persistence.ThreadRepository; import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; +import de.vorb.platon.services.feeds.atom.AtomEntry; +import de.vorb.platon.services.feeds.atom.AtomFeed; +import de.vorb.platon.services.feeds.atom.AtomLink; +import de.vorb.platon.services.feeds.atom.AtomPerson; -import com.rometools.rome.feed.synd.SyndContent; -import com.rometools.rome.feed.synd.SyndContentImpl; -import com.rometools.rome.feed.synd.SyndEntry; -import com.rometools.rome.feed.synd.SyndEntryImpl; -import com.rometools.rome.feed.synd.SyndFeed; -import com.rometools.rome.feed.synd.SyndFeedImpl; -import com.rometools.rome.io.FeedException; -import com.rometools.rome.io.SyndFeedOutput; +import com.google.common.collect.ImmutableMap; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.sql.Date; -import java.time.Clock; -import java.time.LocalDateTime; -import java.time.ZoneOffset; +import java.net.URI; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -40,45 +32,87 @@ public class AtomFeedController { private final ThreadRepository threadRepository; private final CommentRepository commentRepository; - @GetMapping(value = "/threads/{threadId}/feed") - public void getAtomFeedForThread(@PathVariable("threadId") long threadId, HttpServletResponse response) - throws IOException, FeedException { + @Value("${platon.public-self-url}") + private URI publicSelfUrl; - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - - final SyndFeed atomFeed = new SyndFeedImpl(); - atomFeed.setFeedType(ATOM_1_0); + @GetMapping(value = "/threads/{threadId}/feed", produces = MediaType.APPLICATION_XML_VALUE) + public AtomFeed getAtomFeedForThread(@PathVariable("threadId") long threadId) { final CommentThread thread = threadRepository.findById(threadId).orElseThrow(RuntimeException::new); final List comments = commentRepository.findPublicByThreadId(threadId); - atomFeed.setTitle(thread.getTitle()); - atomFeed.setLink(thread.getUrl()); - comments.stream().map(Comment::getLastModificationDate).max(LocalDateTime::compareTo).ifPresent(maxDateTime -> { - atomFeed.setPublishedDate(Date.from(maxDateTime.toInstant(ZoneOffset.UTC))); - }); + final URI selfLink = UriComponentsBuilder.fromUri(publicSelfUrl) + .path("/threads/{threadId}/feed") + .build() + .expand(Collections.singletonMap("threadId", threadId)) + .toUri(); - final List entries = comments.stream() - .map(comment -> { - final SyndEntry entry = new SyndEntryImpl(); - entry.setTitle(comment.getTextReference()); - final SyndContent content = new SyndContentImpl(); - content.setType("text/html"); - content.setValue(comment.getTextHtml()); - entry.setContents(Collections.singletonList(content)); - entry.setAuthor(comment.getAuthor()); - entry.setLink("/threads/" + threadId + "/comments#comment-" + comment.getId()); - entry.setPublishedDate(Date.from(comment.getLastModificationDate().toInstant(ZoneOffset.UTC))); - return entry; - }) - .collect(Collectors.toList()); + final List entries = comments.stream().map(comment -> { + final URI commentSelfLink = UriComponentsBuilder.fromUri(publicSelfUrl) + .path("/threads/{threadId}/comments/{commentId}") + .build() + .expand(ImmutableMap.of("threadId", threadId, "commentId", comment.getId())) + .toUri(); - atomFeed.setEntries(entries); + final AtomPerson.AtomPersonBuilder authorBuilder = AtomPerson.builder().name(comment.getAuthor()); + if (comment.getUrl() != null) { + authorBuilder.uri(URI.create(comment.getUrl())); + } + return AtomEntry.builder() + .id(commentSelfLink.toString()) + .authors(Collections.singletonList(authorBuilder.build())) + .content(comment.getTextHtml()) + .build(); + }).collect(Collectors.toList()); - final SyndFeedOutput output = new SyndFeedOutput(); - try (final OutputStreamWriter outputWriter = - new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8)) { - output.output(atomFeed, outputWriter); - } + return AtomFeed.builder() + .id(selfLink.toString()) + .title(String.format("Comments for “%s”", thread.getTitle())) + .links(Collections.singletonList( + AtomLink.builder().href(selfLink.toString()).rel("self").build())) + .entries(entries) + .build(); } + +// public void getAtomFeedForThread(@PathVariable("threadId") long threadId, HttpServletResponse response) +// throws IOException, FeedException { +// +// response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); +// +// final SyndFeed atomFeed = new SyndFeedImpl(); +// atomFeed.setFeedType(ATOM_1_0); +// +// final CommentThread thread = threadRepository.findById(threadId).orElseThrow(RuntimeException::new); +// final List comments = commentRepository.findPublicByThreadId(threadId); +// +// atomFeed.setTitle(thread.getTitle()); +// atomFeed.setLink(thread.getUrl()); +// comments.stream().map(Comment::getLastModificationDate).max(LocalDateTime::compareTo).ifPresent(maxDateTime +// -> { +// atomFeed.setPublishedDate(Date.from(maxDateTime.toInstant(ZoneOffset.UTC))); +// }); +// +// final List entries = comments.stream() +// .map(comment -> { +// final SyndEntry entry = new SyndEntryImpl(); +// entry.setTitle(comment.getTextReference()); +// final SyndContent content = new SyndContentImpl(); +// content.setType("text/html"); +// content.setValue(comment.getTextHtml()); +// entry.setContents(Collections.singletonList(content)); +// entry.setAuthor(comment.getAuthor()); +// entry.setLink("/threads/" + threadId + "/comments#comment-" + comment.getId()); +// entry.setPublishedDate(Date.from(comment.getLastModificationDate().toInstant(ZoneOffset.UTC))); +// return entry; +// }) +// .collect(Collectors.toList()); +// +// atomFeed.setEntries(entries); +// +// final SyndFeedOutput output = new SyndFeedOutput(); +// try (final OutputStreamWriter outputWriter = +// new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8)) { +// output.output(atomFeed, outputWriter); +// } +// } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4b5b2e3..d6fdabd 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -27,6 +27,7 @@ spring.freemarker.settings: api_builtin_enabled: true platon: + public-self-url: https://comments.example.com/ feed-reader: import-rules: [] # - feed-url: https://example.org/blog.atom From 9d0f403f53afa3a3d0342b2dbae80af7937120b0 Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Thu, 5 Jul 2018 21:18:59 +0200 Subject: [PATCH 14/16] Improve Atom feed --- pom.xml | 6 -- .../services/feeds/atom/AtomCategory.java | 2 +- .../services/feeds/atom/AtomDateTime.java | 30 ++++++ .../platon/services/feeds/atom/AtomEntry.java | 33 ++++++- .../platon/services/feeds/atom/AtomFeed.java | 10 +- .../services/feeds/atom/AtomGenerator.java | 32 +++++++ .../platon/services/feeds/atom/AtomIcon.java | 25 +++++ .../platon/services/feeds/atom/AtomLink.java | 2 +- .../platon/services/feeds/atom/AtomLogo.java | 25 +++++ .../services/feeds/atom/AtomPerson.java | 6 ++ .../services/feeds/atom/AtomRights.java | 25 +++++ .../platon/services/feeds/atom/AtomText.java | 41 +++++++++ .../platon/services/feeds/atom/TextType.java | 25 +++++ .../services/feeds/atom/package-info.java | 2 +- .../platon/web/mvc/AtomFeedController.java | 92 +++++++++---------- .../web/mvc/comments/CommentController.java | 8 +- .../web/mvc/threads/ReplyFormController.java | 6 +- 17 files changed, 298 insertions(+), 72 deletions(-) create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomDateTime.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomGenerator.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomIcon.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomLogo.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomRights.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/AtomText.java create mode 100644 src/main/java/de/vorb/platon/services/feeds/atom/TextType.java diff --git a/pom.xml b/pom.xml index 8d49d94..17c129d 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,6 @@ 1.2 0.11.0 1.10.0 - 2.3.0 4.1.1 @@ -176,11 +175,6 @@ rome ${rome.version} - - javax.xml.bind - jaxb-api - ${jaxb-api.version} - io.micrometer diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomCategory.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomCategory.java index 4c43933..add8306 100644 --- a/src/main/java/de/vorb/platon/services/feeds/atom/AtomCategory.java +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomCategory.java @@ -12,7 +12,7 @@ import javax.xml.bind.annotation.XmlType; import java.net.URI; -@XmlType(propOrder = {"term"}) +@XmlType @XmlAccessorType(XmlAccessType.FIELD) @Data @Builder diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomDateTime.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomDateTime.java new file mode 100644 index 0000000..b5a8247 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomDateTime.java @@ -0,0 +1,30 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; + +@XmlType +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomDateTime { + + @XmlValue + @XmlJavaTypeAdapter(InstantAdapter.class) + private Instant dateTime; + + public static AtomDateTime of(Instant dateTime) { + return new AtomDateTime(dateTime); + } + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomEntry.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomEntry.java index e675522..c69b003 100644 --- a/src/main/java/de/vorb/platon/services/feeds/atom/AtomEntry.java +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomEntry.java @@ -5,11 +5,14 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.w3c.dom.Element; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAnyElement; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; import java.util.List; @XmlType @@ -20,13 +23,37 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) public class AtomEntry { - @XmlElement(name = "id") + @XmlElement(name = "id", required = true) private String id; + @XmlElement(name = "title", required = true) + private String title; + + @XmlElement(name = "published") + private AtomDateTime published; + + @XmlElement(name = "updated", required = true) + private AtomDateTime updated; + @XmlElement(name = "author") - private List authors; + private List authors = new ArrayList<>(); + + @XmlElement(name = "contributor") + private List contributors = new ArrayList<>(); + + @XmlElement(name = "category") + private List categories = new ArrayList<>(); + + @XmlElement(name = "summary") + private AtomText summary; @XmlElement(name = "content") - private String content; + private AtomText content; + + @XmlElement(name = "link") + private List links = new ArrayList<>(); + + @XmlAnyElement + private List extensionElements; } diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomFeed.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomFeed.java index 6f84995..5d73b7f 100644 --- a/src/main/java/de/vorb/platon/services/feeds/atom/AtomFeed.java +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomFeed.java @@ -12,8 +12,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import java.time.Instant; +import java.util.ArrayList; import java.util.List; @XmlRootElement(name = "feed") @@ -33,7 +32,7 @@ public class AtomFeed { private String id; @XmlElement(name = "link") - private List links; + private List links = new ArrayList<>(); @XmlElement(name = "category") private List categories; @@ -45,10 +44,9 @@ public class AtomFeed { private String subtitle; @XmlElement(name = "updated") - @XmlJavaTypeAdapter(InstantAdapter.class) - private Instant updated; + private AtomDateTime updated; @XmlElement(name = "entry") - private List entries; + private List entries = new ArrayList<>(); } diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomGenerator.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomGenerator.java new file mode 100644 index 0000000..7af81dc --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomGenerator.java @@ -0,0 +1,32 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; + +@XmlType +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomGenerator { + + @XmlAttribute(name = "uri") + private String uri; + + @XmlAttribute(name = "version") + private String version; + + @XmlValue + private String value; + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomIcon.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomIcon.java new file mode 100644 index 0000000..1caa995 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomIcon.java @@ -0,0 +1,25 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; + +@XmlType +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomIcon { + + @XmlValue + private String uri; + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomLink.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomLink.java index f12457f..eb23712 100644 --- a/src/main/java/de/vorb/platon/services/feeds/atom/AtomLink.java +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomLink.java @@ -11,7 +11,7 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlType; -@XmlType(propOrder = {"href", "rel", "type", "hreflang", "title", "length"}) +@XmlType @XmlAccessorType(XmlAccessType.FIELD) @Data @Builder diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomLogo.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomLogo.java new file mode 100644 index 0000000..e44b940 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomLogo.java @@ -0,0 +1,25 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; + +@XmlType +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomLogo { + + @XmlValue + private String uri; + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomPerson.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomPerson.java index 3144f70..474f9e2 100644 --- a/src/main/java/de/vorb/platon/services/feeds/atom/AtomPerson.java +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomPerson.java @@ -5,12 +5,15 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.w3c.dom.Element; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAnyElement; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; import java.net.URI; +import java.util.List; @XmlType @XmlAccessorType(XmlAccessType.FIELD) @@ -29,4 +32,7 @@ public class AtomPerson { @XmlElement(name = "email") private String email; + @XmlAnyElement + private List extensionElements; + } diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomRights.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomRights.java new file mode 100644 index 0000000..a0a64f2 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomRights.java @@ -0,0 +1,25 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; + +@XmlType +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomRights { + + @XmlValue + private String text; + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/AtomText.java b/src/main/java/de/vorb/platon/services/feeds/atom/AtomText.java new file mode 100644 index 0000000..ae77243 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/AtomText.java @@ -0,0 +1,41 @@ +package de.vorb.platon.services.feeds.atom; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +@XmlType +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AtomText { + + @XmlAttribute(name = "type") + @XmlJavaTypeAdapter(TextType.Adapter.class) + private TextType type; + + @XmlValue + private String text; + + public static AtomText of(TextType type, String text) { + return new AtomText(type, text); + } + + public static AtomText plainText(String text) { + return AtomText.of(TextType.TEXT, text); + } + + public static AtomText html(String html) { + return AtomText.of(TextType.HTML, html); + } + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/TextType.java b/src/main/java/de/vorb/platon/services/feeds/atom/TextType.java new file mode 100644 index 0000000..6be1e14 --- /dev/null +++ b/src/main/java/de/vorb/platon/services/feeds/atom/TextType.java @@ -0,0 +1,25 @@ +package de.vorb.platon.services.feeds.atom; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +public enum TextType { + + TEXT, + HTML, + XHTML; + + public static class Adapter extends XmlAdapter { + + @Override + public TextType unmarshal(String v) throws Exception { + return TextType.valueOf(v.toUpperCase()); + } + + @Override + public String marshal(TextType v) throws Exception { + return v.toString().toLowerCase(); + } + + } + +} diff --git a/src/main/java/de/vorb/platon/services/feeds/atom/package-info.java b/src/main/java/de/vorb/platon/services/feeds/atom/package-info.java index 0047d01..b59cec0 100644 --- a/src/main/java/de/vorb/platon/services/feeds/atom/package-info.java +++ b/src/main/java/de/vorb/platon/services/feeds/atom/package-info.java @@ -1,5 +1,5 @@ @XmlSchema(namespace = AtomFeed.NS_ATOM, elementFormDefault = XmlNsForm.QUALIFIED, xmlns = { - @XmlNs(namespaceURI = AtomFeed.NS_ATOM, prefix = "atom") + @XmlNs(namespaceURI = AtomFeed.NS_ATOM, prefix = "") }) package de.vorb.platon.services.feeds.atom; diff --git a/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java b/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java index cb7742f..9c2750d 100644 --- a/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java +++ b/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java @@ -4,11 +4,14 @@ import de.vorb.platon.persistence.ThreadRepository; import de.vorb.platon.persistence.jooq.tables.pojos.Comment; import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; +import de.vorb.platon.services.feeds.atom.AtomDateTime; import de.vorb.platon.services.feeds.atom.AtomEntry; import de.vorb.platon.services.feeds.atom.AtomFeed; import de.vorb.platon.services.feeds.atom.AtomLink; import de.vorb.platon.services.feeds.atom.AtomPerson; +import de.vorb.platon.services.feeds.atom.AtomText; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -19,6 +22,9 @@ import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -27,8 +33,6 @@ @RequiredArgsConstructor public class AtomFeedController { - private static final String ATOM_1_0 = "atom_1.0"; - private final ThreadRepository threadRepository; private final CommentRepository commentRepository; @@ -41,12 +45,24 @@ public AtomFeed getAtomFeedForThread(@PathVariable("threadId") long threadId) { final CommentThread thread = threadRepository.findById(threadId).orElseThrow(RuntimeException::new); final List comments = commentRepository.findPublicByThreadId(threadId); + final Instant updated = comments.stream() + .map(Comment::getLastModificationDate) + .max(LocalDateTime::compareTo) + .map(localDateTime -> localDateTime.toInstant(ZoneOffset.UTC)) + .orElseGet(Instant::now); + final URI selfLink = UriComponentsBuilder.fromUri(publicSelfUrl) .path("/threads/{threadId}/feed") .build() .expand(Collections.singletonMap("threadId", threadId)) .toUri(); + final URI htmlLink = UriComponentsBuilder.fromUri(publicSelfUrl) + .path("/threads/{threadId}/comments") + .build() + .expand(Collections.singletonMap("threadId", threadId)) + .toUri(); + final List entries = comments.stream().map(comment -> { final URI commentSelfLink = UriComponentsBuilder.fromUri(publicSelfUrl) .path("/threads/{threadId}/comments/{commentId}") @@ -60,59 +76,41 @@ public AtomFeed getAtomFeedForThread(@PathVariable("threadId") long threadId) { } return AtomEntry.builder() .id(commentSelfLink.toString()) + .title(comment.getTextReference()) + .updated(AtomDateTime.of(comment.getLastModificationDate().toInstant(ZoneOffset.UTC))) .authors(Collections.singletonList(authorBuilder.build())) - .content(comment.getTextHtml()) + .content(AtomText.html(comment.getTextHtml())) + .links(ImmutableList.of( + AtomLink.builder() + .href(commentSelfLink.toString()) + .rel("self") + .type("text/html") + .build(), + AtomLink.builder() + .href(htmlLink.toString() + "#comment-" + comment.getId()) + .rel("alternate") + .type("text/html") + .build())) .build(); }).collect(Collectors.toList()); return AtomFeed.builder() - .id(selfLink.toString()) + .id(htmlLink.toString()) .title(String.format("Comments for “%s”", thread.getTitle())) - .links(Collections.singletonList( - AtomLink.builder().href(selfLink.toString()).rel("self").build())) + .updated(AtomDateTime.of(updated)) + .links(ImmutableList.of( + AtomLink.builder() + .href(selfLink.toString()) + .rel("self") + .type(MediaType.APPLICATION_ATOM_XML_VALUE) + .build(), + AtomLink.builder() + .href(htmlLink.toString()) + .rel("alternate") + .type(MediaType.TEXT_HTML_VALUE) + .build())) .entries(entries) .build(); } -// public void getAtomFeedForThread(@PathVariable("threadId") long threadId, HttpServletResponse response) -// throws IOException, FeedException { -// -// response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); -// -// final SyndFeed atomFeed = new SyndFeedImpl(); -// atomFeed.setFeedType(ATOM_1_0); -// -// final CommentThread thread = threadRepository.findById(threadId).orElseThrow(RuntimeException::new); -// final List comments = commentRepository.findPublicByThreadId(threadId); -// -// atomFeed.setTitle(thread.getTitle()); -// atomFeed.setLink(thread.getUrl()); -// comments.stream().map(Comment::getLastModificationDate).max(LocalDateTime::compareTo).ifPresent(maxDateTime -// -> { -// atomFeed.setPublishedDate(Date.from(maxDateTime.toInstant(ZoneOffset.UTC))); -// }); -// -// final List entries = comments.stream() -// .map(comment -> { -// final SyndEntry entry = new SyndEntryImpl(); -// entry.setTitle(comment.getTextReference()); -// final SyndContent content = new SyndContentImpl(); -// content.setType("text/html"); -// content.setValue(comment.getTextHtml()); -// entry.setContents(Collections.singletonList(content)); -// entry.setAuthor(comment.getAuthor()); -// entry.setLink("/threads/" + threadId + "/comments#comment-" + comment.getId()); -// entry.setPublishedDate(Date.from(comment.getLastModificationDate().toInstant(ZoneOffset.UTC))); -// return entry; -// }) -// .collect(Collectors.toList()); -// -// atomFeed.setEntries(entries); -// -// final SyndFeedOutput output = new SyndFeedOutput(); -// try (final OutputStreamWriter outputWriter = -// new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8)) { -// output.output(atomFeed, outputWriter); -// } -// } } diff --git a/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java b/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java index ac69122..fab92ed 100644 --- a/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java +++ b/src/main/java/de/vorb/platon/web/mvc/comments/CommentController.java @@ -126,7 +126,7 @@ public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, return new ModelAndView("comment-form", ImmutableMap.of("threadUrl", threadUrl, "threadTitle", threadTitle)); } -// @PostMapping(value = PATH_LIST_COMMENTS, produces = TEXT_HTML_VALUE) +// @PostMapping(uri = PATH_LIST_COMMENTS, produces = TEXT_HTML_VALUE) // public ModelAndView postComment( // @RequestBody MultiValueMap commentData) { // @@ -158,11 +158,11 @@ public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, // } // } // -// @PostMapping(value = PATH_LIST_COMMENTS, consumes = APPLICATION_FORM_URLENCODED_VALUE, produces = TEXT_HTML_VALUE) +// @PostMapping(uri = PATH_LIST_COMMENTS, consumes = APPLICATION_FORM_URLENCODED_VALUE, produces = TEXT_HTML_VALUE) // public ModelAndView postComment( // @RequestParam("url") String threadUrl, // @RequestParam("threadTitle") String threadTitle, -// @RequestParam(value = "action", defaultValue = "CREATE") CommentAction action, +// @RequestParam(uri = "action", defaultValue = "CREATE") CommentAction action, // @RequestBody MultiValueMap commentData) { // // if (commentData.getId() != null) { @@ -233,7 +233,7 @@ public ModelAndView showCommentForm(@RequestParam("threadUrl") String threadUrl, // } // // -// @PutMapping(value = PATH_SINGLE_COMMENT, consumes = APPLICATION_JSON_VALUE) +// @PutMapping(uri = PATH_SINGLE_COMMENT, consumes = APPLICATION_JSON_VALUE) // public void updateComment( // @PathVariable(PATH_VAR_COMMENT_ID) Long commentId, // @RequestHeader(SIGNATURE_HEADER) String signature, diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java index 197de52..d88f490 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java @@ -115,9 +115,9 @@ private Comment createComment(HttpServletRequest request, long threadId, Long pa final LocalDateTime now = LocalDateTime.now(clock); final String textHtml = markdownRenderer.renderToHtml(formData.getText()); - final String textWithoutTags = HTML_TAG_PATTERN.matcher(formData.getText()).replaceAll("").trim(); - final String textReference = StringUtils.abbreviate(textWithoutTags, "…", - MAX_TEXT_REFERENCE_LENGTH); + final String textWithoutLineBreaks = formData.getText().replace("\n", "").replace("\r", ""); + final String textWithoutMarkup = HTML_TAG_PATTERN.matcher(textWithoutLineBreaks).replaceAll("").trim(); + final String textReference = StringUtils.abbreviate(textWithoutMarkup, "…", MAX_TEXT_REFERENCE_LENGTH); return new Comment() .setThreadId(threadId) From cbe1ed37676ab99bdfe8ca1cc3f2e357e8ee13ad Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Thu, 5 Jul 2018 22:08:30 +0200 Subject: [PATCH 15/16] Improve descriptions in comment form --- src/main/resources/templates/comment-form.ftl | 25 +++++++++++++------ .../resources/templates/snippets/base.ftl | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/resources/templates/comment-form.ftl b/src/main/resources/templates/comment-form.ftl index 2de960f..cf586ed 100644 --- a/src/main/resources/templates/comment-form.ftl +++ b/src/main/resources/templates/comment-form.ftl @@ -34,7 +34,7 @@
- <@platon.formValidationClass "comment" "text"/> + <@platon.formValidationClass "comment" "text"/> <@spring.formTextarea "comment.text" 'class="form-control ${platon.validationClass}" rows="6" placeholder="Your comment goes here"'/>
<@spring.showErrors ", "/> @@ -45,38 +45,49 @@
-
+
👤
<@platon.formValidationClass "comment" "author"/> - <@spring.formInput "comment.author" 'class="form-control ${platon.validationClass}" placeholder="Name"'/> + <@spring.formInput "comment.author" 'class="form-control rounded-right ${platon.validationClass}" placeholder="Name"'/>
<@spring.showErrors ", "/>
+ + Your name. Feel free to enter a pseudonym if you don’t want to give your real name. +
-
+
🔗
<@platon.formValidationClass "comment" "url"/> - <@spring.formInput "comment.url" 'class="form-control ${platon.validationClass}" placeholder="URL (optional)"'/> + <@spring.formInput "comment.url" 'class="form-control rounded-right ${platon.validationClass}" placeholder="URL (optional)"'/>
<@spring.showErrors ", "/>
+ + An HTTP(S) URL that you want to link your name to. This might be a personal blog, Twitter account, etc. +
<@spring.formCheckbox "comment.acceptCookie" 'class="form-check-input"'/>
+ + A randomly generated identicon will be visible next to your comment, if you don't give consent. Please + be aware, that you can withdraw your given consent at any time by clicking the “delete cookie” link + next to any of your comments. +
diff --git a/src/main/resources/templates/snippets/base.ftl b/src/main/resources/templates/snippets/base.ftl index 3fe3adc..c07450d 100644 --- a/src/main/resources/templates/snippets/base.ftl +++ b/src/main/resources/templates/snippets/base.ftl @@ -48,7 +48,7 @@ <@page_content/> -
From f2411d6a7d48be5143d98e2982d176dd809bc830 Mon Sep 17 00:00:00 2001 From: Paul Vorbach Date: Tue, 10 Jul 2018 22:41:25 +0200 Subject: [PATCH 16/16] WIP --- src/main/java/de/vorb/platon/PlatonApp.java | 6 ++ .../platon/persistence/CommentRepository.java | 4 ++ .../persistence/PropertyRepository.java | 4 ++ .../platon/persistence/ThreadRepository.java | 4 ++ .../security/DatabaseSecretKeyProvider.java | 2 + .../platon/view/ByteArrayEqualsMethod.java | 26 +++++++ .../platon/view/FreemarkerConfiguration.java | 1 + .../platon/web/mvc/AtomFeedController.java | 2 + .../web/mvc/threads/CommentController.java | 26 ++++++- ...roller.java => CommentFormController.java} | 69 +++++++++++++++---- .../mvc/threads/ThreadRedirectController.java | 1 + src/main/resources/templates/comment-form.ftl | 2 +- .../templates/snippets/page-comment.ftl | 5 +- 13 files changed, 132 insertions(+), 20 deletions(-) create mode 100644 src/main/java/de/vorb/platon/view/ByteArrayEqualsMethod.java rename src/main/java/de/vorb/platon/web/mvc/threads/{ReplyFormController.java => CommentFormController.java} (70%) diff --git a/src/main/java/de/vorb/platon/PlatonApp.java b/src/main/java/de/vorb/platon/PlatonApp.java index 4a30423..cf08593 100644 --- a/src/main/java/de/vorb/platon/PlatonApp.java +++ b/src/main/java/de/vorb/platon/PlatonApp.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; +import java.security.SecureRandom; import java.time.Clock; @SpringBootApplication @@ -34,4 +35,9 @@ public Clock systemClock() { return Clock.systemUTC(); } + @Bean + public SecureRandom secureRandom() { + return new SecureRandom(); + } + } diff --git a/src/main/java/de/vorb/platon/persistence/CommentRepository.java b/src/main/java/de/vorb/platon/persistence/CommentRepository.java index 44786dd..e468e89 100644 --- a/src/main/java/de/vorb/platon/persistence/CommentRepository.java +++ b/src/main/java/de/vorb/platon/persistence/CommentRepository.java @@ -19,11 +19,15 @@ import de.vorb.platon.model.CommentStatus; import de.vorb.platon.persistence.jooq.tables.pojos.Comment; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +@Transactional(propagation = Propagation.MANDATORY) public interface CommentRepository { List findByThreadId(long threadId); diff --git a/src/main/java/de/vorb/platon/persistence/PropertyRepository.java b/src/main/java/de/vorb/platon/persistence/PropertyRepository.java index 1c86bf9..7f10143 100644 --- a/src/main/java/de/vorb/platon/persistence/PropertyRepository.java +++ b/src/main/java/de/vorb/platon/persistence/PropertyRepository.java @@ -16,6 +16,10 @@ package de.vorb.platon.persistence; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(propagation = Propagation.MANDATORY) public interface PropertyRepository { String findValueByKey(String key); diff --git a/src/main/java/de/vorb/platon/persistence/ThreadRepository.java b/src/main/java/de/vorb/platon/persistence/ThreadRepository.java index 1a902d8..3fd5b3d 100644 --- a/src/main/java/de/vorb/platon/persistence/ThreadRepository.java +++ b/src/main/java/de/vorb/platon/persistence/ThreadRepository.java @@ -18,9 +18,13 @@ import de.vorb.platon.persistence.jooq.tables.pojos.CommentThread; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + import java.util.List; import java.util.Optional; +@Transactional(propagation = Propagation.MANDATORY) public interface ThreadRepository { Optional findById(long id); diff --git a/src/main/java/de/vorb/platon/security/DatabaseSecretKeyProvider.java b/src/main/java/de/vorb/platon/security/DatabaseSecretKeyProvider.java index 09d43ea..9122c23 100644 --- a/src/main/java/de/vorb/platon/security/DatabaseSecretKeyProvider.java +++ b/src/main/java/de/vorb/platon/security/DatabaseSecretKeyProvider.java @@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -42,6 +43,7 @@ public class DatabaseSecretKeyProvider implements SecretKeyProvider { private SecretKey secretKey; @Override + @Transactional public SecretKey getSecretKey() { if (secretKey == null) { diff --git a/src/main/java/de/vorb/platon/view/ByteArrayEqualsMethod.java b/src/main/java/de/vorb/platon/view/ByteArrayEqualsMethod.java new file mode 100644 index 0000000..4f61e15 --- /dev/null +++ b/src/main/java/de/vorb/platon/view/ByteArrayEqualsMethod.java @@ -0,0 +1,26 @@ +package de.vorb.platon.view; + +import freemarker.template.DefaultArrayAdapter; +import freemarker.template.TemplateMethodModelEx; +import freemarker.template.TemplateModelException; + +import java.util.Arrays; +import java.util.List; + +public enum ByteArrayEqualsMethod implements TemplateMethodModelEx { + + INSTANCE; + + @Override + public Object exec(List arguments) throws TemplateModelException { + try { + return Arrays.equals( + (byte[]) ((DefaultArrayAdapter) arguments.get(0)).getWrappedObject(), + (byte[]) ((DefaultArrayAdapter) arguments.get(1)).getWrappedObject() + ); + } catch (Exception e) { + throw new TemplateModelException(e); + } + } + +} diff --git a/src/main/java/de/vorb/platon/view/FreemarkerConfiguration.java b/src/main/java/de/vorb/platon/view/FreemarkerConfiguration.java index 4efa175..488bca8 100644 --- a/src/main/java/de/vorb/platon/view/FreemarkerConfiguration.java +++ b/src/main/java/de/vorb/platon/view/FreemarkerConfiguration.java @@ -14,6 +14,7 @@ public class FreemarkerConfiguration { @PostConstruct private void addSharedVariables() { configuration.setSharedVariable("base64Url", Base64UrlMethod.INSTANCE); + configuration.setSharedVariable("byteArrayEquals", ByteArrayEqualsMethod.INSTANCE); } } diff --git a/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java b/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java index 9c2750d..2f098d4 100644 --- a/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java +++ b/src/main/java/de/vorb/platon/web/mvc/AtomFeedController.java @@ -16,6 +16,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @@ -40,6 +41,7 @@ public class AtomFeedController { private URI publicSelfUrl; @GetMapping(value = "/threads/{threadId}/feed", produces = MediaType.APPLICATION_XML_VALUE) + @Transactional(readOnly = true) public AtomFeed getAtomFeedForThread(@PathVariable("threadId") long threadId) { final CommentThread thread = threadRepository.findById(threadId).orElseThrow(RuntimeException::new); diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java b/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java index db8ba44..7f96522 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/CommentController.java @@ -11,11 +11,16 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -38,8 +43,10 @@ public class CommentController { private final CommentRepository commentRepository; @GetMapping(value = PATH_SINGLE_THREAD, produces = TEXT_HTML_VALUE) - public ModelAndView findCommentsByThreadUrl(@PathVariable(PATH_VAR_THREAD_ID) long threadId, - HttpServletRequest request) { + @Transactional(readOnly = true) + public ModelAndView findCommentsByThreadUrl(HttpServletRequest request, + @PathVariable(PATH_VAR_THREAD_ID) long threadId, + @CookieValue(value = CommentFormController.AUTHOR_ID_COOKIE, required = false) String authorId) { final CommentThread thread = threadRepository.findById(threadId) .orElseThrow(() -> RequestException.withStatus(HttpStatus.NOT_FOUND) @@ -50,8 +57,21 @@ public ModelAndView findCommentsByThreadUrl(@PathVariable(PATH_VAR_THREAD_ID) lo .collect(Collectors.toMap(Comment::getId, Function.identity(), throwingMerger(), LinkedHashMap::new)); + byte[] authorHash = null; + final String authorOrSessionId = authorId != null ? authorId : request.getSession(true).getId(); + try { + final MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + authorHash = sha1.digest(authorOrSessionId.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + log.warn("SHA-1 not supported"); + } + if (authorHash == null) { + authorHash = CommentFormController.EMPTY_STRING_HASH; + } + return new ModelAndView("comments-flat", - ImmutableMap.of("thread", thread, "commentCount", comments.size(), "comments", commentsById)); + ImmutableMap.of("thread", thread, "commentCount", comments.size(), "comments", commentsById, + "authorHash", authorHash)); } private static BinaryOperator throwingMerger() { diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java b/src/main/java/de/vorb/platon/web/mvc/threads/CommentFormController.java similarity index 70% rename from src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java rename to src/main/java/de/vorb/platon/web/mvc/threads/CommentFormController.java index d88f490..8210410 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/ReplyFormController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/CommentFormController.java @@ -14,31 +14,41 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; +import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.time.Clock; +import java.time.Duration; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.regex.Pattern; @Slf4j @Controller @RequiredArgsConstructor -public class ReplyFormController { +public class CommentFormController { - private static final byte[] EMPTY_STRING_HASH = new byte[20]; + static final String AUTHOR_ID_COOKIE = "author_id"; + private static final int MAX_COOKIE_AGE = (int) Duration.ofDays(10 * 365).getSeconds(); + + static final byte[] EMPTY_STRING_HASH = new byte[20]; static { try { @@ -59,10 +69,12 @@ public class ReplyFormController { private final CommentRepository commentRepository; private final MarkdownRenderer markdownRenderer; private final Clock clock; + private final SecureRandom secureRandom; @GetMapping(value = {"/threads/{threadId}/reply", "/threads/{threadId}/comments/{parentCommentId}/reply"}, produces = MediaType.TEXT_HTML_VALUE) - public String showThreadReplyForm( + @Transactional(readOnly = true) + public String showReplyForm( @PathVariable(CommentController.PATH_VAR_THREAD_ID) long threadId, @PathVariable(value = "parentCommentId", required = false) Long parentCommentId, @ModelAttribute("comment") CommentFormData comment, Model model) { @@ -74,39 +86,66 @@ public String showThreadReplyForm( @PostMapping(value = {"/threads/{threadId}/reply", "/threads/{threadId}/comments/{parentCommentId}/reply"}, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.TEXT_HTML_VALUE) - public String postComment(HttpServletRequest request, + @Transactional + public String postNewComment(HttpServletRequest request, HttpServletResponse response, @PathVariable(CommentController.PATH_VAR_THREAD_ID) long threadId, @PathVariable(value = "parentCommentId", required = false) Long parentCommentId, + @CookieValue(value = AUTHOR_ID_COOKIE, required = false) String authorId, @Valid @ModelAttribute("comment") CommentFormData formData, BindingResult bindingResult, Model model) { applyCommentFormModel(model, threadId, parentCommentId); + if (formData.isAcceptCookie() && authorId == null) { + final Cookie commentAuthorCookie = new Cookie(AUTHOR_ID_COOKIE, UUID.randomUUID().toString()); + commentAuthorCookie.setMaxAge(MAX_COOKIE_AGE); + response.addCookie(commentAuthorCookie); + } + if (bindingResult.hasErrors()) { return VIEW_NAME; } else { - final Comment comment = createComment(request, threadId, parentCommentId, formData); + final Comment comment = createComment(request, threadId, parentCommentId, authorId, formData); if (formData.getAction() == CommentAction.CREATE) { final Comment storedComment = commentRepository.insert(comment); return "redirect:" + CommentController.pathSingleThread(threadId) + "#comment-" + storedComment.getId(); - } else { + } else if (formData.getAction() == CommentAction.PREVIEW) { model.addAttribute("previewComment", comment); return VIEW_NAME; + } else { + throw new RuntimeException(); } } } + @GetMapping(value = {"/threads/{threadId}/comments/{commentId}/edit"}, produces = MediaType.TEXT_HTML_VALUE) + @Transactional(readOnly = true) + public String showEditForm( + @PathVariable(CommentController.PATH_VAR_THREAD_ID) long threadId, + @PathVariable(value = "commentId") Long commentId, + @ModelAttribute("comment") CommentFormData comment, Model model) { + + final CommentThread thread = threadRepository.findById(threadId).orElseThrow(RuntimeException::new); + final Comment edit = commentRepository.findById(commentId).orElseThrow(RuntimeException::new); + + model.addAttribute("thread", thread); + comment.setText(edit.getTextSource()); + comment.setAuthor(edit.getAuthor()); + comment.setUrl(edit.getUrl()); + + return VIEW_NAME; + } + private Comment createComment(HttpServletRequest request, long threadId, Long parentCommentId, - CommentFormData formData) { + String authorId, CommentFormData formData) { byte[] authorHash = null; - if (formData.isAcceptCookie()) { - final String sessionId = request.getSession(true).getId(); - try { - final MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); - authorHash = sha1.digest(sessionId.getBytes(StandardCharsets.UTF_8)); - } catch (NoSuchAlgorithmException e) { - log.warn("SHA-1 not supported"); - } + + final String authorOrSessionId = authorId != null ? authorId : request.getSession(true).getId(); + try { + final MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + authorHash = sha1.digest(authorOrSessionId.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + log.warn("SHA-1 not supported"); } if (authorHash == null) { authorHash = EMPTY_STRING_HASH; diff --git a/src/main/java/de/vorb/platon/web/mvc/threads/ThreadRedirectController.java b/src/main/java/de/vorb/platon/web/mvc/threads/ThreadRedirectController.java index ead9945..89ce836 100644 --- a/src/main/java/de/vorb/platon/web/mvc/threads/ThreadRedirectController.java +++ b/src/main/java/de/vorb/platon/web/mvc/threads/ThreadRedirectController.java @@ -37,6 +37,7 @@ public String redirectToComments( @GetMapping(value = CommentController.PATH_LIST_THREADS + '/' + THREAD_ID_REPLACEMENT_TARGET) @ResponseStatus(HttpStatus.TEMPORARY_REDIRECT) + @Transactional(readOnly = true) public String redirectToComments(@PathVariable("threadId") long threadId) { CommentThread thread = threadRepository.findById(threadId).orElseThrow(IndexOutOfBoundsException::new); diff --git a/src/main/resources/templates/comment-form.ftl b/src/main/resources/templates/comment-form.ftl index cf586ed..d0a582e 100644 --- a/src/main/resources/templates/comment-form.ftl +++ b/src/main/resources/templates/comment-form.ftl @@ -5,7 +5,7 @@ <#include "snippets/page-comment.ftl"/> <#macro page_title> - Comments for “${thread.title}” + Leave a comment on “${thread.title}” <#macro page_header> diff --git a/src/main/resources/templates/snippets/page-comment.ftl b/src/main/resources/templates/snippets/page-comment.ftl index 48af8cd..7ecbfd9 100644 --- a/src/main/resources/templates/snippets/page-comment.ftl +++ b/src/main/resources/templates/snippets/page-comment.ftl @@ -31,7 +31,10 @@ <#if !in_form>