diff --git a/package-lock.json b/package-lock.json index 4157594cb..836fdb6ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -610,18 +610,18 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", "dev": true } } @@ -691,18 +691,18 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", - "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", "dev": true } } @@ -4930,6 +4930,7 @@ "dev": true }, "caniuse-lite": { + "version": "1.0.30001697", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz", "integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==" @@ -4951,30 +4952,87 @@ "dev": true }, "cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==", "requires": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" - } - }, - "cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "requires": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + }, + "dependencies": { + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "chokidar": { @@ -5004,9 +5062,9 @@ "dev": true }, "cjs-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", - "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "dev": true }, "clean-stack": { @@ -5409,21 +5467,57 @@ } }, "css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + }, + "dependencies": { + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" + } } }, "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" }, "damerau-levenshtein": { "version": "1.0.8", @@ -8230,37 +8324,38 @@ } }, "@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "requires": { - "@babel/highlight": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", + "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", "dev": true }, "@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz", + "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.7", + "@babel/parser": "^7.26.7", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.26.7", + "@babel/types": "^7.26.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -8277,26 +8372,27 @@ } }, "@babel/generator": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", - "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", "dev": true, "requires": { - "@babel/types": "^7.25.6", + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" } }, "@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", "dev": true, "requires": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -8310,127 +8406,103 @@ } }, "@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, "requires": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" - } - }, - "@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "dev": true, - "requires": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" } }, "@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true }, "@babel/helpers": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", - "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", + "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", "dev": true, "requires": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6" - } - }, - "@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7" } }, "@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", "dev": true, "requires": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.26.7" } }, "@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dev": true, "requires": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/traverse": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", - "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", + "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", "dev": true, "requires": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.6", - "@babel/parser": "^7.25.6", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.7", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" } }, "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, "requires": { "@jridgewell/set-array": "^1.2.1", @@ -8469,15 +8541,15 @@ } }, "browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" } }, "convert-source-map": { @@ -8487,9 +8559,15 @@ "dev": true }, "electron-to-chromium": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.22.tgz", - "integrity": "sha512-tKYm5YHPU1djz0O+CGJ+oJIvimtsCcwR2Z9w7Skh08lUdyzXY5djods3q+z2JkWdb7tCcmM//eVavSRAiaPRNg==", + "version": "1.5.92", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.92.tgz", + "integrity": "sha512-BeHgmNobs05N1HMmMZ7YIuHfYBGlq/UmvlsTgg+fsbFs9xVMj+xJHFg19GN04+9Q+r8Xnh9LXqaYIyEWElnNgQ==", + "dev": true + }, + "jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true }, "lru-cache": { @@ -8502,15 +8580,15 @@ } }, "node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true }, "yallist": { @@ -8548,9 +8626,9 @@ } }, "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true }, "supports-color": { @@ -10260,9 +10338,9 @@ "dev": true }, "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true }, "supports-color": { @@ -11002,16 +11080,71 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==" + }, + "lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==" + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, + "lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==" + + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" + }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==" + }, + "lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==" + }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==" }, "log-update": { "version": "4.0.0", @@ -11756,11 +11889,11 @@ } }, "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", "requires": { - "boolbase": "^1.0.0" + "boolbase": "~1.0.0" } }, "number-is-nan": { @@ -12587,23 +12720,6 @@ "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" }, - "parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "requires": { - "entities": "^4.4.0" - } - }, - "parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", - "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", - "requires": { - "domhandler": "^5.0.2", - "parse5": "^7.0.0" - } - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -13067,9 +13183,9 @@ "dev": true }, "resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", "dev": true }, "reusify": { @@ -14668,13 +14784,13 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", "dev": true, "requires": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "dependencies": { "escalade": { @@ -14684,9 +14800,9 @@ "dev": true }, "picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true } } diff --git a/package.json b/package.json index 2336fca25..051a5125d 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,11 @@ "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-babel-module": "^5.3.1", "eslint-plugin-import": "^2.28.0", - "husky": "^8.0.1", - "jest": "^29.7.0", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.33.1", "eslint-plugin-react-hooks": "^4.6.0", + "husky": "^8.0.1", + "jest": "^29.7.0", "lint-staged": "^13.0.3", "mongodb-memory-server": "^7.2.1", "nodemon": "^3.0.1", @@ -59,7 +59,7 @@ "babel-plugin-module-resolver": "^5.0.0", "bcryptjs": "^2.4.3", "body-parser": "^1.18.3", - "cheerio": "^1.0.0-rc.12", + "cheerio": "^0.22.0", "cors": "^2.8.4", "cron": "^1.8.2", "dotenv": "^5.0.1", diff --git a/src/controllers/badgeController.js b/src/controllers/badgeController.js index 84d9bccaf..198f36c70 100644 --- a/src/controllers/badgeController.js +++ b/src/controllers/badgeController.js @@ -3,7 +3,7 @@ const UserProfile = require('../models/userProfile'); const helper = require('../utilities/permissions'); const escapeRegex = require('../utilities/escapeRegex'); const cacheClosure = require('../utilities/nodeCache'); -// const userHelper = require('../helpers/userHelper')(); +//const userHelper = require('../helpers/userHelper')(); const badgeController = function (Badge) { /** @@ -343,7 +343,7 @@ const badgeController = function (Badge) { return { - // awardBadgesTest, + //awardBadgesTest, getAllBadges, assignBadges, postBadge, diff --git a/src/controllers/formController.js b/src/controllers/formController.js new file mode 100644 index 000000000..e9eb5203d --- /dev/null +++ b/src/controllers/formController.js @@ -0,0 +1,338 @@ +const userprofile=require('../models/userProfile'); + +const formController = function (Form,formResponse) { + // creating a new form + const createForm =async function (req,res) { + try { + const { formName, questions, createdBy } = req.body; + + // Check if required fields are present + if (!formName || !questions || questions.length === 0) { + return res.status(400).json({ message: 'Form name and questions are required.' }); + } + + // check if form already exists or not + let form_temp=await Form.find({formName:formName}) + if(form_temp.length>0){return res.status(400).json({message:"Form already exists with that name"})} + // Create a new form with the provided structure + const newForm = new Form({ + formName, + questions, + createdBy, + }); + + // Save the form in the database + const savedForm = await newForm.save(); + + // Generate a unique link to the form + const formLink = `/forms/${savedForm.formID}`; + + return res.status(201).json({ + message: 'Form created successfully', + formID: savedForm.formID, + id:savedForm._id, + formLink: "hostname"+formLink, + }); + } catch (error) { + console.error('Error creating form:', error); + return res.status(500).json({ message: 'Server error, could not create form.' }); + } + } + + const editFormFormat = async function (req, res) { + try { + const { id, userId, formName, formQuestions } = req.body; + + // Fetch the existing form + const existingForm = await Form.findById(id); + if (!existingForm) { + return res.status(400).json({ message: "Invalid Form ID" }); + } + + // Check if user exists and is active + const user_temp = await userprofile.findById(userId); + if (!user_temp || user_temp.isActive === false) { + return res.status(400).json({ message: "Invalid or inactive user ID" }); + } + + let updateData = {}; + + // Check if the form name is actually changing + if (formName && formName !== existingForm.formName) { + updateData.formName = formName; + } + + // Validate and compare formQuestions before updating + if (formQuestions) { + if (!Array.isArray(formQuestions) || formQuestions.length === 0) { + return res.status(400).json({ message: "Questions must be a non-empty array" }); + } + + let isDifferent = false; + let newQuestions = []; + + for (let question of formQuestions) { + if (!question.label || typeof question.label !== "string") { + return res.status(400).json({ message: "Each question must have a valid 'label' of type string" }); + } + + if (!question.type || typeof question.type !== "string") { + return res.status(400).json({ message: "Each question must have a valid 'type' of type string" }); + } + + if (["radio", "checkbox"].includes(question.type)) { + if (!Array.isArray(question.options) || question.options.length === 0) { + return res.status(400).json({ message: `Question of type '${question.type}' must have a non-empty options array` }); + } + + for (let option of question.options) { + if (typeof option !== "string" || option.trim() === "") { + return res.status(400).json({ message: "Each option must be a valid non-empty string" }); + } + } + } + + // Check if this question already exists in the database + let existingQuestion = existingForm.questions.find(q => q.label === question.label); + + if (!existingQuestion || JSON.stringify(existingQuestion.options) !== JSON.stringify(question.options)) { + isDifferent = true; // Mark as different if any question changes + } + + newQuestions.push(question); + } + + // Only update if something actually changed + if (isDifferent) { + updateData.questions = newQuestions; + } + } + + // If there's nothing to update, return early + if (Object.keys(updateData).length === 0) { + return res.status(200).json({ message: "No changes detected" }); + } + + // Update the form + await Form.updateOne({ _id: id }, { $set: updateData }); + + return res.status(200).json({ message: "Form Updated Successfully" }); + + } catch (err) { + console.error("Error updating form:", err); + return res.status(500).json({ message: "Internal Server Error", error: err.message }); + } + }; + + const deleteFormFormat = async function(req,res){ + try { + // here id is the record Id of the form. + const {id}=req.body; + let result=await Form.deleteOne({ _id : id}); + // Check if the form was actually deleted + if (result.deletedCount === 0) { + return res.status(400).json({ message: 'Error removing Form.' }); + } + return res.status(200).json({message:"Form Deleted Successfully"}) + }catch(error){ + return res.status(400).json({message:"Error removing Form."}) + } + } + + // check if a user has already responded to a form or not. + const checkForResponse = async function(req,res) { + try{ + const {formID,userID}=req.query; + let result=await formResponse.find({formID:formID,submittedBy:userID}) + if(result.length==0){ + return res.status(400).json({message:"No records Found"}); + } + return res.status(200).json({message:result}) + }catch(error){ + return res.status(404).json({message:"Error Searching for Recods"}) + } + } + + const getFormData =async function (req,res) { + try { + const formID = req.query.formID; + // Check if formID is provided + if (!formID) { + return res.status(400).json({ message: 'Form ID is required.' }); + } + + // Check if the form exists + const form = await Form.findOne({ formID }); + if (!form || form.length===0) { + return res.status(404).json({ message: 'Form not found.' }); + } + + // Fetch all responses associated with the formID + const responses = await formResponse.find({ formID }); + + // If no responses found, return a message + if (responses.length === 0) { + return res.status(404).json({ message: 'No responses found for this form.' }); + } + + return res.status(200).json({ + message: 'Responses retrieved successfully', + formID: formID, + formName: form.formName, + responses: responses, + }); + + } catch (error) { + console.error('Error fetching form responses:', error); + return res.status(500).json({ message: 'Server error, could not fetch form responses.' }); + } + } + + const addDataToForm = async function (req, res) { + try { + const { formID, responses, submittedBy } = req.body; + + // Ensure all required fields are present + if (!formID || !responses || responses.length === 0) { + return res.status(400).json({ message: 'Form ID and responses are required.' }); + } + if (!submittedBy) { + return res.status(400).json({ message: 'User ID is required to submit this form.' }); + } + + // Check if the form exists + const form = await Form.findOne({ formID }); + if (!form) { + return res.status(404).json({ message: 'Form not found.' }); + } + + // Check if user exists and is active + const user = await userprofile.findById(submittedBy); + if (!user || user.isActive === false) { + return res.status(400).json({ message: 'Invalid or inactive user.' }); + } + + // Validate responses against the form questions + if (!Array.isArray(responses)) { + return res.status(400).json({ message: 'Responses must be an array.' }); + } + + const formQuestions = form.questions; + if (responses.length !== formQuestions.length) { + return res.status(400).json({ message: 'Number of responses does not match the number of form questions.' }); + } + + const validatedResponses = []; + + for (let i = 0; i < responses.length; i++) { + const question = formQuestions[i]; + const response = responses[i]; + + // Ensure response has the required label and value + if (!response.questionLabel || response.answer === undefined) { + return res.status(400).json({ message: `Response for question "${question.label}" is incomplete.` }); + } + + // Ensure response label matches the expected question label + if (response.questionLabel !== question.label) { + return res.status(400).json({ message: `Invalid question label: Expected "${question.label}", got "${response.questionLabel}".` }); + } + + // Validate response based on question type + if (question.type === 'radio') { + if (typeof response.answer !== 'string') { + return res.status(400).json({ message: `Response for "${question.label}" must be a single string value.` }); + } + if (!question.options.includes(response.answer)) { + return res.status(400).json({ message: `Invalid response for "${question.label}". Expected one of: ${question.options.join(', ')}` }); + } + } else if (question.type === 'checkbox') { + if (!Array.isArray(response.answer)) { + return res.status(400).json({ message: `Response for "${question.label}" must be an array of selected options.` }); + } + if (response.answer.length === 0) { + return res.status(400).json({ message: `At least one option must be selected for "${question.label}".` }); + } + const invalidOptions = response.answer.filter(option => !question.options.includes(option)); + if (invalidOptions.length > 0) { + return res.status(400).json({ message: `Invalid options selected for "${question.label}": ${invalidOptions.join(', ')}` }); + } + } else if (question.type === 'text') { + if (typeof response.answer !== 'string' || response.answer.trim() === '') { + return res.status(400).json({ message: `Response for "${question.label}" must be a non-empty text string.` }); + } + } else { + return res.status(400).json({ message: `Invalid question type: "${question.type}"` }); + } + + // Push the validated response into the validatedResponses array + validatedResponses.push({ + questionLabel: response.questionLabel, + answer: response.answer, + }); + } + + // Create and save the valid response + const formResp = new formResponse({ + formID, + responses: validatedResponses, + submittedBy, + }); + + const savedResponse = await formResp.save(); + + return res.status(201).json({ + message: 'Form response submitted successfully', + responseID: savedResponse._id, + }); + + } catch (error) { + console.error('Error submitting form response:', error); + return res.status(500).json({ message: 'Server error, could not submit form response.' }); + } + }; + + + const getAllForms = async (req,res)=>{ + try{ + const result=await Form.find({}) + return res.status(200).json({data:result}) + }catch(err){ + return res.status(404).json({error:err}) + } + } + + const getFormFormat = async (req,res)=>{ + try{ + // const formID=req.params.id; + const {formID, userId}=req.body; + console.log(formID) + console.log(userId); + const result=await Form.find({formID}) + console.log(result) + if (!result || result.length===0) { + return res.status(404).json({ message: 'Form not found.' }); + } + + let user=await userprofile.find({_id:userId}) + if(user[0].isActive === false || user===undefined || user === null || user.length===0){ + return res.status(400).json({message: 'Invalid User'}); + } + return res.status(200).json({data:result}) + }catch(err){ + return res.status(404).json({data:err}) + } + } + return { + createForm, + getFormData, + addDataToForm, + getAllForms, + getFormFormat, + editFormFormat, + deleteFormFormat, + checkForResponse + } +}; + +module.exports = formController; \ No newline at end of file diff --git a/src/controllers/formController.spec.js b/src/controllers/formController.spec.js new file mode 100644 index 000000000..c37542087 --- /dev/null +++ b/src/controllers/formController.spec.js @@ -0,0 +1,191 @@ +// test/formController.spec.js +const { createForm, editFormFormat, deleteFormFormat, checkForResponse, getFormData, addDataToForm, getAllForms, getFormFormat } = require('./formController')( + require('../models/forms.js'), + require('../models/formResponse.js') + ); + const Form = require('../models/forms'); + const FormResponse = require('../models/formResponse'); + const UserProfile = require('../models/userProfile'); + + jest.mock('../models/forms'); + jest.mock('../models/formResponse'); + jest.mock('../models/userProfile'); + + describe('Form Controller', () => { + let req, res; + + beforeEach(() => { + req = { body: {}, params: {}, query: {} }; + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + }); + + describe('createForm', () => { + it('should create a form successfully', async () => { + req.body = { formName: 'Test Form', questions: ['Question 1'], createdBy: 'user123' }; + + Form.prototype.save = jest.fn().mockResolvedValue({ formID: 'form123', _id: 'testId' }); + + await createForm(req, res); + + expect(res.status).toHaveBeenCalledWith(201); + expect(res.json).toHaveBeenCalledWith({ + message: 'Form created successfully', + formID: 'form123', + id: 'testId', + formLink: expect.stringContaining('/forms/'), + }); + }); + + it('should return 400 if formName or questions are missing', async () => { + req.body = { formName: '', questions: [] }; + + await createForm(req, res); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ message: 'Form name and questions are required.' }); + }); + }); + + describe('editFormFormat', () => { + it('should edit a form successfully', async () => { + req.body = { id: 'testId', formName: 'Updated Form', formQuestions: ['New Question'], userId: 'user123' }; + Form.findById = jest.fn().mockResolvedValue({ _id: 'testId' }); + UserProfile.findById = jest.fn().mockResolvedValue({ _id: 'user123', isActive: true }); + Form.updateOne = jest.fn().mockResolvedValue({}); + + await editFormFormat(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ message: 'Form Updated' }); + }); + + it('should return 400 if form does not exist', async () => { + Form.findById = jest.fn().mockResolvedValue(null); + + await editFormFormat(req, res); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ message: 'Invalid FormID' }); + }); + + it('should return 400 if user is inactive', async () => { + Form.findById = jest.fn().mockResolvedValue({ _id: 'testId' }); + UserProfile.findById = jest.fn().mockResolvedValue({ isActive: false }); + + await editFormFormat(req, res); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ message: 'Invalid userid' }); + }); + }); + + describe('deleteFormFormat', () => { + it('should delete a form successfully', async () => { + req.body = { formID: 'testId' }; + Form.deleteOne = jest.fn().mockResolvedValue({ deletedCount: 1 }); + + await deleteFormFormat(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ message: 'Form Deleted Successfully' }); + }); + + it('should return 400 if form deletion fails', async () => { + req.body = { formID: 'invalidId' }; + Form.deleteOne = jest.fn().mockResolvedValue({ deletedCount: 0 }); + + await deleteFormFormat(req, res); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ message: 'Error removing Form.' }); + }); + }); + + describe('checkForResponse', () => { + it('should return 400 if no responses are found', async () => { + req.query = { formID: 'form123', userID: 'user123' }; + FormResponse.find = jest.fn().mockResolvedValue([]); + + await checkForResponse(req, res); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ message: 'No records Found' }); + }); + + it('should return 200 if responses are found', async () => { + req.query = { formID: 'form123', userID: 'user123' }; + FormResponse.find = jest.fn().mockResolvedValue([{ answer: 'Yes' }]); + + await checkForResponse(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ message: [{ answer: 'Yes' }] }); + }); + }); + + describe('getFormData', () => { + it('should retrieve form data with responses successfully', async () => { + req.params.id = 'form123'; + Form.findOne = jest.fn().mockResolvedValue({ formID: 'form123', formName: 'Test Form' }); + FormResponse.find = jest.fn().mockResolvedValue([{ response: 'Sample Response' }]); + + await getFormData(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ + message: 'Responses retrieved successfully', + formID: 'form123', + formName: 'Test Form', + responses: [{ response: 'Sample Response' }], + }); + }); + }); + + describe('addDataToForm', () => { + it('should add a response to a form successfully', async () => { + req.body = { + formID: 'form123', + responses: [{ questionLabel: 'What is your favorite color?', answer: 'Blue' }], + submittedBy: 'user123', + }; + Form.findOne = jest.fn().mockResolvedValue({ formID: 'form123' }); + UserProfile.find = jest.fn().mockResolvedValue([{ _id: 'user123', isActive: true }]); + FormResponse.prototype.save = jest.fn().mockResolvedValue({ _id: 'response123' }); + + await addDataToForm(req, res); + + expect(res.status).toHaveBeenCalledWith(201); + expect(res.json).toHaveBeenCalledWith({ + message: 'Form response submitted successfully', + responseID: 'response123', + }); + }); + }); + + describe('getAllForms', () => { + it('should retrieve all forms successfully', async () => { + Form.find = jest.fn().mockResolvedValue([{ formID: 'form123', formName: 'Test Form' }]); + + await getAllForms(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ data: [{ formID: 'form123', formName: 'Test Form' }] }); + }); + }); + + describe('getFormFormat', () => { + it('should retrieve form format successfully', async () => { + req.params.id = 'form123'; + Form.find = jest.fn().mockResolvedValue([{ formID: 'form123', formName: 'Test Form' }]); + + await getFormFormat(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ data: [{ formID: 'form123', formName: 'Test Form' }] }); + }); + }); + }); + \ No newline at end of file diff --git a/src/controllers/userProfileController.js b/src/controllers/userProfileController.js index 69c9a5431..07ba75e1e 100644 --- a/src/controllers/userProfileController.js +++ b/src/controllers/userProfileController.js @@ -1653,7 +1653,6 @@ const userProfileController = function (UserProfile, Project) { res.status(403).send('You are not authorized to add blue square'); return; } - const userid = req.params.userId; cache.removeCache(`user-${userid}`); @@ -1668,9 +1667,6 @@ const userProfileController = function (UserProfile, Project) { res.status(404).send('No valid records found'); return; } - - req.body.blueSquare.reasons = ['other']; - // find userData in cache const isUserInCache = cache.hasCache('allusers'); let allUserData; @@ -1719,7 +1715,7 @@ const userProfileController = function (UserProfile, Project) { return; } const { userId, blueSquareId } = req.params; - const { dateStamp, summary, reasons } = req.body; + const { dateStamp, summary } = req.body; UserProfile.findById(userId, async (err, record) => { if (err || !record) { @@ -1733,9 +1729,6 @@ const userProfileController = function (UserProfile, Project) { if (blueSquare._id.equals(blueSquareId)) { blueSquare.date = dateStamp ?? blueSquare.date; blueSquare.description = summary ?? blueSquare.description; - if (Array.isArray(reasons)) { - blueSquare.reasons = reasons; - } } return blueSquare; }); @@ -1768,6 +1761,7 @@ const userProfileController = function (UserProfile, Project) { return; } const { userId, blueSquareId } = req.params; + UserProfile.findById(userId, async (err, record) => { if (err || !record) { res.status(404).send('No valid records found'); diff --git a/src/helpers/checkXHrsForXWeeks.test.js b/src/helpers/checkXHrsForXWeeks.test.js new file mode 100644 index 000000000..7a9a02931 --- /dev/null +++ b/src/helpers/checkXHrsForXWeeks.test.js @@ -0,0 +1,24 @@ +const userHelper = require('../helpers/userHelper'); +const { checkXHrsForXWeeks,increaseBadgeCount, addBadge, userProfile, badge } = require('../helpers/userHelper'); + +async function testCheckXHrsForXWeeks() { + let personId = '6722a58e71a31f53b4063d49'; + let badgeCollection = []; + let testCases = [ + [30, 2], [30, 3], [30, 4], [30, 6], [30, 10], [30, 15], [30, 20], [30, 40], + [30, 60], [30, 80], [30, 100], [30, 150], [30, 200], [40, 2], [40, 3], [40, 4], + [40, 6], [40, 10], [40, 15], [40, 20], [40, 40], [40, 60], [40, 80], [40, 100], + [40, 150], [40, 200], [50, 2], [50, 3], [50, 4], [50, 6], [60, 2], [60, 3], + [60, 4], [60, 6] + ]; + + for (let [hours, weeks] of testCases) { + let user = { savedTangibleHrs: new Array(weeks).fill(hours) }; + + console.log(`Testing ${hours} hours for ${weeks} weeks streak...`); + await checkXHrsForXWeeks(personId, user, badgeCollection); + console.log(`Test completed for ${hours} hours and ${weeks} weeks.\n`); + } +} + +testCheckXHrsForXWeeks(); diff --git a/src/helpers/userHelper.js b/src/helpers/userHelper.js index 0a8d7e332..3b08a47b9 100644 --- a/src/helpers/userHelper.js +++ b/src/helpers/userHelper.js @@ -204,7 +204,6 @@ const userHelper = function () {
Oops, it looks like something happened and you’ve managed to get a blue square.
Date Assigned: ${moment(infringement.date).format('M-D-YYYY')}
\Description: ${emailDescription}
- ${infringement.reasons?.length ? `Reasons: ${infringement.reasons.join(', ')}
` : ''} ${descrInfringement} ${finalParagraph}Thank you,
@@ -1305,10 +1304,7 @@ const userHelper = function () { getInfringementEmailBody( firstName, lastName, - { - ...element, - reasonsText: element.reasons, - }, + element, totalInfringements, undefined, undefined, @@ -1357,6 +1353,21 @@ const userHelper = function () { ); }; + const decreaseBadgeCount = async function (personId, badgeId) { + try { + const result = await userProfile.updateOne( + { _id: personId, 'badgeCollection.badge': badgeId,}, + { + $inc: { 'badgeCollection.$.count': -1 }, + $set: { 'badgeCollection.$.lastModified': Date.now().toString() }, + } + ); + + } catch (error) { + console.error("Error decrementing badge count:", error); + } +}; + const addBadge = async function (personId, badgeId, count = 1, featured = false) { userProfile.findByIdAndUpdate( personId, @@ -1745,221 +1756,165 @@ const userHelper = function () { }; // 'X Hours in one week', const checkXHrsInOneWeek = async function (personId, user, badgeCollection) { + // Set lastWeek value + const lastWeek = user.savedTangibleHrs[user.savedTangibleHrs.length-1]; + const badgesOfType = []; for (let i = 0; i < badgeCollection.length; i += 1) { if (badgeCollection[i].badge?.type === 'X Hours for X Week Streak') { badgesOfType.push(badgeCollection[i].badge); } } + await badge - .find({ type: 'X Hours for X Week Streak', weeks: 1 }) + .find({ type: 'X Hours for X Week Streak', weeks: 1 }) .sort({ totalHrs: -1 }) .then((results) => { results.every((elem) => { - if (elem.totalHrs <= user.lastWeekTangibleHrs) { - let theBadge; + const badgeName = `${elem.totalHrs} Hours in 1 Week`; + + if (elem.totalHrs=== lastWeek) { + + let theBadge = null; for (let i = 0; i < badgesOfType.length; i += 1) { if (badgesOfType[i]._id.toString() === elem._id.toString()) { theBadge = badgesOfType[i]._id; break; } } + if (theBadge) { increaseBadgeCount(personId, mongoose.Types.ObjectId(theBadge)); - return false; + } else { + addBadge(personId, mongoose.Types.ObjectId(elem._id)); } - addBadge(personId, mongoose.Types.ObjectId(elem._id)); - return false; + return false; // Exit the loop early } - return true; + return true; }); + }) + .catch((error) => { + console.error("Error while fetching badges or processing results:", error); }); }; + + // 'X Hours for X Week Streak', + const checkXHrsForXWeeks = async (personId, user, badgeCollection) => { + try { + if (user.savedTangibleHrs.length === 0) { + console.log("No tangible hours available."); + return; + } - // 'X Hours for X Week Streak', - const checkXHrsForXWeeks = async function (personId, user, badgeCollection) { - let higherBadge = false; - // Check each Streak Greater than One to check if it works - await badge - .aggregate([ - { $match: { type: 'X Hours for X Week Streak', weeks: { $gt: 1 } } }, - // Group by 'week' property and sorting groups in descending order by 'week', then sorting badges within groups by 'totalHrs' in descending order. - { - $group: { - _id: '$weeks', - badges: { - $push: { _id: '$_id', hrs: '$totalHrs', weeks: '$weeks' }, - }, - }, - }, - { - $project: { - _id: 1, - badges: { - $slice: [ - { - $map: { - input: '$badges', - in: { - _id: '$$this._id', - hrs: '$$this.hrs', - weeks: '$$this.weeks', - }, - }, - }, - { $size: '$badges' }, - ], - }, - }, - }, - { $unwind: '$badges' }, - { $sort: { _id: -1, 'badges.hrs': -1 } }, // Primary sort on _id, secondary sort on badges.hrs - { - $group: { - _id: '$_id', - badges: { - $push: { - _id: '$badges._id', - hrs: '$badges.hrs', - weeks: '$badges.weeks', - }, - }, - }, - }, - { $sort: { _id: -1 } }, // Add this $sort stage for the final sorting by _id - ]) - .then((results) => { - let lastHr = -1; - results.forEach((streak) => { - streak.badges.every((bdge) => { - let badgeOfType; - for (let i = 0; i < badgeCollection.length; i += 1) { - if ( - badgeCollection[i].badge?.type === 'X Hours for X Week Streak' && - badgeCollection[i].badge?.weeks === bdge.weeks - ) { - if (badgeOfType && badgeOfType.totalHrs <= badgeCollection[i].badge.totalHrs) { - removeDupBadge(personId, badgeOfType._id); - badgeOfType = badgeCollection[i].badge; - } else if ( - badgeOfType && - badgeOfType.totalHrs > badgeCollection[i].badge.totalHrs - ) { - removeDupBadge(personId, badgeCollection[i].badge._id); - } else if (!badgeOfType) { - badgeOfType = badgeCollection[i].badge; - } - } + const savedTangibleHrs = user.savedTangibleHrs; + const currentMaxHours = savedTangibleHrs[savedTangibleHrs.length - 1]; + let streak = 0; + + for (let i = savedTangibleHrs.length - 1; i >= 0; i--) { + if (savedTangibleHrs[i] === currentMaxHours) { + streak++; + } else { + break; } - // check if it is possible to earn this streak - if (user.savedTangibleHrs.length >= bdge.weeks) { - let awardBadge = true; - const endOfArr = user.savedTangibleHrs.length - 1; - for (let i = endOfArr; i >= endOfArr - bdge.weeks + 1; i -= 1) { - if (user.savedTangibleHrs[i] < bdge.hrs) { - awardBadge = false; - return true; + } + + console.log("Calculated streak:", streak); + + if (streak === 0) { + console.log("No valid streak found."); + return; + } + + if (streak === 1) { + await checkXHrsInOneWeek(personId, user, badgeCollection); + return; + } + + // Fetch matching badges + const allBadges = await badge.find({ + badgeName: { + $in: [ + `${currentMaxHours} HOURS ${streak}-WEEK STREAK`, + `${currentMaxHours}-Hours Streak ${streak} Weeks in a Row`, + `${currentMaxHours} Hours Streak ${streak}-WEEk STREAK`, + `${currentMaxHours} Hours Streak ${streak}-WEEK STREAK` + ] + } + }); + + if (allBadges.length === 0) { + return; + } + + const newBadge = allBadges[0]; + + if (!badgeCollection || !Array.isArray(badgeCollection)) { + return; + } + + let badgeInCollection = null; + for (let i = 0; i < badgeCollection.length; i++) { + if (!badgeCollection[i] || !badgeCollection[i].badge) continue; // Skip invalid entries + + + if (badgeCollection[i].badge.badgeName === newBadge.badgeName) { + badgeInCollection = badgeCollection[i]; + break; + } + } + + if (badgeInCollection) { + console.log(`Badge already exists: ${newBadge.badgeName}, increasing count.`); + await increaseBadgeCount(personId, newBadge._id); + return; + } + + + // Loop through badgeCollection to find and handle replacements or downgrades + for (let j = badgeCollection.length - 1; j >= 0; j--) { + let lastBadge = badgeCollection[j]; + + + if (!lastBadge || !lastBadge.badge) { + continue; + } + + console.log("lastBadge.badge.totalHrs ::", lastBadge.badge.totalHrs === currentMaxHours); + + if (lastBadge.badge.totalHrs === currentMaxHours) { + // Check if the badge is eligible for downgrade or replacement + if (lastBadge.badge.weeks < streak && lastBadge.count > 1) { + await decreaseBadgeCount(personId, lastBadge.badge._id); + + console.log(`Adding new badge: ${newBadge.badgeName}`); + await addBadge(personId, newBadge._id); + return; } - } - // if all checks for award badge are green double check that we havent already awarded a higher streak for the same number of hours - if (awardBadge && bdge.hrs > lastHr) { - higherBadge = true; - lastHr = bdge.hrs; - if (badgeOfType && badgeOfType.totalHrs < bdge.hrs) { - replaceBadge( - personId, - mongoose.Types.ObjectId(badgeOfType._id), - mongoose.Types.ObjectId(bdge._id), - ); - removePrevHrBadge(personId, user, badgeCollection, bdge.hrs, bdge.weeks); - } else if (!badgeOfType) { - addBadge(personId, mongoose.Types.ObjectId(bdge._id)); - removePrevHrBadge(personId, user, badgeCollection, bdge.hrs, bdge.weeks); - } else if (badgeOfType && badgeOfType.totalHrs === bdge.hrs) { - const lowerBound = badgeOfType.weeks; - let upperBound; - streak = 0; - - switch (bdge.weeks) { - case 2: - // In between 2Wk and 3Wk - upperBound = 3; - break; - case 3: - // In between 3Wk and 4Wk - upperBound = 4; - break; - case 4: - // In between 4Wk and 6Wk - upperBound = 6; - break; - case 6: - // In between 6Wk and 10Wk - upperBound = 10; - break; - case 10: - // In between 10Wk and 15Wk - upperBound = 15; - break; - case 15: - // In between 50Wk and 20Wk - upperBound = 20; - break; - case 20: - // In between 20Wk and 40Wk - upperBound = 40; - break; - case 40: - // In between 40Wk and 60Wk - upperBound = 60; - break; - case 60: - // In between 60Wk and 80Wk - upperBound = 80; - break; - case 80: - // In between 80Wk and 100Wk - upperBound = 100; - break; - case 100: - // In between 100Wk and 150Wk - upperBound = 150; - break; - case 150: - // In between 150Wk and 200Wk - upperBound = 200; - break; - default: - // Default case. Exiting function. - return; - } - for (let i = endOfArr; i >= endOfArr - upperBound + 1; i -= 1) { - if (user.savedTangibleHrs[i] >= bdge.hrs) { - streak += 1; - } - } - if (streak > lowerBound && streak < upperBound) { - higherBadge = false; - console.log('You are currently building an existing streak, no badge awarded.'); - } else { - console.log('You are currently building a new streak, new badge awarded'); - increaseBadgeCount(personId, mongoose.Types.ObjectId(badgeOfType._id)); - removePrevHrBadge(personId, user, badgeCollection, bdge.hrs, bdge.weeks); - } + if (lastBadge.badge.weeks < streak) { + await userProfile.updateOne( + { _id: personId, "badgeCollection.badge": lastBadge.badge._id }, + { + $set: { + "badgeCollection.$.badge": newBadge._id, + "badgeCollection.$.lastModified": Date.now().toString(), + "badgeCollection.$.count": 1, + "badgeCollection.$.earnedDate": [earnedDateBadge()], + }, + } + ); + return; } - return false; - } } - return true; - }); - }); - }); + } - // Handle Increasing the 1 week streak badges - if (!higherBadge) await checkXHrsInOneWeek(personId, user, badgeCollection); - }; + await addBadge(personId, newBadge._id); + } catch (error) { + console.error("Error in checkXHrsForXWeeks function:", error); + } + }; + // 'Lead a team of X+' const checkLeadTeamOfXplus = async function (personId, user, badgeCollection) { @@ -2119,7 +2074,8 @@ const userHelper = function () { const awardNewBadges = async () => { try { - const users = await userProfile.find({ isActive: true }).populate('badgeCollection.badge'); + const users = await userProfile.find({isActive: true}).populate('badgeCollection.badge'); + console.log("awardNewBadge working") for (let i = 0; i < users.length; i += 1) { const user = users[i]; const { _id, badgeCollection } = user; @@ -2133,7 +2089,7 @@ const userHelper = function () { await checkLeadTeamOfXplus(personId, user, badgeCollection); await checkXHrsForXWeeks(personId, user, badgeCollection); await checkNoInfringementStreak(personId, user, badgeCollection); - // remove cache after badge asssignment. + //remove cache after badge asssignment. if (cache.hasCache(`user-${_id}`)) { cache.removeCache(`user-${_id}`); } diff --git a/src/models/formResponse.js b/src/models/formResponse.js new file mode 100644 index 000000000..8ad7bbff5 --- /dev/null +++ b/src/models/formResponse.js @@ -0,0 +1,25 @@ +const mongoose = require('mongoose'); + +// Define the schema for form responses +const FormResponseSchema = new mongoose.Schema({ + formID: { + type: String, + required: true, // Links the response to a specific form by formID + }, + responses: [ + { + questionLabel: { type: String, required: true }, // The label of the question (e.g., "What is your name?") + answer: { type: mongoose.Schema.Types.Mixed, required: true }, // The answer can be of any type (string, number, array) + }, + ], + submittedAt: { + type: Date, + default: Date.now, // Date and time when the form was submitted + }, + submittedBy: { + type: String, // ID of the user who submitted the form (optional, depends on your app’s logic) + required:true + }, +}); + +module.exports = mongoose.model('FormResponse', FormResponseSchema); diff --git a/src/models/forms.js b/src/models/forms.js new file mode 100644 index 000000000..ab8ddaae4 --- /dev/null +++ b/src/models/forms.js @@ -0,0 +1,34 @@ +const mongoose = require('mongoose'); +const { v4: uuidv4 } = require('uuid'); + +// Define the schema for a form +const FormSchema = new mongoose.Schema({ + formID: { + type: String, + default: uuidv4, // Automatically generates a unique ID for each form + unique: true, + }, + formName: { + type: String, + required: true, + unique:true, + }, + questions: [ + { + label: { type: String, required: true }, // Question label + type: { type: String, required: true }, // e.g., 'text', 'radio', 'checkbox' + options: [String], // For questions with options (e.g., radio buttons, checkboxes) + }, + ], + createdAt: { + type: Date, + default: Date.now, + }, + createdBy: { + // user id of the user who created it. + type:String, + required:true + } +}); + +module.exports = mongoose.model('Form', FormSchema); \ No newline at end of file diff --git a/src/models/userProfile.js b/src/models/userProfile.js index ee7f90e53..34961fc80 100644 --- a/src/models/userProfile.js +++ b/src/models/userProfile.js @@ -52,7 +52,7 @@ const userProfileSchema = new Schema({ index: true, }, phoneNumber: [{ type: String, phoneNumber: String }], - jobTitle: [{ type: String, jobTitle: String, required: true }], + jobTitle: [{ type: String, jobTitle: String }], bio: { type: String }, email: { type: String, @@ -112,7 +112,6 @@ const userProfileSchema = new Schema({ date: { type: String, required: true }, description: { type: String, required: true }, createdDate: { type: String }, - reasons: {type: [String], default: []}, }, ], warnings: [ diff --git a/src/routes/formRouter.js b/src/routes/formRouter.js new file mode 100644 index 000000000..0398f57bf --- /dev/null +++ b/src/routes/formRouter.js @@ -0,0 +1,28 @@ +const express=require('express') + +const routes=function(Form,formResponse){ + const controller=require('../controllers/formController')(Form,formResponse); + const formRouter=express.Router(); + + + formRouter.route('/form') + .post(controller.createForm) // route to create a new form + .get(controller.getAllForms) // route to get list of all forms + .get(controller.getFormFormat) // route to get format for a format + .put(controller.editFormFormat) // route to edit form format + .delete(controller.deleteFormFormat); // route to delete form format + + // route to add data to a form + formRouter.route('/form/response') + .post(controller.addDataToForm) // route to add data to a form + .get(controller.getFormData); // route to get form data for a specific form + + // route to check if user has responded to a specific form or not + formRouter.route('/form/status').get(controller.checkForResponse); + + return formRouter; +} + +module.exports=routes; +// add a form, edit a form, delete a form, update a form format +// \ No newline at end of file diff --git a/src/routes/formRouter.test.js b/src/routes/formRouter.test.js new file mode 100644 index 000000000..5307fc837 --- /dev/null +++ b/src/routes/formRouter.test.js @@ -0,0 +1,210 @@ +// test/formRoutes.test.js +const request = require('supertest'); +const { jwtPayload } = require('../test'); +const cache = require('../utilities/nodeCache')(); +const { app } = require('../app'); +const { + mockReq, + createUser, + mongoHelper: { dbConnect, dbDisconnect, dbClearCollections, dbClearAll }, +} = require('../test'); + +const agent = request.agent(app); + +describe('Form Routes', () => { + let user; + let token; + let reqBody; + + beforeAll(async () => { + await dbConnect(); + user = await createUser(); + token = jwtPayload(user); + reqBody = { + formName: 'Survey Form', + questions: [ + { + label: 'What is your favorite color?', + type: 'radio', + options: ['Red', 'Blue', 'Green'], + }, + ], + createdBy: user._id, + }; + }); + + beforeEach(async () => { + await dbClearCollections('forms', 'formResponses'); + }); + + afterAll(async () => { + await dbClearAll(); + await dbDisconnect(); + }); + + describe('POST /api/form/createform', () => { + it('should return 401 if authorization header is not present', async () => { + await agent.post('/api/form/createform').send(reqBody).expect(401); + }); + + it('should create a form successfully', async () => { + const res = await agent + .post('/api/form/createform') + .set('Authorization', token) + .send(reqBody) + .expect(201); + + expect(res.body).toEqual({ + message: 'Form created successfully', + formID: expect.any(String), + id:expect.any(String), + formLink: expect.stringContaining('/forms/'), + }); + }); + + it('should return 400 if form name or questions are missing', async () => { + const invalidReqBody = { formName: '', questions: [] }; + const res = await agent + .post('/api/form/createform') + .set('Authorization', token) + .send(invalidReqBody) + .expect(400); + + expect(res.body).toHaveProperty('message', 'Form name and questions are required.'); + }); + }); + + describe('GET /api/form/allforms', () => { + it('should retrieve all forms', async () => { + cache.setCache('forms', JSON.stringify([{ formName: 'Survey Form' }])); + const res = await agent + .get('/api/form/allforms') + .set('Authorization', token) + .expect(200); + + expect(res.body).toHaveProperty('data'); + }); + }); + + describe('DELETE /api/form/delete', () => { + it('should return 400 if the form does not exist', async () => { + const res = await agent + .delete('/api/form/delete') + .set('Authorization', token) + .send({ formID: 'nonexistentID' }) + .expect(400); + + expect(res.body).toHaveProperty('message', 'Error removing Form.'); + }); + }); + + describe('POST /api/form/edit', () => { + it('should edit a form successfully', async () => { + const form = await agent + .post('/api/form/createform') + .set('Authorization', token) + .send(reqBody); + + const res = await agent + .post('/api/form/edit') + .set('Authorization', token) + .send({ + id: form.body.id, + formName: reqBody.formName, + formQuestions: reqBody.questions, + userId: user._id, + }) + .expect(200); + + expect(res.body).toHaveProperty('message', 'Form Updated'); + }); + }); + + describe('GET /api/form/status', () => { + it('should check if user has responded to the form', async () => { + const form = await agent + .post('/api/form/createform') + .set('Authorization', token) + .send(reqBody); + + const res = await agent + .get(`/api/form/status?formID=${form.body.formID}&userID=${user._id}`) + .set('Authorization', token) + .expect(400); // Expecting 400 if no responses are found + + expect(res.body).toHaveProperty('message', 'No records Found'); + }); + }); + + describe('GET /api/form/:id', () => { + it('should retrieve form data by ID', async () => { + const form = await agent + .post('/api/form/createform') + .set('Authorization', token) + .send(reqBody); + + formID = form.body.formID; + + // Step 2: Submit a response to the form using the route + await agent + .post('/api/form/submit') + .set('Authorization', token) + .send({ + formID, + responses: [{ questionLabel: 'What is your favorite color?', answer: 'Blue' }], + submittedBy: user._id, + }) + .expect(201); + + const res = await agent + .get(`/api/form/${form.body.formID}`) + .set('Authorization', token) + .expect(200); + + expect(res.body).toHaveProperty('message', 'Responses retrieved successfully'); + expect(res.body).toHaveProperty('formID', form.body.formID); + }); + }); + + describe('POST /api/form/submit', () => { + it('should add data to a form successfully', async () => { + const form = await agent + .post('/api/form/createform') + .set('Authorization', token) + .send(reqBody); + const res = await agent + .post('/api/form/submit') + .set('Authorization', token) + .send({ + formID: form.body.formID, + responses: [{ questionLabel: reqBody.questions[0].label, answer: 'Blue' }], + submittedBy: user._id, + }) + .expect(201); + + expect(res.body).toHaveProperty('message', 'Form response submitted successfully'); + expect(res.body).toHaveProperty('responseID'); + }); + }); + + describe('GET /api/form/format/', () => { + it('should retrieve form format by ID', async () => { + const form = await agent + .post('/api/form/createform') + .set('Authorization', token) + .send(reqBody); + + const res = await agent + .post(`/api/form/format`) + .set('Authorization', token) + .send({ + formID:form.body.formID, + userId:user._id + }) + .expect(200); + + expect(res.body).toHaveProperty('data'); + }); + }); + +}); diff --git a/src/startup/routes.js b/src/startup/routes.js index 99b339849..5f0b0827d 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -103,7 +103,9 @@ const timeOffRequestRouter = require('../routes/timeOffRequestRouter')( userProfile, ); const followUpRouter = require('../routes/followUpRouter')(followUp); - +const form=require('../models/forms') +const formResponse=require('../models/formResponse') +const formRouter=require('../routes/formRouter')(form,formResponse); // bm dashboard const bmLoginRouter = require('../routes/bmdashboard/bmLoginRouter')(); const bmMaterialsRouter = require('../routes/bmdashboard/bmMaterialsRouter')(buildingMaterial); @@ -176,6 +178,7 @@ module.exports = function (app) { app.use('/api', timeOffRequestRouter); app.use('/api', followUpRouter); app.use('/api', blueSquareEmailAssignmentRouter); + app.use('/api',formRouter); app.use('/api', collaborationRouter); app.use('/api/jobs', jobsRouter); app.use('/api/questions', hgnformRouter);