From cf94f4633fff7afceca7ee35b6cf0166b81cb57b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Kabaday=C4=B1?= Date: Tue, 19 Dec 2023 00:34:56 +0300 Subject: [PATCH 01/43] add annotation library --- app/frontend/package.json | 1 + app/frontend/pnpm-lock.yaml | 773 ++++++++++++++++++++++++++++++++++-- 2 files changed, 751 insertions(+), 23 deletions(-) diff --git a/app/frontend/package.json b/app/frontend/package.json index b64be722..016acbaf 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@ant-design/icons": "^5.2.6", + "@recogito/recogito-js": "^1.8.2", "@types/js-cookie": "^3.0.5", "antd": "^5.10.2", "axios": "^1.6.0", diff --git a/app/frontend/pnpm-lock.yaml b/app/frontend/pnpm-lock.yaml index 8d08d618..7c172fd1 100644 --- a/app/frontend/pnpm-lock.yaml +++ b/app/frontend/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: '@ant-design/icons': specifier: ^5.2.6 version: 5.2.6(react-dom@18.2.0)(react@18.2.0) + '@recogito/recogito-js': + specifier: ^1.8.2 + version: 1.8.2(@types/react@18.2.28)(react-dom@18.2.0)(react@18.2.0) '@types/js-cookie': specifier: ^3.0.5 version: 3.0.5 @@ -183,7 +186,6 @@ packages: dependencies: '@babel/highlight': 7.22.20 chalk: 2.4.2 - dev: true /@babel/compat-data@7.23.2: resolution: {integrity: sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==} @@ -259,7 +261,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.23.0 - dev: true /@babel/helper-module-transforms@7.23.0(@babel/core@7.23.2): resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==} @@ -297,12 +298,10 @@ packages: /@babel/helper-string-parser@7.22.5: resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-option@7.22.15: resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} @@ -327,7 +326,6 @@ packages: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 - dev: true /@babel/parser@7.23.0: resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} @@ -397,21 +395,109 @@ packages: '@babel/helper-string-parser': 7.22.5 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - dev: true /@ctrl/tinycolor@3.6.1: resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} engines: {node: '>=10'} dev: false + /@emotion/babel-plugin@11.11.0: + resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} + dependencies: + '@babel/helper-module-imports': 7.22.15 + '@babel/runtime': 7.23.2 + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/serialize': 1.1.2 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + dev: false + + /@emotion/cache@11.11.0: + resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==} + dependencies: + '@emotion/memoize': 0.8.1 + '@emotion/sheet': 1.2.2 + '@emotion/utils': 1.2.1 + '@emotion/weak-memoize': 0.3.1 + stylis: 4.2.0 + dev: false + /@emotion/hash@0.8.0: resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} dev: false + /@emotion/hash@0.9.1: + resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} + dev: false + + /@emotion/memoize@0.8.1: + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} + dev: false + + /@emotion/react@11.11.1(@types/react@18.2.28)(react@18.2.0): + resolution: {integrity: sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@emotion/babel-plugin': 11.11.0 + '@emotion/cache': 11.11.0 + '@emotion/serialize': 1.1.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) + '@emotion/utils': 1.2.1 + '@emotion/weak-memoize': 0.3.1 + '@types/react': 18.2.28 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + dev: false + + /@emotion/serialize@1.1.2: + resolution: {integrity: sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==} + dependencies: + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/unitless': 0.8.1 + '@emotion/utils': 1.2.1 + csstype: 3.1.2 + dev: false + + /@emotion/sheet@1.2.2: + resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} + dev: false + /@emotion/unitless@0.7.5: resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} dev: false + /@emotion/unitless@0.8.1: + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + dev: false + + /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0): + resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + + /@emotion/utils@1.2.1: + resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==} + dev: false + + /@emotion/weak-memoize@0.3.1: + resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} + dev: false + /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -925,6 +1011,37 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@recogito/recogito-client-core@1.7.7(@types/react@18.2.28)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-YDvtpLTPPkMCPKJDpeyhJBqit2wYDIYtYGRzmmTDOZtzmx+ERE0pZYLHyQsBNtz6DEf4X+TjqaHxdEJu9Vny+Q==} + dependencies: + core-js: 3.34.0 + fast-deep-equal: 3.1.3 + node-polyglot: 2.5.0 + react-autosize-textarea: 7.1.0(react-dom@18.2.0)(react@18.2.0) + react-draggable: 4.4.6(react-dom@18.2.0)(react@18.2.0) + react-select: 4.3.1(@types/react@18.2.28)(react-dom@18.2.0)(react@18.2.0) + react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + regenerator-runtime: 0.13.11 + timeago-react: 3.0.6(react@18.2.0) + uuid: 8.3.2 + transitivePeerDependencies: + - '@types/react' + - react + - react-dom + dev: false + + /@recogito/recogito-js@1.8.2(@types/react@18.2.28)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-uzc8ebQqcpwd6vVAMFNC7D5vZesj/Qn9tJAaALeQ+/MisVT5Sqk6QuVXWnc7RbF3d8oqq818iK7HfDvKBnVoow==} + dependencies: + '@recogito/recogito-client-core': 1.7.7(@types/react@18.2.28)(react-dom@18.2.0)(react@18.2.0) + preact: 10.19.3 + tiny-emitter: 2.1.0 + transitivePeerDependencies: + - '@types/react' + - react + - react-dom + dev: false + /@remix-run/router@1.10.0: resolution: {integrity: sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==} engines: {node: '>=14.0.0'} @@ -983,9 +1100,12 @@ packages: resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} dev: false + /@types/parse-json@4.0.2: + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + dev: false + /@types/prop-types@15.7.8: resolution: {integrity: sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==} - dev: true /@types/react-color@3.0.10: resolution: {integrity: sha512-6K5BAn3zyd8lW8UbckIAVeXGxR82Za9jyGD2DBEynsa7fKaguLDVtjfypzs7fgEV7bULgs7uhds8A8v1wABTvQ==} @@ -1018,7 +1138,6 @@ packages: '@types/prop-types': 15.7.8 '@types/scheduler': 0.16.4 csstype: 3.1.2 - dev: true /@types/reactcss@1.2.9: resolution: {integrity: sha512-dN2TtynLIZaZZ4gQNK6WM0Nff8GWYCXKl1Kvsp59WgROtx03ixCwuC1UWdesgt2O1P5Qk+0+SIfsy3eiwblMEA==} @@ -1028,7 +1147,6 @@ packages: /@types/scheduler@0.16.4: resolution: {integrity: sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==} - dev: true /@types/semver@7.5.3: resolution: {integrity: sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==} @@ -1214,7 +1332,6 @@ packages: engines: {node: '>=4'} dependencies: color-convert: 1.9.3 - dev: true /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} @@ -1308,6 +1425,13 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.5 + is-array-buffer: 3.0.2 + dev: false + /array-tree-filter@2.1.0: resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==} dev: false @@ -1317,6 +1441,31 @@ packages: engines: {node: '>=8'} dev: true + /array.prototype.foreach@1.0.5: + resolution: {integrity: sha512-FSk2BdZDQVdxGeh63usPldJo5xtkdBp3iYBqEGlGnId5TV0xtrKOnz9kXzfFL5L/81EIuVkxtiYtJSE2IjKoPA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + es-array-method-boxes-properly: 1.0.0 + get-intrinsic: 1.2.2 + is-string: 1.0.7 + dev: false + + /arraybuffer.prototype.slice@1.0.2: + resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + get-intrinsic: 1.2.2 + is-array-buffer: 3.0.2 + is-shared-array-buffer: 1.0.2 + dev: false + /async-validator@4.2.5: resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} dev: false @@ -1325,6 +1474,15 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: false + /autosize@4.0.4: + resolution: {integrity: sha512-5yxLQ22O0fCRGoxGfeLSNt3J8LB1v+umtpMnPW6XjkTWXKoN0AmXAIhelJcDtFT/Y/wYWmfE+oqU10Q0b8FhaQ==} + dev: false + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: false + /axios@1.6.0: resolution: {integrity: sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==} dependencies: @@ -1335,6 +1493,15 @@ packages: - debug dev: false + /babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + dependencies: + '@babel/runtime': 7.23.2 + cosmiconfig: 7.1.0 + resolve: 1.22.8 + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1414,7 +1581,6 @@ packages: /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - dev: true /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} @@ -1432,7 +1598,6 @@ packages: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - dev: true /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1474,7 +1639,6 @@ packages: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 - dev: true /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -1485,7 +1649,6 @@ packages: /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -1507,9 +1670,17 @@ packages: resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==} dev: false + /computed-style@0.1.4: + resolution: {integrity: sha512-WpAmaKbMNmS3OProfHIdJiNleNJdgUrJfbKArXua28QF7+0CoZjlLn0lp6vlc+dl5r2/X9GQiQRQQU4BzSa69w==} + dev: false + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: false + /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true @@ -1520,6 +1691,22 @@ packages: toggle-selection: 1.0.6 dev: false + /core-js@3.34.0: + resolution: {integrity: sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==} + requiresBuild: true + dev: false + + /cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -1573,6 +1760,15 @@ packages: has-property-descriptors: 1.0.1 dev: false + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + has-property-descriptors: 1.0.1 + object-keys: 1.1.1 + dev: false + /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1604,6 +1800,13 @@ packages: esutils: 2.0.3 dev: true + /dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dependencies: + '@babel/runtime': 7.23.2 + csstype: 3.1.2 + dev: false + /dom-walk@0.1.2: resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} dev: false @@ -1612,6 +1815,79 @@ packages: resolution: {integrity: sha512-Q0umzPJjfBrrj8unkONTgbKQXzXRrH7sVV7D9ea2yBV3Oaogz991yhbpfvo2LMNkJItmruXTEzVpP9cp7vaIiQ==} dev: true + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: false + + /es-abstract@1.22.3: + resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + arraybuffer.prototype.slice: 1.0.2 + available-typed-arrays: 1.0.5 + call-bind: 1.0.5 + es-set-tostringtag: 2.0.2 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.2 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.0 + internal-slot: 1.0.6 + is-array-buffer: 3.0.2 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.12 + is-weakref: 1.0.2 + object-inspect: 1.13.1 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.1 + safe-array-concat: 1.0.1 + safe-regex-test: 1.0.0 + string.prototype.trim: 1.2.8 + string.prototype.trimend: 1.0.7 + string.prototype.trimstart: 1.0.7 + typed-array-buffer: 1.0.0 + typed-array-byte-length: 1.0.0 + typed-array-byte-offset: 1.0.0 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.13 + dev: false + + /es-array-method-boxes-properly@1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + dev: false + + /es-set-tostringtag@2.0.2: + resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + has-tostringtag: 1.0.0 + hasown: 2.0.0 + dev: false + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: false + /esbuild@0.18.20: resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} engines: {node: '>=12'} @@ -1654,7 +1930,6 @@ packages: /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - dev: true /eslint-plugin-react-hooks@4.6.0(eslint@8.51.0): resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} @@ -1775,7 +2050,6 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} @@ -1822,6 +2096,10 @@ packages: dependencies: to-regex-range: 5.0.1 + /find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + dev: false + /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1853,6 +2131,12 @@ packages: optional: true dev: false + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: false + /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -1876,6 +2160,20 @@ packages: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} dev: false + /function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + functions-have-names: 1.2.3 + dev: false + + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: false + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1890,6 +2188,14 @@ packages: hasown: 2.0.0 dev: false + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + dev: false + /gifwrap@0.9.4: resolution: {integrity: sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==} dependencies: @@ -1949,6 +2255,13 @@ packages: type-fest: 0.20.2 dev: true + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.1 + dev: false + /globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -1971,10 +2284,13 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: false + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -1997,6 +2313,18 @@ packages: engines: {node: '>= 0.4'} dev: false + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: false + + /has@1.0.4: + resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} + engines: {node: '>= 0.4.0'} + dev: false + /hasown@2.0.0: resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} engines: {node: '>= 0.4'} @@ -2004,6 +2332,12 @@ packages: function-bind: 1.1.2 dev: false + /hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + dependencies: + react-is: 16.13.1 + dev: false + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false @@ -2028,7 +2362,6 @@ packages: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - dev: true /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -2044,18 +2377,65 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + /internal-slot@1.0.6: + resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + hasown: 2.0.0 + side-channel: 1.0.4 + dev: false + + /is-array-buffer@3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + is-typed-array: 1.1.12 + dev: false + + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: false + + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: false + /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: false + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: false + /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: hasown: 2.0.0 dev: false + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: false + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2070,6 +2450,18 @@ packages: dependencies: is-extglob: 2.1.1 + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: false + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: false + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -2079,6 +2471,51 @@ packages: engines: {node: '>=8'} dev: true + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: false + + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.5 + dev: false + + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: false + + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: false + + /is-typed-array@1.1.12: + resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} + engines: {node: '>= 0.4'} + dependencies: + which-typed-array: 1.1.13 + dev: false + + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.5 + dev: false + + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: false + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -2121,6 +2558,10 @@ packages: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} dev: true + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: false + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true @@ -2160,6 +2601,13 @@ packages: engines: {node: '>=10'} dev: false + /line-height@0.3.1: + resolution: {integrity: sha512-YExecgqPwnp5gplD2+Y8e8A5+jKpr25+DzMbFdI1/1UAr0FJrTFv4VkHLf8/6B590i1wUPJWMKKldkd/bdQ//w==} + engines: {node: '>= 4.0.0'} + dependencies: + computed-style: 0.1.4 + dev: false + /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: false @@ -2226,6 +2674,10 @@ packages: resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==} dev: false + /memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + dev: false + /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2313,6 +2765,16 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /node-polyglot@2.5.0: + resolution: {integrity: sha512-zXVwHNhFsG3mls+LKHxoHF70GQOL3FTDT3jH7ldkb95kG76RdU7F/NbvxV7D2hNIL9VpWXW6y78Fz+3KZkatRg==} + dependencies: + array.prototype.foreach: 1.0.5 + has: 1.0.4 + object.entries: 1.1.7 + string.prototype.trim: 1.2.8 + warning: 4.0.3 + dev: false + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: true @@ -2347,6 +2809,30 @@ packages: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} dev: false + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: false + + /object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: false + + /object.entries@1.1.7: + resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + dev: false + /oblivious-set@1.0.0: resolution: {integrity: sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==} dev: false @@ -2395,7 +2881,6 @@ packages: engines: {node: '>=6'} dependencies: callsites: 3.1.0 - dev: true /parse-bmfont-ascii@1.0.6: resolution: {integrity: sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==} @@ -2416,6 +2901,16 @@ packages: resolution: {integrity: sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==} dev: false + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.22.13 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: false + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2437,7 +2932,6 @@ packages: /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - dev: true /peek-readable@4.1.0: resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} @@ -2557,6 +3051,10 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /preact@10.19.3: + resolution: {integrity: sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==} + dev: false + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -3116,6 +3614,19 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-autosize-textarea@7.1.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-BHpjCDkuOlllZn3nLazY2F8oYO1tS2jHnWhcjTWQdcKiiMU6gHLNt/fzmqMSyerR0eTdKtfSIqtSeTtghNwS+g==} + peerDependencies: + react: ^0.14.0 || ^15.0.0 || ^16.0.0 + react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 + dependencies: + autosize: 4.0.4 + line-height: 0.3.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-color@2.19.3(react@18.2.0): resolution: {integrity: sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==} peerDependencies: @@ -3156,9 +3667,30 @@ packages: react: 18.2.0 scheduler: 0.23.0 + /react-draggable@4.4.6(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==} + peerDependencies: + react: '>= 16.3.0' + react-dom: '>= 16.3.0' + dependencies: + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + /react-input-autosize@3.0.0(react@18.2.0): + resolution: {integrity: sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==} + peerDependencies: + react: ^16.3.0 || ^17.0.0 + dependencies: + prop-types: 15.8.1 + react: 18.2.0 + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: false @@ -3237,6 +3769,25 @@ packages: react: 18.2.0 dev: false + /react-select@4.3.1(@types/react@18.2.28)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 + react-dom: ^16.8.0 || ^17.0.0 + dependencies: + '@babel/runtime': 7.23.2 + '@emotion/cache': 11.11.0 + '@emotion/react': 11.11.1(@types/react@18.2.28)(react@18.2.0) + memoize-one: 5.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-input-autosize: 3.0.0(react@18.2.0) + react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + dev: false + /react-spinners@0.13.8(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==} peerDependencies: @@ -3258,6 +3809,20 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.23.2 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -3308,6 +3873,15 @@ packages: /regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + /regexp.prototype.flags@1.5.1: + resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + set-function-name: 2.0.1 + dev: false + /remove-accents@0.4.2: resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} dev: false @@ -3319,7 +3893,6 @@ packages: /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - dev: true /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} @@ -3353,10 +3926,28 @@ packages: dependencies: queue-microtask: 1.2.3 + /safe-array-concat@1.0.1: + resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} + engines: {node: '>=0.4'} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + has-symbols: 1.0.3 + isarray: 2.0.5 + dev: false + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: false + /safe-regex-test@1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + is-regex: 1.1.4 + dev: false + /sass@1.69.4: resolution: {integrity: sha512-+qEreVhqAy8o++aQfCJwp0sklr2xyEzkm9Pp/Igu9wNPoe7EZEQ8X/MBvvXggI2ql607cxKg/RKOwDj6pp2XDA==} engines: {node: '>=14.0.0'} @@ -3404,6 +3995,15 @@ packages: has-property-descriptors: 1.0.1 dev: false + /set-function-name@2.0.1: + resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.1 + dev: false + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3433,10 +4033,40 @@ packages: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + /source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + dev: false + /string-convert@0.2.1: resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} dev: false + /string.prototype.trim@1.2.8: + resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + dev: false + + /string.prototype.trimend@1.0.7: + resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + dev: false + + /string.prototype.trimstart@1.0.7: + resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + dev: false + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: @@ -3463,6 +4093,10 @@ packages: peek-readable: 4.1.0 dev: false + /stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + dev: false + /stylis@4.3.0: resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==} dev: false @@ -3486,7 +4120,6 @@ packages: engines: {node: '>=4'} dependencies: has-flag: 3.0.0 - dev: true /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -3554,10 +4187,27 @@ packages: engines: {node: '>=12.22'} dev: false + /timeago-react@3.0.6(react@18.2.0): + resolution: {integrity: sha512-4ywnCX3iFjdp84WPK7gt8s4n0FxXbYM+xv8hYL73p83dpcMxzmO+0W4xJuxflnkWNvum5aEaqTe6LZ3lUIudjQ==} + peerDependencies: + react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + timeago.js: 4.0.2 + dev: false + + /timeago.js@4.0.2: + resolution: {integrity: sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==} + dev: false + /timm@1.7.1: resolution: {integrity: sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==} dev: false + /tiny-emitter@2.1.0: + resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} + dev: false + /tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} dev: false @@ -3565,7 +4215,6 @@ packages: /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - dev: true /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -3621,12 +4270,59 @@ packages: engines: {node: '>=10'} dev: true + /typed-array-buffer@1.0.0: + resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + is-typed-array: 1.1.12 + dev: false + + /typed-array-byte-length@1.0.0: + resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.12 + dev: false + + /typed-array-byte-offset@1.0.0: + resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.5 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.12 + dev: false + + /typed-array-length@1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.5 + for-each: 0.3.3 + is-typed-array: 1.1.12 + dev: false + /typescript@5.2.2: resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} hasBin: true dev: true + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.5 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: false + /unload@2.2.0: resolution: {integrity: sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==} dependencies: @@ -3679,6 +4375,11 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: false + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + /vite@4.4.11(sass@1.69.4): resolution: {integrity: sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==} engines: {node: ^14.18.0 || >=16.0.0} @@ -3720,6 +4421,27 @@ packages: dependencies: loose-envify: 1.4.0 + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: false + + /which-typed-array@1.1.13: + resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.5 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: false + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3770,6 +4492,11 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: false + /yaml@2.3.4: resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} engines: {node: '>= 14'} From 5f4efa91cd5d96a73aa5a30256f55f1fdbb57c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Kabaday=C4=B1?= Date: Tue, 19 Dec 2023 00:35:37 +0300 Subject: [PATCH 02/43] add annotation service --- app/frontend/src/Services/annotation.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 app/frontend/src/Services/annotation.ts diff --git a/app/frontend/src/Services/annotation.ts b/app/frontend/src/Services/annotation.ts new file mode 100644 index 00000000..fbb5588f --- /dev/null +++ b/app/frontend/src/Services/annotation.ts @@ -0,0 +1,25 @@ +import axios from "axios"; + +export const createAnnotation = async (annotation: any) => { + const response = await axios.post( + import.meta.env.VITE_APP_ANNOTATION_API_URL + "/annotation/create", + annotation + ); + return response.data; +}; + +export const updateAnnotation = async (annotation: any) => { + const response = await axios.put( + import.meta.env.VITE_APP_ANNOTATION_API_URL + "/annotation/update", + annotation + ); + return response.data; +}; + +export const deleteAnnotation = async (id: string) => { + id = id.replace("#", "%23"); + const response = await axios.delete( + `${import.meta.env.VITE_APP_ANNOTATION_API_URL}/annotation/delete?id=${id}` + ); + return response.data; +}; From 98d8f9703c1bdd55fb8b2d546ca6b1e1609061df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Kabaday=C4=B1?= Date: Tue, 19 Dec 2023 00:36:15 +0300 Subject: [PATCH 03/43] add annotation functionality to forum posts --- .../src/Pages/ForumPost/ForumPost.tsx | 77 +++++++++++++++++-- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/app/frontend/src/Pages/ForumPost/ForumPost.tsx b/app/frontend/src/Pages/ForumPost/ForumPost.tsx index 812242d3..1258e6f9 100644 --- a/app/frontend/src/Pages/ForumPost/ForumPost.tsx +++ b/app/frontend/src/Pages/ForumPost/ForumPost.tsx @@ -10,7 +10,6 @@ import { getPost } from "../../Services/forum"; import { getCommentList } from "../../Services/comment"; import CommentForm from "../../Components/Comment/CommentForm/CommentForm.tsx"; import Comment from "../../Components/Comment/Comment/Comment.tsx"; - import { useVote } from "../../Components/Hooks/useVote.tsx"; import { useAuth } from "../../Components/Hooks/useAuth.tsx"; import { @@ -19,7 +18,6 @@ import { UpOutlined, CommentOutlined, WarningOutlined, - BackwardOutlined, ArrowLeftOutlined, } from "@ant-design/icons"; import clsx from "clsx"; @@ -30,17 +28,32 @@ import { twj } from "tw-to-css"; import Achievement from "../../Components/Achievement/Achievement/Achievement.tsx"; import { grantAchievement } from "../../Services/achievement.ts"; import { formatDate } from "../../Library/utils/formatDate.ts"; -import { handleError } from "../../Library/utils/handleError.ts"; +import { + handleAxiosError, + handleError, +} from "../../Library/utils/handleError.ts"; +import { Recogito } from "@recogito/recogito-js"; + +import "@recogito/recogito-js/dist/recogito.min.css"; +import { + createAnnotation, + deleteAnnotation, + updateAnnotation, +} from "../../Services/annotation.ts"; +import { NotificationUtil } from "../../Library/utils/notification.ts"; function ForumPost() { const { isLoggedIn, user } = useAuth(); const navigate = useNavigate(); const location = useLocation(); const [searchParams] = useSearchParams(); + const [isAnnotationsApplied, setIsAnnotationsApplied] = useState(false); const { postId, forumId } = useParams(); const { data: post, isLoading } = useQuery(["post", postId], () => getPost(postId!) ); + + const isAdmin = user?.role === "ADMIN"; const { upvote, downvote } = useVote({ voteType: "POST", typeId: postId!, @@ -71,8 +84,60 @@ function ForumPost() { const toggleCommenting = () => { setCommenting(!isCommenting); - console.log(isCommenting); - console.log(post); + }; + + const linkAnnotation = (elem: any) => { + if (elem && isAnnotationsApplied === false) { + const r = new Recogito({ + content: elem, + readOnly: !(user.id === post.poster.id || isAdmin), + }); + setIsAnnotationsApplied(true); + + r.loadAnnotations( + `${ + import.meta.env.VITE_APP_ANNOTATION_API_URL + }/annotation/get-source-annotations?source=${postId}` + ) + .then(function (annotations) {}) + .catch((error) => { + if (error instanceof SyntaxError) { + return; + } + NotificationUtil.error("Error occurred while retrieving annotations"); + }); + + r.on("createAnnotation", async (annotation: any, _overrideId) => { + try { + annotation.target = { ...annotation.target, source: postId }; + + await createAnnotation(annotation); + NotificationUtil.success("You successfully create the annotation"); + } catch (error) { + handleAxiosError(error); + } + }); + + r.on("deleteAnnotation", async function (annotation: any) { + try { + const id = annotation.id; + await deleteAnnotation(id); + NotificationUtil.success("You successfully delete the annotation"); + } catch (error) { + handleAxiosError(error); + } + }); + + r.on("updateAnnotation", async function (annotation, _previous) { + try { + annotation.target = { ...annotation.target, source: postId }; + await updateAnnotation(annotation); + NotificationUtil.success("You successfully update the annotation"); + } catch (error) { + handleAxiosError(error); + } + }); + } }; return ( @@ -143,7 +208,7 @@ function ForumPost() { )} - {post.postContent} + linkAnnotation(elem)}>{post.postContent} Poster: {post.poster?.username} Last edit: {formatDate(post.lastEditedAt)} From f9eba9172dd4b8ba677fdbd24e29c2096a636cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Kabaday=C4=B1?= Date: Tue, 19 Dec 2023 00:36:22 +0300 Subject: [PATCH 04/43] remove unused import --- app/frontend/src/Pages/GameDetails/GameDetails.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/frontend/src/Pages/GameDetails/GameDetails.tsx b/app/frontend/src/Pages/GameDetails/GameDetails.tsx index 82097667..323de3c8 100644 --- a/app/frontend/src/Pages/GameDetails/GameDetails.tsx +++ b/app/frontend/src/Pages/GameDetails/GameDetails.tsx @@ -1,4 +1,3 @@ -import { StarFilled, StarOutlined } from "@ant-design/icons"; import styles from "./GameDetails.module.scss"; import { useState } from "react"; import Summary from "../../Components/GameDetails/Summary/Summary"; From dbfa1fa99dc79d534f55cc5d9ffc1a32d47b4c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Kabaday=C4=B1?= Date: Tue, 19 Dec 2023 00:37:27 +0300 Subject: [PATCH 05/43] add annotation functionality to game detail --- .../GameDetails/Summary/Summary.tsx | 117 ++++++++++++++---- 1 file changed, 94 insertions(+), 23 deletions(-) diff --git a/app/frontend/src/Components/GameDetails/Summary/Summary.tsx b/app/frontend/src/Components/GameDetails/Summary/Summary.tsx index 17f8f3e8..63cca3b1 100644 --- a/app/frontend/src/Components/GameDetails/Summary/Summary.tsx +++ b/app/frontend/src/Components/GameDetails/Summary/Summary.tsx @@ -4,16 +4,90 @@ import styles from "./Summary.module.scss"; import { getAchievementByGame } from "../../../Services/achievement"; import Achievement from "../../Achievement/Achievement/Achievement"; import { useQuery } from "react-query"; +import { + handleAxiosError, + handleError, +} from "../../../Library/utils/handleError.ts"; +import { Recogito } from "@recogito/recogito-js"; - - +import "@recogito/recogito-js/dist/recogito.min.css"; +import { + createAnnotation, + deleteAnnotation, + updateAnnotation, +} from "../../../Services/annotation.ts"; +import { NotificationUtil } from "../../../Library/utils/notification.ts"; +import { useState } from "react"; +import { useAuth } from "../../Hooks/useAuth.tsx"; function Summary({ game }: { game: any }) { + const { user } = useAuth(); + const [isAnnotationsApplied, setIsAnnotationsApplied] = useState(false); const { data: achievements, isLoading: isLoadingAchievements } = useQuery( ["achievements", game.id], () => getAchievementByGame({ gameId: game.id! }) ); - + + const isAdmin = user?.role === "ADMIN"; + + const linkAnnotation = (elem: any) => { + if (elem && isAnnotationsApplied === false) { + const r = new Recogito({ + content: elem, + readOnly: !isAdmin, + }); + setIsAnnotationsApplied(true); + + r.loadAnnotations( + `${ + import.meta.env.VITE_APP_ANNOTATION_API_URL + }/annotation/get-source-annotations?source=${game.id}` + ) + .then(function (annotations) { + console.log(annotations); + }) + .catch((error) => { + if (error instanceof SyntaxError) { + return; + } + NotificationUtil.error("Error occurred while retrieving annotations"); + }); + + console.log(game); + + r.on("createAnnotation", async (annotation: any, _overrideId) => { + try { + annotation.target = { ...annotation.target, source: game.id }; + + await createAnnotation(annotation); + NotificationUtil.success("You successfully create the annotation"); + } catch (error) { + handleAxiosError(error); + } + }); + + r.on("deleteAnnotation", async function (annotation: any) { + try { + const id = annotation.id; + await deleteAnnotation(id); + NotificationUtil.success("You successfully delete the annotation"); + } catch (error) { + handleAxiosError(error); + } + }); + + r.on("updateAnnotation", async function (annotation, _previous) { + try { + annotation.target = { ...annotation.target, source: game.id }; + await updateAnnotation(annotation); + NotificationUtil.success("You successfully update the annotation"); + } catch (error) { + handleAxiosError(error); + } + }); + } + }; + return (
@@ -67,7 +141,9 @@ function Summary({ game }: { game: any }) { )}
- {game?.gameDescription} + linkAnnotation(elem)}> + {game?.gameDescription} +
{game.minSystemReq && (
@@ -76,26 +152,21 @@ function Summary({ game }: { game: any }) {
)} - - {!isLoadingAchievements && achievements.length > 0 && -
-
Achievements
-
- { - achievements.map( - (achievement: any) => - !achievement.isDeleted && ( -
- -
- ) - )} + {!isLoadingAchievements && achievements.length > 0 && ( +
+
Achievements
+
+ {achievements.map( + (achievement: any) => + !achievement.isDeleted && ( +
+ +
+ ) + )} +
-
- } - - - + )}
); } From af10bf837ebef0880f8ea335d431ea8966748aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Kabaday=C4=B1?= Date: Tue, 19 Dec 2023 00:37:42 +0300 Subject: [PATCH 06/43] fix a bug in handling error --- app/frontend/src/Library/utils/handleError.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/frontend/src/Library/utils/handleError.ts b/app/frontend/src/Library/utils/handleError.ts index 19478509..c2a72996 100644 --- a/app/frontend/src/Library/utils/handleError.ts +++ b/app/frontend/src/Library/utils/handleError.ts @@ -17,7 +17,7 @@ export function handleError(error: any) { export function handleAxiosError(error: any) { { - const errorMessage = error.response.data || `An error occurred`; + const errorMessage = error?.response?.data || `An error occurred`; NotificationUtil.error(errorMessage); } } From 0bd7648ec64fa36bccb370e40bd047fc1523b169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Kabaday=C4=B1?= Date: Thu, 21 Dec 2023 23:44:50 +0300 Subject: [PATCH 07/43] replace messages with notification util --- .../Components/Application/Application.tsx | 30 +++++++------------ .../src/Components/Groups/PublicGroup.tsx | 7 +++-- .../src/Components/Profile/EditProfile.tsx | 6 ++-- .../Pages/ChangePassword/ChangePassword.tsx | 12 ++++---- .../src/Pages/ForumPost/ForumPost.tsx | 6 ++-- 5 files changed, 28 insertions(+), 33 deletions(-) diff --git a/app/frontend/src/Components/Application/Application.tsx b/app/frontend/src/Components/Application/Application.tsx index f8ce1eac..2e5991af 100644 --- a/app/frontend/src/Components/Application/Application.tsx +++ b/app/frontend/src/Components/Application/Application.tsx @@ -1,21 +1,15 @@ -import { TeamOutlined, UserOutlined } from "@ant-design/icons"; +import { TeamOutlined } from "@ant-design/icons"; import styles from "./Application.module.scss"; -import { Button, message } from "antd"; -import TagRenderer from "../TagRenderer/TagRenderer"; -import { formatDate } from "../../Library/utils/formatDate"; -import { useNavigate } from "react-router-dom"; +import { Button } from "antd"; import { useMutation, useQueryClient } from "react-query"; -import { joinGroup, leaveGroup } from "../../Services/group"; import { reviewApplication } from "../../Services/applications"; import { NotificationUtil } from "../../Library/utils/notification"; +import { handleAxiosError } from "../../Library/utils/handleError"; function Application({ application }: { application: any }) { - const navigate = useNavigate(); const queryClient = useQueryClient(); - - - const { mutate: approve } = useMutation( + const { mutate: approve } = useMutation( (applicationId: string) => reviewApplication(applicationId, "APPROVE"), { onSuccess() { @@ -23,12 +17,12 @@ function Application({ application }: { application: any }) { NotificationUtil.success("You successfully approved the application"); }, onError(error: any) { - message.error(error.response.data); + handleAxiosError(error); }, } ); - const { mutate: reject } = useMutation( + const { mutate: reject } = useMutation( (applicationId: string) => reviewApplication(applicationId, "REJECT"), { onSuccess() { @@ -36,7 +30,7 @@ function Application({ application }: { application: any }) { NotificationUtil.success("You successfully rejected the application"); }, onError(error: any) { - message.error(error.response.data); + handleAxiosError(error); }, } ); @@ -58,18 +52,16 @@ function Application({ application }: { application: any }) {
-
{application?.message}
-
- -
+
- - + diff --git a/app/frontend/src/Components/Groups/PublicGroup.tsx b/app/frontend/src/Components/Groups/PublicGroup.tsx index 59229a07..5872f85b 100644 --- a/app/frontend/src/Components/Groups/PublicGroup.tsx +++ b/app/frontend/src/Components/Groups/PublicGroup.tsx @@ -1,11 +1,12 @@ import { TeamOutlined, UserOutlined } from "@ant-design/icons"; import styles from "./PublicGroup.module.scss"; -import { Button, message } from "antd"; +import { Button } from "antd"; import TagRenderer from "../TagRenderer/TagRenderer"; import { formatDate } from "../../Library/utils/formatDate"; import { useNavigate } from "react-router-dom"; import { useMutation, useQueryClient } from "react-query"; import { joinGroup, leaveGroup } from "../../Services/group"; +import { handleAxiosError } from "../../Library/utils/handleError"; function PublicGroup({ group }: { group: any }) { const navigate = useNavigate(); @@ -18,7 +19,7 @@ function PublicGroup({ group }: { group: any }) { queryClient.invalidateQueries(["groups"]); }, onError(error: any) { - message.error(error.response.data); + handleAxiosError(error); }, } ); @@ -30,7 +31,7 @@ function PublicGroup({ group }: { group: any }) { queryClient.invalidateQueries(["groups"]); }, onError(error: any) { - message.error(error.response.data); + handleAxiosError(error); }, } ); diff --git a/app/frontend/src/Components/Profile/EditProfile.tsx b/app/frontend/src/Components/Profile/EditProfile.tsx index 0bb6ac98..f16bfbea 100644 --- a/app/frontend/src/Components/Profile/EditProfile.tsx +++ b/app/frontend/src/Components/Profile/EditProfile.tsx @@ -5,7 +5,7 @@ import { editProfile } from "../../Services/profile"; import UploadArea from "../UploadArea/UploadArea"; import { useForm } from "antd/es/form/Form"; import { useMutation, useQueryClient } from "react-query"; -import { message } from "antd"; +import { handleAxiosError } from "../../Library/utils/handleError"; function EditProfile({ profile }: { profile: any }) { const profileId = profile.id; @@ -19,7 +19,7 @@ function EditProfile({ profile }: { profile: any }) { form.setFieldsValue(profile); form.setFieldValue("username", profile?.user?.username); setImageUrl(profile?.profilePhoto); - }, [profile]); + }, [profile, form]); const showModal = () => { setOpen(true); @@ -37,7 +37,7 @@ function EditProfile({ profile }: { profile: any }) { setOpen(false); }, onError(error: any) { - message.error(error.message); + handleAxiosError(error); setConfirmLoading(false); }, } diff --git a/app/frontend/src/Pages/ChangePassword/ChangePassword.tsx b/app/frontend/src/Pages/ChangePassword/ChangePassword.tsx index 6e5e6cc3..9f9d11c2 100644 --- a/app/frontend/src/Pages/ChangePassword/ChangePassword.tsx +++ b/app/frontend/src/Pages/ChangePassword/ChangePassword.tsx @@ -1,7 +1,9 @@ -import { Button, Form, Input, Card, message } from "antd"; +import { Button, Form, Input, Card } from "antd"; import styles from "./ChangePassword.module.scss"; import { changePassword } from "../../Services/changepassword"; import { useNavigate } from "react-router-dom"; +import { NotificationUtil } from "../../Library/utils/notification"; +import { handleAxiosError } from "../../Library/utils/handleError"; const ChangePassword = () => { const [form] = Form.useForm(); @@ -12,19 +14,19 @@ const ChangePassword = () => { try { const response = await changePassword(currentPassword, newPassword); if (response?.status == 200) { - message.success("Password is changed successfully!"); + NotificationUtil.success("Password is changed successfully!"); navigate("/profile"); } } catch (error: any) { - message.error(error.message); + handleAxiosError(error); return; } - message.success("Password is changed successfully!"); + NotificationUtil.success("Password is changed successfully!"); }; const onFinishFailed = () => { - message.error("Couldn't change the password"); + NotificationUtil.error("Couldn't change the password"); }; return ( diff --git a/app/frontend/src/Pages/ForumPost/ForumPost.tsx b/app/frontend/src/Pages/ForumPost/ForumPost.tsx index 1d62338e..5ebd24c0 100644 --- a/app/frontend/src/Pages/ForumPost/ForumPost.tsx +++ b/app/frontend/src/Pages/ForumPost/ForumPost.tsx @@ -19,11 +19,10 @@ import { UpOutlined, CommentOutlined, WarningOutlined, - BackwardOutlined, ArrowLeftOutlined, } from "@ant-design/icons"; import clsx from "clsx"; -import { Button, message } from "antd"; +import { Button } from "antd"; import { useState } from "react"; import TagRenderer from "../../Components/TagRenderer/TagRenderer.tsx"; import { twj } from "tw-to-css"; @@ -32,6 +31,7 @@ import { grantAchievement } from "../../Services/achievement.ts"; import { formatDate } from "../../Library/utils/formatDate.ts"; import { handleError } from "../../Library/utils/handleError.ts"; import CharacterDetails from "../../Components/Character/CharacterDetails.tsx"; +import { NotificationUtil } from "../../Library/utils/notification.ts"; function ForumPost() { const { isLoggedIn, user } = useAuth(); @@ -61,7 +61,7 @@ function ForumPost() { () => grantAchievement(post.poster.id, post.achievement.id), { onSuccess() { - message.success(`Achievement Granted`); + NotificationUtil.success(`Achievement Granted`); }, onError(err: any) { From 2ab30d855113e066f819a2fc50c44c4f9ca4639c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Kabaday=C4=B1?= Date: Thu, 21 Dec 2023 23:45:05 +0300 Subject: [PATCH 08/43] add pacman animation to home page --- .../src/Pages/HomePage/HomePage.module.scss | 6 ++ app/frontend/src/Pages/HomePage/HomePage.tsx | 69 +++++++++++-------- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/app/frontend/src/Pages/HomePage/HomePage.module.scss b/app/frontend/src/Pages/HomePage/HomePage.module.scss index 79235b5f..8b90d909 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.module.scss +++ b/app/frontend/src/Pages/HomePage/HomePage.module.scss @@ -13,3 +13,9 @@ } } } +.spinnerContainer { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} diff --git a/app/frontend/src/Pages/HomePage/HomePage.tsx b/app/frontend/src/Pages/HomePage/HomePage.tsx index 68d8a8e2..7681c3ad 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.tsx +++ b/app/frontend/src/Pages/HomePage/HomePage.tsx @@ -9,6 +9,7 @@ import { SortAscendingOutlined, SortDescendingOutlined, } from "@ant-design/icons"; +import { PacmanLoader } from "react-spinners"; const sortOptions = [ { label: "Creation Date", value: "CREATION_DATE" }, { label: "Overall Vote", value: "OVERALL_VOTE" }, @@ -30,37 +31,45 @@ function HomePage() { ); return ( -
-
-
- - +
+ {data?.map((post: any) => ( + + ))} +
+
+ )} + ); } From f2ef6e36c6a02520ac86a0a98424f677f246f8e5 Mon Sep 17 00:00:00 2001 From: cisel-zumbul Date: Sat, 23 Dec 2023 10:29:55 +0300 Subject: [PATCH 09/43] Adds container to the posts --- .../src/Pages/HomePage/HomePage.module.scss | 7 ++++++ app/frontend/src/Pages/HomePage/HomePage.tsx | 22 ++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/frontend/src/Pages/HomePage/HomePage.module.scss b/app/frontend/src/Pages/HomePage/HomePage.module.scss index 79235b5f..42d6557a 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.module.scss +++ b/app/frontend/src/Pages/HomePage/HomePage.module.scss @@ -1,9 +1,13 @@ .pageContainer { padding: 1em; + display: flex; + justify-content: center; .postsContainer { display: flex; gap: 1em; flex-direction: column; + align-items: center; + width: 70%; .filterContainer { display: flex; justify-content: flex-end; @@ -11,5 +15,8 @@ gap: 0.5em; padding: 0.5em; } + .postsContainer { + width: 100%; + } } } diff --git a/app/frontend/src/Pages/HomePage/HomePage.tsx b/app/frontend/src/Pages/HomePage/HomePage.tsx index 68d8a8e2..eeb7dd1b 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.tsx +++ b/app/frontend/src/Pages/HomePage/HomePage.tsx @@ -48,16 +48,18 @@ function HomePage() { />
{data?.map((post: any) => ( - +
+ +
))}
From 214fd99d1c17095bc9043f3edc6a2cb6b3190b9e Mon Sep 17 00:00:00 2001 From: AliBasarann Date: Sat, 23 Dec 2023 12:43:57 +0300 Subject: [PATCH 10/43] styling of grant achievement button is changed --- .../src/Pages/ForumPost/ForumPost.module.scss | 30 +++++++++++++++++-- .../src/Pages/ForumPost/ForumPost.tsx | 21 ++++++++----- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/app/frontend/src/Pages/ForumPost/ForumPost.module.scss b/app/frontend/src/Pages/ForumPost/ForumPost.module.scss index 57fc1997..f921416a 100644 --- a/app/frontend/src/Pages/ForumPost/ForumPost.module.scss +++ b/app/frontend/src/Pages/ForumPost/ForumPost.module.scss @@ -54,10 +54,17 @@ font-size: 1.5em; font-weight: bold; margin: 0em 2em; - opacity: 50%; + opacity: 80%; } } + .mySpan { + font-size: 1.5em; + font-weight: bold; + margin: 0em 2em; + opacity: 80%; + background-color: $vanilla-light-50; + } .character { display: flex; align-items: center; @@ -69,7 +76,7 @@ font-size: 1.5em; font-weight: bold; margin: 0em 2em; - opacity: 50%; + opacity: 80%; } } .title { @@ -160,4 +167,23 @@ margin-left: 3.5em; margin-top: 1em; } + .grantButton { + position: absolute; + margin-top: 5px; + margin-left: 20em; + padding: 0.6em 0.9em; + font-size: 0.8em; + display: flex; + background-color: $orange-light-10 ; + align-items: center; + justify-content: center ; + border-radius: 0.3em; + color: white; + + right: 0.5em; + + &:hover { + background-color: darken($orange, 10%); + } + } } diff --git a/app/frontend/src/Pages/ForumPost/ForumPost.tsx b/app/frontend/src/Pages/ForumPost/ForumPost.tsx index 1d62338e..94b28dad 100644 --- a/app/frontend/src/Pages/ForumPost/ForumPost.tsx +++ b/app/frontend/src/Pages/ForumPost/ForumPost.tsx @@ -21,9 +21,11 @@ import { WarningOutlined, BackwardOutlined, ArrowLeftOutlined, + VerifiedOutlined, + CheckOutlined, } from "@ant-design/icons"; import clsx from "clsx"; -import { Button, message } from "antd"; +import { Button, Tooltip, message } from "antd"; import { useState } from "react"; import TagRenderer from "../../Components/TagRenderer/TagRenderer.tsx"; import { twj } from "tw-to-css"; @@ -136,12 +138,17 @@ function ForumPost() {
)} {post.achievement && ( -
- Achievement: - - {user?.role === "ADMIN" && ( - - )} +
+ +
+ Achievement: + + {user?.role === "ADMIN" && ( + + grant()} /> + + )} +
)} {post.character && ( From d7651dc9159a726278cd7d038a9e7d47da6c9caf Mon Sep 17 00:00:00 2001 From: AliBasarann Date: Sat, 23 Dec 2023 13:24:02 +0300 Subject: [PATCH 11/43] create private group bug is fixed --- app/frontend/src/Pages/CreateGroup/CreateGroup.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/frontend/src/Pages/CreateGroup/CreateGroup.tsx b/app/frontend/src/Pages/CreateGroup/CreateGroup.tsx index 3e22dca2..566043e2 100644 --- a/app/frontend/src/Pages/CreateGroup/CreateGroup.tsx +++ b/app/frontend/src/Pages/CreateGroup/CreateGroup.tsx @@ -29,6 +29,8 @@ function CreateGroup() { } ); const [imageUrl, setImageUrl] = useState(""); + const [membershipPolicy, setMembershipPolicy] = useState("PUBLIC"); + const [avatarOnly, setAvatarOnly] = useState(false); const { mutate: submit, isLoading } = useMutation( ({ @@ -67,8 +69,7 @@ function CreateGroup() { } ); - const [membershipPolicy, setMembershipPolicy] = useState("PUBLIC"); - const [avatarOnly, setAvatarOnly] = useState(false); + const gameList = useQuery(["games"], () => getGames()); @@ -83,7 +84,6 @@ function CreateGroup() { useEffect(() => { form.setFieldsValue({ membershipPolicy: membershipPolicy, - avatarOnly: avatarOnly, quota: 3, }); }, []); From 51e5e480e02fb1e513573372f448d15edcfe9731 Mon Sep 17 00:00:00 2001 From: cisel-zumbul Date: Sat, 23 Dec 2023 14:53:29 +0300 Subject: [PATCH 12/43] Post size bug is fixed --- app/frontend/src/Components/Forum/ForumPost/ForumPost.tsx | 6 ------ app/frontend/src/Pages/HomePage/HomePage.module.scss | 1 - app/frontend/src/Pages/HomePage/HomePage.tsx | 3 ++- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/frontend/src/Components/Forum/ForumPost/ForumPost.tsx b/app/frontend/src/Components/Forum/ForumPost/ForumPost.tsx index 22a60805..742526ef 100644 --- a/app/frontend/src/Components/Forum/ForumPost/ForumPost.tsx +++ b/app/frontend/src/Components/Forum/ForumPost/ForumPost.tsx @@ -6,7 +6,6 @@ import { DownOutlined, EditOutlined, UpOutlined, - WarningOutlined, } from "@ant-design/icons"; import { useMutation } from "react-query"; import { deletePost } from "../../../Services/forum"; @@ -127,11 +126,6 @@ function ForumPost({
{post.poster?.username} {post.createdAt && formatDate(post.createdAt)} -
{type !== "STANDARD" && ( diff --git a/app/frontend/src/Pages/HomePage/HomePage.module.scss b/app/frontend/src/Pages/HomePage/HomePage.module.scss index 42d6557a..19551d96 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.module.scss +++ b/app/frontend/src/Pages/HomePage/HomePage.module.scss @@ -6,7 +6,6 @@ display: flex; gap: 1em; flex-direction: column; - align-items: center; width: 70%; .filterContainer { display: flex; diff --git a/app/frontend/src/Pages/HomePage/HomePage.tsx b/app/frontend/src/Pages/HomePage/HomePage.tsx index eeb7dd1b..83beb361 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.tsx +++ b/app/frontend/src/Pages/HomePage/HomePage.tsx @@ -4,7 +4,7 @@ import { useAuth } from "../../Components/Hooks/useAuth"; import { getHomePosts } from "../../Services/home"; import ForumPost from "../../Components/Forum/ForumPost/ForumPost"; import { useState } from "react"; -import { Button, Select } from "antd"; +import { Button, Pagination, Select } from "antd"; import { SortAscendingOutlined, SortDescendingOutlined, @@ -61,6 +61,7 @@ function HomePage() { />
))} + ;
); From bbd22c1a1ea83f8e5edab46bc397796dd3fd5db8 Mon Sep 17 00:00:00 2001 From: Deniz Unal Date: Sat, 23 Dec 2023 15:21:03 +0300 Subject: [PATCH 13/43] improved backend dockerfiles and added compose.yml --- app/annotation/annotation/Dockerfile | 10 ++++---- app/backend/.dockerignore | 5 ++++ app/backend/.gitignore | 3 ++- app/backend/Dockerfile | 11 +++++---- app/backend/images/README.md | 1 + .../com/app/gamereview/config/CorsConfig.java | 2 +- app/compose.yml | 24 +++++++++++++++++++ 7 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 app/backend/.dockerignore create mode 100644 app/backend/images/README.md create mode 100644 app/compose.yml diff --git a/app/annotation/annotation/Dockerfile b/app/annotation/annotation/Dockerfile index a9bae591..9ad98765 100644 --- a/app/annotation/annotation/Dockerfile +++ b/app/annotation/annotation/Dockerfile @@ -1,6 +1,6 @@ -FROM eclipse-temurin:17-jdk-alpine -WORKDIR /app -VOLUME /app/images -COPY target/*.jar /app/bounswe.jar +FROM maven:3.9.6-eclipse-temurin-21-jammy +WORKDIR /app/annotation +COPY . /app/annotation +RUN mvn clean install EXPOSE 8080 -ENTRYPOINT ["java","-jar","/app/bounswe.jar"] \ No newline at end of file +ENTRYPOINT ["java","-jar","/app/annotation/target/annotation-1.0-SNAPSHOT.jar"] \ No newline at end of file diff --git a/app/backend/.dockerignore b/app/backend/.dockerignore new file mode 100644 index 00000000..9a19c5db --- /dev/null +++ b/app/backend/.dockerignore @@ -0,0 +1,5 @@ +target +images +README.md +format.sh +.gitignore \ No newline at end of file diff --git a/app/backend/.gitignore b/app/backend/.gitignore index ccba6195..4af7fcd1 100644 --- a/app/backend/.gitignore +++ b/app/backend/.gitignore @@ -36,4 +36,5 @@ build/ *.env ### Image Files -/images/ +images/* +!images/README.md diff --git a/app/backend/Dockerfile b/app/backend/Dockerfile index a9bae591..b8df3362 100644 --- a/app/backend/Dockerfile +++ b/app/backend/Dockerfile @@ -1,6 +1,7 @@ -FROM eclipse-temurin:17-jdk-alpine -WORKDIR /app -VOLUME /app/images -COPY target/*.jar /app/bounswe.jar +FROM maven:3.9.6-eclipse-temurin-21-jammy +WORKDIR /app/backend +VOLUME /app/backend/images +COPY . /app/backend/ +RUN mvn clean install EXPOSE 8080 -ENTRYPOINT ["java","-jar","/app/bounswe.jar"] \ No newline at end of file +ENTRYPOINT ["java","-jar","/app/backend/target/gamereview-0.0.1-SNAPSHOT.jar"] \ No newline at end of file diff --git a/app/backend/images/README.md b/app/backend/images/README.md new file mode 100644 index 00000000..56199813 --- /dev/null +++ b/app/backend/images/README.md @@ -0,0 +1 @@ +This directory is where the application stores its images, when it is run locally. \ No newline at end of file diff --git a/app/backend/src/main/java/com/app/gamereview/config/CorsConfig.java b/app/backend/src/main/java/com/app/gamereview/config/CorsConfig.java index 5c1adb26..cd6f63b0 100644 --- a/app/backend/src/main/java/com/app/gamereview/config/CorsConfig.java +++ b/app/backend/src/main/java/com/app/gamereview/config/CorsConfig.java @@ -15,7 +15,7 @@ public class CorsConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { final CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList("http://localhost:5173", "http://ec2-51-20-78-40.eu-north-1.compute.amazonaws.com/")); + configuration.setAllowedOrigins(Arrays.asList("http://localhost:5173", "http://localhost", "http://ec2-51-20-78-40.eu-north-1.compute.amazonaws.com/")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowCredentials(true); configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "Set-Cookie", "credentials")); diff --git a/app/compose.yml b/app/compose.yml new file mode 100644 index 00000000..d357cf38 --- /dev/null +++ b/app/compose.yml @@ -0,0 +1,24 @@ +version: "3.8" + +services: + frontend: + build: ./frontend + image: lastmusketeer/451_frontend:latest + ports: + - "80:5173" + + backend: + build: ./backend + image: unaldenizzz/bounswe2023group5:latest + ports: + - "3001:8080" + volumes: + - type: bind + source: ./backend/images + target: /app/backend/images + + annotation: + build: ./annotation/annotation + image: nopaew/bounswe2023group5annotation:latest + ports: + - "3002:8080" From 4c56d2d4e63e8d1789d8f956567d37c71d925b69 Mon Sep 17 00:00:00 2001 From: cisel-zumbul Date: Sat, 23 Dec 2023 15:41:21 +0300 Subject: [PATCH 14/43] Pagination templete added --- .../src/Components/Forum/ForumPost/ForumPost.tsx | 1 - .../src/Pages/HomePage/HomePage.module.scss | 4 ++++ app/frontend/src/Pages/HomePage/HomePage.tsx | 15 +++++++++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/frontend/src/Components/Forum/ForumPost/ForumPost.tsx b/app/frontend/src/Components/Forum/ForumPost/ForumPost.tsx index 742526ef..f6eb9b04 100644 --- a/app/frontend/src/Components/Forum/ForumPost/ForumPost.tsx +++ b/app/frontend/src/Components/Forum/ForumPost/ForumPost.tsx @@ -19,7 +19,6 @@ import { twj } from "tw-to-css"; import { NotificationUtil } from "../../../Library/utils/notification"; import { handleAxiosError } from "../../../Library/utils/handleError"; import SquareAchievement from "../../Achievement/SquareAchievement/SquareAchievement"; -import Character from "../../Character/Character"; import CharacterDetails from "../../Character/CharacterDetails"; function ForumPost({ diff --git a/app/frontend/src/Pages/HomePage/HomePage.module.scss b/app/frontend/src/Pages/HomePage/HomePage.module.scss index 19551d96..46aec844 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.module.scss +++ b/app/frontend/src/Pages/HomePage/HomePage.module.scss @@ -1,12 +1,16 @@ +@import "../../colors"; + .pageContainer { padding: 1em; display: flex; justify-content: center; + .postsContainer { display: flex; gap: 1em; flex-direction: column; width: 70%; + background-color: $orange-light-90; .filterContainer { display: flex; justify-content: flex-end; diff --git a/app/frontend/src/Pages/HomePage/HomePage.tsx b/app/frontend/src/Pages/HomePage/HomePage.tsx index 83beb361..8ed4b258 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.tsx +++ b/app/frontend/src/Pages/HomePage/HomePage.tsx @@ -4,7 +4,7 @@ import { useAuth } from "../../Components/Hooks/useAuth"; import { getHomePosts } from "../../Services/home"; import ForumPost from "../../Components/Forum/ForumPost/ForumPost"; import { useState } from "react"; -import { Button, Pagination, Select } from "antd"; +import { Button, Pagination, PaginationProps, Select } from "antd"; import { SortAscendingOutlined, SortDescendingOutlined, @@ -20,6 +20,8 @@ function HomePage() { const [sortDirection, setSortDir] = useState<"ASCENDING" | "DESCENDING">( "DESCENDING" ); + const [pageData, setPageData] = useState([]); + const toggleSortDir = () => { setSortDir((currentSortDir) => currentSortDir === "ASCENDING" ? "DESCENDING" : "ASCENDING" @@ -29,6 +31,10 @@ function HomePage() { getHomePosts(sortDirection, sortBy as any) ); + const handleChange = (page: any, pageSize: any) => { + setPageData(data?.slice((page - 1) * pageSize, page * pageSize)); + }; + return (
@@ -61,7 +67,12 @@ function HomePage() { />
))} - ; +
); From 3ee06f493038aaf2c084f44f6bf8a17025703331 Mon Sep 17 00:00:00 2001 From: alperen-bircak Date: Sat, 23 Dec 2023 15:45:08 +0300 Subject: [PATCH 15/43] incalidate homepage on vote --- app/frontend/src/Components/Hooks/useVote.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/frontend/src/Components/Hooks/useVote.tsx b/app/frontend/src/Components/Hooks/useVote.tsx index 8daa42f8..7ae0f676 100644 --- a/app/frontend/src/Components/Hooks/useVote.tsx +++ b/app/frontend/src/Components/Hooks/useVote.tsx @@ -17,6 +17,7 @@ export function useVote({ (choice: "UPVOTE" | "DOWNVOTE") => createVote({ voteType, typeId, choice }), { onSuccess() { + queryClient.invalidateQueries(["home"]); if (invalidateKey) { queryClient.invalidateQueries(invalidateKey); } From e92c28c6540817f091dd962cc4974d5acdb755fe Mon Sep 17 00:00:00 2001 From: cisel-zumbul Date: Sat, 23 Dec 2023 16:16:06 +0300 Subject: [PATCH 16/43] Homepage is visually improved, pagination is added --- .../src/Pages/HomePage/HomePage.module.scss | 7 ++- app/frontend/src/Pages/HomePage/HomePage.tsx | 51 ++++++++++--------- app/frontend/src/_colors.scss | 14 ++--- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/app/frontend/src/Pages/HomePage/HomePage.module.scss b/app/frontend/src/Pages/HomePage/HomePage.module.scss index 46aec844..ea431d04 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.module.scss +++ b/app/frontend/src/Pages/HomePage/HomePage.module.scss @@ -10,7 +10,10 @@ gap: 1em; flex-direction: column; width: 70%; - background-color: $orange-light-90; + background-color: $vanilla-light-60; + padding: 10px; + border-radius: 0.5em; + .filterContainer { display: flex; justify-content: flex-end; @@ -18,7 +21,7 @@ gap: 0.5em; padding: 0.5em; } - .postsContainer { + .postContainer { width: 100%; } } diff --git a/app/frontend/src/Pages/HomePage/HomePage.tsx b/app/frontend/src/Pages/HomePage/HomePage.tsx index 8ed4b258..7a65a1a5 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.tsx +++ b/app/frontend/src/Pages/HomePage/HomePage.tsx @@ -4,7 +4,7 @@ import { useAuth } from "../../Components/Hooks/useAuth"; import { getHomePosts } from "../../Services/home"; import ForumPost from "../../Components/Forum/ForumPost/ForumPost"; import { useState } from "react"; -import { Button, Pagination, PaginationProps, Select } from "antd"; +import { Button, Pagination, Select } from "antd"; import { SortAscendingOutlined, SortDescendingOutlined, @@ -14,13 +14,15 @@ const sortOptions = [ { label: "Overall Vote", value: "OVERALL_VOTE" }, { label: "Vote Count", value: "VOTE_COUNT" }, ]; + +const PAGE_SIZE = 5; function HomePage() { const { user } = useAuth(); const [sortBy, setSortBy] = useState(sortOptions[0].value); const [sortDirection, setSortDir] = useState<"ASCENDING" | "DESCENDING">( "DESCENDING" ); - const [pageData, setPageData] = useState([]); + const [page, setPage] = useState(1); const toggleSortDir = () => { setSortDir((currentSortDir) => @@ -31,10 +33,6 @@ function HomePage() { getHomePosts(sortDirection, sortBy as any) ); - const handleChange = (page: any, pageSize: any) => { - setPageData(data?.slice((page - 1) * pageSize, page * pageSize)); - }; - return (
@@ -50,28 +48,31 @@ function HomePage() { options={sortOptions} value={sortBy} onChange={setSortBy} - style={{ width: "120px" }} + style={{ width: "200px" }} />
- {data?.map((post: any) => ( -
- -
- ))} + {data + ?.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE) + .map((post: any) => ( +
+ +
+ ))}
diff --git a/app/frontend/src/_colors.scss b/app/frontend/src/_colors.scss index 294ca0c3..e5524689 100644 --- a/app/frontend/src/_colors.scss +++ b/app/frontend/src/_colors.scss @@ -157,12 +157,12 @@ $orange-dark-90: #190800; $orange-light-10: #ff6c1a; $orange-light-20: #ff7733; $orange-light-30: #ff824d; -$orange-light-40: #ff8e66; -$orange-light-50: #ff9980; -$orange-light-60: #ffa599; -$orange-light-70: #ffb0b3; -$orange-light-80: #ffcccc; -$orange-light-90: #ffe7e6; +$orange-light-40: #ff8948; +$orange-light-50: #ff985f; +$orange-light-60: #ffa776; +$orange-light-70: #ffb68d; +$orange-light-80: #ffc4a3; +$orange-light-90: #ffe2d1; // Celadon Colors $celadon: #aacdbe; @@ -219,4 +219,4 @@ $color-container: $sky-blue; $color-background: $vanilla-light-80; $color-text: $prussian-blue-dark-50; $color-text-light: $vanilla-light-50; -$color-container-background: $sky-blue ; +$color-container-background: $sky-blue; From 35471baf0dec93688d249b4f76e2d5a8cf6fd0df Mon Sep 17 00:00:00 2001 From: alperen-bircak Date: Sat, 23 Dec 2023 16:17:04 +0300 Subject: [PATCH 17/43] recommendation queries --- app/frontend/src/Pages/HomePage/HomePage.tsx | 32 +++++++++++++++++--- app/frontend/src/Services/recommendation.ts | 17 +++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 app/frontend/src/Services/recommendation.ts diff --git a/app/frontend/src/Pages/HomePage/HomePage.tsx b/app/frontend/src/Pages/HomePage/HomePage.tsx index 8ed4b258..6c8149e0 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.tsx +++ b/app/frontend/src/Pages/HomePage/HomePage.tsx @@ -4,18 +4,22 @@ import { useAuth } from "../../Components/Hooks/useAuth"; import { getHomePosts } from "../../Services/home"; import ForumPost from "../../Components/Forum/ForumPost/ForumPost"; import { useState } from "react"; -import { Button, Pagination, PaginationProps, Select } from "antd"; +import { Button, Carousel, Pagination, PaginationProps, Select } from "antd"; import { SortAscendingOutlined, SortDescendingOutlined, } from "@ant-design/icons"; +import { + getGameRecommendations, + getGroupRecommendations, +} from "../../Services/recommendation"; const sortOptions = [ { label: "Creation Date", value: "CREATION_DATE" }, { label: "Overall Vote", value: "OVERALL_VOTE" }, { label: "Vote Count", value: "VOTE_COUNT" }, ]; function HomePage() { - const { user } = useAuth(); + const { user, isLoading: userLoading } = useAuth(); const [sortBy, setSortBy] = useState(sortOptions[0].value); const [sortDirection, setSortDir] = useState<"ASCENDING" | "DESCENDING">( "DESCENDING" @@ -27,8 +31,27 @@ function HomePage() { currentSortDir === "ASCENDING" ? "DESCENDING" : "ASCENDING" ); }; - const { data, isLoading } = useQuery(["home", user?.id], () => - getHomePosts(sortDirection, sortBy as any) + const { data, isLoading } = useQuery( + ["home", user?.id], + () => getHomePosts(sortDirection, sortBy as any), + { + enabled: !userLoading, + } + ); + + const { data: games } = useQuery( + ["gamerec", user?.id], + getGameRecommendations, + { + enabled: !userLoading, + } + ); + const { data: groups } = useQuery( + ["gamerec", user?.id], + getGroupRecommendations, + { + enabled: !userLoading, + } ); const handleChange = (page: any, pageSize: any) => { @@ -53,6 +76,7 @@ function HomePage() { style={{ width: "120px" }} /> +
{data?.map((post: any) => (
Date: Sat, 23 Dec 2023 16:17:49 +0300 Subject: [PATCH 18/43] remote merge is completed From ff7cd52dcb0ecfdeb6c88d05feae644e42c77246 Mon Sep 17 00:00:00 2001 From: zeynep-baydemir Date: Sat, 23 Dec 2023 17:56:22 +0300 Subject: [PATCH 19/43] changed group recommendations logic --- .../gamereview/config/SpringdocConfig.java | 36 +++--- .../controller/GroupController.java | 14 ++- .../controller/ImageController.java | 112 +++++++++--------- .../app/gamereview/service/GroupService.java | 52 ++++++-- app/backend/src/main/resources/.env.example | 22 ++-- 5 files changed, 140 insertions(+), 96 deletions(-) diff --git a/app/backend/src/main/java/com/app/gamereview/config/SpringdocConfig.java b/app/backend/src/main/java/com/app/gamereview/config/SpringdocConfig.java index 8109ede7..2f9679fc 100644 --- a/app/backend/src/main/java/com/app/gamereview/config/SpringdocConfig.java +++ b/app/backend/src/main/java/com/app/gamereview/config/SpringdocConfig.java @@ -1,18 +1,18 @@ -package com.app.gamereview.config; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@OpenAPIDefinition -@Configuration -public class SpringdocConfig { - - @Bean - public OpenAPI baseOpenAPI() { - return new OpenAPI().info(new Info().title("Spring Doc").version("1.0.0").description("Spring doc")); - } - -} +//package com.app.gamereview.config; +// +//import io.swagger.v3.oas.annotations.OpenAPIDefinition; +//import io.swagger.v3.oas.models.OpenAPI; +//import io.swagger.v3.oas.models.info.Info; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +// +//@OpenAPIDefinition +//@Configuration +//public class SpringdocConfig { +// +// @Bean +// public OpenAPI baseOpenAPI() { +// return new OpenAPI().info(new Info().title("Spring Doc").version("1.0.0").description("Spring doc")); +// } +// +//} diff --git a/app/backend/src/main/java/com/app/gamereview/controller/GroupController.java b/app/backend/src/main/java/com/app/gamereview/controller/GroupController.java index 20dbfe5e..f2ee86e2 100644 --- a/app/backend/src/main/java/com/app/gamereview/controller/GroupController.java +++ b/app/backend/src/main/java/com/app/gamereview/controller/GroupController.java @@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Optional; @RestController @RequestMapping("/api/group") @@ -184,11 +185,16 @@ public ResponseEntity> listApplications(@Reque return ResponseEntity.ok(applications); } - @AuthorizationRequired @GetMapping("/get-recommendations") - public ResponseEntity> getRecommendedGroups(@RequestHeader String Authorization, HttpServletRequest request) { - User user = (User) request.getAttribute("authenticatedUser"); - List groups = groupService.getRecommendedGroups(user); + public ResponseEntity> getRecommendedGroups(@RequestHeader(name = HttpHeaders.AUTHORIZATION, + required = false) String Authorization) { + String email = ""; + if(Authorization == null){ + return ResponseEntity.ok(groupService.getRecommendedGroups(email)); + } + if (JwtUtil.validateToken(Authorization)) + email = JwtUtil.extractSubject(Authorization); + List groups = groupService.getRecommendedGroups(email); return ResponseEntity.ok(groups); } } diff --git a/app/backend/src/main/java/com/app/gamereview/controller/ImageController.java b/app/backend/src/main/java/com/app/gamereview/controller/ImageController.java index e8036fda..10d420f8 100644 --- a/app/backend/src/main/java/com/app/gamereview/controller/ImageController.java +++ b/app/backend/src/main/java/com/app/gamereview/controller/ImageController.java @@ -1,56 +1,56 @@ -package com.app.gamereview.controller; - -import com.app.gamereview.service.FileStorageService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.FileSystemResource; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; -import org.springframework.core.io.Resource; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.io.IOException; - -@RestController -public class ImageController { - - private final FileStorageService fileService; - @Value("${image.base-directory}") - private String imageBaseDirectory; - - public ImageController(FileStorageService fileService) { - this.fileService = fileService; - } - - @GetMapping("/api/{folder}/{fileName:.+}") - public ResponseEntity serveImage(@PathVariable String folder, @PathVariable String fileName) { - try { - File imageFile = new File(imageBaseDirectory + folder + File.separator + fileName); - - if (imageFile.exists() && imageFile.isFile()) { - Resource resource = new FileSystemResource(imageFile); - return ResponseEntity.ok() - .contentLength(imageFile.length()) - .contentType(MediaType.IMAGE_PNG) // Set appropriate content type - // (e.g., IMAGE_PNG, IMAGE_JPEG) - .body(resource); - } - else { - // Handle resource not found error - return ResponseEntity.notFound().build(); - } - } - catch (Exception e) { - // Handle exceptions, e.g., file not found - return ResponseEntity.status(500).build(); - } - } - - @PostMapping("/api/image/upload") - public ResponseEntity uploadImage(@RequestParam String folder, @RequestPart MultipartFile image) throws IOException { - return ResponseEntity.ok(folder + "/" + fileService.storeFile(image, folder)); - } - -} +//package com.app.gamereview.controller; +// +//import com.app.gamereview.service.FileStorageService; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.core.io.FileSystemResource; +//import org.springframework.http.MediaType; +//import org.springframework.http.ResponseEntity; +//import org.springframework.stereotype.Controller; +//import org.springframework.web.bind.annotation.*; +//import org.springframework.core.io.Resource; +//import org.springframework.web.multipart.MultipartFile; +// +//import java.io.File; +//import java.io.IOException; +// +//@RestController +//public class ImageController { +// +// private final FileStorageService fileService; +// @Value("${image.base-directory}") +// private String imageBaseDirectory; +// +// public ImageController(FileStorageService fileService) { +// this.fileService = fileService; +// } +// +// @GetMapping("/api/{folder}/{fileName:.+}") +// public ResponseEntity serveImage(@PathVariable String folder, @PathVariable String fileName) { +// try { +// File imageFile = new File(imageBaseDirectory + folder + File.separator + fileName); +// +// if (imageFile.exists() && imageFile.isFile()) { +// Resource resource = new FileSystemResource(imageFile); +// return ResponseEntity.ok() +// .contentLength(imageFile.length()) +// .contentType(MediaType.IMAGE_PNG) // Set appropriate content type +// // (e.g., IMAGE_PNG, IMAGE_JPEG) +// .body(resource); +// } +// else { +// // Handle resource not found error +// return ResponseEntity.notFound().build(); +// } +// } +// catch (Exception e) { +// // Handle exceptions, e.g., file not found +// return ResponseEntity.status(500).build(); +// } +// } +// +// @PostMapping("/api/image/upload") +// public ResponseEntity uploadImage(@RequestParam String folder, @RequestPart MultipartFile image) throws IOException { +// return ResponseEntity.ok(folder + "/" + fileService.storeFile(image, folder)); +// } +// +//} diff --git a/app/backend/src/main/java/com/app/gamereview/service/GroupService.java b/app/backend/src/main/java/com/app/gamereview/service/GroupService.java index e78333a5..551af5d6 100644 --- a/app/backend/src/main/java/com/app/gamereview/service/GroupService.java +++ b/app/backend/src/main/java/com/app/gamereview/service/GroupService.java @@ -639,29 +639,65 @@ private GroupApplicationResponseDto contertToDto(GroupApplication application) { return dto; } - public List getRecommendedGroups(User user) { - Optional findProfile = profileRepository.findByUserIdAndIsDeletedFalse(user.getId()); + public List getRecommendedGroups(String email) { + Optional optUser = userRepository.findByEmailAndIsDeletedFalse(email); + + if(optUser.isEmpty()){ + Query query = new Query(); + query.addCriteria(Criteria.where("isDeleted").is(false)); + query.limit(10); + return mongoTemplate.find(query, Group.class); + } + User user = optUser.get(); + Optional findProfile = profileRepository.findByUserIdAndIsDeletedFalse(user.getId()); if (findProfile.isEmpty()) { throw new ResourceNotFoundException("Profile of the user is not found, unexpected error has occurred"); } - + List recommendations = new ArrayList<>(); List memberGroups = groupRepository.findUserGroups(user.getId()); + List userGames = findProfile.get().getGames(); if (memberGroups.isEmpty()) { - return Collections.emptyList(); + if (userGames.isEmpty()){ + Query query = new Query(); + query.addCriteria(Criteria.where("isDeleted").is(false)); + query.limit(10); + return mongoTemplate.find(query, Group.class); + }else{ + List recommendationGameGroups = new ArrayList<>(); + for(String gameId : userGames){ + Query query = new Query(); + query.addCriteria(Criteria.where("isDeleted").is(false)); + query.addCriteria(Criteria.where("gameId").is(gameId)); + query.limit(2); + List gameGroups = mongoTemplate.find(query, Group.class); + recommendationGameGroups.addAll(gameGroups); + } + return recommendationGameGroups; + } } TreeSet recommendedGroups = new TreeSet<>(Comparator.reverseOrder()); for (Group group : memberGroups) { String groupId = group.getId(); recommendedGroups.addAll(recommendationByGroupId(groupId)); } - - List recommendations = new ArrayList<>(); - + if(!userGames.isEmpty()){ + List recommendedGameGroups = new ArrayList<>(); + for(String gameId : userGames){ + Query query = new Query(); + query.addCriteria(Criteria.where("isDeleted").is(false)); + query.addCriteria(Criteria.where("gameId").is(gameId)); + query.limit(2); + List gameGroups = mongoTemplate.find(query, Group.class); + recommendedGameGroups.addAll(gameGroups); + } + recommendations.addAll(recommendedGameGroups); + } for (RecommendGroupDto groupDto : recommendedGroups) { recommendations.add(groupDto.getGroup()); } + return recommendations; } @@ -696,7 +732,7 @@ public TreeSet recommendationByGroupId(String groupId) { } } - Query allGroupsQuery = new Query(); // all games except the base game + Query allGroupsQuery = new Query(); allGroupsQuery.addCriteria(Criteria.where("isDeleted").is(false)); allGroupsQuery.addCriteria(Criteria.where("_id").nin(idList)); diff --git a/app/backend/src/main/resources/.env.example b/app/backend/src/main/resources/.env.example index 6eb48ecf..c67b54dd 100644 --- a/app/backend/src/main/resources/.env.example +++ b/app/backend/src/main/resources/.env.example @@ -1,10 +1,12 @@ -MONGO_DATABASE= -MONGO_USER= -MONGO_PASSWORD= -MONGO_CLUSTER= -MAIL_HOST= -MAIL_PORT= -MAIL_USERNAME= -MAIL_PASSWORD= -MAIL_AUTH= -MAIL_STARTTLS= \ No newline at end of file +MONGO_DATABASE="game-review-dev" +MONGO_USER="uzdurancan" +MONGO_PASSWORD="kQXN23hjiDo8WroF" +MONGO_CLUSTER="bounswe-group5-cluster.1uup3ah.mongodb.net" +SECRET_KEY="bounswe2023group5gamereview" +MAIL_HOST="smtp.gmail.com" +MAIL_PORT="587" +MAIL_USERNAME="bounswe2023.group5@gmail.com" +MAIL_PASSWORD="eevcddbrjtikrtcn" +MAIL_AUTH="true" +MAIL_STARTTLS="true" +IMAGE_DIR = "" \ No newline at end of file From a62f4ad21e919229fea6ef74a0eef5df523a99a8 Mon Sep 17 00:00:00 2001 From: zeynep-baydemir Date: Sat, 23 Dec 2023 18:07:03 +0300 Subject: [PATCH 20/43] fix --- .../gamereview/config/SpringdocConfig.java | 36 +++--- .../controller/ImageController.java | 112 +++++++++--------- app/backend/src/main/resources/.env.example | 22 ++-- 3 files changed, 84 insertions(+), 86 deletions(-) diff --git a/app/backend/src/main/java/com/app/gamereview/config/SpringdocConfig.java b/app/backend/src/main/java/com/app/gamereview/config/SpringdocConfig.java index 2f9679fc..8109ede7 100644 --- a/app/backend/src/main/java/com/app/gamereview/config/SpringdocConfig.java +++ b/app/backend/src/main/java/com/app/gamereview/config/SpringdocConfig.java @@ -1,18 +1,18 @@ -//package com.app.gamereview.config; -// -//import io.swagger.v3.oas.annotations.OpenAPIDefinition; -//import io.swagger.v3.oas.models.OpenAPI; -//import io.swagger.v3.oas.models.info.Info; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -// -//@OpenAPIDefinition -//@Configuration -//public class SpringdocConfig { -// -// @Bean -// public OpenAPI baseOpenAPI() { -// return new OpenAPI().info(new Info().title("Spring Doc").version("1.0.0").description("Spring doc")); -// } -// -//} +package com.app.gamereview.config; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@OpenAPIDefinition +@Configuration +public class SpringdocConfig { + + @Bean + public OpenAPI baseOpenAPI() { + return new OpenAPI().info(new Info().title("Spring Doc").version("1.0.0").description("Spring doc")); + } + +} diff --git a/app/backend/src/main/java/com/app/gamereview/controller/ImageController.java b/app/backend/src/main/java/com/app/gamereview/controller/ImageController.java index 10d420f8..e8036fda 100644 --- a/app/backend/src/main/java/com/app/gamereview/controller/ImageController.java +++ b/app/backend/src/main/java/com/app/gamereview/controller/ImageController.java @@ -1,56 +1,56 @@ -//package com.app.gamereview.controller; -// -//import com.app.gamereview.service.FileStorageService; -//import org.springframework.beans.factory.annotation.Value; -//import org.springframework.core.io.FileSystemResource; -//import org.springframework.http.MediaType; -//import org.springframework.http.ResponseEntity; -//import org.springframework.stereotype.Controller; -//import org.springframework.web.bind.annotation.*; -//import org.springframework.core.io.Resource; -//import org.springframework.web.multipart.MultipartFile; -// -//import java.io.File; -//import java.io.IOException; -// -//@RestController -//public class ImageController { -// -// private final FileStorageService fileService; -// @Value("${image.base-directory}") -// private String imageBaseDirectory; -// -// public ImageController(FileStorageService fileService) { -// this.fileService = fileService; -// } -// -// @GetMapping("/api/{folder}/{fileName:.+}") -// public ResponseEntity serveImage(@PathVariable String folder, @PathVariable String fileName) { -// try { -// File imageFile = new File(imageBaseDirectory + folder + File.separator + fileName); -// -// if (imageFile.exists() && imageFile.isFile()) { -// Resource resource = new FileSystemResource(imageFile); -// return ResponseEntity.ok() -// .contentLength(imageFile.length()) -// .contentType(MediaType.IMAGE_PNG) // Set appropriate content type -// // (e.g., IMAGE_PNG, IMAGE_JPEG) -// .body(resource); -// } -// else { -// // Handle resource not found error -// return ResponseEntity.notFound().build(); -// } -// } -// catch (Exception e) { -// // Handle exceptions, e.g., file not found -// return ResponseEntity.status(500).build(); -// } -// } -// -// @PostMapping("/api/image/upload") -// public ResponseEntity uploadImage(@RequestParam String folder, @RequestPart MultipartFile image) throws IOException { -// return ResponseEntity.ok(folder + "/" + fileService.storeFile(image, folder)); -// } -// -//} +package com.app.gamereview.controller; + +import com.app.gamereview.service.FileStorageService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.core.io.Resource; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; + +@RestController +public class ImageController { + + private final FileStorageService fileService; + @Value("${image.base-directory}") + private String imageBaseDirectory; + + public ImageController(FileStorageService fileService) { + this.fileService = fileService; + } + + @GetMapping("/api/{folder}/{fileName:.+}") + public ResponseEntity serveImage(@PathVariable String folder, @PathVariable String fileName) { + try { + File imageFile = new File(imageBaseDirectory + folder + File.separator + fileName); + + if (imageFile.exists() && imageFile.isFile()) { + Resource resource = new FileSystemResource(imageFile); + return ResponseEntity.ok() + .contentLength(imageFile.length()) + .contentType(MediaType.IMAGE_PNG) // Set appropriate content type + // (e.g., IMAGE_PNG, IMAGE_JPEG) + .body(resource); + } + else { + // Handle resource not found error + return ResponseEntity.notFound().build(); + } + } + catch (Exception e) { + // Handle exceptions, e.g., file not found + return ResponseEntity.status(500).build(); + } + } + + @PostMapping("/api/image/upload") + public ResponseEntity uploadImage(@RequestParam String folder, @RequestPart MultipartFile image) throws IOException { + return ResponseEntity.ok(folder + "/" + fileService.storeFile(image, folder)); + } + +} diff --git a/app/backend/src/main/resources/.env.example b/app/backend/src/main/resources/.env.example index c67b54dd..6eb48ecf 100644 --- a/app/backend/src/main/resources/.env.example +++ b/app/backend/src/main/resources/.env.example @@ -1,12 +1,10 @@ -MONGO_DATABASE="game-review-dev" -MONGO_USER="uzdurancan" -MONGO_PASSWORD="kQXN23hjiDo8WroF" -MONGO_CLUSTER="bounswe-group5-cluster.1uup3ah.mongodb.net" -SECRET_KEY="bounswe2023group5gamereview" -MAIL_HOST="smtp.gmail.com" -MAIL_PORT="587" -MAIL_USERNAME="bounswe2023.group5@gmail.com" -MAIL_PASSWORD="eevcddbrjtikrtcn" -MAIL_AUTH="true" -MAIL_STARTTLS="true" -IMAGE_DIR = "" \ No newline at end of file +MONGO_DATABASE= +MONGO_USER= +MONGO_PASSWORD= +MONGO_CLUSTER= +MAIL_HOST= +MAIL_PORT= +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_AUTH= +MAIL_STARTTLS= \ No newline at end of file From 92eb99bcd3e39477e90e59dc64049c82143f342c Mon Sep 17 00:00:00 2001 From: AliBasarann Date: Sat, 23 Dec 2023 19:07:49 +0300 Subject: [PATCH 21/43] Promoted Entities component is implemented and added to games page --- .../PromotedEntities.module.scss | 82 +++++++++++++++++++ .../PromotedEntities/PromotedEntities.tsx | 59 +++++++++++++ .../src/Pages/Achievement/Achievements.tsx | 38 ++++++--- app/frontend/src/Pages/Games/Games.tsx | 27 ++++-- 4 files changed, 189 insertions(+), 17 deletions(-) create mode 100644 app/frontend/src/Components/PromotedEntities/PromotedEntities.module.scss create mode 100644 app/frontend/src/Components/PromotedEntities/PromotedEntities.tsx diff --git a/app/frontend/src/Components/PromotedEntities/PromotedEntities.module.scss b/app/frontend/src/Components/PromotedEntities/PromotedEntities.module.scss new file mode 100644 index 00000000..2c77c1de --- /dev/null +++ b/app/frontend/src/Components/PromotedEntities/PromotedEntities.module.scss @@ -0,0 +1,82 @@ + + +@import "../../colors"; + +.container { + background-color: $violet-light-80; + border-radius: 0.5em; + overflow: hidden; + border: 3px solid $yellow; + justify-content: center; + align-items: center; + +} + +@media only screen and (max-width: 976px) { + .container { + flex: 0 1 100%; + } + } + +@media only screen and (min-width: 976px) { +.container { + flex: 0 1 calc(100% * (1 / 2) - 1rem); +} +} + + +.header { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + background-color: $violet; + cursor: pointer; + h1{ + margin-left: 10px; + } + +} + +.descriptionContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-weight: bold; + font-size: 1.2rem; +} + +.description { + margin-left: 1rem; + margin-top: 0; + text-wrap: wrap; +} + +.icon { + margin-right: 1rem; + border-radius: 10px; + width: 240px; + height: 200px; + cursor: pointer; +} + +.content { + display: flex; + padding: 1rem; + text-wrap: wrap; + position: relative; + align-items: center; +} + +.button { + position: absolute; + + right: 0.5em; + bottom: 0.5em; +} + +.promotion{ + color: $orange-dark-60; +} + diff --git a/app/frontend/src/Components/PromotedEntities/PromotedEntities.tsx b/app/frontend/src/Components/PromotedEntities/PromotedEntities.tsx new file mode 100644 index 00000000..3d4c4491 --- /dev/null +++ b/app/frontend/src/Components/PromotedEntities/PromotedEntities.tsx @@ -0,0 +1,59 @@ +import { TeamOutlined, UserOutlined } from "@ant-design/icons"; +import styles from "./PromotedEntities.module.scss"; +import { Button, Carousel, message } from "antd"; + +import { useNavigate } from "react-router-dom"; +import { useMutation, useQueryClient } from "react-query"; +import { joinGroup, leaveGroup } from "../../Services/group"; +import { truncateWithEllipsis } from "../../Library/utils/truncate"; +import GameConsole from "../../../assets/images/game-console.png"; + + +function PromotedEntities({games}: {games: any[]}) { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + + + return ( + + ( +
+ + + + { + games.map((game: any) => ( +
+
navigate(`/game/detail/${game.id}`)}> +

{game.gameName}

+ +

{ "(PROMOTED)"}

+
+ +
+ {game.gameName} navigate(`/game/detail/${game.id}`)} + > +
+

+ {truncateWithEllipsis(game.gameDescription, 300)} +

+
+
+
+ )) + } +
+ + +
+ ) +) + +} + +export default PromotedEntities; diff --git a/app/frontend/src/Pages/Achievement/Achievements.tsx b/app/frontend/src/Pages/Achievement/Achievements.tsx index c1fdc7c6..95a42d29 100644 --- a/app/frontend/src/Pages/Achievement/Achievements.tsx +++ b/app/frontend/src/Pages/Achievement/Achievements.tsx @@ -2,28 +2,42 @@ import { useQuery } from "react-query"; import { getAchievementByGame } from "../../Services/achievement"; import Achievement from "../../Components/Achievement/Achievement/Achievement"; import SquareAchievement from "../../Components/Achievement/SquareAchievement/SquareAchievement"; +import PromotedEntities from "../../Components/PromotedEntities/PromotedEntities"; +import { getGames } from "../../Services/games"; +import { useState } from "react"; function Achievements(){ const gameId:string = "841cbf45-90cc-47b7-a763-fa3a18218bf9" - const { data: achievements, isLoading: isLoadingAchievements } = useQuery( - ["achievements", gameId], - () => getAchievementByGame({ gameId: gameId! }) + const { data: games, isLoading: isLoadingGames } = useQuery( + ["Games", gameId], + () => getGames(), + { + onSuccess: (data) => { + // Check if the data is an array and has at least two elements + if (Array.isArray(data) && data.length >= 2) { + // Update promotedEntities with the first two elements of the data array + setPromotedEntities(data.slice(0, 2)); + } + }, + } ); - - + + const [promotedEntities, setPromotedEntities] = useState([]); return(
- {!isLoadingAchievements && - achievements.map( - (achievement: any) => - !achievement.isDeleted && ( - - ) - )} + {!isLoadingGames && + + +
+ +
+ + }
+ ) } diff --git a/app/frontend/src/Pages/Games/Games.tsx b/app/frontend/src/Pages/Games/Games.tsx index 551c6b4d..e97ee1f6 100644 --- a/app/frontend/src/Pages/Games/Games.tsx +++ b/app/frontend/src/Pages/Games/Games.tsx @@ -8,6 +8,7 @@ import MultipleSelect from "../../Components/MultipleSelect/MultipleSelect"; import { Button, Input } from "antd"; import { getGames } from "../../Services/games"; import { getTags } from "../../Services/tags"; +import PromotedEntities from "../../Components/PromotedEntities/PromotedEntities"; function Games() { const [filters, setFilters] = useState({ @@ -22,10 +23,23 @@ function Games() { const [searchText, setSearchText] = useState(""); const [activeFilters, setActiveFilters] = useState(); - const { data: games } = useQuery(["games", activeFilters, searchText], () => - getGames(activeFilters, searchText.length <= 0 ? undefined : searchText) - ); + const { data: games } = useQuery( + ["games", activeFilters, searchText], () => + getGames(activeFilters, searchText.length <= 0 ? undefined : searchText), + { + onSuccess: (data) => { + // Check if the data is an array and has at least two elements + if (Array.isArray(data) && data.length >= 2) { + // Update promotedEntities with the first two elements of the data array + setPromotedEntities(data.slice(0, 2)); + setNotPromotedGames(data.slice(2)); + } + }, + } + ); + const [promotedEntities, setPromotedEntities] = useState([]); + const [notPromotedgames, setNotPromotedGames] = useState([]); const { data: tags } = useQuery(["tags"], () => getTags()); const onChange = (filterKey: string, value: string[] | string) => { @@ -126,9 +140,12 @@ function Games() { Filter
+
+ +
- {Array.isArray(games) && - games.map((game) => { + {Array.isArray(notPromotedgames) && + notPromotedgames.map((game) => { return ; })}
From bfcdcf020afe63bd65281f7e3308c4c96947575e Mon Sep 17 00:00:00 2001 From: Halis Bal Date: Sat, 23 Dec 2023 21:26:54 +0300 Subject: [PATCH 22/43] added plugin for test coverage report --- app/backend/pom.xml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/backend/pom.xml b/app/backend/pom.xml index abd955da..6ff52a17 100644 --- a/app/backend/pom.xml +++ b/app/backend/pom.xml @@ -104,6 +104,25 @@ spring-javaformat-maven-plugin 0.0.39 + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + + prepare-agent + + + + generate-code-coverage-report + test + + report + + + + From 3d69e02fd67a4f4d365ad92abc7ae1e783773138 Mon Sep 17 00:00:00 2001 From: zeynep-baydemir Date: Sat, 23 Dec 2023 22:16:28 +0300 Subject: [PATCH 23/43] added posts of promoted games in homepage --- .../home/HomePagePostResponseDto.java | 2 + .../app/gamereview/service/PostService.java | 83 ++++++++++++++++++- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/app/backend/src/main/java/com/app/gamereview/dto/response/home/HomePagePostResponseDto.java b/app/backend/src/main/java/com/app/gamereview/dto/response/home/HomePagePostResponseDto.java index 8a082bed..761655f8 100644 --- a/app/backend/src/main/java/com/app/gamereview/dto/response/home/HomePagePostResponseDto.java +++ b/app/backend/src/main/java/com/app/gamereview/dto/response/home/HomePagePostResponseDto.java @@ -60,4 +60,6 @@ public class HomePagePostResponseDto { private LocalDateTime createdAt; private Boolean isDeleted; + private Boolean isPromoted; + } diff --git a/app/backend/src/main/java/com/app/gamereview/service/PostService.java b/app/backend/src/main/java/com/app/gamereview/service/PostService.java index cac4b01a..664f1dfb 100644 --- a/app/backend/src/main/java/com/app/gamereview/service/PostService.java +++ b/app/backend/src/main/java/com/app/gamereview/service/PostService.java @@ -468,6 +468,38 @@ public List getHomePagePostsOfGuest(HomePagePostsFilter List gameForums = mongoTemplate.find(query, Forum.class); List postsToShow = new ArrayList<>(); + Query allGamesQuery = new Query(); + allGamesQuery.addCriteria(Criteria.where("isDeleted").is(false)); + allGamesQuery.addCriteria(Criteria.where("isPromoted").is(true)); + List promotedGames = mongoTemplate.find(allGamesQuery, Game.class); + int promotedCount = 0; + if(promotedGames.size() >= 2){ + Collections.shuffle(promotedGames); + Game randomGame1 = promotedGames.get(0); + Game randomGame2 = promotedGames.get(1); + List game1Posts = postRepository.findByForumAndIsDeletedFalse(randomGame1.getForum()); + List game2Posts = postRepository.findByForumAndIsDeletedFalse(randomGame2.getForum()); + Collections.shuffle(game1Posts); + Collections.shuffle(game2Posts); + if(!game1Posts.isEmpty()){ + postsToShow.add(game1Posts.get(0)); + promotedCount++; + } + if(!game2Posts.isEmpty()){ + postsToShow.add(game2Posts.get(0)); + promotedCount++; + } + }else if(promotedGames.size() == 1){ + Game promotedGame = promotedGames.get(0); + List posts = postRepository.findByForumAndIsDeletedFalse(promotedGame.getForum()); + Collections.shuffle(posts); + if(!posts.isEmpty()){ + postsToShow.add(posts.get(0)); + postsToShow.add(posts.get(1)); + promotedCount= promotedCount+2; + } + } + for(Forum forum : gameForums){ postsToShow.addAll(postRepository.findByForumAndIsDeletedFalse(forum.getId())); } @@ -506,7 +538,7 @@ else if(filter.getSortBy().equals(SortType.VOTE_COUNT.name())){ List first20 = postsToShow.subList(0, Math.min(20, postsToShow.size())); List first20dto = new ArrayList<>(); - + int index = 0; for(Post post : first20){ HomePagePostResponseDto dto = modelMapper.map(post,HomePagePostResponseDto.class); @@ -556,6 +588,12 @@ else if(forumOfPost.getType().equals(ForumType.GAME)){ dto.setTypeName(typeName); + if(index<=promotedCount){ + dto.setIsPromoted(true); + }else { + dto.setIsPromoted(false); + } + index++; first20dto.add(dto); } @@ -643,10 +681,42 @@ else if(filter.getSortBy().equals(SortType.VOTE_COUNT.name())){ } } - List first20 = postsToShow.subList(0, Math.min(20, postsToShow.size())); - List first20dto = new ArrayList<>(); + Query allGamesQuery = new Query(); + allGamesQuery.addCriteria(Criteria.where("isDeleted").is(false)); + allGamesQuery.addCriteria(Criteria.where("isPromoted").is(true)); + List promotedGames = mongoTemplate.find(allGamesQuery, Game.class); + int promotedCount = 0; + if(promotedGames.size() >= 2){ + Collections.shuffle(promotedGames); + Game randomGame1 = promotedGames.get(0); + Game randomGame2 = promotedGames.get(1); + List game1Posts = postRepository.findByForumAndIsDeletedFalse(randomGame1.getForum()); + List game2Posts = postRepository.findByForumAndIsDeletedFalse(randomGame2.getForum()); + Collections.shuffle(game1Posts); + Collections.shuffle(game2Posts); + if(!game1Posts.isEmpty()){ + postsToShow.add(0,game1Posts.get(0)); + promotedCount++; + } + if(!game2Posts.isEmpty()){ + postsToShow.add(0,game2Posts.get(0)); + promotedCount++; + } + }else if(promotedGames.size() == 1){ + Game promotedGame = promotedGames.get(0); + List posts = postRepository.findByForumAndIsDeletedFalse(promotedGame.getForum()); + Collections.shuffle(posts); + if(!posts.isEmpty()){ + postsToShow.add(0,posts.get(0)); + postsToShow.add(0,posts.get(1)); + promotedCount = promotedCount +2; + + } + } + List first20 = postsToShow.subList(0, Math.min(20, postsToShow.size())); + int index = 0; for(Post post : first20){ HomePagePostResponseDto dto = modelMapper.map(post,HomePagePostResponseDto.class); dto.setTags(populatedTags(post.getTags())); @@ -696,7 +766,12 @@ else if(forumOfPost.getType().equals(ForumType.GAME)){ dto.setUserVote(getUserVote(post.getId(), user.getId())); dto.setTypeName(typeName); - + if(index<=promotedCount){ + dto.setIsPromoted(true); + }else { + dto.setIsPromoted(false); + } + index++; first20dto.add(dto); } From fbbfafb26fd23405a7a4817c29b893935199ebd3 Mon Sep 17 00:00:00 2001 From: AliBasarann Date: Sat, 23 Dec 2023 22:36:31 +0300 Subject: [PATCH 24/43] promoted game symbol is changed --- .../PromotedEntities/PromotedEntities.module.scss | 6 ++++++ .../src/Components/PromotedEntities/PromotedEntities.tsx | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/frontend/src/Components/PromotedEntities/PromotedEntities.module.scss b/app/frontend/src/Components/PromotedEntities/PromotedEntities.module.scss index 2c77c1de..7467c544 100644 --- a/app/frontend/src/Components/PromotedEntities/PromotedEntities.module.scss +++ b/app/frontend/src/Components/PromotedEntities/PromotedEntities.module.scss @@ -80,3 +80,9 @@ color: $orange-dark-60; } +.crown{ + color: $yellow-dark-20; + font-size: 1.5rem; + margin-left: 20px; + margin-right: 20px; +} \ No newline at end of file diff --git a/app/frontend/src/Components/PromotedEntities/PromotedEntities.tsx b/app/frontend/src/Components/PromotedEntities/PromotedEntities.tsx index 3d4c4491..83b10e50 100644 --- a/app/frontend/src/Components/PromotedEntities/PromotedEntities.tsx +++ b/app/frontend/src/Components/PromotedEntities/PromotedEntities.tsx @@ -1,6 +1,6 @@ -import { TeamOutlined, UserOutlined } from "@ant-design/icons"; +import { CrownFilled, TeamOutlined, UserOutlined } from "@ant-design/icons"; import styles from "./PromotedEntities.module.scss"; -import { Button, Carousel, message } from "antd"; +import { Button, Carousel, Tooltip, message } from "antd"; import { useNavigate } from "react-router-dom"; import { useMutation, useQueryClient } from "react-query"; @@ -27,8 +27,9 @@ function PromotedEntities({games}: {games: any[]}) {
navigate(`/game/detail/${game.id}`)}>

{game.gameName}

- -

{ "(PROMOTED)"}

+ + +
From a0d8a7cff77e92212a7b903c4266672faac50b19 Mon Sep 17 00:00:00 2001 From: Can Date: Sat, 23 Dec 2023 23:04:41 +0300 Subject: [PATCH 25/43] enhancing the vote service unit test --- .../gamereview/service/VoteServiceTest.java | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) diff --git a/app/backend/src/test/java/com/app/gamereview/service/VoteServiceTest.java b/app/backend/src/test/java/com/app/gamereview/service/VoteServiceTest.java index f00be100..c9af7243 100644 --- a/app/backend/src/test/java/com/app/gamereview/service/VoteServiceTest.java +++ b/app/backend/src/test/java/com/app/gamereview/service/VoteServiceTest.java @@ -1,5 +1,6 @@ package com.app.gamereview.service; +import com.app.gamereview.dto.request.notification.CreateNotificationRequestDto; import com.app.gamereview.dto.request.vote.*; import com.app.gamereview.enums.*; import com.app.gamereview.exception.ResourceNotFoundException; @@ -22,9 +23,15 @@ public class VoteServiceTest { @Mock private VoteRepository voteRepository; + @Mock + private UserRepository userRepository; + @Mock private ReviewRepository reviewRepository; + @Mock + private NotificationRepository notificationRepository; + @Mock private ProfileRepository profileRepository; @@ -43,6 +50,8 @@ public class VoteServiceTest { @Mock private CommentRepository commentRepository; + @Mock NotificationService notificationService; + @InjectMocks private VoteService voteService; @@ -110,6 +119,210 @@ void testAddVote() { assertEquals("userId", result.getVotedBy()); } + @Test + void testAddReviewVoteFirstTime() { + CreateVoteRequestDto requestDto = new CreateVoteRequestDto(); + requestDto.setChoice(VoteChoice.DOWNVOTE.name()); + requestDto.setTypeId("newReviewId"); + requestDto.setVoteType(VoteType.REVIEW.name()); + + User user = new User(); + user.setId("userId"); + user.setUsername("userName"); + + Profile profile = new Profile(); + profile.setUserId("userId"); + profile.setIsVotedYet(false); + + Vote newVote = new Vote(); + newVote.setId("newVoteId"); + newVote.setChoice(VoteChoice.DOWNVOTE); + newVote.setVotedBy(user.getId()); + + Review newReview = new Review(); + newReview.setId("newReviewId"); + + when(mongoTemplate.findOne(any(Query.class), eq(Profile.class))).thenReturn(profile); + when(modelMapper.map(requestDto, Vote.class)).thenReturn(newVote); + when(reviewRepository.findById(requestDto.getTypeId())).thenReturn(Optional.of(newReview)); + when(notificationService.createNotification(any(CreateNotificationRequestDto.class))).thenReturn(null); + when(voteRepository.save(any(Vote.class))).thenReturn(newVote); + + Vote result = voteService.addVote(requestDto, user); + + assertNotNull(result); + assertEquals(VoteChoice.DOWNVOTE, result.getChoice()); + assertEquals("newVoteId", result.getId()); + assertEquals(user.getId(), result.getVotedBy()); + } + + @Test + void testAddCommentVoteFirstTime() { + CreateVoteRequestDto requestDto = new CreateVoteRequestDto(); + requestDto.setChoice(VoteChoice.DOWNVOTE.name()); + requestDto.setTypeId("newCommentId"); + requestDto.setVoteType(VoteType.COMMENT.name()); + + User user = new User(); + user.setId("userId"); + user.setUsername("userName"); + + Profile profile = new Profile(); + profile.setUserId("userId"); + profile.setIsVotedYet(false); + + Vote newVote = new Vote(); + newVote.setId("newVoteId"); + newVote.setChoice(VoteChoice.DOWNVOTE); + newVote.setVotedBy(user.getId()); + + Comment newComment = new Comment(); + newComment.setId("newCommentId"); + + when(mongoTemplate.findOne(any(Query.class), eq(Profile.class))).thenReturn(profile); + when(modelMapper.map(requestDto, Vote.class)).thenReturn(newVote); + when(commentRepository.findById(requestDto.getTypeId())).thenReturn(Optional.of(newComment)); + when(notificationService.createNotification(any(CreateNotificationRequestDto.class))).thenReturn(null); + when(voteRepository.save(any(Vote.class))).thenReturn(newVote); + + Vote result = voteService.addVote(requestDto, user); + + assertNotNull(result); + assertEquals(VoteChoice.DOWNVOTE, result.getChoice()); + assertEquals("newVoteId", result.getId()); + assertEquals(user.getId(), result.getVotedBy()); + } + + + @Test + void testAddPostVoteFirstTime() { + CreateVoteRequestDto requestDto = new CreateVoteRequestDto(); + requestDto.setChoice(VoteChoice.DOWNVOTE.name()); + requestDto.setTypeId("newPostId"); + requestDto.setVoteType(VoteType.POST.name()); + + User user = new User(); + user.setId("userId"); + user.setUsername("userName"); + + Profile profile = new Profile(); + profile.setUserId("userId"); + profile.setIsVotedYet(false); + + Vote newVote = new Vote(); + newVote.setId("newVoteId"); + newVote.setChoice(VoteChoice.DOWNVOTE); + newVote.setVotedBy(user.getId()); + + Post newPost = new Post(); + newPost.setId("newPostId"); + + Notification newNotification = new Notification(); + + when(mongoTemplate.findOne(any(Query.class), eq(Profile.class))).thenReturn(profile); + when(modelMapper.map(requestDto, Vote.class)).thenReturn(newVote); + when(postRepository.findById(requestDto.getTypeId())).thenReturn(Optional.of(newPost)); + when(notificationService.createNotification(any(CreateNotificationRequestDto.class))).thenReturn(null); + when(notificationRepository.findByParentAndUser(any(String.class), any(String.class))).thenReturn(Optional.of(newNotification)); + when(voteRepository.save(any(Vote.class))).thenReturn(newVote); + + Vote result = voteService.addVote(requestDto, user); + + assertNotNull(result); + assertEquals(VoteChoice.DOWNVOTE, result.getChoice()); + assertEquals("newVoteId", result.getId()); + assertEquals(user.getId(), result.getVotedBy()); + } + + @Test + void testAddPostVoteFirstTimeWithOverallVote10() { + CreateVoteRequestDto requestDto = new CreateVoteRequestDto(); + requestDto.setChoice(VoteChoice.DOWNVOTE.name()); + requestDto.setTypeId("newPostId"); + requestDto.setVoteType(VoteType.POST.name()); + + User user = new User(); + user.setId("userId"); + user.setUsername("userName"); + + Profile profile = new Profile(); + profile.setUserId("userId"); + profile.setIsVotedYet(false); + + Vote newVote = new Vote(); + newVote.setId("newVoteId"); + newVote.setChoice(VoteChoice.DOWNVOTE); + newVote.setVotedBy(user.getId()); + + Post newPost = new Post(); + newPost.setId("newPostId"); + newPost.setOverallVote(11); // plus 1 since downovote + newPost.setPoster("userId"); + newPost.setTitle("Test Post"); + + Notification newNotification = new Notification(); + + when(userRepository.findById(any(String.class))).thenReturn(Optional.of(user)); + when(mongoTemplate.findOne(any(Query.class), eq(Profile.class))).thenReturn(profile); + when(modelMapper.map(requestDto, Vote.class)).thenReturn(newVote); + when(postRepository.findById(requestDto.getTypeId())).thenReturn(Optional.of(newPost)); + when(notificationService.createNotification(any(CreateNotificationRequestDto.class))).thenReturn(null); + when(notificationRepository.findByParentAndUser(any(String.class), any(String.class))).thenReturn(Optional.of(newNotification)); + when(voteRepository.save(any(Vote.class))).thenReturn(newVote); + + Vote result = voteService.addVote(requestDto, user); + + assertNotNull(result); + assertEquals(VoteChoice.DOWNVOTE, result.getChoice()); + assertEquals("newVoteId", result.getId()); + assertEquals(user.getId(), result.getVotedBy()); + } + + + @Test + void testAddPostVoteFirstTimeWithOverallVote10NoParent() { + CreateVoteRequestDto requestDto = new CreateVoteRequestDto(); + requestDto.setChoice(VoteChoice.DOWNVOTE.name()); + requestDto.setTypeId("newPostId"); + requestDto.setVoteType(VoteType.POST.name()); + + User user = new User(); + user.setId("userId"); + user.setUsername("userName"); + + Profile profile = new Profile(); + profile.setUserId("userId"); + profile.setIsVotedYet(false); + + Vote newVote = new Vote(); + newVote.setId("newVoteId"); + newVote.setChoice(VoteChoice.DOWNVOTE); + newVote.setVotedBy(user.getId()); + + Post newPost = new Post(); + newPost.setId("newPostId"); + newPost.setOverallVote(11); // plus 1 since downovote + newPost.setPoster("userId"); + newPost.setTitle("Test Post"); + + Notification newNotification = new Notification(); + + when(userRepository.findById(any(String.class))).thenReturn(Optional.of(user)); + when(mongoTemplate.findOne(any(Query.class), eq(Profile.class))).thenReturn(profile); + when(modelMapper.map(requestDto, Vote.class)).thenReturn(newVote); + when(postRepository.findById(requestDto.getTypeId())).thenReturn(Optional.of(newPost)); + when(notificationService.createNotification(any(CreateNotificationRequestDto.class))).thenReturn(null); + when(notificationRepository.findByParentAndUser(any(String.class), any(String.class))).thenReturn(Optional.empty()); + when(voteRepository.save(any(Vote.class))).thenReturn(newVote); + + Vote result = voteService.addVote(requestDto, user); + + assertNotNull(result); + assertEquals(VoteChoice.DOWNVOTE, result.getChoice()); + assertEquals("newVoteId", result.getId()); + assertEquals(user.getId(), result.getVotedBy()); + } + @Test void testAddVoteResourceNotFound() { CreateVoteRequestDto requestDto = new CreateVoteRequestDto(); @@ -135,4 +348,19 @@ void testDeleteVoteResourceNotFound() { assertThrows(ResourceNotFoundException.class, () -> voteService.deleteVote(nonExistingVoteId)); } + + @Test + void testDeleteVote() { + + Vote vote = new Vote(); + vote.setId("existingVoteId"); + + String existingVoteId = "existingVoteId"; + when(voteRepository.findById(existingVoteId)).thenReturn(Optional.of(vote)); + + Boolean result = voteService.deleteVote(vote.getId()); + + assertNotNull(result); + assertEquals(true,result); + } } From f07cbf8b395c74d19f964de80c127c6bbadd5fb5 Mon Sep 17 00:00:00 2001 From: Deniz Unal Date: Sun, 24 Dec 2023 10:49:03 +0300 Subject: [PATCH 26/43] reimplemented the broken tests and added new ones to improve coverage --- .../gamereview/service/GameServiceTest.java | 646 +++++++++++++++--- 1 file changed, 556 insertions(+), 90 deletions(-) diff --git a/app/backend/src/test/java/com/app/gamereview/service/GameServiceTest.java b/app/backend/src/test/java/com/app/gamereview/service/GameServiceTest.java index f833c15b..9041dc76 100644 --- a/app/backend/src/test/java/com/app/gamereview/service/GameServiceTest.java +++ b/app/backend/src/test/java/com/app/gamereview/service/GameServiceTest.java @@ -1,140 +1,606 @@ package com.app.gamereview.service; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.*; + +import com.app.gamereview.dto.request.game.*; +import com.app.gamereview.dto.response.game.GameDetailResponseDto; +import com.app.gamereview.dto.response.game.GetGameListResponseDto; +import com.app.gamereview.dto.response.tag.AddGameTagResponseDto; import com.app.gamereview.dto.response.tag.GetAllTagsOfGameResponseDto; +import com.app.gamereview.enums.TagType; import com.app.gamereview.exception.ResourceNotFoundException; -import com.app.gamereview.model.Game; -import com.app.gamereview.repository.GameRepository; -import com.app.gamereview.repository.TagRepository; -import com.app.gamereview.service.GameService; -import com.app.gamereview.service.TagService; - -import org.junit.jupiter.api.BeforeEach; +import com.app.gamereview.model.*; +import com.app.gamereview.repository.*; +import org.bson.Document; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.springframework.data.mongodb.core.MongoTemplate; - -import java.util.Optional; +import org.mockito.junit.jupiter.MockitoExtension; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; +import org.modelmapper.ModelMapper; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Query; +@ExtendWith(MockitoExtension.class) class GameServiceTest { @Mock private GameRepository gameRepository; + @Mock + private UserRepository userRepository; + @Mock private TagRepository tagRepository; @Mock - private TagService tagService; + private ForumRepository forumRepository; + + @Mock + private ProfileRepository profileRepository; - @InjectMocks - private GameService gameService; - @Mock private MongoTemplate mongoTemplate; - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } + @Mock + private ModelMapper modelMapper; + + @InjectMocks + private GameService gameService; + + @Test + void testCreateGame() { + // Arrange + CreateGameRequestDto requestDto = new CreateGameRequestDto(); + requestDto.setGameName("Test Game"); + requestDto.setDeveloper("developer"); + + List artstyles = new ArrayList<>(); + artstyles.add("art"); + + List other = new ArrayList<>(); + other.add("other"); + + requestDto.setArtStyles(artstyles); + requestDto.setOtherTags(other); + + + List playerTypes = new ArrayList<>(); + playerTypes.add("single"); + requestDto.setPlayerTypes(playerTypes); + List genres = new ArrayList<>(); + genres.add("mmorpg"); + requestDto.setGenre(genres); + List platforms = new ArrayList<>(); + platforms.add("ps3"); + requestDto.setPlatforms(platforms); + // Add other required fields in the requestDto - // @Test - // void testGetAllGames() { - // // Arrange - // GetGameListRequestDto filter = new GetGameListRequestDto(); - // Game game1 = new Game("Game 1", "Description 1", null, "SystemReq 1"); - // Game game2 = new Game("Game 2", "Description 2", null, "SystemReq 2"); - // List games = List.of(game1, game2); - // when(gameRepository.findAll()).thenReturn(games); + Tag developer = new Tag(); + developer.setTagType(TagType.DEVELOPER); - // // Act - // List response = gameService.getAllGames(filter); + Tag single = new Tag(); + single.setTagType(TagType.PLAYER_TYPE); + + Tag mmorpg = new Tag(); + mmorpg.setTagType(TagType.GENRE); + + Tag ps3 = new Tag(); + ps3.setTagType(TagType.PLATFORM); + + Tag art = new Tag(); + art.setTagType(TagType.ART_STYLE); + + Tag otherTag = new Tag(); + otherTag.setTagType(TagType.OTHER); + + Game gameToCreate = new Game(); + gameToCreate.setGameName("Test Game"); + + // Mock the repository calls + when(gameRepository.findByGameNameAndIsDeletedFalse("Test Game")).thenReturn(Optional.empty()); + when(tagRepository.findByIdAndIsDeletedFalse(eq("developer"))).thenReturn(Optional.of(developer)); + when(tagRepository.findByIdAndIsDeletedFalse(eq("single"))).thenReturn(Optional.of(single)); + when(tagRepository.findByIdAndIsDeletedFalse(eq("mmorpg"))).thenReturn(Optional.of(mmorpg)); + when(tagRepository.findByIdAndIsDeletedFalse(eq("ps3"))).thenReturn(Optional.of(ps3)); + when(tagRepository.findByIdAndIsDeletedFalse(eq("art"))).thenReturn(Optional.of(art)); + when(tagRepository.findByIdAndIsDeletedFalse(eq("other"))).thenReturn(Optional.of(otherTag)); + when(modelMapper.map(any(CreateGameRequestDto.class), eq(Game.class))).thenReturn(gameToCreate); + when(gameRepository.save(any(Game.class))).thenReturn(gameToCreate); + + // Act + Game createdGame = gameService.createGame(requestDto); + + // Assert + assertNotNull(createdGame); + assertEquals("Test Game", createdGame.getGameName()); + + // Verify repository method calls + verify(gameRepository, times(1)).findByGameNameAndIsDeletedFalse("Test Game"); + verify(tagRepository, times(6)).findByIdAndIsDeletedFalse(any()); + verify(gameRepository, times(1)).save(any(Game.class)); + verify(forumRepository, times(1)).save(any(Forum.class)); + } - // // Assert - // assertEquals(2, response.size()); - // assertEquals("Game 1", response.get(0).getGameName()); - // assertEquals("Game 2", response.get(1).getGameName()); - // } -/* @Test void testGetGameTags() { // Arrange String gameId = "123"; - Game game = new Game("Game 1", "Description 1", null, "SystemReq 1"); - game.setIsDeleted(false); - when(gameRepository.findById(gameId)).thenReturn(Optional.of(game)); + List artTags = Arrays.asList( + new Tag("Art1", TagType.ART_STYLE, "blue"), + new Tag("Art2", TagType.ART_STYLE, "red") + ); + + Tag developer = new Tag("developer", TagType.DEVELOPER, "blue"); + + Game game = new Game(); + game.addTag(developer); + game.addTag(artTags.get(0)); + game.addTag(artTags.get(1)); + + // Mock the repository call + when(gameRepository.findById(eq(gameId))).thenReturn(Optional.of(game)); // Act - GetAllTagsOfGameResponseDto response = gameService.getGameTags(gameId); + GetAllTagsOfGameResponseDto actualTags = gameService.getGameTags(gameId); + + List artTagIds = new ArrayList<>(); + + for (Tag t : artTags) { + artTagIds.add(t.getId()); + } // Assert - assertNotNull(response); - assertEquals(game.getPlayerTypes(), response.getPlayerTypes()); - assertEquals(game.getGenre(), response.getGenre()); - assertEquals(game.getProduction(), response.getProduction()); - assertEquals(game.getDuration(), response.getDuration()); - assertEquals(game.getPlatforms(), response.getPlatforms()); - assertEquals(game.getArtStyles(), response.getArtStyles()); - assertEquals(game.getDeveloper(), response.getDeveloper()); - assertEquals(game.getOtherTags(), response.getOtherTags()); - } - - // @Test - // void testAddGameTag() { - // // Arrange - // String gameId = "123"; - // String tagId = "456"; - // Game game = new Game("Game 1", "Description 1", null, "SystemReq 1"); - // Tag tag = new Tag("Tag 1", com.app.gamereview.enums.TagType.PLAYER_TYPE, "#c06123"); - // game.setIsDeleted(false); - // tag.setIsDeleted(false); - // tag.setId(tagId); - // AddGameTagRequestDto request = new AddGameTagRequestDto(gameId, tagId); - // when(gameRepository.findById(gameId)).thenReturn(Optional.of(game)); - // when(tagRepository.findById(tagId)).thenReturn(Optional.of(tag)); - // CreateTagRequestDto createRequest = new CreateTagRequestDto("Tag 1", com.app.gamereview.enums.TagType.PLAYER_TYPE, "#c06123"); - - // tagService.createTag(createRequest); - // // Act - // AddGameTagResponseDto response = gameService.addGameTag(request); - - // // Assert - // assertNotNull(response); - // assertEquals(gameId, response.getGameId()); - // assertEquals(tag, response.getAddedTag()); - // assertTrue(game.getPlayerTypes().contains(tag)); - // } - - @Test - void testGetGameDetail() { + assertEquals(artTagIds, game.getArtStyles()); + assertEquals(developer.getId(), game.getDeveloper()); + + + assertEquals(actualTags.getPlayerTypes(), new ArrayList<>()); + assertEquals(actualTags.getGenre(), new ArrayList<>()); + assertNull(actualTags.getProduction()); + assertNull(actualTags.getDuration()); + assertEquals(actualTags.getPlatforms(), new ArrayList<>()); + assertEquals(actualTags.getOtherTags(), new ArrayList<>()); + + // Verify repository method call + verify(gameRepository, times(1)).findById(eq(gameId)); + } + + @Test + void testAddGameTag() { + + AddGameTagRequestDto request = new AddGameTagRequestDto("game", "tag"); + + Game game = new Game(); + + Tag tag = new Tag(); + tag.setTagType(TagType.DEVELOPER); + + when(gameRepository.findById(eq("game"))).thenReturn(Optional.of(game)); + when(tagRepository.findById(eq("tag"))).thenReturn(Optional.of(tag)); + + AddGameTagResponseDto response = gameService.addGameTag(request); + + assertEquals(response.getGameId(), game.getId()); + assertEquals(response.getAddedTag(), tag); + + verify(gameRepository, times(1)).findById(eq("game")); + verify(gameRepository, times(1)).save(eq(game)); + verify(tagRepository, times(1)).findById(eq("tag")); + } + + @Test + void testRemoveGameTag() { + + RemoveGameTagRequestDto request = new RemoveGameTagRequestDto("game", "tag"); + + Game game = new Game(); + + Tag tag = new Tag(); + tag.setTagType(TagType.DEVELOPER); + + game.addTag(tag); + + when(gameRepository.findById(eq("game"))).thenReturn(Optional.of(game)); + when(tagRepository.findById(eq("tag"))).thenReturn(Optional.of(tag)); + + Boolean response = gameService.removeGameTag(request); + + assertTrue(response); + + verify(gameRepository, times(1)).findById(eq("game")); + verify(gameRepository, times(1)).save(eq(game)); + verify(tagRepository, times(1)).findById(eq("tag")); + + } + + @Test + void testRecommendationByGameId() { + // Arrange + String gameId = "game"; + Game baseGame = new Game(); // Set up your base game + baseGame.setId(gameId); + baseGame.setGameName("gameName"); + when(gameRepository.findByIdAndIsDeletedFalse(gameId)).thenReturn(Optional.of(baseGame)); + + // Mock the MongoDB template to return a list of similar games + when(mongoTemplate.find(any(Query.class), eq(Game.class))) + .thenReturn(Arrays.asList(createGame("Game1"), createGame("Game2"))); + + // Mock other dependencies as needed + + // Act + TreeSet result = gameService.recommendationByGameId(gameId); + + // Assert + assertNotNull(result); + // Add more specific assertions based on your expected behavior + + // Verify that certain methods were called with expected parameters + verify(gameRepository).findByIdAndIsDeletedFalse(gameId); + verify(mongoTemplate, times(2)).find(any(Query.class), eq(Game.class)); + } + + @Test + void testGetGameDetailWhenGameExists() { // Arrange String gameId = "123"; - Game game = new Game("Game 1", "Description 1", null, "SystemReq 1"); + Game game = new Game(); // Assuming you have a Game entity with necessary properties + game.setId(gameId); + + Tag tag = new Tag("tag", TagType.DEVELOPER, "blue"); + game.addTag(tag); + + GameDetailResponseDto expectedResponse = new GameDetailResponseDto(); + expectedResponse.setId(gameId); + when(gameRepository.findById(gameId)).thenReturn(Optional.of(game)); + when(tagRepository.findById(any())).thenReturn(Optional.of(tag)); // Assuming Tag entity is available + when(modelMapper.map(eq(game), eq(GameDetailResponseDto.class))).thenReturn(expectedResponse); + + // Act + GameDetailResponseDto actualResponse = gameService.getGameDetail(gameId); + + // Assert + assertEquals(expectedResponse, actualResponse); + verify(gameRepository, times(1)).findById(gameId); + verify(tagRepository, times(1)).findById(any()); + verify(modelMapper, times(1)).map(eq(game), eq(GameDetailResponseDto.class)); + } + + @Test + void testGetGameByNameWhenGameExists() { + // Arrange + String gameName = "TestGame"; + Game game = new Game(); // Assuming you have a Game entity with necessary properties + game.setGameName(gameName); + + Tag tag = new Tag("tag", TagType.DEVELOPER, "blue"); + game.addTag(tag); + + GameDetailResponseDto expectedResponse = new GameDetailResponseDto(); + expectedResponse.setGameName(gameName); + + when(gameRepository.findByGameNameAndIsDeletedFalse(gameName)).thenReturn(Optional.of(game)); + when(tagRepository.findById(any())).thenReturn(Optional.of(tag)); // Assuming Tag entity is available + when(modelMapper.map(eq(game), eq(GameDetailResponseDto.class))).thenReturn(expectedResponse); + + // Act + GameDetailResponseDto actualResponse = gameService.getGameByName(gameName); + + // Assert + assertEquals(expectedResponse, actualResponse); + verify(gameRepository, times(1)).findByGameNameAndIsDeletedFalse(gameName); + verify(tagRepository, times(1)).findById(any()); + verify(modelMapper, times(1)).map(eq(game), eq(GameDetailResponseDto.class)); + } + + @Test + void testGetRecommendedGames() { + // Arrange + String userEmail = "test@example.com"; + User user = new User(); // Set up your user + + String gameId = "game"; + Game baseGame = new Game(); // Set up your base game + baseGame.setId(gameId); + baseGame.setGameName("gameName"); + + Profile profile = new Profile(); // Set up your profile + profile.addGame(gameId); + + when(userRepository.findByEmailAndIsDeletedFalse(userEmail)).thenReturn(Optional.of(user)); + + when(profileRepository.findByUserIdAndIsDeletedFalse(user.getId())).thenReturn(Optional.of(profile)); + + when(gameRepository.findByIdAndIsDeletedFalse(gameId)).thenReturn(Optional.of(baseGame)); + + // Mock the MongoDB template to return a list of similar games + when(mongoTemplate.find(any(Query.class), eq(Game.class))) + .thenReturn(Arrays.asList(createGame("Game1"), createGame("Game2"))); + + + // Act + List result = gameService.getRecommendedGames(userEmail); + + // Assert + assertNotNull(result); + // Add more specific assertions based on your expected behavior + + // Verify that certain methods were called with expected parameters + verify(userRepository).findByEmailAndIsDeletedFalse(userEmail); + verify(profileRepository).findByUserIdAndIsDeletedFalse(user.getId()); + verify(gameRepository).findByIdAndIsDeletedFalse(gameId); + verify(mongoTemplate, times(2)).find(any(Query.class), eq(Game.class)); + } + + @Test + void testGetRecommendedGamesGuest() { + // Arrange + List expectedGames = Arrays.asList( + createGame("Game1"), + createGame("Game2"), + createGame("Game3") + // Add more games as needed + ); + + when(mongoTemplate.find(any(Query.class), eq(Game.class))).thenReturn(expectedGames); + + // Act + List actualGames = gameService.getRecommendedGames(); + + // Assert + assertEquals(expectedGames, actualGames); + } + + @Test + void testEditGameWhenGameExists() { + // Arrange + String gameId = "123"; + UpdateGameRequestDto updateRequest = new UpdateGameRequestDto(); + updateRequest.setGameName("UpdatedName"); + updateRequest.setGameDescription("UpdatedDescription"); + updateRequest.setGameIcon("UpdatedIcon"); + // Set other properties as needed + + Game existingGame = new Game(); + existingGame.setId(gameId); + + when(gameRepository.findByIdAndIsDeletedFalse(gameId)).thenReturn(Optional.of(existingGame)); + when(gameRepository.save(any())).thenReturn(existingGame); // Act - var response = gameService.getGameDetail(gameId); + Game updatedGame = gameService.editGame(gameId, updateRequest); // Assert - assertNotNull(response); - //assertEquals(game, response.getGame()); + assertEquals(updateRequest.getGameName(), updatedGame.getGameName()); + assertEquals(updateRequest.getGameDescription(), updatedGame.getGameDescription()); + assertEquals(updateRequest.getGameIcon(), updatedGame.getGameIcon()); + // Add additional assertions for other properties as needed } @Test - void testGetGameDetailNotFound() { + void testEditGameWhenGameNotExists() { + // Arrange + String gameId = "NonExistentGame"; + UpdateGameRequestDto updateRequest = new UpdateGameRequestDto(); + + when(gameRepository.findByIdAndIsDeletedFalse(gameId)).thenReturn(Optional.empty()); + + // Act and Assert + assertThrows(ResourceNotFoundException.class, () -> gameService.editGame(gameId, updateRequest)); + } + + @Test + void testDeleteGameWhenGameExists() { // Arrange String gameId = "123"; - when(gameRepository.findById(gameId)).thenReturn(Optional.empty()); + Game existingGame = new Game(); + existingGame.setId(gameId); + + when(gameRepository.findByIdAndIsDeletedFalse(gameId)).thenReturn(Optional.of(existingGame)); + when(gameRepository.save(any())).thenReturn(existingGame); - // Act & Assert - assertThrows(ResourceNotFoundException.class, () -> gameService.getGameDetail(gameId)); + // Act + boolean result = gameService.deleteGame(gameId); + + // Assert + assertTrue(result); + assertTrue(existingGame.getIsDeleted()); } - */ -} + @Test + void testDeleteGameWhenGameNotExists() { + // Arrange + String gameId = "NonExistentGame"; + when(gameRepository.findByIdAndIsDeletedFalse(gameId)).thenReturn(Optional.empty()); + + // Act and Assert + assertThrows(ResourceNotFoundException.class, () -> gameService.deleteGame(gameId)); + } + + @Test + void testChangePromotionStatusOfGameWhenGameExists() { + // Arrange + String gameId = "123"; + Game existingGame = new Game(); + existingGame.setId(gameId); + existingGame.setIsPromoted(false); + + when(gameRepository.findByIdAndIsDeletedFalse(gameId)).thenReturn(Optional.of(existingGame)); + when(gameRepository.save(any())).thenReturn(existingGame); + + // Act + Game updatedGame = gameService.changePromotionStatusOfGame(gameId); + + // Assert + assertTrue(updatedGame.getIsPromoted()); + verify(gameRepository, times(1)).save(existingGame); + } + + @Test + void testChangePromotionStatusOfGameWhenGameNotExists() { + // Arrange + String gameId = "NonExistentGame"; + when(gameRepository.findByIdAndIsDeletedFalse(gameId)).thenReturn(Optional.empty()); + + // Act and Assert + assertThrows(ResourceNotFoundException.class, () -> gameService.changePromotionStatusOfGame(gameId)); + verify(gameRepository, never()).save(any()); + } + + @Test + void testCalculateSimilarityScore() { + // Arrange + Game basedGame = new Game(); + basedGame.setId("1"); + + List mockTags = Arrays.asList( + createTag("tag1", TagType.PRODUCTION), + createTag("tag2", TagType.GENRE), + createTag("tag3", TagType.GENRE), + createTag("tag4", TagType.PRODUCTION) + ); + + basedGame.addTag(mockTags.get(0)); + basedGame.addTag(mockTags.get(1)); + basedGame.addTag(mockTags.get(2)); + basedGame.addTag(mockTags.get(3)); + + Game candidateGame = new Game(); + candidateGame.setId("2"); + + candidateGame.addTag(mockTags.get(1)); + candidateGame.addTag(mockTags.get(2)); + candidateGame.addTag(mockTags.get(3)); + + when(tagRepository.findByIdAndIsDeletedFalse("tag2")).thenReturn(Optional.of(mockTags.get(1))); + when(tagRepository.findByIdAndIsDeletedFalse("tag3")).thenReturn(Optional.of(mockTags.get(2))); + when(tagRepository.findByIdAndIsDeletedFalse("tag4")).thenReturn(Optional.of(mockTags.get(3))); + + // Act + int similarityScore = gameService.calculateSimilarityScore(basedGame, candidateGame); + + // Assert + assertEquals(13, similarityScore); + } + + @Test + void testGetGames() { + // Arrange + GetGameListRequestDto filter = new GetGameListRequestDto(); + filter.setGameName("TestGame"); + filter.setFindDeleted(false); + + List playerTypes = new ArrayList<>(); + playerTypes.add("playerType"); + + List genres = new ArrayList<>(); + genres.add("genre"); + + List platforms = new ArrayList<>(); + platforms.add("platform"); + + List artstyles = new ArrayList<>(); + artstyles.add("art"); + + filter.setPlayerTypes(playerTypes); + filter.setGenre(genres); + filter.setProduction("aaa"); + filter.setPlatform(platforms); + filter.setArtStyle(artstyles); + + Game filtered1 = new Game(); + + Game filtered2 = new Game(); + + List filteredGames = Arrays.asList( + filtered1, + filtered2 + ); + + when(mongoTemplate.aggregate(any(Aggregation.class), eq("Game"), eq(Game.class))) + .thenReturn(new AggregationResults<>(Collections.emptyList(), new Document())); +// when(mongoTemplate.aggregate(any(Aggregation.class), eq("Game"), eq(Game.class)).getMappedResults()).thenReturn(promotedGames); + when(mongoTemplate.find(any(Query.class), eq(Game.class))).thenReturn(filteredGames); + + // Act + List result = gameService.getGames(filter); + + // Assert + assertEquals(2, result.size()); // 2 promoted games + 2 filtered games + verify(mongoTemplate, times(1)).aggregate(any(Aggregation.class), eq("Game"), eq(Game.class)); + verify(mongoTemplate, times(1)).find(any(Query.class), eq(Game.class)); + } + + @Test + void testGetAllGames() { + // Arrange + GetGameListRequestDto filter = new GetGameListRequestDto(); + filter.setGameName("TestGame"); + filter.setFindDeleted(false); + + List playerTypes = new ArrayList<>(); + playerTypes.add("playerType"); + + List genres = new ArrayList<>(); + genres.add("genre"); + + List platforms = new ArrayList<>(); + platforms.add("platform"); + + List artstyles = new ArrayList<>(); + artstyles.add("art"); + + filter.setPlayerTypes(playerTypes); + filter.setGenre(genres); + filter.setProduction("aaa"); + filter.setPlatform(platforms); + filter.setArtStyle(artstyles); + + Game filtered1 = new Game(); + + Game filtered2 = new Game(); + + List filteredGames = Arrays.asList( + filtered1, + filtered2 + ); + + when(mongoTemplate.aggregate(any(Aggregation.class), eq("Game"), eq(Game.class))) + .thenReturn(new AggregationResults<>(Collections.emptyList(), new Document())); +// when(mongoTemplate.aggregate(any(Aggregation.class), eq("Game"), eq(Game.class)).getMappedResults()).thenReturn(promotedGames); + when(mongoTemplate.find(any(Query.class), eq(Game.class))).thenReturn(filteredGames); + + // Act + List result = gameService.getAllGames(filter); + + // Assert + assertEquals(2, result.size()); // 2 promoted games + 2 filtered games + verify(mongoTemplate, times(1)).aggregate(any(Aggregation.class), eq("Game"), eq(Game.class)); + verify(mongoTemplate, times(1)).find(any(Query.class), eq(Game.class)); + } + + private Game createGame(String gameName) { + Game game = new Game(); + game.setGameName(gameName); + // Set other properties as needed + return game; + } + + private Tag createTag(String tagId, TagType type) { + Tag tag = new Tag(); + tag.setId(tagId); + tag.setTagType(type); + // Set other properties as needed + return tag; + } +} \ No newline at end of file From ac299e61e3e9d358a283b6cf0190086faaa40f01 Mon Sep 17 00:00:00 2001 From: Deniz Unal Date: Sun, 24 Dec 2023 11:38:29 +0300 Subject: [PATCH 27/43] added Fragment Selector support --- .../annotation/dto/request/SelectorDto.java | 5 ++++ .../annotation/model/FragmentSelector.java | 27 +++++++++++++++++++ .../java/com/app/annotation/model/Target.java | 16 ++++++----- .../annotation/service/AnnotationService.java | 16 +++-------- 4 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 app/annotation/annotation/src/main/java/com/app/annotation/model/FragmentSelector.java diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/dto/request/SelectorDto.java b/app/annotation/annotation/src/main/java/com/app/annotation/dto/request/SelectorDto.java index 39854f1b..31de5160 100644 --- a/app/annotation/annotation/src/main/java/com/app/annotation/dto/request/SelectorDto.java +++ b/app/annotation/annotation/src/main/java/com/app/annotation/dto/request/SelectorDto.java @@ -27,4 +27,9 @@ public class SelectorDto { @Positive private Integer end; + + private String value; + + @Pattern(regexp = "^(https?|ftp)://[^\\s/$.?#].\\S*$", message = "Invalid URL") + private String conformsTo; } diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/model/FragmentSelector.java b/app/annotation/annotation/src/main/java/com/app/annotation/model/FragmentSelector.java new file mode 100644 index 00000000..178c9340 --- /dev/null +++ b/app/annotation/annotation/src/main/java/com/app/annotation/model/FragmentSelector.java @@ -0,0 +1,27 @@ +package com.app.annotation.model; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +@NoArgsConstructor +@Getter +@Setter +public class FragmentSelector extends Selector { + + private String value; + + private String conformsTo; + + @Override + public Map toJSON() { + Map json = new HashMap<>(); + if (this.type != null) json.put("type", this.type); + if (this.value != null) json.put("value", this.value); + if (this.conformsTo != null) json.put("conformsTo", this.conformsTo); + return json; + } +} diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/model/Target.java b/app/annotation/annotation/src/main/java/com/app/annotation/model/Target.java index 936c6347..0a0591f5 100644 --- a/app/annotation/annotation/src/main/java/com/app/annotation/model/Target.java +++ b/app/annotation/annotation/src/main/java/com/app/annotation/model/Target.java @@ -32,13 +32,17 @@ public Map toJSON() { if (this.format != null) json.put("format", this.format); if (this.textDirection != null) json.put("textDirection", this.textDirection); if (this.source != null) json.put("source", this.source); - if (this.selector != null) { - List> selectorList = new ArrayList<>(); - - for (Selector s : this.selector) { - selectorList.add(s.toJSON()); + if (this.selector != null && !this.selector.isEmpty()) { + if (this.selector.size() > 1) { + List> selectorList = new ArrayList<>(); + + for (Selector s : this.selector) { + selectorList.add(s.toJSON()); + } + json.put("selector", selectorList); + } else { + json.put("selector", this.selector.get(0).toJSON()); } - json.put("selector", selectorList); } return json; } diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java b/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java index efc3179b..8f0abe97 100644 --- a/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java +++ b/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java @@ -40,18 +40,10 @@ public Map createAnnotation(CreateAnnotationRequestDto dto) { List selectorList = new ArrayList<>(); for (SelectorDto s : dto.getTarget().getSelector()) { - if (s.getExact() != null) { - System.out.println(s.getType()); - System.out.println(s.getExact()); - } - if (s.getStart() != null) { - System.out.println(s.getType()); - System.out.println(s.getStart()); - } - if (s.getType().equals("TextQuoteSelector")) { - selectorList.add(modelMapper.map(s, TextQuoteSelector.class)); - } else if (s.getType().equals("TextPositionSelector")) { - selectorList.add(modelMapper.map(s, TextPositionSelector.class)); + switch (s.getType()) { + case "TextQuoteSelector" -> selectorList.add(modelMapper.map(s, TextQuoteSelector.class)); + case "TextPositionSelector" -> selectorList.add(modelMapper.map(s, TextPositionSelector.class)); + case "FragmentSelector" -> selectorList.add(modelMapper.map(s, FragmentSelector.class)); } //TODO add more selectors if implemented in the future } From 8f7fe2bf9025cc2d332614f7838fd52d5d4de376 Mon Sep 17 00:00:00 2001 From: Deniz Unal Date: Sun, 24 Dec 2023 11:56:15 +0300 Subject: [PATCH 28/43] added get-image-annotations endpoint --- .../controller/AnnotationController.java | 9 ++++++ .../annotation/service/AnnotationService.java | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/controller/AnnotationController.java b/app/annotation/annotation/src/main/java/com/app/annotation/controller/AnnotationController.java index 5a87776a..803cd474 100644 --- a/app/annotation/annotation/src/main/java/com/app/annotation/controller/AnnotationController.java +++ b/app/annotation/annotation/src/main/java/com/app/annotation/controller/AnnotationController.java @@ -50,4 +50,13 @@ public ResponseEntity>> getAnnotation(@RequestParam Str } return ResponseEntity.ok(annotations); } + + @GetMapping("/get-image-annotations") + public ResponseEntity>> getImageAnnotations(@RequestParam String source) { + List> annotations = annotationService.getImageAnnotations(source); + if(annotations == null || annotations.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(annotations); + } } diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java b/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java index 8f0abe97..63ec1844 100644 --- a/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java +++ b/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java @@ -93,8 +93,39 @@ public List> getAnnotations(String source) { List annotations = annotationRepository.findAllByTargetSource(source); List> jsonAnnotations = new ArrayList<>(); for (Annotation a : annotations) { + + List selectors = a.getTarget().getSelector(); + + boolean haveTextSelectors = true; + + for (Selector s : selectors) { + if (!s.getType().equals("TextQuoteSelector") && !s.getType().equals("TextPositionSelector")) { + haveTextSelectors = false; + break; + } + + // TODO change logic as more selectors are implemented in the future + } + + if (!haveTextSelectors) { + continue; + } + jsonAnnotations.add(a.toJSON()); } return jsonAnnotations; } + + public List> getImageAnnotations(String source) { + List annotations = annotationRepository.findAllByTargetSource(source); + List> jsonAnnotations = new ArrayList<>(); + for (Annotation a : annotations) { + + if (a.getTarget().getSelector().size() == 1 && a.getTarget().getSelector().get(0).getType().equals("FragmentSelector")) { + jsonAnnotations.add(a.toJSON()); + } + // TODO change logic if more image selectors are implemented in the future + } + return jsonAnnotations; + } } From df92060be5b48b47bd7db535400394e23b0f5718 Mon Sep 17 00:00:00 2001 From: Can Date: Sun, 24 Dec 2023 13:34:55 +0300 Subject: [PATCH 29/43] enhancing the post service unit test --- .../gamereview/service/PostServiceTest.java | 338 +++++++++++++++++- 1 file changed, 335 insertions(+), 3 deletions(-) diff --git a/app/backend/src/test/java/com/app/gamereview/service/PostServiceTest.java b/app/backend/src/test/java/com/app/gamereview/service/PostServiceTest.java index ae09e2fb..0fc7cffe 100644 --- a/app/backend/src/test/java/com/app/gamereview/service/PostServiceTest.java +++ b/app/backend/src/test/java/com/app/gamereview/service/PostServiceTest.java @@ -1,28 +1,40 @@ package com.app.gamereview.service; +import com.app.gamereview.dto.request.home.HomePagePostsFilterRequestDto; import com.app.gamereview.dto.request.notification.CreateNotificationRequestDto; import com.app.gamereview.dto.request.post.CreatePostRequestDto; import com.app.gamereview.dto.request.post.EditPostRequestDto; import com.app.gamereview.dto.request.post.GetPostListFilterRequestDto; +import com.app.gamereview.dto.response.comment.GetPostCommentsResponseDto; +import com.app.gamereview.dto.response.home.HomePagePostResponseDto; import com.app.gamereview.dto.response.post.GetPostDetailResponseDto; import com.app.gamereview.dto.response.post.GetPostListResponseDto; +import com.app.gamereview.enums.ForumType; +import com.app.gamereview.enums.SortDirection; +import com.app.gamereview.enums.SortType; +import com.app.gamereview.enums.VoteChoice; import com.app.gamereview.exception.ResourceNotFoundException; import com.app.gamereview.model.*; import com.app.gamereview.repository.*; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.modelmapper.ModelMapper; import org.springframework.data.mongodb.core.MongoTemplate; - -import org.mockito.*; import org.springframework.data.mongodb.core.query.Query; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import static org.bson.assertions.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; @@ -46,6 +58,12 @@ public class PostServiceTest { @Mock private CommentRepository commentRepository; + @Mock + private GameRepository gameRepository; + + @Mock + private GroupRepository groupRepository; + @Mock private NotificationRepository notificationRepository; @@ -142,7 +160,7 @@ void testGetPostById_NotFound() { when(userRepository.findByEmailAndIsDeletedFalse(anyString())).thenReturn(Optional.of(loggedInUser)); // Act and Assert - Assertions.assertThrows(ResourceNotFoundException.class, + assertThrows(ResourceNotFoundException.class, () -> postService.getPostById(postId, email), "Should throw ResourceNotFoundException when post is not found"); @@ -253,4 +271,318 @@ void testGetUserPostList_Success() { Assertions.assertNotNull(result); verify(mongoTemplate, times(1)).find(any(Query.class), eq(Post.class)); } + + @Test + void testGetCommentList_Success() { + // Arrange + String postId = "postId"; + String userId = "userId"; + User user = new User(); + user.setId(userId); + + // Mock data + Post post = new Post(); + post.setId(postId); + post.setForum("forumId"); + + Forum forum = new Forum(); + forum.setId("forumId"); + forum.setBannedUsers(List.of("")); + + Comment comment1 = new Comment(); + comment1.setId("commentId1"); + comment1.setCreatedAt(LocalDateTime.now()); + comment1.setCommenter("userId"); + comment1.setParentComment(null); + comment1.setCommentContent(""); + comment1.setPost(postId); + comment1.setLastEditedAt(LocalDateTime.now()); + comment1.setIsDeleted(false); + comment1.setOverallVote(5); + comment1.setVoteCount(10); + + + Comment comment2 = new Comment(); + comment2.setId("commentId2"); + comment2.setCreatedAt(LocalDateTime.now()); + comment2.setCommenter("userId"); + comment2.setCommentContent(""); + comment2.setPost(postId); + comment2.setLastEditedAt(LocalDateTime.now()); + comment2.setIsDeleted(false); + comment2.setOverallVote(5); + comment2.setVoteCount(10); + comment2.setParentComment("commentId1"); + + Vote vote = new Vote(); + vote.setId("voteId"); + vote.setChoice(VoteChoice.UPVOTE); + + List comments = List.of(comment1, comment2); + + when(postRepository.findById(anyString())).thenReturn(Optional.of(post)); + when(forumRepository.findById(anyString())).thenReturn(Optional.of(forum)); + when(commentRepository.findByPost(anyString())).thenReturn(comments); + when(userRepository.findByIdAndIsDeletedFalse(any(String.class))).thenReturn(Optional.of(user)); + when(voteRepository.findByTypeIdAndVotedBy(any(String.class), any(String.class))).thenReturn(Optional.of(vote)); + + List result = postService.getCommentList(postId, user); + + assertNotNull(result); + assertEquals(1, result.size()); // Assuming only one top-level comment in the test data + assertEquals(comment1.getId(), result.get(0).getId()); + assertEquals(1, result.get(0).getReplies().size()); // Assuming one reply in the test data + assertEquals(comment2.getId(), result.get(0).getReplies().get(0).getId()); + + verify(postRepository, times(1)).findById(anyString()); + verify(forumRepository, times(1)).findById(anyString()); + verify(commentRepository, times(1)).findByPost(anyString()); + } + + @Test + void testGetHomePagePostsOfUser_Success() { + HomePagePostsFilterRequestDto filter = new HomePagePostsFilterRequestDto(); + User user = new User(); + user.setId("userId"); + + Profile profile = new Profile(); + profile.setUserId(user.getId()); + profile.setGames(Collections.singletonList("gameId")); + + Game game = new Game(); + game.setId("gameId"); + game.setForum("gameForumId"); + + Group group = new Group(); + group.setId("groupId"); + group.setForumId("groupForumId"); + + Forum forum = new Forum(); + forum.setId("forumId"); + forum.setType(ForumType.GAME); + forum.setParent("gameId"); + + Post post = new Post(); + post.setId("postId"); + post.setForum("gameForumId"); + post.setTags(List.of("tagId")); + + Post extraPost = new Post(); + extraPost.setId("extraPostId"); + extraPost.setForum("extraGameForumId"); + extraPost.setTags(List.of("tagId")); + + Tag randomTag = new Tag(); + randomTag.setId("tagId"); + + HomePagePostResponseDto responseDto = new HomePagePostResponseDto(); + responseDto.setTags(List.of(randomTag)); + + when(profileRepository.findByUserIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(profile)); + when(gameRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(game)); + when(groupRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(group)); + when(postRepository.findByForumAndIsDeletedFalse(anyString())).thenReturn(Collections.singletonList(post)); + when(forumRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(forum)); + when(mongoTemplate.find(any(Query.class), eq(Group.class))).thenReturn(Collections.singletonList(group)); + when(mongoTemplate.find(any(Query.class), eq(Post.class))).thenReturn(Collections.singletonList(extraPost)); + when(modelMapper.map(any(Post.class), eq(HomePagePostResponseDto.class))).thenReturn(responseDto); + + List result = postService.getHomePagePostsOfUser(filter, user); + + assertNotNull(result); + assertEquals(3, result.size()); + } + + @Test + void testGetHomePagePostsOfUser_Success_Group_Forum() { + HomePagePostsFilterRequestDto filter = new HomePagePostsFilterRequestDto(); + filter.setSortBy(SortType.OVERALL_VOTE.name()); + filter.setSortDirection(SortDirection.ASCENDING.name()); + + User user = new User(); + user.setId("userId"); + + Profile profile = new Profile(); + profile.setUserId(user.getId()); + profile.setGames(Collections.singletonList("gameId")); + + Game game = new Game(); + game.setId("gameId"); + game.setForum("gameForumId"); + + Group group = new Group(); + group.setId("groupId"); + group.setForumId("groupForumId"); + + Forum forum = new Forum(); + forum.setId("forumId"); + forum.setType(ForumType.GAME); + forum.setParent("gameId"); + + Post post = new Post(); + post.setId("postId"); + post.setForum("gameForumId"); + post.setTags(List.of("tagId")); + + Post extraPost = new Post(); + extraPost.setId("extraPostId"); + extraPost.setForum("extraGameForumId"); + extraPost.setTags(List.of("tagId")); + + Tag randomTag = new Tag(); + randomTag.setId("tagId"); + + HomePagePostResponseDto responseDto = new HomePagePostResponseDto(); + responseDto.setTags(List.of(randomTag)); + + when(profileRepository.findByUserIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(profile)); + when(gameRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(game)); + when(groupRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(group)); + when(postRepository.findByForumAndIsDeletedFalse(anyString())).thenReturn(Collections.singletonList(post)); + when(forumRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(forum)); + when(mongoTemplate.find(any(Query.class), eq(Group.class))).thenReturn(Collections.singletonList(group)); + when(mongoTemplate.find(any(Query.class), eq(Post.class))).thenReturn(Collections.singletonList(extraPost)); + when(modelMapper.map(any(Post.class), eq(HomePagePostResponseDto.class))).thenReturn(responseDto); + + List result = postService.getHomePagePostsOfUser(filter, user); + + assertNotNull(result); + assertEquals(3, result.size()); + } + + @Test + void testGetHomePagePostsOfGuest_Success() { + HomePagePostsFilterRequestDto filter = new HomePagePostsFilterRequestDto(); + User user = new User(); + user.setId("userId"); + + Profile profile = new Profile(); + profile.setUserId(user.getId()); + profile.setGames(Collections.singletonList("gameId")); + + Game game = new Game(); + game.setId("gameId"); + game.setForum("gameForumId"); + + Group group = new Group(); + group.setId("groupId"); + group.setForumId("groupForumId"); + + Forum forum = new Forum(); + forum.setId("forumId"); + forum.setType(ForumType.GAME); + forum.setParent("gameId"); + + Post post = new Post(); + post.setId("postId"); + post.setForum("gameForumId"); + post.setTags(List.of("tagId")); + + Post extraPost = new Post(); + extraPost.setId("extraPostId"); + extraPost.setForum("extraGameForumId"); + extraPost.setTags(List.of("tagId")); + + Tag randomTag = new Tag(); + randomTag.setId("tagId"); + + HomePagePostResponseDto responseDto = new HomePagePostResponseDto(); + responseDto.setTags(List.of(randomTag)); + + when(profileRepository.findByUserIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(profile)); + when(gameRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(game)); + when(groupRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(group)); + when(postRepository.findByForumAndIsDeletedFalse(anyString())).thenReturn(Collections.singletonList(post)); + when(forumRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(forum)); + when(mongoTemplate.find(any(Query.class), eq(Group.class))).thenReturn(Collections.singletonList(group)); + when(mongoTemplate.find(any(Query.class), eq(Post.class))).thenReturn(Collections.singletonList(extraPost)); + when(mongoTemplate.find(any(Query.class), eq(Forum.class))).thenReturn(Collections.singletonList(forum)); + when(modelMapper.map(any(Post.class), eq(HomePagePostResponseDto.class))).thenReturn(responseDto); + + List result = postService.getHomePagePostsOfGuest(filter); + + assertNotNull(result); + assertEquals(1, result.size()); + } + + @Test + void testGetHomePagePostsOfGuest_Success_Group_Forum() { + HomePagePostsFilterRequestDto filter = new HomePagePostsFilterRequestDto(); + filter.setSortBy(SortType.OVERALL_VOTE.name()); + filter.setSortDirection(SortDirection.ASCENDING.name()); + + User user = new User(); + user.setId("userId"); + + Profile profile = new Profile(); + profile.setUserId(user.getId()); + profile.setGames(Collections.singletonList("gameId")); + + Game game = new Game(); + game.setId("gameId"); + game.setForum("gameForumId"); + + Group group = new Group(); + group.setId("groupId"); + group.setForumId("groupForumId"); + + Forum forum = new Forum(); + forum.setId("forumId"); + forum.setType(ForumType.GROUP); + forum.setParent("gameId"); + + Post post = new Post(); + post.setId("postId"); + post.setForum("gameForumId"); + post.setTags(List.of("tagId")); + + Post extraPost = new Post(); + extraPost.setId("extraPostId"); + extraPost.setForum("extraGameForumId"); + extraPost.setTags(List.of("tagId")); + + Tag randomTag = new Tag(); + randomTag.setId("tagId"); + + HomePagePostResponseDto responseDto = new HomePagePostResponseDto(); + responseDto.setTags(List.of(randomTag)); + + when(profileRepository.findByUserIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(profile)); + when(gameRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(game)); + when(groupRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(group)); + when(postRepository.findByForumAndIsDeletedFalse(anyString())).thenReturn(Collections.singletonList(post)); + when(forumRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(forum)); + when(mongoTemplate.find(any(Query.class), eq(Group.class))).thenReturn(Collections.singletonList(group)); + when(mongoTemplate.find(any(Query.class), eq(Post.class))).thenReturn(Collections.singletonList(extraPost)); + when(mongoTemplate.find(any(Query.class), eq(Forum.class))).thenReturn(Collections.singletonList(forum)); + when(modelMapper.map(any(Post.class), eq(HomePagePostResponseDto.class))).thenReturn(responseDto); + + List result = postService.getHomePagePostsOfGuest(filter); + + assertNotNull(result); + assertEquals(1, result.size()); + } + + @Test + void testGetHomepagePosts_UserNotFound() { + HomePagePostsFilterRequestDto filter = new HomePagePostsFilterRequestDto(); + String email = "nonexistentuser@example.com"; + + when(userRepository.findByEmailAndIsDeletedFalse(anyString())).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, + () -> postService.getHomepagePosts(filter, email), + "Should throw ResourceNotFoundException when user is not found"); + + verify(userRepository, times(1)).findByEmailAndIsDeletedFalse(anyString()); + } + + @Test + void testGetHomepagePosts_Guest() { + HomePagePostsFilterRequestDto filter = new HomePagePostsFilterRequestDto(); + + List result = postService.getHomepagePosts(filter, null); + + verifyNoInteractions(userRepository); + } } From fdad69f73876b51b910c6b3d851d8ca8dea7763f Mon Sep 17 00:00:00 2001 From: zeynep-baydemir Date: Sun, 24 Dec 2023 13:57:23 +0300 Subject: [PATCH 30/43] improved group tests --- .../gamereview/service/GroupServiceTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/app/backend/src/test/java/com/app/gamereview/service/GroupServiceTest.java b/app/backend/src/test/java/com/app/gamereview/service/GroupServiceTest.java index 10c14986..9d2a9dcb 100644 --- a/app/backend/src/test/java/com/app/gamereview/service/GroupServiceTest.java +++ b/app/backend/src/test/java/com/app/gamereview/service/GroupServiceTest.java @@ -913,6 +913,58 @@ void testAddModeratorNotAllowed() { assertThrows(BadRequestException.class, () -> groupService.addModerator(groupId, userId, user)); verify(groupRepository, never()).save(any()); } + @Test + void testRemoveModeratorNotFound() { + String groupId = "groupId"; + String userId = "userId"; + String notGroupId = "groupId2"; + + Group group = new Group(); + group.setId(groupId); + group.addModerator(userId); + + User user = new User(); + user.setId(userId); + + when(groupRepository.findById(groupId)).thenReturn(Optional.of(group)); + + assertThrows(ResourceNotFoundException.class, () -> groupService.removeModerator(notGroupId, userId)); + verify(groupRepository, never()).save(any()); + } + + @Test + void testRemoveModerator() { + // Arrange + String groupId = "groupId"; + String moderatorId = "moderatorId"; + + Group group = new Group(); + group.setId(groupId); + group.addModerator(moderatorId); + + when(groupRepository.findById(groupId)).thenReturn(Optional.of(group)); + + boolean result = groupService.removeModerator(groupId, moderatorId); + + assertTrue(result); + assertFalse(group.getModerators().contains(moderatorId)); + verify(groupRepository, times(1)).save(group); + } + + @Test + void testReviewApplicationNotFound() { + String applicationId = "nonexistent"; + User user = new User(); + user.setId("moderator1"); + + GroupApplicationReviewDto reviewDto = new GroupApplicationReviewDto(); + reviewDto.setResult(GroupApplicationReviewResult.APPROVE.name()); + + when(groupApplicationRepository.findById(applicationId)).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, () -> groupService.reviewApplication(applicationId, user, reviewDto)); + verify(groupRepository, never()).save(any()); + } From 41ba1052a437e0a7968794883ebec1c2d0b3c9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Kabaday=C4=B1?= Date: Sun, 24 Dec 2023 14:08:50 +0300 Subject: [PATCH 31/43] duplicate import is removed --- .../src/Pages/ForumPost/ForumPost.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/frontend/src/Pages/ForumPost/ForumPost.tsx b/app/frontend/src/Pages/ForumPost/ForumPost.tsx index 01a7c0db..b4274591 100644 --- a/app/frontend/src/Pages/ForumPost/ForumPost.tsx +++ b/app/frontend/src/Pages/ForumPost/ForumPost.tsx @@ -19,7 +19,6 @@ import { CommentOutlined, WarningOutlined, ArrowLeftOutlined, - VerifiedOutlined, CheckOutlined, } from "@ant-design/icons"; import clsx from "clsx"; @@ -43,7 +42,6 @@ import { updateAnnotation, } from "../../Services/annotation.ts"; import { NotificationUtil } from "../../Library/utils/notification.ts"; -import { handleError } from "../../Library/utils/handleError.ts"; import CharacterDetails from "../../Components/Character/CharacterDetails.tsx"; function ForumPost() { @@ -205,15 +203,17 @@ function ForumPost() { )} {post.achievement && (
-
- Achievement: - - {user?.role === "ADMIN" && ( - - grant()} /> - - )} + Achievement: + + {user?.role === "ADMIN" && ( + + grant()} /> + + )}
)} From 82afb71db58e11ebae2c1d3894441d5c128ad67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Kabaday=C4=B1?= Date: Sun, 24 Dec 2023 14:09:04 +0300 Subject: [PATCH 32/43] console logs are removed --- .../Achievement/Achievement/Achievement.tsx | 17 +-------- .../Components/Character/CharacterDetails.tsx | 1 - .../GameDetails/Summary/Summary.tsx | 11 ++---- .../src/Components/Groups/PrivateGroup.tsx | 5 +-- .../LastActivities/LastActivities.tsx | 1 - .../src/Components/Profile/EditProfile.tsx | 1 - .../Admin/Game/UpdateGame/UpdateGame.tsx | 2 - app/frontend/src/Pages/Group/Group.tsx | 28 +++++++------- app/frontend/src/Pages/Groups/Groups.tsx | 2 - .../ReviewApplication/ReviewApplication.tsx | 35 +++++++---------- app/frontend/src/Services/Register.ts | 1 - app/frontend/src/Services/applications.ts | 38 +++++++++++-------- 12 files changed, 58 insertions(+), 84 deletions(-) diff --git a/app/frontend/src/Components/Achievement/Achievement/Achievement.tsx b/app/frontend/src/Components/Achievement/Achievement/Achievement.tsx index 6050c8bc..4e9bef59 100644 --- a/app/frontend/src/Components/Achievement/Achievement/Achievement.tsx +++ b/app/frontend/src/Components/Achievement/Achievement/Achievement.tsx @@ -16,13 +16,10 @@ function Achievement({ props }: { props: any }) { function handleClick() { if (props.onClick) { props.onClick(); - console.log("props.onClick: "); } else { if (!props.game) { return; - } - console.log("gameId: ", props.game); const gameId = props.game; navigate(`/game/detail/${gameId}`); } @@ -44,20 +41,10 @@ function Achievement({ props }: { props: any }) { className={styles.icon} > -
-

- {props.title} -

-

- {props.description} -

+

{props.title}

+

{props.description}

-
); diff --git a/app/frontend/src/Components/Character/CharacterDetails.tsx b/app/frontend/src/Components/Character/CharacterDetails.tsx index 43a0a556..47208846 100644 --- a/app/frontend/src/Components/Character/CharacterDetails.tsx +++ b/app/frontend/src/Components/Character/CharacterDetails.tsx @@ -42,7 +42,6 @@ function CharacterDetails({ // Access colors from the palette if (palette) { setPalette(palette); - console.log(palette); } }) .catch((error) => { diff --git a/app/frontend/src/Components/GameDetails/Summary/Summary.tsx b/app/frontend/src/Components/GameDetails/Summary/Summary.tsx index 8a8318b1..b98c2668 100644 --- a/app/frontend/src/Components/GameDetails/Summary/Summary.tsx +++ b/app/frontend/src/Components/GameDetails/Summary/Summary.tsx @@ -45,9 +45,7 @@ function Summary({ game }: { game: any }) { import.meta.env.VITE_APP_ANNOTATION_API_URL }/annotation/get-source-annotations?source=${game.id}` ) - .then(function (annotations) { - console.log(annotations); - }) + .then(function (annotations) {}) .catch((error) => { if (error instanceof SyntaxError) { return; @@ -55,8 +53,6 @@ function Summary({ game }: { game: any }) { NotificationUtil.error("Error occurred while retrieving annotations"); }); - console.log(game); - r.on("createAnnotation", async (annotation: any, _overrideId) => { try { annotation.target = { ...annotation.target, source: game.id }; @@ -182,8 +178,9 @@ function Summary({ game }: { game: any }) { (achievement: any) => !achievement.isDeleted && (
-
- +
+ +
) )} diff --git a/app/frontend/src/Components/Groups/PrivateGroup.tsx b/app/frontend/src/Components/Groups/PrivateGroup.tsx index 191013fd..6ec5b31b 100644 --- a/app/frontend/src/Components/Groups/PrivateGroup.tsx +++ b/app/frontend/src/Components/Groups/PrivateGroup.tsx @@ -16,11 +16,10 @@ function PrivateGroup({ group }: { group: any }) { if (response) { NotificationUtil.success("You successfully applied to the group"); } - } catch (error:any) { + } catch (error: any) { NotificationUtil.error(error.response.data); - console.log(error); } - } + }; return (
diff --git a/app/frontend/src/Components/LastActivities/LastActivities.tsx b/app/frontend/src/Components/LastActivities/LastActivities.tsx index c929ef72..334dc7a5 100644 --- a/app/frontend/src/Components/LastActivities/LastActivities.tsx +++ b/app/frontend/src/Components/LastActivities/LastActivities.tsx @@ -12,7 +12,6 @@ import { Link } from "react-router-dom"; function LastActivities() { const activities = useQuery(["activites"], () => getActivities()); - console.log(activities.data); return (
{!activities.data ? ( diff --git a/app/frontend/src/Components/Profile/EditProfile.tsx b/app/frontend/src/Components/Profile/EditProfile.tsx index 0bb6ac98..c2a7d7fa 100644 --- a/app/frontend/src/Components/Profile/EditProfile.tsx +++ b/app/frontend/src/Components/Profile/EditProfile.tsx @@ -46,7 +46,6 @@ function EditProfile({ profile }: { profile: any }) { const handleConfirm = async (data: any) => { setConfirmLoading(true); const newdata = { ...data, ...{ profilePhoto: imageUrl } }; - console.log(newdata); edit(newdata); }; diff --git a/app/frontend/src/Pages/Admin/Game/UpdateGame/UpdateGame.tsx b/app/frontend/src/Pages/Admin/Game/UpdateGame/UpdateGame.tsx index edd7005d..0292180e 100644 --- a/app/frontend/src/Pages/Admin/Game/UpdateGame/UpdateGame.tsx +++ b/app/frontend/src/Pages/Admin/Game/UpdateGame/UpdateGame.tsx @@ -30,7 +30,6 @@ function UpdateGame() { NotificationUtil.success("You successfully update the game."); }, onError: (error) => { - console.log(error); handleAxiosError(error); }, }); @@ -46,7 +45,6 @@ function UpdateGame() { const handleClick = async () => { let gameIcon; if (fileList.length > 0) { - console.log(fileList); gameIcon = await uploadImageMutation.mutateAsync( fileList[0].originFileObj ); diff --git a/app/frontend/src/Pages/Group/Group.tsx b/app/frontend/src/Pages/Group/Group.tsx index b4755f5d..c96b54d2 100644 --- a/app/frontend/src/Pages/Group/Group.tsx +++ b/app/frontend/src/Pages/Group/Group.tsx @@ -31,7 +31,6 @@ function Group() { ); const showModal = () => { - console.log(group?.moderators); setIsModalOpen(true); }; @@ -55,9 +54,8 @@ function Group() { const handleReview = () => { navigate(`/group/review-application/${groupId}`); - } + }; - console.log(user); return (
@@ -81,22 +79,24 @@ function Group() { - {(group?.moderators.some((moderator:any) => moderator.id === user?.id) && group.membershipPolicy === "PRIVATE") && ( -
- -
- )} + {group?.moderators.some( + (moderator: any) => moderator.id === user?.id + ) && + group.membershipPolicy === "PRIVATE" && ( +
+ +
+ )} {(user?.role === "ADMIN" || group?.moderators.includes(user?.id)) && (
- +
)} -
diff --git a/app/frontend/src/Pages/Groups/Groups.tsx b/app/frontend/src/Pages/Groups/Groups.tsx index 2beeaf1d..19fff7b0 100644 --- a/app/frontend/src/Pages/Groups/Groups.tsx +++ b/app/frontend/src/Pages/Groups/Groups.tsx @@ -50,8 +50,6 @@ function Groups() { "DESCENDING" ); - console.log("tags here: ", tags); - const { data: groups } = useQuery( ["groups", title, gameName, tags, membershipPolicy, sortBy, sortDir], () => diff --git a/app/frontend/src/Pages/ReviewApplication/ReviewApplication.tsx b/app/frontend/src/Pages/ReviewApplication/ReviewApplication.tsx index 443a90fe..336db0d7 100644 --- a/app/frontend/src/Pages/ReviewApplication/ReviewApplication.tsx +++ b/app/frontend/src/Pages/ReviewApplication/ReviewApplication.tsx @@ -27,41 +27,34 @@ function ReviewApplication() { { value: null, label: "All" }, ]; - - const groupId = window.location.pathname.split("/")[3]; const [title, setTitle] = useState(""); const [tags, setTags] = useState([]); - console.log("tags here: ", tags); - const { data: applications, isLoading: isLoadingComments } = useQuery( ["applications", groupId], () => getApplications({ groupId: groupId! }) ); - return (
- { - e.target.value === "" ? setTitle("") : ""; - }} - style={{ width: "50%" }} - /> - + { + e.target.value === "" ? setTitle("") : ""; + }} + style={{ width: "50%" }} + /> + {applications && - applications.map((group: any) => - ( - - ) - )} + applications.map((group: any) => ( + + ))}
); diff --git a/app/frontend/src/Services/Register.ts b/app/frontend/src/Services/Register.ts index 5a91ee7e..5c795409 100644 --- a/app/frontend/src/Services/Register.ts +++ b/app/frontend/src/Services/Register.ts @@ -1,7 +1,6 @@ import axios from "axios"; async function postRegister(formData: any) { try { - console.log(formData) const response = await axios.post( `${import.meta.env.VITE_APP_API_URL}/auth/register`, formData, diff --git a/app/frontend/src/Services/applications.ts b/app/frontend/src/Services/applications.ts index 5c4aa8ed..04d7f054 100644 --- a/app/frontend/src/Services/applications.ts +++ b/app/frontend/src/Services/applications.ts @@ -1,20 +1,26 @@ import axios from "axios"; - export const getApplications = async ({ groupId }: { groupId: string }) => { - const response = await axios.get( - import.meta.env.VITE_APP_API_URL + "/group/list-applications?groupId=" + groupId, - ); - console.log(response.data); - return response.data; - }; + const response = await axios.get( + import.meta.env.VITE_APP_API_URL + + "/group/list-applications?groupId=" + + groupId + ); + + return response.data; +}; - export async function reviewApplication(applicationId: string, message: string) { - const response = await axios.post( - `${import.meta.env.VITE_APP_API_URL}/group/review-application?applicationId=${applicationId}`, - { - result: message, - } - ); - return response.data; - } \ No newline at end of file +export async function reviewApplication( + applicationId: string, + message: string +) { + const response = await axios.post( + `${ + import.meta.env.VITE_APP_API_URL + }/group/review-application?applicationId=${applicationId}`, + { + result: message, + } + ); + return response.data; +} From 84ba8e1eb8a4fb36c859abda13b566d8d59b95f9 Mon Sep 17 00:00:00 2001 From: zeynep-baydemir Date: Sun, 24 Dec 2023 14:52:54 +0300 Subject: [PATCH 33/43] added last activities test and improved edit profile tests --- .../service/ProfileServiceTests.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/app/backend/src/test/java/com/app/gamereview/service/ProfileServiceTests.java b/app/backend/src/test/java/com/app/gamereview/service/ProfileServiceTests.java index 6ce00a6b..14cfe13e 100644 --- a/app/backend/src/test/java/com/app/gamereview/service/ProfileServiceTests.java +++ b/app/backend/src/test/java/com/app/gamereview/service/ProfileServiceTests.java @@ -3,7 +3,14 @@ import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; +import com.app.gamereview.dto.request.review.GetAllReviewsFilterRequestDto; +import com.app.gamereview.dto.request.vote.GetAllVotesFilterRequestDto; +import com.app.gamereview.dto.response.profile.GetLastActivitiesResponseDto; +import com.app.gamereview.dto.response.review.GetAllReviewsResponseDto; +import com.app.gamereview.enums.ActivityType; import com.app.gamereview.enums.UserRole; +import com.app.gamereview.enums.VoteChoice; +import com.app.gamereview.enums.VoteType; import com.app.gamereview.model.*; import com.app.gamereview.repository.*; import com.app.gamereview.dto.request.profile.EditProfileRequestDto; @@ -17,6 +24,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; public class ProfileServiceTests { @@ -34,6 +43,15 @@ public class ProfileServiceTests { @Mock private UserRepository userRepository; + @Mock + private ReviewService reviewService; + @Mock + private VoteService voteService; + + @Mock + private CommentService commentService; + @Mock + private PostService postService; @InjectMocks private ProfileService profileService; @@ -151,6 +169,37 @@ public void testGetProfilePrivateProfileAccessDenied() { profileService.getProfile(profileUserId, nonOwnerUser.getEmail()); }, "Expected BadRequestException for accessing a private profile by a non-owner and non-admin user"); } + @Test + public void testEditProfileUsernameAlreadyTaken() { + String profileId = "profileId"; + String userId = "user1"; + String existingUsername = "existingUsername"; + String newUsername = "newUsername"; + + Profile mockProfile = new Profile(); + mockProfile.setUserId(userId); + + when(profileRepository.findById(profileId)).thenReturn(Optional.of(mockProfile)); + + User existingUser = new User(); + existingUser.setId("existingUserId"); + existingUser.setUsername(newUsername); + when(userRepository.findByUsername(newUsername)).thenReturn(Optional.of(existingUser)); + + User mockUser = new User(); + mockUser.setId(userId); + mockUser.setUsername(existingUsername); + + when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); + + EditProfileRequestDto request = new EditProfileRequestDto(); + request.setUsername(newUsername); + + assertThrows(BadRequestException.class, () -> { + profileService.editProfile(profileId, request, mockUser); + }, "Expected BadRequestException for attempting to use an already taken username"); + } + @Test public void testGetProfileSuccess() { @@ -174,4 +223,61 @@ public void testGetProfileSuccess() { assertEquals(userId, mockProfile.getUserId(), "The profile should match the requested user id"); assertFalse(mockProfile.getIsPrivate(), "The profile should be public"); } + @Test + public void testGetLastActivities() { + User user = new User(); + user.setId("userId"); + user.setEmail("user@example.com"); + + GetAllReviewsFilterRequestDto reviewFilter = new GetAllReviewsFilterRequestDto(); + reviewFilter.setReviewedBy(user.getId()); + reviewFilter.setSortBy("CREATION_DATE"); + + GetAllVotesFilterRequestDto voteFilter = new GetAllVotesFilterRequestDto(); + voteFilter.setVotedBy(user.getId()); + + GetAllReviewsResponseDto mockReview = new GetAllReviewsResponseDto(); + mockReview.setId("reviewId"); + mockReview.setGameId("gameId"); + mockReview.setReviewDescription("This is a review"); + mockReview.setCreatedAt(LocalDateTime.parse("2023-01-01T12:00:00")); + + Vote mockVote = new Vote(); + mockVote.setId("voteId"); + mockVote.setTypeId("reviewId"); + mockVote.setVoteType(VoteType.REVIEW); + mockVote.setChoice(VoteChoice.UPVOTE); + mockVote.setVotedBy(user.getId()); + mockVote.setCreatedAt(LocalDateTime.parse("2023-01-02T12:00:00")); + + Comment mockComment = new Comment(); + mockComment.setId("commentId"); + mockComment.setPost("postId"); + mockComment.setCommentContent("This is a comment"); + mockComment.setCreatedAt(LocalDateTime.parse("2023-01-04T12:00:00")); + + // Mock post data + Post mockPost = new Post(); + mockPost.setId("postId"); + mockPost.setForum("forumId"); + mockPost.setPostContent("This is a post"); + mockPost.setCreatedAt(LocalDateTime.parse("2023-01-05T12:00:00")); + + when(reviewService.getAllReviews(any(), any())).thenReturn(List.of(mockReview)); + when(voteService.getAllVotes(any())).thenReturn(List.of(mockVote)); + when(commentService.getUserCommentList(any())).thenReturn(List.of(mockComment)); + when(postService.getUserPostList(any())).thenReturn(List.of(mockPost)); + + List lastActivities = profileService.getLastActivities(user); + System.out.println(lastActivities); + assertNotNull(lastActivities, "The result should not be null"); + assertEquals(4, lastActivities.size(), "There should be four activity"); + for (int i = 1; i < lastActivities.size(); i++) { + GetLastActivitiesResponseDto currentActivity = lastActivities.get(i); + GetLastActivitiesResponseDto previousActivity = lastActivities.get(i - 1); + + // Check that the current activity's creation date is equal to or after the previous one + assertFalse(currentActivity.getCreatedAt().isAfter(previousActivity.getCreatedAt()), "Activities should be sorted in descending order based on creation date"); + } + } } From 86857d4beac2573c8b5ec1cbc1355b55eaf2d97e Mon Sep 17 00:00:00 2001 From: Halis Bal Date: Sun, 24 Dec 2023 14:56:21 +0300 Subject: [PATCH 34/43] updated pom file --- app/annotation/annotation/pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/annotation/annotation/pom.xml b/app/annotation/annotation/pom.xml index 04318859..42c2a8f3 100644 --- a/app/annotation/annotation/pom.xml +++ b/app/annotation/annotation/pom.xml @@ -79,4 +79,13 @@ + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file From da9cc8a553b8d49dfd1ccd1aea274f4c1bf82df7 Mon Sep 17 00:00:00 2001 From: alperen-bircak Date: Sun, 24 Dec 2023 15:00:17 +0300 Subject: [PATCH 35/43] carousels added --- .../RecommendationCarousel.module.scss | 17 ++++ .../RecommendationCarousel.tsx | 37 +++++++++ .../RecommendationItem.module.scss | 78 +++++++++++++++++++ .../RecommendationItem/RecommendationItem.tsx | 41 ++++++++++ .../src/Pages/HomePage/HomePage.module.scss | 3 + app/frontend/src/Pages/HomePage/HomePage.tsx | 27 ++++++- app/frontend/src/globals.scss | 2 + 7 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.module.scss create mode 100644 app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.tsx create mode 100644 app/frontend/src/Components/ReccomendationCarousel/RecommendationItem/RecommendationItem.module.scss create mode 100644 app/frontend/src/Components/ReccomendationCarousel/RecommendationItem/RecommendationItem.tsx diff --git a/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.module.scss b/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.module.scss new file mode 100644 index 00000000..8769df84 --- /dev/null +++ b/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.module.scss @@ -0,0 +1,17 @@ +@import "../../colors"; + +.recommendationCarouselContainer { + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; + background-color: $prussian-blue; + padding: 0.5em; + border-radius: 0.5em; + + h2 { + color: $blue-green-light-80; + margin: 0; + margin-bottom: 0.75em; + } +} diff --git a/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.tsx b/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.tsx new file mode 100644 index 00000000..8fd13a03 --- /dev/null +++ b/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.tsx @@ -0,0 +1,37 @@ +import React, { useRef } from "react"; +import { Carousel } from "antd"; +import { useElementSize } from "usehooks-ts"; +import styles from "./RecommendationCarousel.module.scss"; +import RecommendationItem from "./RecommendationItem/RecommendationItem"; + +function RecommendationCarousel({ + items, + title, +}: { + items: { + image: string; + name: string; + link: string; + showName?: boolean; + }[]; + title?: string; +}) { + const [containerRef, { width }] = useElementSize(); + + return ( +
+ {title &&

{title}

} + + {items?.map((item, index) => ( + + ))} + +
+ ); +} + +export default RecommendationCarousel; diff --git a/app/frontend/src/Components/ReccomendationCarousel/RecommendationItem/RecommendationItem.module.scss b/app/frontend/src/Components/ReccomendationCarousel/RecommendationItem/RecommendationItem.module.scss new file mode 100644 index 00000000..1dd05cfa --- /dev/null +++ b/app/frontend/src/Components/ReccomendationCarousel/RecommendationItem/RecommendationItem.module.scss @@ -0,0 +1,78 @@ +@import "../../../colors"; + +.container { + height: 150px; + width: 150px; + padding: 0.5em; + background-color: $blue-green-dark-40; + margin-bottom: 2em; + border-radius: 0.5em; + position: relative; + display: block; + img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 0.5em; + overflow: hidden; + } + + .index { + font-size: 2em; + font-weight: bold; + color: $blue-green-light-90; + text-shadow: 2px 0 $blue-green, -2px 0 $blue-green, 0 2px $blue-green, + 0 -2px $blue-green, 1px 1px $blue-green, -1px -1px $blue-green, + 1px -1px $blue-green, -1px 1px $blue-green; + + position: absolute; + top: 0em; + left: 0.5em; + } + + .showName { + font-size: 1.2em; + font-weight: bold; + color: $blue-green-light-90; + text-shadow: 2px 0 $blue-green, -2px 0 $blue-green, 0 2px $blue-green, + 0 -2px $blue-green, 1px 1px $blue-green, -1px -1px $blue-green, + 1px -1px $blue-green, -1px 1px $blue-green; + + position: absolute; + bottom: 0.5em; + left: 0.5em; + } + + .overlay { + background-color: #00000088; + position: absolute; + inset: 0.5em; + border-radius: 0.5em; + opacity: 0%; + transition: opacity 200ms; + color: white; + font-size: 1.1em; + font-weight: bold; + + .icon { + top: 0.5em; + right: 0.5em; + position: absolute; + font-size: 1.1em; + } + + .name { + bottom: 0; + right: 0; + left: 0; + padding: 0.5em; + position: absolute; + } + } + + &:hover { + .overlay { + opacity: 100%; + } + } +} diff --git a/app/frontend/src/Components/ReccomendationCarousel/RecommendationItem/RecommendationItem.tsx b/app/frontend/src/Components/ReccomendationCarousel/RecommendationItem/RecommendationItem.tsx new file mode 100644 index 00000000..30560f15 --- /dev/null +++ b/app/frontend/src/Components/ReccomendationCarousel/RecommendationItem/RecommendationItem.tsx @@ -0,0 +1,41 @@ +import { ArrowRightOutlined, ExportOutlined } from "@ant-design/icons"; +import styles from "./RecommendationItem.module.scss"; +import { Link } from "react-router-dom"; + +function RecommendationItem({ + image, + name, + link, + index, + showName, +}: { + image: string; + name: string; + link: string; + index?: number; + showName?: boolean; +}) { + return ( + + {index !== undefined && {index}} + {image && ( + name of the game + )} + {showName && {name}} +
+ {!showName && {name}}{" "} +
+ +
+
+ + ); +} + +export default RecommendationItem; diff --git a/app/frontend/src/Pages/HomePage/HomePage.module.scss b/app/frontend/src/Pages/HomePage/HomePage.module.scss index ea431d04..494e6b16 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.module.scss +++ b/app/frontend/src/Pages/HomePage/HomePage.module.scss @@ -4,12 +4,15 @@ padding: 1em; display: flex; justify-content: center; + width: 100%; + overflow: hidden; .postsContainer { display: flex; gap: 1em; flex-direction: column; width: 70%; + overflow: hidden; background-color: $vanilla-light-60; padding: 10px; border-radius: 0.5em; diff --git a/app/frontend/src/Pages/HomePage/HomePage.tsx b/app/frontend/src/Pages/HomePage/HomePage.tsx index 33781b7c..4e651125 100644 --- a/app/frontend/src/Pages/HomePage/HomePage.tsx +++ b/app/frontend/src/Pages/HomePage/HomePage.tsx @@ -3,8 +3,8 @@ import styles from "./HomePage.module.scss"; import { useAuth } from "../../Components/Hooks/useAuth"; import { getHomePosts } from "../../Services/home"; import ForumPost from "../../Components/Forum/ForumPost/ForumPost"; -import { useState } from "react"; -import { Button, Pagination, Select } from "antd"; +import { useRef, useState } from "react"; +import { Button, Carousel, Pagination, Select } from "antd"; import { SortAscendingOutlined, SortDescendingOutlined, @@ -13,6 +13,9 @@ import { getGameRecommendations, getGroupRecommendations, } from "../../Services/recommendation"; +import GameReccomendation from "../../Components/ReccomendationCarousel/RecommendationItem/RecommendationItem"; +import { useElementSize } from "usehooks-ts"; +import RecommendationCarousel from "../../Components/ReccomendationCarousel/RecommendationCarousel"; const sortOptions = [ { label: "Creation Date", value: "CREATION_DATE" }, { label: "Overall Vote", value: "OVERALL_VOTE" }, @@ -49,7 +52,7 @@ function HomePage() { } ); const { data: groups } = useQuery( - ["gamerec", user?.id], + ["grouprec", user?.id], getGroupRecommendations, { enabled: !userLoading, @@ -59,6 +62,23 @@ function HomePage() { return (
+ ({ + image: game.gameIcon, + name: game.gameName, + link: `/game/detail/${game.id}`, + }))} + /> + ({ + image: group.groupIcon, + name: group.title, + link: `/group/detail/${group.id}`, + showName: true, + }))} + />
+ {data ?.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE) .map((post: any) => ( diff --git a/app/frontend/src/globals.scss b/app/frontend/src/globals.scss index ebdd78ec..2dbb3a9c 100644 --- a/app/frontend/src/globals.scss +++ b/app/frontend/src/globals.scss @@ -18,4 +18,6 @@ html, #root { height: 0px; min-height: calc(100% - 0.5px); + width: 100vw; + overflow-x: hidden; } From cfc75b3c500b957d901621c58ff893b9984a6052 Mon Sep 17 00:00:00 2001 From: zeynep-baydemir Date: Sun, 24 Dec 2023 15:17:34 +0300 Subject: [PATCH 36/43] group recommendations size is max 10 --- .../com/app/gamereview/service/GroupService.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/backend/src/main/java/com/app/gamereview/service/GroupService.java b/app/backend/src/main/java/com/app/gamereview/service/GroupService.java index 551af5d6..e52a7d91 100644 --- a/app/backend/src/main/java/com/app/gamereview/service/GroupService.java +++ b/app/backend/src/main/java/com/app/gamereview/service/GroupService.java @@ -672,14 +672,15 @@ public List getRecommendedGroups(String email) { query.limit(2); List gameGroups = mongoTemplate.find(query, Group.class); recommendationGameGroups.addAll(gameGroups); + Collections.shuffle(recommendationGameGroups); } - return recommendationGameGroups; + return recommendationGameGroups.subList(0, Math.min(10, recommendationGameGroups.size())); } } TreeSet recommendedGroups = new TreeSet<>(Comparator.reverseOrder()); for (Group group : memberGroups) { String groupId = group.getId(); - recommendedGroups.addAll(recommendationByGroupId(groupId)); + recommendedGroups.addAll(recommendationByGroupId(groupId, user.getId())); } if(!userGames.isEmpty()){ List recommendedGameGroups = new ArrayList<>(); @@ -687,6 +688,7 @@ public List getRecommendedGroups(String email) { Query query = new Query(); query.addCriteria(Criteria.where("isDeleted").is(false)); query.addCriteria(Criteria.where("gameId").is(gameId)); + query.addCriteria(Criteria.where("members").nin(user.getId())); query.limit(2); List gameGroups = mongoTemplate.find(query, Group.class); recommendedGameGroups.addAll(gameGroups); @@ -697,11 +699,10 @@ public List getRecommendedGroups(String email) { recommendations.add(groupDto.getGroup()); } - - return recommendations; + return recommendations.subList(0, Math.min(10, recommendations.size())); } - public TreeSet recommendationByGroupId(String groupId) { + public TreeSet recommendationByGroupId(String groupId, String userId) { Optional findGroup = groupRepository.findByIdAndIsDeletedFalse(groupId); Set idList = new HashSet<>(); @@ -722,6 +723,7 @@ public TreeSet recommendationByGroupId(String groupId) { Query query = new Query(); query.addCriteria(Criteria.where("title").regex(regexPattern, "i")); query.addCriteria(Criteria.where("_id").ne(group.getId())); + query.addCriteria(Criteria.where("members").nin(userId)); List similarNames = mongoTemplate.find(query, Group.class); for (Group i : similarNames) { RecommendGroupDto dto = new RecommendGroupDto(); @@ -735,6 +737,7 @@ public TreeSet recommendationByGroupId(String groupId) { Query allGroupsQuery = new Query(); allGroupsQuery.addCriteria(Criteria.where("isDeleted").is(false)); allGroupsQuery.addCriteria(Criteria.where("_id").nin(idList)); + allGroupsQuery.addCriteria(Criteria.where("members").nin(userId)); List allGroups = mongoTemplate.find(allGroupsQuery, Group.class); From 6197e8ae4dcf78972276f4b281ad633767757962 Mon Sep 17 00:00:00 2001 From: Can Date: Sun, 24 Dec 2023 15:37:05 +0300 Subject: [PATCH 37/43] followed games are not being recommended to users --- .../main/java/com/app/gamereview/service/GameService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/backend/src/main/java/com/app/gamereview/service/GameService.java b/app/backend/src/main/java/com/app/gamereview/service/GameService.java index 4162427a..fd6a5a5c 100644 --- a/app/backend/src/main/java/com/app/gamereview/service/GameService.java +++ b/app/backend/src/main/java/com/app/gamereview/service/GameService.java @@ -426,7 +426,10 @@ public List getRecommendedGames(String email){ List recommendations = new ArrayList<>(); for(RecommendGameDto gameDto : recommendedGames){ - recommendations.add(gameDto.getGame()); + Game gameToRecommend = gameDto.getGame(); + if(!followedGameIds.contains(gameToRecommend.getId())){ + recommendations.add(gameToRecommend); + } if(recommendations.size() >= 10){ // get only top 10 recommendations break; } From 9006f06c6b0de62449e61e89d47da9c2b4ca4c8a Mon Sep 17 00:00:00 2001 From: alperen-bircak Date: Sun, 24 Dec 2023 15:50:14 +0300 Subject: [PATCH 38/43] added loaders --- .../RecommendationCarousel.module.scss | 1 + .../RecommendationCarousel.tsx | 28 +++++++++++++------ .../src/Pages/ForumPost/ForumPost.tsx | 1 - app/frontend/src/router.tsx | 4 --- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.module.scss b/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.module.scss index 8769df84..f68d48f7 100644 --- a/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.module.scss +++ b/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.module.scss @@ -8,6 +8,7 @@ background-color: $prussian-blue; padding: 0.5em; border-radius: 0.5em; + height: auto; h2 { color: $blue-green-light-80; diff --git a/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.tsx b/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.tsx index 8fd13a03..da6df06a 100644 --- a/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.tsx +++ b/app/frontend/src/Components/ReccomendationCarousel/RecommendationCarousel.tsx @@ -3,10 +3,13 @@ import { Carousel } from "antd"; import { useElementSize } from "usehooks-ts"; import styles from "./RecommendationCarousel.module.scss"; import RecommendationItem from "./RecommendationItem/RecommendationItem"; +import { PacmanLoader } from "react-spinners"; +import { twj } from "tw-to-css"; function RecommendationCarousel({ items, title, + loading, }: { items: { image: string; @@ -15,21 +18,28 @@ function RecommendationCarousel({ showName?: boolean; }[]; title?: string; + loading?: boolean; }) { const [containerRef, { width }] = useElementSize(); return (
{title &&

{title}

} - - {items?.map((item, index) => ( - - ))} - + {!loading ? ( + + {items?.map((item, index) => ( + + ))} + + ) : ( +
+ +
+ )}
); } diff --git a/app/frontend/src/Pages/ForumPost/ForumPost.tsx b/app/frontend/src/Pages/ForumPost/ForumPost.tsx index eff4f2c2..85295d8f 100644 --- a/app/frontend/src/Pages/ForumPost/ForumPost.tsx +++ b/app/frontend/src/Pages/ForumPost/ForumPost.tsx @@ -44,7 +44,6 @@ import { } from "../../Services/annotation.ts"; import { NotificationUtil } from "../../Library/utils/notification.ts"; import CharacterDetails from "../../Components/Character/CharacterDetails.tsx"; -import { NotificationUtil } from "../../Library/utils/notification.ts"; function ForumPost() { const { isLoggedIn, user } = useAuth(); diff --git a/app/frontend/src/router.tsx b/app/frontend/src/router.tsx index 1f0115c4..2487b41f 100644 --- a/app/frontend/src/router.tsx +++ b/app/frontend/src/router.tsx @@ -32,10 +32,6 @@ import UpdateGame from "./Pages/Admin/Game/UpdateGame/UpdateGame"; import HomePage from "./Pages/HomePage/HomePage"; import Char from "./Pages/Char/Char"; import DeleteGame from "./Pages/Admin/Game/DeleteGame/DeleteGame"; -import Notifications from "./Pages/Notifications/Notifications"; -import ReviewApplication from "./Pages/ReviewApplication/ReviewApplication"; - - axios.defaults.headers.common["Content-Type"] = "application/json"; From aa6b07d6c52c7a943f775af5563942e3deb81f2d Mon Sep 17 00:00:00 2001 From: Halis Bal Date: Sun, 24 Dec 2023 17:43:20 +0300 Subject: [PATCH 39/43] added character service tests --- .../service/CharacterServiceTest.java | 344 ++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 app/backend/src/test/java/com/app/gamereview/service/CharacterServiceTest.java diff --git a/app/backend/src/test/java/com/app/gamereview/service/CharacterServiceTest.java b/app/backend/src/test/java/com/app/gamereview/service/CharacterServiceTest.java new file mode 100644 index 00000000..e021cb2b --- /dev/null +++ b/app/backend/src/test/java/com/app/gamereview/service/CharacterServiceTest.java @@ -0,0 +1,344 @@ +package com.app.gamereview.service; + +import com.app.gamereview.dto.request.character.CreateCharacterRequestDto; +import com.app.gamereview.dto.request.character.UpdateCharacterRequestDto; +import com.app.gamereview.exception.BadRequestException; +import com.app.gamereview.exception.ResourceNotFoundException; +import com.app.gamereview.model.Character; +import com.app.gamereview.model.Game; +import com.app.gamereview.repository.CharacterRepository; +import com.app.gamereview.repository.GameRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.modelmapper.ModelMapper; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CharacterServiceTest { + + @Mock + private CharacterRepository characterRepository; + + @Mock + private GameRepository gameRepository; + + @Mock + private ModelMapper modelMapper; + + @InjectMocks + private CharacterService characterService; + + @BeforeEach + void setUp() { + // Set up any common mocking behavior here + } + + @Test + void testCreateCharacterWithValidData() { + // Arrange + CreateCharacterRequestDto requestDto = new CreateCharacterRequestDto(); + requestDto.setGames(Collections.singletonList("validGameId")); + Character character = new Character(); + when(modelMapper.map(requestDto, Character.class)).thenReturn(character); + when(gameRepository.findByIdAndIsDeletedFalse("validGameId")).thenReturn(Optional.of(new Game())); + when(characterRepository.save(any(Character.class))).thenReturn(character); + + // Act + Character createdCharacter = characterService.createCharacter(requestDto); + + // Assert + assertNotNull(createdCharacter, "Character should not be null"); + } + + @Test + void testCreateCharacterWithEmptyGames() { + // Arrange + CreateCharacterRequestDto requestDto = new CreateCharacterRequestDto(); + requestDto.setGames(Collections.emptyList()); + + // Act & Assert + assertThrows(BadRequestException.class, + () -> characterService.createCharacter(requestDto), + "Expected BadRequestException to be thrown when games list is empty" + ); + } + + @Test + void testCreateCharacterWithNonExistentGame() { + // Arrange + CreateCharacterRequestDto requestDto = new CreateCharacterRequestDto(); + requestDto.setGames(Collections.singletonList("nonExistentGameId")); + when(gameRepository.findByIdAndIsDeletedFalse("nonExistentGameId")).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(ResourceNotFoundException.class, + () -> characterService.createCharacter(requestDto), + "Expected ResourceNotFoundException to be thrown for a non-existent game" + ); + } + + @Test + void testCreateCharacterWithNullRequest() { + // Act & Assert + assertThrows(NullPointerException.class, + () -> characterService.createCharacter(null), + "Expected NullPointerException to be thrown when request is null" + ); + } + + @Test + void testUpdateCharacterWithValidData() { + // Arrange + UpdateCharacterRequestDto requestDto = new UpdateCharacterRequestDto(); + requestDto.setName("UpdatedName"); + requestDto.setIcon("UpdatedIcon"); + requestDto.setDescription("UpdatedDescription"); + requestDto.setGames(Collections.singletonList("validGameId")); + requestDto.setType("UpdatedType"); + requestDto.setGender("UpdatedGender"); + requestDto.setRace("UpdatedRace"); + requestDto.setStatus("UpdatedStatus"); + requestDto.setOccupation("UpdatedOccupation"); + requestDto.setBirthDate("UpdatedBirthDate"); + requestDto.setVoiceActor("UpdatedVoiceActor"); + requestDto.setHeight("UpdatedHeight"); + requestDto.setAge("UpdatedAge"); + requestDto.setCustomFields(Collections.singletonMap("key", "value")); + + Character existingCharacter = new Character(); + when(characterRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(existingCharacter)); + when(gameRepository.findByIdAndIsDeletedFalse("validGameId")).thenReturn(Optional.of(new Game())); + when(characterRepository.save(any(Character.class))).thenReturn(existingCharacter); + + // Act + Character updatedCharacter = characterService.updateCharacter("existingId", requestDto); + + // Assert + assertNotNull(updatedCharacter, "Character should be updated and not null"); + assertEquals("UpdatedName", updatedCharacter.getName()); + } + + @Test + void testUpdateCharacterWithValidBlankData() { + // Arrange + UpdateCharacterRequestDto requestDto = new UpdateCharacterRequestDto(); + requestDto.setName("UpdatedName"); + requestDto.setIcon("UpdatedIcon"); + requestDto.setDescription("description"); + requestDto.setGames(Collections.singletonList("validGameId")); + requestDto.setType(""); + requestDto.setGender(""); + requestDto.setRace(""); + requestDto.setStatus(""); + requestDto.setOccupation(""); + requestDto.setBirthDate(""); + requestDto.setVoiceActor(""); + requestDto.setHeight(""); + requestDto.setAge(""); + requestDto.setCustomFields(Collections.singletonMap("key", "value")); + + Character existingCharacter = new Character(); + when(characterRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(existingCharacter)); + when(gameRepository.findByIdAndIsDeletedFalse("validGameId")).thenReturn(Optional.of(new Game())); + when(characterRepository.save(any(Character.class))).thenReturn(existingCharacter); + + // Act + Character updatedCharacter = characterService.updateCharacter("existingId", requestDto); + + // Assert + assertNotNull(updatedCharacter, "Character should be updated and not null"); + assertEquals("UpdatedName", updatedCharacter.getName()); + } + + @Test + void testUpdateCharacterNonExistentCharacter() { + // Arrange + when(characterRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(ResourceNotFoundException.class, + () -> characterService.updateCharacter("nonExistentId", new UpdateCharacterRequestDto()), + "Expected ResourceNotFoundException for non-existent character ID" + ); + } + + @Test + void testUpdateCharacterWithBlankName() { + // Arrange + UpdateCharacterRequestDto requestDto = new UpdateCharacterRequestDto(); + requestDto.setName(""); + + Character existingCharacter = new Character(); + when(characterRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(existingCharacter)); + + // Act & Assert + assertThrows(BadRequestException.class, + () -> characterService.updateCharacter("existingId", requestDto), + "Expected BadRequestException when the name is blank" + ); + } + + @Test + void testUpdateCharacterWithBlankIcon() { + // Arrange + UpdateCharacterRequestDto requestDto = new UpdateCharacterRequestDto(); + requestDto.setIcon(""); + + Character existingCharacter = new Character(); + when(characterRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(existingCharacter)); + + // Act & Assert + assertThrows(BadRequestException.class, + () -> characterService.updateCharacter("existingId", requestDto), + "Expected BadRequestException when the icon is blank" + ); + } + + @Test + void testUpdateCharacterWithBlankDescription() { + // Arrange + UpdateCharacterRequestDto requestDto = new UpdateCharacterRequestDto(); + requestDto.setDescription(""); + + Character existingCharacter = new Character(); + when(characterRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(existingCharacter)); + + // Act & Assert + assertThrows(BadRequestException.class, + () -> characterService.updateCharacter("existingId", requestDto), + "Expected BadRequestException when the description is blank" + ); + } + + @Test + void testUpdateCharacterWithEmptyGamesList() { + // Arrange + UpdateCharacterRequestDto requestDto = new UpdateCharacterRequestDto(); + requestDto.setGames(Collections.emptyList()); + + Character existingCharacter = new Character(); + when(characterRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(existingCharacter)); + + // Act & Assert + assertThrows(BadRequestException.class, + () -> characterService.updateCharacter("existingId", requestDto), + "Expected BadRequestException when the games list is empty" + ); + } + + @Test + void testUpdateCharacterWithNonExistentGame() { + // Arrange + UpdateCharacterRequestDto requestDto = new UpdateCharacterRequestDto(); + requestDto.setGames(Collections.singletonList("nonExistentGameId")); + + Character existingCharacter = new Character(); + when(characterRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(existingCharacter)); + when(gameRepository.findByIdAndIsDeletedFalse("nonExistentGameId")).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(ResourceNotFoundException.class, + () -> characterService.updateCharacter("existingId", requestDto), + "Expected ResourceNotFoundException for non-existent game in the games list" + ); + } + + @Test + void testUpdateCharacterWithNullRequest() { + // Arrange + Character existingCharacter = new Character(); + when(characterRepository.findByIdAndIsDeletedFalse(anyString())).thenReturn(Optional.of(existingCharacter)); + + // Act & Assert + assertThrows(NullPointerException.class, + () -> characterService.updateCharacter("existingId", null), + "Expected NullPointerException when request is null" + ); + } + + @Test + void testDeleteCharacterWithValidId() { + // Arrange + String validId = "validId"; + Character existingCharacter = new Character(); + when(characterRepository.findByIdAndIsDeletedFalse(validId)).thenReturn(Optional.of(existingCharacter)); + + // Act + Character deletedCharacter = characterService.deleteCharacter(validId); + + // Assert + assertTrue(deletedCharacter.getIsDeleted(), "Character should be marked as deleted"); + verify(characterRepository).save(deletedCharacter); + } + + @Test + void testDeleteNonExistentCharacter() { + // Arrange + when(characterRepository.findByIdAndIsDeletedFalse("nonExistentId")).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(ResourceNotFoundException.class, + () -> characterService.deleteCharacter("nonExistentId"), + "Expected ResourceNotFoundException for a non-existent character ID" + ); + } + + @Test + void testDeleteCharacterAlreadyDeleted() { + // Arrange + String validId = "validId"; + Character alreadyDeletedCharacter = new Character(); + alreadyDeletedCharacter.setIsDeleted(true); + // create empty optional character object + Optional characterOptional = Optional.empty(); + when(characterRepository.findByIdAndIsDeletedFalse(validId)).thenReturn(characterOptional); + + // Act + assertThrows(ResourceNotFoundException.class, + () -> characterService.deleteCharacter(validId), + "Expected ResourceNotFoundException for a non-existent character ID" + ); + + // Assert + assertTrue(alreadyDeletedCharacter.getIsDeleted(), "Character should remain deleted"); + verify(characterRepository, never()).save(alreadyDeletedCharacter); + } + + @Test + void testGetGameCharactersWithValidGameId() { + // Arrange + String validGameId = "validGameId"; + when(gameRepository.findByIdAndIsDeletedFalse(validGameId)).thenReturn(Optional.of(new Game())); + when(characterRepository.findByGamesContains(validGameId)).thenReturn(Collections.singletonList(new Character())); + + // Act + List characters = characterService.getGameCharacters(validGameId); + + // Assert + assertFalse(characters.isEmpty(), "Character list should not be empty"); + } + + @Test + void testGetGameCharactersWithNonExistentGameId() { + // Arrange + when(gameRepository.findByIdAndIsDeletedFalse("nonExistentGameId")).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(ResourceNotFoundException.class, + () -> characterService.getGameCharacters("nonExistentGameId"), + "Expected ResourceNotFoundException for a non-existent game ID" + ); + } +} \ No newline at end of file From cc4c08c7e44baf6948c5192c8c7077cd2b9db33e Mon Sep 17 00:00:00 2001 From: Deniz Unal Date: Sun, 24 Dec 2023 17:43:59 +0300 Subject: [PATCH 40/43] fixed a bug that prevents selector field being json --- .../app/annotation/dto/request/TargetDto.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/dto/request/TargetDto.java b/app/annotation/annotation/src/main/java/com/app/annotation/dto/request/TargetDto.java index 8c063b3c..c963f559 100644 --- a/app/annotation/annotation/src/main/java/com/app/annotation/dto/request/TargetDto.java +++ b/app/annotation/annotation/src/main/java/com/app/annotation/dto/request/TargetDto.java @@ -1,11 +1,16 @@ package com.app.annotation.dto.request; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonSetter; import jakarta.validation.constraints.NotEmpty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; @Data @AllArgsConstructor @@ -24,5 +29,46 @@ public class TargetDto { private String source; @NotEmpty(message = "target selector can not be empty.") + @JsonIgnore private List selector; + + @JsonSetter("selector") + public void setSelector(Object selector) { + if (selector instanceof List) { + List> hashList; + hashList = (List>) selector; + this.selector = mapToSelectorDtoList(hashList); + } else { + LinkedHashMap hash = (LinkedHashMap) selector; + this.selector = Collections.singletonList(convertToSelectorDto(hash)); + } + } + + public static List mapToSelectorDtoList(List> inputList) { + List resultList = new ArrayList<>(); + + if (inputList != null) { + for (LinkedHashMap map : inputList) { + SelectorDto selectorDto = convertToSelectorDto(map); + resultList.add(selectorDto); + } + } + + return resultList; + } + + private static SelectorDto convertToSelectorDto(LinkedHashMap map) { + SelectorDto selectorDto = new SelectorDto(); + selectorDto.setType((String) map.get("type")); + selectorDto.setExact((String) map.get("exact")); + selectorDto.setPrefix((String) map.get("prefix")); + selectorDto.setSuffix((String) map.get("suffix")); + selectorDto.setStart((Integer) map.get("start")); + selectorDto.setEnd((Integer) map.get("end")); + selectorDto.setValue((String) map.get("value")); + selectorDto.setConformsTo((String) map.get("conformsTo")); + // Add other fields as needed + + return selectorDto; + } } From 4de3fc516ab9610d6558c35e52fdb3ff2bb3c2fc Mon Sep 17 00:00:00 2001 From: AliBasarann Date: Sun, 24 Dec 2023 17:44:00 +0300 Subject: [PATCH 41/43] create private group bug is fixed --- app/frontend/src/Pages/CreateGroup/CreateGroup.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/frontend/src/Pages/CreateGroup/CreateGroup.tsx b/app/frontend/src/Pages/CreateGroup/CreateGroup.tsx index 3e22dca2..41170118 100644 --- a/app/frontend/src/Pages/CreateGroup/CreateGroup.tsx +++ b/app/frontend/src/Pages/CreateGroup/CreateGroup.tsx @@ -34,7 +34,6 @@ function CreateGroup() { ({ title, tags, - membershipPolicy, quota, description, gameId, @@ -42,7 +41,6 @@ function CreateGroup() { }: { title: string; tags: string[]; - membershipPolicy: string; quota: number; description: string; gameId: string; From ac419efb6c7d9c044c575bbdcd90201c1ccacb62 Mon Sep 17 00:00:00 2001 From: Deniz Unal Date: Sun, 24 Dec 2023 17:52:33 +0300 Subject: [PATCH 42/43] prevented annotations from having same id --- .../controller/CustomExceptionHandler.java | 34 +++++++++++++++++++ .../exception/BadRequestException.java | 7 ++++ .../exception/ResourceNotFoundException.java | 7 ++++ .../annotation/service/AnnotationService.java | 7 ++++ 4 files changed, 55 insertions(+) create mode 100644 app/annotation/annotation/src/main/java/com/app/annotation/controller/CustomExceptionHandler.java create mode 100644 app/annotation/annotation/src/main/java/com/app/annotation/exception/BadRequestException.java create mode 100644 app/annotation/annotation/src/main/java/com/app/annotation/exception/ResourceNotFoundException.java diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/controller/CustomExceptionHandler.java b/app/annotation/annotation/src/main/java/com/app/annotation/controller/CustomExceptionHandler.java new file mode 100644 index 00000000..af1c47b1 --- /dev/null +++ b/app/annotation/annotation/src/main/java/com/app/annotation/controller/CustomExceptionHandler.java @@ -0,0 +1,34 @@ +package com.app.annotation.controller; + +import com.app.annotation.exception.BadRequestException; +import com.app.annotation.exception.ResourceNotFoundException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ControllerAdvice +public class CustomExceptionHandler { + @ExceptionHandler(ResourceNotFoundException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ResponseEntity handleResourceNotFoundException(ResourceNotFoundException ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); + } + + @ExceptionHandler(BadRequestException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ResponseEntity handleBadRequestException(BadRequestException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ResponseEntity handleValidationException(MethodArgumentNotValidException ex) { + String defaultMessage = ex.getBindingResult() + .getFieldError() + .getDefaultMessage(); + return ResponseEntity.badRequest().body(defaultMessage); + } +} diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/exception/BadRequestException.java b/app/annotation/annotation/src/main/java/com/app/annotation/exception/BadRequestException.java new file mode 100644 index 00000000..47488e85 --- /dev/null +++ b/app/annotation/annotation/src/main/java/com/app/annotation/exception/BadRequestException.java @@ -0,0 +1,7 @@ +package com.app.annotation.exception; + +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { + super(message); + } +} diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/exception/ResourceNotFoundException.java b/app/annotation/annotation/src/main/java/com/app/annotation/exception/ResourceNotFoundException.java new file mode 100644 index 00000000..e39a2c98 --- /dev/null +++ b/app/annotation/annotation/src/main/java/com/app/annotation/exception/ResourceNotFoundException.java @@ -0,0 +1,7 @@ +package com.app.annotation.exception; + +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String message) { + super(message); + } +} diff --git a/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java b/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java index 63ec1844..7bda5471 100644 --- a/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java +++ b/app/annotation/annotation/src/main/java/com/app/annotation/service/AnnotationService.java @@ -2,6 +2,7 @@ import com.app.annotation.dto.request.SelectorDto; import com.app.annotation.dto.request.TargetDto; +import com.app.annotation.exception.BadRequestException; import com.app.annotation.model.*; import com.app.annotation.repository.AnnotationRepository; import com.app.annotation.dto.request.CreateAnnotationRequestDto; @@ -36,6 +37,12 @@ protected void configure() { public Map createAnnotation(CreateAnnotationRequestDto dto) { + Optional prevAnnotation = annotationRepository.findById(dto.getId()); + + if (prevAnnotation.isPresent()) { + throw new BadRequestException("There is an annotation with same id."); + } + Annotation annotationToCreate = modelMapper.map(dto, Annotation.class); List selectorList = new ArrayList<>(); From 5bd85d66c5c4b48bd3bf4d15dadfb3434fe37148 Mon Sep 17 00:00:00 2001 From: Halis Bal Date: Sun, 24 Dec 2023 17:53:49 +0300 Subject: [PATCH 43/43] added junit tests for notification service --- .../service/NotificationServiceTest.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 app/backend/src/test/java/com/app/gamereview/service/NotificationServiceTest.java diff --git a/app/backend/src/test/java/com/app/gamereview/service/NotificationServiceTest.java b/app/backend/src/test/java/com/app/gamereview/service/NotificationServiceTest.java new file mode 100644 index 00000000..041db566 --- /dev/null +++ b/app/backend/src/test/java/com/app/gamereview/service/NotificationServiceTest.java @@ -0,0 +1,106 @@ +package com.app.gamereview.service; + +import com.app.gamereview.dto.request.notification.CreateNotificationRequestDto; +import com.app.gamereview.dto.request.notification.GetNotificationsRequestDto; +import com.app.gamereview.model.Notification; +import com.app.gamereview.repository.NotificationRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.modelmapper.ModelMapper; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +public class NotificationServiceTest { + + @Mock + private NotificationRepository notificationRepository; + + @Mock + private MongoTemplate mongoTemplate; + + @Mock + private ModelMapper modelMapper; + + @InjectMocks + private NotificationService notificationService; + + private Notification notification; + + @BeforeEach + public void setup() { + notification = new Notification(); + notification.setId("123"); + notification.setUser("user1"); + notification.setIsRead(false); + notification.setCreatedAt(LocalDateTime.now()); + } + + @Test + public void testGetNotificationList() { + when(mongoTemplate.find(any(Query.class), eq(Notification.class))).thenReturn(Arrays.asList(notification)); + + GetNotificationsRequestDto requestDto = new GetNotificationsRequestDto(); + requestDto.setIsRead(false); + + List result = notificationService.getNotificationList("user1", requestDto); + + assertFalse(result.isEmpty()); + verify(mongoTemplate, times(1)).find(any(Query.class), eq(Notification.class)); + } + + @Test + public void testCreateNotification() { + CreateNotificationRequestDto requestDto = new CreateNotificationRequestDto(); + requestDto.setUser("user1"); + + when(modelMapper.map(requestDto, Notification.class)).thenReturn(notification); + when(notificationRepository.save(any(Notification.class))).thenReturn(notification); + + Notification result = notificationService.createNotification(requestDto); + + assertNotNull(result); + assertEquals("user1", result.getUser()); + assertFalse(result.getIsRead()); + verify(notificationRepository, times(1)).save(any(Notification.class)); + } + + @Test + public void testGetNotificationListWithNullFilter() { + // Setup notifications + Notification unreadNotification = new Notification(); + unreadNotification.setId("notif1"); + unreadNotification.setUser("user1"); + unreadNotification.setIsRead(false); + + Notification readNotification = new Notification(); + readNotification.setId("notif2"); + readNotification.setUser("user1"); + readNotification.setIsRead(true); + + // Setup the returned list from mongoTemplate + when(mongoTemplate.find(any(Query.class), eq(Notification.class))) + .thenReturn(Arrays.asList(unreadNotification, readNotification)); + + // Call the method with null filter + List result = notificationService.getNotificationList("user1", null); + + // Assertions and Verifications + assertFalse(result.isEmpty()); + assertEquals(2, result.size()); + verify(notificationRepository, times(1)).save(unreadNotification); // Only the unread notification should be saved + verify(notificationRepository, never()).save(readNotification); // Read notification should not be saved + } +} \ No newline at end of file