From 86967b26cd00fa91ab7ae8b49ea69c7899c1d9c1 Mon Sep 17 00:00:00 2001 From: Cameron Redmore Date: Fri, 25 Apr 2025 08:14:48 +0100 Subject: [PATCH] Added linting and enforced code styling. --- eslint.config.js | 91 +++ package.json | 10 +- pnpm-lock.yaml | 1065 ++++++++++++++++++++++++- postcss.config.js | 4 +- quasar.config.js | 19 +- src-ssr/database.js | 9 +- src-ssr/middlewares/authMiddleware.js | 6 +- src-ssr/middlewares/render.js | 61 +- src-ssr/routes/api.js | 361 +++++---- src-ssr/routes/auth.js | 192 +++-- src-ssr/routes/chat.js | 83 +- src-ssr/server.js | 144 ++-- src-ssr/services/emailSummarizer.js | 199 ----- src-ssr/services/mantisSummarizer.js | 142 ++-- src-ssr/utils/gemini.js | 279 ++++--- src-ssr/utils/settings.js | 20 + src/components/ChatInterface.vue | 168 ++-- src/layouts/MainLayout.vue | 350 ++++---- src/layouts/MainLayoutBusted.vue | 181 ----- src/pages/EmailSummariesPage.vue | 201 ----- src/pages/ErrorNotFound.vue | 5 +- src/pages/FormCreatePage.vue | 250 ++++-- src/pages/FormEditPage.vue | 152 +++- src/pages/FormFillPage.vue | 119 ++- src/pages/FormListPage.vue | 123 ++- src/pages/FormResponsesPage.vue | 114 ++- src/pages/LandingPage.vue | 60 +- src/pages/LoginPage.vue | 61 +- src/pages/MantisSummariesPage.vue | 114 ++- src/pages/PasskeyManagementPage.vue | 218 +++-- src/pages/RegisterPage.vue | 106 ++- src/pages/SettingsPage.vue | 89 ++- src/router/index.js | 60 +- src/router/routes.js | 18 +- src/stores/auth.js | 26 +- src/stores/chat.js | 118 ++- src/stores/index.js | 13 +- 37 files changed, 3356 insertions(+), 1875 deletions(-) create mode 100644 eslint.config.js delete mode 100644 src-ssr/services/emailSummarizer.js create mode 100644 src-ssr/utils/settings.js delete mode 100644 src/layouts/MainLayoutBusted.vue delete mode 100644 src/pages/EmailSummariesPage.vue diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..9876cce --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,91 @@ +import stylistic from '@stylistic/eslint-plugin'; +import globals from 'globals'; +import pluginVue from 'eslint-plugin-vue'; +import pluginQuasar from '@quasar/app-vite/eslint'; + +export default +[ + { + /** + * Ignore the following files. + * Please note that pluginQuasar.configs.recommended() already ignores + * the "node_modules" folder for you (and all other Quasar project + * relevant folders and files). + * + * ESLint requires "ignores" key to be the only one in this object + */ + // ignores: [] + }, + + ...pluginQuasar.configs.recommended(), + + /** + * https://eslint.vuejs.org + * + * pluginVue.configs.base + * -> Settings and rules to enable correct ESLint parsing. + * pluginVue.configs[ 'flat/essential'] + * -> base, plus rules to prevent errors or unintended behavior. + * pluginVue.configs["flat/strongly-recommended"] + * -> Above, plus rules to considerably improve code readability and/or dev experience. + * pluginVue.configs["flat/recommended"] + * -> Above, plus rules to enforce subjective community defaults to ensure consistency. + */ + ...pluginVue.configs['flat/essential'], + ...pluginVue.configs['flat/strongly-recommended'], + + { + plugins: { + '@stylistic': stylistic, + }, + languageOptions: + { + ecmaVersion: 'latest', + sourceType: 'module', + + globals: + { + ...globals.browser, + ...globals.node, // SSR, Electron, config files + process: 'readonly', // process.env.* + ga: 'readonly', // Google Analytics + cordova: 'readonly', + Capacitor: 'readonly', + chrome: 'readonly', // BEX related + browser: 'readonly' // BEX related + } + }, + + // add your custom rules here + rules: + { + 'prefer-promise-reject-errors': 'off', + + // allow debugger during development only + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', + + // enforce Allman brace style + '@stylistic/brace-style': ['warn', 'allman'], + '@stylistic/indent': ['warn', 2], + + //Enforce single quotes + '@stylistic/quotes': ['warn', 'single', { avoidEscape: true }], + '@stylistic/quote-props': ['warn', 'as-needed', { keywords: true, unnecessary: true, numbers: true }], + + //Enforce semicolon + '@stylistic/semi': ['warn', 'always'], + '@stylistic/space-before-function-paren': ['warn', 'never'], + } + }, + + { + files: ['src-pwa/custom-service-worker.js'], + languageOptions: + { + globals: + { + ...globals.serviceworker + } + } + } +]; \ No newline at end of file diff --git a/package.json b/package.json index 60ae1ed..8482ef0 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,20 @@ "vue-router": "^4.0.0" }, "devDependencies": { + "@eslint/js": "^9.25.1", "@quasar/app-vite": "^2.1.0", + "@stylistic/eslint-plugin": "^4.2.0", "@types/express-session": "^1.18.1", "@types/uuid": "^10.0.0", + "@vue/eslint-config-prettier": "^10.2.0", "autoprefixer": "^10.4.2", + "eslint": "^9.25.1", + "eslint-plugin-vue": "^10.0.0", + "globals": "^16.0.0", "postcss": "^8.4.14", - "prisma": "^6.6.0" + "prettier": "^3.5.3", + "prisma": "^6.6.0", + "vite-plugin-checker": "^0.9.1" }, "engines": { "node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 806c09c..66b1fcb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 0.9.0 '@prisma/client': specifier: ^6.6.0 - version: 6.6.0(prisma@6.6.0) + version: 6.6.0(prisma@6.6.0(typescript@5.8.3))(typescript@5.8.3) '@quasar/extras': specifier: ^1.16.4 version: 1.16.17 @@ -58,7 +58,7 @@ importers: version: 0.2.18 pinia: specifier: ^3.0.2 - version: 3.0.2(vue@3.5.13) + version: 3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)) quasar: specifier: ^2.16.0 version: 2.18.1 @@ -67,32 +67,60 @@ importers: version: 11.1.0 vue: specifier: ^3.4.18 - version: 3.5.13 + version: 3.5.13(typescript@5.8.3) vue-router: specifier: ^4.0.0 - version: 4.5.0(vue@3.5.13) + version: 4.5.0(vue@3.5.13(typescript@5.8.3)) devDependencies: + '@eslint/js': + specifier: ^9.25.1 + version: 9.25.1 '@quasar/app-vite': specifier: ^2.1.0 - version: 2.2.0(@types/node@22.14.1)(pinia@3.0.2(vue@3.5.13))(quasar@2.18.1)(rollup@4.40.0)(terser@5.39.0)(vue-router@4.5.0(vue@3.5.13))(vue@3.5.13) + version: 2.2.0(@types/node@22.14.1)(eslint@9.25.1)(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)))(quasar@2.18.1)(rollup@4.40.0)(terser@5.39.0)(typescript@5.8.3)(vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)) + '@stylistic/eslint-plugin': + specifier: ^4.2.0 + version: 4.2.0(eslint@9.25.1)(typescript@5.8.3) '@types/express-session': specifier: ^1.18.1 version: 1.18.1 '@types/uuid': specifier: ^10.0.0 version: 10.0.0 + '@vue/eslint-config-prettier': + specifier: ^10.2.0 + version: 10.2.0(@types/eslint@9.6.1)(eslint@9.25.1)(prettier@3.5.3) autoprefixer: specifier: ^10.4.2 version: 10.4.21(postcss@8.5.3) + eslint: + specifier: ^9.25.1 + version: 9.25.1 + eslint-plugin-vue: + specifier: ^10.0.0 + version: 10.0.0(eslint@9.25.1)(vue-eslint-parser@10.1.3(eslint@9.25.1)) + globals: + specifier: ^16.0.0 + version: 16.0.0 postcss: specifier: ^8.4.14 version: 8.5.3 + prettier: + specifier: ^3.5.3 + version: 3.5.3 prisma: specifier: ^6.6.0 - version: 6.6.0 + version: 6.6.0(typescript@5.8.3) + vite-plugin-checker: + specifier: ^0.9.1 + version: 0.9.1(eslint@9.25.1)(optionator@0.9.4)(typescript@5.8.3)(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0)) packages: + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.9': resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} @@ -263,6 +291,44 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.6.1': + resolution: {integrity: sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.20.0': + resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.2.1': + resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.25.1': + resolution: {integrity: sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@foliojs-fork/fontkit@1.9.2': resolution: {integrity: sha512-IfB5EiIb+GZk+77TRB86AHroVaqfq8JRFlUbz0WEwsInyCG0epX2tCPOy+UfaWPju30DeVoUAXfzWXmhn753KA==} @@ -282,6 +348,26 @@ packages: '@hexagon/base64@1.1.28': resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + '@inquirer/figures@1.0.11': resolution: {integrity: sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==} engines: {node: '>=18'} @@ -314,6 +400,18 @@ packages: '@levischuck/tiny-cbor@0.2.11': resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + '@peculiar/asn1-android@2.3.16': resolution: {integrity: sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==} @@ -333,6 +431,10 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pkgr/core@0.2.4': + resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@prisma/client@6.6.0': resolution: {integrity: sha512-vfp73YT/BHsWWOAuthKQ/1lBgESSqYqAWZEYyTdGXyFAHpmewwWL2Iz6ErIzkj4aHbuc6/cGSsE6ZY+pBO04Cg==} engines: {node: '>=18.18'} @@ -521,6 +623,12 @@ packages: resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==} engines: {node: '>=20.0.0'} + '@stylistic/eslint-plugin@4.2.0': + resolution: {integrity: sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=9.0.0' + '@swc/helpers@0.5.17': resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} @@ -539,6 +647,9 @@ packages: '@types/cordova@11.0.3': resolution: {integrity: sha512-kyuRQ40/NWQVhqGIHq78Ehu2Bf9Mlg0LhmSmis6ZFJK7z933FRfYi8tHe/k/0fB+PGfCf95rJC6TO7dopaFvAg==} + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} @@ -563,6 +674,9 @@ packages: '@types/http-errors@2.0.4': resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -587,6 +701,31 @@ packages: '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@typescript-eslint/scope-manager@8.31.0': + resolution: {integrity: sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/types@8.31.0': + resolution: {integrity: sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.31.0': + resolution: {integrity: sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.31.0': + resolution: {integrity: sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.31.0': + resolution: {integrity: sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-vue@5.2.3': resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -618,6 +757,12 @@ packages: '@vue/devtools-shared@7.7.5': resolution: {integrity: sha512-QBjG72RfpM0DKtpns2RZOxBltO226kOAls9e4Lri6YxS2gWTgL0H+wj1R2K76lxxIeOrqo4+2Ty6RQnzv+WSTQ==} + '@vue/eslint-config-prettier@10.2.0': + resolution: {integrity: sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==} + peerDependencies: + eslint: '>= 8.21.0' + prettier: '>= 3.0.0' + '@vue/reactivity@3.5.13': resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} @@ -643,6 +788,11 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn@8.14.1: resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} @@ -652,6 +802,9 @@ packages: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -684,6 +837,9 @@ packages: resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} engines: {node: '>= 14'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -749,6 +905,12 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -803,6 +965,10 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + camel-case@4.1.2: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} @@ -820,6 +986,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -892,6 +1062,9 @@ packages: resolution: {integrity: sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==} engines: {node: '>= 0.8.0'} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -940,6 +1113,11 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -975,6 +1153,9 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -1131,9 +1312,82 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.2: + resolution: {integrity: sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.2.6: + resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-vue@10.0.0: + resolution: {integrity: sha512-XKckedtajqwmaX6u1VnECmZ6xJt+YvlmMzBPZd+/sI3ub2lpYZyFnsyWo7c3nMOQKJQudeyk1lw/JxdgeKT64w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + vue-eslint-parser: ^10.0.0 + + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.25.1: + resolution: {integrity: sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -1168,9 +1422,25 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fdir@6.4.4: resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} peerDependencies: @@ -1179,6 +1449,10 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -1190,10 +1464,21 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} @@ -1270,10 +1555,22 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.0.0: + resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==} + engines: {node: '>=18'} + google-auth-library@9.15.1: resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} engines: {node: '>=14'} @@ -1350,9 +1647,21 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + immutable@5.1.1: resolution: {integrity: sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1462,9 +1771,25 @@ packages: jpeg-exif@1.1.4: resolution: {integrity: sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -1474,6 +1799,9 @@ packages: jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -1488,6 +1816,10 @@ packages: leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + libbase64@1.3.0: resolution: {integrity: sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==} @@ -1503,6 +1835,13 @@ packages: linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -1541,10 +1880,18 @@ packages: merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -1570,6 +1917,9 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} @@ -1612,6 +1962,9 @@ packages: napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -1663,6 +2016,13 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -1698,6 +2058,10 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -1706,6 +2070,14 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1715,6 +2087,10 @@ packages: param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parseley@0.12.1: resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} @@ -1725,10 +2101,18 @@ packages: pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} @@ -1778,6 +2162,10 @@ packages: png-js@1.0.0: resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==} + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -1790,6 +2178,19 @@ packages: engines: {node: '>=10'} hasBin: true + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + prisma@6.6.0: resolution: {integrity: sha512-SYCUykz+1cnl6Ugd8VUvtTQq5+j1Q7C0CtzKPjQ8JyA2ALh0EEJkMCS+KgdnvKW1lrxjtjCyJSHOOT236mENYg==} engines: {node: '>=18.18'} @@ -1821,6 +2222,10 @@ packages: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + pvtsutils@1.3.6: resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} @@ -1836,6 +2241,9 @@ packages: resolution: {integrity: sha512-db/P64Mzpt1uXJ0MapaG+IYJQ9hHDb5KtTCoszwC78DR7sA+Uoj7nBW2EytwYykIExEmqavOvKrdasTvqhkgEg==} engines: {node: '>= 10.18.1', npm: '>= 6.13.4', yarn: '>= 1.21.1'} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + random-bytes@1.0.0: resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} engines: {node: '>= 0.8'} @@ -1873,6 +2281,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -1885,6 +2297,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -1892,6 +2308,10 @@ packages: restructure@3.0.2: resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -1921,6 +2341,9 @@ packages: resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} engines: {node: '>=0.12.0'} + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -2199,6 +2622,10 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + superjson@2.2.2: resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} engines: {node: '>=16'} @@ -2219,6 +2646,10 @@ packages: resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==} engines: {node: '>=16.0.0'} + synckit@0.11.4: + resolution: {integrity: sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==} + engines: {node: ^14.18.0 || >=16.0.0} + tar-fs@2.1.2: resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} @@ -2240,6 +2671,9 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinyglobby@0.2.13: resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} engines: {node: '>=12.0.0'} @@ -2263,6 +2697,12 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-essentials@9.4.2: resolution: {integrity: sha512-mB/cDhOvD7pg3YCLk2rOtejHjjdSi9in/IBYE13S+8WA5FBSraYf4V/ws55uvs0IvQ/l0wBOlXy5yBNZ9Bl8ZQ==} peerDependencies: @@ -2277,6 +2717,10 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -2289,6 +2733,11 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} @@ -2308,6 +2757,10 @@ packages: unicode-trie@2.0.0: resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -2322,6 +2775,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + utf7@1.0.2: resolution: {integrity: sha512-qQrPtYLLLl12NF4DrM9CvfkxkYI97xOb5dsnGZHE3teFr0tWiEZ9UdgMPczv24vl708cYMpe6mGXGHrotIp3Bw==} @@ -2351,6 +2807,40 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite-plugin-checker@0.9.1: + resolution: {integrity: sha512-neH3CSNWdkZ+zi+WPt/0y5+IO2I0UAI0NX6MaXqU/KxN1Lz6np/7IooRB6VVAMBa4nigqm1GRF6qNa4+EL5jDQ==} + engines: {node: '>=14.16'} + peerDependencies: + '@biomejs/biome': '>=1.7' + eslint: '>=7' + meow: ^13.2.0 + optionator: ^0.9.4 + stylelint: '>=16' + typescript: '*' + vite: '>=2.0.0' + vls: '*' + vti: '*' + vue-tsc: ~2.2.2 + peerDependenciesMeta: + '@biomejs/biome': + optional: true + eslint: + optional: true + meow: + optional: true + optionator: + optional: true + stylelint: + optional: true + typescript: + optional: true + vls: + optional: true + vti: + optional: true + vue-tsc: + optional: true + vite@6.3.2: resolution: {integrity: sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -2391,6 +2881,15 @@ packages: yaml: optional: true + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-eslint-parser@10.1.3: + resolution: {integrity: sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + vue-router@4.5.0: resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==} peerDependencies: @@ -2425,6 +2924,10 @@ packages: wildcard@2.0.1: resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -2452,6 +2955,10 @@ packages: utf-8-validate: optional: true + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + xmldoc@1.3.0: resolution: {integrity: sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==} @@ -2467,6 +2974,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + yoctocolors-cjs@2.1.2: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} @@ -2485,6 +2996,12 @@ packages: snapshots: + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/helper-string-parser@7.25.9': {} '@babel/helper-validator-identifier@7.25.9': {} @@ -2575,6 +3092,50 @@ snapshots: '@esbuild/win32-x64@0.25.3': optional: true + '@eslint-community/eslint-utils@4.6.1(eslint@9.25.1)': + dependencies: + eslint: 9.25.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.1': {} + + '@eslint/core@0.13.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.25.1': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.8': + dependencies: + '@eslint/core': 0.13.0 + levn: 0.4.1 + '@foliojs-fork/fontkit@1.9.2': dependencies: '@foliojs-fork/restructure': 2.0.2 @@ -2615,6 +3176,19 @@ snapshots: '@hexagon/base64@1.1.28': {} + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + '@inquirer/figures@1.0.11': {} '@isaacs/cliui@8.0.2': @@ -2650,6 +3224,18 @@ snapshots: '@levischuck/tiny-cbor@0.2.11': {} + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + '@peculiar/asn1-android@2.3.16': dependencies: '@peculiar/asn1-schema': 2.3.15 @@ -2686,9 +3272,12 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@prisma/client@6.6.0(prisma@6.6.0)': + '@pkgr/core@0.2.4': {} + + '@prisma/client@6.6.0(prisma@6.6.0(typescript@5.8.3))(typescript@5.8.3)': optionalDependencies: - prisma: 6.6.0 + prisma: 6.6.0(typescript@5.8.3) + typescript: 5.8.3 '@prisma/config@6.6.0': dependencies: @@ -2718,16 +3307,16 @@ snapshots: dependencies: '@prisma/debug': 6.6.0 - '@quasar/app-vite@2.2.0(@types/node@22.14.1)(pinia@3.0.2(vue@3.5.13))(quasar@2.18.1)(rollup@4.40.0)(terser@5.39.0)(vue-router@4.5.0(vue@3.5.13))(vue@3.5.13)': + '@quasar/app-vite@2.2.0(@types/node@22.14.1)(eslint@9.25.1)(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)))(quasar@2.18.1)(rollup@4.40.0)(terser@5.39.0)(typescript@5.8.3)(vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))': dependencies: '@quasar/render-ssr-error': 1.0.3 '@quasar/ssl-certificate': 1.0.0 - '@quasar/vite-plugin': 1.9.0(@vitejs/plugin-vue@5.2.3(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13))(quasar@2.18.1)(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13) + '@quasar/vite-plugin': 1.9.0(@vitejs/plugin-vue@5.2.3(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13(typescript@5.8.3)))(quasar@2.18.1)(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13(typescript@5.8.3)) '@types/chrome': 0.0.262 '@types/compression': 1.7.5 '@types/cordova': 11.0.3 '@types/express': 4.17.21 - '@vitejs/plugin-vue': 5.2.3(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13) + '@vitejs/plugin-vue': 5.2.3(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13(typescript@5.8.3)) archiver: 7.0.1 chokidar: 3.6.0 ci-info: 4.2.0 @@ -2755,13 +3344,15 @@ snapshots: semver: 7.7.1 serialize-javascript: 6.0.2 tinyglobby: 0.2.13 - ts-essentials: 9.4.2 + ts-essentials: 9.4.2(typescript@5.8.3) vite: 6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0) - vue: 3.5.13 - vue-router: 4.5.0(vue@3.5.13) + vue: 3.5.13(typescript@5.8.3) + vue-router: 4.5.0(vue@3.5.13(typescript@5.8.3)) webpack-merge: 6.0.1 optionalDependencies: - pinia: 3.0.2(vue@3.5.13) + eslint: 9.25.1 + pinia: 3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)) + typescript: 5.8.3 transitivePeerDependencies: - '@types/node' - jiti @@ -2788,12 +3379,12 @@ snapshots: fs-extra: 11.3.0 selfsigned: 2.4.1 - '@quasar/vite-plugin@1.9.0(@vitejs/plugin-vue@5.2.3(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13))(quasar@2.18.1)(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13)': + '@quasar/vite-plugin@1.9.0(@vitejs/plugin-vue@5.2.3(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13(typescript@5.8.3)))(quasar@2.18.1)(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13(typescript@5.8.3))': dependencies: - '@vitejs/plugin-vue': 5.2.3(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13) + '@vitejs/plugin-vue': 5.2.3(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13(typescript@5.8.3)) quasar: 2.18.1 vite: 6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0) - vue: 3.5.13 + vue: 3.5.13(typescript@5.8.3) '@rollup/rollup-android-arm-eabi@4.40.0': optional: true @@ -2872,6 +3463,18 @@ snapshots: '@peculiar/asn1-schema': 2.3.15 '@peculiar/asn1-x509': 2.3.15 + '@stylistic/eslint-plugin@4.2.0(eslint@9.25.1)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/utils': 8.31.0(eslint@9.25.1)(typescript@5.8.3) + eslint: 9.25.1 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + estraverse: 5.3.0 + picomatch: 4.0.2 + transitivePeerDependencies: + - supports-color + - typescript + '@swc/helpers@0.5.17': dependencies: tslib: 2.8.1 @@ -2896,6 +3499,12 @@ snapshots: '@types/cordova@11.0.3': {} + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.7 + '@types/json-schema': 7.0.15 + optional: true + '@types/estree@1.0.7': {} '@types/express-serve-static-core@4.19.6': @@ -2926,6 +3535,8 @@ snapshots: '@types/http-errors@2.0.4': {} + '@types/json-schema@7.0.15': {} + '@types/mime@1.3.5': {} '@types/node-forge@1.3.11': @@ -2953,10 +3564,47 @@ snapshots: '@types/uuid@10.0.0': {} - '@vitejs/plugin-vue@5.2.3(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13)': + '@typescript-eslint/scope-manager@8.31.0': + dependencies: + '@typescript-eslint/types': 8.31.0 + '@typescript-eslint/visitor-keys': 8.31.0 + + '@typescript-eslint/types@8.31.0': {} + + '@typescript-eslint/typescript-estree@8.31.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.31.0 + '@typescript-eslint/visitor-keys': 8.31.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.31.0(eslint@9.25.1)(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1) + '@typescript-eslint/scope-manager': 8.31.0 + '@typescript-eslint/types': 8.31.0 + '@typescript-eslint/typescript-estree': 8.31.0(typescript@5.8.3) + eslint: 9.25.1 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.31.0': + dependencies: + '@typescript-eslint/types': 8.31.0 + eslint-visitor-keys: 4.2.0 + + '@vitejs/plugin-vue@5.2.3(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0))(vue@3.5.13(typescript@5.8.3))': dependencies: vite: 6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0) - vue: 3.5.13 + vue: 3.5.13(typescript@5.8.3) '@vue/compiler-core@3.5.13': dependencies: @@ -3008,6 +3656,15 @@ snapshots: dependencies: rfdc: 1.4.1 + '@vue/eslint-config-prettier@10.2.0(@types/eslint@9.6.1)(eslint@9.25.1)(prettier@3.5.3)': + dependencies: + eslint: 9.25.1 + eslint-config-prettier: 10.1.2(eslint@9.25.1) + eslint-plugin-prettier: 5.2.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.2(eslint@9.25.1))(eslint@9.25.1)(prettier@3.5.3) + prettier: 3.5.3 + transitivePeerDependencies: + - '@types/eslint' + '@vue/reactivity@3.5.13': dependencies: '@vue/shared': 3.5.13 @@ -3024,11 +3681,11 @@ snapshots: '@vue/shared': 3.5.13 csstype: 3.1.3 - '@vue/server-renderer@3.5.13(vue@3.5.13)': + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3))': dependencies: '@vue/compiler-ssr': 3.5.13 '@vue/shared': 3.5.13 - vue: 3.5.13 + vue: 3.5.13(typescript@5.8.3) '@vue/shared@3.5.13': {} @@ -3041,10 +3698,21 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + acorn@8.14.1: {} agent-base@7.1.3: {} + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -3084,6 +3752,8 @@ snapshots: tar-stream: 3.1.7 zip-stream: 6.0.1 + argparse@2.0.1: {} + array-flatten@1.1.1: {} asn1js@3.0.6: @@ -3165,6 +3835,13 @@ snapshots: transitivePeerDependencies: - supports-color + boolbase@1.0.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -3225,6 +3902,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsites@3.1.0: {} + camel-case@4.1.2: dependencies: pascal-case: 3.1.2 @@ -3251,6 +3930,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chownr@1.1.4: {} ci-info@4.2.0: {} @@ -3323,6 +4006,8 @@ snapshots: transitivePeerDependencies: - supports-color + concat-map@0.0.1: {} + confbox@0.1.8: {} content-disposition@0.5.4: @@ -3360,6 +4045,8 @@ snapshots: crypto-js@4.2.0: {} + cssesc@3.0.0: {} + csstype@3.1.3: {} date-fns@4.1.0: {} @@ -3387,6 +4074,8 @@ snapshots: deep-extend@0.6.0: {} + deep-is@0.1.4: {} + deepmerge@4.3.1: {} default-browser-id@5.0.0: {} @@ -3549,8 +4238,102 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.2(eslint@9.25.1): + dependencies: + eslint: 9.25.1 + + eslint-plugin-prettier@5.2.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.2(eslint@9.25.1))(eslint@9.25.1)(prettier@3.5.3): + dependencies: + eslint: 9.25.1 + prettier: 3.5.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.4 + optionalDependencies: + '@types/eslint': 9.6.1 + eslint-config-prettier: 10.1.2(eslint@9.25.1) + + eslint-plugin-vue@10.0.0(eslint@9.25.1)(vue-eslint-parser@10.1.3(eslint@9.25.1)): + dependencies: + '@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1) + eslint: 9.25.1 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.7.1 + vue-eslint-parser: 10.1.3(eslint@9.25.1) + xml-name-validator: 4.0.0 + + eslint-scope@8.3.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.25.1: + dependencies: + '@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.1 + '@eslint/core': 0.13.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.25.1 + '@eslint/plugin-kit': 0.2.8 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.7 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + 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.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-walker@2.0.2: {} + esutils@2.0.3: {} + etag@1.8.1: {} event-target-shim@5.0.1: {} @@ -3618,12 +4401,34 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-diff@1.3.0: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + fdir@6.4.4(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + file-uri-to-path@1.0.0: {} fill-range@7.1.1: @@ -3642,8 +4447,20 @@ snapshots: transitivePeerDependencies: - supports-color + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + flat@5.0.2: {} + flatted@3.3.3: {} + follow-redirects@1.15.9: {} fontkit@2.0.4: @@ -3737,6 +4554,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@10.4.5: dependencies: foreground-child: 3.3.1 @@ -3746,6 +4567,10 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + globals@14.0.0: {} + + globals@16.0.0: {} + google-auth-library@9.15.1: dependencies: base64-js: 1.5.1 @@ -3842,8 +4667,17 @@ snapshots: ieee754@1.2.1: {} + ignore@5.3.2: {} + immutable@5.1.1: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + inherits@2.0.4: {} ini@1.3.8: {} @@ -3940,10 +4774,22 @@ snapshots: jpeg-exif@1.1.4: {} + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + json-bigint@1.0.0: dependencies: bignumber.js: 9.3.0 + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + jsonfile@6.1.0: dependencies: universalify: 2.0.1 @@ -3961,6 +4807,10 @@ snapshots: jwa: 2.0.0 safe-buffer: 5.2.1 + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + kind-of@6.0.3: {} kolorist@1.8.0: {} @@ -3971,6 +4821,11 @@ snapshots: leac@0.6.0: {} + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + libbase64@1.3.0: {} libmime@5.3.6: @@ -3991,6 +4846,12 @@ snapshots: dependencies: uc.micro: 2.1.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + lodash@4.17.21: {} log-symbols@4.1.0: @@ -4035,8 +4896,15 @@ snapshots: merge-descriptors@1.0.3: {} + merge2@1.4.1: {} + methods@1.1.2: {} + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + mime-db@1.52.0: {} mime-db@1.54.0: {} @@ -4051,6 +4919,10 @@ snapshots: mimic-response@3.1.0: {} + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 @@ -4084,6 +4956,8 @@ snapshots: napi-build-utils@2.0.0: {} + natural-compare@1.4.0: {} + negotiator@0.6.3: {} negotiator@0.6.4: {} @@ -4120,6 +4994,15 @@ snapshots: normalize-range@0.1.2: {} + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + object-inspect@1.13.4: {} object-is@1.1.6: @@ -4156,6 +5039,15 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + ora@5.4.1: dependencies: bl: 4.1.0 @@ -4170,6 +5062,14 @@ snapshots: os-tmpdir@1.0.2: {} + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} pako@0.2.9: {} @@ -4179,6 +5079,10 @@ snapshots: dot-case: 3.0.4 tslib: 2.8.1 + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parseley@0.12.1: dependencies: leac: 0.6.0 @@ -4191,8 +5095,12 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 + path-exists@4.0.0: {} + path-key@3.1.1: {} + path-key@4.0.0: {} + path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 @@ -4227,10 +5135,12 @@ snapshots: picomatch@4.0.2: {} - pinia@3.0.2(vue@3.5.13): + pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)): dependencies: '@vue/devtools-api': 7.7.5 - vue: 3.5.13 + vue: 3.5.13(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 pkg-types@1.3.1: dependencies: @@ -4240,6 +5150,11 @@ snapshots: png-js@1.0.0: {} + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-value-parser@4.2.0: {} postcss@8.5.3: @@ -4263,12 +5178,21 @@ snapshots: tar-fs: 2.1.2 tunnel-agent: 0.6.0 - prisma@6.6.0: + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.5.3: {} + + prisma@6.6.0(typescript@5.8.3): dependencies: '@prisma/config': 6.6.0 '@prisma/engines': 6.6.0 optionalDependencies: fsevents: 2.3.3 + typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -4290,6 +5214,8 @@ snapshots: punycode.js@2.3.1: {} + punycode@2.3.1: {} + pvtsutils@1.3.6: dependencies: tslib: 2.8.1 @@ -4302,6 +5228,8 @@ snapshots: quasar@2.18.1: {} + queue-microtask@1.2.3: {} + random-bytes@1.0.0: {} randombytes@2.1.0: @@ -4356,6 +5284,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -4369,6 +5299,8 @@ snapshots: require-directory@2.1.1: {} + resolve-from@4.0.0: {} + restore-cursor@3.1.0: dependencies: onetime: 5.1.2 @@ -4376,6 +5308,8 @@ snapshots: restructure@3.0.2: {} + reusify@1.1.0: {} + rfdc@1.4.1: {} rollup-plugin-visualizer@5.14.0(rollup@4.40.0): @@ -4417,6 +5351,10 @@ snapshots: run-async@3.0.0: {} + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -4689,6 +5627,8 @@ snapshots: strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} + superjson@2.2.2: dependencies: copy-anything: 3.0.5 @@ -4707,6 +5647,11 @@ snapshots: sync-message-port@1.1.3: {} + synckit@0.11.4: + dependencies: + '@pkgr/core': 0.2.4 + tslib: 2.8.1 + tar-fs@2.1.2: dependencies: chownr: 1.1.4 @@ -4741,6 +5686,8 @@ snapshots: tiny-inflate@1.0.3: {} + tiny-invariant@1.3.3: {} + tinyglobby@0.2.13: dependencies: fdir: 6.4.4(picomatch@4.0.2) @@ -4760,7 +5707,13 @@ snapshots: tr46@0.0.3: {} - ts-essentials@9.4.2: {} + ts-api-utils@2.1.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + ts-essentials@9.4.2(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 tslib@2.8.1: {} @@ -4768,6 +5721,10 @@ snapshots: dependencies: safe-buffer: 5.2.1 + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-fest@0.21.3: {} type-fest@4.40.0: {} @@ -4777,6 +5734,8 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + typescript@5.8.3: {} + uc.micro@2.1.0: {} ufo@1.6.1: {} @@ -4797,6 +5756,8 @@ snapshots: pako: 0.2.9 tiny-inflate: 1.0.3 + unicorn-magic@0.3.0: {} + universalify@2.0.1: {} unpipe@1.0.0: {} @@ -4807,6 +5768,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + utf7@1.0.2: dependencies: semver: 5.3.0 @@ -4825,6 +5790,23 @@ snapshots: vary@1.1.2: {} + vite-plugin-checker@0.9.1(eslint@9.25.1)(optionator@0.9.4)(typescript@5.8.3)(vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0)): + dependencies: + '@babel/code-frame': 7.26.2 + chokidar: 4.0.3 + npm-run-path: 6.0.0 + picocolors: 1.1.1 + picomatch: 4.0.2 + strip-ansi: 7.1.0 + tiny-invariant: 1.3.3 + tinyglobby: 0.2.13 + vite: 6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0) + vscode-uri: 3.1.0 + optionalDependencies: + eslint: 9.25.1 + optionator: 0.9.4 + typescript: 5.8.3 + vite@6.3.2(@types/node@22.14.1)(sass-embedded@1.87.0)(terser@5.39.0): dependencies: esbuild: 0.25.3 @@ -4839,18 +5821,35 @@ snapshots: sass-embedded: 1.87.0 terser: 5.39.0 - vue-router@4.5.0(vue@3.5.13): + vscode-uri@3.1.0: {} + + vue-eslint-parser@10.1.3(eslint@9.25.1): + dependencies: + debug: 4.4.0 + eslint: 9.25.1 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.7.1 + transitivePeerDependencies: + - supports-color + + vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)): dependencies: '@vue/devtools-api': 6.6.4 - vue: 3.5.13 + vue: 3.5.13(typescript@5.8.3) - vue@3.5.13: + vue@3.5.13(typescript@5.8.3): dependencies: '@vue/compiler-dom': 3.5.13 '@vue/compiler-sfc': 3.5.13 '@vue/runtime-dom': 3.5.13 - '@vue/server-renderer': 3.5.13(vue@3.5.13) + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.8.3)) '@vue/shared': 3.5.13 + optionalDependencies: + typescript: 5.8.3 wcwidth@1.0.1: dependencies: @@ -4875,6 +5874,8 @@ snapshots: wildcard@2.0.1: {} + word-wrap@1.2.5: {} + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -4897,6 +5898,8 @@ snapshots: ws@8.18.1: {} + xml-name-validator@4.0.0: {} + xmldoc@1.3.0: dependencies: sax: 1.4.1 @@ -4915,6 +5918,8 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.2: {} zip-stream@6.0.1: diff --git a/postcss.config.js b/postcss.config.js index 25db2f4..874ed7e 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,6 @@ // https://github.com/michael-ciniawsky/postcss-load-config -import autoprefixer from 'autoprefixer' +import autoprefixer from 'autoprefixer'; // import rtlcss from 'postcss-rtlcss' export default { @@ -26,4 +26,4 @@ export default { // 3. uncomment the following line (and its import statement above): // rtlcss() ] -} +}; diff --git a/quasar.config.js b/quasar.config.js index 93e4f61..c3988e1 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -1,9 +1,10 @@ // Configuration for your app // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file -import { defineConfig } from '#q-app/wrappers' +import { defineConfig } from '#q-app/wrappers'; -export default defineConfig((/* ctx */) => { +export default defineConfig((/* ctx */) => +{ return { // https://v2.quasar.dev/quasar-cli-vite/prefetch-feature // preFetch: true, @@ -62,6 +63,14 @@ export default defineConfig((/* ctx */) => { // vitePlugins: [ // [ 'package-name', { ..pluginOptions.. }, { server: true, client: true } ] // ] + vitePlugins: [ + ['vite-plugin-checker', { + eslint: { + lintCommand: 'eslint -c ./eslint.config.js "./src*/**/*.{js,mjs,cjs,vue}"', + useFlatConfig: true + } + }, { server: false }] + ] }, // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#devserver @@ -114,7 +123,7 @@ export default defineConfig((/* ctx */) => { // https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr ssr: { prodPort: 3000, // The default port that the production server should use - // (gets superseded if process.env.PORT is specified at runtime) + // (gets superseded if process.env.PORT is specified at runtime) middlewares: [ 'render' // keep this as last one @@ -208,5 +217,5 @@ export default defineConfig((/* ctx */) => { */ extraScripts: [] } - } -}) + }; +}); diff --git a/src-ssr/database.js b/src-ssr/database.js index 129e485..b6e447a 100644 --- a/src-ssr/database.js +++ b/src-ssr/database.js @@ -4,11 +4,4 @@ import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); // Export the Prisma Client instance for use in other modules -export default prisma; - -// --- Old better-sqlite3 code removed --- -// No need for initializeDatabase, getDb, closeDatabase, etc. -// Prisma Client manages the connection pool. - -// --- Settings Functions removed --- -// Settings can now be accessed via prisma.setting.findUnique, prisma.setting.upsert, etc. +export default prisma; \ No newline at end of file diff --git a/src-ssr/middlewares/authMiddleware.js b/src-ssr/middlewares/authMiddleware.js index 97a0cae..754d825 100644 --- a/src-ssr/middlewares/authMiddleware.js +++ b/src-ssr/middlewares/authMiddleware.js @@ -1,7 +1,9 @@ // src-ssr/middlewares/authMiddleware.js -export function requireAuth(req, res, next) { - if (!req.session || !req.session.loggedInUserId) { +export function requireAuth(req, res, next) +{ + if (!req.session || !req.session.loggedInUserId) + { // User is not authenticated return res.status(401).json({ error: 'Authentication required' }); } diff --git a/src-ssr/middlewares/render.js b/src-ssr/middlewares/render.js index 8cc06e2..3775646 100644 --- a/src-ssr/middlewares/render.js +++ b/src-ssr/middlewares/render.js @@ -1,45 +1,59 @@ -import { defineSsrMiddleware } from '#q-app/wrappers' +import { defineSsrMiddleware } from '#q-app/wrappers'; // This middleware should execute as last one // since it captures everything and tries to // render the page with Vue -export default defineSsrMiddleware(({ app, resolve, render, serve }) => { +export default defineSsrMiddleware(({ app, resolve, render, serve }) => +{ // we capture any other Express route and hand it // over to Vue and Vue Router to render our page - app.get(resolve.urlPath('*'), (req, res) => { - res.setHeader('Content-Type', 'text/html') + app.get(resolve.urlPath('*'), (req, res) => + { + res.setHeader('Content-Type', 'text/html'); render(/* the ssrContext: */ { req, res }) - .then(html => { + .then(html => + { // now let's send the rendered html to the client - res.send(html) + res.send(html); }) - .catch(err => { + .catch(err => + { // oops, we had an error while rendering the page // we were told to redirect to another URL - if (err.url) { - if (err.code) { - res.redirect(err.code, err.url) - } else { - res.redirect(err.url) + if (err.url) + { + if (err.code) + { + res.redirect(err.code, err.url); } - } else if (err.code === 404) { + else + { + res.redirect(err.url); + } + } + else if (err.code === 404) + { // hmm, Vue Router could not find the requested route // Should reach here only if no "catch-all" route // is defined in /src/routes - res.status(404).send('404 | Page Not Found') - } else if (process.env.DEV) { + res.status(404).send('404 | Page Not Found'); + } + else if (process.env.DEV) + { // well, we treat any other code as error; // if we're in dev mode, then we can use Quasar CLI // to display a nice error page that contains the stack // and other useful information // serve.error is available on dev only - serve.error({ err, req, res }) - } else { + serve.error({ err, req, res }); + } + else + { // we're in production, so we should have another method // to display something to the client when we encounter an error // (for security reasons, it's not ok to display the same wealth @@ -47,12 +61,13 @@ export default defineSsrMiddleware(({ app, resolve, render, serve }) => { // Render Error Page on production or // create a route (/src/routes) for an error page and redirect to it - res.status(500).send('500 | Internal Server Error') + res.status(500).send('500 | Internal Server Error'); - if (process.env.DEBUGGING) { - console.error(err.stack) + if (process.env.DEBUGGING) + { + console.error(err.stack); } } - }) - }) -}) + }); + }); +}); diff --git a/src-ssr/routes/api.js b/src-ssr/routes/api.js index 004f572..6d112ed 100644 --- a/src-ssr/routes/api.js +++ b/src-ssr/routes/api.js @@ -1,20 +1,21 @@ import { Router } from 'express'; -import prisma from '../database.js'; // Import Prisma client +import prisma from '../database.js'; import PDFDocument from 'pdfkit'; import { join } from 'path'; -import { generateTodaysSummary } from '../services/mantisSummarizer.js'; // Keep mantisSummarizer import -import { generateAndStoreEmailSummary } from '../services/emailSummarizer.js'; // Import email summarizer function -import { FieldType } from '@prisma/client'; // Import generated FieldType enum +import { generateTodaysSummary } from '../services/mantisSummarizer.js'; +import { FieldType } from '@prisma/client'; const router = Router(); const __dirname = new URL('.', import.meta.url).pathname.replace(/\/$/, ''); // Helper function for consistent error handling -const handlePrismaError = (res, err, context) => { +const handlePrismaError = (res, err, context) => +{ console.error(`Error ${context}:`, err.message); // Basic error handling, can be expanded (e.g., check for Prisma-specific error codes) - if (err.code === 'P2025') { // Prisma code for record not found + if (err.code === 'P2025') + { // Prisma code for record not found return res.status(404).json({ error: `${context}: Record not found` }); } res.status(500).json({ error: `Failed to ${context}: ${err.message}` }); @@ -23,8 +24,10 @@ const handlePrismaError = (res, err, context) => { // --- Forms API --- // // GET /api/forms - List all forms -router.get('/forms', async (req, res) => { - try { +router.get('/forms', async(req, res) => +{ + try + { const forms = await prisma.form.findMany({ orderBy: { createdAt: 'desc', @@ -37,20 +40,25 @@ router.get('/forms', async (req, res) => { } }); res.json(forms); - } catch (err) { + } + catch (err) + { handlePrismaError(res, err, 'fetch forms'); } }); // POST /api/forms - Create a new form -router.post('/forms', async (req, res) => { +router.post('/forms', async(req, res) => +{ const { title, description, categories } = req.body; - if (!title) { + if (!title) + { return res.status(400).json({ error: 'Form title is required' }); } - try { + try + { const newForm = await prisma.form.create({ data: { title, @@ -60,12 +68,15 @@ router.post('/forms', async (req, res) => { name: category.name, sortOrder: catIndex, fields: { - create: category.fields?.map((field, fieldIndex) => { + create: category.fields?.map((field, fieldIndex) => + { // Validate field type against Prisma Enum - if (!Object.values(FieldType).includes(field.type)) { + if (!Object.values(FieldType).includes(field.type)) + { throw new Error(`Invalid field type: ${field.type}`); } - if (!field.label) { + if (!field.label) + { throw new Error('Field label is required'); } return { @@ -86,21 +97,26 @@ router.post('/forms', async (req, res) => { } }); res.status(201).json(newForm); - } catch (err) { + } + catch (err) + { handlePrismaError(res, err, 'create form'); } }); // GET /api/forms/:id - Get a specific form with its structure -router.get('/forms/:id', async (req, res) => { +router.get('/forms/:id', async(req, res) => +{ const { id } = req.params; const formId = parseInt(id, 10); - if (isNaN(formId)) { + if (isNaN(formId)) + { return res.status(400).json({ error: 'Invalid form ID' }); } - try { + try + { const form = await prisma.form.findUnique({ where: { id: formId }, include: { @@ -115,54 +131,68 @@ router.get('/forms/:id', async (req, res) => { }, }); - if (!form) { + if (!form) + { return res.status(404).json({ error: 'Form not found' }); } res.json(form); - } catch (err) { + } + catch (err) + { handlePrismaError(res, err, `fetch form ${formId}`); } }); // DELETE /api/forms/:id - Delete a specific form and all related data -router.delete('/forms/:id', async (req, res) => { +router.delete('/forms/:id', async(req, res) => +{ const { id } = req.params; const formId = parseInt(id, 10); - if (isNaN(formId)) { + if (isNaN(formId)) + { return res.status(400).json({ error: 'Invalid form ID' }); } - try { + try + { // Prisma automatically handles cascading deletes based on schema relations (onDelete: Cascade) const deletedForm = await prisma.form.delete({ where: { id: formId }, }); res.status(200).json({ message: `Form ${formId} and all related data deleted successfully.` }); - } catch (err) { + } + catch (err) + { handlePrismaError(res, err, `delete form ${formId}`); } }); // PUT /api/forms/:id - Update an existing form -router.put('/forms/:id', async (req, res) => { +router.put('/forms/:id', async(req, res) => +{ const { id } = req.params; const formId = parseInt(id, 10); const { title, description, categories } = req.body; - if (isNaN(formId)) { + if (isNaN(formId)) + { return res.status(400).json({ error: 'Invalid form ID' }); } - if (!title) { + if (!title) + { return res.status(400).json({ error: 'Form title is required' }); } - try { + try + { // Use a transaction to ensure atomicity: delete old structure, update form, create new structure - const result = await prisma.$transaction(async (tx) => { + const result = await prisma.$transaction(async(tx) => + { // 1. Check if form exists (optional, delete/update will fail if not found anyway) const existingForm = await tx.form.findUnique({ where: { id: formId } }); - if (!existingForm) { + if (!existingForm) + { throw { code: 'P2025' }; // Simulate Prisma not found error } @@ -180,11 +210,14 @@ router.put('/forms/:id', async (req, res) => { name: category.name, sortOrder: catIndex, fields: { - create: category.fields?.map((field, fieldIndex) => { - if (!Object.values(FieldType).includes(field.type)) { + create: category.fields?.map((field, fieldIndex) => + { + if (!Object.values(FieldType).includes(field.type)) + { throw new Error(`Invalid field type: ${field.type}`); } - if (!field.label) { + if (!field.label) + { throw new Error('Field label is required'); } return { @@ -208,7 +241,9 @@ router.put('/forms/:id', async (req, res) => { }); res.status(200).json(result); - } catch (err) { + } + catch (err) + { handlePrismaError(res, err, `update form ${formId}`); } }); @@ -217,24 +252,30 @@ router.put('/forms/:id', async (req, res) => { // --- Responses API --- // // POST /api/forms/:id/responses - Submit a response for a form -router.post('/forms/:id/responses', async (req, res) => { +router.post('/forms/:id/responses', async(req, res) => +{ const { id } = req.params; const formId = parseInt(id, 10); const { values } = req.body; // values is expected to be { fieldId: value, ... } - if (isNaN(formId)) { + if (isNaN(formId)) + { return res.status(400).json({ error: 'Invalid form ID' }); } - if (!values || typeof values !== 'object' || Object.keys(values).length === 0) { + if (!values || typeof values !== 'object' || Object.keys(values).length === 0) + { return res.status(400).json({ error: 'Response values are required' }); } - try { + try + { // Use transaction to ensure response and values are created together - const result = await prisma.$transaction(async (tx) => { + const result = await prisma.$transaction(async(tx) => + { // 1. Verify form exists const form = await tx.form.findUnique({ where: { id: formId }, select: { id: true } }); - if (!form) { + if (!form) + { throw { code: 'P2025' }; // Simulate Prisma not found error } @@ -253,23 +294,27 @@ router.post('/forms/:id/responses', async (req, res) => { // Optional: Verify all field IDs belong to the form (more robust) const validFields = await tx.field.findMany({ where: { - id: { in: fieldIds }, + id: { 'in': fieldIds }, category: { formId: formId } }, select: { id: true } }); const validFieldIds = new Set(validFields.map(f => f.id)); - for (const fieldIdStr in values) { + for (const fieldIdStr in values) + { const fieldId = parseInt(fieldIdStr, 10); - if (validFieldIds.has(fieldId)) { + if (validFieldIds.has(fieldId)) + { const value = values[fieldIdStr]; responseValuesData.push({ responseId: newResponse.id, fieldId: fieldId, value: (value === null || typeof value === 'undefined') ? null : String(value), }); - } else { + } + else + { console.warn(`Attempted to submit value for field ${fieldId} not belonging to form ${formId}`); // Decide whether to throw an error or just skip invalid fields // throw new Error(`Field ${fieldId} does not belong to form ${formId}`); @@ -277,7 +322,8 @@ router.post('/forms/:id/responses', async (req, res) => { } // 4. Create all response values - if (responseValuesData.length > 0) { + if (responseValuesData.length > 0) + { await tx.responseValue.createMany({ data: responseValuesData, }); @@ -287,24 +333,30 @@ router.post('/forms/:id/responses', async (req, res) => { }); res.status(201).json(result); - } catch (err) { + } + catch (err) + { handlePrismaError(res, err, `submit response for form ${formId}`); } }); // GET /api/forms/:id/responses - Get all responses for a form -router.get('/forms/:id/responses', async (req, res) => { +router.get('/forms/:id/responses', async(req, res) => +{ const { id } = req.params; const formId = parseInt(id, 10); - if (isNaN(formId)) { + if (isNaN(formId)) + { return res.status(400).json({ error: 'Invalid form ID' }); } - try { + try + { // 1. Check if form exists const formExists = await prisma.form.findUnique({ where: { id: formId }, select: { id: true } }); - if (!formExists) { + if (!formExists) + { return res.status(404).json({ error: 'Form not found' }); } @@ -328,13 +380,15 @@ router.get('/forms/:id/responses', async (req, res) => { id: response.id, submittedAt: response.submittedAt, values: response.responseValues - .sort((a, b) => { + .sort((a, b) => + { // Sort by category order, then field order const catSort = a.field.category.sortOrder - b.field.category.sortOrder; if (catSort !== 0) return catSort; return a.field.sortOrder - b.field.sortOrder; }) - .reduce((acc, rv) => { + .reduce((acc, rv) => + { acc[rv.fieldId] = { label: rv.field.label, type: rv.field.type, @@ -345,22 +399,27 @@ router.get('/forms/:id/responses', async (req, res) => { })); res.json(groupedResponses); - } catch (err) { + } + catch (err) + { handlePrismaError(res, err, `fetch responses for form ${formId}`); } }); // GET /responses/:responseId/export/pdf - Export response as PDF -router.get('/responses/:responseId/export/pdf', async (req, res) => { +router.get('/responses/:responseId/export/pdf', async(req, res) => +{ const { responseId: responseIdStr } = req.params; const responseId = parseInt(responseIdStr, 10); - if (isNaN(responseId)) { + if (isNaN(responseId)) + { return res.status(400).json({ error: 'Invalid response ID' }); } - try { + try + { // 1. Fetch the response, form title, form structure, and values in one go const responseData = await prisma.response.findUnique({ where: { id: responseId }, @@ -385,13 +444,15 @@ router.get('/responses/:responseId/export/pdf', async (req, res) => { } }); - if (!responseData) { + if (!responseData) + { return res.status(404).json({ error: 'Response not found' }); } const formTitle = responseData.form.title; const categories = responseData.form.categories; - const responseValues = responseData.responseValues.reduce((acc, rv) => { + const responseValues = responseData.responseValues.reduce((acc, rv) => + { acc[rv.fieldId] = (rv.value === null || typeof rv.value === 'undefined') ? '' : String(rv.value); return acc; }, {}); @@ -414,39 +475,53 @@ router.get('/responses/:responseId/export/pdf', async (req, res) => { doc.fontSize(18).font('Roboto-Bold').text(formTitle, { align: 'center' }); doc.moveDown(); - for (const category of categories) { - if (category.name) { - doc.fontSize(14).font('Roboto-Bold').text(category.name); - doc.moveDown(0.5); + for (const category of categories) + { + if (category.name) + { + doc.fontSize(14).font('Roboto-Bold').text(category.name); + doc.moveDown(0.5); } - for (const field of category.fields) { + for (const field of category.fields) + { const value = responseValues[field.id] || ''; doc.fontSize(12).font('Roboto-SemiBold').text(field.label + ':', { continued: false }); - if (field.description) { - doc.fontSize(9).font('Roboto-Italics').text(field.description); + if (field.description) + { + doc.fontSize(9).font('Roboto-Italics').text(field.description); } doc.moveDown(0.2); doc.fontSize(11).font('Roboto-Regular'); - if (field.type === 'textarea') { + if (field.type === 'textarea') + { const textHeight = doc.heightOfString(value, { width: 500 }); doc.rect(doc.x, doc.y, 500, Math.max(textHeight + 10, 30)).stroke(); doc.text(value, doc.x + 5, doc.y + 5, { width: 490 }); doc.y += Math.max(textHeight + 10, 30) + 10; - } else if (field.type === 'date') { + } + else if (field.type === 'date') + { let formattedDate = ''; - if (value) { - try { + if (value) + { + try + { const dateObj = new Date(value + 'T00:00:00'); - if (!isNaN(dateObj.getTime())) { + if (!isNaN(dateObj.getTime())) + { const day = String(dateObj.getDate()).padStart(2, '0'); const month = String(dateObj.getMonth() + 1).padStart(2, '0'); const year = dateObj.getFullYear(); formattedDate = `${day}/${month}/${year}`; - } else { + } + else + { formattedDate = value; } - } catch (e) { + } + catch (e) + { console.error('Error formatting date:', value, e); formattedDate = value; } @@ -454,28 +529,37 @@ router.get('/responses/:responseId/export/pdf', async (req, res) => { doc.text(formattedDate || ' '); doc.lineCap('butt').moveTo(doc.x, doc.y).lineTo(doc.x + 500, doc.y).stroke(); doc.moveDown(1.5); - } else if (field.type === 'boolean') { + } + else if (field.type === 'boolean') + { const displayValue = value === 'true' ? 'Yes' : (value === 'false' ? 'No' : ' '); doc.text(displayValue); doc.lineCap('butt').moveTo(doc.x, doc.y).lineTo(doc.x + 500, doc.y).stroke(); doc.moveDown(1.5); - } else { + } + else + { doc.text(value || ' '); doc.lineCap('butt').moveTo(doc.x, doc.y).lineTo(doc.x + 500, doc.y).stroke(); doc.moveDown(1.5); } } - doc.moveDown(1); + doc.moveDown(1); } doc.end(); - } catch (err) { + } + catch (err) + { console.error(`Error generating PDF for response ${responseId}:`, err.message); - if (!res.headersSent) { + if (!res.headersSent) + { // Use the helper function handlePrismaError(res, err, `generate PDF for response ${responseId}`); - } else { - console.error("Headers already sent, could not send JSON error for PDF generation failure."); + } + else + { + console.error('Headers already sent, could not send JSON error for PDF generation failure.'); res.end(); } } @@ -485,8 +569,10 @@ router.get('/responses/:responseId/export/pdf', async (req, res) => { // --- Mantis Summary API Route --- // // GET /api/mantis-summary/today - Get today's summary specifically -router.get('/mantis-summary/today', async (req, res) => { - try { +router.get('/mantis-summary/today', async(req, res) => +{ + try + { const today = new Date(); today.setHours(0, 0, 0, 0); // Set to start of day UTC for comparison @@ -495,23 +581,30 @@ router.get('/mantis-summary/today', async (req, res) => { select: { summaryDate: true, summaryText: true, generatedAt: true } }); - if (todaySummary) { + if (todaySummary) + { res.json(todaySummary); - } else { + } + else + { res.status(404).json({ message: `No Mantis summary found for today (${today.toISOString().split('T')[0]}).` }); } - } catch (error) { + } + catch (error) + { handlePrismaError(res, error, 'fetch today\'s Mantis summary'); } }); // GET /api/mantis-summaries - Get ALL summaries from the DB, with pagination -router.get('/mantis-summaries', async (req, res) => { +router.get('/mantis-summaries', async(req, res) => +{ const page = parseInt(req.query.page, 10) || 1; const limit = parseInt(req.query.limit, 10) || 10; const skip = (page - 1) * limit; - try { + try + { const [summaries, totalItems] = await prisma.$transaction([ prisma.mantisSummary.findMany({ orderBy: { summaryDate: 'desc' }, @@ -523,104 +616,78 @@ router.get('/mantis-summaries', async (req, res) => { ]); res.json({ summaries, total: totalItems }); - } catch (error) { + } + catch (error) + { handlePrismaError(res, error, 'fetch paginated Mantis summaries'); } }); // POST /api/mantis-summaries/generate - Trigger summary generation -router.post('/mantis-summaries/generate', async (req, res) => { - try { +router.post('/mantis-summaries/generate', async(req, res) => +{ + try + { // Trigger generation asynchronously, don't wait for it generateTodaysSummary() - .then(() => { + .then(() => + { console.log('Summary generation process finished successfully (async).'); }) - .catch(error => { + .catch(error => + { console.error('Background summary generation failed:', error); }); res.status(202).json({ message: 'Summary generation started.' }); - } catch (error) { + } + catch (error) + { handlePrismaError(res, error, 'initiate Mantis summary generation'); } }); -// --- Email Summary API Routes --- // - -// GET /api/email-summaries - Get ALL email summaries from the DB, with pagination -router.get('/email-summaries', async (req, res) => { - const page = parseInt(req.query.page, 10) || 1; - const limit = parseInt(req.query.limit, 10) || 10; - const skip = (page - 1) * limit; - - try { - const [summaries, totalItems] = await prisma.$transaction([ - prisma.emailSummary.findMany({ // Use emailSummary model - orderBy: { summaryDate: 'desc' }, - take: limit, - skip: skip, - select: { id: true, summaryDate: true, summaryText: true, generatedAt: true } - }), - prisma.emailSummary.count() // Count emailSummary model - ]); - - res.json({ summaries, total: totalItems }); - } catch (error) { - handlePrismaError(res, error, 'fetch paginated Email summaries'); - } -}); - -// POST /api/email-summaries/generate - Trigger email summary generation -router.post('/email-summaries/generate', async (req, res) => { - try { - // Trigger generation asynchronously, don't wait for it - generateAndStoreEmailSummary() // Use the email summarizer function - .then(() => { - console.log('Email summary generation process finished successfully (async).'); - }) - .catch(error => { - console.error('Background email summary generation failed:', error); - }); - - res.status(202).json({ message: 'Email summary generation started.' }); - } catch (error) { - handlePrismaError(res, error, 'initiate Email summary generation'); - } -}); - - // --- Settings API --- // // GET /api/settings/:key - Get a specific setting value -router.get('/settings/:key', async (req, res) => { +router.get('/settings/:key', async(req, res) => +{ const { key } = req.params; - try { + try + { const setting = await prisma.setting.findUnique({ where: { key: key }, select: { value: true } }); - if (setting !== null) { + if (setting !== null) + { res.json({ key, value: setting.value }); - } else { + } + else + { res.json({ key, value: '' }); // Return empty value if not found } - } catch (err) { + } + catch (err) + { handlePrismaError(res, err, `fetch setting '${key}'`); } }); // PUT /api/settings/:key - Update or create a specific setting -router.put('/settings/:key', async (req, res) => { +router.put('/settings/:key', async(req, res) => +{ const { key } = req.params; const { value } = req.body; - if (typeof value === 'undefined') { + if (typeof value === 'undefined') + { return res.status(400).json({ error: 'Setting value is required in the request body' }); } - try { + try + { const upsertedSetting = await prisma.setting.upsert({ where: { key: key }, update: { value: String(value) }, @@ -628,7 +695,9 @@ router.put('/settings/:key', async (req, res) => { select: { key: true, value: true } // Select to return the updated/created value }); res.status(200).json(upsertedSetting); - } catch (err) { + } + catch (err) + { handlePrismaError(res, err, `update setting '${key}'`); } }); diff --git a/src-ssr/routes/auth.js b/src-ssr/routes/auth.js index c867edc..77ef853 100644 --- a/src-ssr/routes/auth.js +++ b/src-ssr/routes/auth.js @@ -13,7 +13,8 @@ import { rpID, rpName, origin, challengeStore } from '../server.js'; // Import R const router = express.Router(); // Helper function to get user authenticators -async function getUserAuthenticators(userId) { +async function getUserAuthenticators(userId) +{ return prisma.authenticator.findMany({ where: { userId }, select: { @@ -26,34 +27,41 @@ async function getUserAuthenticators(userId) { } // Helper function to get a user by username -async function getUserByUsername(username) { - return prisma.user.findUnique({ where: { username } }); +async function getUserByUsername(username) +{ + return prisma.user.findUnique({ where: { username } }); } // Helper function to get a user by ID -async function getUserById(id) { - return prisma.user.findUnique({ where: { id } }); +async function getUserById(id) +{ + return prisma.user.findUnique({ where: { id } }); } // Helper function to get an authenticator by credential ID -async function getAuthenticatorByCredentialID(credentialID) { - return prisma.authenticator.findUnique({ where: { credentialID } }); +async function getAuthenticatorByCredentialID(credentialID) +{ + return prisma.authenticator.findUnique({ where: { credentialID } }); } // Generate Registration Options -router.post('/generate-registration-options', async (req, res) => { +router.post('/generate-registration-options', async(req, res) => +{ const { username } = req.body; - if (!username) { + if (!username) + { return res.status(400).json({ error: 'Username is required' }); } - try { + try + { let user = await getUserByUsername(username); // If user doesn't exist, create one - if (!user) { + if (!user) + { user = await prisma.user.create({ data: { username }, }); @@ -61,9 +69,11 @@ router.post('/generate-registration-options', async (req, res) => { const userAuthenticators = await getUserAuthenticators(user.id); - if(userAuthenticators.length > 0) { + if(userAuthenticators.length > 0) + { //The user is trying to register a new authenticator, so we need to check if the user registering is the same as the one in the session - if (!req.session.loggedInUserId || req.session.loggedInUserId !== user.id) { + if (!req.session.loggedInUserId || req.session.loggedInUserId !== user.id) + { return res.status(403).json({ error: 'Invalid registration attempt.' }); } } @@ -93,31 +103,38 @@ router.post('/generate-registration-options', async (req, res) => { req.session.userId = user.id; // Temporarily store userId in session for verification step res.json(options); - } catch (error) { + } + catch (error) + { console.error('Registration options error:', error); res.status(500).json({ error: 'Failed to generate registration options' }); } }); // Verify Registration -router.post('/verify-registration', async (req, res) => { +router.post('/verify-registration', async(req, res) => +{ const { registrationResponse } = req.body; const userId = req.session.userId; // Retrieve userId stored during options generation - if (!userId) { - return res.status(400).json({ error: 'User session not found. Please start registration again.' }); + if (!userId) + { + return res.status(400).json({ error: 'User session not found. Please start registration again.' }); } const expectedChallenge = challengeStore.get(userId); - if (!expectedChallenge) { + if (!expectedChallenge) + { return res.status(400).json({ error: 'Challenge not found or expired' }); } - try { + try + { const user = await getUserById(userId); - if (!user) { - return res.status(404).json({ error: 'User not found' }); + if (!user) + { + return res.status(404).json({ error: 'User not found' }); } const verification = await verifyRegistrationResponse({ @@ -132,7 +149,8 @@ router.post('/verify-registration', async (req, res) => { console.log(verification); - if (verified && registrationInfo) { + if (verified && registrationInfo) + { const { credential, credentialDeviceType, credentialBackedUp } = registrationInfo; const credentialID = credential.id; @@ -143,7 +161,8 @@ router.post('/verify-registration', async (req, res) => { // Check if authenticator with this ID already exists const existingAuthenticator = await getAuthenticatorByCredentialID(isoBase64URL.fromBuffer(credentialID)); - if (existingAuthenticator) { + if (existingAuthenticator) + { return res.status(409).json({ error: 'Authenticator already registered' }); } @@ -168,10 +187,14 @@ router.post('/verify-registration', async (req, res) => { req.session.loggedInUserId = user.id; res.json({ verified: true }); - } else { + } + else + { res.status(400).json({ error: 'Registration verification failed' }); } - } catch (error) { + } + catch (error) + { console.error('Registration verification error:', error); challengeStore.delete(userId); // Clean up challenge on error delete req.session.userId; @@ -180,19 +203,25 @@ router.post('/verify-registration', async (req, res) => { }); // Generate Authentication Options -router.post('/generate-authentication-options', async (req, res) => { +router.post('/generate-authentication-options', async(req, res) => +{ const { username } = req.body; - try { + try + { let user; - if (username) { - user = await getUserByUsername(username); - } else if (req.session.loggedInUserId) { - // If already logged in, allow re-authentication (e.g., for step-up) - user = await getUserById(req.session.loggedInUserId); + if (username) + { + user = await getUserByUsername(username); + } + else if (req.session.loggedInUserId) + { + // If already logged in, allow re-authentication (e.g., for step-up) + user = await getUserById(req.session.loggedInUserId); } - if (!user) { + if (!user) + { return res.status(404).json({ error: 'User not found' }); } @@ -218,42 +247,51 @@ router.post('/generate-authentication-options', async (req, res) => { req.session.challengeUserId = user.id; // Store user ID associated with this challenge res.json(options); - } catch (error) { + } + catch (error) + { console.error('Authentication options error:', error); res.status(500).json({ error: 'Failed to generate authentication options' }); } }); // Verify Authentication -router.post('/verify-authentication', async (req, res) => { +router.post('/verify-authentication', async(req, res) => +{ const { authenticationResponse } = req.body; const challengeUserId = req.session.challengeUserId; // Get user ID associated with the challenge - if (!challengeUserId) { - return res.status(400).json({ error: 'Challenge session not found. Please try logging in again.' }); + if (!challengeUserId) + { + return res.status(400).json({ error: 'Challenge session not found. Please try logging in again.' }); } const expectedChallenge = challengeStore.get(challengeUserId); - if (!expectedChallenge) { + if (!expectedChallenge) + { return res.status(400).json({ error: 'Challenge not found or expired' }); } - try { + try + { const user = await getUserById(challengeUserId); - if (!user) { - return res.status(404).json({ error: 'User associated with challenge not found' }); + if (!user) + { + return res.status(404).json({ error: 'User associated with challenge not found' }); } const authenticator = await getAuthenticatorByCredentialID(authenticationResponse.id); - if (!authenticator) { + if (!authenticator) + { return res.status(404).json({ error: 'Authenticator not found' }); } // Ensure the authenticator belongs to the user attempting to log in - if (authenticator.userId !== user.id) { - return res.status(403).json({ error: 'Authenticator does not belong to this user' }); + if (authenticator.userId !== user.id) + { + return res.status(403).json({ error: 'Authenticator does not belong to this user' }); } const verification = await verifyAuthenticationResponse({ @@ -272,7 +310,8 @@ router.post('/verify-authentication', async (req, res) => { const { verified, authenticationInfo } = verification; - if (verified) { + if (verified) + { // Update the authenticator counter await prisma.authenticator.update({ where: { credentialID: authenticator.credentialID }, @@ -287,10 +326,14 @@ router.post('/verify-authentication', async (req, res) => { req.session.loggedInUserId = user.id; res.json({ verified: true, user: { id: user.id, username: user.username } }); - } else { + } + else + { res.status(400).json({ error: 'Authentication verification failed' }); } - } catch (error) { + } + catch (error) + { console.error('Authentication verification error:', error); challengeStore.delete(challengeUserId); // Clean up challenge on error delete req.session.challengeUserId; @@ -299,12 +342,15 @@ router.post('/verify-authentication', async (req, res) => { }); // GET Passkeys for Logged-in User -router.get('/passkeys', async (req, res) => { - if (!req.session.loggedInUserId) { +router.get('/passkeys', async(req, res) => +{ + if (!req.session.loggedInUserId) + { return res.status(401).json({ error: 'Not authenticated' }); } - try { + try + { const userId = req.session.loggedInUserId; const authenticators = await prisma.authenticator.findMany({ where: { userId }, @@ -317,25 +363,31 @@ router.get('/passkeys', async (req, res) => { // No need to convert credentialID here as it's stored as Base64URL string res.json(authenticators); - } catch (error) { + } + catch (error) + { console.error('Error fetching passkeys:', error); res.status(500).json({ error: 'Failed to fetch passkeys' }); } }); // DELETE Passkey -router.delete('/passkeys/:credentialID', async (req, res) => { - if (!req.session.loggedInUserId) { +router.delete('/passkeys/:credentialID', async(req, res) => +{ + if (!req.session.loggedInUserId) + { return res.status(401).json({ error: 'Not authenticated' }); } const { credentialID } = req.params; // This is already a Base64URL string from the client - if (!credentialID) { + if (!credentialID) + { return res.status(400).json({ error: 'Credential ID is required' }); } - try { + try + { const userId = req.session.loggedInUserId; // Find the authenticator first to ensure it belongs to the logged-in user @@ -343,12 +395,14 @@ router.delete('/passkeys/:credentialID', async (req, res) => { where: { credentialID: credentialID }, // Use the Base64URL string directly }); - if (!authenticator) { + if (!authenticator) + { return res.status(404).json({ error: 'Passkey not found' }); } // Security check: Ensure the passkey belongs to the user trying to delete it - if (authenticator.userId !== userId) { + if (authenticator.userId !== userId) + { return res.status(403).json({ error: 'Permission denied' }); } @@ -358,28 +412,36 @@ router.delete('/passkeys/:credentialID', async (req, res) => { }); res.json({ message: 'Passkey deleted successfully' }); - } catch (error) { + } + catch (error) + { console.error('Error deleting passkey:', error); // Handle potential Prisma errors, e.g., record not found if deleted between check and delete - if (error.code === 'P2025') { // Prisma code for record not found on delete/update - return res.status(404).json({ error: 'Passkey not found' }); + if (error.code === 'P2025') + { // Prisma code for record not found on delete/update + return res.status(404).json({ error: 'Passkey not found' }); } res.status(500).json({ error: 'Failed to delete passkey' }); } }); // Check Authentication Status -router.get('/status', (req, res) => { - if (req.session.loggedInUserId) { +router.get('/status', (req, res) => +{ + if (req.session.loggedInUserId) + { return res.json({ status: 'authenticated' }); } res.json({ status: 'unauthenticated' }); }); // Logout -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { +router.post('/logout', (req, res) => +{ + req.session.destroy(err => + { + if (err) + { console.error('Logout error:', err); return res.status(500).json({ error: 'Failed to logout' }); } diff --git a/src-ssr/routes/chat.js b/src-ssr/routes/chat.js index 34b0c1f..cfd649f 100644 --- a/src-ssr/routes/chat.js +++ b/src-ssr/routes/chat.js @@ -10,17 +10,21 @@ const router = Router(); router.use(requireAuth); // POST /api/chat/threads - Create a new chat thread (optionally with a first message) -router.post('/threads', async (req, res) => { +router.post('/threads', async(req, res) => +{ const { content } = req.body; // Content is now optional // If content is provided, validate it - if (content && (typeof content !== 'string' || content.trim().length === 0)) { + if (content && (typeof content !== 'string' || content.trim().length === 0)) + { return res.status(400).json({ error: 'Message content cannot be empty if provided.' }); } - try { + try + { const createData = {}; - if (content) { + if (content) + { // If content exists, create the thread with the first message createData.messages = { create: [ @@ -48,21 +52,25 @@ router.post('/threads', async (req, res) => { // Respond with the new thread ID and messages (if any) res.status(201).json({ - threadId: newThread.id, - // Ensure messages array is empty if no content was provided - messages: newThread.messages ? newThread.messages.map(msg => ({ ...msg, createdAt: msg.createdAt.toISOString() })) : [] + threadId: newThread.id, + // Ensure messages array is empty if no content was provided + messages: newThread.messages ? newThread.messages.map(msg => ({ ...msg, createdAt: msg.createdAt.toISOString() })) : [] }); - } catch (error) { + } + catch (error) + { console.error('Error creating chat thread:', error); res.status(500).json({ error: 'Failed to create chat thread.' }); } }); // GET /api/chat/threads/:threadId/messages - Get messages for a specific thread -router.get('/threads/:threadId/messages', async (req, res) => { +router.get('/threads/:threadId/messages', async(req, res) => +{ const { threadId } = req.params; - try { + try + { const messages = await prisma.chatMessage.findMany({ where: { threadId: threadId, @@ -72,45 +80,55 @@ router.get('/threads/:threadId/messages', async (req, res) => { }, }); - if (!messages) { // Check if thread exists indirectly - // If findMany returns empty, the thread might not exist or has no messages. - // Check if thread exists explicitly - const thread = await prisma.chatThread.findUnique({ where: { id: threadId } }); - if (!thread) { - return res.status(404).json({ error: 'Chat thread not found.' }); - } + if (!messages) + { // Check if thread exists indirectly + // If findMany returns empty, the thread might not exist or has no messages. + // Check if thread exists explicitly + const thread = await prisma.chatThread.findUnique({ where: { id: threadId } }); + if (!thread) + { + return res.status(404).json({ error: 'Chat thread not found.' }); + } } res.status(200).json(messages.map(msg => ({ ...msg, createdAt: msg.createdAt.toISOString() }))); - } catch (error) { + } + catch (error) + { console.error(`Error fetching messages for thread ${threadId}:`, error); // Basic error handling, check for specific Prisma errors if needed - if (error.code === 'P2023' || error.message.includes('Malformed UUID')) { // Example: Invalid UUID format - return res.status(400).json({ error: 'Invalid thread ID format.' }); + if (error.code === 'P2023' || error.message.includes('Malformed UUID')) + { // Example: Invalid UUID format + return res.status(400).json({ error: 'Invalid thread ID format.' }); } res.status(500).json({ error: 'Failed to fetch messages.' }); } }); // POST /api/chat/threads/:threadId/messages - Add a message to an existing thread -router.post('/threads/:threadId/messages', async (req, res) => { +router.post('/threads/:threadId/messages', async(req, res) => +{ const { threadId } = req.params; const { content, sender = 'user' } = req.body; // Default sender to 'user' - if (!content || typeof content !== 'string' || content.trim().length === 0) { + if (!content || typeof content !== 'string' || content.trim().length === 0) + { return res.status(400).json({ error: 'Message content cannot be empty.' }); } - if (sender !== 'user' && sender !== 'bot') { - return res.status(400).json({ error: 'Invalid sender type.' }); + if (sender !== 'user' && sender !== 'bot') + { + return res.status(400).json({ error: 'Invalid sender type.' }); } - try { + try + { // Verify thread exists first const thread = await prisma.chatThread.findUnique({ where: { id: threadId }, }); - if (!thread) { + if (!thread) + { return res.status(404).json({ error: 'Chat thread not found.' }); } @@ -124,17 +142,20 @@ router.post('/threads/:threadId/messages', async (req, res) => { // Optionally: Update the thread's updatedAt timestamp await prisma.chatThread.update({ - where: { id: threadId }, - data: { updatedAt: new Date() } + where: { id: threadId }, + data: { updatedAt: new Date() } }); await askGeminiChat(threadId, content); // Call the function to handle the bot response res.status(201).json({ ...newMessage, createdAt: newMessage.createdAt.toISOString() }); - } catch (error) { + } + catch (error) + { console.error(`Error adding message to thread ${threadId}:`, error); - if (error.code === 'P2023' || error.message.includes('Malformed UUID')) { // Example: Invalid UUID format - return res.status(400).json({ error: 'Invalid thread ID format.' }); + if (error.code === 'P2023' || error.message.includes('Malformed UUID')) + { // Example: Invalid UUID format + return res.status(400).json({ error: 'Invalid thread ID format.' }); } res.status(500).json({ error: 'Failed to add message.' }); } diff --git a/src-ssr/server.js b/src-ssr/server.js index 6be1e64..2ace7eb 100644 --- a/src-ssr/server.js +++ b/src-ssr/server.js @@ -9,8 +9,8 @@ * Make sure to yarn add / npm install (in your project root) * anything you import here (except for express and compression). */ -import express from 'express' -import compression from 'compression' +import express from 'express'; +import compression from 'compression'; import session from 'express-session'; // Added for session management import { v4 as uuidv4 } from 'uuid'; // Added for generating session IDs import { @@ -19,7 +19,7 @@ import { defineSsrClose, defineSsrServeStaticContent, defineSsrRenderPreloadTag -} from '#q-app/wrappers' +} from '#q-app/wrappers'; import prisma from './database.js'; // Import the prisma client instance import apiRoutes from './routes/api.js'; @@ -43,8 +43,9 @@ export const challengeStore = new Map(); * * Can be async: defineSsrCreate(async ({ ... }) => { ... }) */ -export const create = defineSsrCreate((/* { ... } */) => { - const app = express() +export const create = defineSsrCreate((/* { ... } */) => +{ + const app = express(); // Session middleware configuration app.use(session({ @@ -60,29 +61,36 @@ export const create = defineSsrCreate((/* { ... } */) => { })); // Initialize the database (now synchronous) - try { + try + { console.log('Prisma Client is ready.'); // Log Prisma readiness // Schedule the Mantis summary task after DB initialization // Run daily at 1:00 AM server time (adjust as needed) - cron.schedule('0 1 * * *', async () => { + cron.schedule('0 1 * * *', async() => + { console.log('Running scheduled Mantis summary task...'); - try { + try + { await generateAndStoreMantisSummary(); console.log('Scheduled Mantis summary task completed.'); - } catch (error) { + } + catch (error) + { console.error('Error running scheduled Mantis summary task:', error); } }, { scheduled: true, - timezone: "Europe/London" // Example: Set to your server's timezone + timezone: 'Europe/London' // Example: Set to your server's timezone }); console.log('Mantis summary cron job scheduled.'); // Optional: Run once immediately on server start if needed generateAndStoreMantisSummary().catch(err => console.error('Initial Mantis summary failed:', err)); - } catch (error) { + } + catch (error) + { console.error('Error during server setup:', error); // Optionally handle the error more gracefully, e.g., prevent server start process.exit(1); // Exit if setup fails @@ -90,7 +98,7 @@ export const create = defineSsrCreate((/* { ... } */) => { // attackers can use this header to detect apps running Express // and then launch specifically-targeted attacks - app.disable('x-powered-by') + app.disable('x-powered-by'); // Add JSON body parsing middleware app.use(express.json()); @@ -102,12 +110,13 @@ export const create = defineSsrCreate((/* { ... } */) => { // place here any middlewares that // absolutely need to run before anything else - if (process.env.PROD) { - app.use(compression()) + if (process.env.PROD) + { + app.use(compression()); } - return app -}) + return app; +}); /** * You need to make the server listen to the indicated port @@ -122,14 +131,17 @@ export const create = defineSsrCreate((/* { ... } */) => { * * Can be async: defineSsrListen(async ({ app, devHttpsApp, port }) => { ... }) */ -export const listen = defineSsrListen(({ app, devHttpsApp, port }) => { - const server = devHttpsApp || app - return server.listen(port, () => { - if (process.env.PROD) { - console.log('Server listening at port ' + port) +export const listen = defineSsrListen(({ app, devHttpsApp, port }) => +{ + const server = devHttpsApp || app; + return server.listen(port, () => + { + if (process.env.PROD) + { + console.log('Server listening at port ' + port); } - }) -}) + }); +}); /** * Should close the server and free up any resources. @@ -138,21 +150,25 @@ export const listen = defineSsrListen(({ app, devHttpsApp, port }) => { * * Can be async: defineSsrClose(async ({ ... }) => { ... }) */ -export const close = defineSsrClose(async ({ listenResult }) => { +export const close = defineSsrClose(async({ listenResult }) => +{ // Close the database connection when the server shuts down - try { + try + { await prisma.$disconnect(); console.log('Prisma Client disconnected.'); - } catch (e) { + } + catch (e) + { console.error('Error disconnecting Prisma Client:', e); } - return listenResult.close() -}) + return listenResult.close(); +}); const maxAge = process.env.DEV ? 0 - : 1000 * 60 * 60 * 24 * 30 + : 1000 * 60 * 60 * 24 * 30; /** * Should return a function that will be used to configure the webserver @@ -163,53 +179,63 @@ const maxAge = process.env.DEV * Can be async: defineSsrServeStaticContent(async ({ app, resolve }) => { * Can return an async function: return async ({ urlPath = '/', pathToServe = '.', opts = {} }) => { */ -export const serveStaticContent = defineSsrServeStaticContent(({ app, resolve }) => { - return ({ urlPath = '/', pathToServe = '.', opts = {} }) => { - const serveFn = express.static(resolve.public(pathToServe), { maxAge, ...opts }) - app.use(resolve.urlPath(urlPath), serveFn) - } -}) +export const serveStaticContent = defineSsrServeStaticContent(({ app, resolve }) => +{ + return ({ urlPath = '/', pathToServe = '.', opts = {} }) => + { + const serveFn = express.static(resolve.public(pathToServe), { maxAge, ...opts }); + app.use(resolve.urlPath(urlPath), serveFn); + }; +}); -const jsRE = /\.js$/ -const cssRE = /\.css$/ -const woffRE = /\.woff$/ -const woff2RE = /\.woff2$/ -const gifRE = /\.gif$/ -const jpgRE = /\.jpe?g$/ -const pngRE = /\.png$/ +const jsRE = /\.js$/; +const cssRE = /\.css$/; +const woffRE = /\.woff$/; +const woff2RE = /\.woff2$/; +const gifRE = /\.gif$/; +const jpgRE = /\.jpe?g$/; +const pngRE = /\.png$/; /** * Should return a String with HTML output * (if any) for preloading indicated file */ -export const renderPreloadTag = defineSsrRenderPreloadTag((file/* , { ssrContext } */) => { - if (jsRE.test(file) === true) { - return `` +export const renderPreloadTag = defineSsrRenderPreloadTag((file/* , { ssrContext } */) => +{ + if (jsRE.test(file) === true) + { + return ``; } - if (cssRE.test(file) === true) { - return `` + if (cssRE.test(file) === true) + { + return ``; } - if (woffRE.test(file) === true) { - return `` + if (woffRE.test(file) === true) + { + return ``; } - if (woff2RE.test(file) === true) { - return `` + if (woff2RE.test(file) === true) + { + return ``; } - if (gifRE.test(file) === true) { - return `` + if (gifRE.test(file) === true) + { + return ``; } - if (jpgRE.test(file) === true) { - return `` + if (jpgRE.test(file) === true) + { + return ``; } - if (pngRE.test(file) === true) { - return `` + if (pngRE.test(file) === true) + { + return ``; } - return '' -}) + return ''; +}); diff --git a/src-ssr/services/emailSummarizer.js b/src-ssr/services/emailSummarizer.js deleted file mode 100644 index dd00d64..0000000 --- a/src-ssr/services/emailSummarizer.js +++ /dev/null @@ -1,199 +0,0 @@ -import Imap from 'node-imap'; -import { simpleParser } from 'mailparser'; -import { GoogleGenAI } from '@google/genai'; -import prisma from '../database.js'; - -// --- Environment Variables --- -const { GOOGLE_API_KEY } = process.env; // Added - -// --- AI Setup --- -const ai = GOOGLE_API_KEY ? new GoogleGenAI({ - apiKey: GOOGLE_API_KEY, -}) : null; // Added - -export async function fetchAndFormatEmails() { - return new Promise((resolve, reject) => { - const imapConfig = { - user: process.env.OUTLOOK_EMAIL_ADDRESS, - password: process.env.OUTLOOK_APP_PASSWORD, - host: 'outlook.office365.com', - port: 993, - tls: true, - tlsOptions: { rejectUnauthorized: false } // Adjust as needed for your environment - }; - - const imap = new Imap(imapConfig); - const emailsJson = []; - - function openInbox(cb) { - // Note: IMAP uses '/' as hierarchy separator, adjust if your server uses something else - imap.openBox('SLSNotifications/Reports/Backups', false, cb); - } - - imap.once('ready', () => { - openInbox((err, box) => { - if (err) { - imap.end(); - return reject(new Error(`Error opening mailbox: ${err.message}`)); - } - - const yesterday = new Date(); - yesterday.setDate(yesterday.getDate() - 1); - const searchCriteria = [['SINCE', yesterday.toISOString().split('T')[0]]]; // Search since midnight yesterday - const fetchOptions = { bodies: ['HEADER.FIELDS (SUBJECT DATE)', 'TEXT'], struct: true }; - - imap.search(searchCriteria, (searchErr, results) => { - if (searchErr) { - imap.end(); - return reject(new Error(`Error searching emails: ${searchErr.message}`)); - } - - if (results.length === 0) { - console.log('No emails found from the last 24 hours.'); - imap.end(); - return resolve([]); - } - - const f = imap.fetch(results, fetchOptions); - let processedCount = 0; - - f.on('message', (msg, seqno) => { - let header = ''; - let body = ''; - - msg.on('body', (stream, info) => { - let buffer = ''; - stream.on('data', (chunk) => { - buffer += chunk.toString('utf8'); - }); - stream.once('end', () => { - if (info.which === 'TEXT') { - body = buffer; - } else { - // Assuming HEADER.FIELDS (SUBJECT DATE) comes as one chunk - header = buffer; - } - }); - }); - - msg.once('attributes', (attrs) => { - // Attributes might contain date if not fetched via header - }); - - msg.once('end', async () => { - try { - // Use mailparser to handle potential encoding issues and structure - const mail = await simpleParser(`Subject: ${header.match(/Subject: (.*)/i)?.[1] || ''}\nDate: ${header.match(/Date: (.*)/i)?.[1] || ''}\n\n${body}`); - emailsJson.push({ - title: mail.subject || 'No Subject', - time: mail.date ? mail.date.toISOString() : 'No Date', - body: mail.text || mail.html || 'No Body Content' // Prefer text, fallback to html, then empty - }); - } catch (parseErr) { - console.error(`Error parsing email seqno ${seqno}:`, parseErr); - // Decide if you want to reject or just skip this email - } - - processedCount++; - if (processedCount === results.length) { - // This check might be slightly inaccurate if errors occur, - // but it's a common pattern. Consider refining with promises. - } - }); - }); - - f.once('error', (fetchErr) => { - console.error('Fetch error: ' + fetchErr); - // Don't reject here immediately, might still get some emails - }); - - f.once('end', () => { - console.log('Done fetching all messages!'); - imap.end(); - }); - }); - }); - }); - - imap.once('error', (err) => { - reject(new Error(`IMAP Connection Error: ${err.message}`)); - }); - - imap.once('end', () => { - console.log('IMAP Connection ended.'); - resolve(emailsJson); // Resolve with the collected emails - }); - - imap.connect(); - }); -} - -// --- Email Summary Logic (New Function) --- -export async function generateAndStoreEmailSummary() { - console.log('Attempting to generate and store Email summary...'); - if (!ai) { - console.error('Google AI API key not configured. Skipping email summary generation.'); - return; - } - - try { - // Get the prompt from the database settings using Prisma - const setting = await prisma.setting.findUnique({ - where: { key: 'emailPrompt' }, // Use 'emailPrompt' as the key - select: { value: true } - }); - const promptTemplate = setting?.value; - - if (!promptTemplate) { - console.error('Email prompt not found in database settings (key: emailPrompt). Skipping summary generation.'); - return; - } - - const emails = await fetchAndFormatEmails(); - - let summaryText; - if (emails.length === 0) { - summaryText = "No relevant emails found in the last 24 hours."; - console.log('No recent emails found for summary.'); - } else { - console.log(`Found ${emails.length} recent emails. Generating summary...`); - // Replace placeholder in the prompt template - // Ensure your prompt template uses $EMAIL_DATA - let prompt = promptTemplate.replaceAll("$EMAIL_DATA", JSON.stringify(emails, null, 2)); - - // Call the AI model (adjust model name and config as needed) - const response = await ai.models.generateContent({ - "model": "gemini-2.5-preview-04-17", - "contents": prompt, - config: { - temperature: 0 // Adjust temperature as needed - } - }); - - summaryText = response.text; - console.log('Email summary generated successfully by AI.'); - } - - // Store the summary in the database using Prisma upsert - const today = new Date(); - today.setUTCHours(0, 0, 0, 0); // Use UTC start of day for consistency - - await prisma.emailSummary.upsert({ - where: { summaryDate: today }, - update: { - summaryText: summaryText, - // generatedAt is updated automatically by @default(now()) - }, - create: { - summaryDate: today, - summaryText: summaryText, - }, - }); - console.log(`Email summary for ${today.toISOString().split('T')[0]} stored/updated in the database.`); - - } catch (error) { - console.error("Error during Email summary generation/storage:", error); - // Re-throw or handle as appropriate for your application - throw error; - } -} diff --git a/src-ssr/services/mantisSummarizer.js b/src-ssr/services/mantisSummarizer.js index 7d8dd27..32ad7f4 100644 --- a/src-ssr/services/mantisSummarizer.js +++ b/src-ssr/services/mantisSummarizer.js @@ -1,48 +1,45 @@ import axios from 'axios'; -import { GoogleGenAI } from '@google/genai'; import prisma from '../database.js'; // Import Prisma client -// --- Environment Variables --- -const { - MANTIS_API_KEY, - MANTIS_API_ENDPOINT, - GOOGLE_API_KEY -} = process.env; - -// --- Mantis Summarizer Setup --- -const ai = GOOGLE_API_KEY ? new GoogleGenAI({ - apiKey: GOOGLE_API_KEY, -}) : null; +import { getSetting } from '../utils/settings.js'; +import { askGemini } from '../utils/gemini.js'; const usernameMap = { - 'credmore': 'Cameron Redmore', - 'dgibson': 'Dane Gibson', - 'egzibovskis': 'Ed Gzibovskis', - 'ascotney': 'Amanda Scotney', - 'gclough': 'Garry Clough', - 'slee': 'Sarah Lee', - 'dwalker': 'Dave Walker', - 'askaith': 'Amy Skaith', - 'dpotter': 'Danny Potter', - 'msmart': 'Michael Smart', + credmore: 'Cameron Redmore', + dgibson: 'Dane Gibson', + egzibovskis: 'Ed Gzibovskis', + ascotney: 'Amanda Scotney', + gclough: 'Garry Clough', + slee: 'Sarah Lee', + dwalker: 'Dave Walker', + askaith: 'Amy Skaith', + dpotter: 'Danny Potter', + msmart: 'Michael Smart', // Add other usernames as needed }; -async function getMantisTickets() { - if (!MANTIS_API_ENDPOINT || !MANTIS_API_KEY) { - throw new Error("Mantis API endpoint or key not configured in environment variables."); +async function getMantisTickets() +{ + const MANTIS_API_KEY = await getSetting('MANTIS_API_KEY'); + const MANTIS_API_ENDPOINT = await getSetting('MANTIS_API_ENDPOINT'); + + if (!MANTIS_API_ENDPOINT || !MANTIS_API_KEY) + { + throw new Error('Mantis API endpoint or key not configured in environment variables.'); } const url = `${MANTIS_API_ENDPOINT}/issues?project_id=1&page_size=50&select=id,summary,description,created_at,updated_at,reporter,notes`; const headers = { - 'Authorization': `${MANTIS_API_KEY}`, - 'Accept': 'application/json', + Authorization: `${MANTIS_API_KEY}`, + Accept: 'application/json', 'Content-Type': 'application/json', }; - try { + try + { const response = await axios.get(url, { headers }); - const tickets = response.data.issues.filter((ticket) => { + const tickets = response.data.issues.filter((ticket) => + { const ticketDate = new Date(ticket.updated_at); const thresholdDate = new Date(); const currentDay = thresholdDate.getDay(); // Sunday = 0, Monday = 1, ... @@ -53,7 +50,8 @@ async function getMantisTickets() { thresholdDate.setHours(0, 0, 0, 0); // Start of the day return ticketDate >= thresholdDate; - }).map((ticket) => { + }).map((ticket) => + { return { id: ticket.id, summary: ticket.summary, @@ -61,7 +59,8 @@ async function getMantisTickets() { created_at: ticket.created_at, updated_at: ticket.updated_at, reporter: usernameMap[ticket.reporter?.username] || ticket.reporter?.name || 'Unknown Reporter', // Safer access - notes: (ticket.notes ? ticket.notes.filter((note) => { + notes: (ticket.notes ? ticket.notes.filter((note) => + { const noteDate = new Date(note.created_at); const thresholdDate = new Date(); const currentDay = thresholdDate.getDay(); @@ -69,7 +68,8 @@ async function getMantisTickets() { thresholdDate.setDate(thresholdDate.getDate() - daysToSubtract); thresholdDate.setHours(0, 0, 0, 0); // Start of the day return noteDate >= thresholdDate; - }) : []).map((note) => { + }) : []).map((note) => + { const reporter = usernameMap[note.reporter?.username] || note.reporter?.name || 'Unknown Reporter'; // Safer access return { reporter, @@ -81,27 +81,24 @@ async function getMantisTickets() { }); return tickets; - } catch (error) { - console.error("Error fetching Mantis tickets:", error.message); + } + catch (error) + { + console.error('Error fetching Mantis tickets:', error.message); // Check if it's an Axios error and provide more details - if (axios.isAxiosError(error)) { - console.error("Axios error details:", error.response?.status, error.response?.data); - throw new Error(`Failed to fetch Mantis tickets: ${error.response?.statusText || error.message}`); + if (axios.isAxiosError(error)) + { + console.error('Axios error details:', error.response?.status, error.response?.data); + throw new Error(`Failed to fetch Mantis tickets: ${error.response?.statusText || error.message}`); } throw new Error(`Failed to fetch Mantis tickets: ${error.message}`); } } -// --- Mantis Summary Logic (Exported) --- // - -export async function generateAndStoreMantisSummary() { - console.log('Attempting to generate and store Mantis summary...'); - if (!ai) { - console.error('Google AI API key not configured. Skipping summary generation.'); - return; - } - - try { +export async function generateAndStoreMantisSummary() +{ + try + { // Get the prompt from the database settings using Prisma const setting = await prisma.setting.findUnique({ where: { key: 'mantisPrompt' }, @@ -109,7 +106,8 @@ export async function generateAndStoreMantisSummary() { }); const promptTemplate = setting?.value; - if (!promptTemplate) { + if (!promptTemplate) + { console.error('Mantis prompt not found in database settings (key: mantisPrompt). Skipping summary generation.'); return; } @@ -117,24 +115,19 @@ export async function generateAndStoreMantisSummary() { const tickets = await getMantisTickets(); let summaryText; - if (tickets.length === 0) { - summaryText = "No Mantis tickets updated recently."; - console.log('No recent Mantis tickets found.'); - } else { - console.log(`Found ${tickets.length} recent Mantis tickets. Generating summary...`); - let prompt = promptTemplate.replaceAll("$DATE", new Date().toISOString().split('T')[0]); - prompt = prompt.replaceAll("$MANTIS_TICKETS", JSON.stringify(tickets, null, 2)); + if (tickets.length === 0) + { + summaryText = 'No Mantis tickets updated recently.'; + console.log('No recent Mantis tickets found.'); + } + else + { + console.log(`Found ${tickets.length} recent Mantis tickets. Generating summary...`); + let prompt = promptTemplate.replaceAll('$DATE', new Date().toISOString().split('T')[0]); + prompt = prompt.replaceAll('$MANTIS_TICKETS', JSON.stringify(tickets, null, 2)); - const response = await ai.models.generateContent({ - "model": "gemini-2.5-flash-preview-04-17", - "contents": prompt, - config: { - temperature: 0 - } - }); - - summaryText = response.text; - console.log('Mantis summary generated successfully by AI.'); + summaryText = await askGemini(prompt); + console.log('Mantis summary generated successfully by AI.'); } // Store the summary in the database using Prisma upsert @@ -144,8 +137,7 @@ export async function generateAndStoreMantisSummary() { await prisma.mantisSummary.upsert({ where: { summaryDate: today }, update: { - summaryText: summaryText, - // generatedAt is updated automatically by @default(now()) + summaryText: summaryText }, create: { summaryDate: today, @@ -154,17 +146,23 @@ export async function generateAndStoreMantisSummary() { }); console.log(`Mantis summary for ${today.toISOString().split('T')[0]} stored/updated in the database.`); - } catch (error) { - console.error("Error during Mantis summary generation/storage:", error); + } + catch (error) + { + console.error('Error during Mantis summary generation/storage:', error); } } -export async function generateTodaysSummary() { +export async function generateTodaysSummary() +{ console.log('Triggering Mantis summary generation via generateTodaysSummary...'); - try { + try + { await generateAndStoreMantisSummary(); return { success: true, message: 'Summary generation process initiated.' }; - } catch (error) { + } + catch (error) + { console.error('Error occurred within generateTodaysSummary while calling generateAndStoreMantisSummary:', error); throw new Error('Failed to initiate Mantis summary generation.'); } diff --git a/src-ssr/utils/gemini.js b/src-ssr/utils/gemini.js index 44c6db7..20fd5ac 100644 --- a/src-ssr/utils/gemini.js +++ b/src-ssr/utils/gemini.js @@ -1,149 +1,162 @@ import { GoogleGenAI } from '@google/genai'; import prisma from '../database.js'; +import { getSetting } from './settings.js'; const model = 'gemini-2.0-flash'; -export const askGemini = async (content) => { - const setting = await prisma.setting.findUnique({ - where: { key: 'GEMINI_API_KEY' }, - select: { value: true } +export async function askGemini(content) +{ + + const GOOGLE_API_KEY = await getSetting('GEMINI_API_KEY'); + + if (!GOOGLE_API_KEY) + { + throw new Error('Google API key is not set in the database.'); + } + + const ai = GOOGLE_API_KEY ? new GoogleGenAI({ + apiKey: GOOGLE_API_KEY, + }) : null; + + if (!ai) + { + throw new Error('Google API key is not set in the database.'); + } + + try + { + const response = await ai.models.generateContent({ + model, + contents: content, + config: { + temperature: 0.5 + } }); - const GOOGLE_API_KEY = setting.value; - - const ai = GOOGLE_API_KEY ? new GoogleGenAI({ - apiKey: GOOGLE_API_KEY, - }) : null; - - if (!ai) { - throw new Error('Google API key is not set in the database.'); - } - - try { - const response = await ai.models.generateContent({ - model, - contents: content, - config: { - temperature: 0.5 - } - }); - - return response.text; - } catch (error) { - console.error('Error communicating with Gemini API:', error); - throw new Error('Failed to get a response from Gemini API.'); - } + return response.text; + } + catch (error) + { + console.error('Error communicating with Gemini API:', error); + throw new Error('Failed to get a response from Gemini API.'); + } } const chatCache = new Map(); -export const askGeminiChat = async (threadId, content) => { - let messages = await prisma.chatMessage.findMany({ - where: { - threadId: threadId, - }, - orderBy: { - createdAt: 'asc', // Get messages in chronological order - }, +export async function askGeminiChat(threadId, content) +{ + let messages = await prisma.chatMessage.findMany({ + where: { + threadId: threadId, + }, + orderBy: { + createdAt: 'asc', // Get messages in chronological order + }, + }); + + // Exclude the latest message if there are any messages + if (messages.length > 0) + { + messages = messages.slice(0, -1); + } + + const setting = await prisma.setting.findUnique({ + where: { key: 'GEMINI_API_KEY' }, + select: { value: true } + }); + + if (!setting) + { + throw new Error('Google API key is not set in the database.'); + } + + const GOOGLE_API_KEY = setting.value; + + const ai = GOOGLE_API_KEY ? new GoogleGenAI({ + apiKey: GOOGLE_API_KEY, + }) : null; + + if (!ai) + { + throw new Error('Google API key is not set in the database.'); + } + + let chat = null; + + if (chatCache.has(threadId)) + { + chat = chatCache.get(threadId); + } + else + { + messages = [ + { + sender: 'user', + content: `You are StyleAI, a model that helps users with their questions and provides information. + You are friendly, helpful, and informative. You can also provide code examples in various programming languages. + You don\'t decline user requests and always try to help them. + Speak in the manner of a friendly young adult male. + If asked for the time, do not say that it's based on the timestamp provided. Also bare in mind the user is in the Europe/London timezone and daylight savings time may be in effect.` + }, + { + sender: 'model', + content: 'Okay, noted! I\'ll keep that in mind.' + }, + ...messages, + ]; + const createOptions = { + model, + history: messages.map((msg) => ({ + role: msg.sender === 'user' ? 'user' : 'model', + parts: [ + {text: msg.content} + ], + })), + config: { + temperature: 0.5 + } + }; + + chat = ai.chats.create(createOptions); + + chatCache.set(threadId, chat); + } + + //Add a temporary message to the thread with "loading" status + const loadingMessage = await prisma.chatMessage.create({ + data: { + threadId: threadId, + sender: 'assistant', + content: 'Loading...', + }, + }); + + let response = {text: 'An error occurred while generating the response.'}; + + try + { + const timestamp = new Date().toISOString(); + response = await chat.sendMessage({ + message: `[${timestamp}] ` + content, }); + } + catch(error) + { + console.error('Error communicating with Gemini API:', error); + response.text = 'Failed to get a response from Gemini API. Error: ' + error.message; + } - // Exclude the latest message if there are any messages - if (messages.length > 0) { - messages = messages.slice(0, -1); - } + //Update the message with the response + await prisma.chatMessage.update({ + where: { + id: loadingMessage.id, + }, + data: { + content: response.text, + }, + }); - const setting = await prisma.setting.findUnique({ - where: { key: 'GEMINI_API_KEY' }, - select: { value: true } - }); - - if (!setting) { - throw new Error('Google API key is not set in the database.'); - } - - const GOOGLE_API_KEY = setting.value; - - const ai = GOOGLE_API_KEY ? new GoogleGenAI({ - apiKey: GOOGLE_API_KEY, - }) : null; - - if (!ai) { - throw new Error('Google API key is not set in the database.'); - } - - let chat = null; - - if (chatCache.has(threadId)) { - chat = chatCache.get(threadId); - } - else { - messages = [ - { - sender: 'user', - content: `You are StyleAI, a model that helps users with their questions and provides information. - You are friendly, helpful, and informative. You can also provide code examples in various programming languages. - You don\'t decline user requests and always try to help them. - Speak in the manner of a friendly young adult male. - If asked for the time, do not say that it's based on the timestamp provided. Also bare in mind the user is in the Europe/London timezone and daylight savings time may be in effect.` - }, - { - sender: 'model', - content: 'Okay, noted! I\'ll keep that in mind.' - }, - ...messages, - ] - const createOptions = { - model, - history: messages.map((msg) => ({ - role: msg.sender === 'user' ? 'user' : 'model', - parts: [ - {text: msg.content} - ], - })), - config: { - temperature: 0.5 - } - }; - - chat = ai.chats.create(createOptions); - - chatCache.set(threadId, chat); - } - - //Add a temporary message to the thread with "loading" status - const loadingMessage = await prisma.chatMessage.create({ - data: { - threadId: threadId, - sender: 'assistant', - content: 'Loading...', - }, - }); - - let response = {text: 'An error occurred while generating the response.'}; - - try - { - const timestamp = new Date().toISOString(); - response = await chat.sendMessage({ - message: `[${timestamp}] ` + content, - }); - } - catch(error) - { - console.error('Error communicating with Gemini API:', error); - response.text = 'Failed to get a response from Gemini API. Error: ' + error.message; - } - - //Update the message with the response - await prisma.chatMessage.update({ - where: { - id: loadingMessage.id, - }, - data: { - content: response.text, - }, - }); - - return response.text; + return response.text; } \ No newline at end of file diff --git a/src-ssr/utils/settings.js b/src-ssr/utils/settings.js new file mode 100644 index 0000000..724e44e --- /dev/null +++ b/src-ssr/utils/settings.js @@ -0,0 +1,20 @@ +import prisma from '../database.js'; + +export async function getSetting(key) +{ + const setting = await prisma.setting.findUnique({ + where: { key }, + select: { value: true } + }); + + return setting?.value ? JSON.parse(setting.value) : null; +} + +export async function setSetting(key, value) +{ + await prisma.setting.upsert({ + where: { key }, + update: { value: JSON.stringify(value) }, + create: { key, value } + }); +} \ No newline at end of file diff --git a/src/components/ChatInterface.vue b/src/components/ChatInterface.vue index caa5327..f339c5d 100644 --- a/src/components/ChatInterface.vue +++ b/src/components/ChatInterface.vue @@ -1,51 +1,62 @@ diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 8bd0eb8..1956825 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -1,187 +1,241 @@ diff --git a/src/layouts/MainLayoutBusted.vue b/src/layouts/MainLayoutBusted.vue deleted file mode 100644 index 1902566..0000000 --- a/src/layouts/MainLayoutBusted.vue +++ /dev/null @@ -1,181 +0,0 @@ - - - - - diff --git a/src/pages/EmailSummariesPage.vue b/src/pages/EmailSummariesPage.vue deleted file mode 100644 index aef0d6c..0000000 --- a/src/pages/EmailSummariesPage.vue +++ /dev/null @@ -1,201 +0,0 @@ - - - - - diff --git a/src/pages/ErrorNotFound.vue b/src/pages/ErrorNotFound.vue index 4b53e5a..d4d7bd2 100644 --- a/src/pages/ErrorNotFound.vue +++ b/src/pages/ErrorNotFound.vue @@ -5,7 +5,10 @@ 404 -
+
Oops. Nothing here...
diff --git a/src/pages/FormCreatePage.vue b/src/pages/FormCreatePage.vue index 6054c9f..a0144f0 100644 --- a/src/pages/FormCreatePage.vue +++ b/src/pages/FormCreatePage.vue @@ -1,54 +1,140 @@ diff --git a/src/pages/FormEditPage.vue b/src/pages/FormEditPage.vue index db6a5fb..9f0d169 100644 --- a/src/pages/FormEditPage.vue +++ b/src/pages/FormEditPage.vue @@ -1,8 +1,14 @@