From eebf25198d5bc675e72e323991d6206203c5c024 Mon Sep 17 00:00:00 2001 From: Wroclaw Date: Wed, 8 Nov 2023 05:35:48 +0100 Subject: [PATCH] Replace mysql2 with prisma also I updated packages, and properly typed api input a lot of time was spent, I don't remeber what really I did x3 but everything was related to replacing mysql2 with prisma --- README.md | 6 +- components/orderView.vue | 8 +- package-lock.json | 576 +++++++----------- package.json | 4 +- pages/client/[id].vue | 34 +- pages/clients.vue | 3 +- schema.prisma | 116 ++++ schemaModel.mysqlWorkbench.mwb | Bin 16539 -> 0 bytes schemaModel.sql | 167 ----- server/api/clients.get.ts | 13 +- server/api/clients.post.ts | 43 +- server/api/clients/[id].delete.ts | 21 +- server/api/clients/[id].get.ts | 17 +- server/api/clients/[id].patch.ts | 23 +- server/api/clients/[id]/orders.get.ts | 17 +- server/api/clients/count.get.ts | 8 +- server/api/firstRun.get.ts | 15 +- server/api/firstRun.post.ts | 24 +- server/api/login.post.ts | 33 +- server/api/logout.ts | 9 +- server/api/orders.get.ts | 84 ++- server/api/orders.post.ts | 139 +++-- server/api/orders/[id].delete.ts | 19 +- server/api/orders/[id].get.ts | 123 +--- server/api/orders/[id].patch.ts | 22 +- .../api/orders/[id]/imported_products.get.ts | 23 +- .../api/orders/[id]/imported_products.post.ts | 30 +- server/api/orders/[id]/work.get.ts | 27 +- server/api/orders/[id]/work.post.ts | 30 +- .../api/orders/[id]/work/[idWork].delete.ts | 20 +- server/api/orders/[id]/work/[idWork].get.ts | 27 +- server/api/users/me.get.ts | 17 +- server/middleware/auth.ts | 16 +- server/utils/baaPageParsing.ts | 108 ++++ server/utils/baaPagination.ts | 261 -------- server/utils/database.ts | 112 +++- server/utils/getRequestingUser.ts | 35 +- server/utils/prismaToWeb.ts | 44 ++ utils/types/database.ts | 99 --- 39 files changed, 1081 insertions(+), 1292 deletions(-) create mode 100644 schema.prisma delete mode 100644 schemaModel.mysqlWorkbench.mwb delete mode 100644 schemaModel.sql create mode 100644 server/utils/baaPageParsing.ts delete mode 100644 server/utils/baaPagination.ts create mode 100644 server/utils/prismaToWeb.ts delete mode 100644 utils/types/database.ts diff --git a/README.md b/README.md index c0fbd05..379b005 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,6 @@ Project uses Oracle MySQL as a database store. It uses environment variables for | Environment variable | Description | |----------------------|-------------------| -| `DB_HOST` | Database host | -| `DB_PORT` | Database port | -| `DB_USER` | Database user | -| `DB_PASSWORD` | Database password | -| `DB_SCHEMA` | Database schema | +| `DB_URL` | Database url, see [this](https://www.prisma.io/docs/concepts/database-connectors/mysql#connection-url) | After setting variables, you can run the project using `npx nuxi dev` or `npx nuxi preview`. diff --git a/components/orderView.vue b/components/orderView.vue index 5e25109..971ed2f 100644 --- a/components/orderView.vue +++ b/components/orderView.vue @@ -1,8 +1,10 @@ @@ -24,7 +26,7 @@ const props = defineProps<{ - {{ i.is_fulfilled }} + {{ i.fulfilled }} {{ i.offer.name }} diff --git a/package-lock.json b/package-lock.json index bb2e2f9..70c125e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "hasInstallScript": true, "dependencies": { + "@prisma/client": "5.5.2", "mysql2": "^3.2.3" }, "devDependencies": { @@ -14,7 +15,8 @@ "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", "eslint": "^8.39.0", - "nuxt": "3.8.0", + "nuxt": "3.8.1", + "prisma": "5.5.2", "sass": "^1.62.0", "vite-plugin-vuetify": "^1.0.2", "vuetify": "^3.1.15" @@ -1822,19 +1824,19 @@ } }, "node_modules/@nuxt/kit": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.8.0.tgz", - "integrity": "sha512-oIthQxeMIVs4ESVP5FqLYn8tj0S1sLd+eYreh+dNYgnJ2pTi7+THR12ONBNHjk668jqEe7ErUJ8UlGwqBzgezg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.8.1.tgz", + "integrity": "sha512-DrhG1Z85iH68QOTkgfb0HVfM2g7+CfcMWrFWMDwck9ofyM2RXQUZyfmvMedwBnui1AjjpgpLO9078yZM+RqNUg==", "dev": true, "dependencies": { - "@nuxt/schema": "3.8.0", + "@nuxt/schema": "3.8.1", "c12": "^1.5.1", "consola": "^3.2.3", - "defu": "^6.1.2", + "defu": "^6.1.3", "globby": "^13.2.2", "hash-sum": "^2.0.0", "ignore": "^5.2.4", - "jiti": "^1.20.0", + "jiti": "^1.21.0", "knitwork": "^1.0.0", "mlly": "^1.4.2", "pathe": "^1.1.1", @@ -1882,18 +1884,17 @@ } }, "node_modules/@nuxt/schema": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.8.0.tgz", - "integrity": "sha512-VEDVeCjdVowhoY5vIBSz94+SSwmM204jN6TNe/ShBJ2d/vZiy9EtLbhOwqaPNFHwnN1fl/XFHThwJiexdB9D1w==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.8.1.tgz", + "integrity": "sha512-fSaWRcI/2mUskfTZTGSnH6Ny0x05CRzylbVn6WFV0d6UEKIVy42Qd6n+h7yoFfp4cq4nji6u16PT4SqS1DEhsw==", "dev": true, "dependencies": { "@nuxt/ui-templates": "^1.3.1", "consola": "^3.2.3", - "defu": "^6.1.2", + "defu": "^6.1.3", "hookable": "^5.5.3", "pathe": "^1.1.1", "pkg-types": "^1.0.3", - "postcss-import-resolver": "^2.0.0", "std-env": "^3.4.3", "ufo": "^1.3.1", "unimport": "^3.4.0", @@ -1938,20 +1939,20 @@ "dev": true }, "node_modules/@nuxt/vite-builder": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-3.8.0.tgz", - "integrity": "sha512-F9BfH+c/Idp6sBGVHR4QJSuoO42evtE4D0OelD45NgkqVvmBmOawlj0Oz5fDKoV64LDPI2+yE+xnBdQtsNv/VA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-3.8.1.tgz", + "integrity": "sha512-Ot/twGONxj22T9U4bxp771ibKVFlZxIiYDHY/e6mZsE4Blc0efKo6MzPPPo0W4/tXQbtKKEq41uINN3dMI3mag==", "dev": true, "dependencies": { - "@nuxt/kit": "3.8.0", - "@rollup/plugin-replace": "^5.0.4", + "@nuxt/kit": "3.8.1", + "@rollup/plugin-replace": "^5.0.5", "@vitejs/plugin-vue": "^4.4.0", "@vitejs/plugin-vue-jsx": "^3.0.2", "autoprefixer": "^10.4.16", "clear": "^0.1.0", "consola": "^3.2.3", "cssnano": "^6.0.1", - "defu": "^6.1.2", + "defu": "^6.1.3", "esbuild": "^0.19.5", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", @@ -1967,8 +1968,6 @@ "perfect-debounce": "^1.0.0", "pkg-types": "^1.0.3", "postcss": "^8.4.31", - "postcss-import": "^15.1.0", - "postcss-url": "^10.1.3", "rollup-plugin-visualizer": "^5.9.2", "std-env": "^3.4.3", "strip-literal": "^1.3.0", @@ -2363,6 +2362,38 @@ "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==", "dev": true }, + "node_modules/@prisma/client": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.5.2.tgz", + "integrity": "sha512-54XkqR8M+fxbzYqe+bIXimYnkkcGqgOh0dn0yWtIk6CQT4IUCAvNFNcQZwk2KqaLU+/1PHTSWrcHtx4XjluR5w==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines-version": "5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a" + }, + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/engines": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.5.2.tgz", + "integrity": "sha512-Be5hoNF8k+lkB3uEMiCHbhbfF6aj1GnrTBnn5iYFT7GEr3TsOEp1soviEcBR0tYCgHbxjcIxJMhdbvxALJhAqg==", + "devOptional": true, + "hasInstallScript": true + }, + "node_modules/@prisma/engines-version": { + "version": "5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a.tgz", + "integrity": "sha512-O+qHFnZvAyOFk1tUco2/VdiqS0ym42a3+6CYLScllmnpbyiTplgyLt2rK/B9BTjYkSHjrgMhkG47S0oqzdIckA==" + }, "node_modules/@rollup/plugin-alias": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.0.1.tgz", @@ -2694,24 +2725,24 @@ } }, "node_modules/@types/estree": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.4.tgz", - "integrity": "sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/http-proxy": { - "version": "1.17.13", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.13.tgz", - "integrity": "sha512-GkhdWcMNiR5QSQRYnJ+/oXzu0+7JJEPC8vkWXK351BkhjraZF+1W13CUYARUvX9+NqIU2n6YHA4iwywsc/M6Sw==", + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/json5": { @@ -2721,18 +2752,18 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", - "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/normalize-package-data": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz", - "integrity": "sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, "node_modules/@types/resolve": { @@ -2742,22 +2773,22 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", - "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz", + "integrity": "sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/type-utils": "6.9.1", - "@typescript-eslint/utils": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/type-utils": "6.10.0", + "@typescript-eslint/utils": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2783,15 +2814,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", - "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", + "integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4" }, "engines": { @@ -2811,13 +2842,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", - "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz", + "integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1" + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2828,13 +2859,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", - "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz", + "integrity": "sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/utils": "6.10.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2855,9 +2886,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", - "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz", + "integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2868,13 +2899,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", - "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz", + "integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2895,17 +2926,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", - "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.10.0.tgz", + "integrity": "sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", "semver": "^7.5.4" }, "engines": { @@ -2920,12 +2951,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", - "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz", + "integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/types": "6.10.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -3167,13 +3198,13 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.7.tgz", - "integrity": "sha512-pACdY6YnTNVLXsB86YD8OF9ihwpolzhhtdLVHhBL6do/ykr6kKXNYABRtNMGrsQXpEXXyAdwvWWkuTbs4MFtPQ==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.8.tgz", + "integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==", "dev": true, "dependencies": { "@babel/parser": "^7.23.0", - "@vue/shared": "3.3.7", + "@vue/shared": "3.3.8", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" } @@ -3185,27 +3216,27 @@ "dev": true }, "node_modules/@vue/compiler-dom": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.7.tgz", - "integrity": "sha512-0LwkyJjnUPssXv/d1vNJ0PKfBlDoQs7n81CbO6Q0zdL7H1EzqYRrTVXDqdBVqro0aJjo/FOa1qBAPVI4PGSHBw==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz", + "integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==", "dev": true, "dependencies": { - "@vue/compiler-core": "3.3.7", - "@vue/shared": "3.3.7" + "@vue/compiler-core": "3.3.8", + "@vue/shared": "3.3.8" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.7.tgz", - "integrity": "sha512-7pfldWy/J75U/ZyYIXRVqvLRw3vmfxDo2YLMwVtWVNew8Sm8d6wodM+OYFq4ll/UxfqVr0XKiVwti32PCrruAw==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz", + "integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==", "dev": true, "dependencies": { "@babel/parser": "^7.23.0", - "@vue/compiler-core": "3.3.7", - "@vue/compiler-dom": "3.3.7", - "@vue/compiler-ssr": "3.3.7", - "@vue/reactivity-transform": "3.3.7", - "@vue/shared": "3.3.7", + "@vue/compiler-core": "3.3.8", + "@vue/compiler-dom": "3.3.8", + "@vue/compiler-ssr": "3.3.8", + "@vue/reactivity-transform": "3.3.8", + "@vue/shared": "3.3.8", "estree-walker": "^2.0.2", "magic-string": "^0.30.5", "postcss": "^8.4.31", @@ -3219,13 +3250,13 @@ "dev": true }, "node_modules/@vue/compiler-ssr": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.7.tgz", - "integrity": "sha512-TxOfNVVeH3zgBc82kcUv+emNHo+vKnlRrkv8YvQU5+Y5LJGJwSNzcmLUoxD/dNzv0bhQ/F0s+InlgV0NrApJZg==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz", + "integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.3.7", - "@vue/shared": "3.3.7" + "@vue/compiler-dom": "3.3.8", + "@vue/shared": "3.3.8" } }, "node_modules/@vue/devtools-api": { @@ -3235,23 +3266,23 @@ "dev": true }, "node_modules/@vue/reactivity": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.7.tgz", - "integrity": "sha512-cZNVjWiw00708WqT0zRpyAgduG79dScKEPYJXq2xj/aMtk3SKvL3FBt2QKUlh6EHBJ1m8RhBY+ikBUzwc7/khg==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.8.tgz", + "integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==", "dev": true, "dependencies": { - "@vue/shared": "3.3.7" + "@vue/shared": "3.3.8" } }, "node_modules/@vue/reactivity-transform": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.7.tgz", - "integrity": "sha512-APhRmLVbgE1VPGtoLQoWBJEaQk4V8JUsqrQihImVqKT+8U6Qi3t5ATcg4Y9wGAPb3kIhetpufyZ1RhwbZCIdDA==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz", + "integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==", "dev": true, "dependencies": { "@babel/parser": "^7.23.0", - "@vue/compiler-core": "3.3.7", - "@vue/shared": "3.3.7", + "@vue/compiler-core": "3.3.8", + "@vue/shared": "3.3.8", "estree-walker": "^2.0.2", "magic-string": "^0.30.5" } @@ -3263,43 +3294,43 @@ "dev": true }, "node_modules/@vue/runtime-core": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.7.tgz", - "integrity": "sha512-LHq9du3ubLZFdK/BP0Ysy3zhHqRfBn80Uc+T5Hz3maFJBGhci1MafccnL3rpd5/3wVfRHAe6c+PnlO2PAavPTQ==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.8.tgz", + "integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==", "dev": true, "dependencies": { - "@vue/reactivity": "3.3.7", - "@vue/shared": "3.3.7" + "@vue/reactivity": "3.3.8", + "@vue/shared": "3.3.8" } }, "node_modules/@vue/runtime-dom": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.7.tgz", - "integrity": "sha512-PFQU1oeJxikdDmrfoNQay5nD4tcPNYixUBruZzVX/l0eyZvFKElZUjW4KctCcs52nnpMGO6UDK+jF5oV4GT5Lw==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz", + "integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==", "dev": true, "dependencies": { - "@vue/runtime-core": "3.3.7", - "@vue/shared": "3.3.7", + "@vue/runtime-core": "3.3.8", + "@vue/shared": "3.3.8", "csstype": "^3.1.2" } }, "node_modules/@vue/server-renderer": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.7.tgz", - "integrity": "sha512-UlpKDInd1hIZiNuVVVvLgxpfnSouxKQOSE2bOfQpBuGwxRV/JqqTCyyjXUWiwtVMyeRaZhOYYqntxElk8FhBhw==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.8.tgz", + "integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==", "dev": true, "dependencies": { - "@vue/compiler-ssr": "3.3.7", - "@vue/shared": "3.3.7" + "@vue/compiler-ssr": "3.3.8", + "@vue/shared": "3.3.8" }, "peerDependencies": { - "vue": "3.3.7" + "vue": "3.3.8" } }, "node_modules/@vue/shared": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.7.tgz", - "integrity": "sha512-N/tbkINRUDExgcPTBvxNkvHGu504k8lzlNQRITVnm6YjOjwa4r0nnbd4Jb01sNpur5hAllyRJzSK5PvB9PPwRg==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", + "integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", "dev": true }, "node_modules/@vuetify/loader-shared": { @@ -4688,12 +4719,6 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", "dev": true }, - "node_modules/cuint": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", - "dev": true - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5006,9 +5031,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.576", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.576.tgz", - "integrity": "sha512-yXsZyXJfAqzWk1WKryr0Wl0MN2D47xodPvEEwlVePBnhU5E7raevLQR+E6b9JAD3GfL/7MbAL9ZtWQQPcLx7wA==", + "version": "1.4.578", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.578.tgz", + "integrity": "sha512-V0ZhSu1BQZKfG0yNEL6Dadzik8E1vAzfpVOapdSiT9F6yapEJ3Bk+4tZ4SMPdWiUchCgnM/ByYtBzp5ntzDMIA==", "dev": true }, "node_modules/emoji-regex": { @@ -5076,18 +5101,6 @@ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "dev": true }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -5869,9 +5882,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -7739,55 +7752,6 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, - "node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/memory-fs/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/memory-fs/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/memory-fs/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/memory-fs/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8685,28 +8649,28 @@ } }, "node_modules/nuxt": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/nuxt/-/nuxt-3.8.0.tgz", - "integrity": "sha512-ZnisJYx5AcUl7xlw18m6zfINBpNhld+ZF+jdTLRZxkLjKSFZeFMGqKxOR1jNVSmxfIXM/guK0uV9GPm6HK/z7g==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/nuxt/-/nuxt-3.8.1.tgz", + "integrity": "sha512-RSGO56Gv0x2f6AXWw4o4GoBaVdsD0qkPCjrX7Ud/jzH3cRJoyYMPuq/9AOLvf2o1ecZWl39j5elqJ4QHmggyOA==", "dev": true, "dependencies": { "@nuxt/devalue": "^2.0.2", "@nuxt/devtools": "^1.0.0", - "@nuxt/kit": "3.8.0", - "@nuxt/schema": "3.8.0", + "@nuxt/kit": "3.8.1", + "@nuxt/schema": "3.8.1", "@nuxt/telemetry": "^2.5.2", "@nuxt/ui-templates": "^1.3.1", - "@nuxt/vite-builder": "3.8.0", - "@unhead/dom": "^1.7.4", - "@unhead/ssr": "^1.7.4", - "@unhead/vue": "^1.7.4", - "@vue/shared": "^3.3.4", - "acorn": "8.10.0", + "@nuxt/vite-builder": "3.8.1", + "@unhead/dom": "^1.8.3", + "@unhead/ssr": "^1.8.3", + "@unhead/vue": "^1.8.3", + "@vue/shared": "^3.3.8", + "acorn": "8.11.2", "c12": "^1.5.1", "chokidar": "^3.5.3", "cookie-es": "^1.0.0", - "defu": "^6.1.2", - "destr": "^2.0.1", + "defu": "^6.1.3", + "destr": "^2.0.2", "devalue": "^4.3.2", "esbuild": "^0.19.5", "escape-string-regexp": "^5.0.0", @@ -8715,12 +8679,12 @@ "globby": "^13.2.2", "h3": "^1.8.2", "hookable": "^5.5.3", - "jiti": "^1.20.0", + "jiti": "^1.21.0", "klona": "^2.0.6", "knitwork": "^1.0.0", "magic-string": "^0.30.5", "mlly": "^1.4.2", - "nitropack": "^2.7.0", + "nitropack": "^2.7.2", "nuxi": "^3.9.1", "nypm": "^0.3.3", "ofetch": "^1.3.3", @@ -8741,7 +8705,7 @@ "unplugin": "^1.5.0", "unplugin-vue-router": "^0.7.0", "untyped": "^1.4.0", - "vue": "^3.3.4", + "vue": "^3.3.8", "vue-bundle-renderer": "^2.0.0", "vue-devtools-stub": "^0.1.0", "vue-router": "^4.2.5" @@ -8766,18 +8730,6 @@ } } }, - "node_modules/nuxt/node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/nuxt/node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -9382,15 +9334,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -9601,55 +9544,6 @@ "postcss": "^8.2.15" } }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-import-resolver": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-import-resolver/-/postcss-import-resolver-2.0.0.tgz", - "integrity": "sha512-y001XYgGvVwgxyxw9J1a5kqM/vtmIQGzx34g0A0Oy44MFcy/ZboZw1hu/iN3VYFjSTRzbvd7zZJJz0Kh0AGkTw==", - "dev": true, - "dependencies": { - "enhanced-resolve": "^4.1.1" - } - }, - "node_modules/postcss-import-resolver/node_modules/enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-import-resolver/node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/postcss-merge-longhand": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.0.tgz", @@ -9972,48 +9866,6 @@ "postcss": "^8.2.15" } }, - "node_modules/postcss-url": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz", - "integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==", - "dev": true, - "dependencies": { - "make-dir": "~3.1.0", - "mime": "~2.5.2", - "minimatch": "~3.0.4", - "xxhashjs": "~0.2.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-url/node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/postcss-url/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -10021,9 +9873,9 @@ "dev": true }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -10059,6 +9911,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prisma": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.5.2.tgz", + "integrity": "sha512-WQtG6fevOL053yoPl6dbHV+IWgKo25IRN4/pwAGqcWmg7CrtoCzvbDbN9fXUc7QS2KK0LimHIqLsaCOX/vHl8w==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "5.5.2" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + } + }, "node_modules/proc-log": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", @@ -10112,12 +9980,6 @@ "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", "dev": true }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -10188,15 +10050,6 @@ "flat": "^5.0.2" } }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "dependencies": { - "pify": "^2.3.0" - } - }, "node_modules/read-package-json": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.0.tgz", @@ -12975,16 +12828,16 @@ "dev": true }, "node_modules/vue": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.7.tgz", - "integrity": "sha512-YEMDia1ZTv1TeBbnu6VybatmSteGOS3A3YgfINOfraCbf85wdKHzscD6HSS/vB4GAtI7sa1XPX7HcQaJ1l24zA==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.8.tgz", + "integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.3.7", - "@vue/compiler-sfc": "3.3.7", - "@vue/runtime-dom": "3.3.7", - "@vue/server-renderer": "3.3.7", - "@vue/shared": "3.3.7" + "@vue/compiler-dom": "3.3.8", + "@vue/compiler-sfc": "3.3.8", + "@vue/runtime-dom": "3.3.8", + "@vue/server-renderer": "3.3.8", + "@vue/shared": "3.3.8" }, "peerDependencies": { "typescript": "*" @@ -13244,15 +13097,6 @@ "node": ">=12" } }, - "node_modules/xxhashjs": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", - "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", - "dev": true, - "dependencies": { - "cuint": "^0.2.2" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 870979c..78d8280 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,14 @@ "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", "eslint": "^8.39.0", - "nuxt": "3.8.0", + "nuxt": "3.8.1", + "prisma": "5.5.2", "sass": "^1.62.0", "vite-plugin-vuetify": "^1.0.2", "vuetify": "^3.1.15" }, "dependencies": { + "@prisma/client": "5.5.2", "mysql2": "^3.2.3" } } diff --git a/pages/client/[id].vue b/pages/client/[id].vue index 23f5e94..b64c240 100644 --- a/pages/client/[id].vue +++ b/pages/client/[id].vue @@ -6,24 +6,28 @@ import { ref, type Ref } from "vue"; import { VBtn, VForm } from "vuetify/components"; import PagedList from "~/components/pagedList.vue"; import Snowflake from "~/utils/snowflake"; -import { client as clientType, order, orderSummary } from "~/utils/types/database"; import OrderView from "~/components/orderView.vue"; import EntryEditor, { type fieldDefinition } from "~/components/entryEditor.vue"; const route = useRoute(); const id = route.params.id; -const clientRequest = await useFetch(`/api/clients/${id}`); +const clientRequest = await useFetch(`/api/clients/${id}` as "/api/clients/:id"); if (clientRequest.error.value) throw createError(clientRequest.error.value?.data ?? ""); -const client = clientRequest.data as Ref; +type Client = NonNullable; +const client = clientRequest.data as Ref; -const clientOrdersRequest = await useFetch(`/api/clients/${id}/orders`); +const clientOrdersRequest = await useFetch(`/api/clients/${id}/orders` as "/api/clients/:id/orders"); if (clientOrdersRequest.error.value) throw createError(clientOrdersRequest.error.value?.data ?? ""); -const clientOrders = clientOrdersRequest.data as Ref>; +type OrderSummary = NonNullable; +const clientOrders = clientOrdersRequest.data as Ref; +type Order = Awaited>>["data"]["value"]; + +// cache const orders = ref>(new Map()); for (const i of clientOrders.value) @@ -36,7 +40,7 @@ async function loadOrder(id: string) { // @ts-expect-error entry.value = await $fetch(`/api/orders/${id}` as "/api/order/:id", { method: "GET", - }); + }) as Order; entry.loading = false; } @@ -184,24 +188,24 @@ function getCreationDate() { > diff --git a/pages/clients.vue b/pages/clients.vue index 87bf07b..83de69b 100644 --- a/pages/clients.vue +++ b/pages/clients.vue @@ -4,7 +4,6 @@ import { type NuxtError } from "nuxt/app"; import { ref, type Ref, reactive } from "vue"; import { VBtn } from "vuetify/components"; -import { type client as clientType } from "~/utils/types/database"; import pagedTable from "~/components/pagedTable.vue"; import Alerts, { type AlertData } from "~/components/alerts.vue"; import { type fieldDefinition } from "~/components/entryEditor.vue"; @@ -33,7 +32,7 @@ async function rowClicked(client: string, edit = false) { async function rowDelete(client: string) { try { - await $fetch(`/api/clients/${client}`, { + await $fetch(`/api/clients/${client}` as "api/clients/:id", { method: "DELETE", }); clients.value = clients.value.filter(e => e.id !== client); diff --git a/schema.prisma b/schema.prisma new file mode 100644 index 0000000..a507bf7 --- /dev/null +++ b/schema.prisma @@ -0,0 +1,116 @@ +datasource db { + provider = "mysql" + url = env("DB_URL") +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id BigInt @id @unique @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt + username String @unique @db.VarChar(30) + email String @unique @db.VarChar(128) + password Bytes @db.Binary(64) + display_name String? @db.VarChar(30) + managedOrders Order[] + sessions Session[] + + @@map("users") +} + +model Session { + id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt + userId BigInt @map("user") @db.UnsignedBigInt + expiry_date DateTime? @default(dbgenerated("(now() + interval 30 day)")) @db.Timestamp(0) + user User @relation(fields: [userId], references: [id]) + + @@index([userId], map: "user_idx") + @@map("sessions") +} + +model Client { + id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt + name String? @db.VarChar(128) + address String? @db.VarChar(128) + phone String? @db.VarChar(16) + email String? @db.VarChar(128) + orders Order[] + + @@map("clients") +} + +model Order { + id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt + clientId BigInt @db.UnsignedBigInt @map("client") + userId BigInt @db.UnsignedBigInt @map("user") + draft Boolean @default(true) @map("is_draft") @db.TinyInt + imported_products ImportedProduct[] + client Client @relation(fields: [clientId], references: [id]) + user User @relation(fields: [userId], references: [id]) + work Work[] + + @@index([clientId]) + @@index([userId]) + @@map("orders") +} + +model ImportedProduct { + id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt + orderId BigInt @db.UnsignedBigInt @map("order") + name String? @db.VarChar(128) + link String @db.VarChar(1024) + price_imported Decimal @default(0.00) @db.Decimal(10, 2) + price Decimal @default(0.00) @db.Decimal(10, 2) + order Order @relation(fields: [orderId], references: [id]) + + @@index([orderId]) + @@map("imported_products") +} + +model Offer { + id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt + name String @db.VarChar(45) + description String? @db.Text + recommended_price Decimal? @db.Decimal(10, 2) + work Work[] + + @@map("offer") +} + +model OrderTemplate { + id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt + name String @db.VarChar(45) + description String? @db.Text + work_templates WorkTemplate[] + + @@map("order_templates") +} + +model Work { + id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt + orderId BigInt @map("order") @db.UnsignedBigInt + offerId BigInt @map("offer") @db.UnsignedBigInt + price Decimal @db.Decimal(10, 2) + notes String? @db.Text + fulfilled Boolean @default(false) @map("is_fulfilled") @db.TinyInt + order Order @relation(fields: [orderId], references: [id]) + offer Offer @relation(fields: [offerId], references: [id]) + + @@index([offerId], map: "offer_idx") + @@index([orderId], map: "order_idx") + @@map("work") +} + +model WorkTemplate { + id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt + orderTemplateId BigInt @map("order_template") @db.UnsignedBigInt + offerId BigInt @map("offer") @db.UnsignedBigInt + price Decimal @default(0.00) @db.Decimal(10, 2) + notes String? @db.Text + orderTemplate OrderTemplate @relation(fields: [orderTemplateId], references: [id]) + + @@index([offerId]) + @@index([orderTemplateId]) + @@map("work_templates") +} diff --git a/schemaModel.mysqlWorkbench.mwb b/schemaModel.mysqlWorkbench.mwb deleted file mode 100644 index bd78215a898c40163d8875a3fcb78da75b9cf379..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16539 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 diff --git a/schemaModel.sql b/schemaModel.sql deleted file mode 100644 index b03f0ac..0000000 --- a/schemaModel.sql +++ /dev/null @@ -1,167 +0,0 @@ --- Server version 8.0.32 - --- --- Table structure for table `users` --- - -CREATE TABLE `users` ( - `id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))), - `username` varchar(30) NOT NULL, - `email` varchar(128) NOT NULL, - `password` binary(64) NOT NULL, - `display_name` varchar(30) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `idusers_UNIQUE` (`id`), - UNIQUE KEY `username_UNIQUE` (`username`), - UNIQUE KEY `email_UNIQUE` (`email`) -); - --- --- Table structure for table `clients` --- - -CREATE TABLE `clients` ( - `id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))), - `name` varchar(128) DEFAULT NULL, - `address` varchar(128) DEFAULT NULL, - `phone` varchar(16) DEFAULT NULL, - `email` varchar(128) DEFAULT NULL, - PRIMARY KEY (`id`) -); - --- --- Table structure for table `orders` --- - -CREATE TABLE `orders` ( - `id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))), - `client` bigint unsigned NOT NULL, - `user` bigint unsigned NOT NULL, - `is_draft` tinyint NOT NULL DEFAULT '1', - PRIMARY KEY (`id`), - KEY `user_idx` (`user`), - KEY `client_idx` (`client`), - CONSTRAINT `client` FOREIGN KEY (`client`) REFERENCES `clients` (`id`), - CONSTRAINT `user` FOREIGN KEY (`user`) REFERENCES `users` (`id`) -); - --- --- Table structure for table `imported_products` --- - -CREATE TABLE `imported_products` ( - `id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))), - `order` bigint unsigned NOT NULL, - `name` varchar(128) DEFAULT NULL, - `link` varchar(1024) NOT NULL, - `price_imported` decimal(10,2) NOT NULL DEFAULT '0.00', - `price` decimal(10,2) NOT NULL DEFAULT '0.00', - PRIMARY KEY (`id`), - KEY `order_idx` (`order`), - CONSTRAINT `order2` FOREIGN KEY (`order`) REFERENCES `orders` (`id`) -); - --- --- Table structure for table `offer` --- - -CREATE TABLE `offer` ( - `id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))), - `name` varchar(45) NOT NULL, - `description` text, - `recommended_price` decimal(10,2) DEFAULT NULL, - PRIMARY KEY (`id`) -); - --- --- Table structure for table `order_templates` --- - -CREATE TABLE `order_templates` ( - `id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))), - `name` varchar(45) NOT NULL, - `description` text, - PRIMARY KEY (`id`) -); - --- --- Table structure for table `sessions` --- - -CREATE TABLE `sessions` ( - `id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))), - `user` bigint unsigned NOT NULL, - `expiry_date` timestamp NULL DEFAULT ((now() + interval 30 day)), - PRIMARY KEY (`id`), - KEY `user_idx` (`user`), - CONSTRAINT `user_session` FOREIGN KEY (`user`) REFERENCES `users` (`id`) -); - --- --- Table structure for table `work` --- - -CREATE TABLE `work` ( - `id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))), - `order` bigint unsigned NOT NULL, - `offer` bigint unsigned NOT NULL, - `price` decimal(10,2) NOT NULL, - `notes` text, - `is_fulfilled` tinyint NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - KEY `order_idx` (`order`), - KEY `offer_idx` (`offer`), - CONSTRAINT `offer` FOREIGN KEY (`offer`) REFERENCES `offer` (`id`), - CONSTRAINT `order` FOREIGN KEY (`order`) REFERENCES `orders` (`id`) -); - --- --- Table structure for table `work_templates` --- - -CREATE TABLE `work_templates` ( - `id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))), - `order_template` bigint unsigned NOT NULL, - `offer` bigint unsigned NOT NULL, - `price` decimal(10,2) NOT NULL DEFAULT '0.00', - `notes` text, - PRIMARY KEY (`id`), - KEY `order_template_idx` (`order_template`), - KEY `offer_idx` (`offer`), - CONSTRAINT `offer2` FOREIGN KEY (`offer`) REFERENCES `offer` (`id`), - CONSTRAINT `order_template` FOREIGN KEY (`order_template`) REFERENCES `order_templates` (`id`) -); - --- --- Final view structure for view `orderSummaries` --- - -CREATE VIEW `orderSummaries` AS -SELECT - `id`, - `client`, - `user`, - `is_draft`, - (COALESCE(`imported_products`.`price`, 0) + COALESCE(`work`.`price`, 0)) AS `value`, - COALESCE(`imported_products`.`count`, 0) as `imported_products_count`, - COALESCE(`work`.`count`, 0) as `work_count` -FROM -`orders` -LEFT JOIN -( - SELECT - `order`, - SUM(`price`) as `price`, - COUNT(*) AS `count` - FROM `imported_products` - GROUP BY `order` -) as `imported_products` ON `orders`.`id` = `imported_products`.`order` -LEFT JOIN -( - SELECT - `order`, - SUM(`price`) AS `price`, - COUNT(*) AS `count` - FROM `work` - GROUP BY `work`.`order` -) AS `work` ON `work`.`order` = `orders`.`id`; diff --git a/server/api/clients.get.ts b/server/api/clients.get.ts index 8d2d498..5a8c756 100644 --- a/server/api/clients.get.ts +++ b/server/api/clients.get.ts @@ -1,11 +1,10 @@ -import { defineEventHandler, getQuery } from "h3"; -import { createError } from "#imports"; +import { defineEventHandler } from "h3"; -import BaaPagination from "~/server/utils/baaPagination"; -import { type client } from "~/utils/types/database"; - -export const baaWrapper = new BaaPagination("clients", "id"); +import getPaginatedParameters from "../utils/baaPageParsing"; +import { database } from "../utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; export default defineEventHandler((e) => { - return baaWrapper.RESTget(e); + const pageParameters = getPaginatedParameters(e, 50, 200); + return database.client.findPaginated(pageParameters, {}).then(prismaToWeb); }); diff --git a/server/api/clients.post.ts b/server/api/clients.post.ts index 6dbe778..97df465 100644 --- a/server/api/clients.post.ts +++ b/server/api/clients.post.ts @@ -1,7 +1,10 @@ -import { defineEventHandler, readBody } from "h3"; +import { defineEventHandler, readBody, setResponseStatus } from "h3"; +import { type Client } from "@prisma/client"; -import { baaWrapper } from "./clients.get"; -import { type client } from "~/utils/types/database"; +import getRequestingUser from "../utils/getRequestingUser"; +import { database } from "../utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; +import Snowflake from "~/utils/snowflake"; import { createError } from "#imports"; @@ -12,10 +15,10 @@ const clientKeys: Array = [ "email", ]; -export function checkIsClient( +export function checkIsClient( value: any, - required = false, -): value is Partial> { + patch: Patch, +): value is Patch extends true ? Partial> : Omit { const errors = new Map(); if (typeof value !== "object") { @@ -25,12 +28,12 @@ export function checkIsClient( }); } - if (!(typeof value.name === "string" || value.name === null || (!required && value.name === undefined))) errors.set("name", "is not string or null"); - if (!(typeof value.address === "string" || value.address === null || (!required && value.address === undefined))) errors.set("address", "is not string or null"); - if (!(typeof value.phone === "string" || value.phone === null || (!required && value.phone === undefined))) errors.set("phone", "is not string or null"); - if (!(typeof value.email === "string" || value.email === null || (!required && value.email === undefined))) errors.set("email", "is not string or null"); + if (!(typeof value.name === "string" || value.name === null || (!patch && value.name === undefined))) errors.set("name", "is not string or null"); + if (!(typeof value.address === "string" || value.address === null || (!patch && value.address === undefined))) errors.set("address", "is not string or null"); + if (!(typeof value.phone === "string" || value.phone === null || (!patch && value.phone === undefined))) errors.set("phone", "is not string or null"); + if (!(typeof value.email === "string" || value.email === null || (!patch && value.email === undefined))) errors.set("email", "is not string or null"); - for (const i in value as Partial>) + for (const i in value as Partial>) if (!clientKeys.includes(i)) errors.set(i, `excessive property`); if (errors.size !== 0) { @@ -50,6 +53,20 @@ export function checkIsClient( return true; } -export default defineEventHandler((e) => { - return baaWrapper.RESTpost(e, clientKeys as Array>, (o): o is Omit => checkIsClient(o, true)); +export default defineEventHandler(async (e) => { + const body = await readBody(e); + const id = new Snowflake().state; + const user = await getRequestingUser(e); + + if (!checkIsClient(body, false)) throw createError({ message: "Invalid body", statusCode: 400 }); + + const rvalue = await database.client.create({ + data: { + ...body, + id, + }, + }); + + setResponseStatus(e, 201); + return prismaToWeb(rvalue); }); diff --git a/server/api/clients/[id].delete.ts b/server/api/clients/[id].delete.ts index 9b6ab20..b740270 100644 --- a/server/api/clients/[id].delete.ts +++ b/server/api/clients/[id].delete.ts @@ -1,7 +1,22 @@ import { defineEventHandler } from "h3"; -import { baaWrapper } from "../clients.get"; +import { database } from "~/server/utils/database"; -export default defineEventHandler((e) => { - return baaWrapper.RESTdeleteRecord(e); +import { createError } from "#imports"; + +export default defineEventHandler(async (e) => { + const id = e.context.params?.id as string; + + try { + await database.client.delete({ + where: { + id: BigInt(id), + }, + }); + } catch (e) { + // FIXME: should be 500 on errors other than "RecordNotFound" + throw createError({ statusCode: 404 }); + } + + return null; }); diff --git a/server/api/clients/[id].get.ts b/server/api/clients/[id].get.ts index 75fcc79..89696e0 100644 --- a/server/api/clients/[id].get.ts +++ b/server/api/clients/[id].get.ts @@ -1,7 +1,18 @@ import { defineEventHandler } from "h3"; -import { baaWrapper } from "../clients.get"; +import { database } from "~/server/utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; -export default defineEventHandler((e) => { - return baaWrapper.RESTgetRecord(e); +import { createError } from "#imports"; + +export default defineEventHandler(async (e) => { + const key = e.context.params?.id as string; + const rvalue = await database.client.findUnique({ + where: { + id: BigInt(key), + }, + }); + + if (!rvalue) throw createError({ statusCode: 404 }); + return prismaToWeb(rvalue); }); diff --git a/server/api/clients/[id].patch.ts b/server/api/clients/[id].patch.ts index 55163ca..604fd4a 100644 --- a/server/api/clients/[id].patch.ts +++ b/server/api/clients/[id].patch.ts @@ -1,8 +1,23 @@ -import { defineEventHandler } from "h3"; +import { defineEventHandler, readBody } from "h3"; import { checkIsClient } from "../clients.post"; -import { baaWrapper } from "../clients.get"; +import { database } from "~/server/utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; -export default defineEventHandler((e) => { - return baaWrapper.RESTpatchRecord(e, checkIsClient); +import { createError } from "#imports"; + +export default defineEventHandler(async (e) => { + const body = await readBody(e); + const id = e.context.params?.id as string; + + if (!checkIsClient(body, true)) throw createError({ message: "Invalid body", statusCode: 400 }); + + const rvalue = await database.client.update({ + where: { + id: BigInt(id), + }, + data: body, + }); + + return prismaToWeb(rvalue); }); diff --git a/server/api/clients/[id]/orders.get.ts b/server/api/clients/[id]/orders.get.ts index de76c37..4279100 100644 --- a/server/api/clients/[id]/orders.get.ts +++ b/server/api/clients/[id]/orders.get.ts @@ -1,9 +1,16 @@ import { defineEventHandler } from "h3"; -import { baaWrapper } from "~/server/api/orders.get"; +import { getOrders } from "~/server/api/orders.get"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; +import getPaginatedParameters from "~/server/utils/baaPageParsing"; -export default defineEventHandler(async (e) => { - const baa = await baaWrapper.RESTget(e, 50, 200, "`client` = ?", [e.context.params?.id]); - console.log(baa); - return baa; +export default defineEventHandler((e) => { + const pageParameters = getPaginatedParameters(e, 50, 200); + const clientId = e.context.params?.id as string; + return getOrders( + pageParameters, + { + clientId: BigInt(clientId), + }, + ).then(prismaToWeb); }); diff --git a/server/api/clients/count.get.ts b/server/api/clients/count.get.ts index 565dcd2..d374451 100644 --- a/server/api/clients/count.get.ts +++ b/server/api/clients/count.get.ts @@ -1,7 +1,9 @@ import { defineEventHandler } from "h3"; -import { baaWrapper } from "../clients.get"; +import { database } from "~/server/utils/database"; -export default defineEventHandler((e) => { - return baaWrapper.RESTrecordCount(e); +export default defineEventHandler(async (e) => { + return { + count: await database.client.count({}), + }; }); diff --git a/server/api/firstRun.get.ts b/server/api/firstRun.get.ts index 21ccb65..a9c60ad 100644 --- a/server/api/firstRun.get.ts +++ b/server/api/firstRun.get.ts @@ -1,13 +1,14 @@ import { defineEventHandler } from "h3"; -import { type data, database } from "../utils/database"; +import { database } from "../utils/database"; export async function isFirstRun() { - const [tables] = await database.query({ sql: "SHOW TABLES", rowsAsArray: true }, []) as data<[string]>; - if (tables.length === 0) return true; - if (!tables.find(a => a[0] === "users")) return true; - const [[users]] = await database.query("SELECT COUNT(*) as `count` FROM `users`") as data<{count: number}>; - if (users.count === 0) return true; - return false; + try { + const numberOfUsers = await database.user.count(); + return numberOfUsers === 0; + } catch { + // We could fall here if the database is not initialized + return true; + } } export default defineEventHandler((e) => { diff --git a/server/api/firstRun.post.ts b/server/api/firstRun.post.ts index 16436cf..714be3b 100644 --- a/server/api/firstRun.post.ts +++ b/server/api/firstRun.post.ts @@ -1,7 +1,7 @@ -import fs from "node:fs/promises"; +import { execSync } from "node:child_process"; import { defineEventHandler, setResponseStatus, readBody } from "h3"; -import { database as db } from "../utils/database"; +import { database } from "../utils/database"; import { isFirstRun } from "./firstRun.get"; import { getPasswordHash } from "./login.post"; import Snowflake from "~/utils/snowflake"; @@ -11,7 +11,7 @@ import { createError } from "#imports"; export default defineEventHandler(async (e) => { if (!isFirstRun()) { setResponseStatus(e, 404); - return ""; + return null; } const body = await readBody(e); @@ -23,12 +23,14 @@ export default defineEventHandler(async (e) => { const email = body.email; if (typeof email !== "string") throw createError({ message: "email is not string", statusCode: 400 }); - const sql = await fs.readFile("./schemaModel.sql", "utf-8"); - - const database = await db.new({ multipleStatements: true }); - await database.query(sql); - await database.execute( - "INSERT INTO `users` (`id`, `username`, `password`, `email`) VALUES (?, ?, ?, ?)", - [new Snowflake().toString(), username, getPasswordHash(password), email]); - return ""; + execSync("npx prisma db push --force-reset"); + database.user.create({ + data: { + id: new Snowflake().state, + username, + email, + password: getPasswordHash(password), + }, + }); + return null; }); diff --git a/server/api/login.post.ts b/server/api/login.post.ts index 49f8690..1b7f00d 100644 --- a/server/api/login.post.ts +++ b/server/api/login.post.ts @@ -1,7 +1,7 @@ import crypto from "crypto"; import { defineEventHandler, getCookie, setCookie, readBody } from "h3"; -import { database, type data } from "../utils/database"; +import { database } from "../utils/database"; import { isString } from "../utils/isString"; import { cookieSettings } from "../utils/rootUtils"; import Snowflake from "~/utils/snowflake"; @@ -28,19 +28,26 @@ export default defineEventHandler(async (e) => { const hashedPassword = getPasswordHash(password); - const [account] = await database.query( - "SELECT CONVERT(`id`, CHAR(32)) AS `id` from `users` WHERE `username` = ? AND `password` = ? LIMIT 1", - [login, hashedPassword], - )as unknown as data<{id: string}>; + const account = await database.user.findUnique({ + where: { + username: login, + password: hashedPassword, + }, + select: { + id: true, + }, + }); - if (account.length === 0) throw createError({ statusCode: 400, message: "Invalid username or password." }); + if (account === null) throw createError({ statusCode: 400, message: "Invalid username or password." }); - const sessionId = new Snowflake().toString(); + const sessionId = new Snowflake(); - await database.query( - "INSERT INTO `sessions` (`id`, `user`) VALUES ( ? , ? )", - [sessionId, account[0].id], - ); - setCookie(e, "token", sessionId, cookieSettings); - return { message: "Login successful", token: sessionId }; + await database.session.create({ + data: { + id: sessionId.state, + userId: account.id, + }, + }); + setCookie(e, "token", sessionId.toString(), cookieSettings); + return { message: "Login successful", token: sessionId.toString() }; }); diff --git a/server/api/logout.ts b/server/api/logout.ts index 59ce9b6..91ddfe8 100644 --- a/server/api/logout.ts +++ b/server/api/logout.ts @@ -23,9 +23,10 @@ export default defineEventHandler(async (e) => { }); } - database.query( - "DELETE FROM `sessions` WHERE `id` = ?", - [token], - ); + database.session.delete({ + where: { + id: BigInt(token), + }, + }); return { message: "Logged out" }; }); diff --git a/server/api/orders.get.ts b/server/api/orders.get.ts index 526401b..a11820b 100644 --- a/server/api/orders.get.ts +++ b/server/api/orders.get.ts @@ -1,37 +1,65 @@ import { defineEventHandler } from "h3"; +import { type Order, type Client, Prisma } from "@prisma/client"; -import BaaPagination from "../utils/baaPagination"; -import { type data, database } from "../utils/database"; -import { type client, type orderSummary } from "~/utils/types/database"; +import getPaginatedParameters, { type pageData } from "../utils/baaPageParsing"; +import { database } from "../utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; -export const baaWrapper = new BaaPagination( - "orderSummaries", - "id", - "*, CONVERT(`client`, CHAR) AS `client`, CONVERT(`user`, CHAR) as `user`", -); +type orderSummary = Omit & { + client: Client; + value: number; + imported_products_count: number; + work_count: number; +}; -export default defineEventHandler(async (e) => { - const orders = await baaWrapper.RESTget(e, 50, 200); +export async function getOrders( + pageParameters: pageData, + where?: Prisma.OrderWhereInput, +) { + const data = await database.order.findPaginated( + pageParameters, + { + select: { + id: true, + client: true, + userId: true, + draft: true, + imported_products: { + select: { + price: true, + }, + }, + work: { + select: { + price: true, + }, + }, + }, + where, + }, + ); - const uniqueClients: Array = []; - for (const i of orders) { - if (!uniqueClients.includes(i.client)) - uniqueClients.push(database.escape(i.client)); + const rvalue = new Array(); + + for (const i of data) { + const importedProductsPriceSum = i.imported_products.reduce((pv, cv) => pv + cv.price.toNumber(), 0); + const workPriceSum = i.work.reduce((pv, cv) => pv + cv.price.toNumber(), 0); + + rvalue.push({ + id: i.id, + client: i.client, + draft: i.draft, + imported_products_count: i.imported_products.length, + userId: i.userId, + value: importedProductsPriceSum + workPriceSum, + work_count: i.work.length, + }); } - const [clients] = await database.query( - ["SELECT", - "*,", - "CONVERT(`id`, CHAR) AS `id`", - "FROM `clients`", - "WHERE `id` IN", - `(${uniqueClients.join(', ')})`, - ].join(" "), - ) as data; - - const rvalue: Array | { client?: client }> = []; - - for (const i of orders) - rvalue.push({ ...i, client: clients.find(e => i.client === e.id) }); return rvalue; +} + +export default defineEventHandler((e) => { + const pageParameters = getPaginatedParameters(e, 50, 200); + return getOrders(pageParameters, {}).then(prismaToWeb); }); diff --git a/server/api/orders.post.ts b/server/api/orders.post.ts index e67913d..f5679eb 100644 --- a/server/api/orders.post.ts +++ b/server/api/orders.post.ts @@ -1,39 +1,44 @@ import { defineEventHandler, readBody, setResponseStatus } from "h3"; +import * as Prisma from "@prisma/client"; import { createValidationError, handleRecursedValidationError } from "../utils/validation"; -import { database as db } from "../utils/database"; +import { database } from "../utils/database"; import getRequestingUser from "../utils/getRequestingUser"; import { getOrder } from "./orders/[id].get"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; import Snowflake from "~/utils/snowflake"; import { createError } from "#imports"; -type importedProduct = { +type importedProduct = { + orderId: inOrder extends true ? never : string, name: string | null, link: string, price_imported: number, price: number, } -type work = { - offer: string, +type work = { + orderId: inOrder extends true ? never : string, + offerId: string, price: number, notes: string | null, - is_fulfilled: boolean | 0 | 1, + fulfilled: boolean, } type order = { - client: string, - // user: string, - is_draft: boolean | 0 | 1, - imported_products: Array, - work: Array, + clientId: string, + // userId: string, + draft: boolean, + imported_products: Array>, + work: Array>, }; -export function checkIsWork( +export function checkIsWork( value: any, patch: Patch, -): value is Patch extends true ? Partial : work { + needsOrderId: inOrder, +): value is Patch extends true ? Partial> : work { const errors = new Map(); if (typeof value !== "object") { @@ -43,19 +48,24 @@ export function checkIsWork( }); } - if (!(typeof value.offer === "string" || (patch && value.offer === undefined))) errors.set("offer", "is not string"); + if (!(typeof value.orderId === "string" || (patch && value.orderId === undefined) || !needsOrderId)) errors.set("orderId", "is not string"); + if (!(typeof value.offerId === "string" || (patch && value.offerId === undefined))) errors.set("offerId", "is not string"); if (!(typeof value.price === "number" || (patch && value.price === undefined))) errors.set("price", "is not price"); if (!(typeof value.notes === "string" || value.notes === null || (patch && value.notes === undefined))) errors.set("notes", "is not string or null"); - if (!(typeof value.is_fulfilled === "boolean" || value.is_fulfilled === 0 || value.is_fulfilled === 1 || (patch && value.is_fulfilled === undefined))) errors.set("is_fulfilled", "is not boolean"); + if (!(typeof value.is_fulfilled === "boolean" || (patch && value.is_fulfilled === undefined))) errors.set("is_fulfilled", "is not boolean"); + + // TODO: Excessive property checking + // Excessive properties should be checked and an error should be thrown if there is one if (errors.size !== 0) throw createValidationError(errors); return true; } -export function checkIsImportedProduct( +export function checkIsImportedProduct( value: any, patch: Patch, -): value is Patch extends true ? Partial : importedProduct { + needsOrderId: inOrder, +): value is Patch extends true ? Partial> : importedProduct { const errors = new Map(); if (typeof value !== "object") { @@ -65,10 +75,14 @@ export function checkIsImportedProduct( }); } + if (!(typeof value.orderId === "string" || (patch && value.orderId === undefined) || !needsOrderId)) errors.set("orderId", "is not string"); if (!(typeof value.name === "string" || value.name === null || (patch && value.name === undefined))) errors.set("name", "is not string or null"); if (!(typeof value.link === "string" || (patch && value.name === undefined))) errors.set("link", "is not string"); if (!(typeof value.price_imported === "number" || (patch && value.name === undefined))) errors.set("price_imported", "is not number"); - if (!(typeof value.price || (patch && value.price === undefined))) errors.set("price", "is not number"); + if (!(typeof value.price === "number" || (patch && value.price === undefined))) errors.set("price", "is not number"); + + // TODO: Excessive property checking + // Excessive properties should be checked and an error should be thrown if there is one if (errors.size !== 0) throw createValidationError(errors); @@ -78,7 +92,7 @@ export function checkIsImportedProduct( export function checkIsOrder( value: any, patch: Patch, -): value is Patch extends true ? Partial> : order { +): value is Patch extends true ? Partial> : order { const errors = new Map(); if (typeof value !== "object") { @@ -88,19 +102,22 @@ export function checkIsOrder( }); } - if (!(typeof value.client === "string" || (patch && value.client === undefined))) errors.set("client", "is not string"); - if (!(typeof value.is_draft === "boolean" || value.is_draft === 0 || value.is_draft === 1 || (patch && value.is_draft === undefined))) errors.set("is_draft", "is not boolean"); + if (!(typeof value.clientId === "string" || (patch && value.clientId === undefined))) errors.set("clientId", "is not string"); + if (!(typeof value.draft === "boolean" || (patch && value.is_draft === undefined))) errors.set("draft", "is not boolean"); if (!(value.imported_products instanceof Array)) errors.set("imported_products", "is not array"); else if (patch && value.imported_products !== undefined) errors.set("imported_products", "cannot patch from order"); if (!(value.work instanceof Array)) errors.set("work", "is not array"); else if (patch && value.work !== undefined) errors.set("work", "cannot patch from order"); + // TODO: Excessive property checking + // Excessive properties should be checked and an error should be thrown if there is one + if (!patch) { const importedProducts = value.imported_products; if (importedProducts instanceof Array) { for (const i in importedProducts) { try { - checkIsImportedProduct(importedProducts[i], patch); + checkIsImportedProduct(importedProducts[i], patch, false); } catch (e) { handleRecursedValidationError(e, errors, `imported_products[${i}]`); } @@ -111,7 +128,7 @@ export function checkIsOrder( if (work instanceof Array) { for (const i in work) { try { - checkIsWork(work[i], patch); + checkIsWork(work[i], patch, false); } catch (e) { handleRecursedValidationError(e, errors, `work[${i}]`); } @@ -126,49 +143,49 @@ export function checkIsOrder( export default defineEventHandler(async (e) => { const body = await readBody(e); - const id = new Snowflake().toString(); + const id = new Snowflake().state; const user = await getRequestingUser(e); if (!checkIsOrder(body, false)) throw createError({ message: "Invalid body", statusCode: 400 }); - const database = await db.new(); - await database.beginTransaction(); - - await database.query( - ["INSERT INTO", - "`orders`", - "VALUES", - "(?, ?, ?, ?)", - ].join(" "), - [id, body.client, user.id, body.is_draft], - ); - - const promises: Array> = []; - for (const i of body.imported_products) { - promises.push(database.query( - ["INSERT INTO", - "`imported_products`", - "VALUES", - "(?, ?, ?, ?, ?, ?)", - ].join(" "), - [new Snowflake().toString(), id, i.name, i.link, i.price_imported, i.price], - )); - } - - for (const i of body.work) { - promises.push(database.query( - ["INSERT INTO", - "`work`", - "VALUES", - "(?, ?, ?, ?, ?, ?)", - ].join(" "), - [new Snowflake().toString(), id, i.offer, i.price, i.notes, i.is_fulfilled], - )); - } - - await Promise.all(promises); - await database.commit(); + await database.order.create({ + data: { + clientId: BigInt(body.clientId), + draft: body.draft, + imported_products: { + createMany: { + data: body.imported_products.reduce( + (pV, cV) => { + pV.push({ + ...cV, + id: new Snowflake().state, + }); + return pV; + }, + [] as Array>, + ), + }, + }, + work: { + createMany: { + data: body.work.reduce( + (pV, cV) => { + pV.push({ + ...cV, + id: new Snowflake().state, + offerId: BigInt(cV.offerId), + }); + return pV; + }, + [] as Array>, + ), + }, + }, + id, + userId: user.id, + }, + }); setResponseStatus(e, 201); - return getOrder(id); + return getOrder(id).then(prismaToWeb); }); diff --git a/server/api/orders/[id].delete.ts b/server/api/orders/[id].delete.ts index 662e7d2..532305d 100644 --- a/server/api/orders/[id].delete.ts +++ b/server/api/orders/[id].delete.ts @@ -1,19 +1,22 @@ import { defineEventHandler } from "h3"; -import { type ResultSetHeader } from "mysql2"; import { database } from "~/server/utils/database"; import { createError } from "#imports"; export default defineEventHandler(async (e) => { - const id = e.context.params?.id; + const id = e.context.params?.id as string; - const [result] = await database.query( - "DELETE FROM `orders` WHERE `id` = ?", - [id], - ) as unknown as [ResultSetHeader]; - - if (result.affectedRows === 0) throw createError({ statusCode: 404 }); + try { + await database.order.delete({ + where: { + id: BigInt(id), + }, + }); + } catch (e) { + // FIXME: should be 500 on errors other than "RecordNotFound" + throw createError({ statusCode: 404 }); + } return null; }); diff --git a/server/api/orders/[id].get.ts b/server/api/orders/[id].get.ts index 6069201..a98bca9 100644 --- a/server/api/orders/[id].get.ts +++ b/server/api/orders/[id].get.ts @@ -1,109 +1,38 @@ import { defineEventHandler } from "h3"; import { createError } from "#imports"; -import { type offer as offerType, type order } from "~/utils/types/database"; -import { database, type data } from "~/server/utils/database"; +import { database } from "~/server/utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; -export async function orderExists(id: string) { - const [[exists]] = await database.query( - "SELECT EXISTS(*) AS `exists` FROM `orders` WHERE `id` = ?", - [id], - ) as data<{exists: 0 | 1}>; - - return exists.exists === 1; +export async function orderExists(id: bigint) { + const exists = await database.order.findUnique({ + where: { + id, + }, + }); + return exists !== null; } -export async function getImportedProducts(id: string) { - const [importedProducts] = await database.query( - ["SELECT", - "CONVERT(`id`, CHAR) AS `id`,", - "`name`,", - "`link`,", - "`price`,", - "`price_imported`", - "FROM `imported_products`", - "WHERE `order` = ?", - ].join(" "), - [id], - ) as data<{ - id: string, - name: string | null, - link: string, - price: string, - price_imported: string - }>; - - return importedProducts; -} - -export async function getWork(id: string) { - const [work] = await database.query( - ["SELECT", - "CONVERT(`id`, CHAR) AS `id`,", - "CONVERT(`offer`, CHAR) AS `offer`,", - "`price`,", - "`notes`,", - "`is_fulfilled`", - "FROM `work`", - "WHERE `order` = ?", - ].join(" "), - [id], - ) as data<{ - id: string, - offer: offerType, - price: number, - notes: string | null, - is_fulfilled: 0 | 1, - }>; - - const [offer] = await database.query( - ["SELECT", - "CONVERT(`offer`.`id`, CHAR) AS `id`,", - "`offer`.`name`,", - "`offer`.`description`,", - "`offer`.`recommended_price`", - "FROM", - "`work`", - "LEFT JOIN `offer` ON `work`.`offer` = `offer`.`id`", - "WHERE `work`.`order` = ?", - ].join(" "), - [id], - ) as data; - - // @ts-ignore i.offer is string, but it needs to be an offer object - for (const i of work) i.offer = offer.find(e => e.id === i.offer) as offerType; - return work; -} - -export async function getOrder(id: string): Promise { - const [[order]] = await database.query( - ["SELECT", - "CONVERT(`id`, CHAR) AS `id`,", - "CONVERT(`client`, CHAR) AS `client`,", - "CONVERT(`user`, CHAR) AS `user`, ", - "`is_draft`,", - "`value`", - "FROM `orderSummaries`", - "WHERE `id` = ?", - ].join(" "), - [id], - ) as data<{ - id: string, - client: string, - user: string, - is_draft: 0 | 1, - value: number, - }>; +export async function getOrder(id: bigint) { + const order = await database.order.findUnique({ + where: { + id, + }, + include: { + imported_products: true, + work: { + include: { + offer: true, + }, + }, + }, + }); if (!order) throw createError({ statusCode: 404 }); - - const importedProducts = await getImportedProducts(id); - const work = await getWork(id); - - return { ...order, imported_products: importedProducts, work }; + return order; } export default defineEventHandler((e) => { - const key = e.context.params?.id; - return getOrder(key as string); + const key = e.context.params?.id as string; + return getOrder(BigInt(key)).then(prismaToWeb); }); diff --git a/server/api/orders/[id].patch.ts b/server/api/orders/[id].patch.ts index 687066a..9c0a787 100644 --- a/server/api/orders/[id].patch.ts +++ b/server/api/orders/[id].patch.ts @@ -1,19 +1,27 @@ import { defineEventHandler, readBody } from "h3"; import { checkIsOrder } from "../orders.post"; -import { database as db } from "~/server/utils/database"; +import { getOrder } from "./[id].get"; +import { database } from "~/server/utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; import { createError } from "#imports"; export default defineEventHandler(async (e) => { const body = await readBody(e); - const id = e.context.params?.id; + const id = e.context.params?.id as string; - if (!checkIsOrder(e, true)) throw createError({ message: "Invalid body", statusCode: 400 }); + if (!checkIsOrder(body, true)) throw createError({ message: "Invalid body", statusCode: 400 }); - const database = await db.new(); - await database.beginTransaction(); + await database.order.update({ + where: { + id: BigInt(id), + }, + data: { + clientId: body.clientId ? BigInt(body.clientId) : undefined, + draft: body.draft, + }, + }); - for (const [k, v] of Object.entries(body)) - database.query(`UPDATE TABLE \`orders\` SET \`${k}\` = ? WHERE \`id\` = ?`, [v, id]); + return getOrder(BigInt(id)).then(prismaToWeb); }); diff --git a/server/api/orders/[id]/imported_products.get.ts b/server/api/orders/[id]/imported_products.get.ts index 1e6a027..636d2c1 100644 --- a/server/api/orders/[id]/imported_products.get.ts +++ b/server/api/orders/[id]/imported_products.get.ts @@ -1,14 +1,27 @@ import { defineEventHandler } from "h3"; -import { orderExists, getImportedProducts } from "../[id].get"; +import { orderExists } from "../[id].get"; +import { database } from "~/server/utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; import { createError } from "#imports"; export default defineEventHandler(async (e) => { - const id = e.context.params?.id as string; + const orderId = e.context.params?.id as string; - if (!orderExists(id)) throw createError({ statusCode: 404 }); + if (!(await orderExists(BigInt(orderId)))) throw createError({ statusCode: 404 }); - const importedProducts = await getImportedProducts(id); - return importedProducts; + return database.importedProduct.findMany({ + where: { + orderId: BigInt(orderId), + }, + select: { + id: true, + link: true, + name: true, + orderId: true, + price: true, + price_imported: true, + }, + }).then(prismaToWeb); }); diff --git a/server/api/orders/[id]/imported_products.post.ts b/server/api/orders/[id]/imported_products.post.ts index debf1a8..72aa628 100644 --- a/server/api/orders/[id]/imported_products.post.ts +++ b/server/api/orders/[id]/imported_products.post.ts @@ -1,29 +1,33 @@ import { defineEventHandler, readBody, setResponseStatus } from "h3"; import { checkIsImportedProduct } from "../../orders.post"; -import { getImportedProducts, orderExists } from "../[id].get"; +import { orderExists } from "../[id].get"; import Snowflake from "~/utils/snowflake"; import { database } from "~/server/utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; import { createError } from "#imports"; export default defineEventHandler(async (e) => { const body = await readBody(e); const idOrder = e.context.params?.id as string; - const idImportedProducts = new Snowflake().toString(); + const idImportedProduct = new Snowflake().state; - if (!orderExists(idOrder)) throw createError({ statusCode: 404 }); - if (!checkIsImportedProduct(body, false)) throw createError({ message: "Invalid body", statusCode: 400 }); + if (!await orderExists(BigInt(idOrder))) throw createError({ statusCode: 404 }); + if (!checkIsImportedProduct(body, false, false)) throw createError({ message: "Invalid body", statusCode: 400 }); - await database.query( - ["INSERT INTO", - "`imported_products`", - "VALUES", - "(?, ?, ?, ?, ?, ?)", - ].join(" "), - [idImportedProducts, idOrder, body.name, body.link, body.price_imported, body.price], - ); + const rvalue = await database.importedProduct.create({ + data: { + id: idImportedProduct, + link: body.link, + name: body.name, + orderId: BigInt(idOrder), + price: body.price, + price_imported: body.price_imported, + }, + }); setResponseStatus(e, 201); - return getImportedProducts(idOrder); + + return prismaToWeb(rvalue); }); diff --git a/server/api/orders/[id]/work.get.ts b/server/api/orders/[id]/work.get.ts index 02bd815..21f4f86 100644 --- a/server/api/orders/[id]/work.get.ts +++ b/server/api/orders/[id]/work.get.ts @@ -1,14 +1,31 @@ import { defineEventHandler } from "h3"; -import { orderExists, getWork } from "../[id].get"; +import { orderExists } from "../[id].get"; +import { database } from "~/server/utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; import { createError } from "#imports"; export default defineEventHandler(async (e) => { - const id = e.context.params?.id as string; + const orderId = e.context.params?.id as string; - if (!orderExists(id)) throw createError({ statusCode: 404 }); + if (!await orderExists(BigInt(orderId))) throw createError({ statusCode: 404 }); - const work = await getWork(id); - return work; + const data = await database.work.findMany({ + where: { + orderId: BigInt(orderId), + }, + select: { + id: true, + fulfilled: true, + notes: true, + offer: true, + orderId: true, + price: true, + }, + }); + + if (!data) throw createError({ statusCode: 404 }); + + return prismaToWeb(data); }); diff --git a/server/api/orders/[id]/work.post.ts b/server/api/orders/[id]/work.post.ts index f153ae6..eec5c67 100644 --- a/server/api/orders/[id]/work.post.ts +++ b/server/api/orders/[id]/work.post.ts @@ -1,30 +1,34 @@ import { defineEventHandler, readBody, setResponseStatus } from "h3"; +import { Decimal } from "@prisma/client/runtime/library"; import { checkIsWork } from "../../orders.post"; -import { getWork, orderExists } from "../[id].get"; +import { orderExists } from "../[id].get"; import Snowflake from "~/utils/snowflake"; import { database } from "~/server/utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; import { createError } from "#imports"; export default defineEventHandler(async (e) => { const body = await readBody(e); const idOrder = e.context.params?.id as string; - const idWork = new Snowflake().toString(); + const idWork = new Snowflake().state; - if (!orderExists(idOrder)) throw createError({ statusCode: 404 }); - if (!checkIsWork(body, false)) throw createError({ message: "Invalid body", statusCode: 400 }); + if (!orderExists(BigInt(idOrder))) throw createError({ statusCode: 404 }); + if (!checkIsWork(body, false, false)) throw createError({ message: "Invalid body", statusCode: 400 }); - await database.query( - ["INSERT INTO", - "`work`", - "VALUES", - "(?, ?, ?, ?, ?, ?)", - ].join(" "), - [idWork, idOrder, body.offer, body.price, body.notes, body.is_fulfilled], - ); + const rvalue = await database.work.create({ + data: { + id: BigInt(idWork), + fulfilled: body.fulfilled, + notes: body.notes, + offerId: BigInt(body.offerId), + orderId: BigInt(body.orderId), + price: new Decimal(body.price), + }, + }); setResponseStatus(e, 201); - return getWork(idWork); + return prismaToWeb(rvalue); }); diff --git a/server/api/orders/[id]/work/[idWork].delete.ts b/server/api/orders/[id]/work/[idWork].delete.ts index 9c7ef0e..c0cfd5d 100644 --- a/server/api/orders/[id]/work/[idWork].delete.ts +++ b/server/api/orders/[id]/work/[idWork].delete.ts @@ -1,7 +1,5 @@ import { defineEventHandler } from "h3"; -import { type ResultSetHeader } from "mysql2"; -import { orderExists } from "../../[id].get"; import { database } from "~/server/utils/database"; import { createError } from "#imports"; @@ -10,13 +8,17 @@ export default defineEventHandler(async (e) => { const idOrder = e.context.params?.id as string; const idWork = e.context.params?.idWork as string; - if (!orderExists(idOrder)) throw createError({ statusCode: 404 }); + try { + await database.work.delete({ + where: { + id: BigInt(idWork), + orderId: BigInt(idOrder), + }, + }); + } catch (e) { + // FIXME: should be 500 on errors other than "RecordNotFound" + throw createError({ statusCode: 404 }); + } - const [response] = await database.query( - "DELETE FROM `work` WHERE `id` = ?", - [idWork], - ) as unknown as [ResultSetHeader]; - - if (response.affectedRows === 0) throw createError({ statusCode: 404 }); return null; }); diff --git a/server/api/orders/[id]/work/[idWork].get.ts b/server/api/orders/[id]/work/[idWork].get.ts index 9c21067..835ee65 100644 --- a/server/api/orders/[id]/work/[idWork].get.ts +++ b/server/api/orders/[id]/work/[idWork].get.ts @@ -1,13 +1,32 @@ import { defineEventHandler } from "h3"; -import { orderExists, getWork } from "../../[id].get"; +import { orderExists } from "../../[id].get"; +import { database } from "~/server/utils/database"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; import { createError } from "#imports"; -export default defineEventHandler((e) => { +export default defineEventHandler(async (e) => { const idOrder = e.context.params?.id as string; const idWork = e.context.params?.idWork as string; - if (!orderExists(idOrder)) throw createError({ statusCode: 404 }); - return getWork(idWork); + if (!await orderExists(BigInt(idOrder))) throw createError({ statusCode: 404 }); + const data = await database.work.findUnique({ + where: { + orderId: BigInt(idOrder), + id: BigInt(idWork), + }, + select: { + id: true, + fulfilled: true, + notes: true, + offer: true, + orderId: true, + price: true, + }, + }); + + if (!data) throw createError({ statusCode: 404 }); + + return prismaToWeb(data); }); diff --git a/server/api/users/me.get.ts b/server/api/users/me.get.ts index 1e2f156..ef9c0cd 100644 --- a/server/api/users/me.get.ts +++ b/server/api/users/me.get.ts @@ -1,15 +1,8 @@ -/* global defineEventHandler, getCookie */ -import { defineEventHandler, getCookie } from "h3"; +import { defineEventHandler } from "h3"; -import { database, type data } from "~/server/utils/database"; -import { type user } from "~/utils/types/database"; +import getRequestingUser from "~/server/utils/getRequestingUser"; +import { prismaToWeb } from "~/server/utils/prismaToWeb"; -export default defineEventHandler(async (e) => { - const token = getCookie(e, "token"); - const [[userData]] = await database.query( - "SELECT CONVERT(`users`.`id`, CHAR(32)) as `id`, `users`.`username` as `username`, `users`.`email` as `email`, `users`.`display_name` as `display_name` FROM `sessions` LEFT JOIN `users` ON `sessions`.`user` = `users`.`id` WHERE `sessions`.`id` = ?", - [token], - ) as unknown as data; - - return userData; +export default defineEventHandler((e) => { + return getRequestingUser(e).then(prismaToWeb); }); diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts index ad4dc48..fb91a7d 100644 --- a/server/middleware/auth.ts +++ b/server/middleware/auth.ts @@ -1,7 +1,8 @@ import { defineEventHandler, getCookie } from "h3"; import { createError } from "#imports"; -import { database, type data } from "~/server/utils/database"; +import { database } from "~/server/utils/database"; +import getRequestingUser from "~/server/utils/getRequestingUser"; const endpointsWithoutAuth: string[] = [ "/dbtest", @@ -34,13 +35,14 @@ export default defineEventHandler(async (e) => { export async function isAuthorised(token: string | undefined): Promise { if (!token) return false; try { - const [[session]] = await database.query( - "SELECT EXISTS(SELECT `id` FROM `sessions` WHERE `id` = ? AND `expiry_date` >= NOW()) as `logged_in`", - [token], - ) as unknown as data<{logged_in: number}>; + await database.session.findUniqueOrThrow({ + where: { + id: BigInt(token), + }, + }); - return session.logged_in === 1; - } catch { + return true; + } catch (e) { return false; } } diff --git a/server/utils/baaPageParsing.ts b/server/utils/baaPageParsing.ts new file mode 100644 index 0000000..2bce640 --- /dev/null +++ b/server/utils/baaPageParsing.ts @@ -0,0 +1,108 @@ +// BAA stands for Before Around After + +import { getQuery, type H3Event } from "h3"; +import { type QueryObject } from "ufo"; + +import { isString } from "./isString"; + +import { createError } from "#imports"; + +type queryType = none extends false ? { + type: "before" | "after" | "around", + id: bigint +} : { + type: null +}; + +export type pageData = queryType & { count: number } + +/** + * Gets queryType for a given query with a value + * @param query the query to parse + * @throws if query malformed (multiple before/after/around) + */ +function getLocationParameterType(query: QueryObject): queryType { + const before = query.before; + const after = query.after; + const around = query.around; + + let setLocationParametersCount = 0; + let rvalue: queryType = { type: null }; + + if (isString(before)) { + setLocationParametersCount++; + rvalue = { type: "before", id: BigInt(before) }; + } + + if (isString(after)) { + setLocationParametersCount++; + rvalue = { type: "after", id: BigInt(after) }; + } + + if (isString(around)) { + setLocationParametersCount++; + rvalue = { type: "around", id: BigInt(around) }; + } + + if (setLocationParametersCount > 1) { + throw createError({ + statusCode: 400, + message: "multiple location parameters not allowed", + }); + } + + return rvalue; +} + +/** Gets the count parameter from the query object. + * @param query the query to check. + * @param defaultCount the default count if the query doesn't have count parameter. (default 50) + * @param countLimit the maximum count of the parameter before throwing. (default 200) + * @returns the value of count parameter. + * @throws if the parameter in query exceeds provided countLimit. + */ +function getRequestedCount( + query: QueryObject, + defaultCount = 50, + countLimit = 200, +) { + let count = defaultCount; + if (query.limit) count = Number(query.limit); + + if (count > countLimit) { + throw createError({ + statusCode: 400, + message: `Cannot retrieve more than ${countLimit} records`, + }); + } + if (count <= 0) { + throw createError({ + statusCode: 400, + message: "Tried to retireve 0 or less records", + }); + } + + return count; +} + +/** Gets the baa page parameters from the H3event, + * @param e the H3event to fetch parameters. + * @param defaultCount the default count to use if there is no count parameter. (default 50) + * @param countLimit the maximum value of the count parameter before throwing an error. (default 200) + * @returns the page data found in the query. + * @throws if event has a count parameter in the query that exceed provided countLimit. + */ +export default function getPaginatedParameters( + e: H3Event, + defaultCount = 50, + countLimit = 200, +): pageData { + const query = getQuery(e); + const queryParameters = getLocationParameterType(query); + const queryCount = getRequestedCount(query, defaultCount, countLimit); + + return { + ...queryParameters, + count: queryCount, + }; +} diff --git a/server/utils/baaPagination.ts b/server/utils/baaPagination.ts deleted file mode 100644 index aa78ec7..0000000 --- a/server/utils/baaPagination.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { defineEventHandler, getQuery, readBody, setResponseStatus, H3Event } from "h3"; -import { type ResultSetHeader } from "mysql2/promise"; - -import { type data, database } from "./database"; -import { isString } from "./isString"; -import Snowflake from "~/utils/snowflake"; -import { type client } from "~/utils/types/database"; - -import { createError } from "#imports"; - -type queryType = { - type: "before" | "after" | "around", - id: string -} | { - type: null -}; - -/** - * Before, around, after pagination wrapper - */ -export default class BaaPagination { - readonly table: string; - readonly key: keyType; - readonly select: string; - readonly groupBy: string; - - private get sqlGroupBy() { - return this.groupBy !== "" ? `GROUP BY ${this.groupBy}` : ""; - } - - /** - * Gets queryType for a given query with a value - * @param query the query to parse - * @throws if query malformed (multiple before/after/around) - */ - static getLocationParameterType(query: any): queryType { - const before = query.before; - const after = query.after; - const around = query.around; - - let setLocationParametersCount = 0; - let rvalue: queryType = { type: null }; - - if (isString(before)) { - setLocationParametersCount++; - rvalue = { type: "before", id: before }; - } - - if (isString(after)) { - setLocationParametersCount++; - rvalue = { type: "after", id: after }; - } - - if (isString(around)) { - setLocationParametersCount++; - rvalue = { type: "around", id: around }; - } - - if (setLocationParametersCount > 1) { - throw createError({ - statusCode: 400, - message: "multiple location parameters not allowed", - }); - } - - return rvalue; - } - - async getPagedResults( - queryType: queryType, - limit = 50, - where = "", - bind: Array = [], - ) { - const sqlwhere = where !== "" ? `AND (${where})` : ""; - switch (queryType.type) { - case "before": { - const [data] = await database.query( - `SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM \`${this.table}\` WHERE \`${this.key}\` < ? ${sqlwhere} ORDER BY \`${this.key}\` DESC ${this.sqlGroupBy} LIMIT ?`, - [queryType.id, ...bind, limit], - ) as unknown as data; - return data; - } - case "after": { - const [data] = await database.query( - `SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM \`${this.table}\` WHERE \`${this.key}\` > ? ${sqlwhere} ORDER BY \`${this.key}\` DESC ${this.sqlGroupBy} LIMIT ?`, - [queryType.id, ...bind, limit], - ) as unknown as data; - return data; - } - case "around": { - const [data] = await database.query( - ` SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM (\n` + - `(SELECT * FROM \`${this.table}\` WHERE \`${this.key}\` >= ? ${sqlwhere} ORDER BY \`${this.key}\` ${this.sqlGroupBy} ASC LIMIT ?)\n` + - "UNION ALL\n" + - `(SELECT ${this.select} FROM \`${this.table}\` WHERE \`${this.key}\` < ? ${sqlwhere} ORDER BY \`${this.key}\` DESC ${this.sqlGroupBy} LIMIT ?)\n` + - `) as \`x\` ORDER BY \`${this.key}\` DESC`, - [queryType.id, ...bind, Math.ceil(limit / 2), queryType.id, ...bind, Math.floor(limit / 2)], - ) as unknown as data; - return data; - } - case null: { - const [data] = await database.query( - `SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM \`${this.table}\` WHERE TRUE ${sqlwhere} ORDER BY \`${this.key}\` DESC ${this.sqlGroupBy} LIMIT ?`, - [...bind, limit], - ) as unknown as data; - return data; - } - default: - throw createError("Not implemented"); - } - } - - RESTget( - e: H3Event, - defaultLimit = 50, - limitLimit = 200, - where = "", - bind: Array = [], - ) { - const query = getQuery(e); - - let limit = defaultLimit; - if (query.limit) limit = Number(query.limit); - if (limit > limitLimit) { - throw createError({ - statusCode: 400, - message: `Cannot retrieve more than ${limitLimit} records`, - }); - } - if (limit <= 0) { - throw createError({ - statusCode: 400, - message: "Tried to retireve 0 or less records", - }); - } - - const queryData = BaaPagination.getLocationParameterType(query); - - return this.getPagedResults(queryData, limit, where, bind); - } - - async RESTpost>( - e: H3Event, - fields: Array, - valueChecker: (obj: unknown) => obj is {[P in K]: T[P]}, - ) { - const body = await readBody(e); - const id = new Snowflake().toString(); - - if (!valueChecker(body)) throw createError({ message: "Invalid body", statusCode: 400 }); - - const arrayToInsert: Array = [id]; - arrayToInsert.push(...fields.map(field => body[field])); - - await database.query( - `INSERT INTO \`${this.table}\` ` + - `(\`${this.key}\`,\`${fields.join("`, `")}\`) ` + - "VALUES (" + - "?, ".repeat(fields.length) + - "?)", - arrayToInsert, - ); - - setResponseStatus(e, 201); - - // FIXME: data may be turncated in the database - // either throw an error when data is too large or - // reply with turncated data - return { id, ...body }; - } - - async RESTgetRecord(e: H3Event) { - const key = e.context.params?.[this.key]; - const [data] = await database.query( - `SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM \`${this.table}\` WHERE \`${this.key}\` = ?`, - [key], - ) as data; - - if (!data[0]) { - throw createError({ - statusCode: 404, - }); - } - - return data[0]; - } - - async RESTpatchRecord( - e: H3Event, - valueChecker: (obj: unknown) => obj is Partial>, - ) { - const body = await readBody(e); - const key = e.context.params?.[this.key]; - - if (!valueChecker(body)) throw createError({ message: "Invalid body", statusCode: 400 }); - - for (const [k, v] of Object.entries(body)) { - // FIXME: use single database.query method instead of looping through keys and values - const [res] = await database.query( - // I believe it is safe to put key (k) in the template - // because it is limited to 4 values here - `UPDATE \`${this.table}\` SET \`${k}\` = ? WHERE \`${this.key}\` = ?`, - [v, key], - ) as unknown as [ResultSetHeader]; - - if (res.affectedRows !== 1) { - throw createError({ - statusCode: 404, - }); - } - } - - const [data] = await database.query( - `SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM \`${this.table}\` WHERE \`${this.key}\` = ?`, - [key], - ) as data; - - return data[0]; - } - - async RESTdeleteRecord(e: H3Event) { - const key = e.context.params?.[this.key]; - - const [result] = await database.query( - `DELETE FROM \`${this.table}\` WHERE \`${this.key}\` = ?`, - [key], - ) as unknown as [ResultSetHeader]; - - if (result.affectedRows === 0) throw createError({ statusCode: 404 }); - - return null; - } - - async RESTrecordCount( - e :H3Event, - where = "", - bind: Array = [], - ) { - const sqlwhere = where !== "" ? `WHERE ${where}` : ""; - const [[data]] = await database.query( - `SELECT COUNT(*) as \`count\` FROM \`${this.table}\` ${sqlwhere} ${this.sqlGroupBy}`, - bind, - ) as data<{count: number}>; - - if (!data) throw createError("Database returned no rows"); - return data; - } - - constructor( - table: string, - key: keyType, - select = "*", - groupBy = "", - ) { - this.table = table; - this.key = key; - this.select = select; - this.groupBy = groupBy; - } -} diff --git a/server/utils/database.ts b/server/utils/database.ts index 9d7cab6..8223437 100644 --- a/server/utils/database.ts +++ b/server/utils/database.ts @@ -1,19 +1,99 @@ -import mysql, { type Connection } from "mysql2/promise"; +import { PrismaClient, Prisma } from "@prisma/client"; -const connectionOptions: mysql.ConnectionOptions = { - host: process.env.DB_HOST, - port: Number(process.env.DB_PORT), - user: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_SCHEMA, - decimalNumbers: true, - supportBigNumbers: true, -}; +import { type pageData } from "./baaPageParsing"; -export const database = - await mysql.createConnection(connectionOptions) as Connection & { - new: (localConnectionOptions?: mysql.ConnectionOptions | undefined) => Promise - }; -database.new = (localConnectionOptions?: mysql.ConnectionOptions | undefined) => { return mysql.createConnection({ ...localConnectionOptions, ...connectionOptions }); }; +type model = PrismaClient[Uncapitalize]; -export type data = [T[], mysql.FieldPacket[]]; +function getBeforeParameters( + pageData: pageData, + fetchArgs: Prisma.Args, +) { + const _fetchArgs = Object.assign({}, fetchArgs); + return Object.assign(_fetchArgs, { + take: pageData.count, + orderBy: [ + { id: "desc" }, + ], + where: { + AND: [ + { + id: { + _lt: pageData.id, + }, + }, + fetchArgs.where, + ], + }, + }); +} + +function getAfterParameters( + pageData: pageData, + fetchArgs: Prisma.Args, +) { + const _fetchArgs = Object.assign({}, fetchArgs); + return Object.assign(_fetchArgs, { + take: pageData.count, + orderBy: [ + { id: "desc" }, + ], + where: { + AND: [ + { + id: { + _gt: pageData.id, + }, + }, + fetchArgs.where, + ], + }, + }); +} + +function getNullParameters( + pageData: pageData, + fetchArgs: Prisma.Args, +) { + const _fetchArgs = Object.assign({}, fetchArgs); + return Object.assign(_fetchArgs, { + take: pageData.count, + orderBy: [ + { id: "desc" }, + ], + }); +} + +export const database = new PrismaClient().$extends({ + model: { + $allModels: { + findPaginated( + this: T, + pageData: pageData, + fetchArgs: Prisma.Exact>, + ): Promise> { + const context = Prisma.getExtensionContext(this) as any; + switch (pageData.type) { + case "before": + return context.findMany(getBeforeParameters(pageData, fetchArgs)); + case "after": + return context.findMany(getAfterParameters(pageData, fetchArgs)); + case "around": + return Promise.all([ + context.findMany(getBeforeParameters({ + type: "before", + id: pageData.id, + count: Math.ceil(pageData.count), + }, fetchArgs)), + context.findMany(getAfterParameters({ + type: "after", + id: pageData.id, + count: Math.floor(pageData.count), + }, fetchArgs)), + ]).then(rv => rv.flat()) as Promise; + case null: + return context.findMany(getNullParameters(pageData, fetchArgs)); + } + }, + }, + }, +}); diff --git a/server/utils/getRequestingUser.ts b/server/utils/getRequestingUser.ts index 02f5b0b..13ed3f9 100644 --- a/server/utils/getRequestingUser.ts +++ b/server/utils/getRequestingUser.ts @@ -1,25 +1,30 @@ import { getCookie, H3Event } from "h3"; -import { database, type data } from "./database"; -import { type user } from "~/utils/types/database"; +import { database } from "./database"; import { createError } from "#imports"; export default async function getRequestingUser(e: H3Event) { const cookie = getCookie(e, "token"); - const [[user]] = await database.query( - ["SELECT", - "CONVERT(`users`.`id`, CHAR) as `id`,", - "`users`.`username`,", - "`users`.`email`,", - "`users`.`display_name`", - "FROM", - "`sessions`", - "LEFT JOIN `users` ON `sessions`.`user` = `users`.`id`", - "WHERE `sessions`.`id` = ?", - ].join(" "), - [cookie], - ) as data; + if (!cookie) throw createError("User not found"); + const { user } = await database.session.findUnique({ + where: { + id: BigInt(cookie), + }, + select: { + user: { + select: { + display_name: true, + email: true, + id: true, + username: true, + }, + }, + }, + }).then((e) => { + if (e === null) throw createError("User not found"); + return e; + }); if (!user) throw createError("User not found"); return user; diff --git a/server/utils/prismaToWeb.ts b/server/utils/prismaToWeb.ts new file mode 100644 index 0000000..d307e5d --- /dev/null +++ b/server/utils/prismaToWeb.ts @@ -0,0 +1,44 @@ +import { Decimal } from "@prisma/client/runtime/library"; + +type func = (...args: any[]) => any | Function; + +export type replaceJsonUnparsableToString = + T extends Array ? Array> + : { + [K in keyof T]: + T[K] extends null ? null + : T[K] extends func ? never + : T[K] extends Decimal ? `${number}` + : T[K] extends Array ? Array> + : T[K] extends object ? replaceJsonUnparsableToString + : T[K] extends bigint ? `${bigint}` + : T[K] + }; + +type exactToInterface = (...args: any[]) => any extends Function ? true : false; + +function arrayPrismaToWeb(array: Array) { + return array.reduce( + (pV, cV) => { + pV.push(prismaToWeb(cV)); + return pV; + }, + [] as Array>, + ); +} + +export function prismaToWeb(ivalue: T): replaceJsonUnparsableToString { + const rvalue: any = ivalue instanceof Array ? [] : {}; + + for (const i in ivalue) { + const current = ivalue[i]; + if (current === null) rvalue[i] = null; + else if (typeof current === 'function') continue; + else if (current instanceof Decimal) rvalue[i] = current.toString(); + else if (current instanceof Array) rvalue[i] = arrayPrismaToWeb(current); + else if (typeof current === 'object') rvalue[i] = prismaToWeb(current); + else if (typeof current === 'bigint') rvalue[i] = current.toString(); + else rvalue[i] = current; + } + return rvalue; +} diff --git a/utils/types/database.ts b/utils/types/database.ts deleted file mode 100644 index 28a4ddc..0000000 --- a/utils/types/database.ts +++ /dev/null @@ -1,99 +0,0 @@ -export interface client { - id: string; - name: string | null; - address: string | null; - phone: string | null; - email: string | null; -} - -export interface user { - id: string; - username: string; - email: string; - display_name?: string; -} - -export interface session { - id: string; - user: string; - expiry_date: string; -} - -export interface imported_product { - id: string; - // order: string, - name?: string; - link: string; - price_imported: string; - price: string; -} - -export interface offer { - id: string; - name: string; - description?: string; - recommended_price?: string; -} - -export interface work { - id: string; - // order: string, - offer: string | offer; - price: string; - notes: string; - is_fulfilled: boolean; -} - -export interface order { - imported_products: Array<{ - id: string; - name: string | null; - link: string; - price: string; - price_imported: string; - }>; - work: { - id: string; - offer: offer; - price: number; - notes: string | null; - is_fulfilled: 0 | 1; - }[]; - id: string; - client: string; - user: string; - is_draft: 0 | 1; - value: number; -} - -export interface orderSummary { - id: string; - client: string; - user: string; - is_draft: 0 | 1; - value: string; - imported_products_count: number; - work_count: number; -} - -export interface work_template { - id: string; - // order_template: string, - offer: string | offer; - price: string; - notes?: string; -} - -export interface order_template { - id: string; - name: string; - description?: string; -} - -// 1 is true, 0 is false -export type Dboolean = - | boolean - | 0 // false - | 1; // true - -export type Dnumber = number | `${number}`;