From 49dbf6e7a9424dd395e857aed62564689e0390d4 Mon Sep 17 00:00:00 2001 From: Chris Dill Date: Sat, 22 Feb 2025 19:57:52 +0000 Subject: [PATCH] Initial commit --- .editorconfig | 51 + .gitattributes | 2 + .gitea/workflows/build.yml | 40 + .gitignore | 12 + README.md | 15 + TestWeb.sln | 28 + client/package.json | 31 + client/pnpm-lock.yaml | 1448 ++++++++++++++++++ client/src/app.d.ts | 9 + client/src/app.html | 17 + client/src/routes/+layout.js | 1 + client/src/routes/+page.svelte | 45 + client/src/routes/jobs.svelte | 93 ++ client/src/routes/users.svelte | 82 + client/src/routes/vehicles.svelte | 91 ++ client/static/favicon.png | Bin 0 -> 1571 bytes client/static/global.css | 188 +++ client/svelte.config.js | 12 + client/tsconfig.json | 19 + client/vite.config.ts | 9 + src/Common/AppDbContext.cs | 28 + src/Common/ClaimsPrincipalExtensions.cs | 14 + src/Common/Job.cs | 34 + src/Common/Jwt.cs | 34 + src/Common/Note.cs | 23 + src/Common/RequestLoggingFilter.cs | 13 + src/Common/RequestValidationFilter.cs | 29 + src/Common/User.cs | 26 + src/Common/ValidationExtensions.cs | 11 + src/Common/Vehicle.cs | 30 + src/Jobs/JobApi.cs | 115 ++ src/Jobs/JobApi.http | 35 + src/Notes/NoteApi.cs | 114 ++ src/Notes/NoteApi.http | 33 + src/Program.cs | 118 ++ src/TestWeb.csproj | 18 + src/Users/UserApi.cs | 41 + src/Users/UserApi.http | 9 + src/Vehicles/VehicleApi.cs | 115 ++ src/Vehicles/VehicleApi.http | 33 + src/WeatherForecasts/WeatherForecastApi.cs | 31 + src/WeatherForecasts/WeatherForecastApi.http | 5 + src/appsettings.json | 19 + tests/TestWeb.Tests.csproj | 26 + tests/UnitTest1.cs | 10 + 45 files changed, 3157 insertions(+) create mode 100755 .editorconfig create mode 100755 .gitattributes create mode 100755 .gitea/workflows/build.yml create mode 100755 .gitignore create mode 100755 README.md create mode 100644 TestWeb.sln create mode 100755 client/package.json create mode 100644 client/pnpm-lock.yaml create mode 100755 client/src/app.d.ts create mode 100755 client/src/app.html create mode 100644 client/src/routes/+layout.js create mode 100644 client/src/routes/+page.svelte create mode 100644 client/src/routes/jobs.svelte create mode 100644 client/src/routes/users.svelte create mode 100644 client/src/routes/vehicles.svelte create mode 100755 client/static/favicon.png create mode 100755 client/static/global.css create mode 100755 client/svelte.config.js create mode 100755 client/tsconfig.json create mode 100755 client/vite.config.ts create mode 100755 src/Common/AppDbContext.cs create mode 100644 src/Common/ClaimsPrincipalExtensions.cs create mode 100755 src/Common/Job.cs create mode 100644 src/Common/Jwt.cs create mode 100755 src/Common/Note.cs create mode 100755 src/Common/RequestLoggingFilter.cs create mode 100755 src/Common/RequestValidationFilter.cs create mode 100755 src/Common/User.cs create mode 100755 src/Common/ValidationExtensions.cs create mode 100755 src/Common/Vehicle.cs create mode 100644 src/Jobs/JobApi.cs create mode 100644 src/Jobs/JobApi.http create mode 100644 src/Notes/NoteApi.cs create mode 100644 src/Notes/NoteApi.http create mode 100755 src/Program.cs create mode 100755 src/TestWeb.csproj create mode 100644 src/Users/UserApi.cs create mode 100644 src/Users/UserApi.http create mode 100644 src/Vehicles/VehicleApi.cs create mode 100644 src/Vehicles/VehicleApi.http create mode 100644 src/WeatherForecasts/WeatherForecastApi.cs create mode 100644 src/WeatherForecasts/WeatherForecastApi.http create mode 100755 src/appsettings.json create mode 100644 tests/TestWeb.Tests.csproj create mode 100644 tests/UnitTest1.cs diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..7a88c35 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,51 @@ +# EditorConfig is awesome: http://EditorConfig.org + +root = true + +[*] +indent_style = space + +# General +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +# C# styles +[*.cs] +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +csharp_style_namespace_declarations = file_scoped:warning + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Spacing +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_around_binary_operators = before_and_after +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false + +# Blocks are allowed +csharp_prefer_braces = true:error +csharp_preserve_single_line_blocks = false +csharp_preserve_single_line_statements = false diff --git a/.gitattributes b/.gitattributes new file mode 100755 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100755 index 0000000..709e8b0 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,40 @@ +name: Build +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Build and test api + run: dotnet test -c release + + - name: Install pnpm + uses: https://github.com/pnpm/action-setup@v4 + with: + version: 9 + run_install: false + + - name: Install packages + working-directory: "client" + run: pnpm install + + - name: Build client + working-directory: "client" + run: pnpm run build diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..aca3928 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.vscode/ +.zed/ +.idea/ +.nuget/ +.svelte-kit/ +build/ +obj/ +bin/ +node_modules/ +app.db +app.db-shm +app.db-wal diff --git a/README.md b/README.md new file mode 100755 index 0000000..c8cfeb1 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# test-web + +Test web api/client. + +1. Start frontend: +```shell +cd client +pnpm i +pnpm dev +``` + +2. Start backend: +```shell +dotnet run --project src +``` diff --git a/TestWeb.sln b/TestWeb.sln new file mode 100644 index 0000000..8554cd9 --- /dev/null +++ b/TestWeb.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWeb", "src\TestWeb.csproj", "{F20748F6-8A41-4126-B1DF-F879E6F392C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWeb.Tests", "tests\TestWeb.Tests.csproj", "{7EAA9F8C-EC88-4D3F-9DBA-6EC206C69FBB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F20748F6-8A41-4126-B1DF-F879E6F392C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F20748F6-8A41-4126-B1DF-F879E6F392C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F20748F6-8A41-4126-B1DF-F879E6F392C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F20748F6-8A41-4126-B1DF-F879E6F392C2}.Release|Any CPU.Build.0 = Release|Any CPU + {7EAA9F8C-EC88-4D3F-9DBA-6EC206C69FBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EAA9F8C-EC88-4D3F-9DBA-6EC206C69FBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EAA9F8C-EC88-4D3F-9DBA-6EC206C69FBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EAA9F8C-EC88-4D3F-9DBA-6EC206C69FBB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/client/package.json b/client/package.json new file mode 100755 index 0000000..fa2dcc2 --- /dev/null +++ b/client/package.json @@ -0,0 +1,31 @@ +{ + "name": "test-web-client", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "test": "vitest", + "format": "prettier --write --plugin prettier-plugin-svelte ." + }, + "devDependencies": { + "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/kit": "^2.17.2", + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "prettier": "^3.5.1", + "prettier-plugin-svelte": "^3.3.3", + "svelte": "^5.20.2", + "svelte-check": "^4.1.4", + "typescript": "^5.7.3", + "vite": "^6.1.1", + "vitest": "^3.0.6" + }, + "dependencies": { + "@tanstack/svelte-query": "^5.66.4", + "axios": "^1.7.9" + }, + "type": "module" +} diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml new file mode 100644 index 0000000..39bdff7 --- /dev/null +++ b/client/pnpm-lock.yaml @@ -0,0 +1,1448 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@tanstack/svelte-query': + specifier: ^5.66.4 + version: 5.66.4(svelte@5.20.2) + axios: + specifier: ^1.7.9 + version: 1.7.9 + devDependencies: + '@sveltejs/adapter-static': + specifier: ^3.0.8 + version: 3.0.8(@sveltejs/kit@2.17.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.1))(svelte@5.20.2)(vite@6.1.1)) + '@sveltejs/kit': + specifier: ^2.17.2 + version: 2.17.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.1))(svelte@5.20.2)(vite@6.1.1) + '@sveltejs/vite-plugin-svelte': + specifier: ^5.0.3 + version: 5.0.3(svelte@5.20.2)(vite@6.1.1) + prettier: + specifier: ^3.5.1 + version: 3.5.1 + prettier-plugin-svelte: + specifier: ^3.3.3 + version: 3.3.3(prettier@3.5.1)(svelte@5.20.2) + svelte: + specifier: ^5.20.2 + version: 5.20.2 + svelte-check: + specifier: ^4.1.4 + version: 4.1.4(svelte@5.20.2)(typescript@5.7.3) + typescript: + specifier: ^5.7.3 + version: 5.7.3 + vite: + specifier: ^6.1.1 + version: 6.1.1 + vitest: + specifier: ^3.0.6 + version: 3.0.6 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@polka/url@1.0.0-next.28': + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + + '@rollup/rollup-android-arm-eabi@4.34.8': + resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.34.8': + resolution: {integrity: sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.34.8': + resolution: {integrity: sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.34.8': + resolution: {integrity: sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.34.8': + resolution: {integrity: sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.34.8': + resolution: {integrity: sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.34.8': + resolution: {integrity: sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.34.8': + resolution: {integrity: sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.34.8': + resolution: {integrity: sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.34.8': + resolution: {integrity: sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.34.8': + resolution: {integrity: sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': + resolution: {integrity: sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.34.8': + resolution: {integrity: sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.34.8': + resolution: {integrity: sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.34.8': + resolution: {integrity: sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.34.8': + resolution: {integrity: sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.34.8': + resolution: {integrity: sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.34.8': + resolution: {integrity: sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.34.8': + resolution: {integrity: sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==} + cpu: [x64] + os: [win32] + + '@sveltejs/adapter-static@3.0.8': + resolution: {integrity: sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + + '@sveltejs/kit@2.17.2': + resolution: {integrity: sha512-Vypk02baf7qd3SOB1uUwUC/3Oka+srPo2J0a8YN3EfJypRshDkNx9HzNKjSmhOnGWwT+SSO06+N0mAb8iVTmTQ==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 || ^6.0.0 + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1': + resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^5.0.0 + svelte: ^5.0.0 + vite: ^6.0.0 + + '@sveltejs/vite-plugin-svelte@5.0.3': + resolution: {integrity: sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.0.0 + + '@tanstack/query-core@5.66.4': + resolution: {integrity: sha512-skM/gzNX4shPkqmdTCSoHtJAPMTtmIJNS0hE+xwTTUVYwezArCT34NMermABmBVUg5Ls5aiUXEDXfqwR1oVkcA==} + + '@tanstack/svelte-query@5.66.4': + resolution: {integrity: sha512-HS5+rmYIkMg1HRYG0BPn8tqoE7nLytfJ+63cR/KTIKe6oyDjvuAWfdDRnjBJAzMwJpS7Ub3KLtNNf6hH1UKA7g==} + peerDependencies: + svelte: ^3.54.0 || ^4.0.0 || ^5.0.0-next.0 + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@vitest/expect@3.0.6': + resolution: {integrity: sha512-zBduHf/ja7/QRX4HdP1DSq5XrPgdN+jzLOwaTq/0qZjYfgETNFCKf9nOAp2j3hmom3oTbczuUzrzg9Hafh7hNg==} + + '@vitest/mocker@3.0.6': + resolution: {integrity: sha512-KPztr4/tn7qDGZfqlSPQoF2VgJcKxnDNhmfR3VgZ6Fy1bO8T9Fc1stUiTXtqz0yG24VpD00pZP5f8EOFknjNuQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.0.6': + resolution: {integrity: sha512-Zyctv3dbNL+67qtHfRnUE/k8qxduOamRfAL1BurEIQSyOEFffoMvx2pnDSSbKAAVxY0Ej2J/GH2dQKI0W2JyVg==} + + '@vitest/runner@3.0.6': + resolution: {integrity: sha512-JopP4m/jGoaG1+CBqubV/5VMbi7L+NQCJTu1J1Pf6YaUbk7bZtaq5CX7p+8sY64Sjn1UQ1XJparHfcvTTdu9cA==} + + '@vitest/snapshot@3.0.6': + resolution: {integrity: sha512-qKSmxNQwT60kNwwJHMVwavvZsMGXWmngD023OHSgn873pV0lylK7dwBTfYP7e4URy5NiBCHHiQGA9DHkYkqRqg==} + + '@vitest/spy@3.0.6': + resolution: {integrity: sha512-HfOGx/bXtjy24fDlTOpgiAEJbRfFxoX3zIGagCqACkFKKZ/TTOE6gYMKXlqecvxEndKFuNHcHqP081ggZ2yM0Q==} + + '@vitest/utils@3.0.6': + resolution: {integrity: sha512-18ktZpf4GQFTbf9jK543uspU03Q2qya7ZGya5yiZ0Gx0nnnalBvd5ZBislbl2EhLjM8A8rt4OilqKG7QwcGkvQ==} + + acorn-typescript@1.4.13: + resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==} + peerDependencies: + acorn: '>=8.9.0' + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + devalue@5.1.1: + resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + esrap@1.4.5: + resolution: {integrity: sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + + fdir@6.4.3: + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.2.7: + resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.5.2: + resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==} + engines: {node: ^10 || ^12 || >=14} + + prettier-plugin-svelte@3.3.3: + resolution: {integrity: sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==} + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + + prettier@3.5.1: + resolution: {integrity: sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==} + engines: {node: '>=14'} + hasBin: true + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + rollup@4.34.8: + resolution: {integrity: sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + + svelte-check@4.1.4: + resolution: {integrity: sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte@5.20.2: + resolution: {integrity: sha512-aYXJreNUiyTob0QOzRZeBXZMGeFZDch6SrSRV8QTncZb6zj0O3BEdUzPpojuHQ1pTvk+KX7I6rZCXPUf8pTPxA==} + engines: {node: '>=18'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + vite-node@3.0.6: + resolution: {integrity: sha512-s51RzrTkXKJrhNbUzQRsarjmAae7VmMPAsRT7lppVpIg6mK3zGthP9Hgz0YQQKuNcF+Ii7DfYk3Fxz40jRmePw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@6.1.1: + resolution: {integrity: sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.0.5: + resolution: {integrity: sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + vite: + optional: true + + vitest@3.0.6: + resolution: {integrity: sha512-/iL1Sc5VeDZKPDe58oGK4HUFLhw6b5XdY1MYawjuSaDA4sEfYlY9HnS6aCEG26fX+MgUi7MwlduTBHHAI/OvMA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.0.6 + '@vitest/ui': 3.0.6 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@esbuild/aix-ppc64@0.24.2': + optional: true + + '@esbuild/android-arm64@0.24.2': + optional: true + + '@esbuild/android-arm@0.24.2': + optional: true + + '@esbuild/android-x64@0.24.2': + optional: true + + '@esbuild/darwin-arm64@0.24.2': + optional: true + + '@esbuild/darwin-x64@0.24.2': + optional: true + + '@esbuild/freebsd-arm64@0.24.2': + optional: true + + '@esbuild/freebsd-x64@0.24.2': + optional: true + + '@esbuild/linux-arm64@0.24.2': + optional: true + + '@esbuild/linux-arm@0.24.2': + optional: true + + '@esbuild/linux-ia32@0.24.2': + optional: true + + '@esbuild/linux-loong64@0.24.2': + optional: true + + '@esbuild/linux-mips64el@0.24.2': + optional: true + + '@esbuild/linux-ppc64@0.24.2': + optional: true + + '@esbuild/linux-riscv64@0.24.2': + optional: true + + '@esbuild/linux-s390x@0.24.2': + optional: true + + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + + '@esbuild/openbsd-x64@0.24.2': + optional: true + + '@esbuild/sunos-x64@0.24.2': + optional: true + + '@esbuild/win32-arm64@0.24.2': + optional: true + + '@esbuild/win32-ia32@0.24.2': + optional: true + + '@esbuild/win32-x64@0.24.2': + optional: true + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@polka/url@1.0.0-next.28': {} + + '@rollup/rollup-android-arm-eabi@4.34.8': + optional: true + + '@rollup/rollup-android-arm64@4.34.8': + optional: true + + '@rollup/rollup-darwin-arm64@4.34.8': + optional: true + + '@rollup/rollup-darwin-x64@4.34.8': + optional: true + + '@rollup/rollup-freebsd-arm64@4.34.8': + optional: true + + '@rollup/rollup-freebsd-x64@4.34.8': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.34.8': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.34.8': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.34.8': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-x64-musl@4.34.8': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.34.8': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.34.8': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.34.8': + optional: true + + '@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.17.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.1))(svelte@5.20.2)(vite@6.1.1))': + dependencies: + '@sveltejs/kit': 2.17.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.1))(svelte@5.20.2)(vite@6.1.1) + + '@sveltejs/kit@2.17.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.1))(svelte@5.20.2)(vite@6.1.1)': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.20.2)(vite@6.1.1) + '@types/cookie': 0.6.0 + cookie: 0.6.0 + devalue: 5.1.1 + esm-env: 1.2.2 + import-meta-resolve: 4.1.0 + kleur: 4.1.5 + magic-string: 0.30.17 + mrmime: 2.0.1 + sade: 1.8.1 + set-cookie-parser: 2.7.1 + sirv: 3.0.1 + svelte: 5.20.2 + vite: 6.1.1 + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.1))(svelte@5.20.2)(vite@6.1.1)': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.20.2)(vite@6.1.1) + debug: 4.4.0 + svelte: 5.20.2 + vite: 6.1.1 + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.1)': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.1))(svelte@5.20.2)(vite@6.1.1) + debug: 4.4.0 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.17 + svelte: 5.20.2 + vite: 6.1.1 + vitefu: 1.0.5(vite@6.1.1) + transitivePeerDependencies: + - supports-color + + '@tanstack/query-core@5.66.4': {} + + '@tanstack/svelte-query@5.66.4(svelte@5.20.2)': + dependencies: + '@tanstack/query-core': 5.66.4 + svelte: 5.20.2 + + '@types/cookie@0.6.0': {} + + '@types/estree@1.0.6': {} + + '@vitest/expect@3.0.6': + dependencies: + '@vitest/spy': 3.0.6 + '@vitest/utils': 3.0.6 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.0.6(vite@6.1.1)': + dependencies: + '@vitest/spy': 3.0.6 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.1.1 + + '@vitest/pretty-format@3.0.6': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.0.6': + dependencies: + '@vitest/utils': 3.0.6 + pathe: 2.0.3 + + '@vitest/snapshot@3.0.6': + dependencies: + '@vitest/pretty-format': 3.0.6 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.0.6': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@3.0.6': + dependencies: + '@vitest/pretty-format': 3.0.6 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + + acorn-typescript@1.4.13(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn@8.14.0: {} + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + asynckit@0.4.0: {} + + axios@1.7.9: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + axobject-query@4.1.0: {} + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + + check-error@2.1.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + clsx@2.1.1: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + cookie@0.6.0: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + deepmerge@4.3.1: {} + + delayed-stream@1.0.0: {} + + devalue@5.1.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.6.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + esm-env@1.2.2: {} + + esrap@1.4.5: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + expect-type@1.1.0: {} + + fdir@6.4.3: {} + + follow-redirects@1.15.9: {} + + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.2.7: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + import-meta-resolve@4.1.0: {} + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + kleur@4.1.5: {} + + locate-character@3.0.0: {} + + loupe@3.1.3: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mri@1.2.0: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.8: {} + + pathe@2.0.3: {} + + pathval@2.0.0: {} + + picocolors@1.1.1: {} + + postcss@8.5.2: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier-plugin-svelte@3.3.3(prettier@3.5.1)(svelte@5.20.2): + dependencies: + prettier: 3.5.1 + svelte: 5.20.2 + + prettier@3.5.1: {} + + proxy-from-env@1.1.0: {} + + readdirp@4.1.2: {} + + rollup@4.34.8: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.34.8 + '@rollup/rollup-android-arm64': 4.34.8 + '@rollup/rollup-darwin-arm64': 4.34.8 + '@rollup/rollup-darwin-x64': 4.34.8 + '@rollup/rollup-freebsd-arm64': 4.34.8 + '@rollup/rollup-freebsd-x64': 4.34.8 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.8 + '@rollup/rollup-linux-arm-musleabihf': 4.34.8 + '@rollup/rollup-linux-arm64-gnu': 4.34.8 + '@rollup/rollup-linux-arm64-musl': 4.34.8 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.8 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.8 + '@rollup/rollup-linux-riscv64-gnu': 4.34.8 + '@rollup/rollup-linux-s390x-gnu': 4.34.8 + '@rollup/rollup-linux-x64-gnu': 4.34.8 + '@rollup/rollup-linux-x64-musl': 4.34.8 + '@rollup/rollup-win32-arm64-msvc': 4.34.8 + '@rollup/rollup-win32-ia32-msvc': 4.34.8 + '@rollup/rollup-win32-x64-msvc': 4.34.8 + fsevents: 2.3.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + set-cookie-parser@2.7.1: {} + + siginfo@2.0.0: {} + + sirv@3.0.1: + dependencies: + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.1 + totalist: 3.0.1 + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.8.0: {} + + svelte-check@4.1.4(svelte@5.20.2)(typescript@5.7.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + chokidar: 4.0.3 + fdir: 6.4.3 + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.20.2 + typescript: 5.7.3 + transitivePeerDependencies: + - picomatch + + svelte@5.20.2: + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.6 + acorn: 8.14.0 + acorn-typescript: 1.4.13(acorn@8.14.0) + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + esm-env: 1.2.2 + esrap: 1.4.5 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.17 + zimmerframe: 1.1.2 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinypool@1.0.2: {} + + tinyrainbow@2.0.0: {} + + tinyspy@3.0.2: {} + + totalist@3.0.1: {} + + typescript@5.7.3: {} + + vite-node@3.0.6: + dependencies: + cac: 6.7.14 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 2.0.3 + vite: 6.1.1 + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.1.1: + dependencies: + esbuild: 0.24.2 + postcss: 8.5.2 + rollup: 4.34.8 + optionalDependencies: + fsevents: 2.3.3 + + vitefu@1.0.5(vite@6.1.1): + optionalDependencies: + vite: 6.1.1 + + vitest@3.0.6: + dependencies: + '@vitest/expect': 3.0.6 + '@vitest/mocker': 3.0.6(vite@6.1.1) + '@vitest/pretty-format': 3.0.6 + '@vitest/runner': 3.0.6 + '@vitest/snapshot': 3.0.6 + '@vitest/spy': 3.0.6 + '@vitest/utils': 3.0.6 + chai: 5.2.0 + debug: 4.4.0 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 2.0.3 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 6.1.1 + vite-node: 3.0.6 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + zimmerframe@1.1.2: {} diff --git a/client/src/app.d.ts b/client/src/app.d.ts new file mode 100755 index 0000000..1cea0dc --- /dev/null +++ b/client/src/app.d.ts @@ -0,0 +1,9 @@ +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +// and what to do when importing types +declare namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface Platform {} +} diff --git a/client/src/app.html b/client/src/app.html new file mode 100755 index 0000000..1cece31 --- /dev/null +++ b/client/src/app.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/client/src/routes/+layout.js b/client/src/routes/+layout.js new file mode 100644 index 0000000..189f71e --- /dev/null +++ b/client/src/routes/+layout.js @@ -0,0 +1 @@ +export const prerender = true; diff --git a/client/src/routes/+page.svelte b/client/src/routes/+page.svelte new file mode 100644 index 0000000..ff866ae --- /dev/null +++ b/client/src/routes/+page.svelte @@ -0,0 +1,45 @@ + + + +
+ +
+ + {#if tableName == "Jobs"} + + {:else if tableName == "Users"} + + {:else if tableName == "Vehicles"} + + {/if} +
+ + diff --git a/client/src/routes/jobs.svelte b/client/src/routes/jobs.svelte new file mode 100644 index 0000000..da4ef0a --- /dev/null +++ b/client/src/routes/jobs.svelte @@ -0,0 +1,93 @@ + + +
+ + + + + {#if row.id == null} + + {:else} + + {/if} +
+ +{#if $query.isLoading} + Loading... +{:else if $query.error} + An error has occurred: {$query.error} +{:else} + + + + + + + + + + + + + {#each $query.data as job} + + + + + + + + + + {/each} + +
IdClient NamePickupDropoffVehicleUser
{job.id}{job.clientName}{job.pickup}{job.dropoff}{job.VehicleId}{job.userId} + + +
+{/if} diff --git a/client/src/routes/users.svelte b/client/src/routes/users.svelte new file mode 100644 index 0000000..76e699a --- /dev/null +++ b/client/src/routes/users.svelte @@ -0,0 +1,82 @@ + + +
+ + + + {#if row.id == null} + + {:else} + + {/if} +
+ +{#if $query.isLoading} + Loading... +{:else if $query.error} + An error has occurred: {$query.error} +{:else} + + + + + + + + + + {#each $query.Database as user} + + + + + + + {/each} +
IdNameAge
{user.id}{user.name}{user.age} + + +
+{/if} diff --git a/client/src/routes/vehicles.svelte b/client/src/routes/vehicles.svelte new file mode 100644 index 0000000..72ba92b --- /dev/null +++ b/client/src/routes/vehicles.svelte @@ -0,0 +1,91 @@ + + +
+ + + + + {#if row.id == null} + + {:else} + + {/if} +
+ +{#if $query.isPending} + Loading... +{:else if $query.error} + An error has occurred: {$query.error} +{:else} + + + + + + + + + + + {#each $query.data as vehicle} + + + + + + + + {/each} + +
IdMakeModelYear
{vehicle.id}{vehicle.make}{vehicle.model}{vehicle.year} + + +
+{/if} diff --git a/client/static/favicon.png b/client/static/favicon.png new file mode 100755 index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097 GIT binary patch literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH options) : DbContext(options) +{ + public DbSet Users + { + get; set; + } + public DbSet Jobs + { + get; set; + } + public DbSet Notes + { + get; set; + } + public DbSet Vehicles + { + get; set; + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + } +} diff --git a/src/Common/ClaimsPrincipalExtensions.cs b/src/Common/ClaimsPrincipalExtensions.cs new file mode 100644 index 0000000..c73803d --- /dev/null +++ b/src/Common/ClaimsPrincipalExtensions.cs @@ -0,0 +1,14 @@ +namespace System.Security.Claims; + +public static class ClaimsPrincipalExtensions +{ + public static int GetUserId(this ClaimsPrincipal claimsPrincipal) + { + if (!int.TryParse(claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier), out var id)) + { + throw new InvalidOperationException("Invalid UserId"); + } + + return 0; + } +} diff --git a/src/Common/Job.cs b/src/Common/Job.cs new file mode 100755 index 0000000..9935a68 --- /dev/null +++ b/src/Common/Job.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TestWeb.Common; + +public class Job +{ + [Key] + public int Id + { + get; set; + } + public required string ClientName + { + get; set; + } + public required string Pickup + { + get; set; + } + public string? Dropoff + { + get; set; + } + public int VehicleId + { + get; set; + } + public DateTime CreatedAtUtc { get; private init; } = DateTime.UtcNow; + public DateTime? LastUpdatedAtUtc + { + get; set; + } +} diff --git a/src/Common/Jwt.cs b/src/Common/Jwt.cs new file mode 100644 index 0000000..e0d2251 --- /dev/null +++ b/src/Common/Jwt.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Text; + +namespace TestWeb.Common; + +public class JwtOptions +{ + public required string Key + { + get; init; + } +} + +public class Jwt(IOptions options) +{ + public string GenerateToken(User user) + { + var key = SecurityKey(options.Value.Key); + var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature); + + var token = new JwtSecurityToken + ( + claims: [new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())], + signingCredentials: new(key, SecurityAlgorithms.HmacSha256Signature), + expires: DateTime.UtcNow.AddYears(1) + ); + + return new JwtSecurityTokenHandler().WriteToken(token); + } + + public static SymmetricSecurityKey SecurityKey(string key) => new(Encoding.ASCII.GetBytes(key)); +} diff --git a/src/Common/Note.cs b/src/Common/Note.cs new file mode 100755 index 0000000..572caf3 --- /dev/null +++ b/src/Common/Note.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TestWeb.Common; + +public class Note +{ + [Key] + public int Id + { + get; private set; + } + public required string Content + { + get; set; + } + + public DateTime CreatedAtUtc { get; private init; } = DateTime.UtcNow; + public DateTime? LastUpdatedAtUtc + { + get; set; + } +} diff --git a/src/Common/RequestLoggingFilter.cs b/src/Common/RequestLoggingFilter.cs new file mode 100755 index 0000000..6cc7bda --- /dev/null +++ b/src/Common/RequestLoggingFilter.cs @@ -0,0 +1,13 @@ +namespace TestWeb.Common; + +public class RequestLoggingFilter(ILogger logger) : IEndpointFilter +{ + public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) + { + logger.LogInformation( + "HTTP {Method} {Path} recieved", + context.HttpContext.Request.Method, + context.HttpContext.Request.Path); + return await next(context); + } +} diff --git a/src/Common/RequestValidationFilter.cs b/src/Common/RequestValidationFilter.cs new file mode 100755 index 0000000..f1aa206 --- /dev/null +++ b/src/Common/RequestValidationFilter.cs @@ -0,0 +1,29 @@ +namespace TestWeb.Common; + +public class RequestValidationFilter( + ILogger> logger, + IValidator? validator = null) : IEndpointFilter +{ + public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) + { + var requestName = typeof(TRequest).FullName; + + if (validator is null) + { + logger.LogInformation("{Request}: No validator configured.", requestName); + return await next(context); + } + + logger.LogInformation("{Request}: Validating...", requestName); + var request = context.Arguments.OfType().First(); + var validationResult = await validator.ValidateAsync(request, context.HttpContext.RequestAborted); + if (!validationResult.IsValid) + { + logger.LogWarning("{Request}: Validation failed.", requestName); + return TypedResults.ValidationProblem(validationResult.ToDictionary()); + } + + logger.LogInformation("{Request}: Validation succeeded.", requestName); + return await next(context); + } +} diff --git a/src/Common/User.cs b/src/Common/User.cs new file mode 100755 index 0000000..7eaaeb9 --- /dev/null +++ b/src/Common/User.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TestWeb.Common; + +public class User +{ + [Key] + public int Id + { + get; private init; + } + public required string Username + { + get; set; + } + public required string Password + { + get; set; + } + public required string DisplayName + { + get; set; + } + public DateTime CreatedAtUtc { get; private init; } = DateTime.UtcNow; +} diff --git a/src/Common/ValidationExtensions.cs b/src/Common/ValidationExtensions.cs new file mode 100755 index 0000000..3206edc --- /dev/null +++ b/src/Common/ValidationExtensions.cs @@ -0,0 +1,11 @@ +namespace TestWeb.Common; + +public static class ValidationExtensions +{ + public static RouteHandlerBuilder WithRequestValidation(this RouteHandlerBuilder builder) + { + return builder + .AddEndpointFilter>() + .ProducesValidationProblem(); + } +} diff --git a/src/Common/Vehicle.cs b/src/Common/Vehicle.cs new file mode 100755 index 0000000..903b77d --- /dev/null +++ b/src/Common/Vehicle.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TestWeb.Common; + +public class Vehicle +{ + [Key] + public int Id + { + get; set; + } + public required string Make + { + get; set; + } + public required string Model + { + get; set; + } + public required int Year + { + get; set; + } + public DateTime CreatedAtUtc { get; private init; } = DateTime.UtcNow; + public DateTime? LastUpdatedAtUtc + { + get; set; + } +} diff --git a/src/Jobs/JobApi.cs b/src/Jobs/JobApi.cs new file mode 100644 index 0000000..d90844b --- /dev/null +++ b/src/Jobs/JobApi.cs @@ -0,0 +1,115 @@ + +namespace TestWeb.Jobs; + +public static class JobApi +{ + public static void MapEndpoints(IEndpointRouteBuilder endpoints) + { + var group = endpoints.MapGroup("/api/jobs").WithTags("Jobs").AllowAnonymous(); + group.MapPost("/", Create).WithSummary("Create a job"); + group.MapGet("/", GetList).WithSummary("Get job list"); + group.MapGet("/{id}", GetById).WithSummary("Get job by id"); + group.MapPut("/{id}", UpdateById).WithSummary("Update job by id"); + group.MapDelete("/{id}", DeleteById).WithSummary("Delete job by id"); + } + + public record CreateRequest(string ClientName); + + public class CreateRequestValidator : AbstractValidator + { + public CreateRequestValidator() + { + RuleFor(x => x.ClientName).NotEmpty().MaximumLength(250); + } + } + + public record CreateResponse(int Id); + + public static async Task> Create( + CreateRequest request, + AppDbContext db, + CancellationToken ct) + { + var row = new Job + { + ClientName = request.ClientName, + Pickup = "", + }; + + await db.Jobs.AddAsync(row, ct); + await db.SaveChangesAsync(ct); + + var response = new CreateResponse(row.Id); + return TypedResults.Ok(response); + } + + + public static async Task> GetList(AppDbContext db, CancellationToken ct) + { + var results = await db.Jobs.ToArrayAsync(ct); + return TypedResults.Ok(results); + } + + + public static async Task, NotFound>> GetById( + int id, + AppDbContext db, + CancellationToken ct) + { + var result = await db.Jobs + .Where(x => x.Id == id) + .SingleOrDefaultAsync(ct); + + return result is null + ? TypedResults.NotFound() + : TypedResults.Ok(result); + } + + + public record UpdateByIdRequest(string ClientName); + + public class UpdateByIdRequestValidator : AbstractValidator + { + public UpdateByIdRequestValidator() + { + RuleFor(x => x.ClientName).NotEmpty().MaximumLength(250); + } + } + + public static async Task> UpdateById( + int id, + UpdateByIdRequest request, + AppDbContext db, + CancellationToken ct) + { + var result = await db.Jobs + .Where(x => x.Id == id) + .SingleOrDefaultAsync(ct); + + if (result == null) + { + return TypedResults.NotFound(); + } + + result.ClientName = request.ClientName; + result.LastUpdatedAtUtc = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + + return TypedResults.Ok(); + } + + + public static async Task> DeleteById( + int id, + AppDbContext db, + CancellationToken ct) + { + var rowsDeleted = await db.Jobs + .Where(x => x.Id == id) + .ExecuteDeleteAsync(ct); + + return rowsDeleted == 1 + ? TypedResults.Ok() + : TypedResults.NotFound(); + } +} diff --git a/src/Jobs/JobApi.http b/src/Jobs/JobApi.http new file mode 100644 index 0000000..bab7f0d --- /dev/null +++ b/src/Jobs/JobApi.http @@ -0,0 +1,35 @@ +### + +POST http://localhost:5000/api/jobs HTTP/1.1 +Content-Type: application/json +Accept: / + +{ + "ClientName": "TestCreate", + "Pickup": "Test1", + "Dropoff": "Test2" +} + +### + +GET http://localhost:5000/api/jobs HTTP/1.1 + +### + +GET http://localhost:5000/api/jobs/1 HTTP/1.1 + +### + +PUT http://localhost:5000/api/jobs/1 HTTP/1.1 +Content-Type: application/json +Accept: / + +{ + "ClientName": "TestUpdate" +} + +### + +DELETE http://localhost:5000/api/jobs/1 HTTP/1.1 +Content-Type: application/json +Accept: / diff --git a/src/Notes/NoteApi.cs b/src/Notes/NoteApi.cs new file mode 100644 index 0000000..bc3f3ae --- /dev/null +++ b/src/Notes/NoteApi.cs @@ -0,0 +1,114 @@ + +namespace TestWeb.Notes; + +public static class NoteApi +{ + public static void MapEndpoints(IEndpointRouteBuilder endpoints) + { + var group = endpoints.MapGroup("/api/notes").WithTags("Notes").AllowAnonymous(); + group.MapPost("/", Create).WithSummary("Create a note"); + group.MapGet("/", GetList).WithSummary("Get note list"); + group.MapGet("/{id}", GetById).WithSummary("Get note by id"); + group.MapPut("/{id}", UpdateById).WithSummary("Update note by id"); + group.MapDelete("/{id}", DeleteById).WithSummary("Delete note by id"); + } + + public record CreateRequest(string Content); + + public class CreateRequestValidator : AbstractValidator + { + public CreateRequestValidator() + { + RuleFor(x => x.Content).NotEmpty().MaximumLength(250); + } + } + + public record CreateResponse(int Id); + + public static async Task> Create( + CreateRequest request, + AppDbContext db, + CancellationToken ct) + { + var row = new Note + { + Content = request.Content + }; + + await db.Notes.AddAsync(row, ct); + await db.SaveChangesAsync(ct); + + var response = new CreateResponse(row.Id); + return TypedResults.Ok(response); + } + + + public static async Task> GetList(AppDbContext db, CancellationToken ct) + { + var results = await db.Notes.ToArrayAsync(ct); + return TypedResults.Ok(results); + } + + + public static async Task, NotFound>> GetById( + int id, + AppDbContext db, + CancellationToken ct) + { + var result = await db.Notes + .Where(x => x.Id == id) + .SingleOrDefaultAsync(ct); + + return result is null + ? TypedResults.NotFound() + : TypedResults.Ok(result); + } + + + public record UpdateByIdRequest(string Content); + + public class UpdateByIdRequestValidator : AbstractValidator + { + public UpdateByIdRequestValidator() + { + RuleFor(x => x.Content).NotEmpty().MaximumLength(250); + } + } + + public static async Task> UpdateById( + int id, + UpdateByIdRequest request, + AppDbContext db, + CancellationToken ct) + { + var result = await db.Notes + .Where(x => x.Id == id) + .SingleOrDefaultAsync(ct); + + if (result == null) + { + return TypedResults.NotFound(); + } + + result.Content = request.Content; + result.LastUpdatedAtUtc = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + + return TypedResults.Ok(); + } + + + public static async Task> DeleteById( + int id, + AppDbContext db, + CancellationToken ct) + { + var rowsDeleted = await db.Notes + .Where(x => x.Id == id) + .ExecuteDeleteAsync(ct); + + return rowsDeleted == 1 + ? TypedResults.Ok() + : TypedResults.NotFound(); + } +} diff --git a/src/Notes/NoteApi.http b/src/Notes/NoteApi.http new file mode 100644 index 0000000..c452619 --- /dev/null +++ b/src/Notes/NoteApi.http @@ -0,0 +1,33 @@ +### + +POST http://localhost:5000/api/notes HTTP/1.1 +Content-Type: application/json +Accept: / + +{ + "Content": "Test" +} + +### + +GET http://localhost:5000/api/notes HTTP/1.1 + +### + +GET http://localhost:5000/api/notes/1 HTTP/1.1 + +### + +PUT http://localhost:5000/api/notes/1 HTTP/1.1 +Content-Type: application/json +Accept: / + +{ + "Content": "TestUpdate" +} + +### + +DELETE http://localhost:5000/api/notes/1 HTTP/1.1 +Content-Type: application/json +Accept: / diff --git a/src/Program.cs b/src/Program.cs new file mode 100755 index 0000000..95ffd2d --- /dev/null +++ b/src/Program.cs @@ -0,0 +1,118 @@ +global using Microsoft.EntityFrameworkCore; +global using Microsoft.AspNetCore.Authentication.JwtBearer; +global using Microsoft.AspNetCore.Http.HttpResults; +global using System.Security.Claims; +global using FluentValidation; +global using TestWeb.Common; + +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.AspNetCore.Identity; +using Microsoft.IdentityModel.Tokens; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using System.IdentityModel.Tokens.Jwt; +using System.Text; +using TestWeb.Jobs; +using TestWeb.Notes; +using TestWeb.Users; +using TestWeb.Vehicles; +using TestWeb.WeatherForecasts; + +namespace TestWeb; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + AddServices(builder); + + var app = builder.Build(); + Configure(app); + + app.Run(); + } + + public static void AddServices(WebApplicationBuilder builder) + { + builder.Services.AddDbContext( + options => options.UseSqlite("DataSource=app.db")); + + builder.Services.AddAuthentication().AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + IssuerSigningKey = Jwt.SecurityKey(builder.Configuration["Jwt:Key"]!), + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ClockSkew = TimeSpan.Zero + }; + }); + builder.Services.AddAuthorization(); + builder.Services.Configure(builder.Configuration.GetSection("Jwt")); + builder.Services.AddTransient(); + + builder.Services.AddOpenApi(); + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(options => + { + options.CustomSchemaIds(type => type.ToString()); + }); + + builder.Services.AddCors(options => + { + options.AddPolicy( + "CorsPolicy", + builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); + }); + + builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly); + } + + public static void Configure(WebApplication app) + { + app.MapOpenApi(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseDeveloperExceptionPage(); + app.UseCors("CorsPolicy"); + app.UseHttpsRedirection(); + app.UseRouting(); + app.UseAuthorization(); + + var apis = app.MapGroup("") + .AddEndpointFilter() + .RequireAuthorization(); + + var securityScheme = new OpenApiSecurityScheme() + { + Type = SecuritySchemeType.Http, + Name = JwtBearerDefaults.AuthenticationScheme, + Scheme = JwtBearerDefaults.AuthenticationScheme, + Reference = new() + { + Type = ReferenceType.SecurityScheme, + Id = JwtBearerDefaults.AuthenticationScheme + } + }; + apis.WithOpenApi(x => new(x) { Security = [new() { [securityScheme] = [] }] }); + + JobApi.MapEndpoints(apis); + NoteApi.MapEndpoints(apis); + UserApi.MapEndpoints(apis); + VehicleApi.MapEndpoints(apis); + WeatherForecastApi.MapEndpoints(apis); + + using var scope = app.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureCreated(); + } +} diff --git a/src/TestWeb.csproj b/src/TestWeb.csproj new file mode 100755 index 0000000..020f819 --- /dev/null +++ b/src/TestWeb.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + diff --git a/src/Users/UserApi.cs b/src/Users/UserApi.cs new file mode 100644 index 0000000..e6b6fe5 --- /dev/null +++ b/src/Users/UserApi.cs @@ -0,0 +1,41 @@ +namespace TestWeb.Users; + +public static class UserApi +{ + public static void MapEndpoints(IEndpointRouteBuilder endpoints) + { + var group = endpoints.MapGroup("/api/users").WithTags("Users").AllowAnonymous(); + group.MapPost("/login", Login).WithSummary("Log in a user").WithRequestValidation(); + } + + public record LoginRequest(string Username, string Password); + + public record LoginResponse(string Token); + + public class LoginRequestValidator : AbstractValidator + { + public LoginRequestValidator() + { + RuleFor(x => x.Username).NotEmpty(); + RuleFor(x => x.Password).NotEmpty(); + } + } + + private static async Task, UnauthorizedHttpResult>> Login( + LoginRequest request, + AppDbContext db, + Jwt jwt, + CancellationToken ct) + { + var user = await db.Users.SingleOrDefaultAsync(x => x.Username == request.Username && x.Password == request.Password, ct); + + if (user is null || user.Password != request.Password) + { + return TypedResults.Unauthorized(); + } + + var token = jwt.GenerateToken(user); + var response = new LoginResponse(token); + return TypedResults.Ok(response); + } +} diff --git a/src/Users/UserApi.http b/src/Users/UserApi.http new file mode 100644 index 0000000..b8ed9c3 --- /dev/null +++ b/src/Users/UserApi.http @@ -0,0 +1,9 @@ +### + +POST http://localhost:5000/api/users/test/login HTTP/1.1 +Content-Type: application/json +Accept: / + +{ + +} diff --git a/src/Vehicles/VehicleApi.cs b/src/Vehicles/VehicleApi.cs new file mode 100644 index 0000000..d3394fe --- /dev/null +++ b/src/Vehicles/VehicleApi.cs @@ -0,0 +1,115 @@ +namespace TestWeb.Vehicles; + +public static class VehicleApi +{ + public static void MapEndpoints(IEndpointRouteBuilder endpoints) + { + var group = endpoints.MapGroup("/api/vehicles").WithTags("Vehicles").AllowAnonymous(); + group.MapPost("/", Create).WithSummary("Create a vehicle"); + group.MapGet("/", GetList).WithSummary("Get vehicle list"); + group.MapGet("/{id}", GetById).WithSummary("Get vehicle by id"); + group.MapPut("/{id}", UpdateById).WithSummary("Update vehicle by id"); + group.MapDelete("/{id}", DeleteById).WithSummary("Delete vehicle by id"); + } + + public record CreateRequest(string Make); + + public class CreateRequestValidator : AbstractValidator + { + public CreateRequestValidator() + { + RuleFor(x => x.Make).NotEmpty().MaximumLength(250); + } + } + + public record CreateResponse(int Id); + + public static async Task> Create( + CreateRequest request, + AppDbContext db, + CancellationToken ct) + { + var row = new Vehicle + { + Make = request.Make, + Model = "", + Year = DateTime.Now.Year + }; + + await db.Vehicles.AddAsync(row, ct); + await db.SaveChangesAsync(ct); + + var response = new CreateResponse(row.Id); + return TypedResults.Ok(response); + } + + + public static async Task> GetList(AppDbContext db, CancellationToken ct) + { + var results = await db.Vehicles.ToArrayAsync(ct); + return TypedResults.Ok(results); + } + + + public static async Task, NotFound>> GetById( + int id, + AppDbContext db, + CancellationToken ct) + { + var result = await db.Vehicles + .Where(x => x.Id == id) + .SingleOrDefaultAsync(ct); + + return result is null + ? TypedResults.NotFound() + : TypedResults.Ok(result); + } + + + public record UpdateByIdRequest(string Make); + + public class UpdateByIdRequestValidator : AbstractValidator + { + public UpdateByIdRequestValidator() + { + RuleFor(x => x.Make).NotEmpty().MaximumLength(250); + } + } + + public static async Task> UpdateById( + int id, + UpdateByIdRequest request, + AppDbContext db, + CancellationToken ct) + { + var result = await db.Vehicles + .Where(x => x.Id == id) + .SingleOrDefaultAsync(ct); + + if (result == null) + { + return TypedResults.NotFound(); + } + + result.Make = request.Make; + result.LastUpdatedAtUtc = DateTime.UtcNow; + await db.SaveChangesAsync(ct); + + return TypedResults.Ok(); + } + + + public static async Task> DeleteById( + int id, + AppDbContext db, + CancellationToken ct) + { + var rowsDeleted = await db.Vehicles + .Where(x => x.Id == id) + .ExecuteDeleteAsync(ct); + + return rowsDeleted == 1 + ? TypedResults.Ok() + : TypedResults.NotFound(); + } +} diff --git a/src/Vehicles/VehicleApi.http b/src/Vehicles/VehicleApi.http new file mode 100644 index 0000000..acad685 --- /dev/null +++ b/src/Vehicles/VehicleApi.http @@ -0,0 +1,33 @@ +### + +POST http://localhost:5000/api/vehicles HTTP/1.1 +Content-Type: application/json +Accept: / + +{ + "Make": "Test" +} + +### + +GET http://localhost:5000/api/vehicles HTTP/1.1 + +### + +GET http://localhost:5000/api/vehicles/1 HTTP/1.1 + +### + +PUT http://localhost:5000/api/vehicles/1 HTTP/1.1 +Content-Type: application/json +Accept: / + +{ + "Make": "TestUpdate" +} + +### + +DELETE http://localhost:5000/api/vehicles/1 HTTP/1.1 +Content-Type: application/json +Accept: / diff --git a/src/WeatherForecasts/WeatherForecastApi.cs b/src/WeatherForecasts/WeatherForecastApi.cs new file mode 100644 index 0000000..39d5dcf --- /dev/null +++ b/src/WeatherForecasts/WeatherForecastApi.cs @@ -0,0 +1,31 @@ +namespace TestWeb.WeatherForecasts; + +public static class WeatherForecastApi +{ + public static void MapEndpoints(IEndpointRouteBuilder endpoints) + { + var group = endpoints.MapGroup("/api/weatherforecasts").WithTags("WeatherForecasts").AllowAnonymous(); + group.MapGet("/", GetList).WithSummary("Get weather forecast list"); + } + + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) + { + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } + + public static WeatherForecast[] GetList() + { + var random = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + random.Next(-20, 55), + Summaries[random.Next(Summaries.Length)] + )).ToArray(); + } +} diff --git a/src/WeatherForecasts/WeatherForecastApi.http b/src/WeatherForecasts/WeatherForecastApi.http new file mode 100644 index 0000000..3630674 --- /dev/null +++ b/src/WeatherForecasts/WeatherForecastApi.http @@ -0,0 +1,5 @@ +### + +GET http://localhost:5000/api/weatherforecasts HTTP/1.1 + +### diff --git a/src/appsettings.json b/src/appsettings.json new file mode 100755 index 0000000..70fb08c --- /dev/null +++ b/src/appsettings.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "System": "Information", + "Microsoft": "Information" + }, + "Console": { + "IncludeScopes": true + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "Default": "Server=.;Database=TestWeb;Trusted_Connection=True;Encrypt=False;" + }, + "Jwt": { + "Key": "superdupersecretkeythatsreallyreallylong" + } +} diff --git a/tests/TestWeb.Tests.csproj b/tests/TestWeb.Tests.csproj new file mode 100644 index 0000000..67e612f --- /dev/null +++ b/tests/TestWeb.Tests.csproj @@ -0,0 +1,26 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + diff --git a/tests/UnitTest1.cs b/tests/UnitTest1.cs new file mode 100644 index 0000000..31b6d09 --- /dev/null +++ b/tests/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace TestWeb.Tests; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +}