From d8f20d9896ced58e56a9d694b0e7c0e03441f6f3 Mon Sep 17 00:00:00 2001 From: Michael Dausmann Date: Tue, 28 Nov 2023 00:28:19 +1100 Subject: [PATCH] setup stores and vitest --- README.md | 26 +- package-lock.json | 1041 +++++++++++++++++++++++++++++++++++- package.json | 7 +- stores/account.store.ts | 337 ++++++------ stores/notes.store.ts | 6 - stores/notify.store.ts | 57 +- test/account.store.spec.ts | 105 ++++ test/notify.store.spec.ts | 54 ++ vitest.config.ts | 14 + 9 files changed, 1436 insertions(+), 211 deletions(-) create mode 100644 test/account.store.spec.ts create mode 100644 test/notify.store.spec.ts create mode 100644 vitest.config.ts diff --git a/README.md b/README.md index 51d3702..0003331 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ Discord [here](https://discord.gg/3hWPDTA4kD) ### Testing - [x] Manual test scenario for auth and sub workflows passing -- [ ] Unit tests for server functions +- [x] Unit test framework (vitest) - [ ] Integration tests for auth and sub workflows ## Special Mention @@ -120,8 +120,22 @@ This https://blog.checklyhq.com/building-a-multi-tenant-saas-data-model/ Article The focus is on separation of concerns and avoiding vendor lock in. +### Diagram + +### Walkthrough + +[](https://www.youtube.com/watch?v=AFfbGuJYRqI) + +### Tricky Decisions + +_Composition over options API_ - I have decided to use composition api and setup functions accross the board including components, pages and Pinia stores. I was resistant at first, especially with the stores as I was used to Vuex but have come to the conclusion that it is easier to go one approach all over. It's also the latest and greatest and folks don't like to use a starter that starts behind the cutting edge. + +_Prisma over Supabase API_ - I went with Prisma for direct DB access rather than use the Supabase client. This is Primarily to avoid lock-in with Supabase too much. Supabase is great but I thought burdening my users with a future situation where it's difficult to move off it wouldn't be very cool. Also, I really like how Prisma handles schema changes and updates to the client layer and types with just two bash commands, after using other approaches, I find this super smooth. + +_Trpc over REST_ - Primarily for full thickness types without duplication on the client. Also I think the remote procedure call paradigm works well. Note however that I still include a [REST endpoint example](/server/api/note.ts) for flexibility. My preference for mobile is Flutter and there is not a Trpc client for Flutter that i'm aware off so it was important for me to make sure REST works also. + ## Externals Setup Things you gotta do that aren't code (and are therefore not very interesting) @@ -224,12 +238,22 @@ Your webhook signing secret is whsec_xxxxxxxxxxxxx (^C to quit) take ths signing secret and update the STRIPE_ENDPOINT_SECRET value in .env +### Start the Server + Start the development server on http://localhost:3000 ```bash npm run dev ``` +### Running the Tests + +There are a few unit tests, just for the stores because I needed to refactor. Feel free to extend the tests for your use cases, or not, it's your SaaS, not mine. + +```bash +npm run test +``` + ## Production Build the application for production: diff --git a/package-lock.json b/package-lock.json index 4065bce..3dea166 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "zod": "^3.20.2" }, "devDependencies": { + "@nuxt/test-utils": "^3.8.1", "@nuxtjs/supabase": "^1.1.3", "@nuxtjs/tailwindcss": "^6.6.6", "@prisma/client": "^5.2.0", @@ -33,7 +34,8 @@ "nuxt-icon": "^0.3.3", "prisma": "^5.2.0", "ts-node": "^10.9.1", - "typescript": "^5.0.3" + "typescript": "^5.0.3", + "vitest": "^0.33.0" } }, "node_modules/@alloc/quick-lru": { @@ -1106,6 +1108,18 @@ "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", "dev": true }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -1421,6 +1435,227 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/@nuxt/test-utils": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-3.8.1.tgz", + "integrity": "sha512-8ZQ+OZ7z5Sc5KG2aCvk0piheYSpGb2UQJMCWr8ORwEyZIw4awrkkwGzUY06e344E4StvJB8zxN122MEcFNOkow==", + "dev": true, + "dependencies": { + "@nuxt/kit": "3.8.1", + "@nuxt/schema": "3.8.1", + "consola": "^3.2.3", + "defu": "^6.1.3", + "execa": "^8.0.1", + "get-port-please": "^3.1.1", + "ofetch": "^1.3.3", + "pathe": "^1.1.1", + "ufo": "^1.3.1" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0" + }, + "peerDependencies": { + "@jest/globals": "^29.5.0", + "playwright-core": "^1.34.3", + "vitest": "^0.30.0 || ^0.31.0 || ^0.32.0 || ^0.33.0", + "vue": "^3.3.4" + }, + "peerDependenciesMeta": { + "@jest/globals": { + "optional": true + }, + "playwright-core": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@nuxt/test-utils/node_modules/@nuxt/kit": { + "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.1", + "c12": "^1.5.1", + "consola": "^3.2.3", + "defu": "^6.1.3", + "globby": "^13.2.2", + "hash-sum": "^2.0.0", + "ignore": "^5.2.4", + "jiti": "^1.21.0", + "knitwork": "^1.0.0", + "mlly": "^1.4.2", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "scule": "^1.0.0", + "semver": "^7.5.4", + "ufo": "^1.3.1", + "unctx": "^2.3.1", + "unimport": "^3.4.0", + "untyped": "^1.4.0" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/@nuxt/test-utils/node_modules/@nuxt/schema": { + "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.3", + "hookable": "^5.5.3", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "std-env": "^3.4.3", + "ufo": "^1.3.1", + "unimport": "^3.4.0", + "untyped": "^1.4.0" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/@nuxt/test-utils/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "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" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@nuxt/test-utils/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/test-utils/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/@nuxt/test-utils/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/test-utils/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/test-utils/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/test-utils/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/test-utils/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/test-utils/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nuxt/test-utils/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@nuxt/ui-templates": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@nuxt/ui-templates/-/ui-templates-1.3.1.tgz", @@ -2189,6 +2424,12 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/@supabase/functions-js": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.5.tgz", @@ -2330,6 +2571,21 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "devOptional": true }, + "node_modules/@types/chai": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", + "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", + "dev": true + }, + "node_modules/@types/chai-subset": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz", + "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, "node_modules/@types/eslint": { "version": "8.44.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", @@ -2590,6 +2846,74 @@ "vue": "^3.0.0" } }, + "node_modules/@vitest/expect": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.33.0.tgz", + "integrity": "sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==", + "dev": true, + "dependencies": { + "@vitest/spy": "0.33.0", + "@vitest/utils": "0.33.0", + "chai": "^4.3.7" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.33.0.tgz", + "integrity": "sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==", + "dev": true, + "dependencies": { + "@vitest/utils": "0.33.0", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.33.0.tgz", + "integrity": "sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.33.0.tgz", + "integrity": "sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==", + "dev": true, + "dependencies": { + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.33.0.tgz", + "integrity": "sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vue-macros/common": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.7.2.tgz", @@ -3232,6 +3556,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/ast-kit": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-0.10.0.tgz", @@ -3636,6 +3969,24 @@ } ] }, + "node_modules/chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -3648,6 +3999,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -4306,6 +4669,18 @@ } } }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", @@ -4331,9 +4706,9 @@ } }, "node_modules/defu": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.2.tgz", - "integrity": "sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==" + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.3.tgz", + "integrity": "sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ==" }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -4411,6 +4786,15 @@ "node": ">=0.3.1" } }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -5100,6 +5484,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", @@ -5115,9 +5508,9 @@ } }, "node_modules/get-port-please": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.0.1.tgz", - "integrity": "sha512-R5pcVO8Z1+pVDu8Ml3xaJCEkBiiy1VQN9za0YqH8GIi1nIqD4IzQhzY6dDzMRtdS1lyiGlucRzm8IN8wtLIXng==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.1.tgz", + "integrity": "sha512-3UBAyM3u4ZBVYDsxOQfJDxEa6XTbpBDrOjp4mf7ExFRt5BKs/QywQQiJsh2B+hxcZLSapWqCRvElUe8DnKcFHA==", "dev": true }, "node_modules/get-stream": { @@ -6354,6 +6747,15 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7314,6 +7716,21 @@ "openapi-typescript": "bin/cli.js" } }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parent-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", @@ -7425,6 +7842,15 @@ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==" }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -8342,6 +8768,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prisma": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.2.0.tgz", @@ -8462,6 +8914,12 @@ "flat": "^5.0.2" } }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -9052,6 +9510,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -9130,6 +9594,12 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "node_modules/standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", @@ -9671,6 +10141,30 @@ "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", "dev": true }, + "node_modules/tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.6.0.tgz", + "integrity": "sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -9792,6 +10286,15 @@ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", @@ -10754,6 +11257,83 @@ "@esbuild/win32-x64": "0.18.20" } }, + "node_modules/vitest": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.33.0.tgz", + "integrity": "sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==", + "dev": true, + "dependencies": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.33.0", + "@vitest/runner": "0.33.0", + "@vitest/snapshot": "0.33.0", + "@vitest/spy": "0.33.0", + "@vitest/utils": "0.33.0", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.6.0", + "vite": "^3.0.0 || ^4.0.0", + "vite-node": "0.33.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, "node_modules/vscode-jsonrpc": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", @@ -11053,6 +11633,22 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -11170,6 +11766,18 @@ "node": ">=6" } }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zhead": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.0.10.tgz", @@ -11955,6 +12563,15 @@ "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", "dev": true }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, "@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -12218,6 +12835,146 @@ } } }, + "@nuxt/test-utils": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-3.8.1.tgz", + "integrity": "sha512-8ZQ+OZ7z5Sc5KG2aCvk0piheYSpGb2UQJMCWr8ORwEyZIw4awrkkwGzUY06e344E4StvJB8zxN122MEcFNOkow==", + "dev": true, + "requires": { + "@nuxt/kit": "3.8.1", + "@nuxt/schema": "3.8.1", + "consola": "^3.2.3", + "defu": "^6.1.3", + "execa": "^8.0.1", + "get-port-please": "^3.1.1", + "ofetch": "^1.3.3", + "pathe": "^1.1.1", + "ufo": "^1.3.1" + }, + "dependencies": { + "@nuxt/kit": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.8.1.tgz", + "integrity": "sha512-DrhG1Z85iH68QOTkgfb0HVfM2g7+CfcMWrFWMDwck9ofyM2RXQUZyfmvMedwBnui1AjjpgpLO9078yZM+RqNUg==", + "dev": true, + "requires": { + "@nuxt/schema": "3.8.1", + "c12": "^1.5.1", + "consola": "^3.2.3", + "defu": "^6.1.3", + "globby": "^13.2.2", + "hash-sum": "^2.0.0", + "ignore": "^5.2.4", + "jiti": "^1.21.0", + "knitwork": "^1.0.0", + "mlly": "^1.4.2", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "scule": "^1.0.0", + "semver": "^7.5.4", + "ufo": "^1.3.1", + "unctx": "^2.3.1", + "unimport": "^3.4.0", + "untyped": "^1.4.0" + } + }, + "@nuxt/schema": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.8.1.tgz", + "integrity": "sha512-fSaWRcI/2mUskfTZTGSnH6Ny0x05CRzylbVn6WFV0d6UEKIVy42Qd6n+h7yoFfp4cq4nji6u16PT4SqS1DEhsw==", + "dev": true, + "requires": { + "@nuxt/ui-templates": "^1.3.1", + "consola": "^3.2.3", + "defu": "^6.1.3", + "hookable": "^5.5.3", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "std-env": "^3.4.3", + "ufo": "^1.3.1", + "unimport": "^3.4.0", + "untyped": "^1.4.0" + } + }, + "execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "requires": { + "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" + } + }, + "get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true + }, + "human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + } + } + }, "@nuxt/ui-templates": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@nuxt/ui-templates/-/ui-templates-1.3.1.tgz", @@ -12678,6 +13435,12 @@ } } }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "@supabase/functions-js": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.5.tgz", @@ -12802,6 +13565,21 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "devOptional": true }, + "@types/chai": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", + "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", + "dev": true + }, + "@types/chai-subset": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz", + "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, "@types/eslint": { "version": "8.44.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", @@ -13015,6 +13793,59 @@ "@vue/babel-plugin-jsx": "^1.1.5" } }, + "@vitest/expect": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.33.0.tgz", + "integrity": "sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==", + "dev": true, + "requires": { + "@vitest/spy": "0.33.0", + "@vitest/utils": "0.33.0", + "chai": "^4.3.7" + } + }, + "@vitest/runner": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.33.0.tgz", + "integrity": "sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==", + "dev": true, + "requires": { + "@vitest/utils": "0.33.0", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + } + }, + "@vitest/snapshot": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.33.0.tgz", + "integrity": "sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==", + "dev": true, + "requires": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + } + }, + "@vitest/spy": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.33.0.tgz", + "integrity": "sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==", + "dev": true, + "requires": { + "tinyspy": "^2.1.1" + } + }, + "@vitest/utils": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.33.0.tgz", + "integrity": "sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==", + "dev": true, + "requires": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + } + }, "@vue-macros/common": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.7.2.tgz", @@ -13568,6 +14399,12 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "ast-kit": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-0.10.0.tgz", @@ -13832,12 +14669,36 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001523.tgz", "integrity": "sha512-I5q5cisATTPZ1mc588Z//pj/Ox80ERYDfR71YnvY7raS/NOk8xXlZcB0sF7JdqaV//kOaa6aus7lRfpdnt1eBA==" }, + "chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + } + }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, + "check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "requires": { + "get-func-name": "^2.0.2" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -14315,6 +15176,15 @@ "ms": "2.1.2" } }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", @@ -14334,9 +15204,9 @@ "dev": true }, "defu": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.2.tgz", - "integrity": "sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==" + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.3.tgz", + "integrity": "sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ==" }, "delayed-stream": { "version": "1.0.0", @@ -14395,6 +15265,12 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "devOptional": true }, + "diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -14918,6 +15794,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true + }, "get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", @@ -14930,9 +15812,9 @@ } }, "get-port-please": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.0.1.tgz", - "integrity": "sha512-R5pcVO8Z1+pVDu8Ml3xaJCEkBiiy1VQN9za0YqH8GIi1nIqD4IzQhzY6dDzMRtdS1lyiGlucRzm8IN8wtLIXng==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.1.tgz", + "integrity": "sha512-3UBAyM3u4ZBVYDsxOQfJDxEa6XTbpBDrOjp4mf7ExFRt5BKs/QywQQiJsh2B+hxcZLSapWqCRvElUe8DnKcFHA==", "dev": true }, "get-stream": { @@ -15888,6 +16770,15 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true }, + "loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.1" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -16596,6 +17487,15 @@ "yargs-parser": "^21.1.1" } }, + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, "parent-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", @@ -16683,6 +17583,12 @@ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==" }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -17247,6 +18153,25 @@ "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", "dev": true }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, "prisma": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.2.0.tgz", @@ -17331,6 +18256,12 @@ "flat": "^5.0.2" } }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17765,6 +18696,12 @@ "object-inspect": "^1.9.0" } }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -17832,6 +18769,12 @@ } } }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", @@ -18217,6 +19160,24 @@ "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", "dev": true }, + "tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "tinypool": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.6.0.tgz", + "integrity": "sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==", + "dev": true + }, + "tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -18299,6 +19260,12 @@ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", @@ -18837,6 +19804,38 @@ } } }, + "vitest": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.33.0.tgz", + "integrity": "sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==", + "dev": true, + "requires": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.33.0", + "@vitest/runner": "0.33.0", + "@vitest/snapshot": "0.33.0", + "@vitest/spy": "0.33.0", + "@vitest/utils": "0.33.0", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.6.0", + "vite": "^3.0.0 || ^4.0.0", + "vite-node": "0.33.0", + "why-is-node-running": "^2.2.2" + } + }, "vscode-jsonrpc": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", @@ -19070,6 +20069,16 @@ "isexe": "^2.0.0" } }, + "why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -19160,6 +20169,12 @@ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "devOptional": true }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + }, "zhead": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.0.10.tgz", diff --git a/package.json b/package.json index 11680ae..0963414 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,11 @@ "dev": "nuxt dev", "generate": "nuxt generate", "preview": "nuxt preview", - "postinstall": "prisma generate && nuxt prepare" + "postinstall": "prisma generate && nuxt prepare", + "test": "vitest" }, "devDependencies": { + "@nuxt/test-utils": "^3.8.1", "@nuxtjs/supabase": "^1.1.3", "@nuxtjs/tailwindcss": "^6.6.6", "@prisma/client": "^5.2.0", @@ -28,7 +30,8 @@ "nuxt-icon": "^0.3.3", "prisma": "^5.2.0", "ts-node": "^10.9.1", - "typescript": "^5.0.3" + "typescript": "^5.0.3", + "vitest": "^0.33.0" }, "dependencies": { "@pinia/nuxt": "^0.4.6", diff --git a/stores/account.store.ts b/stores/account.store.ts index 6ca2319..da90bb8 100644 --- a/stores/account.store.ts +++ b/stores/account.store.ts @@ -1,5 +1,6 @@ import { ACCOUNT_ACCESS } from '~~/prisma/account-access-enum'; import { defineStore } from 'pinia'; +import { ref, computed } from 'vue'; import { FullDBUser, MembershipWithUser } from '~~/lib/services/service.types'; /* @@ -22,167 +23,183 @@ so that other routers can use them to filter results to the active user and acco | | | account account acccount* */ -interface State { - dbUser: FullDBUser | null; - activeAccountId: number | null; - activeAccountMembers: MembershipWithUser[]; -} +export const useAccountStore = defineStore('account', () => { + const dbUser = ref(null); + const activeAccountId = ref(null); + const activeAccountMembers = ref([]); + const activeMembership = computed(() => + dbUser?.value?.memberships.find(m => m.account_id === activeAccountId.value) + ); -export const useAccountStore = defineStore('account', { - state: (): State => { - return { - dbUser: null, - activeAccountId: null, - activeAccountMembers: [] - }; - }, - getters: { - activeMembership: state => - state.dbUser?.memberships.find( - m => m.account_id === state.activeAccountId - ) - }, - actions: { - async init() { - const { $client } = useNuxtApp(); - if (!this.dbUser) { - const { dbUser } = await $client.auth.getDBUser.query(); - if (dbUser) { - this.dbUser = dbUser; - } - } - if (!this.activeAccountId) { - const { activeAccountId } = - await $client.account.getActiveAccountId.query(); - if (activeAccountId) { - this.activeAccountId = activeAccountId; - } - } - }, - signout() { - this.dbUser = null; - this.activeAccountId = null; - this.activeAccountMembers = []; - }, - async getActiveAccountMembers() { - if ( - this.activeMembership && - (this.activeMembership.access === ACCOUNT_ACCESS.ADMIN || - this.activeMembership.access === ACCOUNT_ACCESS.OWNER) - ) { - const { $client } = useNuxtApp(); - const { data: memberships } = - await $client.account.getAccountMembers.useQuery(); - if (memberships.value?.memberships) { - this.activeAccountMembers = memberships.value?.memberships; - } - } - }, - async changeActiveAccount(account_id: number) { - const { $client } = useNuxtApp(); - await $client.account.changeActiveAccount.mutate({ account_id }); // sets active account on context for other routers and sets the preference in a cookie - - this.activeAccountId = account_id; // because this is used as a trigger to some other components, NEEDS TO BE AFTER THE MUTATE CALL - await this.getActiveAccountMembers(); // these relate to the active account and need to ber re-fetched - }, - async changeAccountName(new_name: string) { - if (!this.activeMembership) { - return; - } - const { $client } = useNuxtApp(); - const { account } = await $client.account.changeAccountName.mutate({ - new_name - }); - if (account) { - this.activeMembership.account.name = account.name; - } - }, - async acceptPendingMembership(membership_id: number) { - const { $client } = useNuxtApp(); - const { data: membership } = - await $client.account.acceptPendingMembership.useQuery({ - membership_id - }); - - if (membership.value && membership.value.membership?.pending === false) { - for (const m of this.activeAccountMembers) { - if (m.id === membership_id) { - m.pending = false; - } - } - } - }, - async rejectPendingMembership(membership_id: number) { - const { $client } = useNuxtApp(); - const { data: membership } = - await $client.account.rejectPendingMembership.useQuery({ - membership_id - }); - - if (membership.value) { - this.activeAccountMembers = this.activeAccountMembers.filter( - m => m.id !== membership_id - ); - } - }, - async deleteMembership(membership_id: number) { - const { $client } = useNuxtApp(); - const { data: membership } = - await $client.account.deleteMembership.useQuery({ membership_id }); - - if (membership.value) { - this.activeAccountMembers = this.activeAccountMembers.filter( - m => m.id !== membership_id - ); - } - }, - async rotateJoinPassword() { - const { $client } = useNuxtApp(); - const { account } = await $client.account.rotateJoinPassword.mutate(); - if (account && this.activeMembership) { - this.activeMembership.account = account; - } - }, - async joinUserToAccountPending(account_id: number) { - if (!this.dbUser) { - return; - } - const { $client } = useNuxtApp(); - const { membership } = - await $client.account.joinUserToAccountPending.mutate({ - account_id, - user_id: this.dbUser.id - }); - if (membership && this.activeMembership) { - this.dbUser?.memberships.push(membership); - } - }, - async changeUserAccessWithinAccount( - user_id: number, - access: ACCOUNT_ACCESS - ) { - const { $client } = useNuxtApp(); - const { membership } = - await $client.account.changeUserAccessWithinAccount.mutate({ - user_id, - access - }); - if (membership) { - for (const m of this.activeAccountMembers) { - if (m.id === membership.id) { - m.access = membership.access; - } - } - } - }, - async claimOwnershipOfAccount() { - const { $client } = useNuxtApp(); - const { memberships } = - await $client.account.claimOwnershipOfAccount.mutate(); - if (memberships) { - this.activeAccountMembers = memberships; - this.activeMembership!.access = ACCOUNT_ACCESS.OWNER; + const init = async () => { + const { $client } = useNuxtApp(); + if (!dbUser.value) { + const { dbUser: _dbUser } = await $client.auth.getDBUser.query(); + if (_dbUser) { + dbUser.value = _dbUser; } } - } + if (!activeAccountId.value) { + const { activeAccountId: _activeAccountId } = + await $client.account.getActiveAccountId.query(); + if (_activeAccountId) { + activeAccountId.value = _activeAccountId; + } + } + }; + + const signout = () => { + dbUser.value = null; + activeAccountId.value = null; + activeAccountMembers.value = []; + }; + + const getActiveAccountMembers = async () => { + if ( + activeMembership.value && + (activeMembership.value.access === ACCOUNT_ACCESS.ADMIN || + activeMembership.value.access === ACCOUNT_ACCESS.OWNER) + ) { + const { $client } = useNuxtApp(); + const { data: memberships } = + await $client.account.getAccountMembers.useQuery(); + if (memberships.value?.memberships) { + activeAccountMembers.value = memberships.value?.memberships; + } + } + }; + + const changeActiveAccount = async (account_id: number) => { + const { $client } = useNuxtApp(); + await $client.account.changeActiveAccount.mutate({ account_id }); // sets active account on context for other routers and sets the preference in a cookie + + activeAccountId.value = account_id; // because this is used as a trigger to some other components, NEEDS TO BE AFTER THE MUTATE CALL + await getActiveAccountMembers(); // these relate to the active account and need to ber re-fetched + }; + + const changeAccountName = async (new_name: string) => { + if (!activeMembership.value) { + return; + } + const { $client } = useNuxtApp(); + const { account } = await $client.account.changeAccountName.mutate({ + new_name + }); + if (account) { + activeMembership.value.account.name = account.name; + } + }; + + const acceptPendingMembership = async (membership_id: number) => { + const { $client } = useNuxtApp(); + const { data: membership } = + await $client.account.acceptPendingMembership.useQuery({ + membership_id + }); + + if (membership.value && membership.value.membership?.pending === false) { + for (const m of activeAccountMembers.value) { + if (m.id === membership_id) { + m.pending = false; + } + } + } + }; + + const rejectPendingMembership = async (membership_id: number) => { + const { $client } = useNuxtApp(); + const { data: membership } = + await $client.account.rejectPendingMembership.useQuery({ + membership_id + }); + + if (membership.value) { + activeAccountMembers.value = activeAccountMembers.value.filter( + m => m.id !== membership_id + ); + } + }; + + const deleteMembership = async (membership_id: number) => { + const { $client } = useNuxtApp(); + const { data: membership } = + await $client.account.deleteMembership.useQuery({ membership_id }); + + if (membership.value) { + activeAccountMembers.value = activeAccountMembers.value.filter( + m => m.id !== membership_id + ); + } + }; + + const rotateJoinPassword = async () => { + const { $client } = useNuxtApp(); + const { account } = await $client.account.rotateJoinPassword.mutate(); + if (account && activeMembership.value) { + activeMembership.value.account = account; + } + }; + + const joinUserToAccountPending = async (account_id: number) => { + if (!dbUser.value) { + return; + } + const { $client } = useNuxtApp(); + const { membership } = + await $client.account.joinUserToAccountPending.mutate({ + account_id, + user_id: dbUser.value.id + }); + if (membership && activeMembership.value) { + dbUser?.value?.memberships.push(membership); + } + }; + + const changeUserAccessWithinAccount = async ( + user_id: number, + access: ACCOUNT_ACCESS + ) => { + const { $client } = useNuxtApp(); + const { membership } = + await $client.account.changeUserAccessWithinAccount.mutate({ + user_id, + access + }); + if (membership) { + for (const m of activeAccountMembers.value) { + if (m.id === membership.id) { + m.access = membership.access; + } + } + } + }; + + const claimOwnershipOfAccount = async () => { + const { $client } = useNuxtApp(); + const { memberships } = + await $client.account.claimOwnershipOfAccount.mutate(); + if (memberships) { + activeAccountMembers.value = memberships; + activeMembership.value!.access = ACCOUNT_ACCESS.OWNER; + } + }; + + return { + dbUser, + activeAccountId, + activeAccountMembers, + activeMembership, + init, + signout, + getActiveAccountMembers, + changeActiveAccount, + changeAccountName, + acceptPendingMembership, + rejectPendingMembership, + deleteMembership, + rotateJoinPassword, + joinUserToAccountPending, + changeUserAccessWithinAccount, + claimOwnershipOfAccount + }; }); diff --git a/stores/notes.store.ts b/stores/notes.store.ts index 1bb5aab..2075298 100644 --- a/stores/notes.store.ts +++ b/stores/notes.store.ts @@ -2,12 +2,6 @@ import { Note } from '.prisma/client'; import { defineStore, storeToRefs } from 'pinia'; import { Ref } from 'vue'; -/* -Note) the Notes Store needs to be a 'Setup Store' (https://pinia.vuejs.org/core-concepts/#setup-stores) -because this enables the use of the watch on the Account Store -If the UI does not need to dynamically respond to a change in the active Account e.g. if state is always retrieved with an explicit fetch after onMounted. -then an Options store can be used. -*/ export const useNotesStore = defineStore('notes', () => { const accountStore = useAccountStore(); const { activeAccountId } = storeToRefs(accountStore); diff --git a/stores/notify.store.ts b/stores/notify.store.ts index 0c15fca..ee06ad9 100644 --- a/stores/notify.store.ts +++ b/stores/notify.store.ts @@ -1,4 +1,5 @@ import { defineStore } from 'pinia'; +import { ref } from 'vue'; /* This store manages User and Account state including the ActiveAccount @@ -17,35 +18,33 @@ export enum NotificationType { Error } -interface State { - notifications: Notification[]; - notificationsArchive: Notification[]; -} +export const useNotifyStore = defineStore('notify', () => { + const notifications = ref([]); + const notificationsArchive = ref([]); -export const useNotifyStore = defineStore('notify', { - state: (): State => { - return { - notifications: [], - notificationsArchive: [] + const notify = (messageOrError: unknown, type: NotificationType) => { + let message: string = ''; + if (messageOrError instanceof Error) message = messageOrError.message; + if (typeof messageOrError === 'string') message = messageOrError; + const notification: Notification = { + message, + type, + notifyTime: Date.now() }; - }, - actions: { - notify(messageOrError: unknown, type: NotificationType) { - let message: string = ''; - if (messageOrError instanceof Error) message = messageOrError.message; - if (typeof messageOrError === 'string') message = messageOrError; - const notification: Notification = { - message, - type, - notifyTime: Date.now() - }; - this.notifications.push(notification); - setTimeout(this.removeNotification.bind(this), 5000, notification); - }, - removeNotification(notification: Notification) { - this.notifications = this.notifications.filter( - n => n.notifyTime != notification.notifyTime - ); - } - } + notifications.value.push(notification); + setTimeout(removeNotification.bind(this), 5000, notification); + }; + + const removeNotification = (notification: Notification) => { + notifications.value = notifications.value.filter( + n => n.notifyTime != notification.notifyTime + ); + }; + + return { + notifications, + notificationsArchive, + notify, + removeNotification + }; }); diff --git a/test/account.store.spec.ts b/test/account.store.spec.ts new file mode 100644 index 0000000..b469cbf --- /dev/null +++ b/test/account.store.spec.ts @@ -0,0 +1,105 @@ +import { describe, expect, afterEach, beforeEach, it, vi } from 'vitest'; +import { useAccountStore } from '../stores/account.store'; +import { setActivePinia, createPinia } from 'pinia'; + +import { FullDBUser } from '~/lib/services/service.types'; + +const fakeInitAccountStoreAdmin = (accountStore: any) => { + const dbUser: FullDBUser = { + id: 1, + name: 'John Doe', + memberships: [ + { account_id: 1, access: 'ADMIN' }, + { account_id: 2, access: 'READ_ONLY' } + ] + } as any; + accountStore.dbUser = dbUser; + accountStore.activeAccountId = 1; +}; + +describe('Account Store', async () => { + beforeEach(() => { + setActivePinia(createPinia()); + }); + + it('should initialize the store', async () => { + // stub the useNuxtApp function with a mock client + vi.stubGlobal('useNuxtApp', () => ({ + $client: { + auth: { + getDBUser: { + query: () => ({ + dbUser: { + id: 1, + name: 'John Doe', + memberships: [] + } + }) + } + }, + account: { + getActiveAccountId: { + query: () => ({ activeAccountId: 1 }) + } + } + } + })); + + const accountStore = useAccountStore(); + + // method under test + await accountStore.init(); + + expect(accountStore.dbUser).toEqual({ + id: 1, + name: 'John Doe', + memberships: [] + }); + + expect(accountStore.activeAccountId).toEqual(1); + }); + + it('should get active account members', async () => { + // stub the useNuxtApp function with a mock client + vi.stubGlobal('useNuxtApp', () => ({ + $client: { + account: { + getAccountMembers: { + useQuery: () => ({ + data: { value: { memberships: [new Object() as any] } } + }) + } + } + } + })); + + const accountStore = useAccountStore(); + fakeInitAccountStoreAdmin(accountStore); + + // method under test + await accountStore.getActiveAccountMembers(); + + expect(accountStore.activeAccountMembers.length).toEqual(1); + }); + + it('should get an active membership', async () => { + const accountStore = useAccountStore(); + fakeInitAccountStoreAdmin(accountStore); + + expect(accountStore.activeMembership).toEqual({ + account_id: 1, + access: 'ADMIN' + }); + }); + + it('should signout', async () => { + const accountStore = useAccountStore(); + fakeInitAccountStoreAdmin(accountStore); + + await accountStore.signout(); + + expect(accountStore.dbUser).toBeNull(); + expect(accountStore.activeAccountId).toBeNull(); + expect(accountStore.activeAccountMembers.length).toEqual(0); + }); +}); diff --git a/test/notify.store.spec.ts b/test/notify.store.spec.ts new file mode 100644 index 0000000..153c714 --- /dev/null +++ b/test/notify.store.spec.ts @@ -0,0 +1,54 @@ +import { describe, test, expect, beforeEach } from 'vitest'; +import { setup, $fetch } from '@nuxt/test-utils'; +import { useNotifyStore, NotificationType } from '../stores/notify.store'; +import { setActivePinia, createPinia } from 'pinia'; + +describe('Notify Store', async () => { + await setup({ + // test context options + }); + + beforeEach(() => { + // creates a fresh pinia and makes it active + // so it's automatically picked up by any useStore() call + // without having to pass it to it: `useStore(pinia)` + setActivePinia(createPinia()); + }); + + test('should add a notification', () => { + const notifyStore = useNotifyStore(); + const message = 'Test notification'; + const type = NotificationType.Info; + + notifyStore.notify(message, type); + + expect(notifyStore.notifications).toHaveLength(1); + expect(notifyStore.notifications[0].message).toBe(message); + expect(notifyStore.notifications[0].type).toBe(type); + }); + + test('should add an Error notification', () => { + const notifyStore = useNotifyStore(); + const error = new Error('Test error'); + const type = NotificationType.Error; + + notifyStore.notify(error, type); + + expect(notifyStore.notifications).toHaveLength(1); + expect(notifyStore.notifications[0].message).toBe(error.message); + expect(notifyStore.notifications[0].type).toBe(type); + }); + + test('should remove a notification', () => { + const notifyStore = useNotifyStore(); + const message = 'Test notification'; + const type = NotificationType.Info; + + notifyStore.notify(message, type); + const notification = notifyStore.notifications[0]; + + notifyStore.removeNotification(notification); + + expect(notifyStore.notifications).toHaveLength(0); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..30f8eed --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,14 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // ... Specify options here. + }, + resolve: { + alias: { + '~~': fileURLToPath(new URL('./', import.meta.url)) + } + } +});