diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..9f64215 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,19 @@ +{ + "env": { + "browser": true, + "node": true, + "es6": true + }, + "extends": ["eslint:recommended", "plugin:prettier/recommended"], + "rules": { + "prettier/prettier": [ + "error", + { + "singleQuote": true, + "semi": false, + "trailingComma": "es5" + } + ], + "no-console": "warn" + } +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..698c810 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "plugins": [ + "prettier-plugin-ejs" + ], + "singleQuote": true, + "semi": true, + "trailingComma": "es5" +} \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..2be0c5d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,9 @@ +import globals from 'globals' +import pluginJs from '@eslint/js' + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { files: ['**/*.js'], languageOptions: { sourceType: 'commonjs' } }, + { languageOptions: { globals: { ...globals.browser, ...globals.node } } }, + pluginJs.configs.recommended, +] diff --git a/package-lock.json b/package-lock.json index 41c8b20..52a2de9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,216 @@ "sequelize": "^6.37.5", "sqlite3": "^5.1.7", "uuid": "^11.0.5" + }, + "devDependencies": { + "@eslint/js": "^9.20.0", + "eslint": "^9.20.1", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.3", + "globals": "^15.15.0", + "prettier": "^3.5.1", + "prettier-plugin-ejs": "^1.0.3" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/@eslint/core": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", + "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.20.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", + "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.10.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@gar/promisify": { @@ -27,6 +237,67 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "optional": true }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@noble/ciphers": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", @@ -92,6 +363,18 @@ "node": ">=10" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", @@ -156,6 +439,18 @@ "@types/ms": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -192,6 +487,27 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -252,6 +568,22 @@ "node": ">=8" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -295,6 +627,12 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -466,6 +804,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -594,6 +941,20 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -624,6 +985,12 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -793,6 +1160,229 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.20.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", + "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.11.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.20.0", + "@eslint/plugin-kit": "^0.2.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", + "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", + "dev": true, + "bin": { + "eslint-config-prettier": "build/bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", + "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -854,6 +1444,42 @@ "url": "https://opencollective.com/express" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -903,6 +1529,41 @@ "node": ">= 0.8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1030,6 +1691,30 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1216,11 +1901,36 @@ } ] }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "optional": true, + "devOptional": true, "engines": { "node": ">=0.8.19" } @@ -1290,6 +2000,15 @@ "node": ">= 0.10" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1299,6 +2018,18 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -1309,7 +2040,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "optional": true + "devOptional": true }, "node_modules/jake": { "version": "10.9.2", @@ -1328,17 +2059,90 @@ "node": ">=10" } }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "optional": true }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/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 + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1603,6 +2407,12 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1742,6 +2552,53 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -1757,6 +2614,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1765,6 +2634,15 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1774,6 +2652,15 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -1920,6 +2807,51 @@ "node": ">=10" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", + "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-plugin-ejs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-ejs/-/prettier-plugin-ejs-1.0.3.tgz", + "integrity": "sha512-wTL4U/ou6dHHp1ZTfS67SHVb/dRgVhpIOTgCvkgdqF/Lw6A472W90dxFtsWSXIR7GmLZRgZb2PdArR9ozxX7cg==", + "dev": true, + "peerDependencies": { + "prettier": "2.x - 3.x" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -1960,6 +2892,15 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -2023,6 +2964,15 @@ "node": ">= 6" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -2247,6 +3197,27 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -2535,6 +3506,22 @@ "node": ">=8" } }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -2603,6 +3590,12 @@ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2614,6 +3607,18 @@ "node": "*" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2657,6 +3662,15 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2702,7 +3716,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "optional": true, + "devOptional": true, "dependencies": { "isexe": "^2.0.0" }, @@ -2730,6 +3744,15 @@ "@types/node": "*" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2747,6 +3770,18 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 20d5a81..919b5c0 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,20 @@ "start:containers": "docker compose up -d --build", "stop:containers": "docker compose down", "cli": "node src/cli.js", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "eslint . --fix", + "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,html,ejs}\"" }, "keywords": [], "author": "", - "license": "ISC" + "license": "ISC", + "devDependencies": { + "@eslint/js": "^9.20.0", + "eslint": "^9.20.1", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.3", + "globals": "^15.15.0", + "prettier": "^3.5.1", + "prettier-plugin-ejs": "^1.0.3" + } } diff --git a/src/app.js b/src/app.js index 76ff73c..ad8de10 100644 --- a/src/app.js +++ b/src/app.js @@ -27,5 +27,5 @@ app.use('/api', apiRoutes); app.use(express.static(path.join(__dirname, 'public'))); app.listen(port, () => { - console.log(`Server started on port ${port}`); + console.log(`Server started on port ${port}`); }); diff --git a/src/cli.js b/src/cli.js index dae73ab..3776ee3 100644 --- a/src/cli.js +++ b/src/cli.js @@ -3,13 +3,11 @@ const program = new Command(); const createAppInviteCommand = require('./commands/createAppInvite'); -program - .version('1.0.0') - .description('CLI for managing web app tasks'); +program.version('1.0.0').description('CLI for managing web app tasks'); program - .command('createAppInvite ') - .description('Create an invite') - .action(createAppInviteCommand); + .command('createAppInvite ') + .description('Create an invite') + .action(createAppInviteCommand); -program.parse(process.argv); \ No newline at end of file +program.parse(process.argv); diff --git a/src/commands/createAppInvite.js b/src/commands/createAppInvite.js index 2d9c6e6..98d2222 100644 --- a/src/commands/createAppInvite.js +++ b/src/commands/createAppInvite.js @@ -3,7 +3,7 @@ const { exec } = require('child_process'); const invitesService = require('../services/invitesService'); module.exports = async function createAppInvite(inviterNpub) { - const appInvite = await invitesService.createAppInvite(inviterNpub); - console.log('Invite created'); - console.log(`Check at http://localhost/invite/${appInvite.uuid}`); + const appInvite = await invitesService.createAppInvite(inviterNpub); + console.log('Invite created'); + console.log(`Check at http://localhost/invite/${appInvite.uuid}`); }; diff --git a/src/constants.js b/src/constants.js index 6cf88fe..8bec536 100644 --- a/src/constants.js +++ b/src/constants.js @@ -2,6 +2,6 @@ const DEFAULT_SESSION_DURATION_SECONDS = 60 * 60 * 24 * 30; const DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS = 60 * 60 * 24 * 30; module.exports = { - DEFAULT_SESSION_DURATION_SECONDS, - DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS -} \ No newline at end of file + DEFAULT_SESSION_DURATION_SECONDS, + DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS, +}; diff --git a/src/database.js b/src/database.js index addd67f..ba8a8dd 100644 --- a/src/database.js +++ b/src/database.js @@ -4,24 +4,27 @@ const dotenv = require('dotenv'); dotenv.config(); const sequelize = new Sequelize({ - dialect: 'postgres', - host: 'postgres', - port: 5432, - database: process.env.POSTGRES_DB, - username: process.env.POSTGRES_USER, - password: process.env.POSTGRES_PASSWORD, - define: { - timestamps: false, - freezeTableName: true, - underscored: true, - quoteIdentifiers: false - }, + dialect: 'postgres', + host: 'postgres', + port: 5432, + database: process.env.POSTGRES_DB, + username: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, + define: { + timestamps: false, + freezeTableName: true, + underscored: true, + quoteIdentifiers: false, + }, }); -sequelize.sync().then(() => { +sequelize + .sync() + .then(() => { console.log('Database synced'); -}).catch(err => { + }) + .catch((err) => { console.error('Error syncing the database:', err); -}); + }); -module.exports = sequelize; \ No newline at end of file +module.exports = sequelize; diff --git a/src/errors.js b/src/errors.js index 8c9c0b4..cb17044 100644 --- a/src/errors.js +++ b/src/errors.js @@ -1,34 +1,34 @@ class AlreadyUsedError extends Error { - constructor(message) { - super(message); - this.name = "AlreadyUsedError" - } + constructor(message) { + super(message); + this.name = 'AlreadyUsedError'; + } } class InvalidSignatureError extends Error { - constructor(message) { - super(message); - this.name = "InvalidSignatureError"; - } + constructor(message) { + super(message); + this.name = 'InvalidSignatureError'; + } } class NotFoundError extends Error { - constructor(message) { - super(message); - this.name = "AppInvitedUsedError"; - } + constructor(message) { + super(message); + this.name = 'AppInvitedUsedError'; + } } class ExpiredError extends Error { - constructor(message) { - super(message); - this.name = 'ExpiredError'; - } + constructor(message) { + super(message); + this.name = 'ExpiredError'; + } } module.exports = { - AlreadyUsedError, - InvalidSignatureError, - NotFoundError, - ExpiredError -}; \ No newline at end of file + AlreadyUsedError, + InvalidSignatureError, + NotFoundError, + ExpiredError, +}; diff --git a/src/middlewares/attachPublicKeyMiddleware.js b/src/middlewares/attachPublicKeyMiddleware.js index 23d4877..7c31b84 100644 --- a/src/middlewares/attachPublicKeyMiddleware.js +++ b/src/middlewares/attachPublicKeyMiddleware.js @@ -1,13 +1,12 @@ const sessionService = require('../services/sessionService'); async function attachPublicKeyMiddleware(req, res, next) { + const publicKey = await sessionService.getPublicKeyRelatedToSession( + req.cookies.sessionUuid + ); + req.cookies.publicKey = publicKey; - const publicKey = await sessionService.getPublicKeyRelatedToSession( - req.cookies.sessionUuid - ) - req.cookies.publicKey = publicKey; - - next(); + next(); } module.exports = attachPublicKeyMiddleware; diff --git a/src/middlewares/redirectIfNotAuthorizedMiddleware.js b/src/middlewares/redirectIfNotAuthorizedMiddleware.js index 04715c9..fb2899e 100644 --- a/src/middlewares/redirectIfNotAuthorizedMiddleware.js +++ b/src/middlewares/redirectIfNotAuthorizedMiddleware.js @@ -1,10 +1,10 @@ const sessionService = require('../services/sessionService'); async function redirectIfNotAuthorizedMiddleware(req, res, next) { - if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) { - res.redirect('/'); - } - next(); + if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) { + res.redirect('/'); + } + next(); } module.exports = redirectIfNotAuthorizedMiddleware; diff --git a/src/middlewares/rejectIfNotAuthorizedMiddleware.js b/src/middlewares/rejectIfNotAuthorizedMiddleware.js index cd70f54..85e67ad 100644 --- a/src/middlewares/rejectIfNotAuthorizedMiddleware.js +++ b/src/middlewares/rejectIfNotAuthorizedMiddleware.js @@ -1,13 +1,13 @@ const sessionService = require('../services/sessionService'); async function rejectIfNotAuthorizedMiddleware(req, res, next) { - if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) { - return res.status(403).json({ - success: false, - message: 'Your session is not authorized.' - }) - } - next(); + if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) { + return res.status(403).json({ + success: false, + message: 'Your session is not authorized.', + }); + } + next(); } module.exports = rejectIfNotAuthorizedMiddleware; diff --git a/src/middlewares/sessionMiddleware.js b/src/middlewares/sessionMiddleware.js index 3a68880..8d181d6 100644 --- a/src/middlewares/sessionMiddleware.js +++ b/src/middlewares/sessionMiddleware.js @@ -1,32 +1,33 @@ -const uuid = require("uuid"); +const uuid = require('uuid'); const sessionService = require('../services/sessionService'); const constants = require('../constants'); async function setAndPersistNewSession(res) { - const sessionUuid = uuid.v7(); - res.cookie('sessionUuid', sessionUuid, { httpOnly: true, maxAge: constants.DEFAULT_SESSION_DURATION_SECONDS * 1000 }); - return await sessionService.createSession(sessionUuid); + const sessionUuid = uuid.v7(); + res.cookie('sessionUuid', sessionUuid, { + httpOnly: true, + maxAge: constants.DEFAULT_SESSION_DURATION_SECONDS * 1000, + }); + return await sessionService.createSession(sessionUuid); } async function createSessionMiddleware(req, res, next) { + const sessionUuid = req.cookies.sessionUuid; - const sessionUuid = req.cookies.sessionUuid; - - if (!sessionUuid) { - const newSession = await setAndPersistNewSession(res); - req.cookies.sessionUuid = newSession.uuid; + if (!sessionUuid) { + const newSession = await setAndPersistNewSession(res); + req.cookies.sessionUuid = newSession.uuid; + } + if (sessionUuid) { + if (!(await sessionService.isSessionValid(sessionUuid))) { + const newSession = await setAndPersistNewSession(res); + req.cookies.sessionUuid = newSession.uuid; } + } - if (sessionUuid) { - if (!(await sessionService.isSessionValid(sessionUuid))) { - const newSession = await setAndPersistNewSession(res); - req.cookies.sessionUuid = newSession.uuid; - } - } - - next(); + next(); } -module.exports = createSessionMiddleware; \ No newline at end of file +module.exports = createSessionMiddleware; diff --git a/src/models/AppInviteCreated.js b/src/models/AppInviteCreated.js index 776d239..49967b3 100644 --- a/src/models/AppInviteCreated.js +++ b/src/models/AppInviteCreated.js @@ -1,23 +1,27 @@ const { DataTypes } = require('sequelize'); const sequelize = require('../database'); -const AppInviteCreated = sequelize.define('AppInviteCreated', { +const AppInviteCreated = sequelize.define( + 'AppInviteCreated', + { uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, }, inviter_pub_key: { - type: DataTypes.STRING, - allowNull: false, + type: DataTypes.STRING, + allowNull: false, }, created_at: { - type: DataTypes.DATE, - allowNull: false - } -}, { - tableName: 'app_invite_created' -}); + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'app_invite_created', + } +); -module.exports = AppInviteCreated; \ No newline at end of file +module.exports = AppInviteCreated; diff --git a/src/models/ContactDetailsSet.js b/src/models/ContactDetailsSet.js index 0e6e635..8722e89 100644 --- a/src/models/ContactDetailsSet.js +++ b/src/models/ContactDetailsSet.js @@ -1,27 +1,31 @@ const { DataTypes } = require('sequelize'); const sequelize = require('../database'); -const ContactDetailsSet = sequelize.define('ContactDetailsSet', { +const ContactDetailsSet = sequelize.define( + 'ContactDetailsSet', + { uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, }, public_key: { - type: DataTypes.STRING, - allowNull: false + type: DataTypes.STRING, + allowNull: false, }, encrypted_contact_details: { - type: DataTypes.TEXT, - allowNull: false + type: DataTypes.TEXT, + allowNull: false, }, created_at: { - type: DataTypes.DATE, - allowNull: false - } -}, { - tableName: 'contact_details_set' -}); + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'contact_details_set', + } +); -module.exports = ContactDetailsSet; \ No newline at end of file +module.exports = ContactDetailsSet; diff --git a/src/models/NostrChallengeCompleted.js b/src/models/NostrChallengeCompleted.js index b51b4bb..a656edd 100644 --- a/src/models/NostrChallengeCompleted.js +++ b/src/models/NostrChallengeCompleted.js @@ -1,31 +1,35 @@ const { DataTypes } = require('sequelize'); const sequelize = require('../database'); -const NostrChallengeCompleted = sequelize.define('NostrChallengeCompleted', { +const NostrChallengeCompleted = sequelize.define( + 'NostrChallengeCompleted', + { uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, }, challenge: { - type: DataTypes.STRING, - allowNull: false + type: DataTypes.STRING, + allowNull: false, }, signed_event: { - type: DataTypes.JSONB, - allowNull: false + type: DataTypes.JSONB, + allowNull: false, }, public_key: { - type: DataTypes.STRING, - allowNull: false + type: DataTypes.STRING, + allowNull: false, }, created_at: { - type: DataTypes.DATE, - allowNull: false - } -}, { - tableName: 'nostr_challenge_completed' -}); + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'nostr_challenge_completed', + } +); -module.exports = NostrChallengeCompleted; \ No newline at end of file +module.exports = NostrChallengeCompleted; diff --git a/src/models/NostrChallengeCreated.js b/src/models/NostrChallengeCreated.js index 2ae5f79..97a4c64 100644 --- a/src/models/NostrChallengeCreated.js +++ b/src/models/NostrChallengeCreated.js @@ -1,27 +1,31 @@ const { DataTypes } = require('sequelize'); const sequelize = require('../database'); -const NostrChallengeCreated = sequelize.define('NostrChallengeCreated', { +const NostrChallengeCreated = sequelize.define( + 'NostrChallengeCreated', + { uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, }, challenge: { - type: DataTypes.STRING, - allowNull: false + type: DataTypes.STRING, + allowNull: false, }, expires_at: { - type: DataTypes.DATE, - allowNull: false + type: DataTypes.DATE, + allowNull: false, }, created_at: { - type: DataTypes.DATE, - allowNull: false - } -}, { - tableName: 'nostr_challenge_created' -}); + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'nostr_challenge_created', + } +); -module.exports = NostrChallengeCreated; \ No newline at end of file +module.exports = NostrChallengeCreated; diff --git a/src/models/NymSet.js b/src/models/NymSet.js index 1adb37a..d59a310 100644 --- a/src/models/NymSet.js +++ b/src/models/NymSet.js @@ -1,27 +1,31 @@ const { DataTypes } = require('sequelize'); const sequelize = require('../database'); -const NymSet = sequelize.define('NymSet', { +const NymSet = sequelize.define( + 'NymSet', + { uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, }, public_key: { - type: DataTypes.STRING, - allowNull: false + type: DataTypes.STRING, + allowNull: false, }, nym: { - type: DataTypes.TEXT, - allowNull: false + type: DataTypes.TEXT, + allowNull: false, }, created_at: { - type: DataTypes.DATE, - allowNull: false - } -}, { - tableName: 'nym_set' -}); + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'nym_set', + } +); -module.exports = NymSet; \ No newline at end of file +module.exports = NymSet; diff --git a/src/models/SessionCreated.js b/src/models/SessionCreated.js index dc899f4..8d0de50 100644 --- a/src/models/SessionCreated.js +++ b/src/models/SessionCreated.js @@ -1,23 +1,27 @@ const { DataTypes } = require('sequelize'); const sequelize = require('../database'); -const SessionCreated = sequelize.define('SessionCreated', { +const SessionCreated = sequelize.define( + 'SessionCreated', + { uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, }, created_at: { - type: DataTypes.DATE, - allowNull: false + type: DataTypes.DATE, + allowNull: false, }, expires_at: { - type: DataTypes.DATE, - allowNull: false - } -}, { - tableName: 'session_created' -}); + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'session_created', + } +); -module.exports = SessionCreated; \ No newline at end of file +module.exports = SessionCreated; diff --git a/src/models/SessionRelatedToPublickey.js b/src/models/SessionRelatedToPublickey.js index 3a218e6..4802267 100644 --- a/src/models/SessionRelatedToPublickey.js +++ b/src/models/SessionRelatedToPublickey.js @@ -1,27 +1,31 @@ const { DataTypes } = require('sequelize'); const sequelize = require('../database'); -const SessionRelatedToPublickey = sequelize.define('SessionRelatedToPublickey', { +const SessionRelatedToPublickey = sequelize.define( + 'SessionRelatedToPublickey', + { uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, }, session_uuid: { - type: DataTypes.UUID, - allowNull: false, + type: DataTypes.UUID, + allowNull: false, }, public_key: { - type: DataTypes.STRING, - allowNull: false + type: DataTypes.STRING, + allowNull: false, }, created_at: { - type: DataTypes.DATE, - allowNull: false - } -}, { - tableName: 'session_related_to_public_key' -}); + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'session_related_to_public_key', + } +); -module.exports = SessionRelatedToPublickey; \ No newline at end of file +module.exports = SessionRelatedToPublickey; diff --git a/src/models/SignUpChallengeCompleted.js b/src/models/SignUpChallengeCompleted.js index 0c4fa4c..2960421 100644 --- a/src/models/SignUpChallengeCompleted.js +++ b/src/models/SignUpChallengeCompleted.js @@ -1,31 +1,35 @@ const { DataTypes } = require('sequelize'); const sequelize = require('../database'); -const SignUpChallengeCompleted = sequelize.define('SignUpChallengeCompleted', { +const SignUpChallengeCompleted = sequelize.define( + 'SignUpChallengeCompleted', + { uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, }, nostr_challenge_completed_uuid: { - type: DataTypes.UUID, - allowNull: false, + type: DataTypes.UUID, + allowNull: false, }, app_invite_uuid: { - type: DataTypes.UUID, - allowNull: false + type: DataTypes.UUID, + allowNull: false, }, public_key: { - type: DataTypes.STRING, - allowNull: false + type: DataTypes.STRING, + allowNull: false, }, created_at: { - type: DataTypes.DATE, - allowNull: false - } -}, { - tableName: 'sign_up_challenge_completed' -}); + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'sign_up_challenge_completed', + } +); -module.exports = SignUpChallengeCompleted; \ No newline at end of file +module.exports = SignUpChallengeCompleted; diff --git a/src/models/SignUpChallengeCreated.js b/src/models/SignUpChallengeCreated.js index 1b07436..1d3e22e 100644 --- a/src/models/SignUpChallengeCreated.js +++ b/src/models/SignUpChallengeCreated.js @@ -1,27 +1,31 @@ const { DataTypes } = require('sequelize'); const sequelize = require('../database'); -const SignUpChallengeCreated = sequelize.define('SignUpChallengeCreated', { +const SignUpChallengeCreated = sequelize.define( + 'SignUpChallengeCreated', + { uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, }, nostr_challenge_uuid: { - type: DataTypes.UUID, - allowNull: false + type: DataTypes.UUID, + allowNull: false, }, app_invite_uuid: { - type: DataTypes.UUID, - allowNull: false + type: DataTypes.UUID, + allowNull: false, }, created_at: { - type: DataTypes.DATE, - allowNull: false - } -}, { - tableName: 'sign_up_challenge_created' -}); + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'sign_up_challenge_created', + } +); -module.exports = SignUpChallengeCreated; \ No newline at end of file +module.exports = SignUpChallengeCreated; diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 2812425..aaff424 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -1,6 +1,6 @@ .badge { - border: 2px solid black; - border-radius: 10px; - margin: 5px; - padding: 5px; -} \ No newline at end of file + border: 2px solid black; + border-radius: 10px; + margin: 5px; + padding: 5px; +} diff --git a/src/public/javascript/createProfile.js b/src/public/javascript/createProfile.js index e5a6b7b..41e972e 100644 --- a/src/public/javascript/createProfile.js +++ b/src/public/javascript/createProfile.js @@ -1,104 +1,106 @@ class ContactDetails { - constructor(rootUiElement) { - this.rootUiElement = rootUiElement; - this.details = []; - } + constructor(rootUiElement) { + this.rootUiElement = rootUiElement; + this.details = []; + } - addDetails(type, value) { - this.details.push({ type, value }); - } + addDetails(type, value) { + this.details.push({ type, value }); + } - removeDetails(type, value) { - this.details = this.details.filter(detail => detail.type !== type || detail.value !== value); - } + removeDetails(type, value) { + this.details = this.details.filter( + (detail) => detail.type !== type || detail.value !== value + ); + } - syncUi() { - requestAnimationFrame(() => { - this.rootUiElement.innerHTML = ''; - this.details.forEach((detail) => { - const addedDetailFragment = this.buildContactDetailBadge(detail); - this.rootUiElement.appendChild(addedDetailFragment); - }); - }) - } + syncUi() { + requestAnimationFrame(() => { + this.rootUiElement.innerHTML = ''; + this.details.forEach((detail) => { + const addedDetailFragment = this.buildContactDetailBadge(detail); + this.rootUiElement.appendChild(addedDetailFragment); + }); + }); + } - buildContactDetailBadge(detail) { - const fragment = document.createDocumentFragment(); + buildContactDetailBadge(detail) { + const fragment = document.createDocumentFragment(); - const div = document.createElement("div"); - div.className = "added-contact-detail badge"; + const div = document.createElement('div'); + div.className = 'added-contact-detail badge'; - const p = document.createElement("p"); - p.textContent = `${detail.type}: ${detail.value}`; + const p = document.createElement('p'); + p.textContent = `${detail.type}: ${detail.value}`; - const button = document.createElement("button"); - button.textContent = "Eliminar"; - button.onclick = () => { - this.removeDetails(detail.type, detail.value); - this.syncUi(); - return false; - }; + const button = document.createElement('button'); + button.textContent = 'Eliminar'; + button.onclick = () => { + this.removeDetails(detail.type, detail.value); + this.syncUi(); + return false; + }; - div.appendChild(p); - div.appendChild(button); - fragment.appendChild(div); + div.appendChild(p); + div.appendChild(button); + fragment.appendChild(div); - return fragment; - } + return fragment; + } - async getEncryptedContactDetails() { - const jsonString = JSON.stringify(this.details); - const encryptedContactDetails = await window.nostr.nip04.encrypt(await window.nostr.getPublicKey(), jsonString); - return encryptedContactDetails; - } + async getEncryptedContactDetails() { + const jsonString = JSON.stringify(this.details); + const encryptedContactDetails = await window.nostr.nip04.encrypt( + await window.nostr.getPublicKey(), + jsonString + ); + return encryptedContactDetails; + } } let contactDetails; window.onload = () => { - contactDetails = new ContactDetails(document.querySelector('#created-contact-details-list')); + contactDetails = new ContactDetails( + document.querySelector('#created-contact-details-list') + ); - document.querySelectorAll('.contact-detail-add-button').forEach(button => { - button.addEventListener('click', function () { - const badge = this.parentElement; - const type = badge.getAttribute('data-type'); - const input = badge.querySelector('input'); - const value = input.value.trim(); + document.querySelectorAll('.contact-detail-add-button').forEach((button) => { + button.addEventListener('click', function () { + const badge = this.parentElement; + const type = badge.getAttribute('data-type'); + const input = badge.querySelector('input'); + const value = input.value.trim(); - if (value === '') return; + if (value === '') return; - contactDetails.addDetails(type, value); - contactDetails.syncUi(); + contactDetails.addDetails(type, value); + contactDetails.syncUi(); - input.value = ''; - }); + input.value = ''; }); + }); - document - .querySelector('#submit-details-button') - .addEventListener( - 'click', - async () => { - const encryptedContactDetails = await contactDetails.getEncryptedContactDetails(); - await fetch('/api/set-contact-details', - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ encryptedContactDetails }) - }); + document + .querySelector('#submit-details-button') + .addEventListener('click', async () => { + const encryptedContactDetails = + await contactDetails.getEncryptedContactDetails(); + await fetch('/api/set-contact-details', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ encryptedContactDetails }), + }); - const nym = document.querySelector('#nym-input').value; - await fetch('/api/set-nym', - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ nym }) - }); - - } - ); -}; \ No newline at end of file + const nym = document.querySelector('#nym-input').value; + await fetch('/api/set-nym', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ nym }), + }); + }); +}; diff --git a/src/public/javascript/invite.js b/src/public/javascript/invite.js index 5fcd4d8..7b34efd 100644 --- a/src/public/javascript/invite.js +++ b/src/public/javascript/invite.js @@ -1,69 +1,68 @@ window.onload = function () { - if (!window.nostr) { - console.log("Nostr extension not present"); - document.querySelector('#nostr-signup').disabled = true; - document.querySelector('#no-extension-nudges').style.display = 'block'; - } else { - console.log("Nostr extension present"); - } -} + if (!window.nostr) { + console.log('Nostr extension not present'); + document.querySelector('#nostr-signup').disabled = true; + document.querySelector('#no-extension-nudges').style.display = 'block'; + } else { + console.log('Nostr extension present'); + } +}; async function acceptInvite() { + let challengeResponse; + try { + challengeResponse = await fetch('/api/signup/nostr-challenge', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + console.log(`Something went wrong: ${error}`); + return; + } - let challengeResponse; - try { - challengeResponse = await fetch('/api/signup/nostr-challenge', { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.log(`Something went wrong: ${error}`); - return; - } + const { challenge } = await challengeResponse.json(); - const { challenge } = await challengeResponse.json(); + let pubkey; + try { + pubkey = await window.nostr.getPublicKey(); + } catch (error) { + document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + return; + } + const event = { + kind: 22242, + created_at: Math.floor(Date.now() / 1000), + tags: [['challenge', challenge]], + content: 'Sign this challenge to authenticate', + pubkey: pubkey, + }; - let pubkey; - try { - pubkey = await window.nostr.getPublicKey(); - } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; - return; - } - const event = { - kind: 22242, - created_at: Math.floor(Date.now() / 1000), - tags: [["challenge", challenge]], - content: "Sign this challenge to authenticate", - pubkey: pubkey - }; + let signedEvent; + try { + signedEvent = await window.nostr.signEvent(event); + } catch (error) { + document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + return; + } - let signedEvent; - try { - signedEvent = await window.nostr.signEvent(event); - } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; - return; - } + let verifyResponse; + try { + verifyResponse = await fetch('/api/signup/nostr-verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(signedEvent), + }); + } catch (error) { + console.log(`Something went wrong: ${error}`); + return; + } - let verifyResponse; - try { - verifyResponse = await fetch("/api/signup/nostr-verify", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(signedEvent), - }); - } catch (error) { - console.log(`Something went wrong: ${error}`); - return; - } - - if (verifyResponse.ok) { - document.querySelector('#sign-up-success').style.display = 'block'; - setTimeout(() => { - window.location.href = "/createProfile"; - }, 1000); - } -} \ No newline at end of file + if (verifyResponse.ok) { + document.querySelector('#sign-up-success').style.display = 'block'; + setTimeout(() => { + window.location.href = '/createProfile'; + }, 1000); + } +} diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 21870e9..2f8f8b2 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -11,138 +11,130 @@ const rejectIfNotAuthorizedMiddleware = require('../middlewares/rejectIfNotAutho const router = express.Router(); router.get('/signup/nostr-challenge', async (req, res) => { - const inviteUuid = req.cookies.inviteUuid; + const inviteUuid = req.cookies.inviteUuid; - let signUpChallenge; - try { - signUpChallenge = await invitesService.createSignUpChallenge( - inviteUuid - ) - } catch (error) { - if (error instanceof errors.NotFoundError) { - return res.status(404).json({ - success: false, - message: 'Could not find invite with that id.' - }) - } - - if (error instanceof errors.AlreadyUsedError) { - return res.status(410).json({ - success: false, - message: 'That invite has already been used.' - }) - } - - return res.status(500).json({ - success: false, - message: 'Unexpected error.' - }) + let signUpChallenge; + try { + signUpChallenge = await invitesService.createSignUpChallenge(inviteUuid); + } catch (error) { + if (error instanceof errors.NotFoundError) { + return res.status(404).json({ + success: false, + message: 'Could not find invite with that id.', + }); } - let relatedNostrChallenge; - try { - relatedNostrChallenge = await nostrService.getNostrChallenge( - signUpChallenge.nostr_challenge_uuid - ) - } catch (error) { - return res.status(500).json({ - success: false, - message: 'Unexpected error.' - }) + if (error instanceof errors.AlreadyUsedError) { + return res.status(410).json({ + success: false, + message: 'That invite has already been used.', + }); } - return res.status(200).json({ 'challenge': relatedNostrChallenge.challenge }); + return res.status(500).json({ + success: false, + message: 'Unexpected error.', + }); + } + + let relatedNostrChallenge; + try { + relatedNostrChallenge = await nostrService.getNostrChallenge( + signUpChallenge.nostr_challenge_uuid + ); + } catch (error) { + return res.status(500).json({ + success: false, + message: 'Unexpected error.', + }); + } + + return res.status(200).json({ challenge: relatedNostrChallenge.challenge }); }); +router.post('/signup/nostr-verify', async (req, res) => { + const signedEvent = req.body; + const sessionUuid = req.cookies.sessionUuid; -router.post("/signup/nostr-verify", async (req, res) => { - const signedEvent = req.body; - const sessionUuid = req.cookies.sessionUuid; - - let completedSignUpChallenge; - try { - completedSignUpChallenge = await invitesService.verifySignUpChallenge(signedEvent); - } catch (error) { - if (error instanceof errors.ExpiredError) { - return res.status(410).json({ - success: false, - message: 'The challenge has expired, request a new one.' - }) - } - if (error instanceof errors.AlreadyUsedError) { - return res.status(410).json({ - success: false, - message: 'The challenge has been used, request a new one.' - }) - } - if (error instanceof errors.InvalidSignatureError) { - return res.status(400).json({ - success: false, - message: 'The challenge signature is not valid.' - }) - } + let completedSignUpChallenge; + try { + completedSignUpChallenge = + await invitesService.verifySignUpChallenge(signedEvent); + } catch (error) { + if (error instanceof errors.ExpiredError) { + return res.status(410).json({ + success: false, + message: 'The challenge has expired, request a new one.', + }); } + if (error instanceof errors.AlreadyUsedError) { + return res.status(410).json({ + success: false, + message: 'The challenge has been used, request a new one.', + }); + } + if (error instanceof errors.InvalidSignatureError) { + return res.status(400).json({ + success: false, + message: 'The challenge signature is not valid.', + }); + } + } - await sessionService.relateSessionToPublicKey( - sessionUuid, - completedSignUpChallenge.public_key - ) + await sessionService.relateSessionToPublicKey( + sessionUuid, + completedSignUpChallenge.public_key + ); - return res.status(200).json({ success: true }); + return res.status(200).json({ success: true }); }); router.post( - "/set-contact-details", - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - async (req, res) => { - const encryptedContactDetails = req.body.encryptedContactDetails; - const publicKey = req.cookies.publicKey; + '/set-contact-details', + rejectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + async (req, res) => { + const encryptedContactDetails = req.body.encryptedContactDetails; + const publicKey = req.cookies.publicKey; - if (!encryptedContactDetails) { - return res.status(400).json({ - success: false, - message: 'Missing contact details.' - }) - } - - await profileService.setContactDetails( - publicKey, - encryptedContactDetails - ) - - return res.status(200).json({ - success: true, - message: 'Contact details set successfully.' - }) + if (!encryptedContactDetails) { + return res.status(400).json({ + success: false, + message: 'Missing contact details.', + }); } + + await profileService.setContactDetails(publicKey, encryptedContactDetails); + + return res.status(200).json({ + success: true, + message: 'Contact details set successfully.', + }); + } ); router.post( - "/set-nym", - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - async (req, res) => { - const nym = req.body.nym; - const publicKey = req.cookies.publicKey; + '/set-nym', + rejectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + async (req, res) => { + const nym = req.body.nym; + const publicKey = req.cookies.publicKey; - if (!nym) { - return res.status(400).json({ - success: false, - message: 'Missing nym' - }) - } - - await profileService.setNym( - publicKey, - nym - ) - - return res.status(200).json({ - success: true, - message: 'Nym set successfully.' - }) + if (!nym) { + return res.status(400).json({ + success: false, + message: 'Missing nym', + }); } + + await profileService.setNym(publicKey, nym); + + return res.status(200).json({ + success: true, + message: 'Nym set successfully.', + }); + } ); module.exports = router; diff --git a/src/routes/webRoutes.js b/src/routes/webRoutes.js index 3598708..d525180 100644 --- a/src/routes/webRoutes.js +++ b/src/routes/webRoutes.js @@ -2,66 +2,68 @@ const express = require('express'); const router = express.Router(); const redirectIfNotAuthorizedMiddleware = require('../middlewares/redirectIfNotAuthorizedMiddleware'); -const invitesService = require('../services/invitesService') +const invitesService = require('../services/invitesService'); router.get('/', (req, res) => { - res.render('index', { uuid: req.cookies.sessionUuid }); + res.render('index', { uuid: req.cookies.sessionUuid }); }); router.get('/invite/:inviteUuid', async (req, res) => { - const { inviteUuid } = req.params; + const { inviteUuid } = req.params; - res.cookie('inviteUuid', inviteUuid, { httpOnly: true, maxAge: 86400000 }); + res.cookie('inviteUuid', inviteUuid, { httpOnly: true, maxAge: 86400000 }); - let invite; - try { - invite = await invitesService.getAppInvite(inviteUuid); - if (!invite) { - return res.status(404).render('error', { message: 'Invite not found.' }); - } - - if (await invitesService.isAppInviteSpent(inviteUuid)) { - return res.status(410).render('invite_spent', { invite }) - } - - } catch (error) { - console.error('Error fetching invite:', error); - return res.status(500).render('error', { message: 'An error occurred' }); + let invite; + try { + invite = await invitesService.getAppInvite(inviteUuid); + if (!invite) { + return res.status(404).render('error', { message: 'Invite not found.' }); } - return res.render('invite', { invite }); + if (await invitesService.isAppInviteSpent(inviteUuid)) { + return res.status(410).render('invite_spent', { invite }); + } + } catch (error) { + console.error('Error fetching invite:', error); + return res.status(500).render('error', { message: 'An error occurred' }); + } + + return res.render('invite', { invite }); }); router.get('/invite/:inviteUuid', async (req, res) => { - const { inviteUuid } = req.params; + const { inviteUuid } = req.params; - res.cookie('inviteUuid', inviteUuid, { httpOnly: true, maxAge: 86400000 }); + res.cookie('inviteUuid', inviteUuid, { httpOnly: true, maxAge: 86400000 }); - let invite; - try { - invite = await invitesService.getAppInvite(inviteUuid); - if (!invite) { - return res.status(404).render('error', { message: 'Invite not found.' }); - } - - if (await invitesService.isAppInviteSpent(inviteUuid)) { - return res.status(410).render('invite_spent', { invite }) - } - - } catch (error) { - console.error('Error fetching invite:', error); - return res.status(500).render('error', { message: 'An error occurred' }); + let invite; + try { + invite = await invitesService.getAppInvite(inviteUuid); + if (!invite) { + return res.status(404).render('error', { message: 'Invite not found.' }); } - return res.render('invite', { invite }); + if (await invitesService.isAppInviteSpent(inviteUuid)) { + return res.status(410).render('invite_spent', { invite }); + } + } catch (error) { + console.error('Error fetching invite:', error); + return res.status(500).render('error', { message: 'An error occurred' }); + } + + return res.render('invite', { invite }); }); -router.get('/createProfile', redirectIfNotAuthorizedMiddleware, async (req, res) => { +router.get( + '/createProfile', + redirectIfNotAuthorizedMiddleware, + async (req, res) => { return res.status(200).render('createProfile'); -}) + } +); router.get('/private', redirectIfNotAuthorizedMiddleware, (req, res) => { - res.render('private', {}); + res.render('private', {}); }); module.exports = router; diff --git a/src/services/invitesService.js b/src/services/invitesService.js index b161496..432633c 100644 --- a/src/services/invitesService.js +++ b/src/services/invitesService.js @@ -8,119 +8,112 @@ const SignUpChallengeCompleted = require('../models/SignUpChallengeCompleted'); const errors = require('../errors'); async function appInviteExists(inviteUuid) { - const invite = await AppInviteCreated.findOne({ where: { uuid: inviteUuid } }); - if (invite) { - return true; - } - return false; + const invite = await AppInviteCreated.findOne({ + where: { uuid: inviteUuid }, + }); + if (invite) { + return true; + } + return false; } async function getAppInvite(inviteUuid) { - const invite = await AppInviteCreated.findOne({ where: { uuid: inviteUuid } }); - return invite; + const invite = await AppInviteCreated.findOne({ + where: { uuid: inviteUuid }, + }); + return invite; } async function isAppInviteSpent(appInviteUuid) { - const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne({ - where: { - app_invite_uuid: appInviteUuid - } - }) + const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne({ + where: { + app_invite_uuid: appInviteUuid, + }, + }); - if (signUpChallengeCompleted) { - return true; - } - return false; + if (signUpChallengeCompleted) { + return true; + } + return false; } async function createAppInvite(inviterPubKey) { - return await AppInviteCreated.create({ - uuid: uuid.v7(), - inviter_pub_key: inviterPubKey, - created_at: new Date().toISOString() - } - ); + return await AppInviteCreated.create({ + uuid: uuid.v7(), + inviter_pub_key: inviterPubKey, + created_at: new Date().toISOString(), + }); } async function createSignUpChallenge(appInviteUuid) { + if (!(await appInviteExists(appInviteUuid))) { + throw new errors.NotFoundError("Invite doesn't exist."); + } - if (!(await appInviteExists(appInviteUuid))) { - throw new errors.NotFoundError("Invite doesn't exist.") - } + if (await isAppInviteSpent(appInviteUuid)) { + throw new errors.AlreadyUsedError('Invite has already been used.'); + } - if (await isAppInviteSpent(appInviteUuid)) { - throw new errors.AlreadyUsedError("Invite has already been used.") - } + const nostrChallenge = await nostrService.createNostrChallenge(); - const nostrChallenge = await nostrService.createNostrChallenge() - - return await SignUpChallengeCreated.create({ - 'uuid': uuid.v7(), - nostr_challenge_uuid: nostrChallenge.uuid, - app_invite_uuid: appInviteUuid, - created_at: new Date().toISOString() - } - ) + return await SignUpChallengeCreated.create({ + uuid: uuid.v7(), + nostr_challenge_uuid: nostrChallenge.uuid, + app_invite_uuid: appInviteUuid, + created_at: new Date().toISOString(), + }); } - async function verifySignUpChallenge(signedEvent) { + const challengeTag = signedEvent.tags.find((tag) => tag[0] === 'challenge'); + const challenge = challengeTag[1]; - const challengeTag = signedEvent.tags.find(tag => tag[0] === "challenge"); - const challenge = challengeTag[1]; + const nostrChallenge = await nostrService.getNostrChallenge(null, challenge); - const nostrChallenge = await nostrService.getNostrChallenge( - null, challenge - ); + const signUpChallenge = await SignUpChallengeCreated.findOne({ + where: { + nostr_challenge_uuid: nostrChallenge.uuid, + }, + }); - const signUpChallenge = await SignUpChallengeCreated.findOne({ - where: { - nostr_challenge_uuid: nostrChallenge.uuid - } - }) + if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) { + throw new errors.AlreadyUsedError('This challenge has already been used.'); + } - if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) { - throw new errors.AlreadyUsedError("This challenge has already been used."); - } + const completedNostrChallenge = + await nostrService.verifyNostrChallenge(signedEvent); - const completedNostrChallenge = await nostrService.verifyNostrChallenge(signedEvent); + const completedSignUpChallenge = await SignUpChallengeCompleted.create({ + uuid: uuid.v7(), + nostr_challenge_completed_uuid: completedNostrChallenge.uuid, + app_invite_uuid: signUpChallenge.app_invite_uuid, + public_key: completedNostrChallenge.public_key, + created_at: new Date().toISOString(), + }); - const completedSignUpChallenge = await SignUpChallengeCompleted.create( - { - 'uuid': uuid.v7(), - nostr_challenge_completed_uuid: completedNostrChallenge.uuid, - app_invite_uuid: signUpChallenge.app_invite_uuid, - public_key: completedNostrChallenge.public_key, - created_at: new Date().toISOString() - } - ); - - return completedSignUpChallenge; + return completedSignUpChallenge; } async function isPublicKeySignedUp(publicKey) { - const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne( - { - where: { - public_key: publicKey - } - } - ) + const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne({ + where: { + public_key: publicKey, + }, + }); - if (signUpChallengeCompleted) { - return true; - } - - return false; + if (signUpChallengeCompleted) { + return true; + } + return false; } module.exports = { - appInviteExists, - getAppInvite, - isAppInviteSpent, - createAppInvite, - createSignUpChallenge, - verifySignUpChallenge, - isPublicKeySignedUp -}; \ No newline at end of file + appInviteExists, + getAppInvite, + isAppInviteSpent, + createAppInvite, + createSignUpChallenge, + verifySignUpChallenge, + isPublicKeySignedUp, +}; diff --git a/src/services/nostrService.js b/src/services/nostrService.js index e0e5542..542ba1c 100644 --- a/src/services/nostrService.js +++ b/src/services/nostrService.js @@ -1,115 +1,113 @@ -const uuid = require("uuid"); -const crypto = require("crypto"); +const uuid = require('uuid'); +const crypto = require('crypto'); const { Op, TimeoutError } = require('sequelize'); -const { verifyEvent } = require("nostr-tools"); +const { verifyEvent } = require('nostr-tools'); const NostrChallengeCreated = require('../models/NostrChallengeCreated'); -const NostrChallengeCompleted = require("../models/NostrChallengeCompleted"); +const NostrChallengeCompleted = require('../models/NostrChallengeCompleted'); const constants = require('../constants'); const errors = require('../errors'); async function createNostrChallenge() { + const currentTimestamp = new Date(); + const expiryTimestamp = new Date(currentTimestamp.getTime()); + expiryTimestamp.setSeconds( + expiryTimestamp.getSeconds() + + constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS + ); - const currentTimestamp = new Date(); - const expiryTimestamp = new Date(currentTimestamp.getTime()); - expiryTimestamp.setSeconds(expiryTimestamp.getSeconds() + constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS); + const nostrChallenge = await NostrChallengeCreated.create({ + uuid: uuid.v7(), + challenge: crypto.randomBytes(32).toString('hex'), + expires_at: expiryTimestamp.toISOString(), + created_at: currentTimestamp.toISOString(), + }); - const nostrChallenge = await NostrChallengeCreated.create({ - 'uuid': uuid.v7(), - challenge: crypto.randomBytes(32).toString("hex"), - expires_at: expiryTimestamp.toISOString(), - created_at: currentTimestamp.toISOString() - }); - - return nostrChallenge; + return nostrChallenge; } async function getNostrChallenge(nostrChallengeUuid = null, challenge = null) { + if (nostrChallengeUuid) { + return await NostrChallengeCreated.findOne({ + where: { + uuid: nostrChallengeUuid, + }, + }); + } - if (nostrChallengeUuid) { - return await NostrChallengeCreated.findOne({ - where: { - 'uuid': nostrChallengeUuid - } - }) - } - - if (challenge) { - return await NostrChallengeCreated.findOne({ - where: { - challenge - } - }) - } - - throw Error('You need to pass a uuid or a challenge.') + if (challenge) { + return await NostrChallengeCreated.findOne({ + where: { + challenge, + }, + }); + } + throw Error('You need to pass a uuid or a challenge.'); } async function verifyNostrChallenge(signedEvent) { - const challengeTag = signedEvent.tags.find(tag => tag[0] === "challenge"); - const challenge = challengeTag[1]; + const challengeTag = signedEvent.tags.find((tag) => tag[0] === 'challenge'); + const challenge = challengeTag[1]; - if (!(await isNostrChallengeFresh(challenge))) { - throw TimeoutError("Challenge expired, request new one."); - } + if (!(await isNostrChallengeFresh(challenge))) { + throw TimeoutError('Challenge expired, request new one.'); + } - if (await hasNostrChallengeBeenCompleted(challenge)) { - throw new errors.AlreadyUsedError("Challenge already used, request new one."); - } - - const isSignatureValid = verifyEvent(signedEvent); - if (!isSignatureValid) { - throw new errors.InvalidSignatureError("Signature is not valid."); - } - - return await NostrChallengeCompleted.create({ - 'uuid': uuid.v7(), - challenge: challenge, - signed_event: signedEvent, - public_key: signedEvent.pubkey, - created_at: new Date().toISOString() - } + if (await hasNostrChallengeBeenCompleted(challenge)) { + throw new errors.AlreadyUsedError( + 'Challenge already used, request new one.' ); + } + + const isSignatureValid = verifyEvent(signedEvent); + if (!isSignatureValid) { + throw new errors.InvalidSignatureError('Signature is not valid.'); + } + + return await NostrChallengeCompleted.create({ + uuid: uuid.v7(), + challenge: challenge, + signed_event: signedEvent, + public_key: signedEvent.pubkey, + created_at: new Date().toISOString(), + }); } async function isNostrChallengeFresh(challengeString) { - const nostrChallenge = await NostrChallengeCreated.findOne({ - where: { - challenge: challengeString, - expires_at: { - [Op.gt]: new Date() - } - } - }); + const nostrChallenge = await NostrChallengeCreated.findOne({ + where: { + challenge: challengeString, + expires_at: { + [Op.gt]: new Date(), + }, + }, + }); - if (nostrChallenge) { - return true; - } - return false; + if (nostrChallenge) { + return true; + } + return false; } async function hasNostrChallengeBeenCompleted(challengeString) { - const completedNostrChallenge = await NostrChallengeCompleted.findOne( - { - where: { - challenge: challengeString - } - } - ); + const completedNostrChallenge = await NostrChallengeCompleted.findOne({ + where: { + challenge: challengeString, + }, + }); - if (completedNostrChallenge) { - return true; - } - return false; + if (completedNostrChallenge) { + return true; + } + return false; } - module.exports = { - createNostrChallenge, - getNostrChallenge, - verifyNostrChallenge, - isNostrChallengeFresh, - hasNostrChallengeBeenCompleted -}; \ No newline at end of file + createNostrChallenge, + getNostrChallenge, + verifyNostrChallenge, + isNostrChallengeFresh, + hasNostrChallengeBeenCompleted, +}; diff --git a/src/services/profileService.js b/src/services/profileService.js index 0e4dada..7e6b4a1 100644 --- a/src/services/profileService.js +++ b/src/services/profileService.js @@ -3,28 +3,24 @@ const ContactDetailsSet = require('../models/ContactDetailsSet'); const NymSet = require('../models/NymSet'); async function setContactDetails(publicKey, encryptedContactDetails) { - return await ContactDetailsSet.create( - { - 'uuid': uuid.v7(), - public_key: publicKey, - encrypted_contact_details: encryptedContactDetails, - created_at: new Date().toISOString() - } - ) + return await ContactDetailsSet.create({ + uuid: uuid.v7(), + public_key: publicKey, + encrypted_contact_details: encryptedContactDetails, + created_at: new Date().toISOString(), + }); } async function setNym(publicKey, nym) { - return await NymSet.create( - { - 'uuid': uuid.v7(), - public_key: publicKey, - nym: nym, - created_at: new Date().toISOString() - } - ) + return await NymSet.create({ + uuid: uuid.v7(), + public_key: publicKey, + nym: nym, + created_at: new Date().toISOString(), + }); } module.exports = { - setContactDetails, - setNym -}; \ No newline at end of file + setContactDetails, + setNym, +}; diff --git a/src/services/sessionService.js b/src/services/sessionService.js index 0aaa0d9..f3af704 100644 --- a/src/services/sessionService.js +++ b/src/services/sessionService.js @@ -7,86 +7,82 @@ const invitesService = require('./invitesService'); const constants = require('../constants'); async function createSession(sessionUuid) { - const currentTimestamp = new Date(); - const expiryTimestamp = new Date(currentTimestamp.getTime()); - expiryTimestamp.setSeconds(expiryTimestamp.getSeconds() + constants.DEFAULT_SESSION_DURATION_SECONDS); + const currentTimestamp = new Date(); + const expiryTimestamp = new Date(currentTimestamp.getTime()); + expiryTimestamp.setSeconds( + expiryTimestamp.getSeconds() + constants.DEFAULT_SESSION_DURATION_SECONDS + ); - return await SessionCreated.create({ - uuid: sessionUuid, - created_at: currentTimestamp.toISOString(), - expires_at: expiryTimestamp.toISOString() - }); + return await SessionCreated.create({ + uuid: sessionUuid, + created_at: currentTimestamp.toISOString(), + expires_at: expiryTimestamp.toISOString(), + }); } async function isSessionValid(sessionUuid) { - const currentSession = await SessionCreated.findOne({ - where: { - 'uuid': sessionUuid - } - }); + const currentSession = await SessionCreated.findOne({ + where: { + uuid: sessionUuid, + }, + }); - if (!currentSession) { - return false; - } + if (!currentSession) { + return false; + } - if (currentSession.expires_at <= new Date()) { - return false; - } + if (currentSession.expires_at <= new Date()) { + return false; + } - return true; + return true; } async function relateSessionToPublicKey(sessionUuid, publicKey) { - if (!(await isSessionValid(sessionUuid))) { - throw Error("Session is not valid anymore."); - } + if (!(await isSessionValid(sessionUuid))) { + throw Error('Session is not valid anymore.'); + } - if (!(await invitesService.isPublicKeySignedUp(publicKey))) { - throw Error("Public key is not signed up."); - } + if (!(await invitesService.isPublicKeySignedUp(publicKey))) { + throw Error('Public key is not signed up.'); + } - return SessionRelatedToPublickey.create({ - 'uuid': uuid.v7(), - session_uuid: sessionUuid, - public_key: publicKey, - created_at: new Date().toISOString() - - }); + return SessionRelatedToPublickey.create({ + uuid: uuid.v7(), + session_uuid: sessionUuid, + public_key: publicKey, + created_at: new Date().toISOString(), + }); } async function isSessionAuthorized(sessionUuid) { - const isSessionRelatedToPublicKey = await SessionRelatedToPublickey.findOne( - { - where: { - session_uuid: sessionUuid - } - } - ); + const isSessionRelatedToPublicKey = await SessionRelatedToPublickey.findOne({ + where: { + session_uuid: sessionUuid, + }, + }); - if (isSessionRelatedToPublicKey) { - return true; - } + if (isSessionRelatedToPublicKey) { + return true; + } - return false; + return false; } async function getPublicKeyRelatedToSession(sessionUuid) { - const sessionRelatedToPublickey = await SessionRelatedToPublickey.findOne( - { - where: { - session_uuid: sessionUuid - } - } - ); - - return sessionRelatedToPublickey.public_key; + const sessionRelatedToPublickey = await SessionRelatedToPublickey.findOne({ + where: { + session_uuid: sessionUuid, + }, + }); + return sessionRelatedToPublickey.public_key; } module.exports = { - createSession, - isSessionValid, - relateSessionToPublicKey, - isSessionAuthorized, - getPublicKeyRelatedToSession -} \ No newline at end of file + createSession, + isSessionValid, + relateSessionToPublicKey, + isSessionAuthorized, + getPublicKeyRelatedToSession, +}; diff --git a/src/views/createProfile.ejs b/src/views/createProfile.ejs index 809b7fd..25be8bb 100644 --- a/src/views/createProfile.ejs +++ b/src/views/createProfile.ejs @@ -1,53 +1,77 @@ + + Crear perfil + + + + + - - Crear perfil - - - - - - - -

Crea tu perfil

-

Tu clave de Nostr ya es parte de la seca.

-

Añade detalles a tu perfil para poder empezar a comerciar.

-

-
-
- -
-

Añade métodos de contacto para poder hablar con otros miembros.

-
-
📱 Teléfono
-
📱 WhatsApp
-
📩 Telegram >Añadir
-
📧 Email
-
📧 Nostr
-
📧 Signal
-
📧 Matrix
-
📧 XMPP
-
📧 Simplex
-
-
-
-

Contactos añadidos

-
- -
-
- -
- - - \ No newline at end of file + +

Crea tu perfil

+

Tu clave de Nostr ya es parte de la seca.

+

Añade detalles a tu perfil para poder empezar a comerciar.

+
+
+ +
+

Añade métodos de contacto para poder hablar con otros miembros.

+
+
+ 📱 Teléfono +
+
+ 📱 WhatsApp +
+
+ 📩 Telegram +
+
+ 📧 Email +
+
+ 📧 Nostr +
+
+ 📧 Signal +
+
+ 📧 Matrix +
+
+ 📧 XMPP +
+
+ 📧 Simplex +
+
+
+
+

Contactos añadidos

+
+
+ +
+ + diff --git a/src/views/error.ejs b/src/views/error.ejs index d787e4c..ea2223d 100644 --- a/src/views/error.ejs +++ b/src/views/error.ejs @@ -1,17 +1,13 @@ - - - - + + + Error - + - +

Error

-

- <%= message %> -

- - - \ No newline at end of file +

<%= message %>

+ + diff --git a/src/views/index.ejs b/src/views/index.ejs index da6e956..615b791 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -1,23 +1,26 @@ - - + Hello World - - + + - + - +

Bienvenido a la seca

Usa Nostr para logearte

- +

- ¿No tienes cuenta de Nostr? Crea una - gratis. + ¿No tienes cuenta de Nostr? + Crea una gratis.

- - - \ No newline at end of file + + diff --git a/src/views/invite.ejs b/src/views/invite.ejs index 86b2f04..b927f8e 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -1,53 +1,88 @@ - - + Invite Details - - + + - + - +

¡Has sido invitado!

Has sido invitado a la seca.

-

Invite UUID: <%= invite.uuid %> -

+

Invite UUID: <%= invite.uuid %>

Usa tu extensión de Nostr para darte de alta:

- +
-