From 3fbc5013c3e09374b4fe8c16d426bcb2b4fb40e2 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Sun, 4 Jan 2026 19:30:37 +0100 Subject: [PATCH] refactor(db): migrate to libsql --- .../extract-build-artifact/action.yaml | 12 +- .github/actions/prebuilt-debian/action.yaml | 42 - .github/renovate.json5 | 1 - .../on-pr-prebuilt-debian-validate.yml | 45 -- Dockerfile | 1 - deployments/prebuilt-debian/Dockerfile | 6 - deployments/prebuilt-debian/package.json | 17 - deployments/prebuilt-debian/pnpm-lock.yaml | 286 ------- e2e/shared/e2e-db.ts | 13 +- package.json | 1 - packages/api/src/router/app.ts | 16 +- packages/api/src/router/board.ts | 743 +++++------------- .../router/certificates/certificate-router.ts | 3 +- packages/api/src/router/group.ts | 21 +- .../router/integration/integration-router.ts | 108 +-- packages/api/src/router/test/app.spec.ts | 22 +- packages/api/src/router/test/board.spec.ts | 78 +- .../router/test/board/board-access.spec.ts | 12 +- packages/api/src/router/test/group.spec.ts | 68 +- .../integration/integration-access.spec.ts | 12 +- .../integration/integration-router.spec.ts | 34 +- packages/api/src/router/test/invite.spec.ts | 12 +- .../src/router/test/serverSettings.spec.ts | 8 +- packages/api/src/router/test/user.spec.ts | 18 +- .../api/src/router/test/widgets/app.spec.ts | 6 +- .../integration-query-permissions.spec.ts | 28 +- .../test/basic-authorization.spec.ts | 10 +- .../providers/test/ldap-authorization.spec.ts | 10 +- packages/auth/test/adapter.spec.ts | 8 +- packages/auth/test/callbacks.spec.ts | 10 +- packages/auth/test/events.spec.ts | 20 +- packages/core/package.json | 3 +- .../src/infrastructure/db/drivers/sqlite.ts | 8 +- packages/cron-jobs/src/jobs/icons-updater.ts | 102 +-- packages/db/collection.ts | 58 +- packages/db/driver.ts | 6 +- packages/db/index.ts | 2 - packages/db/migrations/sqlite/migrate.ts | 4 +- packages/db/package.json | 3 +- packages/db/test/db-mock.ts | 14 +- packages/db/transactions.ts | 23 - packages/integrations/test/aria2.spec.ts | 4 +- packages/integrations/test/base.spec.ts | 4 +- .../integrations/test/home-assistant.spec.ts | 4 +- packages/integrations/test/nzbget.spec.ts | 4 +- packages/integrations/test/pi-hole.spec.ts | 4 +- packages/integrations/test/sabnzbd.spec.ts | 4 +- .../import/collections/board-collection.ts | 11 +- .../collections/integration-collection.ts | 4 +- .../src/import/collections/user-collection.ts | 9 +- .../src/import/import-initial-oldmarr.ts | 20 +- .../src/import/import-single-oldmarr.ts | 11 +- 52 files changed, 523 insertions(+), 1450 deletions(-) delete mode 100644 .github/actions/prebuilt-debian/action.yaml delete mode 100644 .github/workflows/on-pr-prebuilt-debian-validate.yml delete mode 100644 deployments/prebuilt-debian/Dockerfile delete mode 100644 deployments/prebuilt-debian/package.json delete mode 100644 deployments/prebuilt-debian/pnpm-lock.yaml delete mode 100644 packages/db/transactions.ts diff --git a/.github/actions/extract-build-artifact/action.yaml b/.github/actions/extract-build-artifact/action.yaml index 4751c9029..e14fe4dd1 100644 --- a/.github/actions/extract-build-artifact/action.yaml +++ b/.github/actions/extract-build-artifact/action.yaml @@ -19,11 +19,6 @@ inputs: runs: using: "composite" steps: - - name: Prebuilt debian dependencies - uses: homarr-labs/homarr/.github/actions/prebuilt-debian@dev - id: prebuilt-debian - with: - architecture: ${{ inputs.architecture }} - name: Start docker container for ${{ inputs.architecture }} run: | docker run --name homarr \ @@ -41,12 +36,9 @@ runs: docker cp homarr:/app/extraction-alpine.tar.gz ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-alpine-${{ inputs.architecture }}.tar.gz && \ docker exec homarr rm /app/extraction-alpine.tar.gz shell: bash - - name: Extract source from ${{ inputs.architecture }} container (debian) + - name: Copy source for debian build artifact run: | - docker cp ${{ steps.prebuilt-debian.outputs.path }}/. homarr:/app/build && \ - docker cp ${{ steps.prebuilt-debian.outputs.path }}/. homarr:/app/node_modules/better-sqlite3/build/Release && \ - docker exec homarr tar -czf extraction-debian.tar.gz -C /app . && \ - docker cp homarr:/app/extraction-debian.tar.gz ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-debian-${{ inputs.architecture }}.tar.gz + cp ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-alpine-${{ inputs.architecture }}.tar.gz ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-debian-${{ inputs.architecture }}.tar.gz shell: bash - name: Stop ${{ inputs.architecture }} container if: always() diff --git a/.github/actions/prebuilt-debian/action.yaml b/.github/actions/prebuilt-debian/action.yaml deleted file mode 100644 index 9770a7eb1..000000000 --- a/.github/actions/prebuilt-debian/action.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: Prebuilt dependencies for debian -description: Provides prebuilt dependencies for debian based docker images -inputs: - architecture: - description: Name of architecture, will be used to build docker image (e.g. amd64, arm64) - required: true -outputs: - path: - description: Path to extracted prebuilt dependencies - value: ${{ runner.temp }}/prebuilts -runs: - using: "composite" - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build docker image for ${{ inputs.architecture }} - id: build - uses: docker/build-push-action@v6 - with: - push: false - load: true - context: ./deployments/prebuilt-debian - platforms: linux/${{ inputs.architecture }} - tags: prebuilt-debian - - name: Start docker container for ${{ inputs.architecture }} - run: | - docker run --name prebuilt-debian \ - --detach --rm prebuilt-debian - shell: bash - - name: Extract prebuilt dependencies from ${{ inputs.architecture }} container - run: | - mkdir -p ${{ runner.temp }}/prebuilts && \ - docker cp prebuilt-debian:/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node ${{ runner.temp }}/prebuilts/better_sqlite3.node - shell: bash - - name: Stop ${{ inputs.architecture }} container - if: always() - run: docker container remove --force --volumes prebuilt-debian - shell: bash diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e3c3e433f..7456c70f8 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -20,7 +20,6 @@ matchPackageNames: [ "@tree-sitter-grammars/tree-sitter-yaml", "bcrypt", - "better-sqlite3", "cpu-features", "esbuild", "sharp", diff --git a/.github/workflows/on-pr-prebuilt-debian-validate.yml b/.github/workflows/on-pr-prebuilt-debian-validate.yml deleted file mode 100644 index e72acca1e..000000000 --- a/.github/workflows/on-pr-prebuilt-debian-validate.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: "[Deployments] Validate prebuilt debian dependencies" - -permissions: - contents: read - -on: - pull_request: - branches: ["*"] - paths: [".github/actions/prebuilt-debian/**", "deployments/prebuilt-debian/**"] - -jobs: - prebuilt-debian-validate-amd64: - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@v6 - - name: Validate prebuilt dependencies for amd64 - id: validate-amd64 - uses: ./.github/actions/prebuilt-debian - with: - architecture: amd64 - - - name: Check extracted files for amd64 - run: | - if [ ! -f "${{ steps.validate-amd64.outputs.path }}/better_sqlite3.node" ]; then - echo "better_sqlite3.node not found for amd64!" - exit 1 - fi - prebuilt-debian-validate-arm64: - runs-on: ubuntu-24.04- - timeout-minutes: 5 - steps: - - uses: actions/checkout@v6 - - name: Validate prebuilt dependencies for arm64 - id: validate-arm64 - uses: ./.github/actions/prebuilt-debian - with: - architecture: arm64 - - - name: Check extracted files for arm64 - run: | - if [ ! -f "${{ steps.validate-arm64.outputs.path }}/better_sqlite3.node" ]; then - echo "better_sqlite3.node not found for arm64!" - exit 1 - fi diff --git a/Dockerfile b/Dockerfile index 4cf6a8533..e466d04f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,7 +45,6 @@ COPY --from=builder /app/apps/nextjs/package.json . COPY --from=builder /app/apps/tasks/tasks.cjs ./apps/tasks/tasks.cjs COPY --from=builder /app/apps/websocket/wssServer.cjs ./apps/websocket/wssServer.cjs -COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node COPY --from=builder /app/packages/db/migrations ./db/migrations diff --git a/deployments/prebuilt-debian/Dockerfile b/deployments/prebuilt-debian/Dockerfile deleted file mode 100644 index 414af691c..000000000 --- a/deployments/prebuilt-debian/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM node:24.12.0-trixie AS base -WORKDIR /app -COPY package.json . -COPY pnpm-lock.yaml . -RUN corepack enable pnpm && pnpm install --frozen-lockfile -CMD ["sleep", "60s"] \ No newline at end of file diff --git a/deployments/prebuilt-debian/package.json b/deployments/prebuilt-debian/package.json deleted file mode 100644 index 1d5739653..000000000 --- a/deployments/prebuilt-debian/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "homarr-prebuilt-debian", - "private": true, - "dependencies": { - "better-sqlite3": "^12.5.0" - }, - "packageManager": "pnpm@10.26.2", - "engines": { - "node": ">=24.12.0", - "pnpm": ">=10.26.2" - }, - "pnpm": { - "onlyBuiltDependencies": [ - "better-sqlite3" - ] - } -} diff --git a/deployments/prebuilt-debian/pnpm-lock.yaml b/deployments/prebuilt-debian/pnpm-lock.yaml deleted file mode 100644 index 1868baaa2..000000000 --- a/deployments/prebuilt-debian/pnpm-lock.yaml +++ /dev/null @@ -1,286 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - better-sqlite3: - specifier: ^12.5.0 - version: 12.5.0 - -packages: - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - better-sqlite3@12.5.0: - resolution: {integrity: sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==} - engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} - - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - - napi-build-utils@2.0.0: - resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - - node-abi@3.85.0: - resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} - engines: {node: '>=10'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - prebuild-install@7.1.3: - resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} - engines: {node: '>=10'} - hasBin: true - - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - - tar-fs@2.1.4: - resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - -snapshots: - - base64-js@1.5.1: {} - - better-sqlite3@12.5.0: - dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.3 - - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - chownr@1.1.4: {} - - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - - deep-extend@0.6.0: {} - - detect-libc@2.1.2: {} - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - expand-template@2.0.3: {} - - file-uri-to-path@1.0.0: {} - - fs-constants@1.0.0: {} - - github-from-package@0.0.0: {} - - ieee754@1.2.1: {} - - inherits@2.0.4: {} - - ini@1.3.8: {} - - mimic-response@3.1.0: {} - - minimist@1.2.8: {} - - mkdirp-classic@0.5.3: {} - - napi-build-utils@2.0.0: {} - - node-abi@3.85.0: - dependencies: - semver: 7.7.3 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - prebuild-install@7.1.3: - dependencies: - detect-libc: 2.1.2 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 2.0.0 - node-abi: 3.85.0 - pump: 3.0.3 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.4 - tunnel-agent: 0.6.0 - - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - safe-buffer@5.2.1: {} - - semver@7.7.3: {} - - simple-concat@1.0.1: {} - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-json-comments@2.0.1: {} - - tar-fs@2.1.4: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.3 - tar-stream: 2.2.0 - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.5 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - - util-deprecate@1.0.2: {} - - wrappy@1.0.2: {} diff --git a/e2e/shared/e2e-db.ts b/e2e/shared/e2e-db.ts index ea59efe6f..73d9e69a1 100644 --- a/e2e/shared/e2e-db.ts +++ b/e2e/shared/e2e-db.ts @@ -1,9 +1,9 @@ import { mkdir } from "fs/promises"; import path from "path"; +import { createClient } from "@libsql/client/node"; import { createId } from "@paralleldrive/cuid2"; -import Database from "better-sqlite3"; -import { BetterSQLite3Database, drizzle } from "drizzle-orm/better-sqlite3"; -import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { drizzle, LibSQLDatabase } from "drizzle-orm/libsql"; +import { migrate } from "drizzle-orm/libsql/migrator"; import { DB_CASING } from "../../packages/core/src/infrastructure/db/constants"; import * as sqliteSchema from "../../packages/db/schema/sqlite"; @@ -14,7 +14,10 @@ export const createSqliteDbFileAsync = async () => { const localDbUrl = path.join(localMountPath, "db", "db.sqlite"); - const connection = new Database(localDbUrl); + const connection = createClient({ + url: `file:${localDbUrl}`, + }); + const db = drizzle(connection, { schema: sqliteSchema, casing: DB_CASING, @@ -30,4 +33,4 @@ export const createSqliteDbFileAsync = async () => { }; }; -export type SqliteDatabase = BetterSQLite3Database; +export type SqliteDatabase = LibSQLDatabase; diff --git a/package.json b/package.json index 921928ba5..4482e5c3b 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "onlyBuiltDependencies": [ "@tree-sitter-grammars/tree-sitter-yaml", "bcrypt", - "better-sqlite3", "cpu-features", "esbuild", "sharp", diff --git a/packages/api/src/router/app.ts b/packages/api/src/router/app.ts index 32c607951..ad1ab2dcc 100644 --- a/packages/api/src/router/app.ts +++ b/packages/api/src/router/app.ts @@ -141,13 +141,15 @@ export const appRouter = createTRPCRouter({ .output(z.void()) .mutation(async ({ ctx, input }) => { await ctx.db.insert(apps).values( - input.map((app) => ({ - id: createId(), - name: app.name, - description: app.description, - iconUrl: app.iconUrl ?? getIconForName(ctx.db, app.name).sync()?.url ?? defaultIcon, - href: app.href, - })), + await Promise.all( + input.map(async (app) => ({ + id: createId(), + name: app.name, + description: app.description, + iconUrl: app.iconUrl ?? (await getIconForName(ctx.db, app.name))?.url ?? defaultIcon, + href: app.href, + })), + ), ); }), update: permissionRequiredProcedure diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts index 8d118018b..af99f2630 100644 --- a/packages/api/src/router/board.ts +++ b/packages/api/src/router/board.ts @@ -6,8 +6,8 @@ import { constructBoardPermissions } from "@homarr/auth/shared"; import { createId } from "@homarr/common"; import type { DeviceType } from "@homarr/common/server"; import type { Database, InferInsertModel, InferSelectModel, SQL } from "@homarr/db"; -import { and, asc, eq, handleTransactionsAsync, inArray, isNull, like, not, or, sql } from "@homarr/db"; -import { createDbInsertCollectionWithoutTransaction } from "@homarr/db/collection"; +import { and, asc, eq, inArray, isNull, like, not, or, sql } from "@homarr/db"; +import { createDbInsertCollection } from "@homarr/db/collection"; import { getServerSettingByKeyAsync } from "@homarr/db/queries"; import { boardGroupPermissions, @@ -272,7 +272,7 @@ export const boardRouter = createTRPCRouter({ }, }); - const createBoardCollection = createDbInsertCollectionWithoutTransaction(["boards", "sections", "layouts"]); + const createBoardCollection = createDbInsertCollection(["boards", "sections", "layouts"]); createBoardCollection.boards.push({ id: boardId, @@ -438,86 +438,41 @@ export const boardRouter = createTRPCRouter({ })), ); - await handleTransactionsAsync(ctx.db, { - async handleAsync(db, schema) { - await db.transaction(async (transaction) => { - transaction.insert(schema.boards).values({ - ...boardProps, - id: newBoardId, - name: input.name, - creatorId: ctx.session.user.id, - }); + await ctx.db.transaction(async (transaction) => { + transaction.insert(boards).values({ + ...boardProps, + id: newBoardId, + name: input.name, + creatorId: ctx.session.user.id, + }); - if (layoutsToInsert.length > 0) { - await transaction.insert(schema.layouts).values(layoutsToInsert); - } + if (layoutsToInsert.length > 0) { + await transaction.insert(layouts).values(layoutsToInsert); + } - if (sectionsToInsert.length > 0) { - await transaction.insert(schema.sections).values(sectionsToInsert); - } + if (sectionsToInsert.length > 0) { + await transaction.insert(sections).values(sectionsToInsert); + } - if (sectionLayoutsToInsert.length > 0) { - await transaction.insert(schema.sectionLayouts).values(sectionLayoutsToInsert); - } + if (sectionLayoutsToInsert.length > 0) { + await transaction.insert(sectionLayouts).values(sectionLayoutsToInsert); + } - if (sectionCollapseStatesToInsert.length > 0) { - await transaction.insert(schema.sectionCollapseStates).values(sectionCollapseStatesToInsert); - } + if (sectionCollapseStatesToInsert.length > 0) { + await transaction.insert(sectionCollapseStates).values(sectionCollapseStatesToInsert); + } - if (itemsToInsert.length > 0) { - await transaction.insert(schema.items).values(itemsToInsert); - } + if (itemsToInsert.length > 0) { + await transaction.insert(items).values(itemsToInsert); + } - if (itemLayoutsToInsert.length > 0) { - await transaction.insert(schema.itemLayouts).values(itemLayoutsToInsert); - } + if (itemLayoutsToInsert.length > 0) { + await transaction.insert(itemLayouts).values(itemLayoutsToInsert); + } - if (itemIntegrationsToInsert.length > 0) { - await transaction.insert(schema.integrationItems).values(itemIntegrationsToInsert); - } - }); - }, - handleSync(db) { - db.transaction((transaction) => { - transaction - .insert(boards) - .values({ - ...boardProps, - id: newBoardId, - name: input.name, - creatorId: ctx.session.user.id, - }) - .run(); - - if (layoutsToInsert.length > 0) { - transaction.insert(layouts).values(layoutsToInsert).run(); - } - - if (sectionsToInsert.length > 0) { - transaction.insert(sections).values(sectionsToInsert).run(); - } - - if (sectionLayoutsToInsert.length > 0) { - transaction.insert(sectionLayouts).values(sectionLayoutsToInsert).run(); - } - - if (sectionCollapseStatesToInsert.length > 0) { - transaction.insert(sectionCollapseStates).values(sectionCollapseStatesToInsert).run(); - } - - if (itemsToInsert.length > 0) { - transaction.insert(items).values(itemsToInsert).run(); - } - - if (itemLayoutsToInsert.length > 0) { - transaction.insert(itemLayouts).values(itemLayoutsToInsert).run(); - } - - if (itemIntegrationsToInsert.length > 0) { - transaction.insert(integrationItems).values(itemIntegrationsToInsert).run(); - } - }); - }, + if (itemIntegrationsToInsert.length > 0) { + await transaction.insert(integrationItems).values(itemIntegrationsToInsert); + } }); }), renameBoard: protectedProcedure.input(boardRenameSchema).mutation(async ({ ctx, input }) => { @@ -755,415 +710,189 @@ export const boardRouter = createTRPCRouter({ const dbBoard = await getFullBoardWithWhereAsync(ctx.db, eq(boards.id, input.id), ctx.session.user.id); - await handleTransactionsAsync(ctx.db, { - async handleAsync(db, schema) { - await db.transaction(async (transaction) => { - const addedSections = filterAddedItems(input.sections, dbBoard.sections); + await ctx.db.transaction(async (transaction) => { + const addedSections = filterAddedItems(input.sections, dbBoard.sections); - if (addedSections.length > 0) { - await transaction.insert(schema.sections).values( - addedSections.map((section) => ({ - id: section.id, - kind: section.kind, - yOffset: section.kind !== "dynamic" ? section.yOffset : null, - xOffset: section.kind === "dynamic" ? null : 0, - options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON, - name: "name" in section ? section.name : null, - boardId: dbBoard.id, - })), - ); + if (addedSections.length > 0) { + await transaction.insert(sections).values( + addedSections.map((section) => ({ + id: section.id, + kind: section.kind, + yOffset: section.kind !== "dynamic" ? section.yOffset : null, + xOffset: section.kind === "dynamic" ? null : 0, + options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON, + name: "name" in section ? section.name : null, + boardId: dbBoard.id, + })), + ); - if (addedSections.some((section) => section.kind === "dynamic")) { - await transaction.insert(schema.sectionLayouts).values( - addedSections - .filter((section) => section.kind === "dynamic") - .flatMap((section) => - section.layouts.map( - (sectionLayout): InferInsertModel => ({ - layoutId: sectionLayout.layoutId, - sectionId: section.id, - parentSectionId: sectionLayout.parentSectionId, - height: sectionLayout.height, - width: sectionLayout.width, - xOffset: sectionLayout.xOffset, - yOffset: sectionLayout.yOffset, - }), - ), - ), - ); - } - } - - const addedItems = filterAddedItems(input.items, dbBoard.items); - - if (addedItems.length > 0) { - await transaction.insert(schema.items).values( - addedItems.map((item) => ({ - id: item.id, - kind: item.kind, - options: superjson.stringify(item.options), - advancedOptions: superjson.stringify(item.advancedOptions), - boardId: dbBoard.id, - })), - ); - await transaction.insert(schema.itemLayouts).values( - addedItems.flatMap((item) => - item.layouts.map( - (layoutSection): InferInsertModel => ({ - layoutId: layoutSection.layoutId, - sectionId: layoutSection.sectionId, - itemId: item.id, - height: layoutSection.height, - width: layoutSection.width, - xOffset: layoutSection.xOffset, - yOffset: layoutSection.yOffset, + if (addedSections.some((section) => section.kind === "dynamic")) { + await transaction.insert(sectionLayouts).values( + addedSections + .filter((section) => section.kind === "dynamic") + .flatMap((section) => + section.layouts.map( + (sectionLayout): InferInsertModel => ({ + layoutId: sectionLayout.layoutId, + sectionId: section.id, + parentSectionId: sectionLayout.parentSectionId, + height: sectionLayout.height, + width: sectionLayout.width, + xOffset: sectionLayout.xOffset, + yOffset: sectionLayout.yOffset, }), ), ), - ); - } - - const inputIntegrationRelations = input.items.flatMap(({ integrationIds, id: itemId }) => - integrationIds.map((integrationId) => ({ - integrationId, - itemId, - })), - ); - const dbIntegrationRelations = dbBoard.items.flatMap(({ integrationIds, id: itemId }) => - integrationIds.map((integrationId) => ({ - integrationId, - itemId, - })), - ); - const addedIntegrationRelations = inputIntegrationRelations.filter( - (inputRelation) => - !dbIntegrationRelations.some( - (dbRelation) => - dbRelation.itemId === inputRelation.itemId && - dbRelation.integrationId === inputRelation.integrationId, - ), ); + } + } - if (addedIntegrationRelations.length > 0) { - await transaction.insert(schema.integrationItems).values( - addedIntegrationRelations.map((relation) => ({ - itemId: relation.itemId, - integrationId: relation.integrationId, - })), - ); - } + const addedItems = filterAddedItems(input.items, dbBoard.items); - const updatedItems = filterUpdatedItems(input.items, dbBoard.items); + if (addedItems.length > 0) { + await transaction.insert(items).values( + addedItems.map((item) => ({ + id: item.id, + kind: item.kind, + options: superjson.stringify(item.options), + advancedOptions: superjson.stringify(item.advancedOptions), + boardId: dbBoard.id, + })), + ); + await transaction.insert(itemLayouts).values( + addedItems.flatMap((item) => + item.layouts.map( + (layoutSection): InferInsertModel => ({ + layoutId: layoutSection.layoutId, + sectionId: layoutSection.sectionId, + itemId: item.id, + height: layoutSection.height, + width: layoutSection.width, + xOffset: layoutSection.xOffset, + yOffset: layoutSection.yOffset, + }), + ), + ), + ); + } - for (const item of updatedItems) { - await transaction - .update(schema.items) - .set({ - kind: item.kind, - options: superjson.stringify(item.options), - advancedOptions: superjson.stringify(item.advancedOptions), - }) - .where(eq(schema.items.id, item.id)); - - for (const itemSectionLayout of item.layouts) { - await transaction - .update(schema.itemLayouts) - .set({ - height: itemSectionLayout.height, - width: itemSectionLayout.width, - xOffset: itemSectionLayout.xOffset, - yOffset: itemSectionLayout.yOffset, - sectionId: itemSectionLayout.sectionId, - }) - .where( - and( - eq(schema.itemLayouts.itemId, item.id), - eq(schema.itemLayouts.layoutId, itemSectionLayout.layoutId), - ), - ); - } - } - - const updatedSections = filterUpdatedItems(input.sections, dbBoard.sections); - - for (const section of updatedSections) { - const prev = dbBoard.sections.find((dbSection) => dbSection.id === section.id); - await transaction - .update(schema.sections) - .set({ - yOffset: prev?.kind !== "dynamic" && "yOffset" in section ? section.yOffset : null, - xOffset: prev?.kind !== "dynamic" && "yOffset" in section ? 0 : null, - options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON, - name: prev?.kind === "category" && "name" in section ? section.name : null, - }) - .where(eq(schema.sections.id, section.id)); - - if (section.kind !== "dynamic") continue; - - for (const sectionLayout of section.layouts) { - await transaction - .update(schema.sectionLayouts) - .set({ - height: sectionLayout.height, - width: sectionLayout.width, - xOffset: sectionLayout.xOffset, - yOffset: sectionLayout.yOffset, - parentSectionId: sectionLayout.parentSectionId, - }) - .where( - and( - eq(schema.sectionLayouts.sectionId, section.id), - eq(schema.sectionLayouts.layoutId, sectionLayout.layoutId), - ), - ); - } - } - - const removedIntegrationRelations = dbIntegrationRelations.filter( + const inputIntegrationRelations = input.items.flatMap(({ integrationIds, id: itemId }) => + integrationIds.map((integrationId) => ({ + integrationId, + itemId, + })), + ); + const dbIntegrationRelations = dbBoard.items.flatMap(({ integrationIds, id: itemId }) => + integrationIds.map((integrationId) => ({ + integrationId, + itemId, + })), + ); + const addedIntegrationRelations = inputIntegrationRelations.filter( + (inputRelation) => + !dbIntegrationRelations.some( (dbRelation) => - !inputIntegrationRelations.some( - (inputRelation) => - dbRelation.itemId === inputRelation.itemId && - dbRelation.integrationId === inputRelation.integrationId, - ), - ); + dbRelation.itemId === inputRelation.itemId && dbRelation.integrationId === inputRelation.integrationId, + ), + ); - for (const relation of removedIntegrationRelations) { - await transaction - .delete(schema.integrationItems) - .where( - and( - eq(integrationItems.itemId, relation.itemId), - eq(integrationItems.integrationId, relation.integrationId), - ), - ); - } + if (addedIntegrationRelations.length > 0) { + await transaction.insert(integrationItems).values( + addedIntegrationRelations.map((relation) => ({ + itemId: relation.itemId, + integrationId: relation.integrationId, + })), + ); + } - const removedItems = filterRemovedItems(input.items, dbBoard.items); + const updatedItems = filterUpdatedItems(input.items, dbBoard.items); - const itemIds = removedItems.map((item) => item.id); - if (itemIds.length > 0) { - await transaction.delete(schema.items).where(inArray(schema.items.id, itemIds)); - } + for (const item of updatedItems) { + await transaction + .update(items) + .set({ + kind: item.kind, + options: superjson.stringify(item.options), + advancedOptions: superjson.stringify(item.advancedOptions), + }) + .where(eq(items.id, item.id)); - const removedSections = filterRemovedItems(input.sections, dbBoard.sections); - const sectionIds = removedSections.map((section) => section.id); + for (const itemSectionLayout of item.layouts) { + await transaction + .update(itemLayouts) + .set({ + height: itemSectionLayout.height, + width: itemSectionLayout.width, + xOffset: itemSectionLayout.xOffset, + yOffset: itemSectionLayout.yOffset, + sectionId: itemSectionLayout.sectionId, + }) + .where(and(eq(itemLayouts.itemId, item.id), eq(itemLayouts.layoutId, itemSectionLayout.layoutId))); + } + } - if (sectionIds.length > 0) { - await transaction.delete(schema.sections).where(inArray(schema.sections.id, sectionIds)); - } - }); - }, - handleSync(db) { - db.transaction((transaction) => { - const addedSections = filterAddedItems(input.sections, dbBoard.sections); + const updatedSections = filterUpdatedItems(input.sections, dbBoard.sections); - if (addedSections.length > 0) { - transaction - .insert(sections) - .values( - addedSections.map((section) => ({ - id: section.id, - kind: section.kind, - yOffset: section.kind !== "dynamic" ? section.yOffset : null, - xOffset: section.kind === "dynamic" ? null : 0, - options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON, - name: "name" in section ? section.name : null, - boardId: dbBoard.id, - })), - ) - .run(); + for (const section of updatedSections) { + const prev = dbBoard.sections.find((dbSection) => dbSection.id === section.id); + await transaction + .update(sections) + .set({ + yOffset: prev?.kind !== "dynamic" && "yOffset" in section ? section.yOffset : null, + xOffset: prev?.kind !== "dynamic" && "yOffset" in section ? 0 : null, + options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON, + name: prev?.kind === "category" && "name" in section ? section.name : null, + }) + .where(eq(sections.id, section.id)); - if (addedSections.some((section) => section.kind === "dynamic")) { - transaction - .insert(sectionLayouts) - .values( - addedSections - .filter((section) => section.kind === "dynamic") - .flatMap((section) => - section.layouts.map( - (sectionLayout): InferInsertModel => ({ - layoutId: sectionLayout.layoutId, - sectionId: section.id, - parentSectionId: sectionLayout.parentSectionId, - height: sectionLayout.height, - width: sectionLayout.width, - xOffset: sectionLayout.xOffset, - yOffset: sectionLayout.yOffset, - }), - ), - ), - ) - .run(); - } - } + if (section.kind !== "dynamic") continue; - const addedItems = filterAddedItems(input.items, dbBoard.items); + for (const sectionLayout of section.layouts) { + await transaction + .update(sectionLayouts) + .set({ + height: sectionLayout.height, + width: sectionLayout.width, + xOffset: sectionLayout.xOffset, + yOffset: sectionLayout.yOffset, + parentSectionId: sectionLayout.parentSectionId, + }) + .where(and(eq(sectionLayouts.sectionId, section.id), eq(sectionLayouts.layoutId, sectionLayout.layoutId))); + } + } - if (addedItems.length > 0) { - transaction - .insert(items) - .values( - addedItems.map((item) => ({ - id: item.id, - kind: item.kind, - options: superjson.stringify(item.options), - advancedOptions: superjson.stringify(item.advancedOptions), - boardId: dbBoard.id, - })), - ) - .run(); - transaction - .insert(itemLayouts) - .values( - addedItems.flatMap((item) => - item.layouts.map( - (layoutSection): InferInsertModel => ({ - layoutId: layoutSection.layoutId, - sectionId: layoutSection.sectionId, - itemId: item.id, - height: layoutSection.height, - width: layoutSection.width, - xOffset: layoutSection.xOffset, - yOffset: layoutSection.yOffset, - }), - ), - ), - ) - .run(); - } - - const inputIntegrationRelations = input.items.flatMap(({ integrationIds, id: itemId }) => - integrationIds.map((integrationId) => ({ - integrationId, - itemId, - })), - ); - const dbIntegrationRelations = dbBoard.items.flatMap(({ integrationIds, id: itemId }) => - integrationIds.map((integrationId) => ({ - integrationId, - itemId, - })), - ); - const addedIntegrationRelations = inputIntegrationRelations.filter( + const removedIntegrationRelations = dbIntegrationRelations.filter( + (dbRelation) => + !inputIntegrationRelations.some( (inputRelation) => - !dbIntegrationRelations.some( - (dbRelation) => - dbRelation.itemId === inputRelation.itemId && - dbRelation.integrationId === inputRelation.integrationId, - ), + dbRelation.itemId === inputRelation.itemId && dbRelation.integrationId === inputRelation.integrationId, + ), + ); + + for (const relation of removedIntegrationRelations) { + await transaction + .delete(integrationItems) + .where( + and( + eq(integrationItems.itemId, relation.itemId), + eq(integrationItems.integrationId, relation.integrationId), + ), ); + } - if (addedIntegrationRelations.length > 0) { - transaction - .insert(integrationItems) - .values( - addedIntegrationRelations.map((relation) => ({ - itemId: relation.itemId, - integrationId: relation.integrationId, - })), - ) - .run(); - } + const removedItems = filterRemovedItems(input.items, dbBoard.items); - const updatedItems = filterUpdatedItems(input.items, dbBoard.items); + const itemIds = removedItems.map((item) => item.id); + if (itemIds.length > 0) { + await transaction.delete(items).where(inArray(items.id, itemIds)); + } - for (const item of updatedItems) { - transaction - .update(items) - .set({ - kind: item.kind, - options: superjson.stringify(item.options), - advancedOptions: superjson.stringify(item.advancedOptions), - }) - .where(eq(items.id, item.id)) - .run(); + const removedSections = filterRemovedItems(input.sections, dbBoard.sections); + const sectionIds = removedSections.map((section) => section.id); - for (const itemSectionLayout of item.layouts) { - transaction - .update(itemLayouts) - .set({ - height: itemSectionLayout.height, - width: itemSectionLayout.width, - xOffset: itemSectionLayout.xOffset, - yOffset: itemSectionLayout.yOffset, - sectionId: itemSectionLayout.sectionId, - }) - .where(and(eq(itemLayouts.itemId, item.id), eq(itemLayouts.layoutId, itemSectionLayout.layoutId))) - .run(); - } - } - - const updatedSections = filterUpdatedItems(input.sections, dbBoard.sections); - - for (const section of updatedSections) { - const prev = dbBoard.sections.find((dbSection) => dbSection.id === section.id); - transaction - .update(sections) - .set({ - yOffset: prev?.kind !== "dynamic" && "yOffset" in section ? section.yOffset : null, - xOffset: prev?.kind !== "dynamic" && "yOffset" in section ? 0 : null, - options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON, - name: prev?.kind === "category" && "name" in section ? section.name : null, - }) - .where(eq(sections.id, section.id)) - .run(); - - if (section.kind !== "dynamic") continue; - - for (const sectionLayout of section.layouts) { - transaction - .update(sectionLayouts) - .set({ - height: sectionLayout.height, - width: sectionLayout.width, - xOffset: sectionLayout.xOffset, - yOffset: sectionLayout.yOffset, - parentSectionId: sectionLayout.parentSectionId, - }) - .where( - and(eq(sectionLayouts.sectionId, section.id), eq(sectionLayouts.layoutId, sectionLayout.layoutId)), - ) - .run(); - } - } - - const removedIntegrationRelations = dbIntegrationRelations.filter( - (dbRelation) => - !inputIntegrationRelations.some( - (inputRelation) => - dbRelation.itemId === inputRelation.itemId && - dbRelation.integrationId === inputRelation.integrationId, - ), - ); - - for (const relation of removedIntegrationRelations) { - transaction - .delete(integrationItems) - .where( - and( - eq(integrationItems.itemId, relation.itemId), - eq(integrationItems.integrationId, relation.integrationId), - ), - ) - .run(); - } - - const removedItems = filterRemovedItems(input.items, dbBoard.items); - - const itemIds = removedItems.map((item) => item.id); - if (itemIds.length > 0) { - transaction.delete(items).where(inArray(items.id, itemIds)).run(); - } - - const removedSections = filterRemovedItems(input.sections, dbBoard.sections); - const sectionIds = removedSections.map((section) => section.id); - - if (sectionIds.length > 0) { - transaction.delete(sections).where(inArray(sections.id, sectionIds)).run(); - } - }); - }, + if (sectionIds.length > 0) { + await transaction.delete(sections).where(inArray(sections.id, sectionIds)); + } }); }), getBoardPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => { @@ -1240,81 +969,35 @@ export const boardRouter = createTRPCRouter({ saveUserBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => { await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full"); - await handleTransactionsAsync(ctx.db, { - async handleAsync(db, schema) { - await db.transaction(async (transaction) => { - await transaction.delete(schema.boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId)); - if (input.permissions.length === 0) { - return; - } - await transaction.insert(schema.boardUserPermissions).values( - input.permissions.map((permission) => ({ - userId: permission.principalId, - permission: permission.permission, - boardId: input.entityId, - })), - ); - }); - }, - handleSync(db) { - db.transaction((transaction) => { - transaction.delete(boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId)).run(); - if (input.permissions.length === 0) { - return; - } - transaction - .insert(boardUserPermissions) - .values( - input.permissions.map((permission) => ({ - userId: permission.principalId, - permission: permission.permission, - boardId: input.entityId, - })), - ) - .run(); - }); - }, + await ctx.db.transaction(async (transaction) => { + await transaction.delete(boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId)); + if (input.permissions.length === 0) { + return; + } + await transaction.insert(boardUserPermissions).values( + input.permissions.map((permission) => ({ + userId: permission.principalId, + permission: permission.permission, + boardId: input.entityId, + })), + ); }); }), saveGroupBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => { await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full"); - await handleTransactionsAsync(ctx.db, { - async handleAsync(db, schema) { - await db.transaction(async (transaction) => { - await transaction - .delete(schema.boardGroupPermissions) - .where(eq(boardGroupPermissions.boardId, input.entityId)); - if (input.permissions.length === 0) { - return; - } - await transaction.insert(schema.boardGroupPermissions).values( - input.permissions.map((permission) => ({ - groupId: permission.principalId, - permission: permission.permission, - boardId: input.entityId, - })), - ); - }); - }, - handleSync(db) { - db.transaction((transaction) => { - transaction.delete(boardGroupPermissions).where(eq(boardGroupPermissions.boardId, input.entityId)).run(); - if (input.permissions.length === 0) { - return; - } - transaction - .insert(boardGroupPermissions) - .values( - input.permissions.map((permission) => ({ - groupId: permission.principalId, - permission: permission.permission, - boardId: input.entityId, - })), - ) - .run(); - }); - }, + await ctx.db.transaction(async (transaction) => { + await transaction.delete(boardGroupPermissions).where(eq(boardGroupPermissions.boardId, input.entityId)); + if (input.permissions.length === 0) { + return; + } + await transaction.insert(boardGroupPermissions).values( + input.permissions.map((permission) => ({ + groupId: permission.principalId, + permission: permission.permission, + boardId: input.entityId, + })), + ); }); }), importOldmarrConfig: permissionRequiredProcedure diff --git a/packages/api/src/router/certificates/certificate-router.ts b/packages/api/src/router/certificates/certificate-router.ts index cab6633f0..616650899 100644 --- a/packages/api/src/router/certificates/certificate-router.ts +++ b/packages/api/src/router/certificates/certificate-router.ts @@ -91,7 +91,7 @@ export const certificateRouter = createTRPCRouter({ hostname: input.hostname, thumbprint: input.thumbprint, }); - const dbResult = await ctx.db + await ctx.db .delete(trustedCertificateHostnames) .where( and( @@ -103,7 +103,6 @@ export const certificateRouter = createTRPCRouter({ logger.info("Removed trusted hostname", { hostname: input.hostname, thumbprint: input.thumbprint, - count: dbResult.changes, }); }), removeCertificate: permissionRequiredProcedure diff --git a/packages/api/src/router/group.ts b/packages/api/src/router/group.ts index 13fb4fdd3..88efb9f9e 100644 --- a/packages/api/src/router/group.ts +++ b/packages/api/src/router/group.ts @@ -3,7 +3,7 @@ import { z } from "zod/v4"; import { createId } from "@homarr/common"; import type { Database } from "@homarr/db"; -import { and, eq, handleTransactionsAsync, like, not } from "@homarr/db"; +import { and, eq, like, not } from "@homarr/db"; import { getMaxGroupPositionAsync } from "@homarr/db/queries"; import { groupMembers, groupPermissions, groups } from "@homarr/db/schema"; import { everyoneGroup } from "@homarr/definitions"; @@ -252,21 +252,10 @@ export const groupRouter = createTRPCRouter({ .mutation(async ({ input, ctx }) => { const positions = input.positions.map((id, index) => ({ id, position: index + 1 })); - await handleTransactionsAsync(ctx.db, { - handleAsync: async (db, schema) => { - await db.transaction(async (trx) => { - for (const { id, position } of positions) { - await trx.update(schema.groups).set({ position }).where(eq(groups.id, id)); - } - }); - }, - handleSync: (db) => { - db.transaction((trx) => { - for (const { id, position } of positions) { - trx.update(groups).set({ position }).where(eq(groups.id, id)).run(); - } - }); - }, + await ctx.db.transaction(async (trx) => { + for (const { id, position } of positions) { + await trx.update(groups).set({ position }).where(eq(groups.id, id)); + } }); }), savePermissions: permissionRequiredProcedure diff --git a/packages/api/src/router/integration/integration-router.ts b/packages/api/src/router/integration/integration-router.ts index 4a12e062e..071c64c0f 100644 --- a/packages/api/src/router/integration/integration-router.ts +++ b/packages/api/src/router/integration/integration-router.ts @@ -5,7 +5,7 @@ import { createId, objectEntries } from "@homarr/common"; import { decryptSecret, encryptSecret } from "@homarr/common/server"; import { createLogger } from "@homarr/core/infrastructure/logs"; import type { Database } from "@homarr/db"; -import { and, asc, eq, handleTransactionsAsync, inArray, like, or } from "@homarr/db"; +import { and, asc, eq, inArray, like, or } from "@homarr/db"; import { apps, groupMembers, @@ -523,45 +523,20 @@ export const integrationRouter = createTRPCRouter({ .mutation(async ({ input, ctx }) => { await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.entityId), "full"); - await handleTransactionsAsync(ctx.db, { - async handleAsync(db, schema) { - await ctx.db.transaction(async (transaction) => { - await transaction - .delete(schema.integrationUserPermissions) - .where(eq(schema.integrationUserPermissions.integrationId, input.entityId)); - if (input.permissions.length === 0) { - return; - } - await transaction.insert(schema.integrationUserPermissions).values( - input.permissions.map((permission) => ({ - userId: permission.principalId, - permission: permission.permission, - integrationId: input.entityId, - })), - ); - }); - }, - handleSync(db) { - db.transaction((transaction) => { - transaction - .delete(integrationUserPermissions) - .where(eq(integrationUserPermissions.integrationId, input.entityId)) - .run(); - if (input.permissions.length === 0) { - return; - } - transaction - .insert(integrationUserPermissions) - .values( - input.permissions.map((permission) => ({ - userId: permission.principalId, - permission: permission.permission, - integrationId: input.entityId, - })), - ) - .run(); - }); - }, + await ctx.db.transaction(async (transaction) => { + await transaction + .delete(integrationUserPermissions) + .where(eq(integrationUserPermissions.integrationId, input.entityId)); + if (input.permissions.length === 0) { + return; + } + await transaction.insert(integrationUserPermissions).values( + input.permissions.map((permission) => ({ + userId: permission.principalId, + permission: permission.permission, + integrationId: input.entityId, + })), + ); }); }), saveGroupIntegrationPermissions: protectedProcedure @@ -569,45 +544,20 @@ export const integrationRouter = createTRPCRouter({ .mutation(async ({ input, ctx }) => { await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.entityId), "full"); - await handleTransactionsAsync(ctx.db, { - async handleAsync(db, schema) { - await db.transaction(async (transaction) => { - await transaction - .delete(schema.integrationGroupPermissions) - .where(eq(schema.integrationGroupPermissions.integrationId, input.entityId)); - if (input.permissions.length === 0) { - return; - } - await transaction.insert(schema.integrationGroupPermissions).values( - input.permissions.map((permission) => ({ - groupId: permission.principalId, - permission: permission.permission, - integrationId: input.entityId, - })), - ); - }); - }, - handleSync(db) { - db.transaction((transaction) => { - transaction - .delete(integrationGroupPermissions) - .where(eq(integrationGroupPermissions.integrationId, input.entityId)) - .run(); - if (input.permissions.length === 0) { - return; - } - transaction - .insert(integrationGroupPermissions) - .values( - input.permissions.map((permission) => ({ - groupId: permission.principalId, - permission: permission.permission, - integrationId: input.entityId, - })), - ) - .run(); - }); - }, + await ctx.db.transaction(async (transaction) => { + await transaction + .delete(integrationGroupPermissions) + .where(eq(integrationGroupPermissions.integrationId, input.entityId)); + if (input.permissions.length === 0) { + return; + } + await transaction.insert(integrationGroupPermissions).values( + input.permissions.map((permission) => ({ + groupId: permission.principalId, + permission: permission.permission, + integrationId: input.entityId, + })), + ); }); }), searchInIntegration: protectedProcedure diff --git a/packages/api/src/router/test/app.spec.ts b/packages/api/src/router/test/app.spec.ts index f8877a403..e6d2a390b 100644 --- a/packages/api/src/router/test/app.spec.ts +++ b/packages/api/src/router/test/app.spec.ts @@ -4,7 +4,7 @@ import { describe, expect, test, vi } from "vitest"; import type { Session } from "@homarr/auth"; import { createId } from "@homarr/common"; import { apps } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import type { GroupPermissionKey } from "@homarr/definitions"; import { appRouter } from "../app"; @@ -21,7 +21,7 @@ const createDefaultSession = (permissions: GroupPermissionKey[] = []): Session = describe("all should return all apps", () => { test("should return all apps with session", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, @@ -55,7 +55,7 @@ describe("all should return all apps", () => { test("should throw UNAUTHORIZED if the user is not authenticated", async () => { // Arrange const caller = appRouter.createCaller({ - db: createDb(), + db: await createDbAsync(), deviceType: undefined, session: null, }); @@ -71,7 +71,7 @@ describe("all should return all apps", () => { describe("byId should return an app by id", () => { test("should return an app by id when canUserSeeAppAsync returns true", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, @@ -103,7 +103,7 @@ describe("byId should return an app by id", () => { test("should throw NOT_FOUND error when canUserSeeAppAsync returns false", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, @@ -129,7 +129,7 @@ describe("byId should return an app by id", () => { test("should throw an error if the app does not exist", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, @@ -147,7 +147,7 @@ describe("byId should return an app by id", () => { describe("create should create a new app with all arguments", () => { test("should create a new app", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, @@ -176,7 +176,7 @@ describe("create should create a new app with all arguments", () => { test("should create a new app only with required arguments", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, @@ -207,7 +207,7 @@ describe("create should create a new app with all arguments", () => { describe("update should update an app", () => { test("should update an app", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, @@ -247,7 +247,7 @@ describe("update should update an app", () => { test("should throw an error if the app does not exist", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, @@ -273,7 +273,7 @@ describe("update should update an app", () => { describe("delete should delete an app", () => { test("should delete an app", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, diff --git a/packages/api/src/router/test/board.spec.ts b/packages/api/src/router/test/board.spec.ts index 8bd21e556..e6aec3ee7 100644 --- a/packages/api/src/router/test/board.spec.ts +++ b/packages/api/src/router/test/board.spec.ts @@ -22,7 +22,7 @@ import { serverSettings, users, } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import type { BoardPermission, GroupPermissionKey } from "@homarr/definitions"; import type { RouterOutputs } from "../.."; @@ -55,7 +55,7 @@ const createRandomUserAsync = async (db: Database) => { describe("getAllBoards should return all boards accessable to the current user", () => { test("without session it should return only public boards", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: null }); const user1 = await createRandomUserAsync(db); @@ -86,7 +86,7 @@ describe("getAllBoards should return all boards accessable to the current user", test("with session containing board-view-all permission it should return all boards", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, @@ -128,7 +128,7 @@ describe("getAllBoards should return all boards accessable to the current user", test("with session user beeing creator it should return all private boards of them", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const user1 = await createRandomUserAsync(db); @@ -170,7 +170,7 @@ describe("getAllBoards should return all boards accessable to the current user", "with %s group board permission it should show board", async (permission) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, @@ -236,7 +236,7 @@ describe("getAllBoards should return all boards accessable to the current user", "with %s user board permission it should show board", async (permission) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, @@ -290,7 +290,7 @@ describe("getAllBoards should return all boards accessable to the current user", describe("createBoard should create a new board", () => { test("should create a new board with permission board-create", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = { ...defaultSession, user: { @@ -333,7 +333,7 @@ describe("createBoard should create a new board", () => { test("should throw error when user has no board-create permission", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -347,7 +347,7 @@ describe("createBoard should create a new board", () => { describe("rename board should rename board", () => { test("should rename board", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); @@ -375,7 +375,7 @@ describe("rename board should rename board", () => { test("should throw error when similar board name exists", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); await db.insert(users).values({ @@ -402,7 +402,7 @@ describe("rename board should rename board", () => { test("should throw error when board not found", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -418,7 +418,7 @@ describe("changeBoardVisibility should change board visibility", () => { "should change board visibility to %s", async (visibility) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); @@ -453,7 +453,7 @@ describe("changeBoardVisibility should change board visibility", () => { describe("deleteBoard should delete board", () => { test("should delete board", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); @@ -480,7 +480,7 @@ describe("deleteBoard should delete board", () => { test("should throw error when board not found", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -495,7 +495,7 @@ describe("getHomeBoard should return home board", () => { test("should return user home board when user has one", async () => { // Arrange const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const fullBoardProps = await createFullBoardAsync(db, "home"); @@ -519,7 +519,7 @@ describe("getHomeBoard should return home board", () => { test("should return global home board when user doesn't have one", async () => { // Arrange const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const fullBoardProps = await createFullBoardAsync(db, "home"); @@ -540,7 +540,7 @@ describe("getHomeBoard should return home board", () => { }); test("should throw error when home board not configured in serverSettings", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); await createFullBoardAsync(db, "home"); @@ -556,7 +556,7 @@ describe("getBoardByName should return board by name", () => { it.each([["default"], ["something"]])("should return board by name %s when present", async (name) => { // Arrange const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const fullBoardProps = await createFullBoardAsync(db, name); @@ -574,7 +574,7 @@ describe("getBoardByName should return board by name", () => { it("should throw error when not present", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); await createFullBoardAsync(db, "default"); @@ -590,7 +590,7 @@ describe("savePartialBoardSettings should save general settings", () => { it("should save general settings", async () => { // Arrange const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const newPageTitle = "newPageTitle"; @@ -647,7 +647,7 @@ describe("savePartialBoardSettings should save general settings", () => { }); it("should throw error when board not found", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const actAsync = async () => @@ -666,7 +666,7 @@ describe("savePartialBoardSettings should save general settings", () => { describe("saveBoard should save full board", () => { it("should remove section when not present in input", async () => { const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, sectionId } = await createFullBoardAsync(db, "default"); @@ -703,7 +703,7 @@ describe("saveBoard should save full board", () => { }); it("should remove item when not present in input", async () => { const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, itemId, sectionId, layoutId } = await createFullBoardAsync(db, "default"); @@ -760,7 +760,7 @@ describe("saveBoard should save full board", () => { }); it("should remove integration reference when not present in input", async () => { const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const anotherIntegration = { id: createId(), @@ -832,7 +832,7 @@ describe("saveBoard should save full board", () => { "should add section when present in input", async (partialSection) => { const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, sectionId } = await createFullBoardAsync(db, "default"); @@ -884,7 +884,7 @@ describe("saveBoard should save full board", () => { ); it("should add item when present in input", async () => { const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, sectionId, layoutId } = await createFullBoardAsync(db, "default"); @@ -956,7 +956,7 @@ describe("saveBoard should save full board", () => { }); it("should add integration reference when present in input", async () => { const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const integration = { id: createId(), @@ -1024,7 +1024,7 @@ describe("saveBoard should save full board", () => { expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify"); }); it("should update section when present in input", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, sectionId } = await createFullBoardAsync(db, "default"); @@ -1083,7 +1083,7 @@ describe("saveBoard should save full board", () => { }); it("should update item when present in input", async () => { const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, itemId, sectionId, layoutId } = await createFullBoardAsync(db, "default"); @@ -1147,7 +1147,7 @@ describe("saveBoard should save full board", () => { expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify"); }); it("should fail when board not found", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const actAsync = async () => @@ -1164,7 +1164,7 @@ describe("saveBoard should save full board", () => { describe("getBoardPermissions should return board permissions", () => { test("should return board permissions", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); @@ -1239,7 +1239,7 @@ describe("saveUserBoardPermissions should save user board permissions", () => { "should save user board permissions", async (permission) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); @@ -1281,7 +1281,7 @@ describe("saveGroupBoardPermissions should save group board permissions", () => "should save group board permissions", async (permission) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); @@ -1339,7 +1339,7 @@ const createNewLayout = (columnCount: number) => ({ describe("saveLayouts should save layout changes", () => { test("should add layout when not present in database", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, layoutId } = await createFullBoardAsync(db, "default"); @@ -1363,7 +1363,7 @@ describe("saveLayouts should save layout changes", () => { }); test("should add items and dynamic sections generated from grid-algorithm when new layout is added", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, layoutId, sectionId, itemId } = await createFullBoardAsync(db, "default"); @@ -1394,7 +1394,7 @@ describe("saveLayouts should save layout changes", () => { }); test("should update layout when present in input", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, layoutId } = await createFullBoardAsync(db, "default"); @@ -1420,7 +1420,7 @@ describe("saveLayouts should save layout changes", () => { }); test("should update position of items when column count changes", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, layoutId, sectionId, itemId } = await createFullBoardAsync(db, "default"); @@ -1447,7 +1447,7 @@ describe("saveLayouts should save layout changes", () => { }); test("should remove layout when not present in input", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { boardId, layoutId } = await createFullBoardAsync(db, "default"); @@ -1466,7 +1466,7 @@ describe("saveLayouts should save layout changes", () => { }); test("should fail when board not found", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); const { layoutId } = await createFullBoardAsync(db, "default"); diff --git a/packages/api/src/router/test/board/board-access.spec.ts b/packages/api/src/router/test/board/board-access.spec.ts index 9ba978381..445226844 100644 --- a/packages/api/src/router/test/board/board-access.spec.ts +++ b/packages/api/src/router/test/board/board-access.spec.ts @@ -4,7 +4,7 @@ import * as authShared from "@homarr/auth/shared"; import { createId } from "@homarr/common"; import { eq } from "@homarr/db"; import { boards, users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { throwIfActionForbiddenAsync } from "../../board/board-access"; @@ -26,7 +26,7 @@ describe("throwIfActionForbiddenAsync should check access to board and return bo ["view" as const, true], ])("with permission %s should return %s when hasFullAccess is true", async (permission, expectedResult) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(authShared, "constructBoardPermissions"); spy.mockReturnValue({ hasFullAccess: true, @@ -55,7 +55,7 @@ describe("throwIfActionForbiddenAsync should check access to board and return bo ["view" as const, true], ])("with permission %s should return %s when hasChangeAccess is true", async (permission, expectedResult) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(authShared, "constructBoardPermissions"); spy.mockReturnValue({ hasFullAccess: false, @@ -84,7 +84,7 @@ describe("throwIfActionForbiddenAsync should check access to board and return bo ["view" as const, true], ])("with permission %s should return %s when hasViewAccess is true", async (permission, expectedResult) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(authShared, "constructBoardPermissions"); spy.mockReturnValue({ hasFullAccess: false, @@ -113,7 +113,7 @@ describe("throwIfActionForbiddenAsync should check access to board and return bo ["view" as const, false], ])("with permission %s should return %s when hasViewAccess is false", async (permission, expectedResult) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(authShared, "constructBoardPermissions"); spy.mockReturnValue({ hasFullAccess: false, @@ -138,7 +138,7 @@ describe("throwIfActionForbiddenAsync should check access to board and return bo test("should throw when board is not found", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); // Act const act = () => throwIfActionForbiddenAsync({ db, session: null }, eq(boards.id, createId()), "full"); diff --git a/packages/api/src/router/test/group.spec.ts b/packages/api/src/router/test/group.spec.ts index 1f5b461cc..87e0dca9c 100644 --- a/packages/api/src/router/test/group.spec.ts +++ b/packages/api/src/router/test/group.spec.ts @@ -5,7 +5,7 @@ import * as env from "@homarr/auth/env"; import { createId } from "@homarr/common"; import { eq } from "@homarr/db"; import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import type { GroupPermissionKey } from "@homarr/definitions"; import { groupRouter } from "../group"; @@ -37,7 +37,7 @@ describe("paginated should return a list of groups with pagination", () => { "with 5 groups in database and pageSize set to 3 on page %s it should return %s groups", async (page, expectedCount) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(groups).values( @@ -61,7 +61,7 @@ describe("paginated should return a list of groups with pagination", () => { test("with 5 groups in database and pagesize set to 3 it should return total count 5", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(groups).values( @@ -83,7 +83,7 @@ describe("paginated should return a list of groups with pagination", () => { test("groups should contain id, name, email and image of members", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); const user = createDummyUser(); @@ -120,7 +120,7 @@ describe("paginated should return a list of groups with pagination", () => { "groups should be searchable by name with contains pattern, query %s should result in %s results", async (query, expectedCount, firstKey) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(groups).values( @@ -144,7 +144,7 @@ describe("paginated should return a list of groups with pagination", () => { test("without admin permissions it should throw unauthorized error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -158,7 +158,7 @@ describe("paginated should return a list of groups with pagination", () => { describe("byId should return group by id including members and permissions", () => { test('should return group with id "1" with members and permissions', async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); const user = createDummyUser(); @@ -203,7 +203,7 @@ describe("byId should return group by id including members and permissions", () test("with group id 1 and group 2 in database it should throw NOT_FOUND error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(groups).values({ @@ -221,7 +221,7 @@ describe("byId should return group by id including members and permissions", () test("without admin permissions it should throw unauthorized error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -235,7 +235,7 @@ describe("byId should return group by id including members and permissions", () describe("create should create group in database", () => { test("with valid input (64 character name) and non existing name it should be successful", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); const name = "a".repeat(64); @@ -259,7 +259,7 @@ describe("create should create group in database", () => { test("with more than 64 characters name it should fail while validation", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); const longName = "a".repeat(65); @@ -280,7 +280,7 @@ describe("create should create group in database", () => { ["test", " TeSt"], ])("with similar name %s it should fail to create %s", async (similarName, nameToCreate) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(groups).values({ @@ -298,7 +298,7 @@ describe("create should create group in database", () => { test("without admin permissions it should throw unauthorized error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -315,7 +315,7 @@ describe("update should update name with value that is no duplicate", () => { ["first", " first", "first"], ])("update should update name from %s to %s normalized", async (initialValue, updateValue, expectedValue) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); const groupId = createId(); @@ -350,7 +350,7 @@ describe("update should update name with value that is no duplicate", () => { [" seCond", "second"], ])("with similar name %s it should fail to update %s", async (updateValue, initialDuplicate) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); const groupId = createId(); @@ -380,7 +380,7 @@ describe("update should update name with value that is no duplicate", () => { test("with non existing id it should throw not found error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(groups).values({ @@ -402,7 +402,7 @@ describe("update should update name with value that is no duplicate", () => { test("without admin permissions it should throw unauthorized error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -420,7 +420,7 @@ describe("update should update name with value that is no duplicate", () => { describe("savePermissions should save permissions for group", () => { test("with existing group and permissions it should save permissions", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); const groupId = createId(); @@ -451,7 +451,7 @@ describe("savePermissions should save permissions for group", () => { test("with non existing group it should throw not found error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(groups).values({ @@ -473,7 +473,7 @@ describe("savePermissions should save permissions for group", () => { test("without admin permissions it should throw unauthorized error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -491,7 +491,7 @@ describe("savePermissions should save permissions for group", () => { describe("transferOwnership should transfer ownership of group", () => { test("with existing group and user it should transfer ownership", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); const groupId = createId(); @@ -529,7 +529,7 @@ describe("transferOwnership should transfer ownership of group", () => { test("with non existing group it should throw not found error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(groups).values({ @@ -551,7 +551,7 @@ describe("transferOwnership should transfer ownership of group", () => { test("without admin permissions it should throw unauthorized error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -569,7 +569,7 @@ describe("transferOwnership should transfer ownership of group", () => { describe("deleteGroup should delete group", () => { test("with existing group it should delete group", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); const groupId = createId(); @@ -600,7 +600,7 @@ describe("deleteGroup should delete group", () => { test("with non existing group it should throw not found error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(groups).values({ @@ -621,7 +621,7 @@ describe("deleteGroup should delete group", () => { test("without admin permissions it should throw unauthorized error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -638,7 +638,7 @@ describe("deleteGroup should delete group", () => { describe("addMember should add member to group", () => { test("with existing group and user it should add member", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(env, "env", "get"); spy.mockReturnValue({ AUTH_PROVIDERS: ["credentials"] } as never); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); @@ -679,7 +679,7 @@ describe("addMember should add member to group", () => { test("with non existing group it should throw not found error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(users).values({ @@ -700,7 +700,7 @@ describe("addMember should add member to group", () => { test("without admin permissions it should throw unauthorized error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -716,7 +716,7 @@ describe("addMember should add member to group", () => { test("without credentials provider it should throw FORBIDDEN error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(env, "env", "get"); spy.mockReturnValue({ AUTH_PROVIDERS: ["ldap"] } as never); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); @@ -755,7 +755,7 @@ describe("addMember should add member to group", () => { describe("removeMember should remove member from group", () => { test("with existing group and user it should remove member", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(env, "env", "get"); spy.mockReturnValue({ AUTH_PROVIDERS: ["credentials"] } as never); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); @@ -799,7 +799,7 @@ describe("removeMember should remove member from group", () => { test("with non existing group it should throw not found error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); await db.insert(users).values({ @@ -820,7 +820,7 @@ describe("removeMember should remove member from group", () => { test("without admin permissions it should throw unauthorized error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: defaultSession }); // Act @@ -836,7 +836,7 @@ describe("removeMember should remove member from group", () => { test("without credentials provider it should throw FORBIDDEN error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(env, "env", "get"); spy.mockReturnValue({ AUTH_PROVIDERS: ["ldap"] } as never); const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); diff --git a/packages/api/src/router/test/integration/integration-access.spec.ts b/packages/api/src/router/test/integration/integration-access.spec.ts index b5349558a..96d44c9fc 100644 --- a/packages/api/src/router/test/integration/integration-access.spec.ts +++ b/packages/api/src/router/test/integration/integration-access.spec.ts @@ -4,7 +4,7 @@ import * as authShared from "@homarr/auth/shared"; import { createId } from "@homarr/common"; import { eq } from "@homarr/db"; import { integrations, users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { throwIfActionForbiddenAsync } from "../../integration/integration-access"; @@ -26,7 +26,7 @@ describe("throwIfActionForbiddenAsync should check access to integration and ret ["use" as const, true], ])("with permission %s should return %s when hasFullAccess is true", async (permission, expectedResult) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(authShared, "constructIntegrationPermissions"); spy.mockReturnValue({ hasFullAccess: true, @@ -56,7 +56,7 @@ describe("throwIfActionForbiddenAsync should check access to integration and ret ["use" as const, true], ])("with permission %s should return %s when hasInteractAccess is true", async (permission, expectedResult) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(authShared, "constructIntegrationPermissions"); spy.mockReturnValue({ hasFullAccess: false, @@ -87,7 +87,7 @@ describe("throwIfActionForbiddenAsync should check access to integration and ret ["use" as const, true], ])("with permission %s should return %s when hasUseAccess is true", async (permission, expectedResult) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(authShared, "constructIntegrationPermissions"); spy.mockReturnValue({ hasFullAccess: false, @@ -118,7 +118,7 @@ describe("throwIfActionForbiddenAsync should check access to integration and ret ["use" as const, false], ])("with permission %s should return %s when hasUseAccess is false", async (permission, expectedResult) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(authShared, "constructIntegrationPermissions"); spy.mockReturnValue({ hasFullAccess: false, @@ -145,7 +145,7 @@ describe("throwIfActionForbiddenAsync should check access to integration and ret test("should throw when integration is not found", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); // Act const act = () => throwIfActionForbiddenAsync({ db, session: null }, eq(integrations.id, createId()), "full"); diff --git a/packages/api/src/router/test/integration/integration-router.spec.ts b/packages/api/src/router/test/integration/integration-router.spec.ts index 17927b9c4..307bc9651 100644 --- a/packages/api/src/router/test/integration/integration-router.spec.ts +++ b/packages/api/src/router/test/integration/integration-router.spec.ts @@ -5,7 +5,7 @@ import type { Session } from "@homarr/auth"; import { createId } from "@homarr/common"; import { encryptSecret } from "@homarr/common/server"; import { apps, integrations, integrationSecrets } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import type { GroupPermissionKey } from "@homarr/definitions"; import { integrationRouter } from "../../integration/integration-router"; @@ -30,7 +30,7 @@ vi.mock("../../integration/integration-test-connection", () => ({ describe("all should return all integrations", () => { test("with any session should return all integrations", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -61,7 +61,7 @@ describe("all should return all integrations", () => { describe("byId should return an integration by id", () => { test("with full access should return an integration by id", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -88,7 +88,7 @@ describe("byId should return an integration by id", () => { }); test("with full access should throw an error if the integration does not exist", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -100,7 +100,7 @@ describe("byId should return an integration by id", () => { }); test("with full access should only return the public secret values", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -148,7 +148,7 @@ describe("byId should return an integration by id", () => { test("without full access should throw integration not found error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -174,7 +174,7 @@ describe("byId should return an integration by id", () => { describe("create should create a new integration", () => { test("with create integration access should create a new integration", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -209,7 +209,7 @@ describe("create should create a new integration", () => { }); test("with create integration access should create a new integration when creating search engine", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -252,7 +252,7 @@ describe("create should create a new integration", () => { }); test("with create integration access should create a new integration with new linked app", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -302,7 +302,7 @@ describe("create should create a new integration", () => { }); test("with create integration access should create a new integration with existing linked app", async () => { - const db = createDb(); + const db = await createDbAsync(); const appId = createId(); await db.insert(apps).values({ id: appId, @@ -349,7 +349,7 @@ describe("create should create a new integration", () => { test("without create integration access should throw permission error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -372,7 +372,7 @@ describe("create should create a new integration", () => { test("without create app access should throw permission error with new linked app", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -403,7 +403,7 @@ describe("create should create a new integration", () => { describe("update should update an integration", () => { test("with full access should update an integration", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -486,7 +486,7 @@ describe("update should update an integration", () => { }); test("with full access should throw an error if the integration does not exist", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -506,7 +506,7 @@ describe("update should update an integration", () => { test("without full access should throw permission error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -530,7 +530,7 @@ describe("update should update an integration", () => { describe("delete should delete an integration", () => { test("with full access should delete an integration", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, @@ -564,7 +564,7 @@ describe("delete should delete an integration", () => { test("without full access should throw permission error", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = integrationRouter.createCaller({ db, deviceType: undefined, diff --git a/packages/api/src/router/test/invite.spec.ts b/packages/api/src/router/test/invite.spec.ts index 57a157de3..a4405ba39 100644 --- a/packages/api/src/router/test/invite.spec.ts +++ b/packages/api/src/router/test/invite.spec.ts @@ -4,7 +4,7 @@ import { describe, expect, test, vi } from "vitest"; import type { Session } from "@homarr/auth"; import { createId } from "@homarr/common"; import { invites, users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { inviteRouter } from "../invite"; @@ -35,7 +35,7 @@ vi.mock("@homarr/auth/env", () => { describe("all should return all existing invites without sensitive informations", () => { test("invites should not contain sensitive informations", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = inviteRouter.createCaller({ db, deviceType: undefined, @@ -70,7 +70,7 @@ describe("all should return all existing invites without sensitive informations" test("invites should be sorted ascending by expiration date", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = inviteRouter.createCaller({ db, deviceType: undefined, @@ -110,7 +110,7 @@ describe("all should return all existing invites without sensitive informations" describe("create should create a new invite expiring on the specified date with a token and id returned to generate url", () => { test("creation should work with a date in the future, but less than 6 months.", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = inviteRouter.createCaller({ db, deviceType: undefined, @@ -142,7 +142,7 @@ describe("create should create a new invite expiring on the specified date with describe("delete should remove invite by id", () => { test("deletion should remove present invite", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = inviteRouter.createCaller({ db, deviceType: undefined, @@ -180,7 +180,7 @@ describe("delete should remove invite by id", () => { test("deletion should throw with NOT_FOUND code when specified invite not present", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = inviteRouter.createCaller({ db, deviceType: undefined, diff --git a/packages/api/src/router/test/serverSettings.spec.ts b/packages/api/src/router/test/serverSettings.spec.ts index 6a0e458c2..984b60e4a 100644 --- a/packages/api/src/router/test/serverSettings.spec.ts +++ b/packages/api/src/router/test/serverSettings.spec.ts @@ -4,7 +4,7 @@ import { describe, expect, test, vi } from "vitest"; import type { Session } from "@homarr/auth"; import { createId } from "@homarr/common"; import { serverSettings } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings"; import { serverSettingsRouter } from "../serverSettings"; @@ -23,7 +23,7 @@ const defaultSession = { describe("getAll server settings", () => { test("getAll should throw error when unauthorized", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = serverSettingsRouter.createCaller({ db, deviceType: undefined, @@ -42,7 +42,7 @@ describe("getAll server settings", () => { await expect(actAsync()).rejects.toThrow(); }); test("getAll should return default server settings when nothing in database", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = serverSettingsRouter.createCaller({ db, deviceType: undefined, @@ -57,7 +57,7 @@ describe("getAll server settings", () => { describe("saveSettings", () => { test("saveSettings should update settings and return true when it updated only one", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = serverSettingsRouter.createCaller({ db, deviceType: undefined, diff --git a/packages/api/src/router/test/user.spec.ts b/packages/api/src/router/test/user.spec.ts index b3b262846..c82008604 100644 --- a/packages/api/src/router/test/user.spec.ts +++ b/packages/api/src/router/test/user.spec.ts @@ -5,7 +5,7 @@ import { createId } from "@homarr/common"; import type { Database } from "@homarr/db"; import { eq } from "@homarr/db"; import { invites, onboarding, users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import type { GroupPermissionKey, OnboardingStep } from "@homarr/definitions"; import { userRouter } from "../user"; @@ -39,7 +39,7 @@ vi.mock("@homarr/auth/env", () => { describe("initUser should initialize the first user", () => { it("should create a user if none exists", async () => { - const db = createDb(); + const db = await createDbAsync(); await createOnboardingStepAsync(db, "user"); const caller = userRouter.createCaller({ db, @@ -63,7 +63,7 @@ describe("initUser should initialize the first user", () => { }); it("should not create a user if the password and confirmPassword do not match", async () => { - const db = createDb(); + const db = await createDbAsync(); await createOnboardingStepAsync(db, "user"); const caller = userRouter.createCaller({ db, @@ -88,7 +88,7 @@ describe("initUser should initialize the first user", () => { ["ABC123+/-"], // does not contain lowercase ["abc123+/-"], // does not contain uppercase ])("should throw error that password requirements do not match for '%s' as password", async (password) => { - const db = createDb(); + const db = await createDbAsync(); await createOnboardingStepAsync(db, "user"); const caller = userRouter.createCaller({ db, @@ -110,7 +110,7 @@ describe("initUser should initialize the first user", () => { describe("register should create a user with valid invitation", () => { test("register should create a user with valid invitation", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = userRouter.createCaller({ db, deviceType: undefined, @@ -166,7 +166,7 @@ describe("register should create a user with valid invitation", () => { "register should throw an error with input %s and date %s if the invitation is invalid", async (partialInput, systemTime) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const caller = userRouter.createCaller({ db, deviceType: undefined, @@ -209,7 +209,7 @@ describe("register should create a user with valid invitation", () => { describe("editProfile shoud update user", () => { test("editProfile should update users and not update emailVerified when email not dirty", async () => { // arrange - const db = createDb(); + const db = await createDbAsync(); const caller = userRouter.createCaller({ db, deviceType: undefined, @@ -246,7 +246,7 @@ describe("editProfile shoud update user", () => { test("editProfile should update users and update emailVerified when email dirty", async () => { // arrange - const db = createDb(); + const db = await createDbAsync(); const caller = userRouter.createCaller({ db, deviceType: undefined, @@ -282,7 +282,7 @@ describe("editProfile shoud update user", () => { describe("delete should delete user", () => { test("delete should delete user", async () => { - const db = createDb(); + const db = await createDbAsync(); const caller = userRouter.createCaller({ db, deviceType: undefined, diff --git a/packages/api/src/router/test/widgets/app.spec.ts b/packages/api/src/router/test/widgets/app.spec.ts index bd5265ff6..7dc389e4a 100644 --- a/packages/api/src/router/test/widgets/app.spec.ts +++ b/packages/api/src/router/test/widgets/app.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from "vitest"; import type { Session } from "@homarr/auth"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import * as ping from "@homarr/ping"; import { appRouter } from "../../widgets/app"; @@ -15,7 +15,7 @@ describe("ping should call sendPingRequestAsync with url and return result", () // Arrange const spy = vi.spyOn(ping, "sendPingRequestAsync"); const url = "http://localhost"; - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, @@ -35,7 +35,7 @@ describe("ping should call sendPingRequestAsync with url and return result", () // Arrange const spy = vi.spyOn(ping, "sendPingRequestAsync"); const url = "http://localhost"; - const db = createDb(); + const db = await createDbAsync(); const caller = appRouter.createCaller({ db, deviceType: undefined, diff --git a/packages/auth/permissions/test/integration-query-permissions.spec.ts b/packages/auth/permissions/test/integration-query-permissions.spec.ts index 4942c71cc..e9fd9e6c7 100644 --- a/packages/auth/permissions/test/integration-query-permissions.spec.ts +++ b/packages/auth/permissions/test/integration-query-permissions.spec.ts @@ -4,7 +4,7 @@ import { describe, expect, test, vi } from "vitest"; import { createId } from "@homarr/common"; import type { InferInsertModel } from "@homarr/db"; import { boardGroupPermissions, boards, boardUserPermissions, groupMembers, groups, users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import * as integrationPermissions from "../integration-permissions"; import { hasQueryAccessToIntegrationsAsync } from "../integration-query-permissions"; @@ -22,7 +22,7 @@ const createSession = (user: Partial): Session => ({ describe("hasQueryAccessToIntegrationsAsync should check if the user has query access to the specified integrations", () => { test("should return true if the user has the board-view-all permission and the integrations are used anywhere", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({ permissions: ["board-view-all"], }); @@ -50,7 +50,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return true if the user has the board-view-all permission, the first integration is used and the second one he has use access", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({ permissions: ["board-view-all"], }); @@ -84,7 +84,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return true if the user has use access to all integrations", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({}); const spy = vi.spyOn(integrationPermissions, "constructIntegrationPermissions"); spy.mockReturnValue({ @@ -116,7 +116,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return true if the user has user permission to access to at least one board of each integration", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({}); await db.insert(users).values({ id: session.user.id }); const spy = vi.spyOn(integrationPermissions, "constructIntegrationPermissions"); @@ -168,7 +168,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return false if the user has user permission to access board of first integration but not of second one", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({}); await db.insert(users).values({ id: session.user.id }); const spy = vi.spyOn(integrationPermissions, "constructIntegrationPermissions"); @@ -215,7 +215,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return true if the user has group permission to access to at least one board of each integration", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({}); await db.insert(users).values({ id: session.user.id }); const spy = vi.spyOn(integrationPermissions, "constructIntegrationPermissions"); @@ -269,7 +269,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return false if the user has group permission to access board of first integration but not of second one", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({}); await db.insert(users).values({ id: session.user.id }); const spy = vi.spyOn(integrationPermissions, "constructIntegrationPermissions"); @@ -318,7 +318,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return true if the user has user permission to access first board and group permission to access second one", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({}); await db.insert(users).values({ id: session.user.id }); const spy = vi.spyOn(integrationPermissions, "constructIntegrationPermissions"); @@ -369,7 +369,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return true if one of the boards the integration is used is public", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({}); const integrations = [ { @@ -413,7 +413,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return true if the user is creator of the board the integration is used", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({}); await db.insert(users).values({ id: session.user.id }); const integrations = [ @@ -458,7 +458,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return false if the user has no access to any of the integrations", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const session = createSession({}); const integrations = [ { @@ -496,7 +496,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return false if the user is anonymous and the board is not public", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const integrations = [ { id: "1", @@ -534,7 +534,7 @@ describe("hasQueryAccessToIntegrationsAsync should check if the user has query a test("should return true if the user is anonymous and the board is public", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const integrations = [ { id: "1", diff --git a/packages/auth/providers/test/basic-authorization.spec.ts b/packages/auth/providers/test/basic-authorization.spec.ts index 17d1d00e1..893fef816 100644 --- a/packages/auth/providers/test/basic-authorization.spec.ts +++ b/packages/auth/providers/test/basic-authorization.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "vitest"; import { createId } from "@homarr/common"; import { users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { createSaltAsync, hashPasswordAsync } from "../../security"; import { authorizeWithBasicCredentialsAsync } from "../credentials/authorization/basic-authorization"; @@ -12,7 +12,7 @@ const defaultUserId = createId(); describe("authorizeWithBasicCredentials", () => { test("should authorize user with correct credentials", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const salt = await createSaltAsync(); await db.insert(users).values({ id: defaultUserId, @@ -33,7 +33,7 @@ describe("authorizeWithBasicCredentials", () => { test("should not authorize user with incorrect credentials", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const salt = await createSaltAsync(); await db.insert(users).values({ id: defaultUserId, @@ -54,7 +54,7 @@ describe("authorizeWithBasicCredentials", () => { test("should not authorize user with incorrect username", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const salt = await createSaltAsync(); await db.insert(users).values({ id: defaultUserId, @@ -75,7 +75,7 @@ describe("authorizeWithBasicCredentials", () => { test("should not authorize user when password is not set", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await db.insert(users).values({ id: defaultUserId, name: "test", diff --git a/packages/auth/providers/test/ldap-authorization.spec.ts b/packages/auth/providers/test/ldap-authorization.spec.ts index 0b9fd8935..4d5cbe081 100644 --- a/packages/auth/providers/test/ldap-authorization.spec.ts +++ b/packages/auth/providers/test/ldap-authorization.spec.ts @@ -5,7 +5,7 @@ import { createId } from "@homarr/common"; import type { Database } from "@homarr/db"; import { and, eq } from "@homarr/db"; import { groups, users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { authorizeWithLdapCredentialsAsync } from "../credentials/authorization/ldap-authorization"; import * as ldapClient from "../credentials/ldap-client"; @@ -123,7 +123,7 @@ describe("authorizeWithLdapCredentials", () => { test("should authorize user with correct credentials and create user", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(ldapClient, "LdapClient"); spy.mockImplementation(function () { return { @@ -161,7 +161,7 @@ describe("authorizeWithLdapCredentials", () => { test("should authorize user with correct credentials and create user with same email when credentials user already exists", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const spy = vi.spyOn(ldapClient, "LdapClient"); spy.mockImplementation(function () { return { @@ -230,7 +230,7 @@ describe("authorizeWithLdapCredentials", () => { }); const userId = createId(); - const db = createDb(); + const db = await createDbAsync(); await db.insert(users).values({ id: userId, name: "test-old", @@ -281,7 +281,7 @@ describe("authorizeWithLdapCredentials", () => { disconnectAsync: vi.fn(), } as unknown as ldapClient.LdapClient; }); - const db = createDb(); + const db = await createDbAsync(); const userId = createId(); await db.insert(users).values({ id: userId, diff --git a/packages/auth/test/adapter.spec.ts b/packages/auth/test/adapter.spec.ts index d20ae3d1a..64ae05fcc 100644 --- a/packages/auth/test/adapter.spec.ts +++ b/packages/auth/test/adapter.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "vitest"; import { users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { createAdapter } from "../adapter"; @@ -10,7 +10,7 @@ describe("createAdapter should create drizzle adapter", () => { "createAdapter getUserByEmail should return user for provider %s when this provider provided", async (provider) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const adapter = createAdapter(db, provider); const email = "test@example.com"; await db.insert(users).values({ id: "1", name: "test", email, provider }); @@ -37,7 +37,7 @@ describe("createAdapter should create drizzle adapter", () => { "createAdapter getUserByEmail should return null if only for other providers than %s exist", async (requestedProvider, existingProviders) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const adapter = createAdapter(db, requestedProvider); const email = "test@example.com"; for (const provider of existingProviders) { @@ -54,7 +54,7 @@ describe("createAdapter should create drizzle adapter", () => { test("createAdapter getUserByEmail should throw error if provider is unknown", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const adapter = createAdapter(db, "unknown"); const email = "test@example.com"; diff --git a/packages/auth/test/callbacks.spec.ts b/packages/auth/test/callbacks.spec.ts index 6709d1bb0..8496a0018 100644 --- a/packages/auth/test/callbacks.spec.ts +++ b/packages/auth/test/callbacks.spec.ts @@ -4,7 +4,7 @@ import type { JWT } from "next-auth/jwt"; import { describe, expect, test, vi } from "vitest"; import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import * as definitions from "@homarr/definitions"; import { createSessionCallback, getCurrentUserPermissionsAsync } from "../callbacks"; @@ -20,7 +20,7 @@ vi.mock("next/headers", () => ({ describe("getCurrentUserPermissions", () => { test("should return empty permissions when non existing user requested", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await db.insert(groups).values({ id: "2", @@ -46,7 +46,7 @@ describe("getCurrentUserPermissions", () => { test("should return empty permissions when user has no groups", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const userId = "1"; await db.insert(groups).values({ @@ -71,7 +71,7 @@ describe("getCurrentUserPermissions", () => { test("should return permissions for user", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); const getPermissionsWithChildrenMock = vi .spyOn(definitions, "getPermissionsWithChildren") .mockReturnValue(["board-create"]); @@ -113,7 +113,7 @@ describe("session callback", () => { emailVerified: new Date("2023-01-13"), }; const token: JWT = {}; - const db = createDb(); + const db = await createDbAsync(); const callback = createSessionCallback(db); // Act diff --git a/packages/auth/test/events.spec.ts b/packages/auth/test/events.spec.ts index ad6178ab2..438baeac6 100644 --- a/packages/auth/test/events.spec.ts +++ b/packages/auth/test/events.spec.ts @@ -6,7 +6,7 @@ import { describe, expect, test, vi } from "vitest"; import { eq } from "@homarr/db"; import type { Database } from "@homarr/db"; import { groupMembers, groups, users } from "@homarr/db/schema"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions"; import { createSignInEventHandler } from "../events"; @@ -39,7 +39,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => { describe("signInEventHandler should add users to everyone group", () => { test("should add user to everyone group if he isn't already", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await createUserAsync(db); await createGroupAsync(db, everyoneGroup); const eventHandler = createSignInEventHandler(db); @@ -62,7 +62,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => { describe("signInEventHandler should synchronize ldap groups", () => { test("should add missing group membership", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await createUserAsync(db); await createGroupAsync(db); const eventHandler = createSignInEventHandler(db); @@ -82,7 +82,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => { }); test("should remove group membership", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await createUserAsync(db); await createGroupAsync(db); await db.insert(groupMembers).values({ @@ -106,7 +106,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => { }); test("should not remove group membership for everyone group", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await createUserAsync(db); await createGroupAsync(db, everyoneGroup); await db.insert(groupMembers).values({ @@ -132,7 +132,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => { describe("signInEventHandler should synchronize oidc groups", () => { test("should add missing group membership", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await createUserAsync(db); await createGroupAsync(db); const eventHandler = createSignInEventHandler(db); @@ -152,7 +152,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => { }); test("should remove group membership", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await createUserAsync(db); await createGroupAsync(db); await db.insert(groupMembers).values({ @@ -176,7 +176,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => { }); test("should not remove group membership for everyone group", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await createUserAsync(db); await createGroupAsync(db, everyoneGroup); await db.insert(groupMembers).values({ @@ -205,7 +205,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => { ["oidc" as const, { name: "test" }, { preferred_username: "test@example.com", name: "test-new" }], ])("signInEventHandler should update username for %s provider", async (_provider, user, profile) => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await createUserAsync(db); const eventHandler = createSignInEventHandler(db); @@ -227,7 +227,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => { }); test("signInEventHandler should set color-scheme cookie", async () => { // Arrange - const db = createDb(); + const db = await createDbAsync(); await createUserAsync(db); const eventHandler = createSignInEventHandler(db); diff --git a/packages/core/package.json b/packages/core/package.json index 85831edfb..0ac080e1e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -37,8 +37,8 @@ }, "prettier": "@homarr/prettier-config", "dependencies": { + "@libsql/client": "0.15.15", "@t3-oss/env-nextjs": "^0.13.10", - "better-sqlite3": "^12.5.0", "dns-caching": "^0.2.9", "drizzle-orm": "^0.45.1", "ioredis": "5.8.2", @@ -52,7 +52,6 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "@types/better-sqlite3": "7.6.13", "@types/pg": "^8.16.0", "eslint": "^9.39.2", "typescript": "^5.9.3" diff --git a/packages/core/src/infrastructure/db/drivers/sqlite.ts b/packages/core/src/infrastructure/db/drivers/sqlite.ts index ce95fe5a0..956ab3326 100644 --- a/packages/core/src/infrastructure/db/drivers/sqlite.ts +++ b/packages/core/src/infrastructure/db/drivers/sqlite.ts @@ -1,10 +1,12 @@ -import Database from "better-sqlite3"; -import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3"; +import { createClient } from "@libsql/client/node"; +import { drizzle as drizzleSqlite } from "drizzle-orm/libsql"; import { dbEnv } from "../env"; import type { SharedDrizzleConfig } from "./shared"; export const createSqliteDb = >(config: SharedDrizzleConfig) => { - const connection = new Database(dbEnv.URL); + const connection = createClient({ + url: dbEnv.URL ? `file:${dbEnv.URL}` : ":memory:", + }); return drizzleSqlite(connection, config); }; diff --git a/packages/cron-jobs/src/jobs/icons-updater.ts b/packages/cron-jobs/src/jobs/icons-updater.ts index b48c26ea6..0789201e2 100644 --- a/packages/cron-jobs/src/jobs/icons-updater.ts +++ b/packages/cron-jobs/src/jobs/icons-updater.ts @@ -3,7 +3,7 @@ import { env } from "@homarr/common/env"; import { createLogger } from "@homarr/core/infrastructure/logs"; import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions"; import type { InferInsertModel } from "@homarr/db"; -import { db, handleTransactionsAsync, inArray, sql } from "@homarr/db"; +import { db, inArray, sql } from "@homarr/db"; import { iconRepositories, icons } from "@homarr/db/schema"; import { fetchIconsAsync } from "@homarr/icons"; @@ -89,81 +89,37 @@ export const iconsUpdaterJob = createCronJob("iconsUpdater", EVERY_WEEK, { (iconRepository) => !repositoryIconGroups.some((group) => group.slug === iconRepository.slug), ); - await handleTransactionsAsync(db, { - async handleAsync(db, schema) { - await db.transaction(async (transaction) => { - if (newIconRepositories.length >= 1) { - await transaction.insert(schema.iconRepositories).values(newIconRepositories); - } + await db.transaction(async (transaction) => { + if (newIconRepositories.length >= 1) { + await transaction.insert(iconRepositories).values(newIconRepositories); + } - if (newIcons.length >= 1) { - // We only insert 5000 icons at a time to avoid SQLite limitations - for (const chunck of splitToNChunks(newIcons, Math.ceil(newIcons.length / 5000))) { - await transaction.insert(schema.icons).values(chunck); - } - } - if (deadIcons.length >= 1) { - await transaction.delete(schema.icons).where( - inArray( - // Combine iconRepositoryId and checksum to allow same icons on different repositories - sql`concat(${icons.iconRepositoryId}, '.', ${icons.checksum})`, - deadIcons.map((icon) => `${icon.iconRepositoryId}.${icon.checksum}`), - ), - ); - } + if (newIcons.length >= 1) { + // We only insert 5000 icons at a time to avoid SQLite limitations + for (const chunck of splitToNChunks(newIcons, Math.ceil(newIcons.length / 5000))) { + await transaction.insert(icons).values(chunck); + } + } + if (deadIcons.length >= 1) { + await transaction.delete(icons).where( + inArray( + // Combine iconRepositoryId and checksum to allow same icons on different repositories + sql`concat(${icons.iconRepositoryId}, '.', ${icons.checksum})`, + deadIcons.map((icon) => `${icon.iconRepositoryId}.${icon.checksum}`), + ), + ); + } - if (deadIconRepositories.length >= 1) { - await transaction.delete(schema.iconRepositories).where( - inArray( - iconRepositories.id, - deadIconRepositories.map((iconRepository) => iconRepository.id), - ), - ); - } + if (deadIconRepositories.length >= 1) { + await transaction.delete(iconRepositories).where( + inArray( + iconRepositories.id, + deadIconRepositories.map((iconRepository) => iconRepository.id), + ), + ); + } - countDeleted += deadIcons.length; - }); - }, - handleSync() { - db.transaction((transaction) => { - if (newIconRepositories.length >= 1) { - transaction.insert(iconRepositories).values(newIconRepositories).run(); - } - - if (newIcons.length >= 1) { - // We only insert 5000 icons at a time to avoid SQLite limitations - for (const chunck of splitToNChunks(newIcons, Math.ceil(newIcons.length / 5000))) { - transaction.insert(icons).values(chunck).run(); - } - } - if (deadIcons.length >= 1) { - transaction - .delete(icons) - .where( - inArray( - // Combine iconRepositoryId and checksum to allow same icons on different repositories - sql`concat(${icons.iconRepositoryId}, '.', ${icons.checksum})`, - deadIcons.map((icon) => `${icon.iconRepositoryId}.${icon.checksum}`), - ), - ) - .run(); - } - - if (deadIconRepositories.length >= 1) { - transaction - .delete(iconRepositories) - .where( - inArray( - iconRepositories.id, - deadIconRepositories.map((iconRepository) => iconRepository.id), - ), - ) - .run(); - } - - countDeleted += deadIcons.length; - }); - }, + countDeleted += deadIcons.length; }); logger.info("Updated icons in database", { diff --git a/packages/db/collection.ts b/packages/db/collection.ts index 00e5aba18..c0bad6e94 100644 --- a/packages/db/collection.ts +++ b/packages/db/collection.ts @@ -1,26 +1,15 @@ import type { InferInsertModel } from "drizzle-orm"; import { objectEntries } from "@homarr/common"; -import { dbEnv } from "@homarr/core/infrastructure/db/env"; -import type { HomarrDatabase, HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver"; +import type { HomarrDatabase, HomarrTransaction } from "./driver"; import * as schema from "./schema"; type TableKey = { [K in keyof typeof schema]: (typeof schema)[K] extends { _: { brand: "Table" } } ? K : never; }[keyof typeof schema]; -export function isMysql(): boolean { - return dbEnv.DRIVER === "mysql2"; -} - -export function isPostgresql(): boolean { - return dbEnv.DRIVER === "node-postgres"; -} - -export const createDbInsertCollectionForTransaction = ( - tablesInInsertOrder: TTableKey[], -) => { +export const createDbInsertCollection = (tablesInInsertOrder: TTableKey[]) => { const context = tablesInInsertOrder.reduce( (acc, key) => { acc[key] = []; @@ -31,25 +20,10 @@ export const createDbInsertCollectionForTransaction = { - db.transaction((transaction) => { + insertAllAsync: async (db: HomarrDatabase | HomarrTransaction) => { + await db.transaction(async (transaction) => { for (const [key, values] of objectEntries(context)) { if (values.length >= 1) { - transaction - .insert(schema[key]) - .values(values as never) - .run(); - } - } - }); - }, - // We allow any database that supports async passed here but then fallback to mysql to prevent typescript errors - insertAllAsync: async (db: HomarrDatabaseMysql | HomarrDatabasePostgresql) => { - const innerDb = db as HomarrDatabaseMysql; - await innerDb.transaction(async (transaction) => { - for (const [key, values] of objectEntries(context)) { - if (values.length >= 1) { - // Below is actually the mysqlSchema when the driver is mysql await transaction.insert(schema[key] as never).values(values as never); } } @@ -57,27 +31,3 @@ export const createDbInsertCollectionForTransaction = ( - tablesInInsertOrder: TTableKey[], -) => { - const { insertAll, insertAllAsync, ...collection } = createDbInsertCollectionForTransaction(tablesInInsertOrder); - - return { - ...collection, - insertAllAsync: async (db: HomarrDatabase) => { - switch (dbEnv.DRIVER) { - case "mysql2": - case "node-postgres": - // For mysql2 and node-postgres, we can use the async insertAllAsync method - await insertAllAsync(db as unknown as HomarrDatabaseMysql | HomarrDatabasePostgresql); - return; - default: - // For better-sqlite3, we need to use the synchronous insertAll method - // default assumes better-sqlite3. It's original implementation. - insertAll(db); - break; - } - }, - }; -}; diff --git a/packages/db/driver.ts b/packages/db/driver.ts index 6c2f97178..adf254a81 100644 --- a/packages/db/driver.ts +++ b/packages/db/driver.ts @@ -1,4 +1,5 @@ -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { ExtractTablesWithRelations } from "drizzle-orm"; +import type { LibSQLDatabase, LibSQLTransaction } from "drizzle-orm/libsql"; import type { MySql2Database } from "drizzle-orm/mysql2"; import type { NodePgDatabase } from "drizzle-orm/node-postgres"; @@ -6,6 +7,7 @@ import type * as mysqlSchema from "./schema/mysql"; import type * as pgSchema from "./schema/postgresql"; import type * as sqliteSchema from "./schema/sqlite"; -export type HomarrDatabase = BetterSQLite3Database; +export type HomarrDatabase = LibSQLDatabase; +export type HomarrTransaction = LibSQLTransaction>; export type HomarrDatabaseMysql = MySql2Database; export type HomarrDatabasePostgresql = NodePgDatabase; diff --git a/packages/db/index.ts b/packages/db/index.ts index d32e46b34..5c9b08802 100644 --- a/packages/db/index.ts +++ b/packages/db/index.ts @@ -8,5 +8,3 @@ export type { HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver"; export const db = createDb(schema); export type Database = typeof db; - -export { handleDiffrentDbDriverOperationsAsync as handleTransactionsAsync } from "./transactions"; diff --git a/packages/db/migrations/sqlite/migrate.ts b/packages/db/migrations/sqlite/migrate.ts index e9c44d9f7..38bf98e2b 100644 --- a/packages/db/migrations/sqlite/migrate.ts +++ b/packages/db/migrations/sqlite/migrate.ts @@ -1,4 +1,4 @@ -import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { migrate } from "drizzle-orm/libsql/migrator"; import { createSharedDbConfig, createSqliteDb } from "@homarr/core/infrastructure/db"; @@ -12,7 +12,7 @@ const migrateAsync = async () => { const config = createSharedDbConfig(sqliteSchema); const db = createSqliteDb(config); - migrate(db, { migrationsFolder }); + await migrate(db, { migrationsFolder }); await seedDataAsync(db); await applyCustomMigrationsAsync(db); diff --git a/packages/db/package.json b/packages/db/package.json index 592bbbfde..8618ba318 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -47,11 +47,11 @@ "@homarr/core": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0", + "@libsql/client": "0.15.15", "@mantine/core": "^8.3.10", "@paralleldrive/cuid2": "^3.1.0", "@testcontainers/mysql": "^11.11.0", "@testcontainers/postgresql": "^11.11.0", - "better-sqlite3": "^12.5.0", "dotenv": "^17.2.3", "drizzle-kit": "^0.31.8", "drizzle-orm": "^0.45.1", @@ -64,7 +64,6 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "@types/better-sqlite3": "7.6.13", "@types/pg": "^8.16.0", "dotenv-cli": "^11.0.0", "esbuild": "^0.27.2", diff --git a/packages/db/test/db-mock.ts b/packages/db/test/db-mock.ts index 4001534a6..57c8bb581 100644 --- a/packages/db/test/db-mock.ts +++ b/packages/db/test/db-mock.ts @@ -1,15 +1,17 @@ -import Database from "better-sqlite3"; -import { drizzle } from "drizzle-orm/better-sqlite3"; -import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { createClient } from "@libsql/client/node"; +import { drizzle } from "drizzle-orm/libsql"; +import { migrate } from "drizzle-orm/libsql/migrator"; import { DB_CASING } from "@homarr/core/infrastructure/db/constants"; import * as sqliteSchema from "../schema/sqlite"; -export const createDb = (debug?: boolean) => { - const sqlite = new Database(":memory:"); +export const createDbAsync = async (debug?: boolean) => { + const sqlite = createClient({ + url: ":memory:", + }); const db = drizzle(sqlite, { schema: sqliteSchema, logger: debug, casing: DB_CASING }); - migrate(db, { + await migrate(db, { migrationsFolder: "./packages/db/migrations/sqlite", }); diff --git a/packages/db/transactions.ts b/packages/db/transactions.ts deleted file mode 100644 index 1ad35b3dd..000000000 --- a/packages/db/transactions.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { isMysql, isPostgresql } from "./collection"; -import type { HomarrDatabase, HomarrDatabaseMysql } from "./driver"; -import type { MySqlSchema } from "./schema"; -import * as schema from "./schema"; - -interface HandleTransactionInput { - handleAsync: (db: HomarrDatabaseMysql, schema: MySqlSchema) => Promise; - handleSync: (db: HomarrDatabase) => void; -} - -/** - * The below method is mostly used to handle transactions in different database drivers. - * As better-sqlite3 transactions have to be synchronous, we have to implement them in a different way. - * But it can also generally be used when dealing with different database drivers. - */ -export const handleDiffrentDbDriverOperationsAsync = async (db: HomarrDatabase, input: HandleTransactionInput) => { - if (isMysql() || isPostgresql()) { - // Schema type is always the correct one based on env variables - await input.handleAsync(db as unknown as HomarrDatabaseMysql, schema as unknown as MySqlSchema); - } else { - input.handleSync(db); - } -}; diff --git a/packages/integrations/test/aria2.spec.ts b/packages/integrations/test/aria2.spec.ts index a204d7ccf..19c6808fc 100644 --- a/packages/integrations/test/aria2.spec.ts +++ b/packages/integrations/test/aria2.spec.ts @@ -2,7 +2,7 @@ import type { StartedTestContainer } from "testcontainers"; import { GenericContainer, getContainerRuntimeClient, ImageName, Wait } from "testcontainers"; import { beforeAll, describe, expect, test, vi } from "vitest"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { Aria2Integration } from "../src"; @@ -11,7 +11,7 @@ vi.mock("@homarr/db", async (importActual) => { const actual = await importActual(); return { ...actual, - db: createDb(), + db: await createDbAsync(), }; }); vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { diff --git a/packages/integrations/test/base.spec.ts b/packages/integrations/test/base.spec.ts index 4fcce3458..96e6d9786 100644 --- a/packages/integrations/test/base.spec.ts +++ b/packages/integrations/test/base.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from "vitest"; import { ResponseError } from "@homarr/common/server"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import type { IntegrationTestingInput } from "../src/base/integration"; import { Integration } from "../src/base/integration"; @@ -12,7 +12,7 @@ vi.mock("@homarr/db", async (importActual) => { const actual = await importActual(); return { ...actual, - db: createDb(), + db: await createDbAsync(), }; }); diff --git a/packages/integrations/test/home-assistant.spec.ts b/packages/integrations/test/home-assistant.spec.ts index 608af1ce0..141205ca2 100644 --- a/packages/integrations/test/home-assistant.spec.ts +++ b/packages/integrations/test/home-assistant.spec.ts @@ -3,7 +3,7 @@ import type { StartedTestContainer } from "testcontainers"; import { GenericContainer, getContainerRuntimeClient, ImageName, Wait } from "testcontainers"; import { beforeAll, describe, expect, test, vi } from "vitest"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { HomeAssistantIntegration } from "../src"; import { TestConnectionError } from "../src/base/test-connection/test-connection-error"; @@ -13,7 +13,7 @@ vi.mock("@homarr/db", async (importActual) => { const actual = await importActual(); return { ...actual, - db: createDb(), + db: await createDbAsync(), }; }); diff --git a/packages/integrations/test/nzbget.spec.ts b/packages/integrations/test/nzbget.spec.ts index 6b485e71d..c5b33d42e 100644 --- a/packages/integrations/test/nzbget.spec.ts +++ b/packages/integrations/test/nzbget.spec.ts @@ -4,7 +4,7 @@ import type { StartedTestContainer } from "testcontainers"; import { GenericContainer, getContainerRuntimeClient, ImageName, Wait } from "testcontainers"; import { beforeAll, describe, expect, test, vi } from "vitest"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { NzbGetIntegration } from "../src"; import { TestConnectionError } from "../src/base/test-connection/test-connection-error"; @@ -14,7 +14,7 @@ vi.mock("@homarr/db", async (importActual) => { const actual = await importActual(); return { ...actual, - db: createDb(), + db: await createDbAsync(), }; }); diff --git a/packages/integrations/test/pi-hole.spec.ts b/packages/integrations/test/pi-hole.spec.ts index 78c272689..9b73bb6c8 100644 --- a/packages/integrations/test/pi-hole.spec.ts +++ b/packages/integrations/test/pi-hole.spec.ts @@ -2,7 +2,7 @@ import type { StartedTestContainer } from "testcontainers"; import { GenericContainer, Wait } from "testcontainers"; import { describe, expect, test, vi } from "vitest"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { PiHoleIntegrationV5, PiHoleIntegrationV6 } from "../src"; import type { SessionStore } from "../src/base/session-store"; @@ -13,7 +13,7 @@ vi.mock("@homarr/db", async (importActual) => { const actual = await importActual(); return { ...actual, - db: createDb(), + db: await createDbAsync(), }; }); diff --git a/packages/integrations/test/sabnzbd.spec.ts b/packages/integrations/test/sabnzbd.spec.ts index 8603f04a0..28af18389 100644 --- a/packages/integrations/test/sabnzbd.spec.ts +++ b/packages/integrations/test/sabnzbd.spec.ts @@ -3,7 +3,7 @@ import { GenericContainer, getContainerRuntimeClient, ImageName, Wait } from "te import type { StartedTestContainer } from "testcontainers"; import { beforeAll, describe, expect, test, vi } from "vitest"; -import { createDb } from "@homarr/db/test"; +import { createDbAsync } from "@homarr/db/test"; import { SabnzbdIntegration } from "../src"; import { TestConnectionError } from "../src/base/test-connection/test-connection-error"; @@ -14,7 +14,7 @@ vi.mock("@homarr/db", async (importActual) => { const actual = await importActual(); return { ...actual, - db: createDb(), + db: await createDbAsync(), }; }); diff --git a/packages/old-import/src/import/collections/board-collection.ts b/packages/old-import/src/import/collections/board-collection.ts index 65bfb37f0..436d6a0d5 100644 --- a/packages/old-import/src/import/collections/board-collection.ts +++ b/packages/old-import/src/import/collections/board-collection.ts @@ -1,6 +1,6 @@ import { createId } from "@homarr/common"; import { createLogger } from "@homarr/core/infrastructure/logs"; -import { createDbInsertCollectionForTransaction } from "@homarr/db/collection"; +import { createDbInsertCollection } from "@homarr/db/collection"; import type { BoardSize, OldmarrConfig } from "@homarr/old-schema"; import { boardSizes, getBoardSizeName } from "@homarr/old-schema"; @@ -21,14 +21,7 @@ export const createBoardInsertCollection = ( { preparedApps, preparedBoards }: Omit, "preparedIntegrations">, settings: InitialOldmarrImportSettings, ) => { - const insertCollection = createDbInsertCollectionForTransaction([ - "apps", - "boards", - "layouts", - "sections", - "items", - "itemLayouts", - ]); + const insertCollection = createDbInsertCollection(["apps", "boards", "layouts", "sections", "items", "itemLayouts"]); logger.info("Preparing boards for insert collection"); const appsMap = new Map( diff --git a/packages/old-import/src/import/collections/integration-collection.ts b/packages/old-import/src/import/collections/integration-collection.ts index 33b182040..48216fa28 100644 --- a/packages/old-import/src/import/collections/integration-collection.ts +++ b/packages/old-import/src/import/collections/integration-collection.ts @@ -1,6 +1,6 @@ import { encryptSecret } from "@homarr/common/server"; import { createLogger } from "@homarr/core/infrastructure/logs"; -import { createDbInsertCollectionForTransaction } from "@homarr/db/collection"; +import { createDbInsertCollection } from "@homarr/db/collection"; import { mapAndDecryptIntegrations } from "../../mappers/map-integration"; import type { PreparedIntegration } from "../../prepare/prepare-integrations"; @@ -11,7 +11,7 @@ export const createIntegrationInsertCollection = ( preparedIntegrations: PreparedIntegration[], encryptionToken: string | null | undefined, ) => { - const insertCollection = createDbInsertCollectionForTransaction(["integrations", "integrationSecrets"]); + const insertCollection = createDbInsertCollection(["integrations", "integrationSecrets"]); if (preparedIntegrations.length === 0) { return insertCollection; diff --git a/packages/old-import/src/import/collections/user-collection.ts b/packages/old-import/src/import/collections/user-collection.ts index 3fb9cc14b..c75311d8e 100644 --- a/packages/old-import/src/import/collections/user-collection.ts +++ b/packages/old-import/src/import/collections/user-collection.ts @@ -1,6 +1,6 @@ import { createId } from "@homarr/common"; import { createLogger } from "@homarr/core/infrastructure/logs"; -import { createDbInsertCollectionForTransaction } from "@homarr/db/collection"; +import { createDbInsertCollection } from "@homarr/db/collection"; import { credentialsAdminGroup } from "@homarr/definitions"; import { mapAndDecryptUsers } from "../../mappers/map-user"; @@ -12,12 +12,7 @@ export const createUserInsertCollection = ( importUsers: OldmarrImportUser[], encryptionToken: string | null | undefined, ) => { - const insertCollection = createDbInsertCollectionForTransaction([ - "users", - "groups", - "groupMembers", - "groupPermissions", - ]); + const insertCollection = createDbInsertCollection(["users", "groups", "groupMembers", "groupPermissions"]); if (importUsers.length === 0) { return insertCollection; diff --git a/packages/old-import/src/import/import-initial-oldmarr.ts b/packages/old-import/src/import/import-initial-oldmarr.ts index f773c1b36..ff3be803c 100644 --- a/packages/old-import/src/import/import-initial-oldmarr.ts +++ b/packages/old-import/src/import/import-initial-oldmarr.ts @@ -2,7 +2,6 @@ import type { z } from "zod/v4"; import { Stopwatch } from "@homarr/common"; import { createLogger } from "@homarr/core/infrastructure/logs"; -import { handleTransactionsAsync } from "@homarr/db"; import type { Database } from "@homarr/db"; import { analyseOldmarrImportAsync } from "../analyse/analyse-oldmarr-import"; @@ -37,21 +36,10 @@ export const importInitialOldmarrAsync = async ( logger.info("Inserting import data to database"); - await handleTransactionsAsync(db, { - async handleAsync(db) { - await db.transaction(async (transaction) => { - await boardInsertCollection.insertAllAsync(transaction); - await userInsertCollection.insertAllAsync(transaction); - await integrationInsertCollection.insertAllAsync(transaction); - }); - }, - handleSync(db) { - db.transaction((transaction) => { - boardInsertCollection.insertAll(transaction); - userInsertCollection.insertAll(transaction); - integrationInsertCollection.insertAll(transaction); - }); - }, + await db.transaction(async (transaction) => { + await boardInsertCollection.insertAllAsync(transaction); + await userInsertCollection.insertAllAsync(transaction); + await integrationInsertCollection.insertAllAsync(transaction); }); logger.info("Import successful", { duration: stopwatch.getElapsedInHumanWords() }); diff --git a/packages/old-import/src/import/import-single-oldmarr.ts b/packages/old-import/src/import/import-single-oldmarr.ts index 2ca6ed4c1..b18bf4e3f 100644 --- a/packages/old-import/src/import/import-single-oldmarr.ts +++ b/packages/old-import/src/import/import-single-oldmarr.ts @@ -1,5 +1,5 @@ -import { handleTransactionsAsync, inArray } from "@homarr/db"; import type { Database } from "@homarr/db"; +import { inArray } from "@homarr/db"; import { apps } from "@homarr/db/schema"; import type { OldmarrConfig } from "@homarr/old-schema"; @@ -31,12 +31,5 @@ export const importSingleOldmarrConfigAsync = async ( const boardInsertCollection = createBoardInsertCollection({ preparedApps, preparedBoards }, settings); - await handleTransactionsAsync(db, { - async handleAsync(db) { - await boardInsertCollection.insertAllAsync(db); - }, - handleSync(db) { - boardInsertCollection.insertAll(db); - }, - }); + await boardInsertCollection.insertAllAsync(db); };