diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1b29ae8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 88 diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 04fd192..7b19ebe 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,23 +1,13 @@ module.exports = { root: true, + parser: "@typescript-eslint/parser", extends: [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:svelte/recommended", - "prettier", ], - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint"], - parserOptions: { - sourceType: "module", - ecmaVersion: 2020, - extraFileExtensions: [".svelte"], - }, - env: { - browser: true, - es2017: true, - node: true, - }, + plugins: ["@typescript-eslint", "no-relative-import-paths"], + ignorePatterns: ["*.cjs"], overrides: [ { files: ["*.svelte"], @@ -27,4 +17,22 @@ module.exports = { }, }, ], + settings: {}, + parserOptions: { + sourceType: "module", + ecmaVersion: 2020, + }, + env: { + browser: true, + es2017: true, + node: true, + }, + rules: { + "no-relative-import-paths/no-relative-import-paths": [ + "warn", + { allowSameFolder: true }, + ], + "no-console": "warn", + "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }], + }, }; diff --git a/package.json b/package.json index 5d37b6d..641e9ba 100644 --- a/package.json +++ b/package.json @@ -7,24 +7,27 @@ "dev": "vite dev", "build": "vite build", "preview": "vite preview", - "test": "npm run test:integration && npm run test:unit", + "test": "vitest --run && vitest --config vitest.config.integration.ts --run", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "lint": "prettier --check . && eslint .", "format": "prettier --write .", - "test:integration": "playwright test", - "test:unit": "vitest" + "test:unit": "vitest", + "test:integration": "vitest --config vitest.config.integration.ts", + "test:e2e": "playwright test" }, "dependencies": { "@auth/core": "^0.18.4", - "@auth/prisma-adapter": "^1.0.9", - "@auth/sveltekit": "^0.3.15", - "@prisma/client": "^5.7.0" + "@auth/sveltekit": "^0.5.0", + "@mdi/js": "^7.3.67", + "@prisma/client": "^5.7.0", + "zod": "^3.22.4" }, "devDependencies": { "@playwright/test": "^1.40.1", - "@sveltejs/adapter-auto": "^2.1.1", - "@sveltejs/kit": "^1.27.7", + "@sveltejs/adapter-node": "^2.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", "@tailwindcss/typography": "^0.5.10", "@types/node": "^20.10.5", "@typescript-eslint/eslint-plugin": "^6.13.2", @@ -33,9 +36,10 @@ "daisyui": "^4.4.19", "eslint": "^8.55.0", "eslint-config-prettier": "^9.1.0", + "eslint-plugin-no-relative-import-paths": "^1.5.3", "eslint-plugin-svelte": "^2.35.1", - "postcss": "^8.4.32", - "postcss-load-config": "^4.0.2", + "postcss-import": "^15.1.0", + "postcss-nesting": "^12.0.1", "prettier": "^3.1.0", "prettier-plugin-svelte": "^3.1.2", "prisma": "^5.7.0", @@ -44,8 +48,8 @@ "tailwindcss": "^3.3.6", "tslib": "^2.6.2", "typescript": "^5.3.3", - "vite": "^4.5.1", - "vitest": "^0.32.4" + "vite": "^5.0.0", + "vitest": "^1.0.0" }, "type": "module" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c37e2d0..3caa6c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,26 +8,32 @@ dependencies: '@auth/core': specifier: ^0.18.4 version: 0.18.4 - '@auth/prisma-adapter': - specifier: ^1.0.9 - version: 1.0.9(@prisma/client@5.7.0) '@auth/sveltekit': - specifier: ^0.3.15 - version: 0.3.15(@sveltejs/kit@1.27.7)(svelte@4.2.8) + specifier: ^0.5.0 + version: 0.5.0(@sveltejs/kit@2.0.4)(svelte@4.2.8) + '@mdi/js': + specifier: ^7.3.67 + version: 7.3.67 '@prisma/client': specifier: ^5.7.0 version: 5.7.0(prisma@5.7.0) + zod: + specifier: ^3.22.4 + version: 3.22.4 devDependencies: '@playwright/test': specifier: ^1.40.1 version: 1.40.1 - '@sveltejs/adapter-auto': - specifier: ^2.1.1 - version: 2.1.1(@sveltejs/kit@1.27.7) + '@sveltejs/adapter-node': + specifier: ^2.0.0 + version: 2.0.1(@sveltejs/kit@2.0.4) '@sveltejs/kit': - specifier: ^1.27.7 - version: 1.27.7(svelte@4.2.8)(vite@4.5.1) + specifier: ^2.0.0 + version: 2.0.4(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.10) + '@sveltejs/vite-plugin-svelte': + specifier: ^3.0.0 + version: 3.0.1(svelte@4.2.8)(vite@5.0.10) '@tailwindcss/typography': specifier: ^0.5.10 version: 0.5.10(tailwindcss@3.3.6) @@ -52,15 +58,18 @@ devDependencies: eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.55.0) + eslint-plugin-no-relative-import-paths: + specifier: ^1.5.3 + version: 1.5.3 eslint-plugin-svelte: specifier: ^2.35.1 version: 2.35.1(eslint@8.55.0)(svelte@4.2.8) - postcss: - specifier: ^8.4.32 - version: 8.4.32 - postcss-load-config: - specifier: ^4.0.2 - version: 4.0.2(postcss@8.4.32) + postcss-import: + specifier: ^15.1.0 + version: 15.1.0(postcss@8.4.32) + postcss-nesting: + specifier: ^12.0.1 + version: 12.0.2(postcss@8.4.32) prettier: specifier: ^3.1.0 version: 3.1.0 @@ -75,7 +84,7 @@ devDependencies: version: 4.2.8 svelte-check: specifier: ^3.6.2 - version: 3.6.2(postcss-load-config@4.0.2)(postcss@8.4.32)(svelte@4.2.8) + version: 3.6.2(postcss@8.4.32)(svelte@4.2.8) tailwindcss: specifier: ^3.3.6 version: 3.3.6 @@ -86,11 +95,11 @@ devDependencies: specifier: ^5.3.3 version: 5.3.3 vite: - specifier: ^4.5.1 - version: 4.5.1(@types/node@20.10.5) + specifier: ^5.0.0 + version: 5.0.10(@types/node@20.10.5) vitest: - specifier: ^0.32.4 - version: 0.32.4 + specifier: ^1.0.0 + version: 1.1.0(@types/node@20.10.5) packages: @@ -127,200 +136,223 @@ packages: preact-render-to-string: 5.2.3(preact@10.11.3) dev: false - /@auth/prisma-adapter@1.0.9(@prisma/client@5.7.0): - resolution: {integrity: sha512-/GiOE/7kVggulEtwnoRv9WDTjz4pDRpB78DvYfOYmVyxk+0bIDxAfaFE5qk2lsBtWOyYPUSgfLkO89ZM93qdqA==} + /@auth/core@0.19.0: + resolution: {integrity: sha512-BkFg2SoNftMN6A2Sn2g1lLFLTO74qMtFKsZmSCEF9d1csqSaEXIv50k6OrfniODWi5tZP8bcfSxGodv75khlOA==} peerDependencies: - '@prisma/client': '>=2.26.0 || >=3 || >=4 || >=5' + nodemailer: ^6.8.0 + peerDependenciesMeta: + nodemailer: + optional: true dependencies: - '@auth/core': 0.18.4 - '@prisma/client': 5.7.0(prisma@5.7.0) - transitivePeerDependencies: - - nodemailer + '@panva/hkdf': 1.1.1 + '@types/cookie': 0.6.0 + cookie: 0.6.0 + jose: 5.1.3 + oauth4webapi: 2.4.0 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) dev: false - /@auth/sveltekit@0.3.15(@sveltejs/kit@1.27.7)(svelte@4.2.8): - resolution: {integrity: sha512-GlnqV6m8/wXaeUntwy+X2yBKG134WrIdpGCZuK8RaI3iHsy883ix9Hbgkp6oVsHGYcIUeuLgGA6BOsMuJL6/Ag==} + /@auth/sveltekit@0.5.0(@sveltejs/kit@2.0.4)(svelte@4.2.8): + resolution: {integrity: sha512-oSfeevQE06KSpA2gucwDarXLzJ6rA/s9XWsH3cq6vrEgxxgeFS+t3CB2QuJHzoBg3UXRkg6EBpxrPY/9tCkYrA==} peerDependencies: - '@sveltejs/kit': ^1.0.0 + '@sveltejs/kit': ^1.0.0 || ^2.0.0 svelte: ^3.54.0 || ^4.0.0 dependencies: - '@auth/core': 0.18.4 - '@sveltejs/kit': 1.27.7(svelte@4.2.8)(vite@4.5.1) + '@auth/core': 0.19.0 + '@sveltejs/kit': 2.0.4(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.10) svelte: 4.2.8 transitivePeerDependencies: - nodemailer dev: false - /@esbuild/android-arm64@0.18.20: - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + /@csstools/selector-specificity@3.0.1(postcss-selector-parser@6.0.13): + resolution: {integrity: sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss-selector-parser: ^6.0.13 + dependencies: + postcss-selector-parser: 6.0.13 + dev: true + + /@esbuild/aix-ppc64@0.19.10: + resolution: {integrity: sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + optional: true + + /@esbuild/android-arm64@0.19.10: + resolution: {integrity: sha512-1X4CClKhDgC3by7k8aOWZeBXQX8dHT5QAMCAQDArCLaYfkppoARvh0fit3X2Qs+MXDngKcHv6XXyQCpY0hkK1Q==} engines: {node: '>=12'} cpu: [arm64] os: [android] requiresBuild: true optional: true - /@esbuild/android-arm@0.18.20: - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + /@esbuild/android-arm@0.19.10: + resolution: {integrity: sha512-7W0bK7qfkw1fc2viBfrtAEkDKHatYfHzr/jKAHNr9BvkYDXPcC6bodtm8AyLJNNuqClLNaeTLuwURt4PRT9d7w==} engines: {node: '>=12'} cpu: [arm] os: [android] requiresBuild: true optional: true - /@esbuild/android-x64@0.18.20: - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + /@esbuild/android-x64@0.19.10: + resolution: {integrity: sha512-O/nO/g+/7NlitUxETkUv/IvADKuZXyH4BHf/g/7laqKC4i/7whLpB0gvpPc2zpF0q9Q6FXS3TS75QHac9MvVWw==} engines: {node: '>=12'} cpu: [x64] os: [android] requiresBuild: true optional: true - /@esbuild/darwin-arm64@0.18.20: - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + /@esbuild/darwin-arm64@0.19.10: + resolution: {integrity: sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /@esbuild/darwin-x64@0.18.20: - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + /@esbuild/darwin-x64@0.19.10: + resolution: {integrity: sha512-alfGtT+IEICKtNE54hbvPg13xGBe4GkVxyGWtzr+yHO7HIiRJppPDhOKq3zstTcVf8msXb/t4eavW3jCDpMSmA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] requiresBuild: true optional: true - /@esbuild/freebsd-arm64@0.18.20: - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + /@esbuild/freebsd-arm64@0.19.10: + resolution: {integrity: sha512-dMtk1wc7FSH8CCkE854GyGuNKCewlh+7heYP/sclpOG6Cectzk14qdUIY5CrKDbkA/OczXq9WesqnPl09mj5dg==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] requiresBuild: true optional: true - /@esbuild/freebsd-x64@0.18.20: - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + /@esbuild/freebsd-x64@0.19.10: + resolution: {integrity: sha512-G5UPPspryHu1T3uX8WiOEUa6q6OlQh6gNl4CO4Iw5PS+Kg5bVggVFehzXBJY6X6RSOMS8iXDv2330VzaObm4Ag==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] requiresBuild: true optional: true - /@esbuild/linux-arm64@0.18.20: - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + /@esbuild/linux-arm64@0.19.10: + resolution: {integrity: sha512-QxaouHWZ+2KWEj7cGJmvTIHVALfhpGxo3WLmlYfJ+dA5fJB6lDEIg+oe/0//FuyVHuS3l79/wyBxbHr0NgtxJQ==} engines: {node: '>=12'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-arm@0.18.20: - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + /@esbuild/linux-arm@0.19.10: + resolution: {integrity: sha512-j6gUW5aAaPgD416Hk9FHxn27On28H4eVI9rJ4az7oCGTFW48+LcgNDBN+9f8rKZz7EEowo889CPKyeaD0iw9Kg==} engines: {node: '>=12'} cpu: [arm] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-ia32@0.18.20: - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + /@esbuild/linux-ia32@0.19.10: + resolution: {integrity: sha512-4ub1YwXxYjj9h1UIZs2hYbnTZBtenPw5NfXCRgEkGb0b6OJ2gpkMvDqRDYIDRjRdWSe/TBiZltm3Y3Q8SN1xNg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-loong64@0.18.20: - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + /@esbuild/linux-loong64@0.19.10: + resolution: {integrity: sha512-lo3I9k+mbEKoxtoIbM0yC/MZ1i2wM0cIeOejlVdZ3D86LAcFXFRdeuZmh91QJvUTW51bOK5W2BznGNIl4+mDaA==} engines: {node: '>=12'} cpu: [loong64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-mips64el@0.18.20: - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + /@esbuild/linux-mips64el@0.19.10: + resolution: {integrity: sha512-J4gH3zhHNbdZN0Bcr1QUGVNkHTdpijgx5VMxeetSk6ntdt+vR1DqGmHxQYHRmNb77tP6GVvD+K0NyO4xjd7y4A==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-ppc64@0.18.20: - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + /@esbuild/linux-ppc64@0.19.10: + resolution: {integrity: sha512-tgT/7u+QhV6ge8wFMzaklOY7KqiyitgT1AUHMApau32ZlvTB/+efeCtMk4eXS+uEymYK249JsoiklZN64xt6oQ==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-riscv64@0.18.20: - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + /@esbuild/linux-riscv64@0.19.10: + resolution: {integrity: sha512-0f/spw0PfBMZBNqtKe5FLzBDGo0SKZKvMl5PHYQr3+eiSscfJ96XEknCe+JoOayybWUFQbcJTrk946i3j9uYZA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-s390x@0.18.20: - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + /@esbuild/linux-s390x@0.19.10: + resolution: {integrity: sha512-pZFe0OeskMHzHa9U38g+z8Yx5FNCLFtUnJtQMpwhS+r4S566aK2ci3t4NCP4tjt6d5j5uo4h7tExZMjeKoehAA==} engines: {node: '>=12'} cpu: [s390x] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-x64@0.18.20: - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + /@esbuild/linux-x64@0.19.10: + resolution: {integrity: sha512-SpYNEqg/6pZYoc+1zLCjVOYvxfZVZj6w0KROZ3Fje/QrM3nfvT2llI+wmKSrWuX6wmZeTapbarvuNNK/qepSgA==} engines: {node: '>=12'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@esbuild/netbsd-x64@0.18.20: - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + /@esbuild/netbsd-x64@0.19.10: + resolution: {integrity: sha512-ACbZ0vXy9zksNArWlk2c38NdKg25+L9pr/mVaj9SUq6lHZu/35nx2xnQVRGLrC1KKQqJKRIB0q8GspiHI3J80Q==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] requiresBuild: true optional: true - /@esbuild/openbsd-x64@0.18.20: - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + /@esbuild/openbsd-x64@0.19.10: + resolution: {integrity: sha512-PxcgvjdSjtgPMiPQrM3pwSaG4kGphP+bLSb+cihuP0LYdZv1epbAIecHVl5sD3npkfYBZ0ZnOjR878I7MdJDFg==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] requiresBuild: true optional: true - /@esbuild/sunos-x64@0.18.20: - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + /@esbuild/sunos-x64@0.19.10: + resolution: {integrity: sha512-ZkIOtrRL8SEJjr+VHjmW0znkPs+oJXhlJbNwfI37rvgeMtk3sxOQevXPXjmAPZPigVTncvFqLMd+uV0IBSEzqA==} engines: {node: '>=12'} cpu: [x64] os: [sunos] requiresBuild: true optional: true - /@esbuild/win32-arm64@0.18.20: - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + /@esbuild/win32-arm64@0.19.10: + resolution: {integrity: sha512-+Sa4oTDbpBfGpl3Hn3XiUe4f8TU2JF7aX8cOfqFYMMjXp6ma6NJDztl5FDG8Ezx0OjwGikIHw+iA54YLDNNVfw==} engines: {node: '>=12'} cpu: [arm64] os: [win32] requiresBuild: true optional: true - /@esbuild/win32-ia32@0.18.20: - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + /@esbuild/win32-ia32@0.19.10: + resolution: {integrity: sha512-EOGVLK1oWMBXgfttJdPHDTiivYSjX6jDNaATeNOaCOFEVcfMjtbx7WVQwPSE1eIfCp/CaSF2nSrDtzc4I9f8TQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] requiresBuild: true optional: true - /@esbuild/win32-x64@0.18.20: - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + /@esbuild/win32-x64@0.19.10: + resolution: {integrity: sha512-whqLG6Sc70AbU73fFYvuYzaE4MNMBIlR1Y/IrUeOXFrWHxBEjjbZaQ3IXIQS8wJdAzue2GwYZCjOrgrU1oUHoA==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -364,10 +396,6 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@fastify/busboy@2.1.0: - resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} - engines: {node: '>=14'} - /@humanwhocodes/config-array@0.11.13: resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} engines: {node: '>=10.10.0'} @@ -420,6 +448,10 @@ packages: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + /@mdi/js@7.3.67: + resolution: {integrity: sha512-MnRjknFqpTC6FifhGHjZ0+QYq2bAkZFQqIj8JA2AdPZbBxUvr8QSgB2yPAJ8/ob/XkR41xlg5majDR3c1JP1hw==} + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -496,31 +528,190 @@ packages: dependencies: '@prisma/debug': 5.7.0 + /@rollup/plugin-commonjs@25.0.7(rollup@4.9.1): + resolution: {integrity: sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.9.1) + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 8.1.0 + is-reference: 1.2.1 + magic-string: 0.30.5 + rollup: 4.9.1 + dev: true + + /@rollup/plugin-json@6.1.0(rollup@4.9.1): + resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.9.1) + rollup: 4.9.1 + dev: true + + /@rollup/plugin-node-resolve@15.2.3(rollup@4.9.1): + resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.9.1) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-builtin-module: 3.2.1 + is-module: 1.0.0 + resolve: 1.22.8 + rollup: 4.9.1 + dev: true + + /@rollup/pluginutils@5.1.0(rollup@4.9.1): + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + rollup: 4.9.1 + dev: true + + /@rollup/rollup-android-arm-eabi@4.9.1: + resolution: {integrity: sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig==} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-android-arm64@4.9.1: + resolution: {integrity: sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ==} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-arm64@4.9.1: + resolution: {integrity: sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-x64@4.9.1: + resolution: {integrity: sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og==} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.9.1: + resolution: {integrity: sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q==} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.9.1: + resolution: {integrity: sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.9.1: + resolution: {integrity: sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.9.1: + resolution: {integrity: sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.9.1: + resolution: {integrity: sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.9.1: + resolution: {integrity: sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.9.1: + resolution: {integrity: sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g==} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.9.1: + resolution: {integrity: sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg==} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.9.1: + resolution: {integrity: sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA==} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true - /@sveltejs/adapter-auto@2.1.1(@sveltejs/kit@1.27.7): - resolution: {integrity: sha512-nzi6x/7/3Axh5VKQ8Eed3pYxastxoa06Y/bFhWb7h3Nu+nGRVxKAy3+hBJgmPCwWScy8n0TsstZjSVKfyrIHkg==} + /@sveltejs/adapter-node@2.0.1(@sveltejs/kit@2.0.4): + resolution: {integrity: sha512-qobmfQ6f71yKlQSufLjtPGCFjuUqy0Y5QQySTlyqO3CEUI/F9dkrsgOUwYtEE0OBjZV+oIJds4vrJ78e2Dl/5w==} peerDependencies: - '@sveltejs/kit': ^1.0.0 + '@sveltejs/kit': ^2.0.0 dependencies: - '@sveltejs/kit': 1.27.7(svelte@4.2.8)(vite@4.5.1) - import-meta-resolve: 4.0.0 + '@rollup/plugin-commonjs': 25.0.7(rollup@4.9.1) + '@rollup/plugin-json': 6.1.0(rollup@4.9.1) + '@rollup/plugin-node-resolve': 15.2.3(rollup@4.9.1) + '@sveltejs/kit': 2.0.4(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.10) + rollup: 4.9.1 dev: true - /@sveltejs/kit@1.27.7(svelte@4.2.8)(vite@4.5.1): - resolution: {integrity: sha512-AzXYDoYt42clCBwLF9GTHsXyg2DFR31Ncyt8yxu8Aw4tgB433V+w+hcr1RTfAN9zKW2J2PY9FMQ8FoX/4Vw8CA==} - engines: {node: ^16.14 || >=18} + /@sveltejs/kit@2.0.4(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.10): + resolution: {integrity: sha512-pqUOo1Slp2fybNjTouF7qgeN815hO2boRCNO1Z7/XtXu5IPNg9OSwI485qmQ0GUU2v2NmA0XZciFHGrgzOixyw==} + engines: {node: '>=18.13'} hasBin: true requiresBuild: true peerDependencies: - svelte: ^3.54.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - vite: ^4.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 dependencies: - '@sveltejs/vite-plugin-svelte': 2.5.3(svelte@4.2.8)(vite@4.5.1) - '@types/cookie': 0.5.4 - cookie: 0.5.0 + '@sveltejs/vite-plugin-svelte': 3.0.1(svelte@4.2.8)(vite@5.0.10) + '@types/cookie': 0.6.0 + cookie: 0.6.0 devalue: 4.3.2 esm-env: 1.0.0 kleur: 4.1.5 @@ -531,42 +722,39 @@ packages: sirv: 2.0.3 svelte: 4.2.8 tiny-glob: 0.2.9 - undici: 5.26.5 - vite: 4.5.1(@types/node@20.10.5) - transitivePeerDependencies: - - supports-color + vite: 5.0.10(@types/node@20.10.5) - /@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.5.3)(svelte@4.2.8)(vite@4.5.1): - resolution: {integrity: sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==} - engines: {node: ^14.18.0 || >= 16} + /@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.10): + resolution: {integrity: sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==} + engines: {node: ^18.0.0 || >=20} peerDependencies: - '@sveltejs/vite-plugin-svelte': ^2.2.0 - svelte: ^3.54.0 || ^4.0.0 - vite: ^4.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.0 dependencies: - '@sveltejs/vite-plugin-svelte': 2.5.3(svelte@4.2.8)(vite@4.5.1) + '@sveltejs/vite-plugin-svelte': 3.0.1(svelte@4.2.8)(vite@5.0.10) debug: 4.3.4 svelte: 4.2.8 - vite: 4.5.1(@types/node@20.10.5) + vite: 5.0.10(@types/node@20.10.5) transitivePeerDependencies: - supports-color - /@sveltejs/vite-plugin-svelte@2.5.3(svelte@4.2.8)(vite@4.5.1): - resolution: {integrity: sha512-erhNtXxE5/6xGZz/M9eXsmI7Pxa6MS7jyTy06zN3Ck++ldrppOnOlJwHHTsMC7DHDQdgUp4NAc4cDNQ9eGdB/w==} - engines: {node: ^14.18.0 || >= 16} + /@sveltejs/vite-plugin-svelte@3.0.1(svelte@4.2.8)(vite@5.0.10): + resolution: {integrity: sha512-CGURX6Ps+TkOovK6xV+Y2rn8JKa8ZPUHPZ/NKgCxAmgBrXReavzFl8aOSCj3kQ1xqT7yGJj53hjcV/gqwDAaWA==} + engines: {node: ^18.0.0 || >=20} peerDependencies: - svelte: ^3.54.0 || ^4.0.0 || ^5.0.0-next.0 - vite: ^4.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.0 dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 1.0.4(@sveltejs/vite-plugin-svelte@2.5.3)(svelte@4.2.8)(vite@4.5.1) + '@sveltejs/vite-plugin-svelte-inspector': 2.0.0(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.10) debug: 4.3.4 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.5 svelte: 4.2.8 svelte-hmr: 0.15.3(svelte@4.2.8) - vite: 4.5.1(@types/node@20.10.5) - vitefu: 0.2.5(vite@4.5.1) + vite: 5.0.10(@types/node@20.10.5) + vitefu: 0.2.5(vite@5.0.10) transitivePeerDependencies: - supports-color @@ -582,18 +770,8 @@ packages: tailwindcss: 3.3.6 dev: true - /@types/chai-subset@1.3.5: - resolution: {integrity: sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==} - dependencies: - '@types/chai': 4.3.11 - dev: true - - /@types/chai@4.3.11: - resolution: {integrity: sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==} - dev: true - - /@types/cookie@0.5.4: - resolution: {integrity: sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==} + /@types/cookie@0.6.0: + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -611,6 +789,10 @@ packages: resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} dev: true + /@types/resolve@1.20.2: + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + dev: true + /@types/semver@7.5.6: resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} dev: true @@ -750,38 +932,38 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@vitest/expect@0.32.4: - resolution: {integrity: sha512-m7EPUqmGIwIeoU763N+ivkFjTzbaBn0n9evsTOcde03ugy2avPs3kZbYmw3DkcH1j5mxhMhdamJkLQ6dM1bk/A==} + /@vitest/expect@1.1.0: + resolution: {integrity: sha512-9IE2WWkcJo2BR9eqtY5MIo3TPmS50Pnwpm66A6neb2hvk/QSLfPXBz2qdiwUOQkwyFuuXEUj5380CbwfzW4+/w==} dependencies: - '@vitest/spy': 0.32.4 - '@vitest/utils': 0.32.4 + '@vitest/spy': 1.1.0 + '@vitest/utils': 1.1.0 chai: 4.3.10 dev: true - /@vitest/runner@0.32.4: - resolution: {integrity: sha512-cHOVCkiRazobgdKLnczmz2oaKK9GJOw6ZyRcaPdssO1ej+wzHVIkWiCiNacb3TTYPdzMddYkCgMjZ4r8C0JFCw==} + /@vitest/runner@1.1.0: + resolution: {integrity: sha512-zdNLJ00pm5z/uhbWF6aeIJCGMSyTyWImy3Fcp9piRGvueERFlQFbUwCpzVce79OLm2UHk9iwaMSOaU9jVHgNVw==} dependencies: - '@vitest/utils': 0.32.4 - p-limit: 4.0.0 + '@vitest/utils': 1.1.0 + p-limit: 5.0.0 pathe: 1.1.1 dev: true - /@vitest/snapshot@0.32.4: - resolution: {integrity: sha512-IRpyqn9t14uqsFlVI2d7DFMImGMs1Q9218of40bdQQgMePwVdmix33yMNnebXcTzDU5eiV3eUsoxxH5v0x/IQA==} + /@vitest/snapshot@1.1.0: + resolution: {integrity: sha512-5O/wyZg09V5qmNmAlUgCBqflvn2ylgsWJRRuPrnHEfDNT6tQpQ8O1isNGgo+VxofISHqz961SG3iVvt3SPK/QQ==} dependencies: magic-string: 0.30.5 pathe: 1.1.1 pretty-format: 29.7.0 dev: true - /@vitest/spy@0.32.4: - resolution: {integrity: sha512-oA7rCOqVOOpE6rEoXuCOADX7Lla1LIa4hljI2MSccbpec54q+oifhziZIJXxlE/CvI2E+ElhBHzVu0VEvJGQKQ==} + /@vitest/spy@1.1.0: + resolution: {integrity: sha512-sNOVSU/GE+7+P76qYo+VXdXhXffzWZcYIPQfmkiRxaNCSPiLANvQx5Mx6ZURJ/ndtEkUJEpvKLXqAYTKEY+lTg==} dependencies: tinyspy: 2.2.0 dev: true - /@vitest/utils@0.32.4: - resolution: {integrity: sha512-Gwnl8dhd1uJ+HXrYyV0eRqfmk9ek1ASE/LWfTCuWMw+d07ogHqp4hEAV28NiecimK6UY9DpSEPh+pXBA5gtTBg==} + /@vitest/utils@1.1.0: + resolution: {integrity: sha512-z+s510fKmYz4Y41XhNs3vcuFTFhcij2YF7F8VQfMEYAAUfqQh0Zfg7+w9xdgFGhPf3tX3TicAe+8BDITk6ampQ==} dependencies: diff-sequences: 29.6.3 loupe: 2.3.7 @@ -903,6 +1085,12 @@ packages: concat-map: 0.0.1 dev: true + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -925,6 +1113,11 @@ packages: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} dev: true + /builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + dev: true + /cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -1011,18 +1204,17 @@ packages: engines: {node: '>= 6'} dev: true + /commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + dev: true + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} - engines: {node: '>= 0.6'} - /cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} - dev: false /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} @@ -1143,34 +1335,35 @@ packages: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} dev: true - /esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + /esbuild@0.19.10: + resolution: {integrity: sha512-S1Y27QGt/snkNYrRcswgRFqZjaTG5a5xM3EQo97uNBnH505pdzSNe/HLBq1v0RO7iK/ngdbhJB6mDAp0OK+iUA==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 + '@esbuild/aix-ppc64': 0.19.10 + '@esbuild/android-arm': 0.19.10 + '@esbuild/android-arm64': 0.19.10 + '@esbuild/android-x64': 0.19.10 + '@esbuild/darwin-arm64': 0.19.10 + '@esbuild/darwin-x64': 0.19.10 + '@esbuild/freebsd-arm64': 0.19.10 + '@esbuild/freebsd-x64': 0.19.10 + '@esbuild/linux-arm': 0.19.10 + '@esbuild/linux-arm64': 0.19.10 + '@esbuild/linux-ia32': 0.19.10 + '@esbuild/linux-loong64': 0.19.10 + '@esbuild/linux-mips64el': 0.19.10 + '@esbuild/linux-ppc64': 0.19.10 + '@esbuild/linux-riscv64': 0.19.10 + '@esbuild/linux-s390x': 0.19.10 + '@esbuild/linux-x64': 0.19.10 + '@esbuild/netbsd-x64': 0.19.10 + '@esbuild/openbsd-x64': 0.19.10 + '@esbuild/sunos-x64': 0.19.10 + '@esbuild/win32-arm64': 0.19.10 + '@esbuild/win32-ia32': 0.19.10 + '@esbuild/win32-x64': 0.19.10 /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -1200,6 +1393,10 @@ packages: eslint: 8.55.0 dev: true + /eslint-plugin-no-relative-import-paths@1.5.3: + resolution: {integrity: sha512-z7c7Km1U0zdLyPziWeRKSsN2mPaGaBHDjfXn98B8XjRIhFi2bPqduRYcxWih1kI5al5tQtiChXVmspLkB0wNsQ==} + dev: true + /eslint-plugin-svelte@2.35.1(eslint@8.55.0)(svelte@4.2.8): resolution: {integrity: sha512-IF8TpLnROSGy98Z3NrsKXWDSCbNY2ReHDcrYTuXZMbfX7VmESISR78TWgO9zdg4Dht1X8coub5jKwHzP0ExRug==} engines: {node: ^14.17.0 || >=16.0.0} @@ -1320,6 +1517,10 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: @@ -1330,6 +1531,21 @@ packages: engines: {node: '>=0.10.0'} dev: true + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -1429,6 +1645,11 @@ packages: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} dev: true + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1465,6 +1686,17 @@ packages: path-is-absolute: 1.0.1 dev: true + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: true + /globals@13.23.0: resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} engines: {node: '>=8'} @@ -1510,6 +1742,11 @@ packages: function-bind: 1.1.2 dev: true + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + /ignore@5.3.0: resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} engines: {node: '>= 4'} @@ -1523,10 +1760,6 @@ packages: resolve-from: 4.0.0 dev: true - /import-meta-resolve@4.0.0: - resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==} - dev: true - /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1550,6 +1783,13 @@ packages: binary-extensions: 2.2.0 dev: true + /is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + dependencies: + builtin-modules: 3.3.0 + dev: true + /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: @@ -1568,6 +1808,10 @@ packages: is-extglob: 2.1.1 dev: true + /is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + dev: true + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1578,11 +1822,22 @@ packages: engines: {node: '>=8'} dev: true + /is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + dependencies: + '@types/estree': 1.0.5 + dev: true + /is-reference@3.0.2: resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} dependencies: '@types/estree': 1.0.5 + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -1655,9 +1910,12 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /local-pkg@0.4.3: - resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} + dependencies: + mlly: 1.4.2 + pkg-types: 1.0.3 dev: true /locate-character@3.0.0: @@ -1711,6 +1969,10 @@ packages: /mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1724,6 +1986,11 @@ packages: picomatch: 2.3.1 dev: true + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1735,6 +2002,13 @@ packages: brace-expansion: 1.1.11 dev: true + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: true @@ -1797,6 +2071,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /npm-run-path@5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + /oauth4webapi@2.4.0: resolution: {integrity: sha512-ZWl8ov8HeGVyc9Icl1cag76HvIcDAp23eIIT+UVGir+dEu8BMgMlvZeZwqLVd0P8DqaumH4N+QLQXN69G1QjSA==} dev: false @@ -1817,6 +2098,13 @@ packages: wrappy: 1.0.2 dev: true + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + /optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -1836,9 +2124,9 @@ packages: yocto-queue: 0.1.0 dev: true - /p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} dependencies: yocto-queue: 1.0.0 dev: true @@ -1872,6 +2160,11 @@ packages: engines: {node: '>=8'} dev: true + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true @@ -2004,6 +2297,17 @@ packages: postcss-selector-parser: 6.0.13 dev: true + /postcss-nesting@12.0.2(postcss@8.4.32): + resolution: {integrity: sha512-63PpJHSeNs93S3ZUIyi+7kKx4JqOIEJ6QYtG3x+0qA4J03+4n0iwsyA1GAHyWxsHYljQS4/4ZK1o2sMi70b5wQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + dependencies: + '@csstools/selector-specificity': 3.0.1(postcss-selector-parser@6.0.13) + postcss: 8.4.32 + postcss-selector-parser: 6.0.13 + dev: true + /postcss-safe-parser@6.0.0(postcss@8.4.32): resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} engines: {node: '>=12.0'} @@ -2164,11 +2468,24 @@ packages: glob: 7.2.3 dev: true - /rollup@3.29.4: - resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} + /rollup@4.9.1: + resolution: {integrity: sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.9.1 + '@rollup/rollup-android-arm64': 4.9.1 + '@rollup/rollup-darwin-arm64': 4.9.1 + '@rollup/rollup-darwin-x64': 4.9.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.9.1 + '@rollup/rollup-linux-arm64-gnu': 4.9.1 + '@rollup/rollup-linux-arm64-musl': 4.9.1 + '@rollup/rollup-linux-riscv64-gnu': 4.9.1 + '@rollup/rollup-linux-x64-gnu': 4.9.1 + '@rollup/rollup-linux-x64-musl': 4.9.1 + '@rollup/rollup-win32-arm64-msvc': 4.9.1 + '@rollup/rollup-win32-ia32-msvc': 4.9.1 + '@rollup/rollup-win32-x64-msvc': 4.9.1 fsevents: 2.3.3 /run-parallel@1.2.0: @@ -2219,6 +2536,11 @@ packages: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} dev: true + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + /sirv@2.0.3: resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} engines: {node: '>= 10'} @@ -2261,6 +2583,11 @@ packages: ansi-regex: 5.0.1 dev: true + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + /strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -2305,7 +2632,7 @@ packages: engines: {node: '>= 0.4'} dev: true - /svelte-check@3.6.2(postcss-load-config@4.0.2)(postcss@8.4.32)(svelte@4.2.8): + /svelte-check@3.6.2(postcss@8.4.32)(svelte@4.2.8): resolution: {integrity: sha512-E6iFh4aUCGJLRz6QZXH3gcN/VFfkzwtruWSRmlKrLWQTiO6VzLsivR6q02WYLGNAGecV3EocqZuCDrC2uttZ0g==} hasBin: true peerDependencies: @@ -2318,7 +2645,7 @@ packages: picocolors: 1.0.0 sade: 1.8.1 svelte: 4.2.8 - svelte-preprocess: 5.1.1(postcss-load-config@4.0.2)(postcss@8.4.32)(svelte@4.2.8)(typescript@5.3.3) + svelte-preprocess: 5.1.1(postcss@8.4.32)(svelte@4.2.8)(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: - '@babel/core' @@ -2357,7 +2684,7 @@ packages: dependencies: svelte: 4.2.8 - /svelte-preprocess@5.1.1(postcss-load-config@4.0.2)(postcss@8.4.32)(svelte@4.2.8)(typescript@5.3.3): + /svelte-preprocess@5.1.1(postcss@8.4.32)(svelte@4.2.8)(typescript@5.3.3): resolution: {integrity: sha512-p/Dp4hmrBW5mrCCq29lEMFpIJT2FZsRlouxEc5qpbOmXRbaFs7clLs8oKPwD3xCFyZfv1bIhvOzpQkhMEVQdMw==} engines: {node: '>= 14.10.0'} requiresBuild: true @@ -2399,7 +2726,6 @@ packages: detect-indent: 6.1.0 magic-string: 0.27.0 postcss: 8.4.32 - postcss-load-config: 4.0.2(postcss@8.4.32) sorcery: 0.11.0 strip-indent: 3.0.0 svelte: 4.2.8 @@ -2482,8 +2808,8 @@ packages: resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} dev: true - /tinypool@0.5.0: - resolution: {integrity: sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ==} + /tinypool@0.8.1: + resolution: {integrity: sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==} engines: {node: '>=14.0.0'} dev: true @@ -2550,12 +2876,6 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - /undici@5.26.5: - resolution: {integrity: sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==} - engines: {node: '>=14.0'} - dependencies: - '@fastify/busboy': 2.1.0 - /update-browserslist-db@1.0.13(browserslist@4.22.2): resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true @@ -2577,17 +2897,16 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true - /vite-node@0.32.4(@types/node@20.10.5): - resolution: {integrity: sha512-L2gIw+dCxO0LK14QnUMoqSYpa9XRGnTTTDjW2h19Mr+GR0EFj4vx52W41gFXfMLqpA00eK4ZjOVYo1Xk//LFEw==} - engines: {node: '>=v14.18.0'} + /vite-node@1.1.0(@types/node@20.10.5): + resolution: {integrity: sha512-jV48DDUxGLEBdHCQvxL1mEh7+naVy+nhUUUaPAZLd3FJgXuxQiewHcfeZebbJ6onDqNGkP4r3MhQ342PRlG81Q==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: cac: 6.7.14 debug: 4.3.4 - mlly: 1.4.2 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.5.1(@types/node@20.10.5) + vite: 5.0.10(@types/node@20.10.5) transitivePeerDependencies: - '@types/node' - less @@ -2599,12 +2918,12 @@ packages: - terser dev: true - /vite@4.5.1(@types/node@20.10.5): - resolution: {integrity: sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==} - engines: {node: ^14.18.0 || >=16.0.0} + /vite@5.0.10(@types/node@20.10.5): + resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@types/node': '>= 14' + '@types/node': ^18.0.0 || >=20.0.0 less: '*' lightningcss: ^1.21.0 sass: '*' @@ -2628,13 +2947,13 @@ packages: optional: true dependencies: '@types/node': 20.10.5 - esbuild: 0.18.20 + esbuild: 0.19.10 postcss: 8.4.32 - rollup: 3.29.4 + rollup: 4.9.1 optionalDependencies: fsevents: 2.3.3 - /vitefu@0.2.5(vite@4.5.1): + /vitefu@0.2.5(vite@5.0.10): resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} peerDependencies: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 @@ -2642,24 +2961,24 @@ packages: vite: optional: true dependencies: - vite: 4.5.1(@types/node@20.10.5) + vite: 5.0.10(@types/node@20.10.5) - /vitest@0.32.4: - resolution: {integrity: sha512-3czFm8RnrsWwIzVDu/Ca48Y/M+qh3vOnF16czJm98Q/AN1y3B6PBsyV8Re91Ty5s7txKNjEhpgtGPcfdbh2MZg==} - engines: {node: '>=v14.18.0'} + /vitest@1.1.0(@types/node@20.10.5): + resolution: {integrity: sha512-oDFiCrw7dd3Jf06HoMtSRARivvyjHJaTxikFxuqJjO76U436PqlVw1uLn7a8OSPrhSfMGVaRakKpA2lePdw79A==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': ^1.0.0 + '@vitest/ui': ^1.0.0 happy-dom: '*' jsdom: '*' - playwright: '*' - safaridriver: '*' - webdriverio: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@types/node': + optional: true '@vitest/browser': optional: true '@vitest/ui': @@ -2668,36 +2987,28 @@ packages: optional: true jsdom: optional: true - playwright: - optional: true - safaridriver: - optional: true - webdriverio: - optional: true dependencies: - '@types/chai': 4.3.11 - '@types/chai-subset': 1.3.5 '@types/node': 20.10.5 - '@vitest/expect': 0.32.4 - '@vitest/runner': 0.32.4 - '@vitest/snapshot': 0.32.4 - '@vitest/spy': 0.32.4 - '@vitest/utils': 0.32.4 - acorn: 8.11.2 + '@vitest/expect': 1.1.0 + '@vitest/runner': 1.1.0 + '@vitest/snapshot': 1.1.0 + '@vitest/spy': 1.1.0 + '@vitest/utils': 1.1.0 acorn-walk: 8.3.1 cac: 6.7.14 chai: 4.3.10 debug: 4.3.4 - local-pkg: 0.4.3 + execa: 8.0.1 + local-pkg: 0.5.0 magic-string: 0.30.5 pathe: 1.1.1 picocolors: 1.0.0 std-env: 3.6.0 strip-literal: 1.3.0 tinybench: 2.5.1 - tinypool: 0.5.0 - vite: 4.5.1(@types/node@20.10.5) - vite-node: 0.32.4(@types/node@20.10.5) + tinypool: 0.8.1 + vite: 5.0.10(@types/node@20.10.5) + vite-node: 1.1.0(@types/node@20.10.5) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -2753,3 +3064,7 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} dev: true + + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + dev: false diff --git a/postcss.config.cjs b/postcss.config.cjs index e48cff5..f4cbe52 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -1,13 +1,8 @@ -const tailwindcss = require("tailwindcss"); -const autoprefixer = require("autoprefixer"); - -const config = { - plugins: [ - //Some plugins, like tailwindcss/nesting, need to run before Tailwind, - tailwindcss(), - //But others, like autoprefixer, need to run after, - autoprefixer, - ], +module.exports = { + plugins: { + "tailwindcss/nesting": "postcss-nesting", + "postcss-import": {}, + tailwindcss: {}, + autoprefixer: {}, + }, }; - -module.exports = config; diff --git a/prisma/migrations/20231220214721_init/migration.sql b/prisma/migrations/20240106164115_init/migration.sql similarity index 98% rename from prisma/migrations/20231220214721_init/migration.sql rename to prisma/migrations/20240106164115_init/migration.sql index 345da9a..1f198ba 100644 --- a/prisma/migrations/20231220214721_init/migration.sql +++ b/prisma/migrations/20240106164115_init/migration.sql @@ -59,6 +59,8 @@ CREATE TABLE "patients" ( CREATE TABLE "categories" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, + "color" TEXT, + "description" TEXT, CONSTRAINT "categories_pkey" PRIMARY KEY ("id") ); @@ -90,6 +92,7 @@ CREATE TABLE "entry_versions" ( CREATE TABLE "entry_execution" ( "id" SERIAL NOT NULL, "entryId" INTEGER NOT NULL, + "text" TEXT NOT NULL, "authorId" INTEGER NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 93bb103..aaf5e4d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -85,6 +85,8 @@ model Patient { model Category { id Int @id @default(autoincrement()) name String + color String? + description String? EntryVersion EntryVersion[] @@map("categories") @@ -128,6 +130,8 @@ model EntryExecution { entry Entry @relation(fields: [entryId], references: [id], onDelete: Cascade) entryId Int + text String + author User @relation(fields: [authorId], references: [id]) authorId Int diff --git a/run/.gitignore b/run/.gitignore new file mode 100644 index 0000000..f134994 --- /dev/null +++ b/run/.gitignore @@ -0,0 +1 @@ +postgres diff --git a/run/db_up.sh b/run/db_up.sh new file mode 100755 index 0000000..82ca3b6 --- /dev/null +++ b/run/db_up.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +DIR="$(cd "$(dirname "$0")" && pwd)" + +docker-compose up -d +echo 'Waiting for database to be ready...' +"$DIR/wait-for-it.sh" "localhost:5432" -- echo 'Database is ready!' + +# Create temporary test database +docker-compose exec -u 999:999 db sh -e -c 'dropdb -f --if-exists test; createdb test' +cd "$DIR/../" && DATABASE_URL="postgresql://postgres:1234@localhost:5432/test?schema=public" npx prisma migrate reset --force diff --git a/run/wait-for-it.sh b/run/wait-for-it.sh new file mode 100755 index 0000000..d990e0d --- /dev/null +++ b/run/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 6a7f114..d27a80e 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,5 +1,5 @@ -import { prisma } from "$lib/prisma"; -import { PrismaAdapter } from "@auth/prisma-adapter"; +import { prisma } from "$lib/server/prisma"; +import { PrismaAdapter } from "$lib/server/authAdapter"; import { SvelteKitAuth } from "@auth/sveltekit"; import Keycloak from "@auth/core/providers/keycloak"; import { @@ -19,7 +19,7 @@ const authorization: Handle = async ({ event, resolve }) => { const session = await event.locals.getSession(); if (!session) { const params = new URLSearchParams({ returnURL: event.url.pathname }); - throw redirect(303, "/login?" + params.toString()); + redirect(303, "/login?" + params.toString()); } } diff --git a/src/lib/components/ui/Icon.svelte b/src/lib/components/ui/Icon.svelte new file mode 100644 index 0000000..d391379 --- /dev/null +++ b/src/lib/components/ui/Icon.svelte @@ -0,0 +1,59 @@ + + + + {#if title}{title}{/if} + + 0} class:spinReverse={spin < 0}> + + + + + diff --git a/src/lib/components/ui/NavLink.svelte b/src/lib/components/ui/NavLink.svelte index 594f5d1..d678d75 100644 --- a/src/lib/components/ui/NavLink.svelte +++ b/src/lib/components/ui/NavLink.svelte @@ -2,12 +2,13 @@ import { page } from "$app/stores"; export let route: string; + export let href: string;
diff --git a/src/lib/server/authAdapter.ts b/src/lib/server/authAdapter.ts new file mode 100644 index 0000000..93cfedb --- /dev/null +++ b/src/lib/server/authAdapter.ts @@ -0,0 +1,83 @@ +import type { Adapter, AdapterAccount, AdapterUser } from "@auth/core/adapters"; +import type { Account, User, PrismaClient } from "@prisma/client"; + +/// Map database user (with numeric ID) to authjs user +function mapUser(user: User): AdapterUser { + return { + id: user.id.toString(), + name: user.name, + email: user.email!, + emailVerified: user.emailVerified, + }; +} + +function mapUserOpt(user: User | null | undefined): AdapterUser | null { + if (!user) return null; + return mapUser(user); +} + +function mapAccount(account: Account): AdapterAccount { + return { + userId: account.userId.toString(), + type: account.type as "oauth" | "oidc" | "email", + provider: account.provider, + providerAccountId: account.providerAccountId, + refresh_token: account.refresh_token ?? undefined, + access_token: account.access_token ?? undefined, + expires_at: account.expires_at ?? undefined, + token_type: account.token_type ?? undefined, + scope: account.scope ?? undefined, + id_token: account.id_token ?? undefined, + }; +} + +export function PrismaAdapter(p: PrismaClient): Adapter { + return { + createUser: async (data) => + mapUser( + await p.user.create({ + data: { + name: data.name, + email: data.email, + emailVerified: data.emailVerified, + }, + }) + ), + getUser: async (id) => + mapUserOpt(await p.user.findUnique({ where: { id: parseInt(id) } })), + getUserByEmail: async (email) => + mapUserOpt(await p.user.findUnique({ where: { email } })), + async getUserByAccount(provider_providerAccountId) { + const account = await p.account.findUnique({ + where: { provider_providerAccountId }, + select: { user: true }, + }); + return mapUserOpt(account?.user) ?? null; + }, + updateUser: async ({ id, ...data }) => + mapUser(await p.user.update({ where: { id: parseInt(id) }, data })), + deleteUser: async (id) => + mapUser(await p.user.delete({ where: { id: parseInt(id) } })), + linkAccount: async (data) => + mapAccount( + await p.account.create({ + data: { + userId: parseInt(data.userId), + type: data.type, + provider: data.provider, + providerAccountId: data.providerAccountId, + refresh_token: data.refresh_token, + access_token: data.access_token, + expires_at: data.expires_at, + token_type: data.token_type, + scope: data.scope, + id_token: data.id_token, + }, + }) + ), + unlinkAccount: (provider_providerAccountId) => + p.account.delete({ + where: { provider_providerAccountId }, + }) as unknown as AdapterAccount, + }; +} diff --git a/src/lib/prisma.ts b/src/lib/server/prisma.ts similarity index 92% rename from src/lib/prisma.ts rename to src/lib/server/prisma.ts index 8919c42..0ee6d72 100644 --- a/src/lib/prisma.ts +++ b/src/lib/server/prisma.ts @@ -5,7 +5,7 @@ const globalForPrisma = global as unknown as { prisma: PrismaClient }; export const prisma = globalForPrisma.prisma || new PrismaClient({ - log: ["query"], + // log: ["query"], }); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; diff --git a/src/lib/server/query/category.ts b/src/lib/server/query/category.ts new file mode 100644 index 0000000..8190038 --- /dev/null +++ b/src/lib/server/query/category.ts @@ -0,0 +1,17 @@ +import type { Category, CategoryNew } from "$lib/shared/model"; +import { ZCategoryNew } from "$lib/shared/model/validation"; +import { prisma } from "$lib/server/prisma"; + +export async function newCategory(category: CategoryNew): Promise { + const data = ZCategoryNew.parse(category); + const created = await prisma.category.create({ data }); + return created.id; +} + +export async function getCategory(id: number): Promise { + return prisma.category.findUniqueOrThrow({ where: { id } }); +} + +export async function getCategories(): Promise { + return prisma.category.findMany({ orderBy: { id: "asc" } }); +} diff --git a/src/lib/server/query/entry.ts b/src/lib/server/query/entry.ts new file mode 100644 index 0000000..1190f3d --- /dev/null +++ b/src/lib/server/query/entry.ts @@ -0,0 +1,259 @@ +import { prisma } from "$lib/server/prisma"; +import type { + EntriesRequest, + Entry, + EntryExecutionNew, + EntryNew, + EntryVersionNew, + Pagination, +} from "$lib/shared/model"; +import { + ZEntryExecutionNew, + ZEntryNew, + ZEntryVersionNew, +} from "$lib/shared/model/validation"; +import { mapEntry } from "./mapping"; +import { QueryBuilder } from "./util"; + +const USER_SELECT = { select: { id: true, name: true } }; + +export async function getEntry(id: number): Promise { + const entry = await prisma.entry.findUniqueOrThrow({ + where: { id }, + include: { + patient: { include: { room: { include: { station: true } } } }, + EntryVersion: { + include: { author: USER_SELECT, category: true }, + orderBy: { createdAt: "desc" }, + take: 1, + }, + EntryExecution: { + include: { author: USER_SELECT }, + orderBy: { createdAt: "desc" }, + take: 1, + }, + }, + }); + + return mapEntry(entry); +} + +export async function newEntry(authorId: number, entry: EntryNew): Promise { + const data = ZEntryNew.parse(entry); + const created = await prisma.entry.create({ + data: { + patientId: data.patientId, + EntryVersion: { + create: { + authorId, + ...data.version, + }, + }, + }, + }); + return created.id; +} + +export async function newEntryVersion( + authorId: number, + entryId: number, + version: EntryVersionNew +): Promise { + const data = ZEntryVersionNew.parse(version); + const created = await prisma.entryVersion.create({ + data: { + entryId, + authorId, + ...data, + }, + }); + return created.id; +} + +export async function newEntryExecution( + authorId: number, + entryId: number, + execution: EntryExecutionNew +): Promise { + const data = ZEntryExecutionNew.parse(execution); + const created = await prisma.entryExecution.create({ + data: { + entryId, + authorId, + ...data, + }, + }); + return created.id; +} + +export async function getEntries(req: EntriesRequest): Promise> { + const qb = new QueryBuilder( + `select + e.id, + e."createdAt", + ev.text, + ev."date", + ev.priority, + ev.id as "versionId", + ev."createdAt" as "versionCreatedAt", + vau.id as "versionAuthorId", + vau."name" as "versionAuthorName", + c.id as "categoryId", + c."name" as "categoryName", + c.color as "categoryColor", + ex.id as "executionId", + ex.text as "executionText", + ex."createdAt" as "executionCreatedAt", + xau.id as "executionAuthorId", + xau.name as "executionAuthorName", + p.id as "patientId", + p."firstName" as "patientFirstName", + p."lastName" as "patientLastName", + p.age as "patientAge", + p."createdAt" as "patientCreatedAt", + r.id as "roomId", + r."name" as "roomName", + s.id as "stationId", + s."name" as "stationName"`, + `from entries e +join entry_versions ev on + ev."entryId" = e.id + and ev.id = ( + select + id + from + entry_versions ev2 + where + ev2."entryId" = ev."entryId" + order by + ev2."createdAt" desc + limit 1) +join users vau on vau.id=ev."authorId" +left join categories c on c.id=ev."categoryId" +left join entry_execution ex on + ex."entryId" = e.id + and ex.id = ( + select + id + from + entry_execution ex2 + where + ex2."entryId" = ex."entryId" + order by + ex2."createdAt" desc + limit 1) +left join users xau on xau.id=ex."authorId" +join patients p on p.id = e."patientId" +join rooms r on r.id = p."roomId" +join stations s on s.id = r."stationId"` + ); + + // qb.addFilterIsNull("ex.id", req.filter?.done); + if (req.filter?.done === true) { + qb.addFilterClause("ex.id is not null"); + } else if (req.filter?.done === false) { + qb.addFilterClause("ex.id is null"); + } + + qb.addFilterList("xau.id", req.filter?.executor); + qb.addFilterList("c.id", req.filter?.category); + qb.addFilterList("p.id", req.filter?.patient); + qb.addFilterList("s.id", req.filter?.station); + qb.addFilterList("r.id", req.filter?.room); + qb.addFilter("ev.priority", req.filter?.priority); + + if (req.filter?.author) { + let author = req.filter?.author; + if (!Array.isArray(author)) { + author = [author]; + } + qb.addFilterClause( + `${qb.pvar()}::integer[] && (select array_agg(ev2."authorId") from entry_versions ev2 where ev2."entryId"=e.id)`, + author + ); + } + + qb.setOrderClause(`order by e."createdAt" desc`); + if (req.pagination) qb.setPagination(req.pagination); + + type RequestItem = { + id: number; + createdAt: Date; + text: string; + date: Date; + priority: boolean; + versionId: number; + versionCreatedAt: Date; + versionAuthorId: number; + versionAuthorName: string; + categoryId: number; + categoryName: string; + categoryColor: string; + executionId: number; + executionText: string; + executionCreatedAt: Date; + executionAuthorId: number; + executionAuthorName: string; + patientId: number; + patientFirstName: string; + patientLastName: string; + patientAge: number; + patientCreatedAt: Date; + roomId: number; + roomName: string; + stationId: number; + stationName: string; + }; + + const [res, countRes] = (await Promise.all([ + prisma.$queryRawUnsafe(qb.getQuery(), ...qb.getParams()), + prisma.$queryRawUnsafe(qb.getCountQuery(), ...qb.getCountParams()), + ])) as [RequestItem[], { count: bigint }[]]; + + const total = Number(countRes[0].count); + + const items: Entry[] = res.map((item) => { + return { + id: item.id, + patient: { + id: item.patientId, + firstName: item.patientFirstName, + lastName: item.patientLastName, + createdAt: item.patientCreatedAt, + age: item.patientAge, + room: { + id: item.roomId, + name: item.roomName, + station: { id: item.stationId, name: item.stationName }, + }, + }, + createdAt: item.createdAt, + currentVersion: { + id: item.versionId, + text: item.text, + date: item.date, + category: item.categoryId + ? { + id: item.categoryId, + name: item.categoryName, + color: item.categoryColor, + description: null, + } + : null, + priority: item.priority, + author: { id: item.versionAuthorId, name: item.versionAuthorName }, + createdAt: item.createdAt, + }, + execution: item.executionId + ? { + id: item.executionId, + author: { id: item.executionAuthorId, name: item.executionAuthorName }, + text: item.executionText, + createdAt: item.executionCreatedAt, + } + : null, + }; + }); + + return { items, offset: qb.getOffset(), total }; +} diff --git a/src/lib/server/query/index.ts b/src/lib/server/query/index.ts new file mode 100644 index 0000000..220256b --- /dev/null +++ b/src/lib/server/query/index.ts @@ -0,0 +1,5 @@ +export * from "./entry"; +export * from "./category"; +export * from "./patient"; +export * from "./user"; +export * from "./room"; diff --git a/src/lib/server/query/mapping.ts b/src/lib/server/query/mapping.ts new file mode 100644 index 0000000..0c3b3b2 --- /dev/null +++ b/src/lib/server/query/mapping.ts @@ -0,0 +1,83 @@ +import type { Entry, Patient, User, UserTag, Room } from "$lib/shared/model"; +import type { + Patient as DbPatient, + Room as DbRoom, + Station as DbStation, + User as DbUser, + Entry as DbEntry, + EntryVersion as DbEntryVersion, + EntryExecution as DbEntryExecution, + Category as DbCategory, +} from "@prisma/client"; + +type DbRoomLn = DbRoom & { station: DbStation }; +type DbPatientLn = DbPatient & { room: DbRoomLn | null }; +type DbEntryVersionLn = DbEntryVersion & { + category: DbCategory | null; + author: UserTag; +}; +type DbEntryExecutionLn = DbEntryExecution & { author: UserTag }; + +export function mapPatient(patient: DbPatientLn): Patient { + return { + id: patient.id, + firstName: patient.firstName, + lastName: patient.lastName, + createdAt: patient.createdAt, + age: patient.age, + room: patient.room + ? { + id: patient.room.id, + name: patient.room.name, + station: patient.room.station, + } + : null, + }; +} + +export function mapUser(user: DbUser): User { + return { id: user.id, name: user.name, email: user.email }; +} + +export function mapUserTag(user: DbUser): UserTag { + return { id: user.id, name: user.name }; +} + +export function mapRoom(room: DbRoomLn): Room { + return { id: room.id, name: room.name, station: room.station }; +} + +export function mapEntry( + entry: DbEntry & { + EntryVersion: DbEntryVersionLn[]; + EntryExecution: DbEntryExecutionLn[]; + patient: DbPatientLn; + } +): Entry { + const v = entry.EntryVersion[0]; + if (!v) throw new Error("no version associated with that entry"); + const x = entry.EntryExecution[0]; + + return { + id: entry.id, + patient: mapPatient(entry.patient), + createdAt: entry.createdAt, + currentVersion: { + id: v.id, + text: v.text, + date: v.date, + category: v.category, + priority: v.priority, + author: v.author, + createdAt: v.createdAt, + }, + execution: x + ? { + id: x.id, + author: x.author, + createdAt: x.createdAt, + text: x.text, + } + : null, + }; +} diff --git a/src/lib/server/query/patient.ts b/src/lib/server/query/patient.ts new file mode 100644 index 0000000..d1fef72 --- /dev/null +++ b/src/lib/server/query/patient.ts @@ -0,0 +1,55 @@ +import type { + Patient, + PatientNew, + PatientsRequest, + Pagination, +} from "$lib/shared/model"; +import { ZPatientNew } from "$lib/shared/model/validation"; +import { prisma } from "$lib/server/prisma"; +import { mapPatient } from "./mapping"; +import { PAGINATION_LIMIT } from "$lib/shared/constants"; +import { convertFilterList } from "./util"; + +export async function newPatient(patient: PatientNew): Promise { + const data = ZPatientNew.parse(patient); + const created = await prisma.patient.create({ data }); + return created.id; +} + +export async function getPatient(id: number): Promise { + const patient = await prisma.patient.findUniqueOrThrow({ + where: { id }, + include: { + room: { include: { station: true } }, + }, + }); + return mapPatient(patient); +} + +export async function getPatients(req: PatientsRequest): Promise> { + const offset = req.pagination?.offset || 0; + const where = { + roomId: convertFilterList(req.filter?.room), + room: { + stationId: convertFilterList(req.filter?.station), + }, + }; + const [patients, total] = await Promise.all([ + prisma.patient.findMany({ + where, + include: { + room: { include: { station: true } }, + }, + orderBy: { createdAt: "desc" }, + skip: offset, + take: req.pagination?.limit || PAGINATION_LIMIT, + }), + prisma.patient.count({ where }), + ]); + + return { + items: patients.map(mapPatient), + offset, + total, + }; +} diff --git a/src/lib/server/query/room.ts b/src/lib/server/query/room.ts new file mode 100644 index 0000000..4b9478e --- /dev/null +++ b/src/lib/server/query/room.ts @@ -0,0 +1,44 @@ +import type { RoomNew, Room, Station, StationNew } from "$lib/shared/model"; +import { ZRoomNew, ZStationNew } from "$lib/shared/model/validation"; +import { prisma } from "$lib/server/prisma"; +import { mapRoom } from "./mapping"; + +export async function newStation(station: StationNew): Promise { + const data = ZStationNew.parse(station); + const created = await prisma.station.create({ data }); + return created.id; +} + +export async function getStation(id: number): Promise { + return await prisma.station.findUniqueOrThrow({ where: { id } }); +} + +export async function getStations(): Promise { + return prisma.station.findMany({ orderBy: { id: "asc" } }); +} + +export async function newRoom(room: RoomNew): Promise { + const data = ZRoomNew.parse(room); + const created = await prisma.room.create({ data }); + return created.id; +} + +export async function getRoom(id: number): Promise { + const room = await prisma.room.findUniqueOrThrow({ + where: { id }, + include: { station: true }, + }); + return { + id: room.id, + name: room.name, + station: room.station, + }; +} + +export async function getRooms(): Promise { + const rooms = await prisma.room.findMany({ + include: { station: true }, + orderBy: { name: "asc" }, + }); + return rooms.map(mapRoom); +} diff --git a/src/lib/server/query/user.ts b/src/lib/server/query/user.ts new file mode 100644 index 0000000..600ef07 --- /dev/null +++ b/src/lib/server/query/user.ts @@ -0,0 +1,26 @@ +import type { Pagination, User, UserTag, UsersRequest } from "$lib/shared/model"; +import { prisma } from "$lib/server/prisma"; +import { mapUser, mapUserTag } from "./mapping"; +import { PAGINATION_LIMIT } from "$lib/shared/constants"; + +export async function getUser(id: number): Promise { + const user = await prisma.user.findUniqueOrThrow({ where: { id } }); + return mapUser(user); +} + +export async function getUsers(req: UsersRequest): Promise> { + const offset = req.pagination?.offset || 0; + const [users, total] = await Promise.all([ + prisma.user.findMany({ + orderBy: { id: "asc" }, + skip: offset, + take: req.pagination?.limit || PAGINATION_LIMIT, + }), + prisma.user.count(), + ]); + return { + items: users.map(mapUserTag), + offset, + total, + }; +} diff --git a/src/lib/server/query/util.test.ts b/src/lib/server/query/util.test.ts new file mode 100644 index 0000000..d8bf743 --- /dev/null +++ b/src/lib/server/query/util.test.ts @@ -0,0 +1,20 @@ +import { expect, test } from "vitest"; +import { QueryBuilder } from "./util"; + +test("query builder", () => { + const qb = new QueryBuilder("select e.id, e.text, e.category", "from entries e"); + qb.addFilterList("category", [1, 2, 3]); + qb.addFilter("text", "HelloWorld"); + qb.setPagination({ limit: 20, offset: 10 }); + + const query = qb.getQuery(); + expect(query).toBe( + "select e.id, e.text, e.category from entries e where category in $1 and text = $2 limit $3 offset $4" + ); + + const params = qb.getParams(); + expect(params[0]).toStrictEqual([1, 2, 3]); + expect(params[1]).toBe("HelloWorld"); + expect(params[2]).toBe(20); + expect(params[3]).toBe(10); +}); diff --git a/src/lib/server/query/util.ts b/src/lib/server/query/util.ts new file mode 100644 index 0000000..17c3582 --- /dev/null +++ b/src/lib/server/query/util.ts @@ -0,0 +1,100 @@ +import { PAGINATION_LIMIT } from "$lib/shared/constants"; +import type { FilterList, PaginationRequest } from "$lib/shared/model"; + +export function convertFilterList( + fl: FilterList | undefined +): { in: T[] } | T | undefined { + if (!fl) { + return undefined; + } else if (Array.isArray(fl)) { + return { in: fl }; + } else { + return fl; + } +} + +export class QueryBuilder { + private selectClause; + private fromClause; + private filterClauses: string[] = []; + private orderClause = ""; + private params: unknown[] = []; + private nP = 0; + private limit = PAGINATION_LIMIT; + private offset = 0; + + constructor(selectClause: string, fromClause: string) { + this.selectClause = selectClause; + this.fromClause = fromClause; + } + + setPagination(pag: PaginationRequest) { + this.limit = pag.limit; + this.offset = pag.offset; + } + + setOrderClause(orderClause: string) { + this.orderClause = orderClause; + } + + /** Get the next parameter variable (e.g. $1) and increment the counter */ + pvar(): string { + this.nP += 1; + return `$${this.nP}`; + } + + addFilter(fname: string, val: unknown | undefined) { + if (val === undefined) return; + + this.params.push(val); + this.filterClauses.push(`${fname} = ${this.pvar()}`); + } + + addFilterClause(clause: string, ...params: unknown[]) { + this.filterClauses.push(clause); + this.params.push(...params); + } + + addFilterList(fname: string, fl: FilterList | undefined) { + if (fl === undefined) return; + + this.params.push(fl); + if (Array.isArray(fl)) { + this.filterClauses.push(`${fname} in ${this.pvar()}`); + } else { + this.filterClauses.push(`${fname} = ${this.pvar()}`); + } + } + + getQuery(): string { + const queryParts = [this.selectClause, this.fromClause]; + if (this.filterClauses.length > 0) { + queryParts.push("where " + this.filterClauses.join(" and ")); + } + + if (this.orderClause.length > 0) queryParts.push(this.orderClause); + queryParts.push(`limit $${this.nP + 1} offset $${this.nP + 2}`); + + return queryParts.join(" "); + } + + getCountQuery(): string { + const queryParts = ["select count(*) as count", this.fromClause]; + if (this.filterClauses.length > 0) { + queryParts.push("where " + this.filterClauses.join(" and ")); + } + return queryParts.join(" "); + } + + getParams(): unknown[] { + return [...this.params, this.limit, this.offset]; + } + + getCountParams(): unknown[] { + return this.params; + } + + getOffset(): number { + return this.offset; + } +} diff --git a/src/lib/shared/constants.ts b/src/lib/shared/constants.ts new file mode 100644 index 0000000..a7659dc --- /dev/null +++ b/src/lib/shared/constants.ts @@ -0,0 +1 @@ +export const PAGINATION_LIMIT = 20; diff --git a/src/lib/shared/model/index.ts b/src/lib/shared/model/index.ts new file mode 100644 index 0000000..fa5ae1e --- /dev/null +++ b/src/lib/shared/model/index.ts @@ -0,0 +1,4 @@ +export type Option = T | null; + +export * from "./model"; +export * from "./requests"; diff --git a/src/lib/shared/model/model.ts b/src/lib/shared/model/model.ts new file mode 100644 index 0000000..af4db7e --- /dev/null +++ b/src/lib/shared/model/model.ts @@ -0,0 +1,108 @@ +import type { Option } from "."; + +export type Pagination = { + items: T[]; + total: number; + offset: number; +}; + +export type User = { + id: number; + name: Option; + email: Option; +}; + +export type UserTag = { + id: number; + name: Option; +}; + +export type Station = { + id: number; + name: string; +}; + +export type StationNew = { + name: string; +}; + +export type Room = { + id: number; + name: string; + station: Station; +}; + +export type RoomNew = { + name: string; + stationId: number; +}; + +export type Category = { + id: number; + name: string; + color: Option; + description: Option; +}; + +export type CategoryNew = { + name: string; + color: Option; + description: Option; +}; + +export type Patient = { + id: number; + firstName: string; + lastName: string; + age: Option; + room: Option; + createdAt: Date; +}; + +export type PatientNew = { + firstName: string; + lastName: string; + age: Option; + roomId: number; +}; + +export type Entry = { + id: number; + patient: Patient; + createdAt: Date; + currentVersion: EntryVersion; + execution: Option; +}; + +export type EntryNew = { + patientId: number; + version: EntryVersionNew; +}; + +export type EntryVersion = { + id: number; + text: string; + date: Date; + category: Option; + priority: boolean; + author: UserTag; + createdAt: Date; +}; + +export type EntryVersionNew = { + text: string; + date: Date; + categoryId: Option; + priority: boolean; +}; + +export type EntryExecution = { + id: number; + author: UserTag; + text: string; + createdAt: Date; +}; + +export type EntryExecutionNew = { + text: string; +}; diff --git a/src/lib/shared/model/requests.ts b/src/lib/shared/model/requests.ts new file mode 100644 index 0000000..a8597ca --- /dev/null +++ b/src/lib/shared/model/requests.ts @@ -0,0 +1,34 @@ +export type PaginationRequest = { + limit: number; + offset: number; +}; + +export type FilterList = T | T[]; + +export type EntriesFilter = Partial<{ + done: boolean; + author: FilterList; + executor: FilterList; + category: FilterList; + patient: FilterList; + station: FilterList; + room: FilterList; + priority: boolean; +}>; + +export type EntriesRequest = Partial<{ + filter: EntriesFilter; + pagination: PaginationRequest; +}>; + +export type UsersRequest = Partial<{ pagination: PaginationRequest }>; + +export type PatientsFilter = Partial<{ + station: FilterList; + room: FilterList; +}>; + +export type PatientsRequest = Partial<{ + filter: PatientsFilter; + pagination: PaginationRequest; +}>; diff --git a/src/lib/shared/model/validation.ts b/src/lib/shared/model/validation.ts new file mode 100644 index 0000000..c5902b6 --- /dev/null +++ b/src/lib/shared/model/validation.ts @@ -0,0 +1,55 @@ +import { z } from "zod"; +import { implement } from "$lib/shared/util/zod"; +import type { + CategoryNew, + EntryExecutionNew, + EntryNew, + EntryVersionNew, + PatientNew, + RoomNew, + StationNew, +} from "."; + +const ZEntityId = z.number().int().nonnegative(); +const ZNameString = z.string().min(1).max(200).trim(); +const ZTextString = z.string().trim(); + +export const ZStationNew = implement().with({ name: ZNameString }); + +export const ZRoomNew = implement().with({ + name: ZNameString, + stationId: ZEntityId, +}); + +export const ZCategoryNew = implement().with({ + name: ZNameString, + description: ZTextString.nullable(), + color: z + .string() + .regex(/[0-9A-Fa-f]{6}/) + .toUpperCase() + .nullable(), +}); + +export const ZPatientNew = implement().with({ + firstName: ZNameString, + lastName: ZNameString, + age: z.number().int().nonnegative().lt(200).nullable(), + roomId: ZEntityId, +}); + +export const ZEntryVersionNew = implement().with({ + text: ZTextString, + date: z.date(), + categoryId: ZEntityId.nullable(), + priority: z.boolean(), +}); + +export const ZEntryNew = implement().with({ + patientId: ZEntityId, + version: ZEntryVersionNew, +}); + +export const ZEntryExecutionNew = implement().with({ + text: ZTextString, +}); diff --git a/src/lib/shared/util/zod.ts b/src/lib/shared/util/zod.ts new file mode 100644 index 0000000..45de1b3 --- /dev/null +++ b/src/lib/shared/util/zod.ts @@ -0,0 +1,25 @@ +import { z } from "zod"; + +// Source: https://github.com/colinhacks/zod/discussions/1928 + +type Implements = { + [key in keyof Model]-?: undefined extends Model[key] + ? null extends Model[key] + ? z.ZodNullableType>> + : z.ZodOptionalType> + : null extends Model[key] + ? z.ZodNullableType> + : z.ZodType; +}; + +export function implement() { + return { + with: < + Schema extends Implements & { + [unknownKey in Exclude]: never; + }, + >( + schema: Schema + ) => z.object(schema), + }; +} diff --git a/src/routes/(user)/+layout.svelte b/src/routes/(app)/+layout.svelte similarity index 64% rename from src/routes/(user)/+layout.svelte rename to src/routes/(app)/+layout.svelte index 5da61c3..b10ceca 100644 --- a/src/routes/(user)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -2,6 +2,9 @@ import { page } from "$app/stores"; import NavLink from "$lib/components/ui/NavLink.svelte"; import { signOut } from "@auth/sveltekit/client"; + import { mdiAccount, mdiHome } from "@mdi/js"; + + import Icon from "$lib/components/ui/Icon.svelte";