diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml
new file mode 100644
index 00000000..756cd995
--- /dev/null
+++ b/.github/workflows/test-backend.yml
@@ -0,0 +1,33 @@
+name: Test (backend)
+on:
+  push:
+    branches: [main]
+    paths: ["backend/**"]
+  pull_request:
+    paths: ["backend/**"]
+
+jobs:
+  lint:
+    name: Test
+    runs-on: ubuntu-latest
+    steps:
+      - name: Clone repository
+        uses: actions/checkout@v6
+
+      - uses: actions/setup-node@v6
+        with:
+          node-version: 24
+          cache: "npm"
+          cache-dependency-path: backend/package.json
+
+      - name: Install NPM packages
+        working-directory: backend
+        run: npm ci
+
+      - name: Run code generators
+        working-directory: backend
+        run: DATABASE_URL=placeholder npx prisma generate
+
+      - name: Test
+        working-directory: backend
+        run: npm run test
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index f181955d..1e5cdf9c 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -7,6 +7,7 @@
     "Prisma.prisma",
     "redhat.vscode-yaml",
     "dbaeumer.vscode-eslint",
-    "timonwong.shellcheck"
+    "timonwong.shellcheck",
+    "vitest.explorer"
   ]
 }
diff --git a/backend/eslint.config.js b/backend/eslint.config.js
index 04b07e07..98e1e033 100644
--- a/backend/eslint.config.js
+++ b/backend/eslint.config.js
@@ -52,7 +52,7 @@ export default defineConfig({
           },
           {
             from: "db",
-            allow: ["db/errors", "env"],
+            allow: ["db/errors"],
           },
           {
             from: "handlers",
@@ -70,7 +70,7 @@ export default defineConfig({
           },
           {
             from: "services",
-            allow: ["db/errors", "services/errors", "lib", "env"],
+            allow: ["db/errors", "services/errors", "lib"],
           },
           {
             from: ["lib", "handlers", "services"],
@@ -79,7 +79,7 @@ export default defineConfig({
           },
           {
             from: "services/index",
-            allow: ["db", "services"],
+            allow: ["db", "services", "env"],
           },
           {
             from: "*",
@@ -98,6 +98,10 @@ export default defineConfig({
             from: "index",
             allow: ["env", "db", "server", "services/index"],
           },
+          {
+            from: "test",
+            allow: "*",
+          },
         ],
       },
     ],
@@ -147,6 +151,8 @@ export default defineConfig({
       { type: "server", pattern: "server/**" },
       // Environment variables
       { type: "env", pattern: "src/lib/env.ts", mode: "full" },
+      // Vitest test files
+      { type: "test", pattern: "test/**" },
     ],
   },
 });
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 660e8138..36a12f9b 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -42,19 +42,23 @@
         "regclient-napi": "file:./regclient-napi"
       },
       "devDependencies": {
+        "@electric-sql/pglite": "^0.4.1",
         "@eslint/js": "^9.39.2",
         "@types/connect-pg-simple": "^7.0.3",
         "@types/cookie-parser": "^1.4.10",
+        "@types/emscripten": "^1.41.5",
         "@types/express": "^5.0.5",
         "@types/express-session": "^1.18.2",
         "@types/morgan": "^1.9.10",
         "@types/ws": "^8.18.1",
         "eslint": "^9.39.2",
         "eslint-plugin-boundaries": "^6.0.2",
+        "pglite-prisma-adapter": "^0.7.2",
         "prisma": "^7.3.0",
         "prisma-json-types-generator": "^4.0.0-beta.1",
         "typescript": "^5.9.3",
-        "typescript-eslint": "^8.53.0"
+        "typescript-eslint": "^8.53.0",
+        "vitest": "^4.1.4"
       }
     },
     "node_modules/@apidevtools/json-schema-ref-parser": {
@@ -119,6 +123,40 @@
         "@electric-sql/pglite": "0.4.1"
       }
     },
+    "node_modules/@emnapi/core": {
+      "version": "1.9.2",
+      "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
+      "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@emnapi/wasi-threads": "1.2.1",
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@emnapi/runtime": {
+      "version": "1.9.2",
+      "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
+      "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@emnapi/wasi-threads": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+      "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.9.1",
       "dev": true,
@@ -386,6 +424,13 @@
         "node": ">=18.0.0"
       }
     },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@js-sdsl/ordered-map": {
       "version": "4.4.2",
       "license": "MIT",
@@ -447,6 +492,25 @@
       "devOptional": true,
       "license": "MIT"
     },
+    "node_modules/@napi-rs/wasm-runtime": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz",
+      "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@tybys/wasm-util": "^0.10.1"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/Brooooooklyn"
+      },
+      "peerDependencies": {
+        "@emnapi/core": "^1.7.1",
+        "@emnapi/runtime": "^1.7.1"
+      }
+    },
     "node_modules/@npmcli/agent": {
       "version": "4.0.0",
       "dev": true,
@@ -1989,6 +2053,16 @@
         "@opentelemetry/api": "^1.1.0"
       }
     },
+    "node_modules/@oxc-project/types": {
+      "version": "0.124.0",
+      "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz",
+      "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==",
+      "dev": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/Boshen"
+      }
+    },
     "node_modules/@pinojs/redact": {
       "version": "0.4.0",
       "license": "MIT"
@@ -2477,6 +2551,270 @@
         }
       }
     },
+    "node_modules/@rolldown/binding-android-arm64": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-darwin-arm64": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-darwin-x64": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-freebsd-x64": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz",
+      "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-arm64-gnu": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-arm64-musl": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz",
+      "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-s390x-gnu": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-x64-gnu": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-x64-musl": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz",
+      "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-openharmony-arm64": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-wasm32-wasi": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz",
+      "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==",
+      "cpu": [
+        "wasm32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@emnapi/core": "1.9.2",
+        "@emnapi/runtime": "1.9.2",
+        "@napi-rs/wasm-runtime": "^1.1.3"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/@rolldown/binding-win32-arm64-msvc": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz",
+      "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-win32-x64-msvc": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz",
+      "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz",
+      "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@standard-schema/spec": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
@@ -2484,6 +2822,17 @@
       "devOptional": true,
       "license": "MIT"
     },
+    "node_modules/@tybys/wasm-util": {
+      "version": "0.10.1",
+      "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+      "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
     "node_modules/@types/aws-lambda": {
       "version": "8.10.159",
       "license": "MIT"
@@ -2504,6 +2853,17 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/chai": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+      "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/deep-eql": "*",
+        "assertion-error": "^2.0.1"
+      }
+    },
     "node_modules/@types/connect": {
       "version": "3.4.38",
       "license": "MIT",
@@ -2529,6 +2889,20 @@
         "@types/express": "*"
       }
     },
+    "node_modules/@types/deep-eql": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+      "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/emscripten": {
+      "version": "1.41.5",
+      "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz",
+      "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/estree": {
       "version": "1.0.8",
       "dev": true,
@@ -2937,6 +3311,119 @@
         "url": "https://opencollective.com/typescript-eslint"
       }
     },
+    "node_modules/@vitest/expect": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz",
+      "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@standard-schema/spec": "^1.1.0",
+        "@types/chai": "^5.2.2",
+        "@vitest/spy": "4.1.4",
+        "@vitest/utils": "4.1.4",
+        "chai": "^6.2.2",
+        "tinyrainbow": "^3.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/mocker": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz",
+      "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/spy": "4.1.4",
+        "estree-walker": "^3.0.3",
+        "magic-string": "^0.30.21"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      },
+      "peerDependencies": {
+        "msw": "^2.4.9",
+        "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "msw": {
+          "optional": true
+        },
+        "vite": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vitest/pretty-format": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz",
+      "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "tinyrainbow": "^3.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/runner": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz",
+      "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/utils": "4.1.4",
+        "pathe": "^2.0.3"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/snapshot": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz",
+      "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/pretty-format": "4.1.4",
+        "@vitest/utils": "4.1.4",
+        "magic-string": "^0.30.21",
+        "pathe": "^2.0.3"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/spy": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz",
+      "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==",
+      "dev": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/utils": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz",
+      "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/pretty-format": "4.1.4",
+        "convert-source-map": "^2.0.0",
+        "tinyrainbow": "^3.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
     "node_modules/@yarnpkg/lockfile": {
       "version": "1.1.0",
       "license": "BSD-2-Clause"
@@ -3047,6 +3534,16 @@
       "version": "2.0.1",
       "license": "Python-2.0"
     },
+    "node_modules/assertion-error": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+      "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "license": "MIT"
@@ -3312,6 +3809,16 @@
         "node": ">=6"
       }
     },
+    "node_modules/chai": {
+      "version": "6.2.2",
+      "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+      "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/chalk": {
       "version": "4.1.2",
       "license": "MIT",
@@ -3492,6 +3999,13 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/cookie": {
       "version": "0.7.2",
       "license": "MIT",
@@ -3626,6 +4140,16 @@
       "devOptional": true,
       "license": "MIT"
     },
+    "node_modules/detect-libc": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+      "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/dotenv": {
       "version": "16.6.1",
       "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@@ -3740,6 +4264,13 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/es-module-lexer": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
+      "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/es-object-atoms": {
       "version": "1.1.1",
       "license": "MIT",
@@ -4021,6 +4552,16 @@
         "node": ">=4.0"
       }
     },
+    "node_modules/estree-walker": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "^1.0.0"
+      }
+    },
     "node_modules/esutils": {
       "version": "2.0.3",
       "dev": true,
@@ -4036,6 +4577,16 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/expect-type": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+      "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
     "node_modules/exponential-backoff": {
       "version": "3.1.3",
       "dev": true,
@@ -4386,6 +4937,21 @@
         "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
       }
     },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
     "node_modules/function-bind": {
       "version": "1.1.2",
       "license": "MIT",
@@ -5017,6 +5583,267 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/lightningcss": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+      "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+      "dev": true,
+      "license": "MPL-2.0",
+      "dependencies": {
+        "detect-libc": "^2.0.3"
+      },
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "lightningcss-android-arm64": "1.32.0",
+        "lightningcss-darwin-arm64": "1.32.0",
+        "lightningcss-darwin-x64": "1.32.0",
+        "lightningcss-freebsd-x64": "1.32.0",
+        "lightningcss-linux-arm-gnueabihf": "1.32.0",
+        "lightningcss-linux-arm64-gnu": "1.32.0",
+        "lightningcss-linux-arm64-musl": "1.32.0",
+        "lightningcss-linux-x64-gnu": "1.32.0",
+        "lightningcss-linux-x64-musl": "1.32.0",
+        "lightningcss-win32-arm64-msvc": "1.32.0",
+        "lightningcss-win32-x64-msvc": "1.32.0"
+      }
+    },
+    "node_modules/lightningcss-android-arm64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+      "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-darwin-arm64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+      "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-darwin-x64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+      "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-freebsd-x64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+      "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm-gnueabihf": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+      "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm64-gnu": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+      "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm64-musl": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+      "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-x64-gnu": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+      "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-x64-musl": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+      "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-win32-arm64-msvc": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+      "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-win32-x64-msvc": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+      "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
     "node_modules/locate-path": {
       "version": "6.0.0",
       "dev": true,
@@ -5070,6 +5897,16 @@
         "url": "https://github.com/sponsors/wellwelwel"
       }
     },
+    "node_modules/magic-string": {
+      "version": "0.30.21",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+      "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
     "node_modules/make-fetch-happen": {
       "version": "15.0.3",
       "dev": true,
@@ -5369,6 +6206,25 @@
         "node": ">=12"
       }
     },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
     "node_modules/natural-compare": {
       "version": "1.4.0",
       "dev": true,
@@ -5529,6 +6385,17 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/obug": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+      "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+      "dev": true,
+      "funding": [
+        "https://github.com/sponsors/sxzz",
+        "https://opencollective.com/debug"
+      ],
+      "license": "MIT"
+    },
     "node_modules/octokit": {
       "version": "5.0.5",
       "license": "MIT",
@@ -5882,6 +6749,48 @@
         "node": ">=4"
       }
     },
+    "node_modules/pglite-prisma-adapter": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/pglite-prisma-adapter/-/pglite-prisma-adapter-0.7.2.tgz",
+      "integrity": "sha512-NqLXIUguFbgEHpIoyCvAbpEurbK+AUukiv96gIogVnNYWBdhAU0idfTXcJrsS7OdWenYa8QxEQM2ND1cTQrU4g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@prisma/driver-adapter-utils": "7.2.0",
+        "postgres-array": "^3.0.4"
+      },
+      "peerDependencies": {
+        "@electric-sql/pglite": ">=0.2.0",
+        "@prisma/client": ">= 7.1.0"
+      }
+    },
+    "node_modules/pglite-prisma-adapter/node_modules/@prisma/debug": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
+      "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
+      "dev": true,
+      "license": "Apache-2.0"
+    },
+    "node_modules/pglite-prisma-adapter/node_modules/@prisma/driver-adapter-utils": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.2.0.tgz",
+      "integrity": "sha512-gzrUcbI9VmHS24Uf+0+7DNzdIw7keglJsD5m/MHxQOU68OhGVzlphQRobLiDMn8CHNA2XN8uugwKjudVtnfMVQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@prisma/debug": "7.2.0"
+      }
+    },
+    "node_modules/pglite-prisma-adapter/node_modules/postgres-array": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
+      "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/pgpass": {
       "version": "1.0.5",
       "license": "MIT",
@@ -5889,6 +6798,13 @@
         "split2": "^4.1.0"
       }
     },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
+      "license": "ISC"
+    },
     "node_modules/picomatch": {
       "version": "2.3.2",
       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
@@ -5954,6 +6870,35 @@
         "pathe": "^2.0.3"
       }
     },
+    "node_modules/postcss": {
+      "version": "8.5.9",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
+      "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
     "node_modules/postgres": {
       "version": "3.4.7",
       "devOptional": true,
@@ -6367,6 +7312,40 @@
       "version": "1.5.4",
       "license": "MIT"
     },
+    "node_modules/rolldown": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz",
+      "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@oxc-project/types": "=0.124.0",
+        "@rolldown/pluginutils": "1.0.0-rc.15"
+      },
+      "bin": {
+        "rolldown": "bin/cli.mjs"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "optionalDependencies": {
+        "@rolldown/binding-android-arm64": "1.0.0-rc.15",
+        "@rolldown/binding-darwin-arm64": "1.0.0-rc.15",
+        "@rolldown/binding-darwin-x64": "1.0.0-rc.15",
+        "@rolldown/binding-freebsd-x64": "1.0.0-rc.15",
+        "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15",
+        "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15",
+        "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15",
+        "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15",
+        "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15",
+        "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15",
+        "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15"
+      }
+    },
     "node_modules/router": {
       "version": "2.2.0",
       "license": "MIT",
@@ -6565,6 +7544,13 @@
         "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,
+      "license": "ISC"
+    },
     "node_modules/signal-exit": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -6634,6 +7620,16 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/split2": {
       "version": "4.2.0",
       "license": "ISC",
@@ -6664,6 +7660,13 @@
         "node": "^20.17.0 || >=22.9.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,
+      "license": "MIT"
+    },
     "node_modules/statuses": {
       "version": "2.0.2",
       "license": "MIT",
@@ -6797,6 +7800,13 @@
         "node": ">=20"
       }
     },
+    "node_modules/tinybench": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+      "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/tinyexec": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz",
@@ -6851,6 +7861,16 @@
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
+    "node_modules/tinyrainbow": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+      "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
     "node_modules/tmp": {
       "version": "0.2.5",
       "license": "MIT",
@@ -7080,6 +8100,207 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/vite": {
+      "version": "8.0.8",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz",
+      "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "lightningcss": "^1.32.0",
+        "picomatch": "^4.0.4",
+        "postcss": "^8.5.8",
+        "rolldown": "1.0.0-rc.15",
+        "tinyglobby": "^0.2.15"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^20.19.0 || >=22.12.0",
+        "@vitejs/devtools": "^0.1.0",
+        "esbuild": "^0.27.0 || ^0.28.0",
+        "jiti": ">=1.21.0",
+        "less": "^4.0.0",
+        "sass": "^1.70.0",
+        "sass-embedded": "^1.70.0",
+        "stylus": ">=0.54.8",
+        "sugarss": "^5.0.0",
+        "terser": "^5.16.0",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "@vitejs/devtools": {
+          "optional": true
+        },
+        "esbuild": {
+          "optional": true
+        },
+        "jiti": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vite/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/vitest": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz",
+      "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vitest/expect": "4.1.4",
+        "@vitest/mocker": "4.1.4",
+        "@vitest/pretty-format": "4.1.4",
+        "@vitest/runner": "4.1.4",
+        "@vitest/snapshot": "4.1.4",
+        "@vitest/spy": "4.1.4",
+        "@vitest/utils": "4.1.4",
+        "es-module-lexer": "^2.0.0",
+        "expect-type": "^1.3.0",
+        "magic-string": "^0.30.21",
+        "obug": "^2.1.1",
+        "pathe": "^2.0.3",
+        "picomatch": "^4.0.3",
+        "std-env": "^4.0.0-rc.1",
+        "tinybench": "^2.9.0",
+        "tinyexec": "^1.0.2",
+        "tinyglobby": "^0.2.15",
+        "tinyrainbow": "^3.1.0",
+        "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+        "why-is-node-running": "^2.3.0"
+      },
+      "bin": {
+        "vitest": "vitest.mjs"
+      },
+      "engines": {
+        "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      },
+      "peerDependencies": {
+        "@edge-runtime/vm": "*",
+        "@opentelemetry/api": "^1.9.0",
+        "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+        "@vitest/browser-playwright": "4.1.4",
+        "@vitest/browser-preview": "4.1.4",
+        "@vitest/browser-webdriverio": "4.1.4",
+        "@vitest/coverage-istanbul": "4.1.4",
+        "@vitest/coverage-v8": "4.1.4",
+        "@vitest/ui": "4.1.4",
+        "happy-dom": "*",
+        "jsdom": "*",
+        "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@edge-runtime/vm": {
+          "optional": true
+        },
+        "@opentelemetry/api": {
+          "optional": true
+        },
+        "@types/node": {
+          "optional": true
+        },
+        "@vitest/browser-playwright": {
+          "optional": true
+        },
+        "@vitest/browser-preview": {
+          "optional": true
+        },
+        "@vitest/browser-webdriverio": {
+          "optional": true
+        },
+        "@vitest/coverage-istanbul": {
+          "optional": true
+        },
+        "@vitest/coverage-v8": {
+          "optional": true
+        },
+        "@vitest/ui": {
+          "optional": true
+        },
+        "happy-dom": {
+          "optional": true
+        },
+        "jsdom": {
+          "optional": true
+        },
+        "vite": {
+          "optional": false
+        }
+      }
+    },
+    "node_modules/vitest/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/vitest/node_modules/std-env": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz",
+      "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/webidl-conversions": {
       "version": "3.0.1",
       "license": "BSD-2-Clause"
@@ -7105,6 +8326,23 @@
         "node": ">= 8"
       }
     },
+    "node_modules/why-is-node-running": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+      "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "siginfo": "^2.0.0",
+        "stackback": "0.0.2"
+      },
+      "bin": {
+        "why-is-node-running": "cli.js"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/word-wrap": {
       "version": "1.2.5",
       "dev": true,
diff --git a/backend/package.json b/backend/package.json
index 1c674800..0486f315 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -8,7 +8,8 @@
     "prisma:generate": "prisma generate",
     "dev": "node --env-file=.env --experimental-strip-types --watch src/index.ts",
     "lint": "tsc --noEmit && eslint .",
-    "postinstall": "patch-package"
+    "postinstall": "patch-package",
+    "test": "vitest"
   },
   "author": "",
   "license": "UNLICENSED",
@@ -46,19 +47,23 @@
     "regclient-napi": "file:./regclient-napi"
   },
   "devDependencies": {
+    "@electric-sql/pglite": "^0.4.1",
     "@eslint/js": "^9.39.2",
     "@types/connect-pg-simple": "^7.0.3",
     "@types/cookie-parser": "^1.4.10",
+    "@types/emscripten": "^1.41.5",
     "@types/express": "^5.0.5",
     "@types/express-session": "^1.18.2",
     "@types/morgan": "^1.9.10",
     "@types/ws": "^8.18.1",
     "eslint": "^9.39.2",
     "eslint-plugin-boundaries": "^6.0.2",
+    "pglite-prisma-adapter": "^0.7.2",
     "prisma": "^7.3.0",
     "prisma-json-types-generator": "^4.0.0-beta.1",
     "typescript": "^5.9.3",
-    "typescript-eslint": "^8.53.0"
+    "typescript-eslint": "^8.53.0",
+    "vitest": "^4.1.4"
   },
   "overrides": {
     "@hono/node-server": "^1.19.10",
diff --git a/backend/src/db/crypto.ts b/backend/src/db/crypto.ts
index 878bca25..be2c73d7 100644
--- a/backend/src/db/crypto.ts
+++ b/backend/src/db/crypto.ts
@@ -1,16 +1,14 @@
 import crypto, { createCipheriv, createDecipheriv } from "node:crypto";
-import { env } from "../lib/env.ts";
 
-const masterKey = Buffer.from(env.FIELD_ENCRYPTION_KEY, "base64");
 const separator = "|";
 
-const unwrapKey = (wrapped: string): Buffer => {
+const unwrapKey = (masterKey: string, wrapped: string): Buffer => {
   const iv = Buffer.alloc(8, 0xa6); // Recommended default initial value
   const decipher = createDecipheriv("aes256-wrap", masterKey, iv);
   return Buffer.concat([decipher.update(wrapped, "base64"), decipher.final()]);
 };
 
-const wrapKey = (key: Buffer): string => {
+const wrapKey = (masterKey: string, key: Buffer): string => {
   const iv = Buffer.alloc(8, 0xa6);
   const cipher = createCipheriv("aes256-wrap", masterKey, iv);
   return cipher.update(key, undefined, "base64") + cipher.final("base64");
@@ -41,20 +39,28 @@ const genKey = (): Buffer => {
   return crypto.randomBytes(32);
 };
 
-export const encryptEnv = (plaintext: PrismaJson.EnvVar[], key: string) => {
-  const unwrapped = unwrapKey(key);
+export const encryptEnv = (
+  masterKey: string,
+  plaintext: PrismaJson.EnvVar[],
+  key: string,
+) => {
+  const unwrapped = unwrapKey(masterKey, key);
   return plaintext.map((envVar) => ({
     ...envVar,
     value: encrypt(envVar.value, unwrapped),
   }));
 };
 
-export const decryptEnv = (ciphertext: PrismaJson.EnvVar[], key: string) => {
-  const unwrapped = unwrapKey(key);
+export const decryptEnv = (
+  masterKey: string,
+  ciphertext: PrismaJson.EnvVar[],
+  key: string,
+) => {
+  const unwrapped = unwrapKey(masterKey, key);
   return ciphertext.map((envVar) => ({
     ...envVar,
     value: decrypt(envVar.value, unwrapped),
   }));
 };
 
-export const generateKey = () => wrapKey(genKey());
+export const generateKey = (masterKey: string) => wrapKey(masterKey, genKey());
diff --git a/backend/src/db/index.ts b/backend/src/db/index.ts
index 9397702c..e4ad66b9 100644
--- a/backend/src/db/index.ts
+++ b/backend/src/db/index.ts
@@ -4,7 +4,6 @@ import connectPgSimple from "connect-pg-simple";
 import session from "express-session";
 import { Pool, type Notification } from "pg";
 import { PrismaClient } from "../generated/prisma/client.ts";
-import { env } from "../lib/env.ts";
 import { AppRepo } from "./repo/app.ts";
 import { AppGroupRepo } from "./repo/appGroup.ts";
 import { CacheRepo } from "./repo/cache.ts";
@@ -24,58 +23,101 @@ export type PrismaClientType = PrismaClient<
   DefaultArgs
 >;
 
+export abstract class Database {
+  abstract app: AppRepo;
+  abstract appGroup: AppGroupRepo;
+  abstract cache: CacheRepo;
+  abstract deployment: DeploymentRepo;
+  abstract invitation: InvitationRepo;
+  abstract org: OrganizationRepo;
+  abstract repoImportState: RepoImportStateRepo;
+  abstract user: UserRepo;
+  abstract sessionStore: session.Store;
+  abstract subscribe(
+    channel: string,
+    callback: (msg: Notification) => void,
+  ): Promise<() => Promise<void>>;
+  abstract publish(channel: string, payload: string): Promise<void>;
+}
+
 /**
- * A Postgres database implementation
+ * A Prisma + Postgres database implementation
  */
-export class Database {
-  private DATABASE_URL =
-    env.DATABASE_URL ??
-    `postgresql://${env.POSTGRES_USER}:${env.POSTGRES_PASSWORD}@${env.POSTGRES_HOSTNAME}/${env.POSTGRES_DB}`;
-
-  private pool = new Pool({
-    connectionString: this.DATABASE_URL,
-    connectionTimeoutMillis: 5000,
-  });
-
-  private prismaPostgresAdapter = new PrismaPg({
-    connectionString: this.DATABASE_URL,
-  });
-
-  private client: PrismaClientType = new PrismaClient({
-    adapter: this.prismaPostgresAdapter,
-    omit: {
-      deployment: {
-        secret: true,
-      },
-    },
-  });
-
-  app = new AppRepo(this.client);
-
-  appGroup = new AppGroupRepo(this.client);
-
-  cache = new CacheRepo(this.client);
-
-  deployment = new DeploymentRepo(this.client, this.publish.bind(this));
-
-  invitation = new InvitationRepo(this.client);
+export class PrismaDatabase extends Database {
+  private client: PrismaClientType;
+  app: AppRepo;
+  appGroup: AppGroupRepo;
+  cache: CacheRepo;
+  deployment: DeploymentRepo;
+  invitation: InvitationRepo;
+  org: OrganizationRepo;
+  repoImportState: RepoImportStateRepo;
+  user: UserRepo;
+  sessionStore: session.Store;
+
+  constructor(client: PrismaClientType, masterKey: string) {
+    super();
+    this.client = client;
+    this.app = new AppRepo(this.client, this.deployment);
+    this.appGroup = new AppGroupRepo(this.client);
+    this.cache = new CacheRepo(this.client);
+    this.deployment = new DeploymentRepo(
+      this.client,
+      this.publish.bind(this),
+      masterKey,
+    );
+    this.invitation = new InvitationRepo(this.client);
+    this.org = new OrganizationRepo(this.client);
+    this.repoImportState = new RepoImportStateRepo(this.client);
+    this.user = new UserRepo(this.client);
+  }
 
-  org = new OrganizationRepo(this.client);
+  /* eslint-disable @typescript-eslint/no-unused-vars */
+  subscribe(
+    channel: string,
+    callback: (msg: Notification) => void,
+  ): Promise<() => Promise<void>> {
+    throw new Error("Method not implemented.");
+  }
+  publish(channel: string, payload: string): Promise<void> {
+    throw new Error("Method not implemented.");
+  }
+  /* eslint-enable @typescript-eslint/no-unused-vars */
+}
 
-  repoImportState = new RepoImportStateRepo(this.client);
+export class PgDatabase extends PrismaDatabase {
+  private pool: Pool;
 
-  user = new UserRepo(this.client);
+  constructor(connectionString: string, masterKey: string) {
+    const prismaPostgresAdapter = new PrismaPg({ connectionString });
 
-  sessionStore = new (connectPgSimple(session))({
-    conString: this.DATABASE_URL,
-  });
+    const client = new PrismaClient({
+      adapter: prismaPostgresAdapter,
+      omit: {
+        deployment: {
+          secret: true,
+        },
+      },
+    });
+    super(client, masterKey);
+    this.pool = new Pool({
+      connectionString,
+      connectionTimeoutMillis: 5000,
+    });
+    this.sessionStore = new (connectPgSimple(session))({
+      conString: connectionString,
+    });
+  }
 
   /**
    * Subscribes to the given channel and runs the callback when a message is received on that channel.
    *
    * @returns A cleanup function to remove the listener
    */
-  async subscribe(channel: string, callback: (msg: Notification) => void) {
+  override async subscribe(
+    channel: string,
+    callback: (msg: Notification) => void,
+  ) {
     const conn = await this.pool.connect();
     if (!channel.match(/^[a-zA-Z0-9_]+$/g)) {
       // Sanitize against potential SQL injection. Postgres unfortunately doesn't provide a way to parameterize the
@@ -109,9 +151,7 @@ export class Database {
    * @param channel The channel to publish on
    * @param payload The message to publish
    */
-  async publish(channel: string, payload: string) {
+  override async publish(channel: string, payload: string) {
     await this.pool.query("SELECT pg_notify($1, $2);", [channel, payload]);
   }
 }
-
-export const db = new Database();
diff --git a/backend/src/db/repo/app.ts b/backend/src/db/repo/app.ts
index 608c5a69..3a98a50f 100644
--- a/backend/src/db/repo/app.ts
+++ b/backend/src/db/repo/app.ts
@@ -20,9 +20,11 @@ import { DeploymentRepo } from "./deployment.ts";
 
 export class AppRepo {
   private client: PrismaClientType;
+  private deploymentRepo: DeploymentRepo;
 
-  constructor(client: PrismaClientType) {
+  constructor(client: PrismaClientType, deploymentRepo: DeploymentRepo) {
     this.client = client;
+    this.deploymentRepo = deploymentRepo;
   }
 
   async getById(
@@ -251,7 +253,7 @@ export class AppRepo {
       },
     });
 
-    return DeploymentRepo.preprocessConfig(app.config);
+    return this.deploymentRepo.preprocessConfig(app.config);
   }
 
   async setConfig(appId: number, configId: number) {
@@ -290,7 +292,7 @@ export class AppRepo {
 
     return deployments.map((deployment) => ({
       ...deployment,
-      config: DeploymentRepo.preprocessConfig(deployment.config),
+      config: this.deploymentRepo.preprocessConfig(deployment.config),
     }));
   }
 
diff --git a/backend/src/db/repo/deployment.ts b/backend/src/db/repo/deployment.ts
index 07a55c5a..63a328d1 100644
--- a/backend/src/db/repo/deployment.ts
+++ b/backend/src/db/repo/deployment.ts
@@ -28,13 +28,16 @@ type PrismaWorkloadConfigCreate = Omit<WorkloadConfigCreate, "appType">;
 export class DeploymentRepo {
   private client: PrismaClientType;
   private publish: (topic: string, payload: string) => Promise<void>;
+  private masterKey: string;
 
   constructor(
     client: PrismaClientType,
     publish: (topic: string, payload: string) => Promise<void>,
+    masterKey: string,
   ) {
     this.client = client;
     this.publish = publish;
+    this.masterKey = masterKey;
   }
 
   async getById(
@@ -108,7 +111,7 @@ export class DeploymentRepo {
             ...(appType === "workload"
               ? {
                   workloadConfig: {
-                    create: DeploymentRepo.encryptEnv(configClone),
+                    create: this.encryptEnv(configClone),
                   },
                 }
               : {
@@ -190,19 +193,19 @@ export class DeploymentRepo {
       },
     });
 
-    return DeploymentRepo.preprocessConfig(deployment.config);
+    return this.preprocessConfig(deployment.config);
   }
 
-  private static encryptEnv(
+  private encryptEnv(
     config: PrismaWorkloadConfigCreate,
   ): WorkloadConfigCreateInput {
     const copy = structuredClone(config) as WorkloadConfigCreateInput;
-    copy.envKey = generateKey();
-    copy.env = encryptEnv(copy.env, copy.envKey);
+    copy.envKey = generateKey(this.masterKey);
+    copy.env = encryptEnv(this.masterKey, copy.env, copy.envKey);
     return copy;
   }
 
-  static preprocessConfig(config: {
+  preprocessConfig(config: {
     appType: AppType;
     workloadConfig?: Omit<PrismaWorkloadConfig, "id" | "deploymentConfigId">;
     helmConfig?: Omit<PrismaHelmConfig, "id" | "deploymentConfigId">;
@@ -213,7 +216,7 @@ export class DeploymentRepo {
 
     let obj: WorkloadConfig | HelmConfig;
     if (config.appType === "workload") {
-      obj = DeploymentRepo.preprocessWorkloadConfig(config.workloadConfig);
+      obj = this.preprocessWorkloadConfig(config.workloadConfig);
     } else if (config.appType === "helm") {
       obj = {
         ...config.helmConfig,
@@ -248,7 +251,7 @@ export class DeploymentRepo {
     return wrapped;
   }
 
-  private static preprocessWorkloadConfig(
+  private preprocessWorkloadConfig(
     config: Omit<PrismaWorkloadConfig, "id" | "deploymentConfigId">,
   ): WorkloadConfig {
     if (config === null) {
@@ -260,7 +263,7 @@ export class DeploymentRepo {
     delete config.envKey;
     delete config.env;
 
-    const decrypted = decryptEnv(env, key);
+    const decrypted = decryptEnv(this.masterKey, env, key);
 
     const obj = {
       ...config,
diff --git a/backend/src/index.ts b/backend/src/index.ts
index 7568bd4b..21def814 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -7,12 +7,11 @@ import session from "express-session";
 import { existsSync, statSync } from "node:fs";
 import path from "node:path";
 import { pinoHttp } from "pino-http";
-import { db } from "./db/index.ts";
 import { env } from "./lib/env.ts";
 import { logger } from "./logger.ts";
 import apiHandler, { openApiSpecPath } from "./server/api.ts";
 import apiRouter, { SESSION_COOKIE_NAME } from "./server/auth.ts";
-import { getSettingsService } from "./service/index.ts";
+import { db, getSettingsService } from "./service/index.ts";
 
 const app = express();
 const port = process.env.PORT ?? 3000;
diff --git a/backend/src/service/auth.ts b/backend/src/service/auth.ts
index 5aeadf2c..df9ee19e 100644
--- a/backend/src/service/auth.ts
+++ b/backend/src/service/auth.ts
@@ -1,6 +1,5 @@
 import * as client from "openid-client";
 import type { UserRepo } from "../db/repo/user.ts";
-import { env, parseCsv } from "../lib/env.ts";
 import { logger } from "../logger.ts";
 import type { RancherService } from "./common/cluster/rancher.ts";
 import { InvalidIDPError, RancherIDNotFoundError } from "./errors/index.ts";
@@ -10,39 +9,59 @@ export class AuthService {
   private rancherService: RancherService;
   private config: Promise<{ oidcConfig: client.Configuration; scope: string }>;
 
-  constructor(userRepo: UserRepo, rancherService: RancherService) {
+  private usingRancherOIDC: boolean;
+  private baseURL: string;
+  private rancherBaseURL: string;
+  private ciLogonAllowedIdps: string[];
+  private clientId: string;
+  private clientSecret: string;
+  private loginClaim: string;
+
+  constructor(
+    userRepo: UserRepo,
+    rancherService: RancherService,
+    useRancherOIDC: boolean,
+    baseURL: string,
+    rancherBaseURL: string,
+    ciLogonAllowedIdps: string[],
+    clientId: string,
+    clientSecret: string,
+    loginClaim: string,
+  ) {
     this.userRepo = userRepo;
     this.rancherService = rancherService;
     this.config = this.getOIDCConfig();
-  }
-
-  usingRancherOIDC() {
-    return (
-      this.rancherService.isRancherManaged() && env.USE_RANCHER_OIDC == "true"
-    );
+    this.usingRancherOIDC =
+      this.rancherService.isRancherManaged() && useRancherOIDC;
+    this.baseURL = baseURL;
+    this.rancherBaseURL = rancherBaseURL;
+    this.ciLogonAllowedIdps = ciLogonAllowedIdps;
+    this.clientId = clientId;
+    this.clientSecret = clientSecret;
+    this.loginClaim = loginClaim;
   }
 
   usingCILogon() {
-    return !this.usingRancherOIDC();
+    return !this.usingRancherOIDC;
   }
 
   getLogoutURL() {
     return this.usingCILogon()
       ? "https://cilogon.org/logout/?skin=access"
-      : env.BASE_URL;
+      : this.baseURL;
   }
 
   async getOIDCConfig() {
-    if (this.usingRancherOIDC()) {
+    if (this.usingRancherOIDC) {
       const server = new URL(
-        `${env.RANCHER_BASE_URL}/oidc/.well-known/openid-configuration`,
+        `${this.rancherBaseURL}/oidc/.well-known/openid-configuration`,
       );
       return {
         scope: "openid profile",
         oidcConfig: await client.discovery(
           server,
-          env.CLIENT_ID,
-          env.CLIENT_SECRET,
+          this.clientId,
+          this.clientSecret,
         ),
       };
     } else {
@@ -53,8 +72,8 @@ export class AuthService {
         scope: "openid email profile org.cilogon.userinfo",
         oidcConfig: await client.discovery(
           server,
-          env.CLIENT_ID,
-          env.CLIENT_SECRET,
+          this.clientId,
+          this.clientSecret,
         ),
       };
     }
@@ -77,11 +96,11 @@ export class AuthService {
       code_challenge,
       code_challenge_method: "S256",
       scope,
-      redirect_uri: env.BASE_URL + "/api/oauth_callback",
+      redirect_uri: this.baseURL + "/api/oauth_callback",
       ...(nonce && { nonce }),
       ...(this.usingCILogon() && {
-        selected_idp: env.ALLOWED_IDPS,
-        idp_hint: env.ALLOWED_IDPS,
+        selected_idp: this.ciLogonAllowedIdps[0],
+        idp_hint: this.ciLogonAllowedIdps[0],
       }),
     };
 
@@ -89,8 +108,6 @@ export class AuthService {
     return { redirect_to: redirect_to.toString(), nonce, code_verifier };
   }
 
-  private ciLogonAllowedIdps = parseCsv(env.ALLOWED_IDPS) ?? [];
-
   async handleOAuthCallback(
     currentUrl: string,
     pkceCodeVerifier: string,
@@ -136,7 +153,7 @@ export class AuthService {
   }
 
   async getClusterUsername(claims: client.IDToken) {
-    if (this.usingRancherOIDC()) {
+    if (this.usingRancherOIDC) {
       return claims.sub;
     }
 
@@ -160,6 +177,6 @@ export class AuthService {
       const email = claims.email as string | undefined;
       return email?.replace("@purdue.edu", "");
     }
-    return claims[env.LOGIN_CLAIM] as string;
+    return claims[this.loginClaim] as string;
   }
 }
diff --git a/backend/src/service/common/app.ts b/backend/src/service/common/app.ts
index c428baad..fab81707 100644
--- a/backend/src/service/common/app.ts
+++ b/backend/src/service/common/app.ts
@@ -1,10 +1,10 @@
 import type { Organization, User } from "../../db/models.ts";
 import type { components } from "../../generated/openapi.ts";
-import { env } from "../../lib/env.ts";
 import { isRFC1123 } from "../../lib/validate.ts";
 import { InstallationNotFoundError, ValidationError } from "../errors/index.ts";
 import type { IsNamespaceAvailableService } from "../isNamespaceAvailable.ts";
 import type { RancherService } from "./cluster/rancher.ts";
+import type { RancherAccessService } from "./cluster/rancherAccess.ts";
 import {
   MAX_GROUPNAME_LEN,
   MAX_NAMESPACE_LEN,
@@ -35,17 +35,23 @@ export class AppService {
   private isNamespaceAvailableService: IsNamespaceAvailableService;
   private gitProviderFactoryService: GitProviderFactoryService;
   private rancherService: RancherService;
+  private rancherAccessService: RancherAccessService;
+  private allowHelmDeployments: boolean;
 
   constructor(
     configService: DeploymentConfigService,
     isNamespaceAvailableService: IsNamespaceAvailableService,
     gitProviderFactoryService: GitProviderFactoryService,
     rancherService: RancherService,
+    rancherAccessService: RancherAccessService,
+    allowHelmDeployments: boolean,
   ) {
     this.configService = configService;
     this.isNamespaceAvailableService = isNamespaceAvailableService;
     this.gitProviderFactoryService = gitProviderFactoryService;
     this.rancherService = rancherService;
+    this.rancherAccessService = rancherAccessService;
+    this.allowHelmDeployments = allowHelmDeployments;
   }
 
   /**
@@ -144,7 +150,7 @@ export class AppService {
       }
 
       if (
-        !(await this.rancherService.canManageProject(
+        !(await this.rancherAccessService.canManageProject(
           user.clusterUsername,
           app.projectId,
         ))
@@ -159,7 +165,7 @@ export class AppService {
         app.type === "update" ? app.existingAppId : undefined,
       );
     } else if (app.config.appType === "helm") {
-      if (!env.ALLOW_HELM_DEPLOYMENTS) {
+      if (!this.allowHelmDeployments) {
         throw new ValidationError("Helm deployments are disabled");
       }
     }
diff --git a/backend/src/service/common/builder.ts b/backend/src/service/common/builder.ts
index b99c3731..a7b6b656 100644
--- a/backend/src/service/common/builder.ts
+++ b/backend/src/service/common/builder.ts
@@ -13,7 +13,6 @@ import type {
 import type { AppRepo } from "../../db/repo/app.ts";
 import type { DeploymentRepo } from "../../db/repo/deployment.ts";
 import type { OrganizationRepo } from "../../db/repo/organization.ts";
-import { env } from "../../lib/env.ts";
 import { logger } from "../../logger.ts";
 import { type KubernetesClientService } from "./cluster/kubernetes.ts";
 import type { LogCollectionService } from "./cluster/resources/logs.ts";
@@ -30,6 +29,16 @@ export class BuilderService {
   private logCollectionService: LogCollectionService;
   private deploymentConfigService: DeploymentConfigService;
   private kubernetesService: KubernetesClientService;
+  private namespace: string;
+  private dockerfileBuilderImage: string;
+  private railpackBuilderImage: string;
+  private registryHostname: string;
+  private harborProjectName: string;
+  private internalBaseURL: string;
+  private buildkitdAddress: string;
+  private railpackInternalFrontendImage: string;
+  private railpackInternalBuilderImage: string;
+  private railpackInternalRuntimeImage: string;
 
   constructor(
     orgRepo: OrganizationRepo,
@@ -39,6 +48,16 @@ export class BuilderService {
     logCollectionService: LogCollectionService,
     deploymentConfigService: DeploymentConfigService,
     kubernetesService: KubernetesClientService,
+    namespace: string,
+    dockerfileBuilderImage: string,
+    railpackBuilderImage: string,
+    registryHostname: string,
+    harborProjectName: string,
+    internalBaseURL: string,
+    buildkitdAddress: string,
+    railpackInternalFrontendImage: string,
+    railpackInternalBuilderImage: string,
+    railpackInternalRuntimeImage: string,
   ) {
     this.orgRepo = orgRepo;
     this.appRepo = appRepo;
@@ -47,6 +66,16 @@ export class BuilderService {
     this.logCollectionService = logCollectionService;
     this.deploymentConfigService = deploymentConfigService;
     this.kubernetesService = kubernetesService;
+    this.namespace = namespace;
+    this.dockerfileBuilderImage = dockerfileBuilderImage;
+    this.railpackBuilderImage = railpackBuilderImage;
+    this.registryHostname = registryHostname;
+    this.harborProjectName = harborProjectName;
+    this.internalBaseURL = internalBaseURL;
+    this.buildkitdAddress = buildkitdAddress;
+    this.railpackInternalFrontendImage = railpackInternalFrontendImage;
+    this.railpackInternalBuilderImage = railpackInternalBuilderImage;
+    this.railpackInternalRuntimeImage = railpackInternalRuntimeImage;
   }
 
   private async createJobFromDeployment(
@@ -86,7 +115,7 @@ export class BuilderService {
         map[name] = value;
       }
       await this.kubernetesService.createNamespacedSecret({
-        namespace: env.CURRENT_NAMESPACE,
+        namespace: this.namespace,
         body: {
           apiVersion: "v1",
           kind: "Secret",
@@ -114,24 +143,24 @@ export class BuilderService {
             name: "builder",
             image:
               config.builder === "dockerfile"
-                ? env.DOCKERFILE_BUILDER_IMAGE
-                : env.RAILPACK_BUILDER_IMAGE,
+                ? this.dockerfileBuilderImage
+                : this.railpackBuilderImage,
             env: [
               { name: "CLONE_URL", value: cloneURL },
               { name: "REF", value: config.commitHash },
               { name: "IMAGE_TAG", value: config.imageTag },
               {
                 name: "CACHE_TAG",
-                value: `${env.REGISTRY_HOSTNAME}/${env.HARBOR_PROJECT_NAME}/${app.imageRepo}:build-cache`,
+                value: `${this.registryHostname}/${this.harborProjectName}/${app.imageRepo}:build-cache`,
               },
               { name: "DEPLOYMENT_API_SECRET", value: deployment.secret },
               {
                 name: "DEPLOYMENT_API_URL",
-                value: `${env.CLUSTER_INTERNAL_BASE_URL}/api`,
+                value: `${this.internalBaseURL}/api`,
               },
               {
                 name: "BUILDKITD_ADDRESS",
-                value: env.BUILDKITD_ADDRESS,
+                value: this.buildkitdAddress,
               },
               { name: "DOCKER_CONFIG", value: "/creds" },
               { name: "ROOT_DIRECTORY", value: config.rootDir },
@@ -168,15 +197,15 @@ export class BuilderService {
                     },
                     {
                       name: "RAILPACK_INTERNAL_FRONTEND_IMAGE",
-                      value: env.RAILPACK_INTERNAL_FRONTEND_IMAGE,
+                      value: this.railpackInternalFrontendImage,
                     },
                     {
                       name: "RAILPACK_INTERNAL_BUILDER_IMAGE",
-                      value: env.RAILPACK_INTERNAL_BUILDER_IMAGE,
+                      value: this.railpackInternalBuilderImage,
                     },
                     {
                       name: "RAILPACK_INTERNAL_RUNTIME_IMAGE",
-                      value: env.RAILPACK_INTERNAL_RUNTIME_IMAGE,
+                      value: this.railpackInternalRuntimeImage,
                     },
                   ]
                 : []),
@@ -272,7 +301,7 @@ export class BuilderService {
     };
 
     const job = await this.kubernetesService.createNamespacedJob({
-      namespace: env.CURRENT_NAMESPACE,
+      namespace: this.namespace,
       body: {
         metadata: {
           name: jobName,
@@ -300,7 +329,7 @@ export class BuilderService {
         await this.kubernetesService.patchNamespacedSecret(
           {
             name: secretName,
-            namespace: env.CURRENT_NAMESPACE,
+            namespace: this.namespace,
             body: {
               metadata: {
                 ownerReferences: [
@@ -327,7 +356,7 @@ export class BuilderService {
           // Remove it manually now and throw an error.
           await this.kubernetesService.deleteNamespacedSecret({
             name: secretName,
-            namespace: env.CURRENT_NAMESPACE,
+            namespace: this.namespace,
           });
         } catch (err) {
           logger.error(
diff --git a/backend/src/service/common/cluster/kubernetes.ts b/backend/src/service/common/cluster/kubernetes.ts
index 66a2d400..998733c3 100644
--- a/backend/src/service/common/cluster/kubernetes.ts
+++ b/backend/src/service/common/cluster/kubernetes.ts
@@ -14,12 +14,13 @@ import {
   type V1Namespace,
 } from "@kubernetes/client-node";
 import { SpanStatusCode, trace } from "@opentelemetry/api";
+import { randomBytes } from "node:crypto";
 import { setTimeout } from "node:timers/promises";
 import type { App } from "../../../db/models.ts";
 import type { UserRepo } from "../../../db/repo/user.ts";
-import { env } from "../../../lib/env.ts";
 import { logger } from "../../../logger.ts";
-import { shouldImpersonate } from "./rancher.ts";
+import type { GitProvider } from "../git/gitProvider.ts";
+import type { RancherService } from "./rancher.ts";
 import type { K8sObject } from "./resources.ts";
 
 const tracer = trace.getTracer("kubernetes-api");
@@ -59,9 +60,17 @@ Object.freeze(svcK8s);
 
 export class KubernetesClientService {
   private userRepo: UserRepo;
+  private rancherService: RancherService;
+  private namespace: string;
 
-  constructor(userRepo: UserRepo) {
+  constructor(
+    userRepo: UserRepo,
+    rancherService: RancherService,
+    namespace: string,
+  ) {
     this.userRepo = userRepo;
+    this.rancherService = rancherService;
+    this.namespace = namespace;
   }
 
   getClientForClusterUsername<T extends APIClassName>(
@@ -99,7 +108,7 @@ export class KubernetesClientService {
             }
           });
 
-          const impersonate = shouldImpersonate(projectId);
+          const impersonate = this.rancherService.shouldImpersonate(projectId);
           const clusterUsername = !impersonate
             ? null
             : await this.userRepo
@@ -145,22 +154,26 @@ export class KubernetesClientService {
     }
   }
 
-  private static REQUIRED_LABELS = env["RANCHER_BASE_URL"]
-    ? ["field.cattle.io/projectId", "lifecycle.cattle.io/create.namespace-auth"]
-    : [];
-
   async ensureNamespace(
     api: KubernetesObjectApi,
     namespace: V1Namespace & K8sObject,
   ) {
     await api.create(namespace);
+
+    const requiredLabels = this.rancherService.isRancherManaged()
+      ? [
+          "field.cattle.io/projectId",
+          "lifecycle.cattle.io/create.namespace-auth",
+        ]
+      : [];
+
     for (let i = 0; i < 20; i++) {
       try {
         // eslint-disable-next-line no-await-in-loop
         const res: V1Namespace = await api.read(namespace);
         if (
           res.status.phase === "Active" &&
-          KubernetesClientService.REQUIRED_LABELS.every((label) =>
+          requiredLabels.every((label) =>
             Object.prototype.hasOwnProperty.call(
               res.metadata.annotations,
               label,
@@ -220,7 +233,7 @@ export class KubernetesClientService {
           const api = this.getClientForClusterUsername(
             app.clusterUsername,
             "KubernetesObjectApi",
-            shouldImpersonate(app.projectId),
+            this.rancherService.shouldImpersonate(app.projectId),
           );
 
           if (await this.resourceExists(api, namespace)) {
@@ -264,7 +277,7 @@ export class KubernetesClientService {
 
   async cancelBuildJobsForApp(appId: number) {
     await svcK8s["BatchV1Api"].deleteCollectionNamespacedJob({
-      namespace: env.CURRENT_NAMESPACE,
+      namespace: this.namespace,
       labelSelector: `anvilops.rcac.purdue.edu/app-id=${appId.toString()}`,
       propagationPolicy: "Background", // Delete dependent resources (pods and secrets) in the background. Without this option, they would not be deleted at all.
     });
@@ -273,7 +286,7 @@ export class KubernetesClientService {
   async countActiveBuildJobs() {
     const jobs = await svcK8s["BatchV1Api"].listNamespacedJob({
       // TODO filter for a certain label that indicates that this Job is a build job
-      namespace: env.CURRENT_NAMESPACE,
+      namespace: this.namespace,
     });
 
     return jobs.items.filter((job) => job.status?.active).length;
@@ -330,4 +343,111 @@ export class KubernetesClientService {
     }
     return false;
   }
+
+  async copyRepoManually(
+    gitProvider: GitProvider,
+    cloneURL: string,
+    pushURL: string,
+  ) {
+    const botUser = await gitProvider.getBotCommitterDetails();
+
+    const job = await this.createNamespacedJob({
+      namespace: this.namespace,
+      body: {
+        metadata: {
+          name: `import-repo-${randomBytes(16).toString("hex")}`,
+        },
+        spec: {
+          ttlSecondsAfterFinished: 30, // Delete job 30 seconds after it completes
+          backoffLimit: 1, // Retry up to 1 time if the job exits with a non-zero status code
+          activeDeadlineSeconds: 2 * 60, // Kill after 2 minutes
+          template: {
+            spec: {
+              containers: [
+                {
+                  name: "importer",
+                  image: "alpine/git:v2.49.0",
+                  env: [
+                    { name: "CLONE_URL", value: cloneURL },
+                    { name: "PUSH_URL", value: pushURL },
+                    { name: "USER_EMAIL", value: botUser.email },
+                    { name: "USER_NAME", value: botUser.name },
+                  ],
+                  imagePullPolicy: "Always",
+                  command: ["/bin/sh", "-c"],
+                  workingDir: "/work",
+                  args: [
+                    `
+git clone --depth=1 --shallow-submodules "$CLONE_URL" .
+rm -rf .git
+
+git init
+git branch -M main
+
+git config user.email "$USER_EMAIL"
+git config user.name "$USER_NAME"
+
+git add .
+git commit -m "Initial commit"
+
+git remote add origin "$PUSH_URL"
+git push -u origin main`,
+                  ],
+                  volumeMounts: [
+                    {
+                      mountPath: "/work",
+                      name: "work-dir",
+                    },
+                  ],
+                  resources: {
+                    requests: {
+                      cpu: "500m",
+                      memory: "512Mi",
+                    },
+                    limits: {
+                      cpu: "500m",
+                      memory: "512Mi",
+                    },
+                  },
+                  securityContext: {
+                    // TODO: Use a custom image that specifies a user. Then, add a securityContext that runs as that non-root user and enables readOnlyRootFileSystem.
+                    capabilities: {
+                      drop: ["ALL"],
+                    },
+                    allowPrivilegeEscalation: false,
+                  },
+                },
+              ],
+              volumes: [
+                {
+                  name: "work-dir",
+                  emptyDir: {
+                    sizeLimit: "1Gi",
+                  },
+                },
+              ],
+              restartPolicy: "Never",
+            },
+          },
+        },
+      },
+    });
+
+    logger.info(
+      { jobNamespace: job.metadata.namespace, jobName: job.metadata.name },
+      "Created manual Git repo import job",
+    );
+    try {
+      await this.awaitJobCompletion(this.namespace, job.metadata.name);
+    } catch (e) {
+      logger.warn(
+        {
+          jobNamespace: job.metadata.namespace,
+          jobName: job.metadata.name,
+        },
+        "Git repo import job failed",
+      );
+      throw e;
+    }
+  }
 }
diff --git a/backend/src/service/common/cluster/rancher.ts b/backend/src/service/common/cluster/rancher.ts
index 390682e7..59ab4308 100644
--- a/backend/src/service/common/cluster/rancher.ts
+++ b/backend/src/service/common/cluster/rancher.ts
@@ -1,55 +1,51 @@
-import { env } from "../../../lib/env.ts";
-import { logger } from "../../../logger.ts";
-import { type KVCacheService } from "../cache.ts";
-import type { KubernetesClientService } from "./kubernetes.ts";
-
-const token = env["RANCHER_TOKEN"];
-const headers = {
-  Authorization: `Basic ${token}`,
-};
-const BASE_URL = env["RANCHER_BASE_URL"];
-
-const SANDBOX_ID = env["SANDBOX_ID"];
-
-const fetchRancherResource = async <T extends { type: string }>(
-  endpoint: string,
-) => {
-  const res = await fetch(`${BASE_URL}/v3/${endpoint}`, { headers });
-  const json = (await res.json()) as T;
-  if (json.type === "error") {
-    throw new Error(JSON.stringify(json));
-  }
-  return json;
-};
-
-const getProjectById = async (id: string) => {
-  const project = await fetchRancherResource<RancherProject>(`projects/${id}`);
-  return {
-    id: project.id,
-    name: project.name,
-    description: project.description,
-  };
-};
-
 export class RancherService {
-  private kubernetesService: KubernetesClientService;
-  private cacheService: KVCacheService;
+  private token: string;
+  private baseURL: string;
+  private loginType: string;
+  private sandboxProjectID: string;
 
   constructor(
-    kubernetesService: KubernetesClientService,
-    cacheService: KVCacheService,
+    token: string,
+    baseURL: string,
+    loginType: string,
+    sandboxProjectID: string,
   ) {
-    this.kubernetesService = kubernetesService;
-    this.cacheService = cacheService;
+    this.token = token;
+    this.baseURL = baseURL;
+    this.loginType = loginType;
+    this.sandboxProjectID = sandboxProjectID;
+  }
+
+  async fetchRancherResource<T extends { type: string }>(endpoint: string) {
+    const res = await fetch(`${this.baseURL}/v3/${endpoint}`, {
+      headers: { Authorization: `Basic ${this.token}` },
+    });
+    const json = (await res.json()) as T;
+    if (json.type === "error") {
+      throw new Error(JSON.stringify(json));
+    }
+    return json;
+  }
+
+  async getProjectById(id: string) {
+    const project = await this.fetchRancherResource<RancherProject>(
+      `projects/${id}`,
+    );
+    return {
+      id: project.id,
+      name: project.name,
+      description: project.description,
+    };
   }
 
   isRancherManaged() {
-    return !!BASE_URL && !!token;
+    return !!this.baseURL && !!this.token;
   }
 
   async getRancherUserID(eppn: string) {
-    const users = await fetchRancherResource<RancherUsersListResponse>("users");
-    const principalId = `${env.LOGIN_TYPE}_user://${eppn}`;
+    const users =
+      await this.fetchRancherResource<RancherUsersListResponse>("users");
+    const principalId = `${this.loginType}_user://${eppn}`;
     const user = users?.data?.find((user) =>
       user.principalIds.some((id: string) => id === principalId),
     );
@@ -57,109 +53,11 @@ export class RancherService {
     return user?.id;
   }
 
-  async getProjectsForUser(rancherId: string): Promise<
-    {
-      id: string;
-      name: string;
-      description: string;
-    }[]
-  > {
-    return JSON.parse(
-      await this.cacheService.getOrCreate(
-        `rancher-projects-${rancherId}`,
-        15,
-        async () => JSON.stringify(await this.fetchUserProjects(rancherId)),
-      ),
-    ) as Awaited<ReturnType<typeof this.fetchUserProjects>>;
-  }
-
-  async canManageProject(userId: string, projectId: string) {
-    return (
-      (await this.cacheService.getOrCreate(
-        `rancher-canmanage-${userId}-${projectId}`,
-        15,
-        () =>
-          this.getProjectAccessReview(userId, projectId).then((res) =>
-            res.toString(),
-          ),
-      )) === "true"
-    );
-  }
-
-  private async getProjectAccessReview(userId: string, projectId: string) {
-    if (!projectId) {
-      return false;
-    }
-    if (projectId === SANDBOX_ID) {
-      return true;
-    }
-    const simpleProjectId = projectId.split(":")[1];
-    const authClient = this.kubernetesService.getClientForClusterUsername(
-      userId,
-      "AuthorizationV1Api",
-      true,
-    );
-    try {
-      const review = await authClient.createSelfSubjectAccessReview({
-        body: {
-          spec: {
-            resourceAttributes: {
-              group: "management.cattle.io",
-              resource: "projects",
-              verb: "manage-namespaces",
-              name: simpleProjectId,
-            },
-          },
-        },
-      });
-      return review.status.allowed;
-    } catch (err) {
-      logger.error(err, "Failed to create SelfSubjectAccessReview");
-      return false;
-    }
-  }
-
-  async fetchUserProjects(rancherId: string) {
-    const bindings =
-      await fetchRancherResource<RancherProjectRoleTemplateBindingsResponse>(
-        `projectRoleTemplateBindings?userId=${rancherId}`,
-      ).then((res) => res.data);
-
-    const projectIds = bindings
-      ? bindings.map((binding) => binding.projectId)
-      : [];
-    projectIds.push(SANDBOX_ID);
-
-    const uniqueProjectIds = [...new Set(projectIds)];
-
-    const canDeployIn = await Promise.all(
-      uniqueProjectIds.map((projectId) =>
-        this.getProjectAccessReview(rancherId, projectId),
-      ),
-    );
-
-    const allowedProjectIds = uniqueProjectIds.filter(
-      (_, idx) => canDeployIn[idx],
-    );
-
-    return Promise.all(
-      allowedProjectIds.map(async (projectId) => {
-        const project = await getProjectById(projectId);
-        return {
-          id: project.id,
-          name: project.name,
-          description: project.description,
-        };
-      }),
-    );
+  shouldImpersonate(projectId: string) {
+    return projectId !== this.sandboxProjectID;
   }
 }
 
-// TODO move inside RancherService and remove cyclic dependency with KubernetesService
-export function shouldImpersonate(projectId: string) {
-  return projectId !== SANDBOX_ID;
-}
-
 type RancherProject = {
   actions: {
     /** URL */
@@ -191,55 +89,6 @@ type RancherProject = {
   uuid: string;
 };
 
-type RancherProjectRoleTemplateBindingsResponse = {
-  type: "collection";
-  links: {
-    /** URL */
-    self: string;
-  };
-  createTypes: {
-    /** URL */
-    projectRoleTemplateBinding: string;
-  };
-  actions: object;
-  pagination: { limit: number; total: number };
-  sort: {
-    order: "asc" | "desc";
-    /** URL */
-    reverse: string;
-    links: {
-      /** URL */
-      serviceAccount: string;
-      /** URL */
-      uuid: string;
-    };
-  };
-  filters: Record<string, object>;
-  resourceType: "projectRoleTemplateBinding";
-  data: {
-    annotations: Record<string, string>;
-    baseType: "projectRoleTemplateBinding";
-    /** ISO date string */
-    created: string;
-    createdTS: number;
-    creatorId: string | null;
-    groupId: string | null;
-    groupPrincipalId: string | null;
-    id: string;
-    labels: Record<string, string>;
-    /** Map of URLs */
-    links: Record<"remove" | "self" | "update", string>;
-    name: string;
-    namespaceId: string | null;
-    projectId: string;
-    roleTemplateId: string;
-    type: "projectRoleTemplateBinding";
-    userId: string;
-    userPrincipalId: string;
-    uuid: string;
-  }[];
-};
-
 type RancherUsersListResponse = {
   type: "collection";
   links: {
diff --git a/backend/src/service/common/cluster/rancherAccess.ts b/backend/src/service/common/cluster/rancherAccess.ts
new file mode 100644
index 00000000..ffba1186
--- /dev/null
+++ b/backend/src/service/common/cluster/rancherAccess.ts
@@ -0,0 +1,170 @@
+import { logger } from "../../../logger.ts";
+import { type KVCacheService } from "../cache.ts";
+import type { KubernetesClientService } from "./kubernetes.ts";
+import type { RancherService } from "./rancher.ts";
+
+export class RancherAccessService {
+  private kubernetesService: KubernetesClientService;
+  private rancherService: RancherService;
+  private cacheService: KVCacheService;
+  private sandboxProjectID: string;
+
+  constructor(
+    kubernetesService: KubernetesClientService,
+    rancherService: RancherService,
+    cacheService: KVCacheService,
+    sandboxProjectID: string,
+  ) {
+    this.kubernetesService = kubernetesService;
+    this.rancherService = rancherService;
+    this.cacheService = cacheService;
+    this.sandboxProjectID = sandboxProjectID;
+  }
+
+  async getProjectsForUser(rancherId: string): Promise<
+    {
+      id: string;
+      name: string;
+      description: string;
+    }[]
+  > {
+    return JSON.parse(
+      await this.cacheService.getOrCreate(
+        `rancher-projects-${rancherId}`,
+        15,
+        async () => JSON.stringify(await this.fetchUserProjects(rancherId)),
+      ),
+    ) as Awaited<ReturnType<typeof this.fetchUserProjects>>;
+  }
+
+  async canManageProject(userId: string, projectId: string) {
+    return (
+      (await this.cacheService.getOrCreate(
+        `rancher-canmanage-${userId}-${projectId}`,
+        15,
+        () =>
+          this.getProjectAccessReview(userId, projectId).then((res) =>
+            res.toString(),
+          ),
+      )) === "true"
+    );
+  }
+
+  private async getProjectAccessReview(userId: string, projectId: string) {
+    if (!projectId) {
+      return false;
+    }
+    if (projectId === this.sandboxProjectID) {
+      return true;
+    }
+    const simpleProjectId = projectId.split(":")[1];
+    const authClient = this.kubernetesService.getClientForClusterUsername(
+      userId,
+      "AuthorizationV1Api",
+      true,
+    );
+    try {
+      const review = await authClient.createSelfSubjectAccessReview({
+        body: {
+          spec: {
+            resourceAttributes: {
+              group: "management.cattle.io",
+              resource: "projects",
+              verb: "manage-namespaces",
+              name: simpleProjectId,
+            },
+          },
+        },
+      });
+      return review.status.allowed;
+    } catch (err) {
+      logger.error(err, "Failed to create SelfSubjectAccessReview");
+      return false;
+    }
+  }
+
+  private async fetchUserProjects(rancherId: string) {
+    const bindings = await this.rancherService
+      .fetchRancherResource<RancherProjectRoleTemplateBindingsResponse>(
+        `projectRoleTemplateBindings?userId=${rancherId}`,
+      )
+      .then((res) => res.data);
+
+    const projectIds = bindings
+      ? bindings.map((binding) => binding.projectId)
+      : [];
+    projectIds.push(this.sandboxProjectID);
+
+    const uniqueProjectIds = [...new Set(projectIds)];
+
+    const canDeployIn = await Promise.all(
+      uniqueProjectIds.map((projectId) =>
+        this.getProjectAccessReview(rancherId, projectId),
+      ),
+    );
+
+    const allowedProjectIds = uniqueProjectIds.filter(
+      (_, idx) => canDeployIn[idx],
+    );
+
+    return Promise.all(
+      allowedProjectIds.map(async (projectId) => {
+        const project = await this.rancherService.getProjectById(projectId);
+        return {
+          id: project.id,
+          name: project.name,
+          description: project.description,
+        };
+      }),
+    );
+  }
+}
+
+type RancherProjectRoleTemplateBindingsResponse = {
+  type: "collection";
+  links: {
+    /** URL */
+    self: string;
+  };
+  createTypes: {
+    /** URL */
+    projectRoleTemplateBinding: string;
+  };
+  actions: object;
+  pagination: { limit: number; total: number };
+  sort: {
+    order: "asc" | "desc";
+    /** URL */
+    reverse: string;
+    links: {
+      /** URL */
+      serviceAccount: string;
+      /** URL */
+      uuid: string;
+    };
+  };
+  filters: Record<string, object>;
+  resourceType: "projectRoleTemplateBinding";
+  data: {
+    annotations: Record<string, string>;
+    baseType: "projectRoleTemplateBinding";
+    /** ISO date string */
+    created: string;
+    createdTS: number;
+    creatorId: string | null;
+    groupId: string | null;
+    groupPrincipalId: string | null;
+    id: string;
+    labels: Record<string, string>;
+    /** Map of URLs */
+    links: Record<"remove" | "self" | "update", string>;
+    name: string;
+    namespaceId: string | null;
+    projectId: string;
+    roleTemplateId: string;
+    type: "projectRoleTemplateBinding";
+    userId: string;
+    userPrincipalId: string;
+    uuid: string;
+  }[];
+};
diff --git a/backend/src/service/common/cluster/resources.ts b/backend/src/service/common/cluster/resources.ts
index 649bf07c..378fcaa5 100644
--- a/backend/src/service/common/cluster/resources.ts
+++ b/backend/src/service/common/cluster/resources.ts
@@ -15,7 +15,6 @@ import type {
   Organization,
   WorkloadConfig,
 } from "../../../db/models.ts";
-import { env } from "../../../lib/env.ts";
 import { logger } from "../../../logger.ts";
 import type { DeploymentConfigService } from "../deploymentConfig.ts";
 import type { GitProviderFactoryService } from "../git/gitProvider.ts";
@@ -63,33 +62,17 @@ export interface K8sObject {
   [key: string]: unknown;
 }
 
-let allowedIngressPeers: V1NetworkPolicyPeer[] | null;
-function getAllowedIngressPeers(): V1NetworkPolicyPeer[] | null {
-  if (!env.CREATE_INGRESS_NETPOL || !env.ALLOW_INGRESS_FROM) {
-    return null;
-  }
-
-  if (!allowedIngressPeers) {
-    const allowedLabels = JSON.parse(env.ALLOW_INGRESS_FROM) as {
-      [key: string]: string;
-    }[];
-    allowedIngressPeers = allowedLabels.map((labels) => ({
-      namespaceSelector: {
-        matchLabels: labels,
-      },
-      podSelector: {},
-    }));
-  }
-
-  return allowedIngressPeers;
-}
-
 export class ClusterResourcesService {
   private gitProviderFactoryService: GitProviderFactoryService;
   private serviceConfigService: ServiceConfigService;
   private ingressConfigService: IngressConfigService;
   private statefulSetConfigService: StatefulSetConfigService;
   private deploymentConfigService: DeploymentConfigService;
+  private createIngressNetworkPolicies: boolean;
+  private allowedIngressPeers: V1NetworkPolicyPeer[] | null = null;
+  private registryHostname: string;
+  private imagePullUsername: string;
+  private imagePullPassword: string;
 
   constructor(
     gitProviderFactoryService: GitProviderFactoryService,
@@ -97,12 +80,30 @@ export class ClusterResourcesService {
     ingressConfigService: IngressConfigService,
     statefulSetConfigService: StatefulSetConfigService,
     deploymentConfigService: DeploymentConfigService,
+    createIngressNetworkPolicies: boolean,
+    allowedLabels: Record<string, string>[],
+    registryHostname: string,
+    imagePullUsername: string,
+    imagePullPassword: string,
   ) {
     this.gitProviderFactoryService = gitProviderFactoryService;
     this.serviceConfigService = serviceConfigService;
     this.ingressConfigService = ingressConfigService;
     this.statefulSetConfigService = statefulSetConfigService;
     this.deploymentConfigService = deploymentConfigService;
+    this.createIngressNetworkPolicies = createIngressNetworkPolicies;
+    this.registryHostname = registryHostname;
+    this.imagePullUsername = imagePullUsername;
+    this.imagePullPassword = imagePullPassword;
+
+    if (createIngressNetworkPolicies && allowedLabels) {
+      this.allowedIngressPeers = allowedLabels.map((labels) => ({
+        namespaceSelector: {
+          matchLabels: labels,
+        },
+        podSelector: {},
+      }));
+    }
   }
 
   async createAppConfigsFromDeployment({
@@ -137,9 +138,9 @@ export class ClusterResourcesService {
       config,
       app,
     );
-    const secretData = getEnvRecord(config.getEnv());
+    const secretData = this.getEnvRecord(config.getEnv());
     if (secretData !== null) {
-      const secretConfig = createSecretConfig(
+      const secretConfig = this.createSecretConfig(
         secretData,
         secretName,
         app.namespace,
@@ -148,7 +149,7 @@ export class ClusterResourcesService {
       // Secrets should be created first
       configs.unshift(secretConfig);
     }
-    configs.unshift(getImagePullSecret(app.namespace));
+    configs.unshift(this.getImagePullSecret(app.namespace));
 
     const params = {
       deploymentId: deployment.id,
@@ -206,7 +207,7 @@ export class ClusterResourcesService {
         applyLabels(config, deletedLabels);
       }
     } else {
-      const netpol = createIngressNetPol({
+      const netpol = this.createIngressNetPol({
         name: params.name,
         namespace: params.namespace,
         groupLabels,
@@ -227,7 +228,7 @@ export class ClusterResourcesService {
       const outdatedResources: KubernetesObject[] = [];
 
       if (migrating) {
-        if (env.CREATE_INGRESS_NETPOL) {
+        if (this.createIngressNetworkPolicies) {
           // When migrating, AnvilOps-specific labels are removed, so grouping network policies will not work.
           // Delete all network policies that are managed by AnvilOps.
           const netpols = await api
@@ -334,123 +335,100 @@ export class ClusterResourcesService {
 
     return envVars;
   }
-}
 
-export function createNamespaceConfig(
-  namespace: string,
-  projectId?: string,
-): V1Namespace & K8sObject {
-  return {
-    apiVersion: "v1",
-    kind: "Namespace",
-    metadata: {
-      name: namespace,
-      annotations: {
-        ...(!!projectId && { "field.cattle.io/projectId": `${projectId}` }),
-      },
-    },
-  };
-}
-
-function getEnvRecord(envVars: PrismaJson.EnvVar[]): Record<string, string> {
-  if (envVars.length == 0) return null;
-  return envVars.reduce((data, env) => {
-    return Object.assign(data, { [env.name]: env.value });
-  }, {});
-}
+  getEnvRecord(envVars: PrismaJson.EnvVar[]): Record<string, string> {
+    if (envVars.length == 0) return null;
+    return envVars.reduce((data, env) => {
+      return Object.assign(data, { [env.name]: env.value });
+    }, {});
+  }
 
-function getImagePullSecret(namespace: string): V1Secret & K8sObject {
-  const config = {
-    auths: {
-      [env.REGISTRY_HOSTNAME]: {
-        username: env.IMAGE_PULL_USERNAME,
-        password: env.IMAGE_PULL_PASSWORD,
-        auth: Buffer.from(
-          env.IMAGE_PULL_USERNAME + ":" + env.IMAGE_PULL_PASSWORD,
-        ).toString("base64"),
+  getImagePullSecret(namespace: string): V1Secret & K8sObject {
+    const config = {
+      auths: {
+        [this.registryHostname]: {
+          username: this.imagePullUsername,
+          password: this.imagePullPassword,
+          auth: Buffer.from(
+            this.imagePullUsername + ":" + this.imagePullPassword,
+          ).toString("base64"),
+        },
       },
-    },
-  };
-
-  return {
-    apiVersion: "v1",
-    kind: "Secret",
-    metadata: {
-      name: "image-pull-secret",
-      namespace,
-    },
-    type: "kubernetes.io/dockerconfigjson",
-    data: {
-      ".dockerconfigjson": Buffer.from(JSON.stringify(config)).toString(
-        "base64",
-      ),
-    },
-  };
-}
-
-function createSecretConfig(
-  secrets: Record<string, string>,
-  name: string,
-  namespace: string,
-): V1Secret & K8sObject {
-  return {
-    apiVersion: "v1",
-    kind: "Secret",
-    metadata: {
-      name,
-      namespace,
-    },
-    stringData: secrets,
-  };
-}
+    };
 
-function createIngressNetPol({
-  name,
-  namespace,
-  groupLabels,
-}: {
-  name: string;
-  namespace: string;
-  groupLabels: { [key: string]: string };
-}): V1NetworkPolicy & K8sObject {
-  if (!env.CREATE_INGRESS_NETPOL) {
-    return null;
+    return {
+      apiVersion: "v1",
+      kind: "Secret",
+      metadata: {
+        name: "image-pull-secret",
+        namespace,
+      },
+      type: "kubernetes.io/dockerconfigjson",
+      data: {
+        ".dockerconfigjson": Buffer.from(JSON.stringify(config)).toString(
+          "base64",
+        ),
+      },
+    };
   }
 
-  if (!env.ALLOW_INGRESS_FROM) {
-    logger.warn(
-      "ALLOW_INGRESS_FROM is not set, skipping network policy creation",
-    );
-    return null;
+  createSecretConfig(
+    secrets: Record<string, string>,
+    name: string,
+    namespace: string,
+  ): V1Secret & K8sObject {
+    return {
+      apiVersion: "v1",
+      kind: "Secret",
+      metadata: {
+        name,
+        namespace,
+      },
+      stringData: secrets,
+    };
   }
 
-  return {
-    apiVersion: "networking.k8s.io/v1",
-    kind: "NetworkPolicy",
-    metadata: {
-      name,
-      namespace,
-    },
-    spec: {
-      podSelector: {
-        matchLabels: groupLabels,
+  createIngressNetPol({
+    name,
+    namespace,
+    groupLabels,
+  }: {
+    name: string;
+    namespace: string;
+    groupLabels: { [key: string]: string };
+  }): V1NetworkPolicy & K8sObject {
+    if (!this.createIngressNetworkPolicies || !this.allowedIngressPeers) {
+      return null;
+    }
+
+    return {
+      apiVersion: "networking.k8s.io/v1",
+      kind: "NetworkPolicy",
+      metadata: {
+        name,
+        namespace,
       },
-      policyTypes: ["Ingress"],
-      ingress: [
-        {
-          _from: [
-            ...getAllowedIngressPeers(),
-            {
-              namespaceSelector: {
-                matchLabels: groupLabels, // Allow ingress from pods in namespaces of this group
-              },
-              podSelector: {},
-            },
-          ],
+      spec: {
+        podSelector: {
+          matchLabels: groupLabels,
         },
-      ],
-    },
-  } satisfies V1NetworkPolicy;
+        policyTypes: ["Ingress"],
+        ingress: [
+          {
+            _from: [
+              ...this.allowedIngressPeers,
+              {
+                namespaceSelector: {
+                  matchLabels: groupLabels, // Allow ingress from pods in namespaces of this group
+                },
+                podSelector: {},
+              },
+            ],
+          },
+        ],
+      },
+    } satisfies V1NetworkPolicy;
+  }
 }
 
 function applyLabels(config: K8sObject, labels: { [key: string]: string }) {
@@ -466,3 +444,19 @@ function applyLabels(config: K8sObject, labels: { [key: string]: string }) {
     }
   }
 }
+
+export function createNamespaceConfig(
+  namespace: string,
+  projectId?: string,
+): V1Namespace & K8sObject {
+  return {
+    apiVersion: "v1",
+    kind: "Namespace",
+    metadata: {
+      name: namespace,
+      annotations: {
+        ...(!!projectId && { "field.cattle.io/projectId": `${projectId}` }),
+      },
+    },
+  };
+}
diff --git a/backend/src/service/common/cluster/resources/ingress.ts b/backend/src/service/common/cluster/resources/ingress.ts
index 486c2b6a..8aa0413a 100644
--- a/backend/src/service/common/cluster/resources/ingress.ts
+++ b/backend/src/service/common/cluster/resources/ingress.ts
@@ -1,5 +1,4 @@
 import { ApiException, type V1Ingress } from "@kubernetes/client-node";
-import { env } from "../../../../lib/env.ts";
 import type { KubernetesClientService } from "../kubernetes.ts";
 import type { K8sObject } from "../resources.ts";
 
@@ -15,21 +14,32 @@ interface IngressInterface {
 
 export class IngressConfigService {
   private kubernetesService: KubernetesClientService;
+  private appDomain: string;
+  private ingressClassName: string;
+  private namespace: string;
 
-  constructor(kubernetesService: KubernetesClientService) {
+  constructor(
+    kubernetesService: KubernetesClientService,
+    appDomain: string,
+    ingressClassName: string,
+    namespace: string,
+  ) {
     this.kubernetesService = kubernetesService;
+    this.appDomain = appDomain;
+    this.ingressClassName = ingressClassName;
+    this.namespace = namespace;
   }
 
   createIngressConfig(app: IngressInterface): (V1Ingress & K8sObject) | null {
     if (
       !app.createIngress ||
-      !env.APP_DOMAIN ||
-      URL.parse(env.APP_DOMAIN) === null
+      !this.appDomain ||
+      URL.parse(this.appDomain) === null
     ) {
       return null;
     }
 
-    const appDomain = new URL(env.APP_DOMAIN);
+    const appDomain = new URL(this.appDomain);
     const hostname = app.subdomain + "." + appDomain.hostname;
 
     return {
@@ -40,7 +50,7 @@ export class IngressConfigService {
         namespace: app.namespace,
       },
       spec: {
-        ingressClassName: env.INGRESS_CLASS_NAME,
+        ingressClassName: this.ingressClassName,
         rules: [
           {
             host: hostname,
@@ -74,7 +84,7 @@ export class IngressConfigService {
     const config = this.createIngressConfig({
       createIngress: true,
       name: "anvilops-ingress-probe",
-      namespace: env.CURRENT_NAMESPACE,
+      namespace: this.namespace,
       port: 80,
       serviceName: "anvilops-ingress-probe",
       subdomain: subdomain,
diff --git a/backend/src/service/common/cluster/resources/logs.ts b/backend/src/service/common/cluster/resources/logs.ts
index 20e7d6d2..d7a5f72a 100644
--- a/backend/src/service/common/cluster/resources/logs.ts
+++ b/backend/src/service/common/cluster/resources/logs.ts
@@ -1,12 +1,19 @@
 import type { V1PodTemplateSpec } from "@kubernetes/client-node";
-import { env } from "../../../../lib/env.ts";
 import type { RegistryService } from "../../registry.ts";
 
 export class LogCollectionService {
   private registryService: RegistryService;
+  private logShipperImage: string;
+  private internalBaseURL: string;
 
-  constructor(registryService: RegistryService) {
+  constructor(
+    registryService: RegistryService,
+    logShipperImage: string,
+    internalBaseURL: string,
+  ) {
     this.registryService = registryService;
+    this.logShipperImage = logShipperImage;
+    this.internalBaseURL = internalBaseURL;
   }
 
   async wrapWithLogExporter<T extends V1PodTemplateSpec>(
@@ -37,7 +44,7 @@ export class LogCollectionService {
     }
     clone.spec.initContainers.push({
       name: "copy-log-shipper-binary",
-      image: env.LOG_SHIPPER_IMAGE,
+      image: this.logShipperImage,
       imagePullPolicy: "Always",
       args: ["/mnt/log-shipper-volume"],
       volumeMounts: [
@@ -95,7 +102,7 @@ export class LogCollectionService {
       container.env.push(
         {
           name: "_PRIVATE_ANVILOPS_LOG_ENDPOINT",
-          value: `${env.CLUSTER_INTERNAL_BASE_URL}/api/logs/ingest`,
+          value: `${this.internalBaseURL}/api/logs/ingest`,
         },
         {
           name: "_PRIVATE_ANVILOPS_LOG_TOKEN",
diff --git a/backend/src/service/common/cluster/resources/statefulset.ts b/backend/src/service/common/cluster/resources/statefulset.ts
index 62fbf776..0197fee5 100644
--- a/backend/src/service/common/cluster/resources/statefulset.ts
+++ b/backend/src/service/common/cluster/resources/statefulset.ts
@@ -1,6 +1,5 @@
 import type { V1EnvVar, V1StatefulSet } from "@kubernetes/client-node";
 import crypto from "node:crypto";
-import { env } from "../../../../lib/env.ts";
 import type { K8sObject } from "../resources.ts";
 import type { LogCollectionService } from "./logs.ts";
 
@@ -24,9 +23,17 @@ interface DeploymentParams {
 
 export class StatefulSetConfigService {
   private logShipperWrapperService: LogCollectionService;
+  private storageClassName: string;
+  private storageAccessModes: string[];
 
-  constructor(logShipperWrapperService: LogCollectionService) {
+  constructor(
+    logShipperWrapperService: LogCollectionService,
+    storageClassName: string,
+    storageAccessModes: string[],
+  ) {
     this.logShipperWrapperService = logShipperWrapperService;
+    this.storageClassName = storageClassName;
+    this.storageAccessModes = storageAccessModes;
   }
 
   async createStatefulSetConfig(
@@ -89,8 +96,8 @@ export class StatefulSetConfigService {
         volumeClaimTemplates: params.mounts.map((mount) => ({
           metadata: { name: this.generateVolumeName(mount.path) },
           spec: {
-            accessModes: env.STORAGE_ACCESS_MODES.split(","),
-            storageClassName: env.STORAGE_CLASS_NAME,
+            accessModes: this.storageAccessModes,
+            storageClassName: this.storageClassName,
             resources: { requests: { storage: `${mount.amountInMiB}Mi` } },
           },
         })),
diff --git a/backend/src/service/common/deployment.ts b/backend/src/service/common/deployment.ts
index 9b04df5e..87c72fde 100644
--- a/backend/src/service/common/deployment.ts
+++ b/backend/src/service/common/deployment.ts
@@ -13,7 +13,6 @@ import type {
 import type { AppRepo } from "../../db/repo/app.ts";
 import type { AppGroupRepo } from "../../db/repo/appGroup.ts";
 import type { DeploymentRepo } from "../../db/repo/deployment.ts";
-import { env } from "../../lib/env.ts";
 import { logger } from "../../logger.ts";
 import { DeploymentError } from "../errors/index.ts";
 import { log } from "../githubWebhook.ts";
@@ -43,6 +42,7 @@ export class DeploymentService {
   private builderService: BuilderService;
   private clusterResourcesService: ClusterResourcesService;
   private kubernetesClientService: KubernetesClientService;
+  private baseURL: string;
 
   constructor(
     appRepo: AppRepo,
@@ -53,6 +53,7 @@ export class DeploymentService {
     builderService: BuilderService,
     clusterResourcesService: ClusterResourcesService,
     kubernetesClientService: KubernetesClientService,
+    baseURL: string,
   ) {
     this.appRepo = appRepo;
     this.appGroupRepo = appGroupRepo;
@@ -62,6 +63,7 @@ export class DeploymentService {
     this.builderService = builderService;
     this.clusterResourcesService = clusterResourcesService;
     this.kubernetesClientService = kubernetesClientService;
+    this.baseURL = baseURL;
   }
 
   /**
@@ -451,7 +453,7 @@ export class DeploymentService {
       repoId,
       config.commitHash,
       status,
-      `${env.BASE_URL}/app/${deployment.appId}/deployment/${deployment.id}`,
+      `${this.baseURL}/app/${deployment.appId}/deployment/${deployment.id}`,
     );
   }
 
diff --git a/backend/src/service/common/deploymentConfig.ts b/backend/src/service/common/deploymentConfig.ts
index e5209ed9..92cbc788 100644
--- a/backend/src/service/common/deploymentConfig.ts
+++ b/backend/src/service/common/deploymentConfig.ts
@@ -11,7 +11,6 @@ import type {
 } from "../../db/models.ts";
 import type { AppRepo } from "../../db/repo/app.ts";
 import type { components } from "../../generated/openapi.ts";
-import { env } from "../../lib/env.ts";
 import { isRFC1123 } from "../../lib/validate.ts";
 import {
   InstallationNotFoundError,
@@ -42,6 +41,9 @@ export class DeploymentConfigService {
   private registryService: RegistryService;
   private ingressConfigService: IngressConfigService;
   private statefulSetConfigService: StatefulSetConfigService;
+  private appDomain: string;
+  private registryHostname: string;
+  private harborProjectName: string;
 
   constructor(
     appRepo: AppRepo,
@@ -49,12 +51,18 @@ export class DeploymentConfigService {
     registryService: RegistryService,
     ingressConfigService: IngressConfigService,
     statefulSetConfigService: StatefulSetConfigService,
+    appDomain: string,
+    registryHostname: string,
+    harborProjectName: string,
   ) {
     this.appRepo = appRepo;
     this.gitProviderFactoryService = gitProviderFactoryService;
     this.registryService = registryService;
     this.ingressConfigService = ingressConfigService;
     this.statefulSetConfigService = statefulSetConfigService;
+    this.appDomain = appDomain;
+    this.registryHostname = registryHostname;
+    this.harborProjectName = harborProjectName;
   }
 
   async prepareDeploymentMetadata(
@@ -152,7 +160,7 @@ export class DeploymentConfigService {
     if (config.source === "GIT") {
       return {
         ...config,
-        imageTag: `${env.REGISTRY_HOSTNAME}/${env.HARBOR_PROJECT_NAME}/${app.imageRepo}:${config.commitHash}`,
+        imageTag: `${this.registryHostname}/${this.harborProjectName}/${app.imageRepo}:${config.commitHash}`,
       } satisfies WorkloadConfigCreate;
     }
 
@@ -289,7 +297,7 @@ export class DeploymentConfigService {
     config: WorkloadConfig,
     app: App,
   ): Promise<{ name: string; value: string }[]> {
-    const appDomain = URL.parse(env.APP_DOMAIN);
+    const appDomain = URL.parse(this.appDomain);
     const list = [
       {
         name: "PORT",
diff --git a/backend/src/service/common/git/gitProvider.ts b/backend/src/service/common/git/gitProvider.ts
index 4d476fca..064477f2 100644
--- a/backend/src/service/common/git/gitProvider.ts
+++ b/backend/src/service/common/git/gitProvider.ts
@@ -9,6 +9,7 @@ import type {
 import type { KVCacheService } from "../cache.ts";
 import type { KubernetesClientService } from "../cluster/kubernetes.ts";
 import { GitHubGitProvider } from "./githubGitProvider.ts";
+import { GitHubUserService } from "./githubUser.ts";
 
 /**
  * Contains all the methods needed to interact with a Git provider like GitHub or GitLab.
@@ -138,17 +139,41 @@ export class GitProviderFactoryService {
   private repoImportStateRepo: RepoImportStateRepo;
   private kubernetesService: KubernetesClientService;
   private cacheService: KVCacheService;
+  private githubUserService: GitHubUserService;
+  private githubPrivateKey: string;
+  private baseURL: string;
+  private githubApiURL: string;
+  private githubBaseURL: string;
+  private githubAppId: string;
+  private githubClientId: string;
+  private githubAppName: string;
 
   constructor(
     orgRepo: OrganizationRepo,
     repoImportStateRepo: RepoImportStateRepo,
     kubernetesService: KubernetesClientService,
     cacheService: KVCacheService,
+    githubUserService: GitHubUserService,
+    githubPrivateKey: string,
+    baseURL: string,
+    githubApiURL: string,
+    githubBaseURL: string,
+    githubAppId: string,
+    githubClientId: string,
+    githubAppName: string,
   ) {
     this.orgRepo = orgRepo;
     this.repoImportStateRepo = repoImportStateRepo;
     this.kubernetesService = kubernetesService;
     this.cacheService = cacheService;
+    this.githubUserService = githubUserService;
+    this.githubPrivateKey = githubPrivateKey;
+    this.baseURL = baseURL;
+    this.githubApiURL = githubApiURL;
+    this.githubBaseURL = githubBaseURL;
+    this.githubAppId = githubAppId;
+    this.githubClientId = githubClientId;
+    this.githubAppName = githubAppName;
   }
 
   /**
@@ -163,6 +188,14 @@ export class GitProviderFactoryService {
       this.repoImportStateRepo,
       this.kubernetesService,
       this.cacheService,
+      this.githubUserService,
+      this.githubPrivateKey,
+      this.baseURL,
+      this.githubApiURL,
+      this.githubBaseURL,
+      this.githubAppId,
+      this.githubClientId,
+      this.githubAppName,
     );
   }
 
diff --git a/backend/src/service/common/git/githubGitProvider.ts b/backend/src/service/common/git/githubGitProvider.ts
index 70130735..b4f0a7c5 100644
--- a/backend/src/service/common/git/githubGitProvider.ts
+++ b/backend/src/service/common/git/githubGitProvider.ts
@@ -1,10 +1,8 @@
-import { createAppAuth, createOAuthUserAuth } from "@octokit/auth-app";
+import { createAppAuth } from "@octokit/auth-app";
 import type { operations } from "@octokit/openapi-types";
-import crypto from "node:crypto";
 import { Octokit, RequestError } from "octokit";
 import type { OrganizationRepo } from "../../../db/repo/organization.ts";
 import type { RepoImportStateRepo } from "../../../db/repo/repoImportState.ts";
-import { env } from "../../../lib/env.ts";
 import { logger } from "../../../logger.ts";
 import {
   InstallationNotFoundError,
@@ -20,13 +18,10 @@ import {
   type GitProvider,
   type GitRepository,
 } from "./gitProvider.ts";
+import type { GitHubUserService } from "./githubUser.ts";
 
 class FastImportUnsupportedError extends Error {}
 
-const privateKey = Buffer.from(env.GITHUB_PRIVATE_KEY, "base64").toString(
-  "utf-8",
-);
-
 const installationIdSymbol = Symbol("installationId");
 
 type InstallationScopedOctokit = Octokit & {
@@ -39,6 +34,12 @@ export class GitHubGitProvider implements GitProvider {
   private repoImportStateRepo: RepoImportStateRepo;
   private kubernetesService: KubernetesClientService;
   private cacheService: KVCacheService;
+  private baseURL: string;
+  private githubApiURL: string;
+  private githubBaseURL: string;
+  private githubClientId: string;
+  private githubAppName: string;
+  private githubUserService: GitHubUserService;
 
   private constructor(
     octokit: Octokit,
@@ -46,6 +47,12 @@ export class GitHubGitProvider implements GitProvider {
     kubernetesService: KubernetesClientService,
     cacheService: KVCacheService,
     installationId: number,
+    baseURL: string,
+    githubApiURL: string,
+    githubBaseURL: string,
+    githubClientId: string,
+    githubAppName: string,
+    githubUserService: GitHubUserService,
   ) {
     if (!octokit || !installationId || installationId < 0) {
       throw new ValidationError();
@@ -55,12 +62,21 @@ export class GitHubGitProvider implements GitProvider {
     this.repoImportStateRepo = repoImportStateRepo;
     this.kubernetesService = kubernetesService;
     this.cacheService = cacheService;
+    this.baseURL = baseURL;
+    this.githubApiURL = githubApiURL;
+    this.githubBaseURL = githubBaseURL;
+    this.githubClientId = githubClientId;
+    this.githubAppName = githubAppName;
+    this.githubUserService = githubUserService;
   }
 
   private static async getOctokit(
     cacheService: KVCacheService,
     installationId: number,
     orgRepo: OrganizationRepo,
+    apiURL: string,
+    privateKey: string,
+    appId: string,
   ) {
     const githubAuthCache = {
       get: (key: string) =>
@@ -79,11 +95,11 @@ export class GitHubGitProvider implements GitProvider {
     };
 
     const octokit = new Octokit({
-      baseUrl: env.GITHUB_API_URL,
+      baseUrl: apiURL,
       authStrategy: createAppAuth,
       auth: {
         privateKey,
-        appId: env.GITHUB_APP_ID,
+        appId,
         cache: githubAuthCache,
         installationId,
       },
@@ -115,6 +131,14 @@ export class GitHubGitProvider implements GitProvider {
     repoImportStateRepo: RepoImportStateRepo,
     kubernetesService: KubernetesClientService,
     cacheService: KVCacheService,
+    githubUserService: GitHubUserService,
+    githubPrivateKey: string,
+    baseURL: string,
+    githubApiURL: string,
+    githubBaseURL: string,
+    githubAppId: string,
+    githubClientId: string,
+    githubAppName: string,
   ) {
     const org = await orgRepo.getById(orgId);
     if (!org.githubInstallationId) {
@@ -125,6 +149,9 @@ export class GitHubGitProvider implements GitProvider {
       cacheService,
       org.githubInstallationId,
       orgRepo,
+      githubApiURL,
+      githubPrivateKey,
+      githubAppId,
     );
 
     return new GitHubGitProvider(
@@ -133,6 +160,12 @@ export class GitHubGitProvider implements GitProvider {
       kubernetesService,
       cacheService,
       org.githubInstallationId,
+      baseURL,
+      githubApiURL,
+      githubBaseURL,
+      githubClientId,
+      githubAppName,
+      githubUserService,
     );
   }
 
@@ -225,11 +258,11 @@ export class GitHubGitProvider implements GitProvider {
             sourceURL.toString(),
           );
 
-          const redirectURL = new URL(env.BASE_URL);
+          const redirectURL = new URL(this.baseURL);
           redirectURL.pathname = "/import-repo"; // this URL is on the frontend; see frontend/src/pages/ImportRepoView.tsx
 
           throw new ImportRepoAuthenticationRequiredError(
-            `${env.GITHUB_BASE_URL}/login/oauth/authorize?client_id=${env.GITHUB_CLIENT_ID}&state=${stateId}&redirect_uri=${encodeURIComponent(redirectURL.toString())}`,
+            `${this.githubBaseURL}/login/oauth/authorize?client_id=${this.githubClientId}&state=${stateId}&redirect_uri=${encodeURIComponent(redirectURL.toString())}`,
           );
         }
       } else {
@@ -261,12 +294,12 @@ export class GitHubGitProvider implements GitProvider {
       throw new ValidationError("State not found");
     }
 
-    const repo = await GitHubGitProvider.getUserOctokit(
-      code,
-    ).rest.repos.createForAuthenticatedUser({
-      name: state.destRepoName,
-      private: state.makePrivate,
-    });
+    const repo = await this.githubUserService
+      .getUserOctokit(code)
+      .rest.repos.createForAuthenticatedUser({
+        name: state.destRepoName,
+        private: state.makePrivate,
+      });
 
     await this.importRepoManually(new URL(state.srcRepoURL), repo.data.id);
     await this.repoImportStateRepo.delete(stateId);
@@ -280,7 +313,7 @@ export class GitHubGitProvider implements GitProvider {
 
   async getBotCommitterDetails(): Promise<{ name: string; email: string }> {
     const bot = await this.octokit.rest.users.getByUsername({
-      username: `${env.GITHUB_APP_NAME}[bot]`, // e.g. "anvilops[bot]"
+      username: `${this.githubAppName}[bot]`, // e.g. "anvilops[bot]"
     });
     return {
       name: bot.data.login,
@@ -454,7 +487,7 @@ export class GitHubGitProvider implements GitProvider {
 
   private async importRepoManually(sourceURL: URL, destRepoId: number) {
     let cloneURL: string;
-    if (sourceURL.host === new URL(env.GITHUB_API_URL).host) {
+    if (sourceURL.host === new URL(this.githubApiURL).host) {
       // The source is on the same GitHub instance, so use our credentials to clone the repo just in case it's private
       const { owner, repoName } = this.getRepoInfoFromURL(sourceURL);
       const repo = await this.getRepoByName(owner, repoName);
@@ -465,14 +498,14 @@ export class GitHubGitProvider implements GitProvider {
 
     const pushURL = await this.generateCloneURL(destRepoId);
 
-    await this.copyRepoManually(this, cloneURL, pushURL);
+    await this.kubernetesService.copyRepoManually(this, cloneURL, pushURL);
   }
 
   private getRepoInfoFromURL(sourceURL: URL): {
     owner: string;
     repoName: string;
   } {
-    if (sourceURL.host !== new URL(env.GITHUB_API_URL).host) {
+    if (sourceURL.host !== new URL(this.githubApiURL).host) {
       throw new ValidationError("GitHub server hostname mismatch");
     }
 
@@ -492,7 +525,7 @@ export class GitHubGitProvider implements GitProvider {
     newRepoName: string,
     makePrivate: boolean,
   ): Promise<number> {
-    const destServer = new URL(env.GITHUB_API_URL);
+    const destServer = new URL(this.githubApiURL);
     if (sourceURL.host !== destServer.host) {
       // Repositories are on different Git servers - we can't use GitHub's template feature
       throw new FastImportUnsupportedError();
@@ -559,151 +592,4 @@ export class GitHubGitProvider implements GitProvider {
       ),
     ) as Repo;
   }
-
-  static async getUserFromOAuthCode(code: string) {
-    const user =
-      await GitHubGitProvider.getUserOctokit(
-        code,
-      ).rest.users.getAuthenticated();
-
-    return { id: user.data.id, login: user.data.login };
-  }
-
-  static async userCanAccessInstallation(
-    code: string,
-    installationId: number,
-  ): Promise<boolean> {
-    const octokit = GitHubGitProvider.getUserOctokit(code);
-    const installations = (
-      await octokit.rest.apps.listInstallationsForAuthenticatedUser()
-    ).data.installations;
-    for (const install of installations) {
-      if (install.id === installationId) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  static getUserOctokit(code: string) {
-    return new Octokit({
-      authStrategy: createOAuthUserAuth,
-      auth: {
-        clientId: env.GITHUB_CLIENT_ID,
-        clientSecret: env.GITHUB_CLIENT_SECRET,
-        code: code,
-      } satisfies Parameters<typeof createOAuthUserAuth>[0],
-      baseUrl: env.GITHUB_API_URL,
-    });
-  }
-
-  async copyRepoManually(
-    gitProvider: GitProvider,
-    cloneURL: string,
-    pushURL: string,
-  ) {
-    const botUser = await gitProvider.getBotCommitterDetails();
-
-    const job = await this.kubernetesService.createNamespacedJob({
-      namespace: env.CURRENT_NAMESPACE,
-      body: {
-        metadata: {
-          name: `import-repo-${crypto.randomBytes(16).toString("hex")}`,
-        },
-        spec: {
-          ttlSecondsAfterFinished: 30, // Delete job 30 seconds after it completes
-          backoffLimit: 1, // Retry up to 1 time if the job exits with a non-zero status code
-          activeDeadlineSeconds: 2 * 60, // Kill after 2 minutes
-          template: {
-            spec: {
-              containers: [
-                {
-                  name: "importer",
-                  image: "alpine/git:v2.49.0",
-                  env: [
-                    { name: "CLONE_URL", value: cloneURL },
-                    { name: "PUSH_URL", value: pushURL },
-                    { name: "USER_EMAIL", value: botUser.email },
-                    { name: "USER_NAME", value: botUser.name },
-                  ],
-                  imagePullPolicy: "Always",
-                  command: ["/bin/sh", "-c"],
-                  workingDir: "/work",
-                  args: [
-                    `
-git clone --depth=1 --shallow-submodules "$CLONE_URL" .
-rm -rf .git
-
-git init
-git branch -M main
-
-git config user.email "$USER_EMAIL"
-git config user.name "$USER_NAME"
-
-git add .
-git commit -m "Initial commit"
-
-git remote add origin "$PUSH_URL"
-git push -u origin main`,
-                  ],
-                  volumeMounts: [
-                    {
-                      mountPath: "/work",
-                      name: "work-dir",
-                    },
-                  ],
-                  resources: {
-                    requests: {
-                      cpu: "500m",
-                      memory: "512Mi",
-                    },
-                    limits: {
-                      cpu: "500m",
-                      memory: "512Mi",
-                    },
-                  },
-                  securityContext: {
-                    // TODO: Use a custom image that specifies a user. Then, add a securityContext that runs as that non-root user and enables readOnlyRootFileSystem.
-                    capabilities: {
-                      drop: ["ALL"],
-                    },
-                    allowPrivilegeEscalation: false,
-                  },
-                },
-              ],
-              volumes: [
-                {
-                  name: "work-dir",
-                  emptyDir: {
-                    sizeLimit: "1Gi",
-                  },
-                },
-              ],
-              restartPolicy: "Never",
-            },
-          },
-        },
-      },
-    });
-
-    logger.info(
-      { jobNamespace: job.metadata.namespace, jobName: job.metadata.name },
-      "Created manual Git repo import job",
-    );
-    try {
-      await this.kubernetesService.awaitJobCompletion(
-        env.CURRENT_NAMESPACE,
-        job.metadata.name,
-      );
-    } catch (e) {
-      logger.warn(
-        {
-          jobNamespace: job.metadata.namespace,
-          jobName: job.metadata.name,
-        },
-        "Git repo import job failed",
-      );
-      throw e;
-    }
-  }
 }
diff --git a/backend/src/service/common/git/githubUser.ts b/backend/src/service/common/git/githubUser.ts
new file mode 100644
index 00000000..7655fe2d
--- /dev/null
+++ b/backend/src/service/common/git/githubUser.ts
@@ -0,0 +1,56 @@
+import { createOAuthUserAuth } from "@octokit/auth-app";
+import { Octokit } from "octokit";
+
+/**
+ * Handles GitHub operations that aren't scoped to an organization's GitHub App installation.
+ * Organization-specific operations should go in GitHubGitProvider.
+ */
+export class GitHubUserService {
+  private githubApiURL: string;
+  private githubClientId: string;
+  private githubClientSecret: string;
+
+  constructor(
+    githubApiURL: string,
+    githubClientId: string,
+    githubClientSecret: string,
+  ) {
+    this.githubApiURL = githubApiURL;
+    this.githubClientId = githubClientId;
+    this.githubClientSecret = githubClientSecret;
+  }
+
+  async getUserFromOAuthCode(code: string) {
+    const user = await this.getUserOctokit(code).rest.users.getAuthenticated();
+
+    return { id: user.data.id, login: user.data.login };
+  }
+
+  async userCanAccessInstallation(
+    code: string,
+    installationId: number,
+  ): Promise<boolean> {
+    const octokit = this.getUserOctokit(code);
+    const installations = (
+      await octokit.rest.apps.listInstallationsForAuthenticatedUser()
+    ).data.installations;
+    for (const install of installations) {
+      if (install.id === installationId) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  getUserOctokit(code: string) {
+    return new Octokit({
+      authStrategy: createOAuthUserAuth,
+      auth: {
+        clientId: this.githubClientId,
+        clientSecret: this.githubClientSecret,
+        code: code,
+      } satisfies Parameters<typeof createOAuthUserAuth>[0],
+      baseUrl: this.githubApiURL,
+    });
+  }
+}
diff --git a/backend/src/service/common/helm.ts b/backend/src/service/common/helm.ts
index 0f7c9105..4ff38aaf 100644
--- a/backend/src/service/common/helm.ts
+++ b/backend/src/service/common/helm.ts
@@ -1,11 +1,10 @@
 import { V1Pod } from "@kubernetes/client-node";
 import { randomBytes } from "node:crypto";
 import type { App, Deployment, HelmConfig } from "../../db/models.ts";
-import { env } from "../../lib/env.ts";
 import { logger } from "../../logger.ts";
 import { ValidationError } from "../errors/index.ts";
 import type { KubernetesClientService } from "./cluster/kubernetes.ts";
-import { shouldImpersonate, type RancherService } from "./cluster/rancher.ts";
+import type { RancherService } from "./cluster/rancher.ts";
 import { createNamespaceConfig } from "./cluster/resources.ts";
 import type { LogCollectionService } from "./cluster/resources/logs.ts";
 
@@ -23,23 +22,38 @@ type ChartTagList = {
 };
 
 export class HelmService {
-  private rancherService: RancherService;
   private logCollectionSerice: LogCollectionService;
   private kubernetesService: KubernetesClientService;
+  private rancherService: RancherService;
+  private registryBaseURL: string;
+  private chartsProjectName: string;
+  private namespace: string;
+  private helmDeployerImageTag: string;
+  private internalBaseURL: string;
 
   constructor(
-    rancherService: RancherService,
     logCollectionSerice: LogCollectionService,
     kubernetesService: KubernetesClientService,
+    rancherService: RancherService,
+    registryBaseURL: string,
+    chartsProjectName: string,
+    namespace: string,
+    helmDeployerImageTag: string,
+    internalBaseURL: string,
   ) {
     this.logCollectionSerice = logCollectionSerice;
-    this.rancherService = rancherService;
     this.kubernetesService = kubernetesService;
+    this.rancherService = rancherService;
+    this.registryBaseURL = registryBaseURL;
+    this.chartsProjectName = chartsProjectName;
+    this.namespace = namespace;
+    this.helmDeployerImageTag = helmDeployerImageTag;
+    this.internalBaseURL = internalBaseURL;
   }
 
   async getChartToken() {
     return await fetch(
-      `${env.REGISTRY_PROTOCOL}://${env.REGISTRY_HOSTNAME}/v2/service/token?service=harbor-registry&scope=repository:${env.CHART_PROJECT_NAME}/charts:pull`,
+      `${this.registryBaseURL}/v2/service/token?service=harbor-registry&scope=repository:${this.chartsProjectName}/charts:pull`,
     )
       .then((res) => {
         if (!res.ok) {
@@ -68,7 +82,7 @@ export class HelmService {
     token: string,
   ): Promise<Chart> {
     const res = await fetch(
-      `${env.REGISTRY_PROTOCOL}://${env.REGISTRY_HOSTNAME}/v2/${repository}/manifests/${version}`,
+      `${this.registryBaseURL}/v2/${repository}/manifests/${version}`,
       {
         headers: {
           Authorization: `Bearer ${token}`,
@@ -107,7 +121,7 @@ export class HelmService {
     token: string,
   ): Promise<Chart | null> {
     const chartTagList = await fetch(
-      `${env.REGISTRY_PROTOCOL}://${env.REGISTRY_HOSTNAME}/v2/${repository}/tags/list`,
+      `${this.registryBaseURL}/v2/${repository}/tags/list`,
       {
         headers: {
           Authorization: `Bearer ${token}`,
@@ -136,7 +150,7 @@ export class HelmService {
     const api = this.kubernetesService.getClientForClusterUsername(
       app.clusterUsername,
       "KubernetesObjectApi",
-      shouldImpersonate(app.projectId),
+      this.rancherService.shouldImpersonate(app.projectId),
     );
     const namespace = createNamespaceConfig(namespaceName, app.projectId);
     if (!(await this.kubernetesService.resourceExists(api, namespace))) {
@@ -190,7 +204,7 @@ export class HelmService {
               { name: "DEPLOYMENT_API_SECRET", value: deployment.secret },
               {
                 name: "DEPLOYMENT_API_URL",
-                value: `${env.CLUSTER_INTERNAL_BASE_URL}/api`,
+                value: `${this.internalBaseURL}/api`,
               },
               {
                 name: "KUBECONFIG",
@@ -198,7 +212,7 @@ export class HelmService {
               },
               {
                 name: "HELM_KUBEASUSER",
-                value: shouldImpersonate(app.projectId)
+                value: this.rancherService.shouldImpersonate(app.projectId)
                   ? app.clusterUsername
                   : "",
               },
@@ -208,7 +222,7 @@ export class HelmService {
               },
             ],
             name: "helm",
-            image: env.HELM_DEPLOYER_IMAGE,
+            image: this.helmDeployerImageTag,
             volumeMounts: [
               {
                 name: "kubeconfig",
@@ -259,7 +273,7 @@ export class HelmService {
     const jobName = `helm-upgrade-${release}-${label}`;
     try {
       await this.kubernetesService.createNamespacedJob({
-        namespace: env.CURRENT_NAMESPACE,
+        namespace: this.namespace,
         body: {
           metadata: {
             name: jobName,
diff --git a/backend/src/service/common/registry.ts b/backend/src/service/common/registry.ts
index cb0acc6d..eb7d6bf0 100644
--- a/backend/src/service/common/registry.ts
+++ b/backend/src/service/common/registry.ts
@@ -1,6 +1,5 @@
 import type { ImageConfig } from "regclient-napi";
 import * as regclient from "regclient-napi";
-import { env } from "../../lib/env.ts";
 import { logger } from "../../logger.ts";
 
 type HarborRepository = {
@@ -14,14 +13,43 @@ type HarborRepository = {
 };
 
 export class RegistryService {
+  private registryHostname: string;
+  private registryProtocol: string;
+  private imagePullUsername: string;
+  private imagePullPassword: string;
+  private deleteRepoUsername: string;
+  private deleteRepoPassword: string;
+  private harborProjectName: string;
+  private inTilt: boolean;
+
+  constructor(
+    registryHostname: string,
+    registryProtocol: string,
+    imagePullUsername: string,
+    imagePullPassword: string,
+    deleteRepoUsername: string,
+    deleteRepoPassword: string,
+    harborProjectName: string,
+    inTilt: boolean,
+  ) {
+    this.registryHostname = registryHostname;
+    this.registryProtocol = registryProtocol;
+    this.imagePullUsername = imagePullUsername;
+    this.imagePullPassword = imagePullPassword;
+    this.deleteRepoUsername = deleteRepoUsername;
+    this.deleteRepoPassword = deleteRepoPassword;
+    this.harborProjectName = harborProjectName;
+    this.inTilt = inTilt;
+  }
+
   async deleteRepo(name: string) {
     logger.info({ name }, "Deleting image repository");
     const headers = {
-      authorization: `Basic ${Buffer.from(env.DELETE_REPO_USERNAME + ":" + env.DELETE_REPO_PASSWORD).toString("base64")}`,
+      authorization: `Basic ${Buffer.from(this.deleteRepoUsername + ":" + this.deleteRepoPassword).toString("base64")}`,
     };
 
     await fetch(
-      `${env.REGISTRY_PROTOCOL}://${env.REGISTRY_HOSTNAME}/api/v2.0/projects/${env.HARBOR_PROJECT_NAME}/repositories/${name}`,
+      `${this.registryProtocol}://${this.registryHostname}/api/v2.0/projects/${this.harborProjectName}/repositories/${name}`,
       {
         method: "DELETE",
         headers,
@@ -36,7 +64,7 @@ export class RegistryService {
 
   async getRepositoriesByProject(projectName: string) {
     const response = await fetch(
-      `${env.REGISTRY_PROTOCOL}://${env.REGISTRY_HOSTNAME}/api/v2.0/projects/${projectName}/repositories`,
+      `${this.registryProtocol}://${this.registryHostname}/api/v2.0/projects/${projectName}/repositories`,
     );
 
     if (!response.ok) {
@@ -47,33 +75,31 @@ export class RegistryService {
   }
 
   async getImageConfig(reference: string): Promise<ImageConfig> {
-    if (env.IN_TILT && reference.startsWith("localhost:")) {
+    if (this.inTilt && reference.startsWith("localhost:")) {
       // When we're in a Tilt development environment, the builder image environment variables contain
       // references to a registry at `localhost`. This works from the host machine, but it doesn't work from inside
       // the container. Instead, we need to replace it with the cluster-internal hostname.
       reference = reference.replace(
         /^localhost:\d+\//,
-        env.REGISTRY_HOSTNAME + "/",
+        this.registryHostname + "/",
       );
     }
 
     let username = "";
     let password = "";
     if (
-      reference.startsWith(
-        `${env.REGISTRY_HOSTNAME}/${env.HARBOR_PROJECT_NAME}`,
-      )
+      reference.startsWith(`${this.registryHostname}/${this.harborProjectName}`)
     ) {
-      username = env.IMAGE_PULL_USERNAME;
-      password = env.IMAGE_PULL_PASSWORD;
+      username = this.imagePullUsername;
+      password = this.imagePullPassword;
     }
 
     return await regclient.getImageConfig(
       reference,
       username,
       password,
-      env.REGISTRY_HOSTNAME,
-      env.REGISTRY_PROTOCOL !== "http" ? "enabled" : "disabled",
+      this.registryHostname,
+      this.registryProtocol !== "http" ? "enabled" : "disabled",
     );
   }
 }
diff --git a/backend/src/service/files.ts b/backend/src/service/files.ts
index 5aeac2b0..3974d268 100644
--- a/backend/src/service/files.ts
+++ b/backend/src/service/files.ts
@@ -2,7 +2,6 @@ import type { ApiException, V1Job } from "@kubernetes/client-node";
 import crypto, { randomBytes } from "node:crypto";
 import { setTimeout } from "node:timers/promises";
 import type { AppRepo } from "../db/repo/app.ts";
-import { env } from "../lib/env.ts";
 import { logger } from "../logger.ts";
 import type { KubernetesClientService } from "./common/cluster/kubernetes.ts";
 import type { StatefulSetConfigService } from "./common/cluster/resources/statefulset.ts";
@@ -16,15 +15,18 @@ export class FileBrowserService {
   private appRepo: AppRepo;
   private statefulSetConfigService: StatefulSetConfigService;
   private kubernetesService: KubernetesClientService;
+  private fileBrowserImage: string;
 
   constructor(
     appRepo: AppRepo,
     statefulSetConfigService: StatefulSetConfigService,
     kubernetesService: KubernetesClientService,
+    fileBrowserImage: string,
   ) {
     this.appRepo = appRepo;
     this.statefulSetConfigService = statefulSetConfigService;
     this.kubernetesService = kubernetesService;
+    this.fileBrowserImage = fileBrowserImage;
   }
 
   async forwardToFileBrowser(
@@ -139,7 +141,7 @@ export class FileBrowserService {
                 containers: [
                   {
                     name: "file-browser",
-                    image: env.FILE_BROWSER_IMAGE,
+                    image: this.fileBrowserImage,
                     imagePullPolicy: "Always",
                     volumeMounts: [
                       {
diff --git a/backend/src/service/getOrgByID.ts b/backend/src/service/getOrgByID.ts
index bc2074d1..9b212e5f 100644
--- a/backend/src/service/getOrgByID.ts
+++ b/backend/src/service/getOrgByID.ts
@@ -3,7 +3,6 @@ import type { AppGroupRepo } from "../db/repo/appGroup.ts";
 import type { InvitationRepo } from "../db/repo/invitation.ts";
 import type { OrganizationRepo } from "../db/repo/organization.ts";
 import type { components } from "../generated/openapi.ts";
-import { env } from "../lib/env.ts";
 import type {
   GitProvider,
   GitProviderFactoryService,
@@ -20,6 +19,7 @@ export class GetOrgByIDService {
   private appGroupRepo: AppGroupRepo;
   private invitationRepo: InvitationRepo;
   private gitProviderFactoryService: GitProviderFactoryService;
+  private appDomain: string;
 
   constructor(
     orgRepo: OrganizationRepo,
@@ -27,12 +27,14 @@ export class GetOrgByIDService {
     appGroupRepo: AppGroupRepo,
     invitationRepo: InvitationRepo,
     gitProviderFactoryService: GitProviderFactoryService,
+    appDomain: string,
   ) {
     this.orgRepo = orgRepo;
     this.appRepo = appRepo;
     this.appGroupRepo = appGroupRepo;
     this.invitationRepo = invitationRepo;
     this.gitProviderFactoryService = gitProviderFactoryService;
+    this.appDomain = appDomain;
   }
 
   async getOrgByID(orgId: number, userId: number) {
@@ -85,7 +87,7 @@ export class GetOrgByIDService {
           }
         }
 
-        const appDomain = URL.parse(env.APP_DOMAIN);
+        const appDomain = URL.parse(this.appDomain);
 
         return {
           id: app.id,
@@ -100,7 +102,7 @@ export class GetOrgByIDService {
             commitHash: config.commitHash,
             link:
               selectedDeployment?.status === "COMPLETE" &&
-              env.APP_DOMAIN &&
+              this.appDomain &&
               config.createIngress
                 ? `${appDomain.protocol}//${config.subdomain}.${appDomain.host}`
                 : undefined,
diff --git a/backend/src/service/getSettings.ts b/backend/src/service/getSettings.ts
index e057af2f..f846199c 100644
--- a/backend/src/service/getSettings.ts
+++ b/backend/src/service/getSettings.ts
@@ -1,5 +1,4 @@
 import { readFile } from "node:fs/promises";
-import { env } from "../lib/env.ts";
 import { type RancherService } from "./common/cluster/rancher.ts";
 
 type ClusterConfig = {
@@ -11,49 +10,64 @@ type ClusterConfig = {
   };
 };
 
-let clusterConfigPromise: Promise<ClusterConfig> | null = null;
-
-const configPath =
-  env["NODE_ENV"] === "development"
-    ? "./cluster.local.json"
-    : env.CLUSTER_CONFIG_PATH;
-
-if (configPath) {
-  clusterConfigPromise = readFile(configPath).then(
-    (file) => JSON.parse(file.toString()) as ClusterConfig,
-  );
-}
-
 export class GetSettingsService {
   private rancherService: RancherService;
+  private clusterConfigPromise: Promise<ClusterConfig> | null = null;
+  private appDomain: string;
+  private storageClassName: string;
+  private allowHelmDeployments: boolean;
+  private version: string;
+  private buildDate: string;
+  private inTilt: boolean;
 
-  constructor(rancherService: RancherService) {
+  constructor(
+    rancherService: RancherService,
+    configPath: string,
+    appDomain: string,
+    storageClassName: string,
+    allowHelmDeployments: boolean,
+    version: string,
+    buildDate: string,
+    inTilt: boolean,
+  ) {
     this.rancherService = rancherService;
+    this.appDomain = appDomain;
+    this.storageClassName = storageClassName;
+    this.allowHelmDeployments = allowHelmDeployments;
+    this.version = version;
+    this.buildDate = buildDate;
+    this.inTilt = inTilt;
+
+    if (configPath) {
+      this.clusterConfigPromise = readFile(configPath).then(
+        (file) => JSON.parse(file.toString()) as ClusterConfig,
+      );
+    }
   }
 
   async getSettings() {
-    const clusterConfig = await clusterConfigPromise;
+    const clusterConfig = await this.clusterConfigPromise;
 
     return {
-      appDomain: env.INGRESS_CLASS_NAME ? env.APP_DOMAIN : undefined,
-      version: getVersionString(),
+      appDomain: this.appDomain,
+      version: this.getVersionString(),
       clusterName: clusterConfig?.name,
       faq: clusterConfig?.faq,
-      storageEnabled: env.STORAGE_CLASS_NAME !== undefined,
+      storageEnabled: this.storageClassName !== undefined,
       isRancherManaged: this.rancherService.isRancherManaged(),
-      allowHelmDeployments: env.ALLOW_HELM_DEPLOYMENTS === "true",
+      allowHelmDeployments: this.allowHelmDeployments,
     };
   }
-}
 
-function getVersionString() {
-  let version = env.ANVILOPS_VERSION;
-  if (env.BUILD_DATE) {
-    version += " (" + new Date(env.BUILD_DATE).toLocaleDateString() + ")";
-  }
+  getVersionString() {
+    let version = this.version;
+    if (this.buildDate) {
+      version += " (" + new Date(this.buildDate).toLocaleDateString() + ")";
+    }
 
-  if (env.IN_TILT) {
-    version += " (dev)";
+    if (this.inTilt) {
+      version += " (dev)";
+    }
+    return version;
   }
-  return version;
 }
diff --git a/backend/src/service/getTemplates.ts b/backend/src/service/getTemplates.ts
index 217b257d..4ddbdc47 100644
--- a/backend/src/service/getTemplates.ts
+++ b/backend/src/service/getTemplates.ts
@@ -1,21 +1,19 @@
 import { readFile } from "node:fs/promises";
 import type { paths } from "../generated/openapi.ts";
-import { env } from "../lib/env.ts";
-
-const path =
-  env.NODE_ENV === "development"
-    ? "../templates/templates.json"
-    : "./templates.json";
 
 type Template =
   paths["/templates"]["get"]["responses"]["200"]["content"]["application/json"][0];
 
-const templatesPromise = readFile(path, "utf8").then(
-  (file) => JSON.parse(file.toString()) as Record<string, Template>,
-);
-
 export class GetTemplatesService {
+  private templatesPromise: Promise<Record<string, Template>>;
+
+  constructor(templateFilePath: string) {
+    this.templatesPromise = readFile(templateFilePath, "utf8").then(
+      (file) => JSON.parse(file.toString()) as Record<string, Template>,
+    );
+  }
+
   async getTemplates() {
-    return await templatesPromise;
+    return await this.templatesPromise;
   }
 }
diff --git a/backend/src/service/getUser.ts b/backend/src/service/getUser.ts
index 5ea51f85..62270e46 100644
--- a/backend/src/service/getUser.ts
+++ b/backend/src/service/getUser.ts
@@ -1,6 +1,7 @@
 import type { InvitationRepo } from "../db/repo/invitation.ts";
 import type { UserRepo } from "../db/repo/user.ts";
 import type { RancherService } from "./common/cluster/rancher.ts";
+import type { RancherAccessService } from "./common/cluster/rancherAccess.ts";
 import type { GitProviderFactoryService } from "./common/git/gitProvider.ts";
 
 export class GetUserService {
@@ -8,17 +9,20 @@ export class GetUserService {
   private invitationRepo: InvitationRepo;
   private gitProviderFactoryService: GitProviderFactoryService;
   private rancherService: RancherService;
+  private rancherAccessService: RancherAccessService;
 
   constructor(
     userRepo: UserRepo,
     invitationRepo: InvitationRepo,
     gitProviderFactoryService: GitProviderFactoryService,
     rancherService: RancherService,
+    rancherAccessService: RancherAccessService,
   ) {
     this.userRepo = userRepo;
     this.invitationRepo = invitationRepo;
     this.gitProviderFactoryService = gitProviderFactoryService;
     this.rancherService = rancherService;
+    this.rancherAccessService = rancherAccessService;
   }
 
   async getUser(userId: number) {
@@ -32,7 +36,9 @@ export class GetUserService {
 
     const projects =
       user?.clusterUsername && this.rancherService.isRancherManaged()
-        ? await this.rancherService.getProjectsForUser(user.clusterUsername)
+        ? await this.rancherAccessService.getProjectsForUser(
+            user.clusterUsername,
+          )
         : undefined;
 
     return {
diff --git a/backend/src/service/githubAppInstall.ts b/backend/src/service/githubAppInstall.ts
index 62b82e65..132a13d0 100644
--- a/backend/src/service/githubAppInstall.ts
+++ b/backend/src/service/githubAppInstall.ts
@@ -2,7 +2,6 @@ import { randomBytes } from "node:crypto";
 import type { GitHubOAuthAction, GitHubOAuthState } from "../db/models.ts";
 import type { OrganizationRepo } from "../db/repo/organization.ts";
 import type { UserRepo } from "../db/repo/user.ts";
-import { env } from "../lib/env.ts";
 import { logger } from "../logger.ts";
 import type { GitProviderFactoryService } from "./common/git/gitProvider.ts";
 import { OrgAlreadyLinkedError, OrgNotFoundError } from "./errors/index.ts";
@@ -11,15 +10,21 @@ export class CreateGitHubAppInstallStateService {
   private orgRepo: OrganizationRepo;
   private userRepo: UserRepo;
   private gitProviderFactoryService: GitProviderFactoryService;
+  private githubBaseURL: string;
+  private githubAppName: string;
 
   constructor(
     orgRepo: OrganizationRepo,
     userRepo: UserRepo,
     gitProviderFactoryService: GitProviderFactoryService,
+    githubBaseURL: string,
+    githubAppName: string,
   ) {
     this.orgRepo = orgRepo;
     this.userRepo = userRepo;
     this.gitProviderFactoryService = gitProviderFactoryService;
+    this.githubBaseURL = githubBaseURL;
+    this.githubAppName = githubAppName;
   }
 
   async createGitHubAppInstallURL(orgId: number, userId: number) {
@@ -43,7 +48,7 @@ export class CreateGitHubAppInstallStateService {
       userId,
       orgId,
     );
-    return `${env.GITHUB_BASE_URL}/github-apps/${env.GITHUB_APP_NAME}/installations/new?state=${newState}`;
+    return `${this.githubBaseURL}/github-apps/${this.githubAppName}/installations/new?state=${newState}`;
   }
 
   async createState(action: GitHubOAuthAction, userId: number, orgId: number) {
diff --git a/backend/src/service/githubInstallCallback.ts b/backend/src/service/githubInstallCallback.ts
index 23021248..7e0c1b0e 100644
--- a/backend/src/service/githubInstallCallback.ts
+++ b/backend/src/service/githubInstallCallback.ts
@@ -1,5 +1,4 @@
 import type { OrganizationRepo } from "../db/repo/organization.ts";
-import { env } from "../lib/env.ts";
 import { logger } from "../logger.ts";
 import {
   GitHubOAuthAccountMismatchError,
@@ -11,13 +10,19 @@ import { type CreateGitHubAppInstallStateService } from "./githubAppInstall.ts";
 export class GitHubInstallCallbackService {
   private orgRepo: OrganizationRepo;
   private appInstallService: CreateGitHubAppInstallStateService;
+  private githubBaseURL: string;
+  private githubClientID: string;
 
   constructor(
     orgRepo: OrganizationRepo,
     appInstallService: CreateGitHubAppInstallStateService,
+    githubBaseURL: string,
+    githubClientID: string,
   ) {
     this.orgRepo = orgRepo;
     this.appInstallService = appInstallService;
+    this.githubBaseURL = githubBaseURL;
+    this.githubClientID = githubClientID;
   }
 
   async createGitHubAuthorizationURL(
@@ -57,7 +62,7 @@ export class GitHubInstallCallbackService {
         stateUserId,
         orgId,
       );
-      return `${env.GITHUB_BASE_URL}/login/oauth/authorize?client_id=${env.GITHUB_CLIENT_ID}&state=${newState}`;
+      return `${this.githubBaseURL}/login/oauth/authorize?client_id=${this.githubClientID}&state=${newState}`;
     }
 
     // Verify the user ID hasn't changed
@@ -84,6 +89,6 @@ export class GitHubInstallCallbackService {
       orgId,
     );
 
-    return `${env.GITHUB_BASE_URL}/login/oauth/authorize?client_id=${env.GITHUB_CLIENT_ID}&state=${newState}`;
+    return `${this.githubBaseURL}/login/oauth/authorize?client_id=${this.githubClientID}&state=${newState}`;
   }
 }
diff --git a/backend/src/service/githubOAuthCallback.ts b/backend/src/service/githubOAuthCallback.ts
index fb475ed6..f594faea 100644
--- a/backend/src/service/githubOAuthCallback.ts
+++ b/backend/src/service/githubOAuthCallback.ts
@@ -2,7 +2,7 @@ import type { GitHubOAuthAction } from "../db/models.ts";
 import type { OrganizationRepo } from "../db/repo/organization.ts";
 import type { UserRepo } from "../db/repo/user.ts";
 import { logger } from "../logger.ts";
-import { GitHubGitProvider } from "./common/git/githubGitProvider.ts";
+import type { GitHubUserService } from "./common/git/githubUser.ts";
 import {
   GitHubInstallationForbiddenError,
   GitHubOAuthAccountMismatchError,
@@ -18,15 +18,18 @@ export class GitHubOAuthCallbackService {
   private orgRepo: OrganizationRepo;
   private userRepo: UserRepo;
   private appInstallService: CreateGitHubAppInstallStateService;
+  private githubUserService: GitHubUserService;
 
   constructor(
     orgRepo: OrganizationRepo,
     userRepo: UserRepo,
     appInstallService: CreateGitHubAppInstallStateService,
+    githubUserService: GitHubUserService,
   ) {
     this.orgRepo = orgRepo;
     this.userRepo = userRepo;
     this.appInstallService = appInstallService;
+    this.githubUserService = githubUserService;
   }
 
   async processGitHubOAuthResponse(
@@ -64,7 +67,7 @@ export class GitHubOAuthCallbackService {
         throw new InstallationNotFoundError(null);
       }
 
-      const canAccess = await GitHubGitProvider.userCanAccessInstallation(
+      const canAccess = await this.githubUserService.userCanAccessInstallation(
         code,
         org.newInstallationId,
       );
@@ -86,7 +89,7 @@ export class GitHubOAuthCallbackService {
       return "done";
     } else if (state === "GET_UID_FOR_LATER_INSTALLATION") {
       const { id: githubUserId, login: userLogin } =
-        await GitHubGitProvider.getUserFromOAuthCode(code);
+        await this.githubUserService.getUserFromOAuthCode(code);
 
       await this.userRepo.setGitHubUserId(userId, githubUserId);
 
diff --git a/backend/src/service/githubWebhook.ts b/backend/src/service/githubWebhook.ts
index caf7c9c8..d4b3618d 100644
--- a/backend/src/service/githubWebhook.ts
+++ b/backend/src/service/githubWebhook.ts
@@ -7,7 +7,6 @@ import type { DeploymentRepo } from "../db/repo/deployment.ts";
 import type { OrganizationRepo } from "../db/repo/organization.ts";
 import type { UserRepo } from "../db/repo/user.ts";
 import type { components } from "../generated/openapi.ts";
-import { env } from "../lib/env.ts";
 import { logger } from "../logger.ts";
 import type { DeploymentService } from "./common/deployment.ts";
 import type { DeploymentConfigService } from "./common/deploymentConfig.ts";
@@ -19,8 +18,6 @@ import {
   ValidationError,
 } from "./errors/index.ts";
 
-const webhooks = new Webhooks({ secret: env.GITHUB_WEBHOOK_SECRET });
-
 export class GitHubWebhookService {
   private orgRepo: OrganizationRepo;
   private appRepo: AppRepo;
@@ -29,6 +26,8 @@ export class GitHubWebhookService {
   private deploymentService: DeploymentService;
   private deploymentConfigService: DeploymentConfigService;
   private gitProviderFactoryService: GitProviderFactoryService;
+  private webhooks: Webhooks;
+  private githubAppId: string;
 
   constructor(
     orgRepo: OrganizationRepo,
@@ -38,6 +37,8 @@ export class GitHubWebhookService {
     deploymentService: DeploymentService,
     deploymentConfigService: DeploymentConfigService,
     gitProviderFactoryService: GitProviderFactoryService,
+    githubAppId: string,
+    githubWebhookSecret: string,
   ) {
     this.orgRepo = orgRepo;
     this.appRepo = appRepo;
@@ -46,10 +47,12 @@ export class GitHubWebhookService {
     this.deploymentService = deploymentService;
     this.deploymentConfigService = deploymentConfigService;
     this.gitProviderFactoryService = gitProviderFactoryService;
+    this.githubAppId = githubAppId;
+    this.webhooks = new Webhooks({ secret: githubWebhookSecret });
   }
 
   async verifyGitHubWebhookPayload(data: string, signature: string) {
-    return await webhooks.verify(data, signature);
+    return await this.webhooks.verify(data, signature);
   }
 
   async processGitHubWebhookPayload(
@@ -179,7 +182,7 @@ export class GitHubWebhookService {
       return;
     }
 
-    if (payload.installation.app_id.toString() !== env.GITHUB_APP_ID) {
+    if (payload.installation.app_id.toString() !== this.githubAppId) {
       // Sanity check
       throw new ValidationError("Invalid GitHub app ID");
     }
diff --git a/backend/src/service/index.ts b/backend/src/service/index.ts
index f87e3232..4cbebc28 100644
--- a/backend/src/service/index.ts
+++ b/backend/src/service/index.ts
@@ -1,4 +1,5 @@
-import { db } from "../db/index.ts";
+import { PgDatabase, type Database } from "../db/index.ts";
+import { env, parseCsv } from "../lib/env.ts";
 import { AcceptInvitationService } from "./acceptInvitation.ts";
 import { AuthService } from "./auth.ts";
 import { ClaimOrgService } from "./claimOrg.ts";
@@ -7,6 +8,7 @@ import { BuilderService } from "./common/builder.ts";
 import { KVCacheService } from "./common/cache.ts";
 import { KubernetesClientService } from "./common/cluster/kubernetes.ts";
 import { RancherService } from "./common/cluster/rancher.ts";
+import { RancherAccessService } from "./common/cluster/rancherAccess.ts";
 import { ClusterResourcesService } from "./common/cluster/resources.ts";
 import { IngressConfigService } from "./common/cluster/resources/ingress.ts";
 import { LogCollectionService } from "./common/cluster/resources/logs.ts";
@@ -14,6 +16,7 @@ import { ServiceConfigService } from "./common/cluster/resources/service.ts";
 import { StatefulSetConfigService } from "./common/cluster/resources/statefulset.ts";
 import { DeploymentService } from "./common/deployment.ts";
 import { DeploymentConfigService } from "./common/deploymentConfig.ts";
+import { GitHubUserService } from "./common/git/githubUser.ts";
 import { GitProviderFactoryService } from "./common/git/gitProvider.ts";
 import { HelmService } from "./common/helm.ts";
 import { RegistryService } from "./common/registry.ts";
@@ -54,36 +57,97 @@ import { SetAppCDService } from "./setAppCD.ts";
 import { UpdateAppService } from "./updateApp.ts";
 import { UpdateDeploymentService } from "./updateDeployment.ts";
 
+export const db: Database = new PgDatabase(
+  env.DATABASE_URL ??
+    `postgresql://${env.POSTGRES_USER}:${env.POSTGRES_PASSWORD}@${env.POSTGRES_HOSTNAME}/${env.POSTGRES_DB}`,
+  env.FIELD_ENCRYPTION_KEY,
+);
+
 export const cacheService = new KVCacheService(db.cache);
 
-export const kubernetesClientService = new KubernetesClientService(db.user);
+export const rancherService = new RancherService(
+  env.RANCHER_TOKEN,
+  env.RANCHER_BASE_URL,
+  env.LOGIN_TYPE,
+  env.SANDBOX_ID,
+);
 
-export const registryService = new RegistryService();
+export const kubernetesClientService = new KubernetesClientService(
+  db.user,
+  rancherService,
+  env.CURRENT_NAMESPACE,
+);
+
+export const rancherAccessService = new RancherAccessService(
+  kubernetesClientService,
+  rancherService,
+  cacheService,
+  env.SANDBOX_ID,
+);
+
+export const registryService = new RegistryService(
+  env.REGISTRY_HOSTNAME,
+  env.REGISTRY_PROTOCOL,
+  env.IMAGE_PULL_USERNAME,
+  env.IMAGE_PULL_PASSWORD,
+  env.DELETE_REPO_USERNAME,
+  env.DELETE_REPO_PASSWORD,
+  env.HARBOR_PROJECT_NAME,
+  !!env.IN_TILT,
+);
+
+export const githubUserService = new GitHubUserService(
+  env.GITHUB_API_URL,
+  env.GITHUB_CLIENT_ID,
+  env.GITHUB_CLIENT_SECRET,
+);
 
 export const gitProviderFactoryService = new GitProviderFactoryService(
   db.org,
   db.repoImportState,
   kubernetesClientService,
   cacheService,
+  githubUserService,
+  Buffer.from(env.GITHUB_PRIVATE_KEY, "base64").toString("utf-8"),
+  env.BASE_URL,
+  env.GITHUB_API_URL,
+  env.GITHUB_BASE_URL,
+  env.GITHUB_APP_ID,
+  env.GITHUB_CLIENT_ID,
+  env.GITHUB_APP_NAME,
 );
 
 export const ingressConfigService = new IngressConfigService(
   kubernetesClientService,
+  env.APP_DOMAIN,
+  env.INGRESS_CLASS_NAME,
+  env.CURRENT_NAMESPACE,
 );
 
-export const rancherService = new RancherService(
-  kubernetesClientService,
-  cacheService,
+export const authService = new AuthService(
+  db.user,
+  rancherService,
+  env.USE_RANCHER_OIDC === "true",
+  env.BASE_URL,
+  env.RANCHER_BASE_URL,
+  parseCsv(env.ALLOWED_IDPS) ?? [],
+  env.CLIENT_ID,
+  env.CLIENT_SECRET,
+  env.LOGIN_CLAIM,
 );
 
-export const authService = new AuthService(db.user, rancherService);
-
 export const serviceConfigService = new ServiceConfigService();
 
-export const logCollectionService = new LogCollectionService(registryService);
+export const logCollectionService = new LogCollectionService(
+  registryService,
+  env.LOG_SHIPPER_IMAGE,
+  env.CLUSTER_INTERNAL_BASE_URL,
+);
 
 export const statefulSetConfigService = new StatefulSetConfigService(
   logCollectionService,
+  env.STORAGE_CLASS_NAME,
+  env.STORAGE_ACCESS_MODES.split(","),
 );
 
 export const deploymentConfigService = new DeploymentConfigService(
@@ -92,6 +156,9 @@ export const deploymentConfigService = new DeploymentConfigService(
   registryService,
   ingressConfigService,
   statefulSetConfigService,
+  env.APP_DOMAIN,
+  env.REGISTRY_HOSTNAME,
+  env.HARBOR_PROJECT_NAME,
 );
 
 export const clusterResourcesService = new ClusterResourcesService(
@@ -100,6 +167,13 @@ export const clusterResourcesService = new ClusterResourcesService(
   ingressConfigService,
   statefulSetConfigService,
   deploymentConfigService,
+  !!env.CREATE_INGRESS_NETPOL,
+  JSON.parse(env.ALLOW_INGRESS_FROM) as {
+    [key: string]: string;
+  }[],
+  env.REGISTRY_HOSTNAME,
+  env.IMAGE_PULL_USERNAME,
+  env.IMAGE_PULL_PASSWORD,
 );
 
 export const isNamespaceAvailableService = new IsNamespaceAvailableService(
@@ -111,6 +185,8 @@ export const appService = new AppService(
   isNamespaceAvailableService,
   gitProviderFactoryService,
   rancherService,
+  rancherAccessService,
+  !!env.ALLOW_HELM_DEPLOYMENTS,
 );
 
 export const builderService = new BuilderService(
@@ -121,12 +197,27 @@ export const builderService = new BuilderService(
   logCollectionService,
   deploymentConfigService,
   kubernetesClientService,
+  env.CURRENT_NAMESPACE,
+  env.DOCKERFILE_BUILDER_IMAGE,
+  env.RAILPACK_BUILDER_IMAGE,
+  env.REGISTRY_HOSTNAME,
+  env.HARBOR_PROJECT_NAME,
+  env.CLUSTER_INTERNAL_BASE_URL,
+  env.BUILDKITD_ADDRESS,
+  env.RAILPACK_INTERNAL_FRONTEND_IMAGE,
+  env.RAILPACK_INTERNAL_BUILDER_IMAGE,
+  env.RAILPACK_INTERNAL_RUNTIME_IMAGE,
 );
 
 export const helmService = new HelmService(
-  rancherService,
   logCollectionService,
   kubernetesClientService,
+  rancherService,
+  `${env.REGISTRY_PROTOCOL}://${env.REGISTRY_HOSTNAME}`,
+  env.CHART_PROJECT_NAME,
+  env.CURRENT_NAMESPACE,
+  env.HELM_DEPLOYER_IMAGE,
+  env.CLUSTER_INTERNAL_BASE_URL,
 );
 
 export const deploymentService = new DeploymentService(
@@ -138,6 +229,7 @@ export const deploymentService = new DeploymentService(
   builderService,
   clusterResourcesService,
   kubernetesClientService,
+  env.BASE_URL,
 );
 
 export const acceptInvitationService = new AcceptInvitationService(
@@ -193,6 +285,7 @@ export const fileBrowserService = new FileBrowserService(
   db.app,
   statefulSetConfigService,
   kubernetesClientService,
+  env.FILE_BROWSER_IMAGE,
 );
 
 export const getAppByIDService = new GetAppByIDService(
@@ -236,17 +329,34 @@ export const getOrgByIDService = new GetOrgByIDService(
   db.appGroup,
   db.invitation,
   gitProviderFactoryService,
+  env.APP_DOMAIN,
 );
 
-export const getSettingsService = new GetSettingsService(rancherService);
+export const getSettingsService = new GetSettingsService(
+  rancherService,
+  env["NODE_ENV"] === "development"
+    ? "./cluster.local.json"
+    : env.CLUSTER_CONFIG_PATH,
+  env.INGRESS_CLASS_NAME ? env.APP_DOMAIN : undefined,
+  env.STORAGE_CLASS_NAME,
+  env.ALLOW_HELM_DEPLOYMENTS === "true",
+  env.ANVILOPS_VERSION,
+  env.BUILD_DATE,
+  !!env.IN_TILT,
+);
 
-export const getTemplatesService = new GetTemplatesService();
+export const getTemplatesService = new GetTemplatesService(
+  env.NODE_ENV === "development"
+    ? "../templates/templates.json"
+    : "./templates.json",
+);
 
 export const getUserService = new GetUserService(
   db.user,
   db.invitation,
   gitProviderFactoryService,
   rancherService,
+  rancherAccessService,
 );
 
 export const createGitHubAppInstallStateService =
@@ -254,17 +364,22 @@ export const createGitHubAppInstallStateService =
     db.org,
     db.user,
     gitProviderFactoryService,
+    env.GITHUB_BASE_URL,
+    env.GITHUB_APP_NAME,
   );
 
 export const githubInstallCallbackService = new GitHubInstallCallbackService(
   db.org,
   createGitHubAppInstallStateService,
+  env.GITHUB_BASE_URL,
+  env.GITHUB_CLIENT_ID,
 );
 
 export const gitHubOAuthCallbackService = new GitHubOAuthCallbackService(
   db.org,
   db.user,
   createGitHubAppInstallStateService,
+  githubUserService,
 );
 
 export const githubWebhookService = new GitHubWebhookService(
@@ -275,6 +390,8 @@ export const githubWebhookService = new GitHubWebhookService(
   deploymentService,
   deploymentConfigService,
   gitProviderFactoryService,
+  env.GITHUB_APP_ID,
+  env.GITHUB_WEBHOOK_SECRET,
 );
 
 export const importGitRepoService = new ImportGitRepoService(
@@ -295,6 +412,9 @@ export const listChartsService = new ListChartsService(
   registryService,
   helmService,
   cacheService,
+  !!env.ALLOW_HELM_DEPLOYMENTS,
+  env.REGISTRY_HOSTNAME,
+  env.CHART_PROJECT_NAME,
 );
 
 export const listDeploymentsService = new ListDeploymentsService(
diff --git a/backend/src/service/listCharts.ts b/backend/src/service/listCharts.ts
index 3a0a8677..2d7d0d49 100644
--- a/backend/src/service/listCharts.ts
+++ b/backend/src/service/listCharts.ts
@@ -1,4 +1,3 @@
-import { env } from "../lib/env.ts";
 import { type KVCacheService } from "./common/cache.ts";
 import type { HelmService } from "./common/helm.ts";
 import type { RegistryService } from "./common/registry.ts";
@@ -8,19 +7,28 @@ export class ListChartsService {
   private registryService: RegistryService;
   private helmService: HelmService;
   private cacheService: KVCacheService;
+  private helmDeploymentsEnabled: boolean;
+  private registryHostname: string;
+  private chartProjectName: string;
 
   constructor(
     registryService: RegistryService,
     helmService: HelmService,
     cacheService: KVCacheService,
+    helmDeploymentsEnabled: boolean,
+    registryHostname: string,
+    chartProjectName: string,
   ) {
     this.registryService = registryService;
     this.helmService = helmService;
     this.cacheService = cacheService;
+    this.helmDeploymentsEnabled = helmDeploymentsEnabled;
+    this.registryHostname = registryHostname;
+    this.chartProjectName = chartProjectName;
   }
 
   async listCharts() {
-    if (!env.ALLOW_HELM_DEPLOYMENTS) {
+    if (!this.helmDeploymentsEnabled) {
       throw new ValidationError("Helm deployments are disabled");
     }
     return JSON.parse(
@@ -32,7 +40,7 @@ export class ListChartsService {
 
   async listChartsFromRegistry() {
     const [repos, token] = await Promise.all([
-      this.registryService.getRepositoriesByProject(env.CHART_PROJECT_NAME),
+      this.registryService.getRepositoriesByProject(this.chartProjectName),
       this.helmService.getChartToken(),
     ]);
 
@@ -45,7 +53,7 @@ export class ListChartsService {
     return charts.filter(Boolean).map((chart) => ({
       name: chart.name,
       note: chart.note,
-      url: `oci://${env.REGISTRY_HOSTNAME}/${env.CHART_PROJECT_NAME}/${chart.name}`,
+      url: `oci://${this.registryHostname}/${this.chartProjectName}/${chart.name}`,
       urlType: "oci",
       version: chart.version,
       valueSpec: chart.values,
diff --git a/backend/test/db.spec.ts b/backend/test/db.spec.ts
new file mode 100644
index 00000000..860c59aa
--- /dev/null
+++ b/backend/test/db.spec.ts
@@ -0,0 +1,7 @@
+import { expect, test } from "vitest";
+import { createDB } from "./util/db.ts";
+
+test("database initializes properly", async () => {
+  const db = await createDB();
+  expect(db).toBeDefined();
+});
diff --git a/backend/test/util/db.ts b/backend/test/util/db.ts
new file mode 100644
index 00000000..724be1f2
--- /dev/null
+++ b/backend/test/util/db.ts
@@ -0,0 +1,66 @@
+import { PGlite } from "@electric-sql/pglite";
+import { randomBytes } from "node:crypto";
+import { readdir, readFile, stat } from "node:fs/promises";
+import { join } from "node:path";
+import type { Notification } from "pg";
+import { PrismaPGlite } from "pglite-prisma-adapter";
+import {
+  PrismaDatabase,
+  type Database,
+  type PrismaClientType,
+} from "../../src/db/index.ts";
+import { PrismaClient } from "../../src/generated/prisma/client.ts";
+
+class PgliteDatabase extends PrismaDatabase {
+  private pglite: PGlite;
+
+  constructor(client: PGlite) {
+    const adapter = new PrismaPGlite(client);
+    const db = new PrismaClient({ adapter }) satisfies PrismaClientType;
+    super(db, randomBytes(32).toString("base64"));
+    this.pglite = client;
+  }
+
+  override async subscribe(
+    channel: string,
+    callback: (msg: Notification) => void,
+  ): Promise<() => Promise<void>> {
+    return await this.pglite.listen(channel, (payload) =>
+      callback({ processId: -1, channel, payload }),
+    );
+  }
+
+  override async publish(channel: string, payload: string): Promise<void> {
+    await this.pglite.query("SELECT pg_notify($1, $2);", [channel, payload]);
+  }
+}
+
+/**
+ * Returns a Prisma Client that points to a new, in-memory PGlite database.
+ * Changes to this database won't affect any other databases returned from previous calls to this function.
+ */
+export async function createDB(): Promise<Database> {
+  // https://makerkit.dev/blog/tutorials/unit-testing-prisma-vitest
+  const client = new PGlite();
+
+  const migrationsDir = join(
+    import.meta.dirname,
+    "..",
+    "..",
+    "prisma",
+    "migrations",
+  );
+  const entries = await readdir(migrationsDir);
+
+  for (const entry of entries) {
+    /* eslint-disable no-await-in-loop */
+    if (!(await stat(join(migrationsDir, entry))).isDirectory()) continue;
+
+    const migration = join(migrationsDir, entry, "migration.sql");
+    const sql = await readFile(migration, "utf-8");
+    await client.exec(sql);
+    /* eslint-enable no-await-in-loop */
+  }
+
+  return new PgliteDatabase(client);
+}
diff --git a/backend/test/util/types.d.ts b/backend/test/util/types.d.ts
new file mode 100644
index 00000000..b60320be
--- /dev/null
+++ b/backend/test/util/types.d.ts
@@ -0,0 +1,11 @@
+// These are the necessary type augmentations to get pglite to build without adding "DOM"
+// to the "lib" list in backend/tsconfig.json (which is undesirable because Node.js generally
+// doesn't support DOM APIs and the project is never meant to run in a browser)
+
+declare namespace WebAssembly {
+  type Instance = unknown;
+  type Imports = unknown;
+  type Exports = unknown;
+}
+
+type IDBDatabase = unknown;
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index dc09039f..31295cee 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -21,7 +21,7 @@
         "@radix-ui/react-switch": "^1.2.6",
         "@radix-ui/react-tabs": "^1.1.13",
         "@radix-ui/react-tooltip": "^1.2.8",
-        "@tailwindcss/vite": "^4.1.17",
+        "@tailwindcss/vite": "^4.2.2",
         "@tanstack/react-query": "^5.90.11",
         "@tanstack/react-query-devtools": "^5.91.1",
         "class-variance-authority": "^0.7.1",
@@ -38,14 +38,14 @@
         "react-router-dom": "^7.9.6",
         "sonner": "^2.0.7",
         "tailwind-merge": "^3.4.0",
-        "tailwindcss": "^4.1.17"
+        "tailwindcss": "^4.2.2"
       },
       "devDependencies": {
         "@eslint/js": "^9.39.1",
         "@types/node": "^24.10.1",
         "@types/react": "^19.2.7",
         "@types/react-dom": "^19.2.3",
-        "@vitejs/plugin-react": "^5.1.1",
+        "@vitejs/plugin-react": "^6.0.1",
         "eslint": "^9.39.1",
         "eslint-plugin-better-tailwindcss": "^4.0.1",
         "eslint-plugin-jsx-a11y": "^6.10.2",
@@ -56,7 +56,7 @@
         "tw-animate-css": "^1.4.0",
         "typescript": "~5.9.3",
         "typescript-eslint": "^8.48.0",
-        "vite": "npm:rolldown-vite@7.3.1"
+        "vite": "^8.0.8"
       }
     },
     "node_modules/@babel/code-frame": {
@@ -191,16 +191,6 @@
         "@babel/core": "^7.0.0"
       }
     },
-    "node_modules/@babel/helper-plugin-utils": {
-      "version": "7.28.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
-      "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
     "node_modules/@babel/helper-string-parser": {
       "version": "7.27.1",
       "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
@@ -261,38 +251,6 @@
         "node": ">=6.0.0"
       }
     },
-    "node_modules/@babel/plugin-transform-react-jsx-self": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
-      "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-plugin-utils": "^7.27.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/@babel/plugin-transform-react-jsx-source": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
-      "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-plugin-utils": "^7.27.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
     "node_modules/@babel/template": {
       "version": "7.28.6",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -342,20 +300,20 @@
       }
     },
     "node_modules/@emnapi/core": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
-      "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
+      "version": "1.9.2",
+      "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
+      "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
       "license": "MIT",
       "optional": true,
       "dependencies": {
-        "@emnapi/wasi-threads": "1.1.0",
+        "@emnapi/wasi-threads": "1.2.1",
         "tslib": "^2.4.0"
       }
     },
     "node_modules/@emnapi/runtime": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
-      "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+      "version": "1.9.2",
+      "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
+      "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
       "license": "MIT",
       "optional": true,
       "dependencies": {
@@ -363,9 +321,9 @@
       }
     },
     "node_modules/@emnapi/wasi-threads": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
-      "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+      "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
       "license": "MIT",
       "optional": true,
       "dependencies": {
@@ -702,34 +660,27 @@
       }
     },
     "node_modules/@napi-rs/wasm-runtime": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
-      "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz",
+      "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==",
       "license": "MIT",
       "optional": true,
       "dependencies": {
-        "@emnapi/core": "^1.7.1",
-        "@emnapi/runtime": "^1.7.1",
         "@tybys/wasm-util": "^0.10.1"
       },
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/Brooooooklyn"
-      }
-    },
-    "node_modules/@oxc-project/runtime": {
-      "version": "0.101.0",
-      "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.101.0.tgz",
-      "integrity": "sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ==",
-      "license": "MIT",
-      "engines": {
-        "node": "^20.19.0 || >=22.12.0"
+      },
+      "peerDependencies": {
+        "@emnapi/core": "^1.7.1",
+        "@emnapi/runtime": "^1.7.1"
       }
     },
     "node_modules/@oxc-project/types": {
-      "version": "0.101.0",
-      "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.101.0.tgz",
-      "integrity": "sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==",
+      "version": "0.124.0",
+      "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz",
+      "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==",
       "license": "MIT",
       "funding": {
         "url": "https://github.com/sponsors/Boshen"
@@ -1821,9 +1772,9 @@
       "license": "MIT"
     },
     "node_modules/@rolldown/binding-android-arm64": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.53.tgz",
-      "integrity": "sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==",
       "cpu": [
         "arm64"
       ],
@@ -1837,9 +1788,9 @@
       }
     },
     "node_modules/@rolldown/binding-darwin-arm64": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.53.tgz",
-      "integrity": "sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==",
       "cpu": [
         "arm64"
       ],
@@ -1853,9 +1804,9 @@
       }
     },
     "node_modules/@rolldown/binding-darwin-x64": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.53.tgz",
-      "integrity": "sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==",
       "cpu": [
         "x64"
       ],
@@ -1869,9 +1820,9 @@
       }
     },
     "node_modules/@rolldown/binding-freebsd-x64": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.53.tgz",
-      "integrity": "sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==",
       "cpu": [
         "x64"
       ],
@@ -1885,9 +1836,9 @@
       }
     },
     "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.53.tgz",
-      "integrity": "sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz",
+      "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==",
       "cpu": [
         "arm"
       ],
@@ -1901,9 +1852,9 @@
       }
     },
     "node_modules/@rolldown/binding-linux-arm64-gnu": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.53.tgz",
-      "integrity": "sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==",
       "cpu": [
         "arm64"
       ],
@@ -1917,9 +1868,9 @@
       }
     },
     "node_modules/@rolldown/binding-linux-arm64-musl": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.53.tgz",
-      "integrity": "sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz",
+      "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==",
       "cpu": [
         "arm64"
       ],
@@ -1932,10 +1883,42 @@
         "node": "^20.19.0 || >=22.12.0"
       }
     },
+    "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-s390x-gnu": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
     "node_modules/@rolldown/binding-linux-x64-gnu": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.53.tgz",
-      "integrity": "sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==",
       "cpu": [
         "x64"
       ],
@@ -1949,9 +1932,9 @@
       }
     },
     "node_modules/@rolldown/binding-linux-x64-musl": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.53.tgz",
-      "integrity": "sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz",
+      "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==",
       "cpu": [
         "x64"
       ],
@@ -1965,9 +1948,9 @@
       }
     },
     "node_modules/@rolldown/binding-openharmony-arm64": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.53.tgz",
-      "integrity": "sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==",
       "cpu": [
         "arm64"
       ],
@@ -1981,25 +1964,27 @@
       }
     },
     "node_modules/@rolldown/binding-wasm32-wasi": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.53.tgz",
-      "integrity": "sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz",
+      "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==",
       "cpu": [
         "wasm32"
       ],
       "license": "MIT",
       "optional": true,
       "dependencies": {
-        "@napi-rs/wasm-runtime": "^1.1.0"
+        "@emnapi/core": "1.9.2",
+        "@emnapi/runtime": "1.9.2",
+        "@napi-rs/wasm-runtime": "^1.1.3"
       },
       "engines": {
         "node": ">=14.0.0"
       }
     },
     "node_modules/@rolldown/binding-win32-arm64-msvc": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.53.tgz",
-      "integrity": "sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz",
+      "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==",
       "cpu": [
         "arm64"
       ],
@@ -2013,9 +1998,9 @@
       }
     },
     "node_modules/@rolldown/binding-win32-x64-msvc": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.53.tgz",
-      "integrity": "sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz",
+      "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==",
       "cpu": [
         "x64"
       ],
@@ -2029,53 +2014,54 @@
       }
     },
     "node_modules/@rolldown/pluginutils": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
-      "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==",
+      "version": "1.0.0-rc.7",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
+      "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/@tailwindcss/node": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
-      "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
+      "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
       "license": "MIT",
       "dependencies": {
-        "@jridgewell/remapping": "^2.3.4",
-        "enhanced-resolve": "^5.18.3",
+        "@jridgewell/remapping": "^2.3.5",
+        "enhanced-resolve": "^5.19.0",
         "jiti": "^2.6.1",
-        "lightningcss": "1.30.2",
+        "lightningcss": "1.32.0",
         "magic-string": "^0.30.21",
         "source-map-js": "^1.2.1",
-        "tailwindcss": "4.1.18"
+        "tailwindcss": "4.2.2"
       }
     },
     "node_modules/@tailwindcss/oxide": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
-      "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz",
+      "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
       "license": "MIT",
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       },
       "optionalDependencies": {
-        "@tailwindcss/oxide-android-arm64": "4.1.18",
-        "@tailwindcss/oxide-darwin-arm64": "4.1.18",
-        "@tailwindcss/oxide-darwin-x64": "4.1.18",
-        "@tailwindcss/oxide-freebsd-x64": "4.1.18",
-        "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
-        "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
-        "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
-        "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
-        "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
-        "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
-        "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
-        "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
+        "@tailwindcss/oxide-android-arm64": "4.2.2",
+        "@tailwindcss/oxide-darwin-arm64": "4.2.2",
+        "@tailwindcss/oxide-darwin-x64": "4.2.2",
+        "@tailwindcss/oxide-freebsd-x64": "4.2.2",
+        "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
+        "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
+        "@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
+        "@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
+        "@tailwindcss/oxide-linux-x64-musl": "4.2.2",
+        "@tailwindcss/oxide-wasm32-wasi": "4.2.2",
+        "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
+        "@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
       }
     },
     "node_modules/@tailwindcss/oxide-android-arm64": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
-      "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz",
+      "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
       "cpu": [
         "arm64"
       ],
@@ -2085,13 +2071,13 @@
         "android"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/oxide-darwin-arm64": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
-      "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz",
+      "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
       "cpu": [
         "arm64"
       ],
@@ -2101,13 +2087,13 @@
         "darwin"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/oxide-darwin-x64": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
-      "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz",
+      "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
       "cpu": [
         "x64"
       ],
@@ -2117,13 +2103,13 @@
         "darwin"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/oxide-freebsd-x64": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
-      "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz",
+      "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
       "cpu": [
         "x64"
       ],
@@ -2133,13 +2119,13 @@
         "freebsd"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
-      "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz",
+      "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
       "cpu": [
         "arm"
       ],
@@ -2149,13 +2135,13 @@
         "linux"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
-      "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz",
+      "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
       "cpu": [
         "arm64"
       ],
@@ -2165,13 +2151,13 @@
         "linux"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
-      "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz",
+      "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
       "cpu": [
         "arm64"
       ],
@@ -2181,13 +2167,13 @@
         "linux"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
-      "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz",
+      "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
       "cpu": [
         "x64"
       ],
@@ -2197,13 +2183,13 @@
         "linux"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/oxide-linux-x64-musl": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
-      "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz",
+      "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
       "cpu": [
         "x64"
       ],
@@ -2213,13 +2199,13 @@
         "linux"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/oxide-wasm32-wasi": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
-      "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz",
+      "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
       "bundleDependencies": [
         "@napi-rs/wasm-runtime",
         "@emnapi/core",
@@ -2234,21 +2220,21 @@
       "license": "MIT",
       "optional": true,
       "dependencies": {
-        "@emnapi/core": "^1.7.1",
-        "@emnapi/runtime": "^1.7.1",
+        "@emnapi/core": "^1.8.1",
+        "@emnapi/runtime": "^1.8.1",
         "@emnapi/wasi-threads": "^1.1.0",
-        "@napi-rs/wasm-runtime": "^1.1.0",
+        "@napi-rs/wasm-runtime": "^1.1.1",
         "@tybys/wasm-util": "^0.10.1",
-        "tslib": "^2.4.0"
+        "tslib": "^2.8.1"
       },
       "engines": {
         "node": ">=14.0.0"
       }
     },
     "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
-      "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz",
+      "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
       "cpu": [
         "arm64"
       ],
@@ -2258,13 +2244,13 @@
         "win32"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
-      "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz",
+      "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
       "cpu": [
         "x64"
       ],
@@ -2274,21 +2260,21 @@
         "win32"
       ],
       "engines": {
-        "node": ">= 10"
+        "node": ">= 20"
       }
     },
     "node_modules/@tailwindcss/vite": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz",
-      "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz",
+      "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==",
       "license": "MIT",
       "dependencies": {
-        "@tailwindcss/node": "4.1.18",
-        "@tailwindcss/oxide": "4.1.18",
-        "tailwindcss": "4.1.18"
+        "@tailwindcss/node": "4.2.2",
+        "@tailwindcss/oxide": "4.2.2",
+        "tailwindcss": "4.2.2"
       },
       "peerDependencies": {
-        "vite": "^5.2.0 || ^6 || ^7"
+        "vite": "^5.2.0 || ^6 || ^7 || ^8"
       }
     },
     "node_modules/@tanstack/query-core": {
@@ -2354,51 +2340,6 @@
         "tslib": "^2.4.0"
       }
     },
-    "node_modules/@types/babel__core": {
-      "version": "7.20.5",
-      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
-      "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/parser": "^7.20.7",
-        "@babel/types": "^7.20.7",
-        "@types/babel__generator": "*",
-        "@types/babel__template": "*",
-        "@types/babel__traverse": "*"
-      }
-    },
-    "node_modules/@types/babel__generator": {
-      "version": "7.27.0",
-      "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
-      "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "node_modules/@types/babel__template": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
-      "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/parser": "^7.1.0",
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "node_modules/@types/babel__traverse": {
-      "version": "7.28.0",
-      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
-      "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/types": "^7.28.2"
-      }
-    },
     "node_modules/@types/estree": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -2730,24 +2671,29 @@
       }
     },
     "node_modules/@vitejs/plugin-react": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz",
-      "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==",
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
+      "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/core": "^7.28.5",
-        "@babel/plugin-transform-react-jsx-self": "^7.27.1",
-        "@babel/plugin-transform-react-jsx-source": "^7.27.1",
-        "@rolldown/pluginutils": "1.0.0-beta.53",
-        "@types/babel__core": "^7.20.5",
-        "react-refresh": "^0.18.0"
+        "@rolldown/pluginutils": "1.0.0-rc.7"
       },
       "engines": {
         "node": "^20.19.0 || >=22.12.0"
       },
       "peerDependencies": {
-        "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+        "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+        "babel-plugin-react-compiler": "^1.0.0",
+        "vite": "^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@rolldown/plugin-babel": {
+          "optional": true
+        },
+        "babel-plugin-react-compiler": {
+          "optional": true
+        }
       }
     },
     "node_modules/acorn": {
@@ -3465,13 +3411,13 @@
       "license": "MIT"
     },
     "node_modules/enhanced-resolve": {
-      "version": "5.18.4",
-      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz",
-      "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==",
+      "version": "5.20.1",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
+      "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
       "license": "MIT",
       "dependencies": {
         "graceful-fs": "^4.2.4",
-        "tapable": "^2.2.0"
+        "tapable": "^2.3.0"
       },
       "engines": {
         "node": ">=10.13.0"
@@ -4979,9 +4925,9 @@
       }
     },
     "node_modules/lightningcss": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
-      "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+      "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
       "license": "MPL-2.0",
       "dependencies": {
         "detect-libc": "^2.0.3"
@@ -4994,23 +4940,23 @@
         "url": "https://opencollective.com/parcel"
       },
       "optionalDependencies": {
-        "lightningcss-android-arm64": "1.30.2",
-        "lightningcss-darwin-arm64": "1.30.2",
-        "lightningcss-darwin-x64": "1.30.2",
-        "lightningcss-freebsd-x64": "1.30.2",
-        "lightningcss-linux-arm-gnueabihf": "1.30.2",
-        "lightningcss-linux-arm64-gnu": "1.30.2",
-        "lightningcss-linux-arm64-musl": "1.30.2",
-        "lightningcss-linux-x64-gnu": "1.30.2",
-        "lightningcss-linux-x64-musl": "1.30.2",
-        "lightningcss-win32-arm64-msvc": "1.30.2",
-        "lightningcss-win32-x64-msvc": "1.30.2"
+        "lightningcss-android-arm64": "1.32.0",
+        "lightningcss-darwin-arm64": "1.32.0",
+        "lightningcss-darwin-x64": "1.32.0",
+        "lightningcss-freebsd-x64": "1.32.0",
+        "lightningcss-linux-arm-gnueabihf": "1.32.0",
+        "lightningcss-linux-arm64-gnu": "1.32.0",
+        "lightningcss-linux-arm64-musl": "1.32.0",
+        "lightningcss-linux-x64-gnu": "1.32.0",
+        "lightningcss-linux-x64-musl": "1.32.0",
+        "lightningcss-win32-arm64-msvc": "1.32.0",
+        "lightningcss-win32-x64-msvc": "1.32.0"
       }
     },
     "node_modules/lightningcss-android-arm64": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
-      "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+      "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
       "cpu": [
         "arm64"
       ],
@@ -5028,9 +4974,9 @@
       }
     },
     "node_modules/lightningcss-darwin-arm64": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
-      "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+      "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
       "cpu": [
         "arm64"
       ],
@@ -5048,9 +4994,9 @@
       }
     },
     "node_modules/lightningcss-darwin-x64": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
-      "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+      "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
       "cpu": [
         "x64"
       ],
@@ -5068,9 +5014,9 @@
       }
     },
     "node_modules/lightningcss-freebsd-x64": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
-      "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+      "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
       "cpu": [
         "x64"
       ],
@@ -5088,9 +5034,9 @@
       }
     },
     "node_modules/lightningcss-linux-arm-gnueabihf": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
-      "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+      "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
       "cpu": [
         "arm"
       ],
@@ -5108,9 +5054,9 @@
       }
     },
     "node_modules/lightningcss-linux-arm64-gnu": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
-      "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+      "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
       "cpu": [
         "arm64"
       ],
@@ -5128,9 +5074,9 @@
       }
     },
     "node_modules/lightningcss-linux-arm64-musl": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
-      "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+      "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
       "cpu": [
         "arm64"
       ],
@@ -5148,9 +5094,9 @@
       }
     },
     "node_modules/lightningcss-linux-x64-gnu": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
-      "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+      "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
       "cpu": [
         "x64"
       ],
@@ -5168,9 +5114,9 @@
       }
     },
     "node_modules/lightningcss-linux-x64-musl": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
-      "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+      "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
       "cpu": [
         "x64"
       ],
@@ -5188,9 +5134,9 @@
       }
     },
     "node_modules/lightningcss-win32-arm64-msvc": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
-      "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+      "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
       "cpu": [
         "arm64"
       ],
@@ -5208,9 +5154,9 @@
       }
     },
     "node_modules/lightningcss-win32-x64-msvc": {
-      "version": "1.30.2",
-      "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
-      "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+      "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
       "cpu": [
         "x64"
       ],
@@ -5676,9 +5622,9 @@
       }
     },
     "node_modules/postcss": {
-      "version": "8.5.6",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
-      "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+      "version": "8.5.9",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
+      "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
       "funding": [
         {
           "type": "opencollective",
@@ -5797,16 +5743,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/react-refresh": {
-      "version": "0.18.0",
-      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
-      "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/react-remove-scroll": {
       "version": "2.7.2",
       "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
@@ -5987,13 +5923,13 @@
       }
     },
     "node_modules/rolldown": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.53.tgz",
-      "integrity": "sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz",
+      "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==",
       "license": "MIT",
       "dependencies": {
-        "@oxc-project/types": "=0.101.0",
-        "@rolldown/pluginutils": "1.0.0-beta.53"
+        "@oxc-project/types": "=0.124.0",
+        "@rolldown/pluginutils": "1.0.0-rc.15"
       },
       "bin": {
         "rolldown": "bin/cli.mjs"
@@ -6002,20 +5938,28 @@
         "node": "^20.19.0 || >=22.12.0"
       },
       "optionalDependencies": {
-        "@rolldown/binding-android-arm64": "1.0.0-beta.53",
-        "@rolldown/binding-darwin-arm64": "1.0.0-beta.53",
-        "@rolldown/binding-darwin-x64": "1.0.0-beta.53",
-        "@rolldown/binding-freebsd-x64": "1.0.0-beta.53",
-        "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.53",
-        "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.53",
-        "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.53",
-        "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.53",
-        "@rolldown/binding-linux-x64-musl": "1.0.0-beta.53",
-        "@rolldown/binding-openharmony-arm64": "1.0.0-beta.53",
-        "@rolldown/binding-wasm32-wasi": "1.0.0-beta.53",
-        "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.53",
-        "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.53"
-      }
+        "@rolldown/binding-android-arm64": "1.0.0-rc.15",
+        "@rolldown/binding-darwin-arm64": "1.0.0-rc.15",
+        "@rolldown/binding-darwin-x64": "1.0.0-rc.15",
+        "@rolldown/binding-freebsd-x64": "1.0.0-rc.15",
+        "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15",
+        "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15",
+        "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15",
+        "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15",
+        "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15",
+        "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15",
+        "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15"
+      }
+    },
+    "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz",
+      "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==",
+      "license": "MIT"
     },
     "node_modules/safe-array-concat": {
       "version": "1.1.3",
@@ -6484,9 +6428,9 @@
       }
     },
     "node_modules/tailwindcss": {
-      "version": "4.1.18",
-      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
-      "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
+      "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
       "license": "MIT"
     },
     "node_modules/tapable": {
@@ -6833,18 +6777,15 @@
       }
     },
     "node_modules/vite": {
-      "name": "rolldown-vite",
-      "version": "7.3.1",
-      "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.3.1.tgz",
-      "integrity": "sha512-LYzdNAjRHhF2yA4JUQm/QyARyi216N2rpJ0lJZb8E9FU2y5v6Vk+xq/U4XBOxMefpWixT5H3TslmAHm1rqIq2w==",
+      "version": "8.0.8",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz",
+      "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
       "license": "MIT",
       "dependencies": {
-        "@oxc-project/runtime": "0.101.0",
-        "fdir": "^6.5.0",
-        "lightningcss": "^1.30.2",
-        "picomatch": "^4.0.3",
-        "postcss": "^8.5.6",
-        "rolldown": "1.0.0-beta.53",
+        "lightningcss": "^1.32.0",
+        "picomatch": "^4.0.4",
+        "postcss": "^8.5.8",
+        "rolldown": "1.0.0-rc.15",
         "tinyglobby": "^0.2.15"
       },
       "bin": {
@@ -6861,7 +6802,8 @@
       },
       "peerDependencies": {
         "@types/node": "^20.19.0 || >=22.12.0",
-        "esbuild": "^0.27.0",
+        "@vitejs/devtools": "^0.1.0",
+        "esbuild": "^0.27.0 || ^0.28.0",
         "jiti": ">=1.21.0",
         "less": "^4.0.0",
         "sass": "^1.70.0",
@@ -6876,6 +6818,9 @@
         "@types/node": {
           "optional": true
         },
+        "@vitejs/devtools": {
+          "optional": true
+        },
         "esbuild": {
           "optional": true
         },
diff --git a/frontend/package.json b/frontend/package.json
index d6332c20..65e0d72c 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -23,7 +23,7 @@
     "@radix-ui/react-switch": "^1.2.6",
     "@radix-ui/react-tabs": "^1.1.13",
     "@radix-ui/react-tooltip": "^1.2.8",
-    "@tailwindcss/vite": "^4.1.17",
+    "@tailwindcss/vite": "^4.2.2",
     "@tanstack/react-query": "^5.90.11",
     "@tanstack/react-query-devtools": "^5.91.1",
     "class-variance-authority": "^0.7.1",
@@ -40,14 +40,14 @@
     "react-router-dom": "^7.9.6",
     "sonner": "^2.0.7",
     "tailwind-merge": "^3.4.0",
-    "tailwindcss": "^4.1.17"
+    "tailwindcss": "^4.2.2"
   },
   "devDependencies": {
     "@eslint/js": "^9.39.1",
     "@types/node": "^24.10.1",
     "@types/react": "^19.2.7",
     "@types/react-dom": "^19.2.3",
-    "@vitejs/plugin-react": "^5.1.1",
+    "@vitejs/plugin-react": "^6.0.1",
     "eslint": "^9.39.1",
     "eslint-plugin-better-tailwindcss": "^4.0.1",
     "eslint-plugin-jsx-a11y": "^6.10.2",
@@ -58,7 +58,7 @@
     "tw-animate-css": "^1.4.0",
     "typescript": "~5.9.3",
     "typescript-eslint": "^8.48.0",
-    "vite": "npm:rolldown-vite@7.3.1"
+    "vite": "^8.0.8"
   },
   "overrides": {
     "dompurify": "^3.3.2"
diff --git a/openapi/package-lock.json b/openapi/package-lock.json
index 59aecbcb..148f7e7b 100644
--- a/openapi/package-lock.json
+++ b/openapi/package-lock.json
@@ -4,6 +4,7 @@
   "requires": true,
   "packages": {
     "": {
+      "name": "openapi",
       "devDependencies": {
         "openapi-typescript": "^7.10.1"
       }
@@ -114,9 +115,9 @@
       "license": "MIT"
     },
     "node_modules/brace-expansion": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
-      "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+      "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
diff --git a/swagger-ui/package-lock.json b/swagger-ui/package-lock.json
index 44bae9c4..42520a0f 100644
--- a/swagger-ui/package-lock.json
+++ b/swagger-ui/package-lock.json
@@ -13,7 +13,7 @@
       "devDependencies": {
         "@types/swagger-ui": "^5.21.1",
         "typescript": "^5.9.3",
-        "vite": "npm:rolldown-vite@7.3.1"
+        "vite": "^8.0.8"
       }
     },
     "node_modules/@babel/runtime": {
@@ -38,21 +38,21 @@
       }
     },
     "node_modules/@emnapi/core": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
-      "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
+      "version": "1.9.2",
+      "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
+      "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
       "dev": true,
       "license": "MIT",
       "optional": true,
       "dependencies": {
-        "@emnapi/wasi-threads": "1.1.0",
+        "@emnapi/wasi-threads": "1.2.1",
         "tslib": "^2.4.0"
       }
     },
     "node_modules/@emnapi/runtime": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
-      "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+      "version": "1.9.2",
+      "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
+      "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
       "dev": true,
       "license": "MIT",
       "optional": true,
@@ -61,9 +61,9 @@
       }
     },
     "node_modules/@emnapi/wasi-threads": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
-      "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+      "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
       "dev": true,
       "license": "MIT",
       "optional": true,
@@ -72,36 +72,28 @@
       }
     },
     "node_modules/@napi-rs/wasm-runtime": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
-      "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz",
+      "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==",
       "dev": true,
       "license": "MIT",
       "optional": true,
       "dependencies": {
-        "@emnapi/core": "^1.7.1",
-        "@emnapi/runtime": "^1.7.1",
         "@tybys/wasm-util": "^0.10.1"
       },
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/Brooooooklyn"
-      }
-    },
-    "node_modules/@oxc-project/runtime": {
-      "version": "0.101.0",
-      "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.101.0.tgz",
-      "integrity": "sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": "^20.19.0 || >=22.12.0"
+      },
+      "peerDependencies": {
+        "@emnapi/core": "^1.7.1",
+        "@emnapi/runtime": "^1.7.1"
       }
     },
     "node_modules/@oxc-project/types": {
-      "version": "0.101.0",
-      "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.101.0.tgz",
-      "integrity": "sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==",
+      "version": "0.124.0",
+      "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz",
+      "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==",
       "dev": true,
       "license": "MIT",
       "funding": {
@@ -109,9 +101,9 @@
       }
     },
     "node_modules/@rolldown/binding-android-arm64": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.53.tgz",
-      "integrity": "sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==",
       "cpu": [
         "arm64"
       ],
@@ -126,9 +118,9 @@
       }
     },
     "node_modules/@rolldown/binding-darwin-arm64": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.53.tgz",
-      "integrity": "sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==",
       "cpu": [
         "arm64"
       ],
@@ -143,9 +135,9 @@
       }
     },
     "node_modules/@rolldown/binding-darwin-x64": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.53.tgz",
-      "integrity": "sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==",
       "cpu": [
         "x64"
       ],
@@ -160,9 +152,9 @@
       }
     },
     "node_modules/@rolldown/binding-freebsd-x64": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.53.tgz",
-      "integrity": "sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==",
       "cpu": [
         "x64"
       ],
@@ -177,9 +169,9 @@
       }
     },
     "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.53.tgz",
-      "integrity": "sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz",
+      "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==",
       "cpu": [
         "arm"
       ],
@@ -194,9 +186,9 @@
       }
     },
     "node_modules/@rolldown/binding-linux-arm64-gnu": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.53.tgz",
-      "integrity": "sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==",
       "cpu": [
         "arm64"
       ],
@@ -211,9 +203,9 @@
       }
     },
     "node_modules/@rolldown/binding-linux-arm64-musl": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.53.tgz",
-      "integrity": "sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz",
+      "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==",
       "cpu": [
         "arm64"
       ],
@@ -227,10 +219,44 @@
         "node": "^20.19.0 || >=22.12.0"
       }
     },
+    "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-s390x-gnu": {
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
     "node_modules/@rolldown/binding-linux-x64-gnu": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.53.tgz",
-      "integrity": "sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz",
+      "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==",
       "cpu": [
         "x64"
       ],
@@ -245,9 +271,9 @@
       }
     },
     "node_modules/@rolldown/binding-linux-x64-musl": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.53.tgz",
-      "integrity": "sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz",
+      "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==",
       "cpu": [
         "x64"
       ],
@@ -262,9 +288,9 @@
       }
     },
     "node_modules/@rolldown/binding-openharmony-arm64": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.53.tgz",
-      "integrity": "sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz",
+      "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==",
       "cpu": [
         "arm64"
       ],
@@ -279,9 +305,9 @@
       }
     },
     "node_modules/@rolldown/binding-wasm32-wasi": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.53.tgz",
-      "integrity": "sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz",
+      "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==",
       "cpu": [
         "wasm32"
       ],
@@ -289,16 +315,18 @@
       "license": "MIT",
       "optional": true,
       "dependencies": {
-        "@napi-rs/wasm-runtime": "^1.1.0"
+        "@emnapi/core": "1.9.2",
+        "@emnapi/runtime": "1.9.2",
+        "@napi-rs/wasm-runtime": "^1.1.3"
       },
       "engines": {
         "node": ">=14.0.0"
       }
     },
     "node_modules/@rolldown/binding-win32-arm64-msvc": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.53.tgz",
-      "integrity": "sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz",
+      "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==",
       "cpu": [
         "arm64"
       ],
@@ -313,9 +341,9 @@
       }
     },
     "node_modules/@rolldown/binding-win32-x64-msvc": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.53.tgz",
-      "integrity": "sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz",
+      "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==",
       "cpu": [
         "x64"
       ],
@@ -330,9 +358,9 @@
       }
     },
     "node_modules/@rolldown/pluginutils": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
-      "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz",
+      "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==",
       "dev": true,
       "license": "MIT"
     },
@@ -1867,9 +1895,9 @@
       }
     },
     "node_modules/lightningcss": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
-      "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+      "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
       "dev": true,
       "license": "MPL-2.0",
       "dependencies": {
@@ -1883,23 +1911,23 @@
         "url": "https://opencollective.com/parcel"
       },
       "optionalDependencies": {
-        "lightningcss-android-arm64": "1.31.1",
-        "lightningcss-darwin-arm64": "1.31.1",
-        "lightningcss-darwin-x64": "1.31.1",
-        "lightningcss-freebsd-x64": "1.31.1",
-        "lightningcss-linux-arm-gnueabihf": "1.31.1",
-        "lightningcss-linux-arm64-gnu": "1.31.1",
-        "lightningcss-linux-arm64-musl": "1.31.1",
-        "lightningcss-linux-x64-gnu": "1.31.1",
-        "lightningcss-linux-x64-musl": "1.31.1",
-        "lightningcss-win32-arm64-msvc": "1.31.1",
-        "lightningcss-win32-x64-msvc": "1.31.1"
+        "lightningcss-android-arm64": "1.32.0",
+        "lightningcss-darwin-arm64": "1.32.0",
+        "lightningcss-darwin-x64": "1.32.0",
+        "lightningcss-freebsd-x64": "1.32.0",
+        "lightningcss-linux-arm-gnueabihf": "1.32.0",
+        "lightningcss-linux-arm64-gnu": "1.32.0",
+        "lightningcss-linux-arm64-musl": "1.32.0",
+        "lightningcss-linux-x64-gnu": "1.32.0",
+        "lightningcss-linux-x64-musl": "1.32.0",
+        "lightningcss-win32-arm64-msvc": "1.32.0",
+        "lightningcss-win32-x64-msvc": "1.32.0"
       }
     },
     "node_modules/lightningcss-android-arm64": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz",
-      "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+      "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
       "cpu": [
         "arm64"
       ],
@@ -1918,9 +1946,9 @@
       }
     },
     "node_modules/lightningcss-darwin-arm64": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz",
-      "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+      "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
       "cpu": [
         "arm64"
       ],
@@ -1939,9 +1967,9 @@
       }
     },
     "node_modules/lightningcss-darwin-x64": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz",
-      "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+      "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
       "cpu": [
         "x64"
       ],
@@ -1960,9 +1988,9 @@
       }
     },
     "node_modules/lightningcss-freebsd-x64": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz",
-      "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+      "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
       "cpu": [
         "x64"
       ],
@@ -1981,9 +2009,9 @@
       }
     },
     "node_modules/lightningcss-linux-arm-gnueabihf": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz",
-      "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+      "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
       "cpu": [
         "arm"
       ],
@@ -2002,9 +2030,9 @@
       }
     },
     "node_modules/lightningcss-linux-arm64-gnu": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz",
-      "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+      "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
       "cpu": [
         "arm64"
       ],
@@ -2023,9 +2051,9 @@
       }
     },
     "node_modules/lightningcss-linux-arm64-musl": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz",
-      "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+      "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
       "cpu": [
         "arm64"
       ],
@@ -2044,9 +2072,9 @@
       }
     },
     "node_modules/lightningcss-linux-x64-gnu": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
-      "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+      "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
       "cpu": [
         "x64"
       ],
@@ -2065,9 +2093,9 @@
       }
     },
     "node_modules/lightningcss-linux-x64-musl": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
-      "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+      "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
       "cpu": [
         "x64"
       ],
@@ -2086,9 +2114,9 @@
       }
     },
     "node_modules/lightningcss-win32-arm64-msvc": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz",
-      "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+      "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
       "cpu": [
         "arm64"
       ],
@@ -2107,9 +2135,9 @@
       }
     },
     "node_modules/lightningcss-win32-x64-msvc": {
-      "version": "1.31.1",
-      "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz",
-      "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+      "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
       "cpu": [
         "x64"
       ],
@@ -2403,9 +2431,9 @@
       }
     },
     "node_modules/postcss": {
-      "version": "8.5.6",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
-      "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+      "version": "8.5.9",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
+      "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
       "dev": true,
       "funding": [
         {
@@ -2743,14 +2771,14 @@
       }
     },
     "node_modules/rolldown": {
-      "version": "1.0.0-beta.53",
-      "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.53.tgz",
-      "integrity": "sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw==",
+      "version": "1.0.0-rc.15",
+      "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz",
+      "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@oxc-project/types": "=0.101.0",
-        "@rolldown/pluginutils": "1.0.0-beta.53"
+        "@oxc-project/types": "=0.124.0",
+        "@rolldown/pluginutils": "1.0.0-rc.15"
       },
       "bin": {
         "rolldown": "bin/cli.mjs"
@@ -2759,19 +2787,21 @@
         "node": "^20.19.0 || >=22.12.0"
       },
       "optionalDependencies": {
-        "@rolldown/binding-android-arm64": "1.0.0-beta.53",
-        "@rolldown/binding-darwin-arm64": "1.0.0-beta.53",
-        "@rolldown/binding-darwin-x64": "1.0.0-beta.53",
-        "@rolldown/binding-freebsd-x64": "1.0.0-beta.53",
-        "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.53",
-        "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.53",
-        "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.53",
-        "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.53",
-        "@rolldown/binding-linux-x64-musl": "1.0.0-beta.53",
-        "@rolldown/binding-openharmony-arm64": "1.0.0-beta.53",
-        "@rolldown/binding-wasm32-wasi": "1.0.0-beta.53",
-        "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.53",
-        "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.53"
+        "@rolldown/binding-android-arm64": "1.0.0-rc.15",
+        "@rolldown/binding-darwin-arm64": "1.0.0-rc.15",
+        "@rolldown/binding-darwin-x64": "1.0.0-rc.15",
+        "@rolldown/binding-freebsd-x64": "1.0.0-rc.15",
+        "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15",
+        "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15",
+        "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15",
+        "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15",
+        "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15",
+        "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15",
+        "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15",
+        "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15"
       }
     },
     "node_modules/safe-buffer": {
@@ -3124,19 +3154,16 @@
       }
     },
     "node_modules/vite": {
-      "name": "rolldown-vite",
-      "version": "7.3.1",
-      "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.3.1.tgz",
-      "integrity": "sha512-LYzdNAjRHhF2yA4JUQm/QyARyi216N2rpJ0lJZb8E9FU2y5v6Vk+xq/U4XBOxMefpWixT5H3TslmAHm1rqIq2w==",
+      "version": "8.0.8",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz",
+      "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@oxc-project/runtime": "0.101.0",
-        "fdir": "^6.5.0",
-        "lightningcss": "^1.30.2",
-        "picomatch": "^4.0.3",
-        "postcss": "^8.5.6",
-        "rolldown": "1.0.0-beta.53",
+        "lightningcss": "^1.32.0",
+        "picomatch": "^4.0.4",
+        "postcss": "^8.5.8",
+        "rolldown": "1.0.0-rc.15",
         "tinyglobby": "^0.2.15"
       },
       "bin": {
@@ -3153,7 +3180,8 @@
       },
       "peerDependencies": {
         "@types/node": "^20.19.0 || >=22.12.0",
-        "esbuild": "^0.27.0",
+        "@vitejs/devtools": "^0.1.0",
+        "esbuild": "^0.27.0 || ^0.28.0",
         "jiti": ">=1.21.0",
         "less": "^4.0.0",
         "sass": "^1.70.0",
@@ -3168,6 +3196,9 @@
         "@types/node": {
           "optional": true
         },
+        "@vitejs/devtools": {
+          "optional": true
+        },
         "esbuild": {
           "optional": true
         },
diff --git a/swagger-ui/package.json b/swagger-ui/package.json
index 52305e3a..010c5d64 100644
--- a/swagger-ui/package.json
+++ b/swagger-ui/package.json
@@ -11,7 +11,7 @@
   "devDependencies": {
     "@types/swagger-ui": "^5.21.1",
     "typescript": "^5.9.3",
-    "vite": "npm:rolldown-vite@7.3.1"
+    "vite": "^8.0.8"
   },
   "dependencies": {
     "swagger-ui": "^5.30.3"
