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 @@
-
+ The all-in-one tool designed for StyleTech Developers. +
+The all-in-one tool designed for StyleTech Developers.
-